From d885a4baa9f292fd294cab163eaa773265689b5d Mon Sep 17 00:00:00 2001 From: oliver Date: Sun, 10 Apr 2016 18:55:57 +0200 Subject: [PATCH] update Piwik to version 2.16 (fixes #91) --- .hgignore | 2 +- views/html/html.tpl | 6 +- www/analytics/CHANGELOG.md | 411 + www/analytics/CONTRIBUTING.md | 12 + www/analytics/LEGALNOTICE | 71 +- www/analytics/PRIVACY.md | 60 + www/analytics/README.md | 50 +- www/analytics/SECURITY.md | 21 + www/analytics/bower.json | 41 + www/analytics/composer.json | 101 +- www/analytics/composer.lock | 2541 +- www/analytics/config/.htaccess | 17 +- www/analytics/config/environment/dev.php | 12 + www/analytics/config/environment/test.php | 97 + www/analytics/config/environment/ui-test.php | 62 + www/analytics/config/global.ini.php | 322 +- www/analytics/config/global.php | 85 + www/analytics/config/manifest.inc.php | 7268 +++- www/analytics/console | 25 +- www/analytics/core/.htaccess | 17 +- www/analytics/core/API/ApiRenderer.php | 131 + www/analytics/core/API/CORSHandler.php | 41 + .../core/API/DataTableGenericFilter.php | 174 +- .../core/API/DataTableManipulator.php | 44 +- .../API/DataTableManipulator/Flattener.php | 44 +- .../API/DataTableManipulator/LabelFilter.php | 39 +- .../ReportTotalsCalculator.php | 189 +- .../core/API/DataTablePostProcessor.php | 436 + .../core/API/DocumentationGenerator.php | 242 +- www/analytics/core/API/Inconsistencies.php | 42 + www/analytics/core/API/Proxy.php | 173 +- www/analytics/core/API/Request.php | 274 +- www/analytics/core/API/ResponseBuilder.php | 506 +- www/analytics/core/Access.php | 212 +- .../core/Application/Environment.php | 246 + .../Application/EnvironmentManipulator.php | 59 + .../Kernel/EnvironmentValidator.php | 79 + .../Kernel/GlobalSettingsProvider.php | 111 + .../core/Application/Kernel/PluginList.php | 120 + www/analytics/core/Archive.php | 421 +- .../core/Archive/ArchiveInvalidator.php | 317 + .../ArchiveInvalidator/InvalidationResult.php | 56 + www/analytics/core/Archive/ArchivePurger.php | 272 + www/analytics/core/Archive/Chunk.php | 144 + www/analytics/core/Archive/DataCollection.php | 49 +- .../core/Archive/DataTableFactory.php | 243 +- www/analytics/core/Archive/Parameters.php | 18 +- www/analytics/core/ArchiveProcessor.php | 260 +- .../core/ArchiveProcessor/Loader.php | 57 +- .../core/ArchiveProcessor/Parameters.php | 16 +- .../core/ArchiveProcessor/PluginsArchiver.php | 74 +- www/analytics/core/ArchiveProcessor/Rules.php | 156 +- www/analytics/core/Archiver/Request.php | 48 + www/analytics/core/AssetManager.php | 64 +- www/analytics/core/AssetManager/UIAsset.php | 2 +- .../AssetManager/UIAsset/InMemoryUIAsset.php | 3 +- .../AssetManager/UIAsset/OnDiskUIAsset.php | 23 +- .../core/AssetManager/UIAssetCacheBuster.php | 2 +- .../core/AssetManager/UIAssetCatalog.php | 21 +- .../AssetManager/UIAssetCatalogSorter.php | 9 +- .../core/AssetManager/UIAssetFetcher.php | 8 +- .../UIAssetFetcher/JScriptUIAssetFetcher.php | 31 +- .../UIAssetFetcher/StaticUIAssetFetcher.php | 5 +- .../StylesheetUIAssetFetcher.php | 35 +- .../core/AssetManager/UIAssetMerger.php | 38 +- .../UIAssetMerger/JScriptUIAssetMerger.php | 17 +- .../UIAssetMerger/StylesheetUIAssetMerger.php | 99 +- .../core/AssetManager/UIAssetMinifier.php | 12 +- www/analytics/core/Auth.php | 114 +- www/analytics/core/BaseFactory.php | 60 + www/analytics/core/Cache.php | 117 + www/analytics/core/CacheFile.php | 206 - www/analytics/core/CacheId.php | 29 + www/analytics/core/CliMulti.php | 247 +- www/analytics/core/CliMulti/CliPhp.php | 101 + www/analytics/core/CliMulti/Output.php | 25 +- www/analytics/core/CliMulti/Process.php | 84 +- .../core/CliMulti/RequestCommand.php | 64 +- www/analytics/core/Columns/Dimension.php | 242 + www/analytics/core/Columns/Updater.php | 372 + www/analytics/core/Common.php | 557 +- www/analytics/core/Composer/ScriptHandler.php | 47 + .../core/Concurrency/DistributedList.php | 170 + www/analytics/core/Config.php | 561 +- .../core/Config/ConfigNotFoundException.php | 16 + www/analytics/core/Config/IniFileChain.php | 474 + www/analytics/core/Console.php | 162 +- .../ContainerDoesNotExistException.php | 18 + .../core/Container/ContainerFactory.php | 151 + .../Container/IniConfigDefinitionSource.php | 95 + .../core/Container/StaticContainer.php | 87 + www/analytics/core/Cookie.php | 34 +- www/analytics/core/CronArchive.php | 1740 +- .../core/CronArchive/FixedSiteIds.php | 5 +- .../SegmentArchivingRequestUrlProvider.php | 208 + .../core/CronArchive/SharedSiteIds.php | 24 +- .../SitesToReprocessDistributedList.php | 40 + www/analytics/core/DataAccess/Actions.php | 34 + .../core/DataAccess/ArchiveSelector.php | 281 +- .../core/DataAccess/ArchiveTableCreator.php | 72 +- .../core/DataAccess/ArchiveTableDao.php | 89 + .../core/DataAccess/ArchiveWriter.php | 209 +- .../core/DataAccess/LogAggregator.php | 221 +- .../core/DataAccess/LogQueryBuilder.php | 391 + www/analytics/core/DataAccess/Model.php | 358 + www/analytics/core/DataAccess/RawLogDao.php | 402 + .../core/DataAccess/TableMetadata.php | 56 + www/analytics/core/DataArray.php | 115 +- www/analytics/core/DataFiles/Countries.php | 326 - www/analytics/core/DataFiles/Currencies.php | 186 - .../core/DataFiles/LanguageToCountry.php | 63 - www/analytics/core/DataFiles/Languages.php | 203 - www/analytics/core/DataFiles/Providers.php | 2 +- .../core/DataFiles/SearchEngines.php | 1033 - www/analytics/core/DataFiles/Socials.php | 226 - www/analytics/core/DataFiles/cacert.pem | 3981 ++ www/analytics/core/DataTable.php | 629 +- www/analytics/core/DataTable/BaseFilter.php | 17 +- www/analytics/core/DataTable/Bridges.php | 12 +- .../core/DataTable/DataTableInterface.php | 2 +- .../Filter/AddColumnsProcessedMetrics.php | 122 +- .../Filter/AddColumnsProcessedMetricsGoal.php | 191 +- .../DataTable/Filter/AddSegmentByLabel.php | 99 + .../Filter/AddSegmentByLabelMapping.php | 63 + .../Filter/AddSegmentBySegmentValue.php | 78 + .../core/DataTable/Filter/AddSegmentValue.php | 32 + .../core/DataTable/Filter/AddSummaryRow.php | 8 +- .../DataTable/Filter/BeautifyRangeLabels.php | 10 +- .../Filter/BeautifyTimeRangeLabels.php | 10 +- .../Filter/CalculateEvolutionFilter.php | 22 +- .../Filter/ColumnCallbackAddColumn.php | 35 +- .../ColumnCallbackAddColumnPercentage.php | 6 +- .../ColumnCallbackAddColumnQuotient.php | 15 +- .../Filter/ColumnCallbackAddMetadata.php | 28 +- .../Filter/ColumnCallbackDeleteMetadata.php | 55 + .../Filter/ColumnCallbackDeleteRow.php | 9 +- .../Filter/ColumnCallbackReplace.php | 27 +- .../core/DataTable/Filter/ColumnDelete.php | 31 +- .../DataTable/Filter/ExcludeLowPopulation.php | 49 +- .../core/DataTable/Filter/GroupBy.php | 35 +- www/analytics/core/DataTable/Filter/Limit.php | 10 +- .../Filter/MetadataCallbackAddMetadata.php | 18 +- .../Filter/MetadataCallbackReplace.php | 8 +- .../core/DataTable/Filter/Pattern.php | 43 +- .../DataTable/Filter/PatternRecursive.php | 23 +- .../DataTable/Filter/PivotByDimension.php | 550 + .../core/DataTable/Filter/PrependSegment.php | 34 + .../Filter/PrependValueToMetadata.php | 65 + .../core/DataTable/Filter/RangeCheck.php | 25 +- .../DataTable/Filter/ReplaceColumnNames.php | 21 +- .../Filter/ReplaceSummaryRowLabel.php | 33 +- .../core/DataTable/Filter/SafeDecodeLabel.php | 4 +- www/analytics/core/DataTable/Filter/Sort.php | 197 +- .../core/DataTable/Filter/Truncate.php | 42 +- www/analytics/core/DataTable/Manager.php | 55 +- www/analytics/core/DataTable/Map.php | 99 +- www/analytics/core/DataTable/Renderer.php | 88 +- .../core/DataTable/Renderer/Console.php | 47 +- www/analytics/core/DataTable/Renderer/Csv.php | 228 +- .../core/DataTable/Renderer/Html.php | 41 +- .../core/DataTable/Renderer/Json.php | 53 +- www/analytics/core/DataTable/Renderer/Php.php | 34 +- www/analytics/core/DataTable/Renderer/Rss.php | 27 +- www/analytics/core/DataTable/Renderer/Tsv.php | 7 +- www/analytics/core/DataTable/Renderer/Xml.php | 101 +- www/analytics/core/DataTable/Row.php | 418 +- .../DataTable/Row/DataTableSummaryRow.php | 31 +- www/analytics/core/DataTable/Simple.php | 6 +- .../core/DataTable/TableNotFoundException.php | 4 +- www/analytics/core/Date.php | 369 +- www/analytics/core/Db.php | 286 +- www/analytics/core/Db/Adapter.php | 20 +- www/analytics/core/Db/Adapter/Mysqli.php | 26 +- www/analytics/core/Db/Adapter/Pdo/Mssql.php | 14 +- www/analytics/core/Db/Adapter/Pdo/Mysql.php | 41 +- www/analytics/core/Db/Adapter/Pdo/Pgsql.php | 7 +- www/analytics/core/Db/AdapterInterface.php | 2 +- www/analytics/core/Db/BatchInsert.php | 98 +- www/analytics/core/Db/Schema.php | 86 +- www/analytics/core/Db/Schema/Mysql.php | 579 +- www/analytics/core/Db/SchemaInterface.php | 18 +- www/analytics/core/Db/Settings.php | 44 + www/analytics/core/DbHelper.php | 31 +- www/analytics/core/Development.php | 196 + www/analytics/core/DeviceDetectorCache.php | 95 + www/analytics/core/DeviceDetectorFactory.php | 37 + www/analytics/core/Error.php | 222 - www/analytics/core/ErrorHandler.php | 135 + www/analytics/core/EventDispatcher.php | 96 +- .../AuthenticationFailedException.php | 13 + ...baseSchemaIsNewerThanCodebaseException.php | 13 + .../core/Exception/ErrorException.php | 22 + www/analytics/core/Exception/Exception.php | 30 + .../InvalidRequestParameterException.php | 13 + .../MissingFilePermissionException.php | 13 + .../core/Exception/NoPrivilegesException.php | 13 + .../Exception/NoWebsiteFoundException.php | 13 + .../UnexpectedWebsiteFoundException.php | 13 + www/analytics/core/ExceptionHandler.php | 135 +- www/analytics/core/Filechecks.php | 68 +- www/analytics/core/Filesystem.php | 299 +- www/analytics/core/FrontController.php | 494 +- www/analytics/core/Http.php | 213 +- .../core/Http/ControllerResolver.php | 141 + www/analytics/core/Http/Router.php | 39 + www/analytics/core/IP.php | 352 +- .../Data/Provider/CurrencyDataProvider.php | 33 + .../Data/Provider/DateTimeFormatProvider.php | 83 + .../Data/Provider/LanguageDataProvider.php | 50 + .../Intl/Data/Provider/RegionDataProvider.php | 57 + .../core/Intl/Data/Resources/continents.php | 24 + .../Intl/Data/Resources/countries-extra.php | 53 + .../core/Intl/Data/Resources/countries.php | 272 + .../core/Intl/Data/Resources/currencies.php | 183 + .../Data/Resources/languages-to-countries.php | 60 + .../core/Intl/Data/Resources/languages.php | 201 + www/analytics/core/Intl/Locale.php | 42 + www/analytics/core/Loader.php | 126 - www/analytics/core/Log.php | 588 +- www/analytics/core/LogDeleter.php | 110 + www/analytics/core/Mail.php | 138 +- www/analytics/core/Measurable/Measurable.php | 32 + .../core/Measurable/MeasurableSetting.php | 70 + .../core/Measurable/MeasurableSettings.php | 103 + .../core/Measurable/Settings/Storage.php | 104 + www/analytics/core/Measurable/Type.php | 62 + .../core/Measurable/Type/TypeManager.php | 39 + www/analytics/core/Menu/Group.php | 31 + www/analytics/core/Menu/MenuAbstract.php | 176 +- www/analytics/core/Menu/MenuAdmin.php | 144 +- www/analytics/core/Menu/MenuMain.php | 84 +- www/analytics/core/Menu/MenuReporting.php | 143 + www/analytics/core/Menu/MenuTop.php | 79 +- www/analytics/core/Menu/MenuUser.php | 91 + www/analytics/core/Metrics.php | 135 +- www/analytics/core/Metrics/Formatter.php | 286 + www/analytics/core/Metrics/Formatter/Html.php | 43 + www/analytics/core/MetricsFormatter.php | 230 +- www/analytics/core/Nonce.php | 34 +- www/analytics/core/Notification.php | 52 +- www/analytics/core/Notification/Manager.php | 42 +- www/analytics/core/NumberFormatter.php | 325 + www/analytics/core/Option.php | 81 +- www/analytics/core/Period.php | 286 +- www/analytics/core/Period/Day.php | 25 +- www/analytics/core/Period/Factory.php | 130 + www/analytics/core/Period/Month.php | 70 +- www/analytics/core/Period/PeriodValidator.php | 52 + www/analytics/core/Period/Range.php | 137 +- www/analytics/core/Period/Week.php | 55 +- www/analytics/core/Period/Year.php | 21 +- www/analytics/core/Piwik.php | 352 +- www/analytics/core/PiwikPro/Advertising.php | 141 + www/analytics/core/Plugin.php | 270 +- www/analytics/core/Plugin/API.php | 75 +- .../core/Plugin/AggregatedMetric.php | 21 + www/analytics/core/Plugin/Archiver.php | 36 +- .../core/Plugin/ComponentFactory.php | 131 + www/analytics/core/Plugin/ConsoleCommand.php | 21 +- www/analytics/core/Plugin/Controller.php | 526 +- www/analytics/core/Plugin/ControllerAdmin.php | 174 +- www/analytics/core/Plugin/Dependency.php | 11 +- .../core/Plugin/Dimension/ActionDimension.php | 254 + .../Plugin/Dimension/ConversionDimension.php | 247 + .../Dimension/DimensionMetadataProvider.php | 107 + .../core/Plugin/Dimension/VisitDimension.php | 396 + www/analytics/core/Plugin/Manager.php | 721 +- www/analytics/core/Plugin/Menu.php | 271 + www/analytics/core/Plugin/MetadataLoader.php | 20 +- www/analytics/core/Plugin/Metric.php | 189 + www/analytics/core/Plugin/PluginException.php | 35 + www/analytics/core/Plugin/ProcessedMetric.php | 70 + www/analytics/core/Plugin/ReleaseChannels.php | 103 + www/analytics/core/Plugin/Report.php | 1001 + .../core/Plugin/RequestProcessors.php | 27 + www/analytics/core/Plugin/Segment.php | 341 + www/analytics/core/Plugin/Settings.php | 284 +- www/analytics/core/Plugin/Tasks.php | 154 + www/analytics/core/Plugin/ViewDataTable.php | 200 +- www/analytics/core/Plugin/Visualization.php | 316 +- www/analytics/core/Plugin/Widgets.php | 198 + .../core/PluginDeactivatedException.php | 20 + www/analytics/core/Profiler.php | 156 +- www/analytics/core/ProxyHeaders.php | 7 +- www/analytics/core/ProxyHttp.php | 231 +- www/analytics/core/QuickForm2.php | 14 +- www/analytics/core/RankingQuery.php | 12 +- www/analytics/core/Registry.php | 36 +- www/analytics/core/ReportRenderer.php | 108 +- www/analytics/core/ReportRenderer/Csv.php | 29 +- www/analytics/core/ReportRenderer/Html.php | 64 +- www/analytics/core/ReportRenderer/Pdf.php | 63 +- www/analytics/core/ScheduledTask.php | 191 +- www/analytics/core/ScheduledTaskTimetable.php | 121 - www/analytics/core/ScheduledTime.php | 225 - .../Schedule}/Daily.php | 12 +- .../Schedule}/Hourly.php | 15 +- .../Schedule}/Monthly.php | 21 +- .../core/Scheduler/Schedule/Schedule.php | 224 + .../Schedule}/Weekly.php | 15 +- www/analytics/core/Scheduler/Scheduler.php | 241 + www/analytics/core/Scheduler/Task.php | 200 + www/analytics/core/Scheduler/TaskLoader.php | 39 + www/analytics/core/Scheduler/Timetable.php | 133 + www/analytics/core/Segment.php | 417 +- .../core/Segment/SegmentExpression.php | 452 + www/analytics/core/SegmentExpression.php | 378 - www/analytics/core/Sequence.php | 127 + www/analytics/core/Session.php | 34 +- .../core/Session/SaveHandler/DbTable.php | 10 +- .../core/Session/SessionNamespace.php | 5 +- www/analytics/core/Settings/Manager.php | 82 +- www/analytics/core/Settings/Setting.php | 157 +- www/analytics/core/Settings/Storage.php | 148 + .../core/Settings/Storage/Factory.php | 28 + .../core/Settings/Storage/StaticStorage.php | 34 + .../core/Settings/StorageInterface.php | 21 +- www/analytics/core/Settings/SystemSetting.php | 81 +- www/analytics/core/Settings/UserSetting.php | 44 +- www/analytics/core/SettingsPiwik.php | 261 +- www/analytics/core/SettingsServer.php | 33 +- www/analytics/core/Singleton.php | 25 +- www/analytics/core/Site.php | 215 +- www/analytics/core/TCPDF.php | 17 +- www/analytics/core/TaskScheduler.php | 170 +- www/analytics/core/Theme.php | 18 +- www/analytics/core/Timer.php | 9 +- www/analytics/core/Tracker.php | 870 +- www/analytics/core/Tracker/Action.php | 340 +- www/analytics/core/Tracker/ActionClickUrl.php | 63 - www/analytics/core/Tracker/ActionEvent.php | 78 - www/analytics/core/Tracker/ActionPageview.php | 57 +- www/analytics/core/Tracker/Cache.php | 149 +- www/analytics/core/Tracker/Db.php | 80 +- www/analytics/core/Tracker/Db/DbException.php | 2 +- www/analytics/core/Tracker/Db/Mysqli.php | 80 +- www/analytics/core/Tracker/Db/Pdo/Mysql.php | 85 +- www/analytics/core/Tracker/Db/Pdo/Pgsql.php | 2 +- www/analytics/core/Tracker/GoalManager.php | 654 +- www/analytics/core/Tracker/Handler.php | 117 + .../core/Tracker/Handler/Factory.php | 42 + www/analytics/core/Tracker/IgnoreCookie.php | 10 +- www/analytics/core/Tracker/Model.php | 464 + www/analytics/core/Tracker/PageUrl.php | 106 +- www/analytics/core/Tracker/Referrer.php | 301 - www/analytics/core/Tracker/Request.php | 469 +- .../core/Tracker/RequestProcessor.php | 174 + www/analytics/core/Tracker/RequestSet.php | 255 + www/analytics/core/Tracker/Response.php | 182 + .../core/Tracker/ScheduledTasksRunner.php | 86 + www/analytics/core/Tracker/Settings.php | 126 + .../core/Tracker/SettingsStorage.php | 58 + www/analytics/core/Tracker/TableLogAction.php | 200 +- .../core/Tracker/TableLogAction/Cache.php | 160 + .../core/Tracker/TrackerCodeGenerator.php | 199 + www/analytics/core/Tracker/TrackerConfig.php | 39 + www/analytics/core/Tracker/Visit.php | 1040 +- www/analytics/core/Tracker/Visit/Factory.php | 48 + .../core/Tracker/Visit/ReferrerSpamFilter.php | 87 + .../core/Tracker/Visit/VisitProperties.php | 73 + www/analytics/core/Tracker/VisitExcluded.php | 162 +- www/analytics/core/Tracker/VisitInterface.php | 8 +- www/analytics/core/Tracker/Visitor.php | 60 + .../core/Tracker/VisitorNotFoundInDb.php | 3 +- .../core/Tracker/VisitorRecognizer.php | 269 + www/analytics/core/Translate.php | 166 +- .../Translate/Validate/CoreTranslations.php | 101 - .../Translation/Loader/DevelopmentLoader.php | 72 + .../Translation/Loader/JsonFileLoader.php | 64 + .../core/Translation/Loader/LoaderCache.php | 65 + .../Translation/Loader/LoaderInterface.php | 23 + .../core/Translation/Transifex/API.php | 148 + www/analytics/core/Translation/Translator.php | 271 + www/analytics/core/Twig.php | 217 +- www/analytics/core/Unzip.php | 24 +- www/analytics/core/Unzip/Tar.php | 84 - .../core/Unzip/UncompressInterface.php | 39 - www/analytics/core/Unzip/ZipArchive.php | 132 - www/analytics/core/UpdateCheck.php | 56 +- .../core/UpdateCheck/ReleaseChannel.php | 73 + www/analytics/core/Updater.php | 515 +- www/analytics/core/Updater/UpdateObserver.php | 114 + www/analytics/core/Updates.php | 89 +- www/analytics/core/Updates/0.2.10.php | 31 +- www/analytics/core/Updates/0.2.12.php | 12 +- www/analytics/core/Updates/0.2.13.php | 10 +- www/analytics/core/Updates/0.2.24.php | 16 +- www/analytics/core/Updates/0.2.27.php | 21 +- www/analytics/core/Updates/0.2.32.php | 8 +- www/analytics/core/Updates/0.2.33.php | 8 +- www/analytics/core/Updates/0.2.34.php | 28 - www/analytics/core/Updates/0.2.35.php | 8 +- www/analytics/core/Updates/0.2.37.php | 8 +- www/analytics/core/Updates/0.4.1.php | 8 +- www/analytics/core/Updates/0.4.2.php | 14 +- www/analytics/core/Updates/0.4.4.php | 5 +- www/analytics/core/Updates/0.4.php | 16 +- www/analytics/core/Updates/0.5.4.php | 8 +- www/analytics/core/Updates/0.5.5.php | 17 +- www/analytics/core/Updates/0.5.php | 26 +- www/analytics/core/Updates/0.6-rc1.php | 38 +- www/analytics/core/Updates/0.6.2.php | 47 - www/analytics/core/Updates/0.6.3.php | 12 +- www/analytics/core/Updates/0.7.php | 8 +- www/analytics/core/Updates/0.9.1.php | 14 +- www/analytics/core/Updates/1.1.php | 5 +- www/analytics/core/Updates/1.10-b4.php | 7 +- www/analytics/core/Updates/1.10.1.php | 7 +- www/analytics/core/Updates/1.10.2-b1.php | 8 +- www/analytics/core/Updates/1.10.2-b2.php | 8 +- www/analytics/core/Updates/1.11-b1.php | 7 +- www/analytics/core/Updates/1.12-b1.php | 11 +- www/analytics/core/Updates/1.12-b15.php | 5 +- www/analytics/core/Updates/1.12-b16.php | 8 +- www/analytics/core/Updates/1.2-rc1.php | 62 +- www/analytics/core/Updates/1.2-rc2.php | 6 +- www/analytics/core/Updates/1.2.3.php | 13 +- www/analytics/core/Updates/1.2.5-rc1.php | 13 +- www/analytics/core/Updates/1.2.5-rc7.php | 12 +- www/analytics/core/Updates/1.4-rc1.php | 10 +- www/analytics/core/Updates/1.4-rc2.php | 14 +- www/analytics/core/Updates/1.5-b1.php | 31 +- www/analytics/core/Updates/1.5-b2.php | 8 +- www/analytics/core/Updates/1.5-b3.php | 8 +- www/analytics/core/Updates/1.5-b4.php | 10 +- www/analytics/core/Updates/1.5-b5.php | 10 +- www/analytics/core/Updates/1.5-rc6.php | 6 +- www/analytics/core/Updates/1.6-b1.php | 10 +- www/analytics/core/Updates/1.6-rc1.php | 6 +- www/analytics/core/Updates/1.7-b1.php | 10 +- www/analytics/core/Updates/1.7.2-rc5.php | 8 +- www/analytics/core/Updates/1.7.2-rc7.php | 12 +- www/analytics/core/Updates/1.8.3-b1.php | 16 +- www/analytics/core/Updates/1.8.4-b1.php | 12 +- www/analytics/core/Updates/1.9-b16.php | 12 +- www/analytics/core/Updates/1.9-b19.php | 10 +- www/analytics/core/Updates/1.9-b9.php | 24 +- www/analytics/core/Updates/1.9.1-b2.php | 8 +- www/analytics/core/Updates/1.9.3-b10.php | 7 +- www/analytics/core/Updates/1.9.3-b3.php | 5 +- www/analytics/core/Updates/1.9.3-b8.php | 8 +- www/analytics/core/Updates/2.0-a12.php | 8 +- www/analytics/core/Updates/2.0-a13.php | 10 +- www/analytics/core/Updates/2.0-a17.php | 11 +- www/analytics/core/Updates/2.0-a7.php | 8 +- www/analytics/core/Updates/2.0-b10.php | 5 +- www/analytics/core/Updates/2.0-b13.php | 10 +- www/analytics/core/Updates/2.0-b3.php | 10 +- www/analytics/core/Updates/2.0-b9.php | 8 +- www/analytics/core/Updates/2.0-rc1.php | 7 +- www/analytics/core/Updates/2.0.3-b7.php | 21 +- www/analytics/core/Updates/2.0.4-b5.php | 17 +- www/analytics/core/Updates/2.0.4-b7.php | 14 +- www/analytics/core/Updates/2.0.4-b8.php | 13 +- www/analytics/core/Updates/2.1.1-b11.php | 31 +- www/analytics/core/Updates/2.10.0-b10.php | 47 + www/analytics/core/Updates/2.10.0-b4.php | 30 + www/analytics/core/Updates/2.10.0-b5.php | 208 + www/analytics/core/Updates/2.10.0-b7.php | 41 + www/analytics/core/Updates/2.10.0-b8.php | 26 + www/analytics/core/Updates/2.11.0-b2.php | 65 + www/analytics/core/Updates/2.11.0-b4.php | 47 + www/analytics/core/Updates/2.11.0-b5.php | 24 + www/analytics/core/Updates/2.11.1-b4.php | 40 + www/analytics/core/Updates/2.13.0-b3.php | 26 + www/analytics/core/Updates/2.13.1.php | 43 + www/analytics/core/Updates/2.14.0-b1.php | 43 + www/analytics/core/Updates/2.14.0-b2.php | 43 + www/analytics/core/Updates/2.14.2.php | 124 + www/analytics/core/Updates/2.15.0-b12.php | 44 + www/analytics/core/Updates/2.15.0-b16.php | 45 + www/analytics/core/Updates/2.15.0-b17.php | 44 + www/analytics/core/Updates/2.15.0-b20.php | 42 + www/analytics/core/Updates/2.15.0-b3.php | 32 + www/analytics/core/Updates/2.15.0-b4.php | 25 + www/analytics/core/Updates/2.15.0.php | 25 + www/analytics/core/Updates/2.16.0-rc2.php | 28 + www/analytics/core/Updates/2.2.0-b15.php | 7 +- www/analytics/core/Updates/2.2.3-b6.php | 25 + www/analytics/core/Updates/2.3.0-rc2.php | 25 + www/analytics/core/Updates/2.4.0-b1.php | 29 + www/analytics/core/Updates/2.4.0-b2.php | 27 + www/analytics/core/Updates/2.4.0-b3.php | 35 + www/analytics/core/Updates/2.4.0-b4.php | 35 + www/analytics/core/Updates/2.4.0-b6.php | 25 + www/analytics/core/Updates/2.4.0-b8.php | 29 + www/analytics/core/Updates/2.5.0-b1.php | 37 + www/analytics/core/Updates/2.5.0-rc2.php | 69 + www/analytics/core/Updates/2.5.0-rc4.php | 28 + www/analytics/core/Updates/2.6.0-b1.php | 31 + www/analytics/core/Updates/2.7.0-b2.php | 28 + www/analytics/core/Updates/2.7.0-b4.php | 33 + www/analytics/core/Updates/2.9.0-b1.php | 89 + www/analytics/core/Updates/2.9.0-b7.php | 90 + www/analytics/core/Url.php | 366 +- www/analytics/core/UrlHelper.php | 267 +- www/analytics/core/Version.php | 20 +- www/analytics/core/View.php | 127 +- www/analytics/core/View/OneClickDone.php | 16 +- www/analytics/core/View/RenderTokenParser.php | 13 +- .../core/View/ReportsByDimension.php | 12 +- www/analytics/core/View/UIControl.php | 15 +- www/analytics/core/View/ViewInterface.php | 4 +- www/analytics/core/ViewDataTable/Config.php | 183 +- www/analytics/core/ViewDataTable/Factory.php | 177 +- www/analytics/core/ViewDataTable/Manager.php | 285 +- www/analytics/core/ViewDataTable/Request.php | 26 +- .../core/ViewDataTable/RequestConfig.php | 135 +- .../core/Visualization/Sparkline.php | 10 +- www/analytics/core/WidgetsList.php | 152 +- www/analytics/core/bootstrap.php | 57 + www/analytics/core/dispatch.php | 31 +- www/analytics/core/testMinimumPhpVersion.php | 131 +- www/analytics/favicon.ico | 0 www/analytics/index.php | 28 +- www/analytics/js/.htaccess | 21 +- www/analytics/js/LICENSE.txt | 46 +- www/analytics/js/README.md | 21 +- www/analytics/js/index.php | 33 +- www/analytics/js/piwik.js | 4700 ++- www/analytics/js/tracker.php | 56 + www/analytics/lang/.htaccess | 17 +- www/analytics/lang/README.md | 4 +- www/analytics/lang/am.json | 751 +- www/analytics/lang/ar.json | 1339 +- www/analytics/lang/be.json | 1164 +- www/analytics/lang/bg.json | 1906 +- www/analytics/lang/bn.json | 533 +- www/analytics/lang/bs.json | 667 +- www/analytics/lang/ca.json | 1692 +- www/analytics/lang/cs.json | 1626 +- www/analytics/lang/cy.json | 493 +- www/analytics/lang/da.json | 2023 +- www/analytics/lang/de.json | 2149 +- www/analytics/lang/dev.json | 5 + www/analytics/lang/el.json | 2089 +- www/analytics/lang/en.json | 2853 +- www/analytics/lang/es.json | 2304 +- www/analytics/lang/et.json | 1459 +- www/analytics/lang/eu.json | 826 +- www/analytics/lang/fa.json | 1812 +- www/analytics/lang/fi.json | 1986 +- www/analytics/lang/fr.json | 2042 +- www/analytics/lang/gl.json | 666 +- www/analytics/lang/he.json | 993 +- www/analytics/lang/hi.json | 1690 +- www/analytics/lang/hr.json | 765 +- www/analytics/lang/hu.json | 1192 +- www/analytics/lang/id.json | 1847 +- www/analytics/lang/is.json | 831 +- www/analytics/lang/it.json | 2206 +- www/analytics/lang/ja.json | 1724 +- www/analytics/lang/ka.json | 1051 +- www/analytics/lang/ko.json | 1784 +- www/analytics/lang/lt.json | 1144 +- www/analytics/lang/lv.json | 1048 +- www/analytics/lang/nb.json | 1573 +- www/analytics/lang/nl.json | 2005 +- www/analytics/lang/nn.json | 1066 +- www/analytics/lang/pl.json | 1495 +- www/analytics/lang/pt-br.json | 2052 +- www/analytics/lang/pt.json | 1329 +- www/analytics/lang/ro.json | 1510 +- www/analytics/lang/ru.json | 1942 +- www/analytics/lang/sk.json | 1223 +- www/analytics/lang/sl.json | 1235 +- www/analytics/lang/sq.json | 1557 +- www/analytics/lang/sr.json | 2003 +- www/analytics/lang/sv.json | 2062 +- www/analytics/lang/ta.json | 947 +- www/analytics/lang/te.json | 742 +- www/analytics/lang/th.json | 1260 +- www/analytics/lang/tl.json | 391 + www/analytics/lang/tr.json | 1189 +- www/analytics/lang/uk.json | 1088 +- www/analytics/lang/vi.json | 1949 +- www/analytics/lang/zh-cn.json | 1903 +- www/analytics/lang/zh-tw.json | 1118 +- www/analytics/libs/.htaccess | 39 +- www/analytics/libs/Archive_Tar/Tar.php | 1993 - www/analytics/libs/MaxMindGeoIP/geoip.inc | 2417 +- www/analytics/libs/MaxMindGeoIP/geoipcity.inc | 388 +- .../libs/MaxMindGeoIP/geoipregionvars.php | 8633 ++-- www/analytics/libs/PEAR/Exception.php | 389 - .../libs/PEAR/FixPHP5PEARWarnings.php | 7 - www/analytics/libs/PEAR5.php | 33 - www/analytics/libs/PclZip/lgpl-2.1.txt | 502 - www/analytics/libs/PclZip/pclzip.lib.php | 5694 --- www/analytics/libs/PiwikTracker/LICENSE.txt | 29 - .../libs/PiwikTracker/PiwikTracker.php | 1538 +- www/analytics/libs/README.md | 9 +- www/analytics/libs/UserAgentParser/README.md | 23 - .../libs/UserAgentParser/UserAgentParser.php | 725 - .../UserAgentParser/UserAgentParser.test.php | 56 - www/analytics/libs/Zend/Session.php | 4 +- www/analytics/libs/Zend/Session/Exception.php | 4 +- www/analytics/libs/Zend/Validate/Hostname.php | 136 +- .../libs/Zend/Validate/StringLength.php | 18 +- www/analytics/libs/angularjs/LICENSE | 21 - .../libs/angularjs/angular-animate.min.js | 26 - .../libs/angularjs/angular-cookies.min.js | 8 - www/analytics/libs/angularjs/angular-csp.css | 18 - .../libs/angularjs/angular-loader.js | 410 - .../libs/angularjs/angular-loader.min.js | 9 - .../libs/angularjs/angular-resource.js | 596 - .../libs/angularjs/angular-resource.min.js | 13 - www/analytics/libs/angularjs/angular-route.js | 921 - .../libs/angularjs/angular-route.min.js | 14 - .../libs/angularjs/angular-sanitize.min.js | 13 - .../libs/angularjs/angular-scenario.js | 32880 ---------------- www/analytics/libs/angularjs/angular-touch.js | 563 - .../libs/angularjs/angular-touch.min.js | 13 - www/analytics/libs/angularjs/angular.min.js | 203 - www/analytics/libs/angularjs/errors.json | 1 - www/analytics/libs/angularjs/version.json | 1 - www/analytics/libs/angularjs/version.txt | 1 - .../angular-animate/README.md | 77 + .../angular-animate}/angular-animate.js | 727 +- .../angular-animate/angular-animate.min.js | 28 + .../angular-animate.min.js.map | 8 + .../angular-animate/bower.json | 9 + .../angular-animate/package.json | 26 + .../angular-cookies/README.md | 77 + .../angular-cookies}/angular-cookies.js | 74 +- .../angular-cookies/angular-cookies.min.js | 8 + .../angular-cookies.min.js.map | 8 + .../angular-cookies/bower.json | 9 + .../angular-cookies/package.json | 26 + .../bower_components/angular-mocks/README.md | 57 + .../angular-mocks}/angular-mocks.js | 441 +- .../bower_components/angular-mocks/bower.json | 9 + .../angular-mocks/package.json | 27 + .../angular-sanitize/README.md | 77 + .../angular-sanitize}/angular-sanitize.js | 126 +- .../angular-sanitize/angular-sanitize.min.js | 15 + .../angular-sanitize.min.js.map | 8 + .../angular-sanitize/bower.json | 9 + .../angular-sanitize/package.json | 26 + .../libs/bower_components/angular/README.md | 67 + .../bower_components/angular/angular-csp.css | 24 + .../angular}/angular.js | 7276 ++-- .../bower_components/angular/angular.min.js | 217 + .../angular/angular.min.js.gzip | Bin 0 -> 40123 bytes .../angular/angular.min.js.map | 8 + .../libs/bower_components/angular/bower.json | 8 + .../bower_components/angular/package.json | 25 + .../libs/bower_components/chroma-js/LICENSE | 28 + .../bower_components/chroma-js/LICENSE-colors | 22 + .../libs/bower_components/chroma-js/Makefile | 24 + .../bower_components/chroma-js/bower.json | 34 + .../libs/bower_components/chroma-js/chroma.js | 1863 + .../bower_components/chroma-js/chroma.min.js | 21 + .../bower_components/chroma-js/package.json | 37 + .../libs/bower_components/chroma-js/readme.md | 81 + .../bower_components/html5shiv/Gruntfile.js | 61 + .../bower_components/html5shiv/bower.json | 15 + .../html5shiv/dist/html5shiv-printshiv.js | 520 + .../html5shiv/dist/html5shiv-printshiv.min.js | 4 + .../html5shiv/dist/html5shiv.js | 322 + .../html5shiv/dist/html5shiv.min.js | 4 + .../bower_components/html5shiv/package.json | 16 + .../libs/bower_components/html5shiv/readme.md | 152 + .../jQuery.dotdotdot/bower.json | 29 + .../src/js/jquery.dotdotdot.js | 666 + .../src/js/jquery.dotdotdot.min.js | 13 + .../jScrollPane/GPL-LICENSE.txt | 278 + .../jScrollPane/MIT-LICENSE.txt | 19 + .../bower_components/jScrollPane/README.md | 6 + .../bower_components/jScrollPane/ajax.html | 151 + .../jScrollPane/ajax_content.html | 73 + .../bower_components/jScrollPane/anchors.html | 140 + .../bower_components/jScrollPane/api.html | 190 + .../jScrollPane/arrow_hover.html | 208 + .../jScrollPane/arrow_positions.html | 634 + .../bower_components/jScrollPane/arrows.html | 203 + .../jScrollPane/auto_reinitialise.html | 98 + .../bower_components/jScrollPane/basic.html | 204 + .../bower_components/jScrollPane/caps.html | 236 + .../jScrollPane/changelog.html | 66 + .../bower_components/jScrollPane/destroy.html | 237 + .../jScrollPane/drag_size.html | 206 + .../jScrollPane/dynamic_content.html | 95 + .../jScrollPane/dynamic_height.html | 211 + .../jScrollPane/dynamic_width.html | 375 + .../bower_components/jScrollPane/events.html | 264 + .../bower_components/jScrollPane/faqs.html | 52 + .../jScrollPane/fixed_width.html | 230 + .../bower_components/jScrollPane/focus.html | 207 + .../jScrollPane/fullpage_scroll.html | 259 + .../bower_components/jScrollPane/iframe.html | 79 + .../bower_components/jScrollPane/iframe2.html | 121 + .../jScrollPane/iframe_content1.html | 143 + .../jScrollPane/iframe_content2.html | 143 + .../jScrollPane/iframe_content3.html | 76 + .../jScrollPane/iframe_content4.html | 76 + .../bower_components/jScrollPane/image.html | 96 + .../jScrollPane/image/logo.png | Bin 0 -> 1838 bytes .../bower_components/jScrollPane/image2.html | 98 + .../bower_components/jScrollPane/index.html | 315 + .../jScrollPane/invisibles.html | 523 + .../jScrollPane/issues/11/after.html | 45 + .../jScrollPane/issues/11/before.html | 45 + .../jScrollPane/issues/11/brs_main.css | 301 + .../jScrollPane/issues/11/index.html | 53 + .../issues/11/jquery.mousewheel.js | 79 + .../jScrollPane/issues/11/jscrollpane-2b3.css | 143 + .../jScrollPane/issues/11/jscrollpane-2b3.js | 1063 + .../jScrollPane/issues/11/native.html | 42 + .../jScrollPane/issues/12/after.html | 80 + .../jScrollPane/issues/12/after_reinit.html | 73 + .../jScrollPane/issues/12/before.html | 72 + .../jScrollPane/issues/12/before_reinit.html | 73 + .../jScrollPane/issues/12/brs_main.css | 301 + .../jScrollPane/issues/12/index.html | 53 + .../issues/12/jquery.mousewheel.js | 79 + .../jScrollPane/issues/12/jscrollpane-2b3.css | 143 + .../jScrollPane/issues/12/jscrollpane-2b3.js | 1063 + .../jScrollPane/issues/12/native.html | 69 + .../jScrollPane/issues/12/wrapped.html | 87 + .../jScrollPane/issues/7/after.html | 190 + .../jScrollPane/issues/7/before.html | 190 + .../jScrollPane/issues/7/index.html | 53 + .../jScrollPane/issues/7/jscrollpane-2b1.css | 120 + .../jScrollPane/issues/7/jscrollpane-2b2.js | 947 + .../jScrollPane/issues/7/native.html | 171 + .../jScrollPane/known_issues.html | 62 + .../jScrollPane/less_basic.html | 164 + .../jScrollPane/mwheel_intent.html | 209 + .../jScrollPane/override_animate.html | 215 + .../bower_components/jScrollPane/runeimp.html | 314 + .../jScrollPane/runeimp2.html | 320 + .../jScrollPane/script/demo.js | 49 + .../jScrollPane/script/jquery.jscrollpane.js | 1438 + .../script/jquery.jscrollpane.min.js | 11 + .../jScrollPane/script}/jquery.mousewheel.js | 0 .../jScrollPane/script/mwheelIntent.js | 76 + .../jScrollPane/scroll_on_left.html | 132 + .../jScrollPane/scroll_to.html | 188 + .../jScrollPane/scroll_to_animate.html | 197 + .../jScrollPane/settings.html | 200 + .../bower_components/jScrollPane/short.html | 82 + .../jScrollPane/style/demo.css | 227 + .../jScrollPane/style/jquery.jscrollpane.css | 120 + .../lozenge/image/ui-icons_222222_256x240.png | Bin 0 -> 4369 bytes .../lozenge/image/ui-icons_888888_256x240.png | Bin 0 -> 4369 bytes .../lozenge/image/ui-icons_cd0a0a_256x240.png | Bin 0 -> 4369 bytes .../jScrollPane/themes/lozenge/index.html | 337 + .../style/jquery.jscrollpane.lozenge.css | 78 + .../libs/bower_components/jScrollPane/v1.html | 49 + .../jquery-mousewheel/ChangeLog.md | 141 + .../jquery-mousewheel/LICENSE.txt | 20 + .../jquery-mousewheel/README.md | 85 + .../jquery-mousewheel/bower.json | 17 + .../jquery-mousewheel/jquery.mousewheel.js | 221 + .../jquery.mousewheel.min.js | 8 + .../jquery-placeholder/LICENSE-MIT.txt} | 0 .../jquery-placeholder/README.md | 76 + .../jquery-placeholder/bower.json | 5 + .../jquery-placeholder/demo.html | 55 + .../jquery-placeholder/jquery.placeholder.js | 185 + .../bower_components/jquery-ui/AUTHORS.txt | 260 + .../jquery-ui/MIT-LICENSE.txt | 26 + .../libs/bower_components/jquery-ui/README.md | 11 + .../bower_components/jquery-ui/bower.json | 10 + .../bower_components/jquery-ui/component.json | 13 + .../bower_components/jquery-ui/composer.json | 63 + .../bower_components/jquery-ui/package.json | 66 + .../jquery-ui/ui/i18n/jquery-ui-i18n.js | 1645 + .../ui/i18n/jquery.ui.datepicker-af.js | 23 + .../ui/i18n/jquery.ui.datepicker-ar-DZ.js | 23 + .../ui/i18n/jquery.ui.datepicker-ar.js | 23 + .../ui/i18n/jquery.ui.datepicker-az.js | 23 + .../ui/i18n/jquery.ui.datepicker-be.js | 23 + .../ui/i18n/jquery.ui.datepicker-bg.js | 24 + .../ui/i18n/jquery.ui.datepicker-bs.js | 23 + .../ui/i18n/jquery.ui.datepicker-ca.js | 23 + .../ui/i18n/jquery.ui.datepicker-cs.js | 23 + .../ui/i18n/jquery.ui.datepicker-cy-GB.js | 23 + .../ui/i18n/jquery.ui.datepicker-da.js | 23 + .../ui/i18n/jquery.ui.datepicker-de.js | 23 + .../ui/i18n/jquery.ui.datepicker-el.js | 23 + .../ui/i18n/jquery.ui.datepicker-en-AU.js | 23 + .../ui/i18n/jquery.ui.datepicker-en-GB.js | 23 + .../ui/i18n/jquery.ui.datepicker-en-NZ.js | 23 + .../ui/i18n/jquery.ui.datepicker-eo.js | 23 + .../ui/i18n/jquery.ui.datepicker-es.js | 23 + .../ui/i18n/jquery.ui.datepicker-et.js | 23 + .../ui/i18n/jquery.ui.datepicker-eu.js | 23 + .../ui/i18n/jquery.ui.datepicker-fa.js | 59 + .../ui/i18n/jquery.ui.datepicker-fi.js | 23 + .../ui/i18n/jquery.ui.datepicker-fo.js | 23 + .../ui/i18n/jquery.ui.datepicker-fr-CA.js | 23 + .../ui/i18n/jquery.ui.datepicker-fr-CH.js | 23 + .../ui/i18n/jquery.ui.datepicker-fr.js | 25 + .../ui/i18n/jquery.ui.datepicker-gl.js | 23 + .../ui/i18n/jquery.ui.datepicker-he.js | 23 + .../ui/i18n/jquery.ui.datepicker-hi.js | 23 + .../ui/i18n/jquery.ui.datepicker-hr.js | 23 + .../ui/i18n/jquery.ui.datepicker-hu.js | 23 + .../ui/i18n/jquery.ui.datepicker-hy.js | 23 + .../ui/i18n/jquery.ui.datepicker-id.js | 23 + .../ui/i18n/jquery.ui.datepicker-is.js | 23 + .../ui/i18n/jquery.ui.datepicker-it.js | 23 + .../ui/i18n/jquery.ui.datepicker-ja.js | 23 + .../ui/i18n/jquery.ui.datepicker-ka.js | 21 + .../ui/i18n/jquery.ui.datepicker-kk.js | 23 + .../ui/i18n/jquery.ui.datepicker-km.js | 23 + .../ui/i18n/jquery.ui.datepicker-ko.js | 23 + .../ui/i18n/jquery.ui.datepicker-ky.js | 24 + .../ui/i18n/jquery.ui.datepicker-lb.js | 23 + .../ui/i18n/jquery.ui.datepicker-lt.js | 23 + .../ui/i18n/jquery.ui.datepicker-lv.js | 23 + .../ui/i18n/jquery.ui.datepicker-mk.js | 23 + .../ui/i18n/jquery.ui.datepicker-ml.js | 23 + .../ui/i18n/jquery.ui.datepicker-ms.js | 23 + .../ui/i18n/jquery.ui.datepicker-nb.js | 22 + .../ui/i18n/jquery.ui.datepicker-nl-BE.js | 23 + .../ui/i18n/jquery.ui.datepicker-nl.js | 23 + .../ui/i18n/jquery.ui.datepicker-nn.js | 22 + .../ui/i18n/jquery.ui.datepicker-no.js | 23 + .../ui/i18n/jquery.ui.datepicker-pl.js | 23 + .../ui/i18n/jquery.ui.datepicker-pt-BR.js | 23 + .../ui/i18n/jquery.ui.datepicker-pt.js | 22 + .../ui/i18n/jquery.ui.datepicker-rm.js | 21 + .../ui/i18n/jquery.ui.datepicker-ro.js | 26 + .../ui/i18n/jquery.ui.datepicker-ru.js | 23 + .../ui/i18n/jquery.ui.datepicker-sk.js | 23 + .../ui/i18n/jquery.ui.datepicker-sl.js | 24 + .../ui/i18n/jquery.ui.datepicker-sq.js | 23 + .../ui/i18n/jquery.ui.datepicker-sr-SR.js | 23 + .../ui/i18n/jquery.ui.datepicker-sr.js | 23 + .../ui/i18n/jquery.ui.datepicker-sv.js | 23 + .../ui/i18n/jquery.ui.datepicker-ta.js | 23 + .../ui/i18n/jquery.ui.datepicker-th.js | 23 + .../ui/i18n/jquery.ui.datepicker-tj.js | 23 + .../ui/i18n/jquery.ui.datepicker-tr.js | 23 + .../ui/i18n/jquery.ui.datepicker-uk.js | 24 + .../ui/i18n/jquery.ui.datepicker-vi.js | 23 + .../ui/i18n/jquery.ui.datepicker-zh-CN.js | 23 + .../ui/i18n/jquery.ui.datepicker-zh-HK.js | 23 + .../ui/i18n/jquery.ui.datepicker-zh-TW.js | 23 + .../jquery-ui/ui/jquery-ui.custom.js | 15008 +++++++ .../jquery-ui/ui/jquery-ui.js | 15008 +++++++ .../jquery-ui/ui/jquery.ui.accordion.js | 570 + .../jquery-ui/ui/jquery.ui.autocomplete.js | 606 + .../jquery-ui/ui/jquery.ui.button.js | 395 + .../jquery-ui/ui/jquery.ui.core.js | 320 + .../jquery-ui/ui/jquery.ui.datepicker.js | 2038 + .../jquery-ui/ui/jquery.ui.dialog.js | 823 + .../jquery-ui/ui/jquery.ui.draggable.js | 958 + .../jquery-ui/ui/jquery.ui.droppable.js | 389 + .../jquery-ui/ui/jquery.ui.effect-blind.js | 82 + .../jquery-ui/ui/jquery.ui.effect-bounce.js | 113 + .../jquery-ui/ui/jquery.ui.effect-clip.js | 67 + .../jquery-ui/ui/jquery.ui.effect-drop.js | 65 + .../jquery-ui/ui/jquery.ui.effect-explode.js | 97 + .../jquery-ui/ui/jquery.ui.effect-fade.js | 30 + .../jquery-ui/ui/jquery.ui.effect-fold.js | 76 + .../ui/jquery.ui.effect-highlight.js | 50 + .../jquery-ui/ui/jquery.ui.effect-pulsate.js | 63 + .../jquery-ui/ui/jquery.ui.effect-scale.js | 318 + .../jquery-ui/ui/jquery.ui.effect-shake.js | 74 + .../jquery-ui/ui/jquery.ui.effect-slide.js | 64 + .../jquery-ui/ui/jquery.ui.effect-transfer.js | 47 + .../jquery-ui/ui/jquery.ui.effect.js | 1289 + .../jquery-ui/ui/jquery.ui.menu.js | 627 + .../jquery-ui/ui/jquery.ui.mouse.js | 169 + .../jquery-ui/ui/jquery.ui.position.js | 501 + .../jquery-ui/ui/jquery.ui.progressbar.js | 145 + .../jquery-ui/ui/jquery.ui.resizable.js | 978 + .../jquery-ui/ui/jquery.ui.selectable.js | 277 + .../jquery-ui/ui/jquery.ui.slider.js | 676 + .../jquery-ui/ui/jquery.ui.sortable.js | 1289 + .../jquery-ui/ui/jquery.ui.spinner.js | 497 + .../jquery-ui/ui/jquery.ui.tabs.js | 849 + .../jquery-ui/ui/jquery.ui.tooltip.js | 402 + .../jquery-ui/ui/jquery.ui.widget.js | 521 + .../ui/minified/i18n/jquery-ui-i18n.min.js | 7 + .../i18n/jquery.ui.datepicker-af.min.js | 5 + .../i18n/jquery.ui.datepicker-ar-DZ.min.js | 5 + .../i18n/jquery.ui.datepicker-ar.min.js | 5 + .../i18n/jquery.ui.datepicker-az.min.js | 5 + .../i18n/jquery.ui.datepicker-be.min.js | 5 + .../i18n/jquery.ui.datepicker-bg.min.js | 5 + .../i18n/jquery.ui.datepicker-bs.min.js | 5 + .../i18n/jquery.ui.datepicker-ca.min.js | 5 + .../i18n/jquery.ui.datepicker-cs.min.js | 5 + .../i18n/jquery.ui.datepicker-cy-GB.min.js | 5 + .../i18n/jquery.ui.datepicker-da.min.js | 5 + .../i18n/jquery.ui.datepicker-de.min.js | 5 + .../i18n/jquery.ui.datepicker-el.min.js | 5 + .../i18n/jquery.ui.datepicker-en-AU.min.js | 5 + .../i18n/jquery.ui.datepicker-en-GB.min.js | 5 + .../i18n/jquery.ui.datepicker-en-NZ.min.js | 5 + .../i18n/jquery.ui.datepicker-eo.min.js | 5 + .../i18n/jquery.ui.datepicker-es.min.js | 5 + .../i18n/jquery.ui.datepicker-et.min.js | 5 + .../i18n/jquery.ui.datepicker-eu.min.js | 5 + .../i18n/jquery.ui.datepicker-fa.min.js | 5 + .../i18n/jquery.ui.datepicker-fi.min.js | 5 + .../i18n/jquery.ui.datepicker-fo.min.js | 5 + .../i18n/jquery.ui.datepicker-fr-CA.min.js | 5 + .../i18n/jquery.ui.datepicker-fr-CH.min.js | 5 + .../i18n/jquery.ui.datepicker-fr.min.js | 5 + .../i18n/jquery.ui.datepicker-gl.min.js | 5 + .../i18n/jquery.ui.datepicker-he.min.js | 5 + .../i18n/jquery.ui.datepicker-hi.min.js | 5 + .../i18n/jquery.ui.datepicker-hr.min.js | 5 + .../i18n/jquery.ui.datepicker-hu.min.js | 5 + .../i18n/jquery.ui.datepicker-hy.min.js | 5 + .../i18n/jquery.ui.datepicker-id.min.js | 5 + .../i18n/jquery.ui.datepicker-is.min.js | 5 + .../i18n/jquery.ui.datepicker-it.min.js | 5 + .../i18n/jquery.ui.datepicker-ja.min.js | 5 + .../i18n/jquery.ui.datepicker-ka.min.js | 5 + .../i18n/jquery.ui.datepicker-kk.min.js | 5 + .../i18n/jquery.ui.datepicker-km.min.js | 5 + .../i18n/jquery.ui.datepicker-ko.min.js | 5 + .../i18n/jquery.ui.datepicker-ky.min.js | 5 + .../i18n/jquery.ui.datepicker-lb.min.js | 5 + .../i18n/jquery.ui.datepicker-lt.min.js | 5 + .../i18n/jquery.ui.datepicker-lv.min.js | 5 + .../i18n/jquery.ui.datepicker-mk.min.js | 5 + .../i18n/jquery.ui.datepicker-ml.min.js | 5 + .../i18n/jquery.ui.datepicker-ms.min.js | 5 + .../i18n/jquery.ui.datepicker-nb.min.js | 5 + .../i18n/jquery.ui.datepicker-nl-BE.min.js | 5 + .../i18n/jquery.ui.datepicker-nl.min.js | 5 + .../i18n/jquery.ui.datepicker-nn.min.js | 5 + .../i18n/jquery.ui.datepicker-no.min.js | 5 + .../i18n/jquery.ui.datepicker-pl.min.js | 5 + .../i18n/jquery.ui.datepicker-pt-BR.min.js | 5 + .../i18n/jquery.ui.datepicker-pt.min.js | 5 + .../i18n/jquery.ui.datepicker-rm.min.js | 5 + .../i18n/jquery.ui.datepicker-ro.min.js | 5 + .../i18n/jquery.ui.datepicker-ru.min.js | 5 + .../i18n/jquery.ui.datepicker-sk.min.js | 5 + .../i18n/jquery.ui.datepicker-sl.min.js | 5 + .../i18n/jquery.ui.datepicker-sq.min.js | 5 + .../i18n/jquery.ui.datepicker-sr-SR.min.js | 5 + .../i18n/jquery.ui.datepicker-sr.min.js | 5 + .../i18n/jquery.ui.datepicker-sv.min.js | 5 + .../i18n/jquery.ui.datepicker-ta.min.js | 5 + .../i18n/jquery.ui.datepicker-th.min.js | 5 + .../i18n/jquery.ui.datepicker-tj.min.js | 5 + .../i18n/jquery.ui.datepicker-tr.min.js | 5 + .../i18n/jquery.ui.datepicker-uk.min.js | 5 + .../i18n/jquery.ui.datepicker-vi.min.js | 5 + .../i18n/jquery.ui.datepicker-zh-CN.min.js | 5 + .../i18n/jquery.ui.datepicker-zh-HK.min.js | 5 + .../i18n/jquery.ui.datepicker-zh-TW.min.js | 5 + .../ui/minified/jquery-ui.custom.min.js | 7 + .../jquery-ui/ui/minified/jquery-ui.min.js | 7 + .../ui/minified/jquery.ui.accordion.min.js | 5 + .../ui/minified/jquery.ui.autocomplete.min.js | 5 + .../ui/minified/jquery.ui.button.min.js | 5 + .../ui/minified/jquery.ui.core.min.js | 5 + .../ui/minified/jquery.ui.datepicker.min.js | 6 + .../ui/minified/jquery.ui.dialog.min.js | 5 + .../ui/minified/jquery.ui.draggable.min.js | 5 + .../ui/minified/jquery.ui.droppable.min.js | 5 + .../ui/minified/jquery.ui.effect-blind.min.js | 5 + .../minified/jquery.ui.effect-bounce.min.js | 5 + .../ui/minified/jquery.ui.effect-clip.min.js | 5 + .../ui/minified/jquery.ui.effect-drop.min.js | 5 + .../minified/jquery.ui.effect-explode.min.js | 5 + .../ui/minified/jquery.ui.effect-fade.min.js | 5 + .../ui/minified/jquery.ui.effect-fold.min.js | 5 + .../jquery.ui.effect-highlight.min.js | 5 + .../minified/jquery.ui.effect-pulsate.min.js | 5 + .../ui/minified/jquery.ui.effect-scale.min.js | 5 + .../ui/minified/jquery.ui.effect-shake.min.js | 5 + .../ui/minified/jquery.ui.effect-slide.min.js | 5 + .../minified/jquery.ui.effect-transfer.min.js | 5 + .../ui/minified/jquery.ui.effect.min.js | 5 + .../ui/minified/jquery.ui.menu.min.js | 5 + .../ui/minified/jquery.ui.mouse.min.js | 5 + .../ui/minified/jquery.ui.position.min.js | 5 + .../ui/minified/jquery.ui.progressbar.min.js | 5 + .../ui/minified/jquery.ui.resizable.min.js | 5 + .../ui/minified/jquery.ui.selectable.min.js | 5 + .../ui/minified/jquery.ui.slider.min.js | 5 + .../ui/minified/jquery.ui.sortable.min.js | 5 + .../ui/minified/jquery.ui.spinner.min.js | 5 + .../ui/minified/jquery.ui.tabs.min.js | 5 + .../ui/minified/jquery.ui.tooltip.min.js | 5 + .../ui/minified/jquery.ui.widget.min.js | 5 + .../bower_components/jquery.scrollTo/LICENSE | 22 + .../jquery.scrollTo/README.md | 71 + .../jquery.scrollTo/bower.json | 27 + .../jquery.scrollTo/jquery.scrollTo.js | 187 + .../jquery.scrollTo/jquery.scrollTo.min.js | 7 + .../jquery.scrollTo/package.json | 30 + .../jquery.scrollTo/scrollTo.jquery.json | 29 + .../bower_components/jquery/MIT-LICENSE.txt | 21 + .../libs/bower_components/jquery/bower.json | 27 + .../bower_components/jquery/dist/jquery.js | 10308 +++++ .../jquery/dist/jquery.min.js | 5 + .../jquery/dist/jquery.min.map | 1 + .../libs/bower_components/jquery/src/ajax.js | 807 + .../bower_components/jquery/src/ajax/jsonp.js | 89 + .../bower_components/jquery/src/ajax/load.js | 75 + .../jquery/src/ajax/parseJSON.js | 51 + .../jquery/src/ajax/parseXML.js | 31 + .../jquery/src/ajax/script.js | 93 + .../jquery/src/ajax/var/nonce.js | 5 + .../jquery/src/ajax/var/rquery.js | 3 + .../bower_components/jquery/src/ajax/xhr.js | 196 + .../bower_components/jquery/src/attributes.js | 11 + .../jquery/src/attributes/attr.js | 271 + .../jquery/src/attributes/classes.js | 157 + .../jquery/src/attributes/prop.js | 134 + .../jquery/src/attributes/support.js | 62 + .../jquery/src/attributes/val.js | 178 + .../bower_components/jquery/src/callbacks.js | 205 + .../libs/bower_components/jquery/src/core.js | 534 + .../jquery/src/core/access.js | 60 + .../bower_components/jquery/src/core/init.js | 132 + .../jquery/src/core/parseHTML.js | 39 + .../bower_components/jquery/src/core/ready.js | 152 + .../jquery/src/core/var/rsingleTag.js | 4 + .../libs/bower_components/jquery/src/css.js | 504 + .../jquery/src/css/addGetHookIf.js | 32 + .../bower_components/jquery/src/css/curCSS.js | 117 + .../jquery/src/css/defaultDisplay.js | 69 + .../jquery/src/css/hiddenVisibleSelectors.js | 20 + .../jquery/src/css/support.js | 149 + .../bower_components/jquery/src/css/swap.js | 28 + .../jquery/src/css/var/cssExpand.js | 3 + .../jquery/src/css/var/isHidden.js | 13 + .../jquery/src/css/var/rmargin.js | 3 + .../jquery/src/css/var/rnumnonpx.js | 5 + .../libs/bower_components/jquery/src/data.js | 335 + .../jquery/src/data/accepts.js | 21 + .../jquery/src/data/support.js | 25 + .../bower_components/jquery/src/deferred.js | 150 + .../bower_components/jquery/src/deprecated.js | 13 + .../bower_components/jquery/src/dimensions.js | 50 + .../bower_components/jquery/src/effects.js | 656 + .../jquery/src/effects/Tween.js | 114 + .../jquery/src/effects/animatedSelector.js | 13 + .../jquery/src/effects/support.js | 55 + .../libs/bower_components/jquery/src/event.js | 1037 + .../jquery/src/event/alias.js | 39 + .../jquery/src/event/support.js | 26 + .../jquery/src/exports/amd.js | 24 + .../jquery/src/exports/global.js | 32 + .../libs/bower_components/jquery/src/intro.js | 44 + .../bower_components/jquery/src/jquery.js | 37 + .../jquery/src/manipulation.js | 744 + .../jquery/src/manipulation/_evalUrl.js | 18 + .../jquery/src/manipulation/support.js | 76 + .../src/manipulation/var/rcheckableType.js | 3 + .../bower_components/jquery/src/offset.js | 211 + .../libs/bower_components/jquery/src/outro.js | 1 + .../libs/bower_components/jquery/src/queue.js | 142 + .../jquery/src/queue/delay.js | 22 + .../jquery/src/selector-sizzle.js | 14 + .../bower_components/jquery/src/selector.js | 1 + .../bower_components/jquery/src/serialize.js | 110 + .../jquery/src/sizzle/dist/sizzle.js | 2044 + .../jquery/src/sizzle/dist/sizzle.min.js | 3 + .../jquery/src/sizzle/dist/sizzle.min.map | 1 + .../bower_components/jquery/src/support.js | 58 + .../bower_components/jquery/src/traversing.js | 200 + .../jquery/src/traversing/findFilter.js | 100 + .../src/traversing/var/rneedsContext.js | 6 + .../jquery/src/var/class2type.js | 4 + .../bower_components/jquery/src/var/concat.js | 5 + .../jquery/src/var/deletedIds.js | 3 + .../bower_components/jquery/src/var/hasOwn.js | 5 + .../jquery/src/var/indexOf.js | 5 + .../bower_components/jquery/src/var/pnum.js | 3 + .../bower_components/jquery/src/var/push.js | 5 + .../jquery/src/var/rnotwhite.js | 3 + .../bower_components/jquery/src/var/slice.js | 5 + .../jquery/src/var/strundefined.js | 3 + .../jquery/src/var/support.js | 4 + .../jquery/src/var/toString.js | 5 + .../libs/bower_components/jquery/src/wrap.js | 75 + .../bower_components/mousetrap/Gruntfile.js | 47 + .../libs/bower_components/mousetrap/README.md | 100 + .../bower_components/mousetrap/mousetrap.js | 953 + .../mousetrap/mousetrap.min.js | 9 + .../bower_components/mousetrap/package.json | 30 + .../mousetrap/plugins/README.md | 24 + .../plugins/bind-dictionary/README.md | 16 + .../mousetrap-bind-dictionary.js | 39 + .../mousetrap-bind-dictionary.min.js | 1 + .../mousetrap/plugins/global-bind/README.md | 15 + .../global-bind/mousetrap-global-bind.js | 36 + .../global-bind/mousetrap-global-bind.min.js | 1 + .../mousetrap/plugins/pause/README.md | 13 + .../plugins/pause/mousetrap-pause.js | 29 + .../plugins/pause/mousetrap-pause.min.js | 1 + .../mousetrap/plugins/record/README.md | 16 + .../plugins/record/mousetrap-record.js | 189 + .../plugins/record/mousetrap-record.min.js | 2 + .../libs/bower_components/ngDialog/README.md | 326 + .../libs/bower_components/ngDialog/bower.json | 38 + .../ngDialog/css/ngDialog-theme-default.css | 196 + .../css/ngDialog-theme-default.min.css | 3 + .../ngDialog/css/ngDialog-theme-plain.css | 132 + .../ngDialog/css/ngDialog-theme-plain.min.css | 3 + .../ngDialog/css/ngDialog.css | 100 + .../ngDialog/css/ngDialog.min.css | 3 + .../bower_components/ngDialog/js/ngDialog.js | 348 + .../ngDialog/js/ngDialog.min.js | 2 + .../bower_components/ngDialog/package.json | 40 + .../libs/bower_components/sprintf/LICENSE | 24 + .../libs/bower_components/sprintf/README.md | 82 + .../libs/bower_components/sprintf/bower.json | 14 + .../sprintf/demo/angular.html | 20 + .../sprintf/dist/angular-sprintf.min.js | 4 + .../sprintf/dist/angular-sprintf.min.map | 1 + .../sprintf/dist/sprintf.min.js | 4 + .../sprintf/dist/sprintf.min.map | 1 + .../bower_components/sprintf/gruntfile.js | 36 + .../bower_components/sprintf/package.json | 22 + .../sprintf/src/angular-sprintf.js | 18 + .../bower_components/sprintf/src/sprintf.js | 195 + .../bower_components/sprintf/test/test.js | 72 + .../visibilityjs/ChangeLog.md | 71 + .../bower_components/visibilityjs/LICENSE | 20 + .../bower_components/visibilityjs/README.md | 325 + .../bower_components/visibilityjs/bower.json | 20 + .../bower_components/visibilityjs/index.js | 1 + .../visibilityjs/lib/visibility.core.js | 191 + .../visibilityjs/lib/visibility.fallback.js | 55 + .../visibilityjs/lib/visibility.js | 4 + .../visibilityjs/lib/visibility.timers.js | 163 + .../bower_components/visibilityjs/logo.svg | 19 + www/analytics/libs/html5shiv/html5shiv.js | 8 - www/analytics/libs/javascript/sprintf.js | 96 - .../libs/jqplot/build_minified_script.sh | 2 +- .../libs/jqplot/jqplot-custom.min.js | 73 +- .../libs/jqplot/plugins/jqplot.pieRenderer.js | 6 +- .../libs/jquery/MIT-LICENSE-jquery.txt | 20 - .../libs/jquery/MIT-LICENSE-jqueryui.txt | 25 - .../libs/jquery/MIT-LICENSE-scrollto.txt | 19 - www/analytics/libs/jquery/jquery-ui.js | 7 - www/analytics/libs/jquery/jquery.history.js | 181 - www/analytics/libs/jquery/jquery.js | 4 - .../libs/jquery/jquery.jscrollpane.js | 1341 - .../libs/jquery/jquery.placeholder.js | 157 - www/analytics/libs/jquery/jquery.scrollTo.js | 7 - .../libs/jquery/jquery.smartbanner.js | 176 +- .../jquery/stylesheets/jquery.smartbanner.css | 3 +- .../base/{jquery-ui.css => jquery-ui.min.css} | 0 .../libs/{pChart2.1.3 => pChart}/GPLv3.txt | 1350 +- www/analytics/libs/pChart/change.log | Bin 0 -> 25310 bytes .../class/pData.class.php | 14 +- .../class/pDraw.class.php | 12429 +++--- .../class/pImage.class.php | 22 +- .../class/pPie.class.php | 10 +- www/analytics/libs/pChart/readme.txt | Bin 0 -> 12528 bytes www/analytics/libs/pChart2.1.3/change.log | 282 - www/analytics/libs/pChart2.1.3/readme.txt | 140 - www/analytics/libs/sparkline/lib/Object.php | 2 +- .../libs/sparkline/lib/Sparkline.php | 4 +- .../libs/sparkline/lib/Sparkline_Bar.php | 4 +- .../libs/sparkline/lib/Sparkline_Line.php | 4 +- www/analytics/libs/tcpdf/2dbarcodes.php | 172 - www/analytics/libs/tcpdf/barcodes.php | 1965 - www/analytics/libs/tcpdf/composer.json | 40 - www/analytics/libs/tcpdf/config/lang/eng.php | 47 - .../libs/tcpdf/config/tcpdf_config_alt.php | 234 - .../libs/tcpdf/fonts/almohanad.ctg.z | Bin 2780 -> 0 bytes www/analytics/libs/tcpdf/fonts/almohanad.php | 102 - www/analytics/libs/tcpdf/fonts/almohanad.z | Bin 121292 -> 0 bytes .../libs/tcpdf/fonts/dejavusans.ctg.z | Bin 10120 -> 0 bytes www/analytics/libs/tcpdf/fonts/dejavusans.php | 15 - www/analytics/libs/tcpdf/fonts/dejavusans.z | Bin 361229 -> 0 bytes www/analytics/libs/tcpdf/fonts/times.php | 12 - www/analytics/libs/tcpdf/fonts/timesb.php | 12 - www/analytics/libs/tcpdf/fonts/timesbi.php | 12 - www/analytics/libs/tcpdf/fonts/timesi.php | 12 - www/analytics/libs/tcpdf/gpl.txt | 674 - www/analytics/libs/tcpdf/htmlcolors.php | 199 - .../libs/tcpdf/include/tcpdf_static.php | 2837 -- www/analytics/libs/tcpdf/lgpl-3.0.txt | 165 - www/analytics/libs/tcpdf/spotcolors.php | 57 - www/analytics/libs/tcpdf/tcpdf.crt | 40 - www/analytics/libs/tcpdf/tcpdf.fdf | Bin 1286 -> 0 bytes www/analytics/libs/tcpdf/tcpdf.p12 | Bin 1749 -> 0 bytes www/analytics/libs/tcpdf/unicode_data.php | 18371 --------- www/analytics/libs/upgradephp/upgrade.php | 46 +- www/analytics/misc/.htaccess | 8 + www/analytics/misc/How to install Piwik.html | 5 +- www/analytics/misc/composer/build-xhprof.sh | 53 + www/analytics/misc/composer/clean-xhprof.sh | 12 + www/analytics/misc/cron/.htaccess | 5 + www/analytics/misc/cron/archive.php | 56 +- www/analytics/misc/cron/archive.sh | 78 +- www/analytics/misc/cron/updatetoken.php | 32 +- .../misc/internal-docs/content-tracking.md | 550 + .../misc/log-analytics/CONTRIBUTING.md | 8 + www/analytics/misc/log-analytics/README.md | 277 +- .../misc/log-analytics/import_logs.py | 1022 +- .../misc/others/ExamplePiwikTracker.php | 2 +- .../misc/others/api_internal_call.php | 7 +- www/analytics/misc/others/api_rest_call.php | 6 +- .../misc/others/cli-script-bootstrap.php | 39 - www/analytics/misc/others/download-count.txt | 2 - www/analytics/misc/others/geoipUpdateRows.php | 234 +- .../phpstorm-codestyles/Piwik_codestyle.xml | 159 - .../misc/others/phpstorm-codestyles/README.md | 21 - ...kies_GenerateHundredsWebsitesAndVisits.php | 27 - .../test_generateLotsVisitsWebsites.php | 154 - .../others/tracker_simpleImageTracker.php | 6 +- .../uninstall-delete-piwik-directory.php | 11 +- .../misc/proxy-hide-piwik-url/README.md | 56 +- .../misc/proxy-hide-piwik-url/piwik.php | 73 - www/analytics/misc/user/.gitkeep | 0 www/analytics/misc/user/.htaccess | 33 - www/analytics/piwik.js | 83 +- www/analytics/piwik.php | 111 +- www/analytics/plugins/.htaccess | 39 +- www/analytics/plugins/API/API.php | 543 +- www/analytics/plugins/API/Controller.php | 30 +- .../plugins/API/DataTable/MergeDataTables.php | 55 + www/analytics/plugins/API/Glossary.php | 108 + www/analytics/plugins/API/Menu.php | 67 + www/analytics/plugins/API/ProcessedReport.php | 422 +- .../plugins/API/Renderer/Console.php | 31 + www/analytics/plugins/API/Renderer/Csv.php | 60 + www/analytics/plugins/API/Renderer/Html.php | 51 + www/analytics/plugins/API/Renderer/Json.php | 108 + www/analytics/plugins/API/Renderer/Json2.php | 33 + .../plugins/API/Renderer/Original.php | 77 + www/analytics/plugins/API/Renderer/Php.php | 82 + www/analytics/plugins/API/Renderer/Rss.php | 51 + www/analytics/plugins/API/Renderer/Tsv.php | 24 + www/analytics/plugins/API/Renderer/Xml.php | 40 + www/analytics/plugins/API/Reports/Get.php | 109 + www/analytics/plugins/API/RowEvolution.php | 58 +- www/analytics/plugins/API/lang/am.json | 5 + www/analytics/plugins/API/lang/ar.json | 12 + www/analytics/plugins/API/lang/be.json | 10 + www/analytics/plugins/API/lang/bg.json | 11 + www/analytics/plugins/API/lang/bs.json | 7 + www/analytics/plugins/API/lang/ca.json | 11 + www/analytics/plugins/API/lang/cs.json | 15 + www/analytics/plugins/API/lang/da.json | 12 + www/analytics/plugins/API/lang/de.json | 15 + www/analytics/plugins/API/lang/el.json | 15 + www/analytics/plugins/API/lang/en.json | 15 + www/analytics/plugins/API/lang/es.json | 13 + www/analytics/plugins/API/lang/et.json | 7 + www/analytics/plugins/API/lang/eu.json | 5 + www/analytics/plugins/API/lang/fa.json | 8 + www/analytics/plugins/API/lang/fi.json | 11 + www/analytics/plugins/API/lang/fr.json | 15 + www/analytics/plugins/API/lang/gl.json | 6 + www/analytics/plugins/API/lang/he.json | 10 + www/analytics/plugins/API/lang/hi.json | 11 + www/analytics/plugins/API/lang/hr.json | 6 + www/analytics/plugins/API/lang/hu.json | 10 + www/analytics/plugins/API/lang/id.json | 11 + www/analytics/plugins/API/lang/is.json | 11 + www/analytics/plugins/API/lang/it.json | 15 + www/analytics/plugins/API/lang/ja.json | 15 + www/analytics/plugins/API/lang/ka.json | 11 + www/analytics/plugins/API/lang/ko.json | 15 + www/analytics/plugins/API/lang/lt.json | 10 + www/analytics/plugins/API/lang/lv.json | 5 + www/analytics/plugins/API/lang/nb.json | 15 + www/analytics/plugins/API/lang/nl.json | 13 + www/analytics/plugins/API/lang/nn.json | 5 + www/analytics/plugins/API/lang/pl.json | 13 + www/analytics/plugins/API/lang/pt-br.json | 15 + www/analytics/plugins/API/lang/pt.json | 11 + www/analytics/plugins/API/lang/ro.json | 11 + www/analytics/plugins/API/lang/ru.json | 12 + www/analytics/plugins/API/lang/sk.json | 6 + www/analytics/plugins/API/lang/sl.json | 10 + www/analytics/plugins/API/lang/sq.json | 11 + www/analytics/plugins/API/lang/sr.json | 12 + www/analytics/plugins/API/lang/sv.json | 13 + www/analytics/plugins/API/lang/ta.json | 8 + www/analytics/plugins/API/lang/te.json | 5 + www/analytics/plugins/API/lang/th.json | 10 + www/analytics/plugins/API/lang/tl.json | 11 + www/analytics/plugins/API/lang/tr.json | 11 + www/analytics/plugins/API/lang/uk.json | 10 + www/analytics/plugins/API/lang/vi.json | 13 + www/analytics/plugins/API/lang/zh-cn.json | 11 + www/analytics/plugins/API/lang/zh-tw.json | 12 + .../plugins/API/templates/glossary.twig | 43 + .../plugins/API/templates/listAllAPI.twig | 25 +- www/analytics/plugins/Actions/API.php | 244 +- www/analytics/plugins/Actions/Actions.php | 888 +- .../Actions/Actions/ActionClickUrl.php | 71 + .../Actions/Actions/ActionDownloadUrl.php | 41 + .../Actions/Actions}/ActionSiteSearch.php | 46 +- www/analytics/plugins/Actions/Archiver.php | 260 +- .../plugins/Actions/ArchivingHelper.php | 142 +- .../plugins/Actions/Columns/ActionType.php | 70 + .../plugins/Actions/Columns/ActionUrl.php | 32 + .../plugins/Actions/Columns/ClickedUrl.php | 31 + .../Actions/Columns/DestinationPage.php | 20 + .../plugins/Actions/Columns/DownloadUrl.php | 31 + .../Actions/Columns/EntryPageTitle.php | 52 + .../plugins/Actions/Columns/EntryPageUrl.php | 53 + .../plugins/Actions/Columns/ExitPageTitle.php | 67 + .../plugins/Actions/Columns/ExitPageUrl.php | 73 + .../plugins/Actions/Columns/Keyword.php | 20 + .../Columns/KeywordwithNoSearchResult.php | 20 + .../Metrics/AveragePageGenerationTime.php | 108 + .../Columns/Metrics/AverageTimeOnPage.php | 51 + .../Actions/Columns/Metrics/BounceRate.php | 51 + .../Actions/Columns/Metrics/ExitRate.php | 51 + .../plugins/Actions/Columns/PageTitle.php | 33 + .../plugins/Actions/Columns/PageUrl.php | 33 + .../Actions/Columns/SearchCategory.php | 20 + .../Actions/Columns/SearchDestinationPage.php | 20 + .../plugins/Actions/Columns/SearchKeyword.php | 30 + .../Actions/Columns/SearchNoResultKeyword.php | 20 + .../Actions/Columns/TimeSpentRefAction.php | 31 + .../Actions/Columns/VisitTotalActions.php | 90 + .../Actions/Columns/VisitTotalSearches.php | 72 + www/analytics/plugins/Actions/Controller.php | 130 +- .../Actions/DataTable/Filter/Actions.php | 53 + www/analytics/plugins/Actions/Menu.php | 29 + www/analytics/plugins/Actions/Metrics.php | 95 + .../plugins/Actions/Reports/Base.php | 112 + www/analytics/plugins/Actions/Reports/Get.php | 37 + .../plugins/Actions/Reports/GetDownloads.php | 58 + .../Actions/Reports/GetEntryPageTitles.php | 86 + .../Actions/Reports/GetEntryPageUrls.php | 87 + .../Actions/Reports/GetExitPageTitles.php | 93 + .../Actions/Reports/GetExitPageUrls.php | 103 + .../plugins/Actions/Reports/GetOutlinks.php | 61 + .../plugins/Actions/Reports/GetPageTitles.php | 87 + .../GetPageTitlesFollowingSiteSearch.php | 86 + .../plugins/Actions/Reports/GetPageUrls.php | 70 + .../GetPageUrlsFollowingSiteSearch.php | 40 + .../Reports/GetSiteSearchCategories.php | 65 + .../Actions/Reports/GetSiteSearchKeywords.php | 70 + .../Reports/GetSiteSearchNoResultKeywords.php | 67 + .../Actions/Reports/SiteSearchBase.php | 64 + www/analytics/plugins/Actions/Segment.php | 22 + .../Tracker/ActionsRequestProcessor.php | 94 + .../Actions/javascripts/actionsDataTable.js | 84 +- .../plugins/Actions/javascripts/rowactions.js | 65 + www/analytics/plugins/Actions/lang/am.json | 10 + www/analytics/plugins/Actions/lang/ar.json | 65 + www/analytics/plugins/Actions/lang/be.json | 27 + www/analytics/plugins/Actions/lang/bg.json | 65 + www/analytics/plugins/Actions/lang/bs.json | 64 + www/analytics/plugins/Actions/lang/ca.json | 65 + www/analytics/plugins/Actions/lang/cs.json | 67 + www/analytics/plugins/Actions/lang/da.json | 65 + www/analytics/plugins/Actions/lang/de.json | 67 + www/analytics/plugins/Actions/lang/el.json | 67 + www/analytics/plugins/Actions/lang/en.json | 67 + www/analytics/plugins/Actions/lang/es.json | 65 + www/analytics/plugins/Actions/lang/et.json | 45 + www/analytics/plugins/Actions/lang/eu.json | 12 + www/analytics/plugins/Actions/lang/fa.json | 61 + www/analytics/plugins/Actions/lang/fi.json | 64 + www/analytics/plugins/Actions/lang/fr.json | 66 + www/analytics/plugins/Actions/lang/gl.json | 7 + www/analytics/plugins/Actions/lang/he.json | 59 + www/analytics/plugins/Actions/lang/hi.json | 62 + www/analytics/plugins/Actions/lang/hr.json | 58 + www/analytics/plugins/Actions/lang/hu.json | 49 + www/analytics/plugins/Actions/lang/id.json | 65 + www/analytics/plugins/Actions/lang/is.json | 14 + www/analytics/plugins/Actions/lang/it.json | 67 + www/analytics/plugins/Actions/lang/ja.json | 66 + www/analytics/plugins/Actions/lang/ka.json | 14 + www/analytics/plugins/Actions/lang/ko.json | 66 + www/analytics/plugins/Actions/lang/lt.json | 16 + www/analytics/plugins/Actions/lang/lv.json | 20 + www/analytics/plugins/Actions/lang/nb.json | 67 + www/analytics/plugins/Actions/lang/nl.json | 65 + www/analytics/plugins/Actions/lang/nn.json | 33 + www/analytics/plugins/Actions/lang/pl.json | 67 + www/analytics/plugins/Actions/lang/pt-br.json | 67 + www/analytics/plugins/Actions/lang/pt.json | 60 + www/analytics/plugins/Actions/lang/ro.json | 64 + www/analytics/plugins/Actions/lang/ru.json | 65 + www/analytics/plugins/Actions/lang/sk.json | 40 + www/analytics/plugins/Actions/lang/sl.json | 51 + www/analytics/plugins/Actions/lang/sq.json | 65 + www/analytics/plugins/Actions/lang/sr.json | 65 + www/analytics/plugins/Actions/lang/sv.json | 67 + www/analytics/plugins/Actions/lang/ta.json | 61 + www/analytics/plugins/Actions/lang/te.json | 31 + www/analytics/plugins/Actions/lang/th.json | 51 + www/analytics/plugins/Actions/lang/tl.json | 64 + www/analytics/plugins/Actions/lang/tr.json | 63 + www/analytics/plugins/Actions/lang/uk.json | 14 + www/analytics/plugins/Actions/lang/vi.json | 64 + www/analytics/plugins/Actions/lang/zh-cn.json | 65 + www/analytics/plugins/Actions/lang/zh-tw.json | 39 + .../Actions/templates/indexSiteSearch.twig | 32 +- www/analytics/plugins/Annotations/API.php | 7 +- .../plugins/Annotations/AnnotationList.php | 9 +- .../plugins/Annotations/Annotations.php | 12 +- .../plugins/Annotations/Controller.php | 12 +- .../Annotations/javascripts/annotations.js | 24 +- .../plugins/Annotations/lang/ar.json | 22 + .../plugins/Annotations/lang/bg.json | 22 + .../plugins/Annotations/lang/bs.json | 19 + .../plugins/Annotations/lang/ca.json | 22 + .../plugins/Annotations/lang/cs.json | 22 + .../plugins/Annotations/lang/da.json | 22 + .../plugins/Annotations/lang/de.json | 22 + .../plugins/Annotations/lang/el.json | 22 + .../plugins/Annotations/lang/en.json | 22 + .../plugins/Annotations/lang/es.json | 22 + .../plugins/Annotations/lang/et.json | 19 + .../plugins/Annotations/lang/fa.json | 22 + .../plugins/Annotations/lang/fi.json | 22 + .../plugins/Annotations/lang/fr.json | 22 + .../plugins/Annotations/lang/gl.json | 11 + .../plugins/Annotations/lang/he.json | 15 + .../plugins/Annotations/lang/hi.json | 22 + .../plugins/Annotations/lang/hr.json | 11 + .../plugins/Annotations/lang/id.json | 22 + .../plugins/Annotations/lang/it.json | 22 + .../plugins/Annotations/lang/ja.json | 22 + .../plugins/Annotations/lang/ko.json | 22 + .../plugins/Annotations/lang/nb.json | 22 + .../plugins/Annotations/lang/nl.json | 22 + .../plugins/Annotations/lang/pl.json | 22 + .../plugins/Annotations/lang/pt-br.json | 22 + .../plugins/Annotations/lang/pt.json | 22 + .../plugins/Annotations/lang/ro.json | 22 + .../plugins/Annotations/lang/ru.json | 22 + .../plugins/Annotations/lang/sk.json | 16 + .../plugins/Annotations/lang/sl.json | 14 + .../plugins/Annotations/lang/sq.json | 22 + .../plugins/Annotations/lang/sr.json | 22 + .../plugins/Annotations/lang/sv.json | 22 + .../plugins/Annotations/lang/ta.json | 20 + .../plugins/Annotations/lang/te.json | 5 + .../plugins/Annotations/lang/th.json | 7 + .../plugins/Annotations/lang/tl.json | 22 + .../plugins/Annotations/lang/tr.json | 21 + .../plugins/Annotations/lang/vi.json | 22 + .../plugins/Annotations/lang/zh-cn.json | 22 + .../plugins/Annotations/lang/zh-tw.json | 7 + .../Annotations/stylesheets/annotations.less | 37 +- .../Annotations/templates/_annotation.twig | 4 +- .../templates/_annotationList.twig | 4 +- .../templates/getAnnotationManager.twig | 2 +- .../templates/getEvolutionIcons.twig | 2 +- .../plugins/BulkTracking/BulkTracking.php | 84 + .../plugins/BulkTracking/Tracker/Handler.php | 128 + .../plugins/BulkTracking/Tracker/Requests.php | 111 + .../plugins/BulkTracking/Tracker/Response.php | 111 + .../plugins/BulkTracking/plugin.json | 4 + www/analytics/plugins/Contents/API.php | 74 + .../Contents/Actions/ActionContent.php | 54 + www/analytics/plugins/Contents/Archiver.php | 320 + .../Contents/Columns/ContentInteraction.php | 57 + .../plugins/Contents/Columns/ContentName.php | 57 + .../plugins/Contents/Columns/ContentPiece.php | 57 + .../Contents/Columns/ContentTarget.php | 52 + .../Columns/Metrics/InteractionRate.php | 57 + www/analytics/plugins/Contents/Contents.php | 50 + www/analytics/plugins/Contents/Controller.php | 51 + www/analytics/plugins/Contents/DataArray.php | 76 + www/analytics/plugins/Contents/Dimensions.php | 33 + www/analytics/plugins/Contents/Menu.php | 24 + www/analytics/plugins/Contents/README.md | 18 + .../plugins/Contents/Reports/Base.php | 60 + .../Contents/Reports/GetContentNames.php | 39 + .../Contents/Reports/GetContentPieces.php | 40 + .../Contents/javascripts/contentsDataTable.js | 52 + www/analytics/plugins/Contents/lang/ar.json | 9 + www/analytics/plugins/Contents/lang/bg.json | 11 + www/analytics/plugins/Contents/lang/ca.json | 6 + www/analytics/plugins/Contents/lang/cs.json | 16 + www/analytics/plugins/Contents/lang/da.json | 12 + www/analytics/plugins/Contents/lang/de.json | 16 + www/analytics/plugins/Contents/lang/el.json | 16 + www/analytics/plugins/Contents/lang/en.json | 16 + www/analytics/plugins/Contents/lang/es.json | 13 + www/analytics/plugins/Contents/lang/et.json | 9 + www/analytics/plugins/Contents/lang/fi.json | 12 + www/analytics/plugins/Contents/lang/fr.json | 16 + www/analytics/plugins/Contents/lang/gl.json | 9 + www/analytics/plugins/Contents/lang/hi.json | 13 + www/analytics/plugins/Contents/lang/it.json | 16 + www/analytics/plugins/Contents/lang/ja.json | 16 + www/analytics/plugins/Contents/lang/ko.json | 11 + www/analytics/plugins/Contents/lang/nb.json | 16 + www/analytics/plugins/Contents/lang/nl.json | 13 + www/analytics/plugins/Contents/lang/pl.json | 7 + .../plugins/Contents/lang/pt-br.json | 16 + www/analytics/plugins/Contents/lang/pt.json | 9 + www/analytics/plugins/Contents/lang/ro.json | 7 + www/analytics/plugins/Contents/lang/ru.json | 12 + www/analytics/plugins/Contents/lang/sl.json | 12 + www/analytics/plugins/Contents/lang/sq.json | 13 + www/analytics/plugins/Contents/lang/sr.json | 13 + www/analytics/plugins/Contents/lang/sv.json | 13 + www/analytics/plugins/Contents/lang/ta.json | 12 + www/analytics/plugins/Contents/lang/tl.json | 12 + www/analytics/plugins/Contents/lang/tr.json | 12 + www/analytics/plugins/Contents/lang/vi.json | 13 + .../Contents/stylesheets/datatable.less | 3 + www/analytics/plugins/CoreAdminHome/API.php | 254 +- .../CoreAdminHome/Commands/DeleteLogsData.php | 201 + .../Commands/FixDuplicateLogActions.php | 201 + .../Commands/InvalidateReportData.php | 200 + .../Commands/OptimizeArchiveTables.php | 124 + .../Commands/PurgeOldArchiveData.php | 207 + .../Commands/RunScheduledTasks.php | 79 + .../CoreAdminHome/Commands/SetConfig.php | 97 + .../SetConfig/ConfigSettingManipulation.php | 176 + .../plugins/CoreAdminHome/Controller.php | 228 +- .../plugins/CoreAdminHome/CoreAdminHome.php | 104 +- .../plugins/CoreAdminHome/CustomLogo.php | 105 +- www/analytics/plugins/CoreAdminHome/Menu.php | 74 + .../Model/DuplicateActionRemover.php | 188 + .../plugins/CoreAdminHome/OptOutManager.php | 225 + www/analytics/plugins/CoreAdminHome/Tasks.php | 146 + .../Tasks/ArchivesToPurgeDistributedList.php | 87 + .../javascripts/generalSettings.js | 43 +- .../javascripts/jsTrackingGenerator.js | 40 +- .../javascripts/pluginSettings.js | 9 +- .../javascripts/protocolCheck.js | 43 + .../plugins/CoreAdminHome/lang/ar.json | 53 + .../plugins/CoreAdminHome/lang/be.json | 23 + .../plugins/CoreAdminHome/lang/bg.json | 71 + .../plugins/CoreAdminHome/lang/bs.json | 9 + .../plugins/CoreAdminHome/lang/ca.json | 35 + .../plugins/CoreAdminHome/lang/cs.json | 95 + .../plugins/CoreAdminHome/lang/da.json | 86 + .../plugins/CoreAdminHome/lang/de.json | 92 + .../plugins/CoreAdminHome/lang/el.json | 95 + .../plugins/CoreAdminHome/lang/en.json | 95 + .../plugins/CoreAdminHome/lang/es.json | 88 + .../plugins/CoreAdminHome/lang/et.json | 33 + .../plugins/CoreAdminHome/lang/fa.json | 58 + .../plugins/CoreAdminHome/lang/fi.json | 81 + .../plugins/CoreAdminHome/lang/fr.json | 93 + .../plugins/CoreAdminHome/lang/he.json | 24 + .../plugins/CoreAdminHome/lang/hi.json | 67 + .../plugins/CoreAdminHome/lang/hr.json | 9 + .../plugins/CoreAdminHome/lang/hu.json | 92 + .../plugins/CoreAdminHome/lang/id.json | 68 + .../plugins/CoreAdminHome/lang/is.json | 12 + .../plugins/CoreAdminHome/lang/it.json | 95 + .../plugins/CoreAdminHome/lang/ja.json | 91 + .../plugins/CoreAdminHome/lang/ka.json | 16 + .../plugins/CoreAdminHome/lang/ko.json | 60 + .../plugins/CoreAdminHome/lang/lt.json | 11 + .../plugins/CoreAdminHome/lang/lv.json | 14 + .../plugins/CoreAdminHome/lang/nb.json | 95 + .../plugins/CoreAdminHome/lang/nl.json | 89 + .../plugins/CoreAdminHome/lang/nn.json | 22 + .../plugins/CoreAdminHome/lang/pl.json | 81 + .../plugins/CoreAdminHome/lang/pt-br.json | 95 + .../plugins/CoreAdminHome/lang/pt.json | 53 + .../plugins/CoreAdminHome/lang/ro.json | 77 + .../plugins/CoreAdminHome/lang/ru.json | 84 + .../plugins/CoreAdminHome/lang/sk.json | 19 + .../plugins/CoreAdminHome/lang/sl.json | 37 + .../plugins/CoreAdminHome/lang/sq.json | 29 + .../plugins/CoreAdminHome/lang/sr.json | 89 + .../plugins/CoreAdminHome/lang/sv.json | 92 + .../plugins/CoreAdminHome/lang/ta.json | 44 + .../plugins/CoreAdminHome/lang/te.json | 6 + .../plugins/CoreAdminHome/lang/th.json | 20 + .../plugins/CoreAdminHome/lang/tl.json | 79 + .../plugins/CoreAdminHome/lang/tr.json | 71 + .../plugins/CoreAdminHome/lang/uk.json | 16 + .../plugins/CoreAdminHome/lang/vi.json | 72 + .../plugins/CoreAdminHome/lang/zh-cn.json | 77 + .../plugins/CoreAdminHome/lang/zh-tw.json | 10 + .../stylesheets/generalSettings.less | 106 +- .../stylesheets/jsTrackingGenerator.css | 73 +- .../CoreAdminHome/stylesheets/menu.less | 78 - .../stylesheets/pluginSettings.less | 40 - .../CoreAdminHome/templates/_menu.twig | 23 - .../templates/generalSettings.twig | 481 +- .../CoreAdminHome/templates/optOut.twig | 106 +- .../templates/pluginSettings.twig | 167 +- .../templates/trackingCodeGenerator.twig | 365 +- .../CoreConsole/Commands/ClearCaches.php | 39 + .../CoreConsole/Commands/CodeCoverage.php | 90 - .../CoreConsole/Commands/CoreArchiver.php | 107 +- .../Commands/DevelopmentEnable.php | 56 + .../Commands/DevelopmentManageTestFiles.php | 66 + .../DevelopmentSyncProcessedSystemTests.php | 84 + .../Commands/GenerateAngularDirective.php | 130 + .../CoreConsole/Commands/GenerateApi.php | 5 +- .../CoreConsole/Commands/GenerateArchiver.php | 59 + .../CoreConsole/Commands/GenerateCommand.php | 27 +- .../Commands/GenerateController.php | 5 +- .../Commands/GenerateDimension.php | 259 + .../CoreConsole/Commands/GenerateMenu.php | 59 + .../CoreConsole/Commands/GeneratePlugin.php | 115 +- .../Commands/GeneratePluginBase.php | 245 +- .../CoreConsole/Commands/GenerateReport.php | 300 + .../Commands/GenerateScheduledTask.php | 59 + .../CoreConsole/Commands/GenerateSettings.php | 5 +- .../CoreConsole/Commands/GenerateTest.php | 117 +- .../CoreConsole/Commands/GenerateUpdate.php | 122 + .../Commands/GenerateVisualizationPlugin.php | 16 +- .../CoreConsole/Commands/GenerateWidget.php | 120 + .../CoreConsole/Commands/GitCommit.php | 10 +- .../plugins/CoreConsole/Commands/GitPull.php | 10 +- .../plugins/CoreConsole/Commands/GitPush.php | 11 +- .../CoreConsole/Commands/ManagePlugin.php | 60 +- .../CoreConsole/Commands/ManageTestFiles.php | 60 - .../plugins/CoreConsole/Commands/RunTests.php | 87 - .../CoreConsole/Commands/RunUITests.php | 70 - .../CoreConsole/Commands/SetupFixture.php | 156 - .../Commands/SyncUITestScreenshots.php | 81 - .../plugins/CoreConsole/Commands/WatchLog.php | 6 +- .../plugins/CoreHome/Columns/IdSite.php | 47 + .../Columns/Metrics/ActionsPerVisit.php | 45 + .../Columns/Metrics/AverageTimeOnSite.php | 53 + .../CoreHome/Columns/Metrics/BounceRate.php | 52 + .../Metrics/CallableProcessedMetric.php | 47 + .../Columns/Metrics/ConversionRate.php | 51 + .../Columns/Metrics/EvolutionMetric.php | 123 + .../Columns/Metrics/VisitsPercent.php | 77 + .../plugins/CoreHome/Columns/ServerTime.php | 38 + .../plugins/CoreHome/Columns/UserId.php | 140 + .../CoreHome/Columns/VisitFirstActionTime.php | 33 + .../CoreHome/Columns/VisitGoalBuyer.php | 131 + .../CoreHome/Columns/VisitGoalConverted.php | 52 + .../plugins/CoreHome/Columns/VisitId.php | 35 + .../plugins/CoreHome/Columns/VisitIp.php | 36 + .../CoreHome/Columns/VisitLastActionTime.php | 60 + .../CoreHome/Columns/VisitTotalTime.php | 108 + .../Columns/VisitorDaysSinceFirst.php | 52 + .../Columns/VisitorDaysSinceOrder.php | 59 + .../plugins/CoreHome/Columns/VisitorId.php | 36 + .../CoreHome/Columns/VisitorReturning.php | 79 + .../plugins/CoreHome/Columns/VisitsCount.php | 52 + www/analytics/plugins/CoreHome/Controller.php | 124 +- www/analytics/plugins/CoreHome/CoreHome.php | 250 +- .../DataTableRowAction/MultiRowEvolution.php | 2 +- .../DataTableRowAction/RowEvolution.php | 42 +- www/analytics/plugins/CoreHome/Menu.php | 69 + www/analytics/plugins/CoreHome/Segment.php | 21 + .../Tracker/VisitRequestProcessor.php | 223 + www/analytics/plugins/CoreHome/Visitor.php | 114 + www/analytics/plugins/CoreHome/Widgets.php | 63 + .../ajax-form/ajax-form.controller.js | 85 + .../ajax-form/ajax-form.directive.js | 142 + .../CoreHome/angularjs/anchorLinkFix.js | 17 +- .../common/directives/autocomplete-matched.js | 57 +- .../directives/autocomplete-matched_spec.js | 43 - .../angularjs/common/directives/dialog.js | 51 +- .../angularjs/common/directives/directive.js | 8 - .../common/directives/directive.module.js | 9 + .../directives/focus-anywhere-but-here.js | 50 +- .../angularjs/common/directives/focusif.js | 36 +- .../common/directives/ignore-click.js | 20 +- .../angularjs/common/directives/onenter.js | 30 +- .../angularjs/common/directives/translate.js | 36 + .../angularjs/common/filters/evolution.js | 61 +- .../angularjs/common/filters/filter.js | 7 - .../angularjs/common/filters/filter.module.js | 9 + .../angularjs/common/filters/htmldecode.js | 26 + .../angularjs/common/filters/length.js | 21 + .../angularjs/common/filters/pretty-url.js | 16 + .../angularjs/common/filters/startfrom.js | 17 +- .../common/filters/startfrom.spec.js | 41 + .../common/filters/startfrom_spec.js | 40 - .../angularjs/common/filters/translate.js | 25 +- .../CoreHome/angularjs/common/filters/trim.js | 20 + .../angularjs/common/filters/ucfirst.js | 21 + .../angularjs/common/services/piwik-api.js | 429 +- .../common/services/piwik-api.spec.js | 273 + .../angularjs/common/services/piwik.js | 15 +- .../angularjs/common/services/piwik.spec.js | 38 + .../angularjs/common/services/piwik_spec.js | 37 - .../angularjs/common/services/service.js | 8 - .../common/services/service.module.js | 9 + .../dialogtoggler-urllistener.service.js | 90 + .../dialogtoggler/dialogtoggler.controller.js | 68 + .../dialogtoggler/dialogtoggler.directive.js | 30 + .../angularjs/dialogtoggler/ngdialog.less | 70 + .../enrichedheadline-directive.js | 66 - .../enrichedheadline.directive.html | 34 + .../enrichedheadline.directive.js | 77 + .../enrichedheadline.directive.less | 60 + .../enrichedheadline/enrichedheadline.html | 29 - .../enrichedheadline/enrichedheadline.less | 44 - .../angularjs/history/history.service.js | 114 + .../CoreHome/angularjs/http404check.js | 52 + .../menudropdown/menudropdown.directive.html | 31 + .../menudropdown/menudropdown.directive.js | 73 + .../menudropdown/menudropdown.directive.less | 105 + .../notification/notification.controller.js | 34 + .../notification/notification.directive.html | 8 + .../notification/notification.directive.js | 95 + .../notification/notification.directive.less | 34 + .../CoreHome/angularjs/piwikApp.config.js | 13 + .../plugins/CoreHome/angularjs/piwikApp.js | 25 +- .../CoreHome/angularjs/piwikAppConfig.js | 9 - .../quick-access/quick-access.controller.js | 86 + .../quick-access/quick-access.directive.html | 38 + .../quick-access/quick-access.directive.js | 282 + .../quick-access/quick-access.directive.less | 55 + .../angularjs/selector/selector.directive.js | 87 + .../selector/selector.directive.less | 59 + .../siteselector/siteselector-controller.js | 49 - .../siteselector/siteselector-directive.js | 80 - .../siteselector/siteselector-model.js | 75 - .../siteselector-model.service.js | 131 + .../siteselector/siteselector.controller.js | 42 + .../siteselector/siteselector.directive.html | 69 + .../siteselector/siteselector.directive.js | 99 + .../siteselector/siteselector.directive.less | 156 + .../angularjs/siteselector/siteselector.html | 61 - .../angularjs/siteselector/siteselector.less | 177 - .../plugins/CoreHome/config/config.php | 8 + .../plugins/CoreHome/images/favicon.png | Bin 0 -> 18085 bytes .../CoreHome/images/navigation_collapse.png | Bin 0 -> 484 bytes .../CoreHome/images/navigation_expand.png | Bin 0 -> 502 bytes .../plugins/CoreHome/javascripts/broadcast.js | 106 +- .../plugins/CoreHome/javascripts/calendar.js | 203 +- .../CoreHome/javascripts/color_manager.js | 30 +- .../plugins/CoreHome/javascripts/corehome.js | 46 +- .../plugins/CoreHome/javascripts/dataTable.js | 461 +- .../javascripts/dataTable_rowactions.js | 91 +- .../plugins/CoreHome/javascripts/donate.js | 6 +- .../plugins/CoreHome/javascripts/menu.js | 158 +- .../plugins/CoreHome/javascripts/menu_init.js | 31 +- .../CoreHome/javascripts/notification.js | 142 +- .../javascripts/notification_parser.js | 2 +- .../CoreHome/javascripts/numberFormatter.js | 143 + .../plugins/CoreHome/javascripts/popover.js | 20 +- .../plugins/CoreHome/javascripts/promo.js | 12 - .../plugins/CoreHome/javascripts/require.js | 6 +- .../plugins/CoreHome/javascripts/sparkline.js | 19 +- .../CoreHome/javascripts/top_controls.js | 97 +- .../plugins/CoreHome/javascripts/uiControl.js | 12 +- www/analytics/plugins/CoreHome/lang/am.json | 7 + www/analytics/plugins/CoreHome/lang/ar.json | 51 + www/analytics/plugins/CoreHome/lang/be.json | 11 + www/analytics/plugins/CoreHome/lang/bg.json | 47 + www/analytics/plugins/CoreHome/lang/bs.json | 8 + www/analytics/plugins/CoreHome/lang/ca.json | 30 + www/analytics/plugins/CoreHome/lang/cs.json | 59 + www/analytics/plugins/CoreHome/lang/da.json | 53 + www/analytics/plugins/CoreHome/lang/de.json | 59 + www/analytics/plugins/CoreHome/lang/el.json | 59 + www/analytics/plugins/CoreHome/lang/en.json | 59 + www/analytics/plugins/CoreHome/lang/es.json | 53 + www/analytics/plugins/CoreHome/lang/et.json | 30 + www/analytics/plugins/CoreHome/lang/eu.json | 8 + www/analytics/plugins/CoreHome/lang/fa.json | 38 + www/analytics/plugins/CoreHome/lang/fi.json | 50 + www/analytics/plugins/CoreHome/lang/fr.json | 57 + www/analytics/plugins/CoreHome/lang/gl.json | 7 + www/analytics/plugins/CoreHome/lang/he.json | 19 + www/analytics/plugins/CoreHome/lang/hi.json | 47 + www/analytics/plugins/CoreHome/lang/hr.json | 5 + www/analytics/plugins/CoreHome/lang/hu.json | 12 + www/analytics/plugins/CoreHome/lang/id.json | 44 + www/analytics/plugins/CoreHome/lang/is.json | 15 + www/analytics/plugins/CoreHome/lang/it.json | 59 + www/analytics/plugins/CoreHome/lang/ja.json | 57 + www/analytics/plugins/CoreHome/lang/ka.json | 18 + www/analytics/plugins/CoreHome/lang/ko.json | 57 + www/analytics/plugins/CoreHome/lang/lt.json | 13 + www/analytics/plugins/CoreHome/lang/lv.json | 11 + www/analytics/plugins/CoreHome/lang/nb.json | 58 + www/analytics/plugins/CoreHome/lang/nl.json | 53 + www/analytics/plugins/CoreHome/lang/nn.json | 22 + www/analytics/plugins/CoreHome/lang/pl.json | 48 + .../plugins/CoreHome/lang/pt-br.json | 59 + www/analytics/plugins/CoreHome/lang/pt.json | 17 + www/analytics/plugins/CoreHome/lang/ro.json | 47 + www/analytics/plugins/CoreHome/lang/ru.json | 51 + www/analytics/plugins/CoreHome/lang/sk.json | 53 + www/analytics/plugins/CoreHome/lang/sl.json | 14 + www/analytics/plugins/CoreHome/lang/sq.json | 13 + www/analytics/plugins/CoreHome/lang/sr.json | 53 + www/analytics/plugins/CoreHome/lang/sv.json | 54 + www/analytics/plugins/CoreHome/lang/ta.json | 31 + www/analytics/plugins/CoreHome/lang/te.json | 6 + www/analytics/plugins/CoreHome/lang/th.json | 15 + www/analytics/plugins/CoreHome/lang/tl.json | 50 + www/analytics/plugins/CoreHome/lang/tr.json | 40 + www/analytics/plugins/CoreHome/lang/uk.json | 10 + www/analytics/plugins/CoreHome/lang/vi.json | 46 + .../plugins/CoreHome/lang/zh-cn.json | 53 + .../plugins/CoreHome/lang/zh-tw.json | 11 + .../plugins/CoreHome/stylesheets/_donate.less | 15 +- .../plugins/CoreHome/stylesheets/cloud.less | 12 + .../CoreHome/stylesheets/coreHome.less | 164 +- .../stylesheets/dataTable/_dataTable.less | 271 +- .../dataTable/_limitSelection.less | 15 +- .../dataTable/_reportDocumentation.less | 25 +- .../stylesheets/dataTable/_rowActions.less | 2 +- .../stylesheets/dataTable/_subDataTable.less | 4 +- .../dataTable/_tableConfiguration.less | 10 +- .../CoreHome/stylesheets/jqplotColors.less | 6 +- .../stylesheets/jquery.ui.autocomplete.css | 2 +- .../plugins/CoreHome/stylesheets/layout.less | 399 + .../plugins/CoreHome/stylesheets/menu.less | 131 - .../CoreHome/stylesheets/notification.less | 92 +- .../plugins/CoreHome/stylesheets/promo.less | 6 +- .../CoreHome/stylesheets/sparklineColors.less | 10 - .../CoreHome/stylesheets/zen-mode.less | 106 + .../ReportRenderer/_htmlReportBody.twig | 14 +- .../ReportRenderer/_htmlReportHeader.twig | 9 +- .../_reportsByDimension.twig | 4 +- .../CoreHome/templates/_adblockDetect.twig | 36 + .../CoreHome/templates/_dataTable.twig | 3 +- .../CoreHome/templates/_dataTableCell.twig | 39 +- .../CoreHome/templates/_dataTableFooter.twig | 54 +- .../CoreHome/templates/_dataTableHead.twig | 5 +- .../plugins/CoreHome/templates/_donate.twig | 18 +- .../plugins/CoreHome/templates/_favicon.twig | 5 + .../CoreHome/templates/_headerMessage.twig | 54 +- .../CoreHome/templates/_indexContent.twig | 20 - .../plugins/CoreHome/templates/_logo.twig | 4 +- .../plugins/CoreHome/templates/_menu.twig | 84 +- .../CoreHome/templates/_periodSelect.twig | 18 +- .../CoreHome/templates/_siteSelectHeader.twig | 6 +- .../plugins/CoreHome/templates/_topBar.twig | 39 +- .../CoreHome/templates/_topBarHelloMenu.twig | 25 - .../CoreHome/templates/_topBarTopMenu.twig | 13 - .../CoreHome/templates/_topScreen.twig | 19 +- .../CoreHome/templates/_uiControl.twig | 9 +- .../templates/_warningInvalidHost.twig | 10 +- .../templates/getDefaultIndexView.twig | 26 +- .../getMultiRowEvolutionPopover.twig | 4 +- .../CoreHome/templates/getPromoVideo.twig | 25 +- .../templates/getRowEvolutionPopover.twig | 8 +- .../Commands/ActivatePlugin.php | 44 + .../Commands/DeactivatePlugin.php | 44 + .../CorePluginsAdmin/Commands/ListPlugins.php | 54 + .../plugins/CorePluginsAdmin/Controller.php | 191 +- .../CorePluginsAdmin/CorePluginsAdmin.php | 89 +- .../plugins/CorePluginsAdmin/Marketplace.php | 32 +- .../CorePluginsAdmin/MarketplaceApiClient.php | 42 +- .../MarketplaceApiException.php | 2 +- .../plugins/CorePluginsAdmin/Menu.php | 73 + .../CorePluginsAdmin/PluginInstaller.php | 39 +- .../PluginInstallerException.php | 2 +- .../plugins/CorePluginsAdmin/Tasks.php | 36 + .../CorePluginsAdmin/UpdateCommunication.php | 77 +- .../CorePluginsAdmin/images/flattr.png | Bin 0 -> 1639 bytes .../CorePluginsAdmin/images/paypal_donate.jpg | Bin 0 -> 3665 bytes .../javascripts/marketplace.js | 50 + .../javascripts/pluginDetail.js | 88 - .../javascripts/pluginExtend.js | 4 +- .../javascripts/pluginOverview.js | 10 +- .../CorePluginsAdmin/javascripts/plugins.js | 2 +- .../plugins/CorePluginsAdmin/lang/am.json | 15 + .../plugins/CorePluginsAdmin/lang/ar.json | 51 + .../plugins/CorePluginsAdmin/lang/be.json | 19 + .../plugins/CorePluginsAdmin/lang/bg.json | 96 + .../plugins/CorePluginsAdmin/lang/bn.json | 12 + .../plugins/CorePluginsAdmin/lang/bs.json | 16 + .../plugins/CorePluginsAdmin/lang/ca.json | 22 + .../plugins/CorePluginsAdmin/lang/cs.json | 106 + .../plugins/CorePluginsAdmin/lang/da.json | 100 + .../plugins/CorePluginsAdmin/lang/de.json | 106 + .../plugins/CorePluginsAdmin/lang/el.json | 106 + .../plugins/CorePluginsAdmin/lang/en.json | 106 + .../plugins/CorePluginsAdmin/lang/es.json | 102 + .../plugins/CorePluginsAdmin/lang/et.json | 69 + .../plugins/CorePluginsAdmin/lang/eu.json | 17 + .../plugins/CorePluginsAdmin/lang/fa.json | 88 + .../plugins/CorePluginsAdmin/lang/fi.json | 95 + .../plugins/CorePluginsAdmin/lang/fr.json | 106 + .../plugins/CorePluginsAdmin/lang/gl.json | 9 + .../plugins/CorePluginsAdmin/lang/he.json | 27 + .../plugins/CorePluginsAdmin/lang/hi.json | 42 + .../plugins/CorePluginsAdmin/lang/hr.json | 5 + .../plugins/CorePluginsAdmin/lang/hu.json | 19 + .../plugins/CorePluginsAdmin/lang/id.json | 20 + .../plugins/CorePluginsAdmin/lang/is.json | 19 + .../plugins/CorePluginsAdmin/lang/it.json | 106 + .../plugins/CorePluginsAdmin/lang/ja.json | 106 + .../plugins/CorePluginsAdmin/lang/ka.json | 19 + .../plugins/CorePluginsAdmin/lang/ko.json | 59 + .../plugins/CorePluginsAdmin/lang/lt.json | 33 + .../plugins/CorePluginsAdmin/lang/lv.json | 19 + .../plugins/CorePluginsAdmin/lang/nb.json | 106 + .../plugins/CorePluginsAdmin/lang/nl.json | 105 + .../plugins/CorePluginsAdmin/lang/nn.json | 19 + .../plugins/CorePluginsAdmin/lang/pl.json | 95 + .../plugins/CorePluginsAdmin/lang/pt-br.json | 106 + .../plugins/CorePluginsAdmin/lang/pt.json | 36 + .../plugins/CorePluginsAdmin/lang/ro.json | 96 + .../plugins/CorePluginsAdmin/lang/ru.json | 95 + .../plugins/CorePluginsAdmin/lang/sk.json | 102 + .../plugins/CorePluginsAdmin/lang/sl.json | 22 + .../plugins/CorePluginsAdmin/lang/sq.json | 21 + .../plugins/CorePluginsAdmin/lang/sr.json | 106 + .../plugins/CorePluginsAdmin/lang/sv.json | 101 + .../plugins/CorePluginsAdmin/lang/ta.json | 21 + .../plugins/CorePluginsAdmin/lang/te.json | 14 + .../plugins/CorePluginsAdmin/lang/th.json | 19 + .../plugins/CorePluginsAdmin/lang/tl.json | 96 + .../plugins/CorePluginsAdmin/lang/tr.json | 71 + .../plugins/CorePluginsAdmin/lang/uk.json | 19 + .../plugins/CorePluginsAdmin/lang/vi.json | 63 + .../plugins/CorePluginsAdmin/lang/zh-cn.json | 74 + .../plugins/CorePluginsAdmin/lang/zh-tw.json | 17 + .../stylesheets/marketplace.less | 425 +- .../stylesheets/plugin-details.less | 101 + .../stylesheets/plugins_admin.less | 92 +- .../templates/browsePlugins.twig | 50 - .../templates/browsePluginsActions.twig | 12 - .../templates/browseThemes.twig | 45 - .../CorePluginsAdmin/templates/extend.twig | 70 - .../CorePluginsAdmin/templates/macros.twig | 60 +- .../templates/marketplace.twig | 77 + .../templates/marketplace/plugin-list.twig | 110 + .../templates/pluginDetails.twig | 7 +- .../templates/pluginMetadata.twig | 9 - .../templates/pluginOverview.twig | 30 - .../CorePluginsAdmin/templates/plugins.twig | 14 +- .../CorePluginsAdmin/templates/safemode.twig | 39 +- .../templates/themeOverview.twig | 30 - .../CorePluginsAdmin/templates/themes.twig | 12 +- .../templates/updatePlugin.twig | 2 +- .../templates/uploadPlugin.twig | 4 +- .../CoreUpdater/ArchiveDownloadException.php | 22 + .../plugins/CoreUpdater/Commands/Update.php | 311 +- .../Commands/Update/CliUpdateObserver.php | 54 + .../plugins/CoreUpdater/Controller.php | 306 +- .../plugins/CoreUpdater/CoreUpdater.php | 122 +- .../Diagnostic/HttpsUpdateCheck.php | 43 + www/analytics/plugins/CoreUpdater/Model.php | 34 + .../CoreUpdater/NoUpdatesFoundException.php | 2 +- .../plugins/CoreUpdater/ReleaseChannel.php | 43 + .../ReleaseChannel/Latest2XBeta.php | 35 + .../ReleaseChannel/Latest2XStable.php | 35 + .../CoreUpdater/ReleaseChannel/LatestBeta.php | 30 + .../ReleaseChannel/LatestStable.php | 40 + www/analytics/plugins/CoreUpdater/Tasks.php | 25 + .../Test/Fixtures/DbUpdaterTestFixture.php | 23 + .../Test/Fixtures/FailUpdateHttpsFixture.php | 24 + .../Test/Integration/Commands/UpdateTest.php | 123 + .../Test/Integration/ReleaseChannelTest.php | 66 + .../Integration/UpdateCommunicationTest.php | 146 + .../CoreUpdater/Test/Mock/UpdaterMock.php | 59 + .../CoreUpdater/Test/Unit/ModelTest.php | 61 + .../CoreUpdater/UpdateCommunication.php | 39 +- www/analytics/plugins/CoreUpdater/Updater.php | 277 + .../plugins/CoreUpdater/UpdaterException.php | 37 + .../plugins/CoreUpdater/config/config.php | 10 + .../CoreUpdater/javascripts/updateLayout.js | 9 +- .../plugins/CoreUpdater/lang/am.json | 29 + .../plugins/CoreUpdater/lang/ar.json | 50 + .../plugins/CoreUpdater/lang/be.json | 49 + .../plugins/CoreUpdater/lang/bg.json | 57 + .../plugins/CoreUpdater/lang/bs.json | 7 + .../plugins/CoreUpdater/lang/ca.json | 51 + .../plugins/CoreUpdater/lang/cs.json | 79 + .../plugins/CoreUpdater/lang/da.json | 62 + .../plugins/CoreUpdater/lang/de.json | 79 + .../plugins/CoreUpdater/lang/el.json | 79 + .../plugins/CoreUpdater/lang/en.json | 79 + .../plugins/CoreUpdater/lang/es.json | 74 + .../plugins/CoreUpdater/lang/et.json | 40 + .../plugins/CoreUpdater/lang/eu.json | 43 + .../plugins/CoreUpdater/lang/fa.json | 52 + .../plugins/CoreUpdater/lang/fi.json | 61 + .../plugins/CoreUpdater/lang/fr.json | 79 + .../plugins/CoreUpdater/lang/gl.json | 22 + .../plugins/CoreUpdater/lang/he.json | 49 + .../plugins/CoreUpdater/lang/hi.json | 52 + .../plugins/CoreUpdater/lang/hu.json | 49 + .../plugins/CoreUpdater/lang/id.json | 51 + .../plugins/CoreUpdater/lang/it.json | 79 + .../plugins/CoreUpdater/lang/ja.json | 78 + .../plugins/CoreUpdater/lang/ka.json | 49 + .../plugins/CoreUpdater/lang/ko.json | 56 + .../plugins/CoreUpdater/lang/lt.json | 55 + .../plugins/CoreUpdater/lang/lv.json | 32 + .../plugins/CoreUpdater/lang/nb.json | 79 + .../plugins/CoreUpdater/lang/nl.json | 74 + .../plugins/CoreUpdater/lang/nn.json | 48 + .../plugins/CoreUpdater/lang/pl.json | 61 + .../plugins/CoreUpdater/lang/pt-br.json | 79 + .../plugins/CoreUpdater/lang/pt.json | 49 + .../plugins/CoreUpdater/lang/ro.json | 60 + .../plugins/CoreUpdater/lang/ru.json | 71 + .../plugins/CoreUpdater/lang/sk.json | 74 + .../plugins/CoreUpdater/lang/sl.json | 36 + .../plugins/CoreUpdater/lang/sq.json | 49 + .../plugins/CoreUpdater/lang/sr.json | 74 + .../plugins/CoreUpdater/lang/sv.json | 76 + .../plugins/CoreUpdater/lang/ta.json | 39 + .../plugins/CoreUpdater/lang/te.json | 9 + .../plugins/CoreUpdater/lang/th.json | 49 + .../plugins/CoreUpdater/lang/tl.json | 60 + .../plugins/CoreUpdater/lang/tr.json | 43 + .../plugins/CoreUpdater/lang/uk.json | 49 + .../plugins/CoreUpdater/lang/vi.json | 51 + .../plugins/CoreUpdater/lang/zh-cn.json | 55 + .../plugins/CoreUpdater/lang/zh-tw.json | 74 + .../CoreUpdater/stylesheets/updateLayout.css | 18 +- .../plugins/CoreUpdater/templates/layout.twig | 52 +- .../templates/newVersionAvailable.twig | 72 +- .../templates/oneClickResults.twig | 26 - .../templates/runUpdaterAndExit_done.twig | 127 +- .../templates/runUpdaterAndExit_done_cli.twig | 55 - .../templates/runUpdaterAndExit_welcome.twig | 165 +- .../runUpdaterAndExit_welcome_cli.twig | 48 - .../templates/updateHttpError.twig | 30 + .../templates/updateHttpsError.twig | 48 + .../CoreUpdater/templates/updateSuccess.twig | 40 + .../CoreVisualizations/CoreVisualizations.php | 44 +- .../JqplotDataGenerator.php | 9 +- .../JqplotDataGenerator/Chart.php | 6 +- .../JqplotDataGenerator/Evolution.php | 5 +- .../Metrics/Formatter/Numeric.php | 45 + .../Visualizations/Cloud.php | 10 +- .../Visualizations/Cloud/Config.php | 2 +- .../Visualizations/Graph.php | 11 +- .../Visualizations/Graph/Config.php | 2 +- .../Visualizations/HtmlTable.php | 16 +- .../Visualizations/HtmlTable/AllColumns.php | 23 +- .../Visualizations/HtmlTable/Config.php | 2 +- .../Visualizations/HtmlTable/PivotBy.php | 36 + .../HtmlTable/RequestConfig.php | 10 +- .../Visualizations/JqplotGraph.php | 3 +- .../Visualizations/JqplotGraph/Bar.php | 4 +- .../Visualizations/JqplotGraph/Config.php | 2 +- .../Visualizations/JqplotGraph/Evolution.php | 2 +- .../JqplotGraph/Evolution/Config.php | 2 +- .../Visualizations/JqplotGraph/Pie.php | 4 +- .../Visualizations/Sparkline.php | 2 +- .../CoreVisualizations/javascripts/jqplot.js | 131 +- .../javascripts/jqplotBarGraph.js | 4 +- .../javascripts/jqplotEvolutionGraph.js | 47 +- .../javascripts/jqplotPieGraph.js | 4 +- .../javascripts/seriesPicker.js | 32 +- .../CoreVisualizations/stylesheets/jqplot.css | 40 +- .../templates/_dataTableViz_htmlTable.twig | 16 +- .../templates/_dataTableViz_jqplotGraph.twig | 4 +- .../templates/_dataTableViz_tagCloud.twig | 6 +- www/analytics/plugins/CustomVariables/API.php | 97 +- .../plugins/CustomVariables/Archiver.php | 71 +- .../plugins/CustomVariables/Columns/Base.php | 48 + .../Columns/CustomVariableName.php | 24 + .../Columns/CustomVariableValue.php | 24 + .../plugins/CustomVariables/Commands/Info.php | 7 +- .../Commands/SetNumberOfCustomVariables.php | 14 +- .../plugins/CustomVariables/Controller.php | 27 +- .../CustomVariables/CustomVariables.php | 178 +- .../CustomVariablesValuesFromNameId.php | 42 + .../plugins/CustomVariables/Menu.php | 33 + .../plugins/CustomVariables/Model.php | 59 +- .../plugins/CustomVariables/Reports/Base.php | 20 + .../Reports/GetCustomVariables.php | 75 + .../GetCustomVariablesValuesFromNameId.php | 39 + .../CustomVariablesRequestProcessor.php | 88 + .../manage-custom-vars.controller.js | 22 + .../manage-custom-vars.directive.html | 54 + .../manage-custom-vars.directive.js | 26 + .../manage-custom-vars.directive.less | 18 + .../manage-custom-vars.model.js | 59 + .../plugins/CustomVariables/config/config.php | 6 + .../plugins/CustomVariables/config/test.php | 4 + .../plugins/CustomVariables/lang/ar.json | 7 + .../plugins/CustomVariables/lang/be.json | 8 + .../plugins/CustomVariables/lang/bg.json | 10 + .../plugins/CustomVariables/lang/ca.json | 10 + .../plugins/CustomVariables/lang/cs.json | 22 + .../plugins/CustomVariables/lang/da.json | 10 + .../plugins/CustomVariables/lang/de.json | 22 + .../plugins/CustomVariables/lang/el.json | 22 + .../plugins/CustomVariables/lang/en.json | 24 + .../plugins/CustomVariables/lang/es.json | 11 + .../plugins/CustomVariables/lang/et.json | 9 + .../plugins/CustomVariables/lang/fa.json | 9 + .../plugins/CustomVariables/lang/fi.json | 10 + .../plugins/CustomVariables/lang/fr.json | 22 + .../plugins/CustomVariables/lang/he.json | 7 + .../plugins/CustomVariables/lang/hi.json | 10 + .../plugins/CustomVariables/lang/id.json | 10 + .../plugins/CustomVariables/lang/it.json | 22 + .../plugins/CustomVariables/lang/ja.json | 10 + .../plugins/CustomVariables/lang/ko.json | 10 + .../plugins/CustomVariables/lang/nb.json | 6 + .../plugins/CustomVariables/lang/nl.json | 11 + .../plugins/CustomVariables/lang/pl.json | 10 + .../plugins/CustomVariables/lang/pt-br.json | 22 + .../plugins/CustomVariables/lang/pt.json | 10 + .../plugins/CustomVariables/lang/ro.json | 10 + .../plugins/CustomVariables/lang/ru.json | 10 + .../plugins/CustomVariables/lang/sk.json | 10 + .../plugins/CustomVariables/lang/sl.json | 5 + .../plugins/CustomVariables/lang/sq.json | 11 + .../plugins/CustomVariables/lang/sr.json | 11 + .../plugins/CustomVariables/lang/sv.json | 13 + .../plugins/CustomVariables/lang/ta.json | 9 + .../plugins/CustomVariables/lang/th.json | 7 + .../plugins/CustomVariables/lang/tl.json | 10 + .../plugins/CustomVariables/lang/tr.json | 9 + .../plugins/CustomVariables/lang/vi.json | 10 + .../plugins/CustomVariables/lang/zh-cn.json | 10 + .../CustomVariables/templates/manage.twig | 11 + www/analytics/plugins/DBStats/.gitignore | 1 - www/analytics/plugins/DBStats/API.php | 17 +- www/analytics/plugins/DBStats/Controller.php | 137 +- www/analytics/plugins/DBStats/DBStats.php | 354 +- www/analytics/plugins/DBStats/Menu.php | 26 + .../DBStats/MySQLMetadataDataAccess.php | 6 +- .../plugins/DBStats/MySQLMetadataProvider.php | 12 +- .../plugins/DBStats/Reports/Base.php | 160 + .../DBStats/Reports/GetAdminDataSummary.php | 33 + .../Reports/GetDatabaseUsageSummary.php | 56 + .../Reports/GetIndividualMetricsSummary.php | 36 + .../Reports/GetIndividualReportsSummary.php | 41 + .../DBStats/Reports/GetMetricDataSummary.php | 41 + .../Reports/GetMetricDataSummaryByYear.php | 42 + .../DBStats/Reports/GetReportDataSummary.php | 40 + .../Reports/GetReportDataSummaryByYear.php | 42 + .../DBStats/Reports/GetTrackerDataSummary.php | 30 + www/analytics/plugins/DBStats/Tasks.php | 34 + www/analytics/plugins/DBStats/config/test.php | 7 + www/analytics/plugins/DBStats/lang/ar.json | 1 - www/analytics/plugins/DBStats/lang/be.json | 1 - www/analytics/plugins/DBStats/lang/bg.json | 1 - www/analytics/plugins/DBStats/lang/ca.json | 1 - www/analytics/plugins/DBStats/lang/cs.json | 15 +- www/analytics/plugins/DBStats/lang/cy.json | 5 + www/analytics/plugins/DBStats/lang/da.json | 2 +- www/analytics/plugins/DBStats/lang/de.json | 4 +- www/analytics/plugins/DBStats/lang/el.json | 2 +- www/analytics/plugins/DBStats/lang/en.json | 24 +- www/analytics/plugins/DBStats/lang/es.json | 14 +- www/analytics/plugins/DBStats/lang/et.json | 1 - www/analytics/plugins/DBStats/lang/fa.json | 1 - www/analytics/plugins/DBStats/lang/fi.json | 3 +- www/analytics/plugins/DBStats/lang/fr.json | 4 +- www/analytics/plugins/DBStats/lang/gl.json | 5 + www/analytics/plugins/DBStats/lang/he.json | 1 - www/analytics/plugins/DBStats/lang/hi.json | 1 - www/analytics/plugins/DBStats/lang/hr.json | 5 + www/analytics/plugins/DBStats/lang/hu.json | 3 +- www/analytics/plugins/DBStats/lang/id.json | 1 - www/analytics/plugins/DBStats/lang/is.json | 1 - www/analytics/plugins/DBStats/lang/it.json | 16 +- www/analytics/plugins/DBStats/lang/ja.json | 2 +- www/analytics/plugins/DBStats/lang/ka.json | 1 - www/analytics/plugins/DBStats/lang/ko.json | 2 +- www/analytics/plugins/DBStats/lang/lt.json | 1 - www/analytics/plugins/DBStats/lang/lv.json | 1 - www/analytics/plugins/DBStats/lang/nb.json | 11 +- www/analytics/plugins/DBStats/lang/nl.json | 2 +- www/analytics/plugins/DBStats/lang/nn.json | 1 - www/analytics/plugins/DBStats/lang/pl.json | 5 +- www/analytics/plugins/DBStats/lang/pt-br.json | 2 +- www/analytics/plugins/DBStats/lang/pt.json | 1 - www/analytics/plugins/DBStats/lang/ro.json | 6 +- www/analytics/plugins/DBStats/lang/ru.json | 2 +- www/analytics/plugins/DBStats/lang/sl.json | 1 - www/analytics/plugins/DBStats/lang/sq.json | 1 - www/analytics/plugins/DBStats/lang/sr.json | 2 +- www/analytics/plugins/DBStats/lang/sv.json | 1 - www/analytics/plugins/DBStats/lang/ta.json | 1 - www/analytics/plugins/DBStats/lang/th.json | 1 - www/analytics/plugins/DBStats/lang/tl.json | 20 + www/analytics/plugins/DBStats/lang/tr.json | 8 +- www/analytics/plugins/DBStats/lang/uk.json | 1 - www/analytics/plugins/DBStats/lang/vi.json | 1 - www/analytics/plugins/DBStats/lang/zh-cn.json | 4 +- www/analytics/plugins/DBStats/lang/zh-tw.json | 3 +- .../DBStats/stylesheets/dbStatsTable.less | 9 + .../plugins/DBStats/templates/index.twig | 20 +- www/analytics/plugins/Dashboard/API.php | 10 +- .../plugins/Dashboard/Controller.php | 128 +- www/analytics/plugins/Dashboard/Dashboard.php | 112 +- .../Dashboard/DashboardManagerControl.php | 8 +- .../DashboardSettingsControlBase.php | 7 +- www/analytics/plugins/Dashboard/Menu.php | 61 + www/analytics/plugins/Dashboard/Model.php | 245 + .../Dashboard/javascripts/dashboard.js | 51 +- .../Dashboard/javascripts/dashboardObject.js | 62 +- .../Dashboard/javascripts/dashboardWidget.js | 33 +- .../Dashboard/javascripts/widgetMenu.js | 16 +- www/analytics/plugins/Dashboard/lang/am.json | 11 + www/analytics/plugins/Dashboard/lang/ar.json | 36 + www/analytics/plugins/Dashboard/lang/be.json | 12 + www/analytics/plugins/Dashboard/lang/bg.json | 36 + www/analytics/plugins/Dashboard/lang/bn.json | 9 + www/analytics/plugins/Dashboard/lang/bs.json | 7 + www/analytics/plugins/Dashboard/lang/ca.json | 36 + www/analytics/plugins/Dashboard/lang/cs.json | 37 + www/analytics/plugins/Dashboard/lang/cy.json | 5 + www/analytics/plugins/Dashboard/lang/da.json | 36 + www/analytics/plugins/Dashboard/lang/de.json | 37 + www/analytics/plugins/Dashboard/lang/el.json | 37 + www/analytics/plugins/Dashboard/lang/en.json | 37 + www/analytics/plugins/Dashboard/lang/es.json | 36 + www/analytics/plugins/Dashboard/lang/et.json | 28 + www/analytics/plugins/Dashboard/lang/eu.json | 11 + www/analytics/plugins/Dashboard/lang/fa.json | 34 + www/analytics/plugins/Dashboard/lang/fi.json | 35 + www/analytics/plugins/Dashboard/lang/fr.json | 37 + www/analytics/plugins/Dashboard/lang/gl.json | 13 + www/analytics/plugins/Dashboard/lang/he.json | 17 + www/analytics/plugins/Dashboard/lang/hi.json | 35 + www/analytics/plugins/Dashboard/lang/hr.json | 17 + www/analytics/plugins/Dashboard/lang/hu.json | 13 + www/analytics/plugins/Dashboard/lang/id.json | 35 + www/analytics/plugins/Dashboard/lang/is.json | 12 + www/analytics/plugins/Dashboard/lang/it.json | 37 + www/analytics/plugins/Dashboard/lang/ja.json | 37 + www/analytics/plugins/Dashboard/lang/ka.json | 12 + www/analytics/plugins/Dashboard/lang/ko.json | 37 + www/analytics/plugins/Dashboard/lang/lt.json | 34 + www/analytics/plugins/Dashboard/lang/lv.json | 13 + www/analytics/plugins/Dashboard/lang/nb.json | 37 + www/analytics/plugins/Dashboard/lang/nl.json | 36 + www/analytics/plugins/Dashboard/lang/nn.json | 32 + www/analytics/plugins/Dashboard/lang/pl.json | 36 + .../plugins/Dashboard/lang/pt-br.json | 37 + www/analytics/plugins/Dashboard/lang/pt.json | 13 + www/analytics/plugins/Dashboard/lang/ro.json | 35 + www/analytics/plugins/Dashboard/lang/ru.json | 35 + www/analytics/plugins/Dashboard/lang/sk.json | 37 + www/analytics/plugins/Dashboard/lang/sl.json | 20 + www/analytics/plugins/Dashboard/lang/sq.json | 18 + www/analytics/plugins/Dashboard/lang/sr.json | 36 + www/analytics/plugins/Dashboard/lang/sv.json | 36 + www/analytics/plugins/Dashboard/lang/ta.json | 30 + www/analytics/plugins/Dashboard/lang/te.json | 7 + www/analytics/plugins/Dashboard/lang/th.json | 28 + www/analytics/plugins/Dashboard/lang/tl.json | 35 + www/analytics/plugins/Dashboard/lang/tr.json | 31 + www/analytics/plugins/Dashboard/lang/uk.json | 12 + www/analytics/plugins/Dashboard/lang/vi.json | 36 + .../plugins/Dashboard/lang/zh-cn.json | 36 + .../plugins/Dashboard/lang/zh-tw.json | 13 + .../Dashboard/stylesheets/dashboard.less | 230 +- .../Dashboard/stylesheets/standalone.css | 9 +- .../plugins/Dashboard/stylesheets/widget.less | 130 + .../templates/_dashboardSettings.twig | 8 +- .../plugins/Dashboard/templates/_header.twig | 6 +- .../templates/_widgetFactoryTemplate.twig | 46 +- .../Dashboard/templates/embeddedIndex.twig | 14 +- .../plugins/Dashboard/templates/index.twig | 5 +- www/analytics/plugins/DevicePlugins/API.php | 99 + .../plugins/DevicePlugins/Archiver.php | 84 + .../plugins/DevicePlugins/Columns/Plugin.php | 20 + .../DevicePlugins/Columns/PluginCookie.php | 32 + .../DevicePlugins/Columns/PluginDirector.php | 32 + .../DevicePlugins/Columns/PluginFlash.php | 32 + .../DevicePlugins/Columns/PluginGears.php | 32 + .../DevicePlugins/Columns/PluginJava.php | 32 + .../DevicePlugins/Columns/PluginPdf.php | 32 + .../DevicePlugins/Columns/PluginQuickTime.php | 32 + .../Columns/PluginRealPlayer.php | 32 + .../Columns/PluginSilverlight.php | 32 + .../Columns/PluginWindowsMedia.php | 32 + .../plugins/DevicePlugins/DevicePlugins.php | 55 + .../plugins/DevicePlugins/Reports/Base.php | 32 + .../DevicePlugins/Reports/GetPlugin.php | 53 + .../plugins/DevicePlugins/Visitor.php | 65 + .../plugins/DevicePlugins/functions.php | 20 + .../images/plugins/cookie.gif | Bin .../images/plugins/director.gif | Bin .../images/plugins/flash.gif | Bin .../images/plugins/gears.gif | Bin .../images/plugins/java.gif | Bin .../images/plugins/pdf.gif | Bin .../images/plugins/quicktime.gif | Bin .../images/plugins/realplayer.gif | Bin .../images/plugins/silverlight.gif | Bin .../images/plugins/windowsmedia.gif | Bin .../plugins/DevicePlugins/lang/am.json | 5 + .../plugins/DevicePlugins/lang/ar.json | 5 + .../plugins/DevicePlugins/lang/be.json | 6 + .../plugins/DevicePlugins/lang/bg.json | 8 + .../plugins/DevicePlugins/lang/ca.json | 9 + .../plugins/DevicePlugins/lang/cs.json | 10 + .../plugins/DevicePlugins/lang/da.json | 9 + .../plugins/DevicePlugins/lang/de.json | 10 + .../plugins/DevicePlugins/lang/el.json | 10 + .../plugins/DevicePlugins/lang/en.json | 10 + .../plugins/DevicePlugins/lang/es.json | 9 + .../plugins/DevicePlugins/lang/et.json | 5 + .../plugins/DevicePlugins/lang/eu.json | 5 + .../plugins/DevicePlugins/lang/fa.json | 7 + .../plugins/DevicePlugins/lang/fi.json | 8 + .../plugins/DevicePlugins/lang/fr.json | 9 + .../plugins/DevicePlugins/lang/gl.json | 5 + .../plugins/DevicePlugins/lang/hi.json | 8 + .../plugins/DevicePlugins/lang/hu.json | 5 + .../plugins/DevicePlugins/lang/id.json | 8 + .../plugins/DevicePlugins/lang/is.json | 6 + .../plugins/DevicePlugins/lang/it.json | 9 + .../plugins/DevicePlugins/lang/ja.json | 9 + .../plugins/DevicePlugins/lang/ka.json | 6 + .../plugins/DevicePlugins/lang/ko.json | 6 + .../plugins/DevicePlugins/lang/lt.json | 7 + .../plugins/DevicePlugins/lang/lv.json | 6 + .../plugins/DevicePlugins/lang/nb.json | 10 + .../plugins/DevicePlugins/lang/nl.json | 9 + .../plugins/DevicePlugins/lang/nn.json | 5 + .../plugins/DevicePlugins/lang/pl.json | 5 + .../plugins/DevicePlugins/lang/pt-br.json | 10 + .../plugins/DevicePlugins/lang/pt.json | 6 + .../plugins/DevicePlugins/lang/ro.json | 8 + .../plugins/DevicePlugins/lang/ru.json | 8 + .../plugins/DevicePlugins/lang/sk.json | 5 + .../plugins/DevicePlugins/lang/sl.json | 7 + .../plugins/DevicePlugins/lang/sq.json | 6 + .../plugins/DevicePlugins/lang/sr.json | 9 + .../plugins/DevicePlugins/lang/sv.json | 9 + .../plugins/DevicePlugins/lang/th.json | 5 + .../plugins/DevicePlugins/lang/tl.json | 8 + .../plugins/DevicePlugins/lang/tr.json | 5 + .../plugins/DevicePlugins/lang/uk.json | 5 + .../plugins/DevicePlugins/lang/vi.json | 8 + .../plugins/DevicePlugins/lang/zh-cn.json | 8 + .../plugins/DevicePlugins/lang/zh-tw.json | 5 + .../plugins/DevicesDetection/API.php | 153 +- .../plugins/DevicesDetection/Archiver.php | 22 +- .../plugins/DevicesDetection/Columns/Base.php | 20 + .../Columns/BrowserEngine.php | 57 + .../DevicesDetection/Columns/BrowserName.php | 56 + .../Columns/BrowserVersion.php | 56 + .../DevicesDetection/Columns/DeviceBrand.php | 63 + .../DevicesDetection/Columns/DeviceModel.php | 39 + .../DevicesDetection/Columns/DeviceType.php | 64 + .../plugins/DevicesDetection/Columns/Os.php | 57 + .../DevicesDetection/Columns/OsVersion.php | 49 + .../plugins/DevicesDetection/Controller.php | 153 +- .../DevicesDetection/DevicesDetection.php | 383 +- .../plugins/DevicesDetection/Menu.php | 33 + .../plugins/DevicesDetection/Reports/Base.php | 19 + .../DevicesDetection/Reports/GetBrand.php | 34 + .../Reports/GetBrowserEngines.php | 39 + .../Reports/GetBrowserVersions.php | 40 + .../DevicesDetection/Reports/GetBrowsers.php | 41 + .../DevicesDetection/Reports/GetModel.php | 34 + .../Reports/GetOsFamilies.php | 42 + .../Reports/GetOsVersions.php | 41 + .../DevicesDetection/Reports/GetType.php | 37 + .../plugins/DevicesDetection/Segment.php | 21 + .../plugins/DevicesDetection/Updates/1.14.php | 8 +- .../plugins/DevicesDetection/Visitor.php | 101 + .../plugins/DevicesDetection/functions.php | 236 +- .../DevicesDetection/images/brand/3Q.ico | Bin 0 -> 577 bytes .../DevicesDetection/images/brand/BBK.ico | Bin 0 -> 263 bytes .../images/brand/Barnes_Noble.ico | Bin 0 -> 799 bytes .../DevicesDetection/images/brand/Celkon.ico | Bin 0 -> 332 bytes .../images/brand/Cherry_Mobile.ico | Bin 0 -> 808 bytes .../DevicesDetection/images/brand/Compaq.ico | Bin 0 -> 453 bytes .../images/brand/ConCorde.ico | Bin 0 -> 602 bytes .../DevicesDetection/images/brand/Coolpad.ico | Bin 0 -> 485 bytes .../images/brand/Crius_Mea.ico | Bin 0 -> 566 bytes .../images/brand/Crosscall.ico | Bin 0 -> 3236 bytes .../DevicesDetection/images/brand/Danew.ico | Bin 0 -> 3221 bytes .../DevicesDetection/images/brand/Easypix.ico | Bin 0 -> 881 bytes .../DevicesDetection/images/brand/Evertek.ico | Bin 0 -> 571 bytes .../DevicesDetection/images/brand/Fujitsu.ico | Bin 0 -> 298 bytes .../images/brand/Gigabyte.ico | Bin 0 -> 343 bytes .../DevicesDetection/images/brand/Gigaset.ico | Bin 0 -> 354 bytes .../DevicesDetection/images/brand/Gionee.ico | Bin 0 -> 3018 bytes .../DevicesDetection/images/brand/Hyundai.ico | Bin 0 -> 407 bytes .../DevicesDetection/images/brand/Le_Pan.ico | Bin 0 -> 408 bytes .../DevicesDetection/images/brand/MSI.ico | Bin 0 -> 377 bytes .../DevicesDetection/images/brand/Nikon.ico | Bin 0 -> 607 bytes .../DevicesDetection/images/brand/OnePlus.ico | Bin 0 -> 181 bytes .../DevicesDetection/images/brand/Quechua.ico | Bin 0 -> 296 bytes .../DevicesDetection/images/brand/SFR.ico | Bin 0 -> 686 bytes .../DevicesDetection/images/brand/Sencor.ico | Bin 0 -> 885 bytes .../images/brand/Smartfren.ico | Bin 0 -> 691 bytes .../images/brand/Tecno_Mobile.ico | Bin 0 -> 437 bytes .../DevicesDetection/images/brand/Tolino.ico | Bin 0 -> 321 bytes .../images/brand/Tunisie_Telecom.ico | Bin 0 -> 3463 bytes .../images/brand/{unknown.ico => Unknown.ico} | Bin .../DevicesDetection/images/brand/Wiko.ico | Bin 0 -> 1558 bytes .../DevicesDetection/images/brand/Wolder.ico | Bin 0 -> 513 bytes .../DevicesDetection/images/brand/Woxter.ico | Bin 0 -> 775 bytes .../DevicesDetection/images/brand/Yarvik.ico | Bin 0 -> 439 bytes .../DevicesDetection/images/brand/Zopo.ico | Bin 0 -> 397 bytes .../DevicesDetection/images/brand/bq.ico | Bin 0 -> 497 bytes .../DevicesDetection/images/brand/iBerry.ico | Bin 0 -> 3613 bytes .../DevicesDetection/images/brand/teXet.ico | Bin 0 -> 643 bytes .../DevicesDetection/images/browsers/36.gif | Bin 0 -> 1036 bytes .../images/browsers/AA.gif | Bin .../images/browsers/AB.gif | Bin .../images/browsers/AG.gif | Bin .../images/browsers/AM.gif | Bin .../images/browsers/AN.gif | Bin .../images/browsers/AR.gif | Bin .../images/browsers/AV.gif | Bin .../images/browsers/AW.gif | Bin .../images/browsers/B2.gif | Bin .../images/browsers/BB.gif | Bin .../images/browsers/BD.gif | Bin .../images/browsers/BE.gif | Bin .../DevicesDetection/images/browsers/BJ.gif | Bin 0 -> 949 bytes .../images/browsers/BP.gif | Bin .../DevicesDetection/images/browsers/BS.gif | Bin 0 -> 980 bytes .../images/browsers/BX.gif | Bin .../images/browsers/CA.gif | Bin .../DevicesDetection/images/browsers/CC.gif | Bin 0 -> 435 bytes .../images/browsers/CD.gif | Bin .../images/browsers/CF.gif | Bin .../images/browsers/CH.gif | Bin .../images/browsers/CK.gif | Bin .../images/browsers/CM.gif | Bin .../images/browsers/CN.gif | Bin .../images/browsers/CO.gif | Bin .../images/browsers/CP.gif | Bin .../DevicesDetection/images/browsers/CR.gif | Bin 0 -> 1007 bytes .../images/browsers/CS.gif | Bin .../DevicesDetection/images/browsers/CX.gif | Bin 0 -> 1067 bytes .../DevicesDetection/images/browsers/DE.gif | Bin 0 -> 1073 bytes .../images/browsers/DF.gif | Bin .../images/browsers/DI.gif | Bin .../images/browsers/EL.gif | Bin .../images/browsers/EP.gif | Bin .../images/browsers/ES.gif | Bin .../images/browsers/FB.gif | Bin .../images/browsers/FD.gif | Bin .../images/browsers/FE.gif | Bin .../images/browsers/FF.gif | Bin .../images/browsers/FL.gif | Bin .../images/browsers/FN.gif | Bin .../images/browsers/GA.gif | Bin .../images/browsers/GE.gif | Bin .../images/browsers/HA.gif | Bin .../images/browsers/HJ.gif | Bin .../images/browsers/IA.gif | Bin .../images/browsers/IB.gif | Bin .../images/browsers/IC.gif | Bin .../images/browsers/ID.gif | Bin .../images/browsers/IE.gif | Bin .../DevicesDetection/images/browsers/IM.gif | Bin 0 -> 999 bytes .../images/browsers/IR.gif | Bin .../images/browsers/IW.gif | Bin .../images/browsers/KI.gif | Bin .../images/browsers/KM.gif | Bin .../images/browsers/KO.gif | Bin .../images/browsers/KP.gif | Bin .../images/browsers/KZ.gif | Bin .../DevicesDetection/images/browsers/LB.gif | Bin 0 -> 991 bytes .../DevicesDetection/images/browsers/LG.gif | Bin 0 -> 1015 bytes .../images/browsers/LI.gif | Bin .../images/browsers/LS.gif | Bin .../images/browsers/LX.gif | Bin .../images/browsers/MC.gif | Bin .../images/browsers/MF.gif} | Bin .../images/browsers/MI.gif | Bin .../images/browsers/MO.gif | Bin .../images/browsers/MS.gif | Bin .../DevicesDetection/images/browsers/MU.gif | Bin 0 -> 1031 bytes .../images/browsers/MX.gif | Bin .../images/browsers/NB.gif | Bin .../images/browsers/NF.gif | Bin .../images/browsers/NL.gif | Bin .../images/browsers/NP.gif | Bin .../images/browsers/NS.gif | Bin .../images/browsers/OB.gif | Bin .../DevicesDetection/images/browsers/OE.gif | Bin 0 -> 562 bytes .../DevicesDetection/images/browsers/OF.gif | Bin 0 -> 861 bytes .../DevicesDetection/images/browsers/OI.gif | Bin 0 -> 911 bytes .../images/browsers/ON.gif | Bin .../images/browsers/OP.gif | Bin .../images/browsers/OR.gif | Bin .../images/browsers/OV.gif | Bin .../images/browsers/OW.gif | Bin .../images/browsers/PL.gif | Bin .../images/browsers/PM.gif | Bin .../images/browsers/PO.gif | Bin .../images/browsers/PU.gif | Bin .../images/browsers/PW.gif | Bin .../images/browsers/PX.gif | Bin .../DevicesDetection/images/browsers/QQ.gif | Bin 0 -> 1080 bytes .../images/browsers/RK.gif | Bin .../images/browsers/SA.gif | Bin .../DevicesDetection/images/browsers/SE.gif | Bin 0 -> 996 bytes .../DevicesDetection/images/browsers/SF.gif | Bin 0 -> 190 bytes .../DevicesDetection/images/browsers/SH.gif | Bin 0 -> 1001 bytes .../images/browsers/SL.gif | Bin .../images/browsers/SM.gif | Bin .../DevicesDetection/images/browsers/SR.gif | Bin 0 -> 1013 bytes .../images/browsers/TB.gif | Bin .../images/browsers/TI.gif | Bin .../images/browsers/TZ.gif | Bin .../images/browsers/UC.gif | Bin .../images/browsers/UN.gif | Bin .../images/browsers/UNK.gif | Bin .../DevicesDetection/images/browsers/VI.gif | Bin 0 -> 2124 bytes .../images/browsers/WE.gif | Bin .../images/browsers/WO.gif | Bin .../images/browsers/WP.gif | Bin .../images/browsers/YA.gif | Bin .../images/os/3DS.gif | Bin .../images/os/AIX.gif | Bin .../images/os/AMG.gif | Bin .../images/os/AMI.gif | Bin .../images/os/AND.gif | Bin .../images/os/ARL.gif | Bin .../images/os/BBX.gif | Bin .../images/os/BEO.gif | Bin .../images/os/BLB.gif | Bin .../DevicesDetection/images/os/BMP.gif | Bin 0 -> 1001 bytes .../images/os/BSD.gif | Bin .../images/os/BTR.gif | Bin .../images/os/CES.gif | Bin .../images/os/COS.gif | Bin .../images/os/DFB.gif | Bin .../images/os/DSI.gif | Bin .../images/os/FED.gif | Bin .../images/os/FOS.gif | Bin .../images/os/GNT.gif | Bin .../images/os/GTV.gif | Bin .../images/os/HPX.gif | Bin .../DevicesDetection/images/os/IOS.gif | Bin 0 -> 594 bytes .../images/os/IPA.gif | Bin .../images/os/IPD.gif | Bin .../images/os/IPH.gif | Bin .../images/os/IRI.gif | Bin .../images/os/KBT.gif | Bin .../images/os/KNO.gif | Bin .../images/os/LBT.gif | Bin .../images/os/LIN.gif | Bin .../images/os/MAC.gif | Bin .../images/os/MAE.gif | Bin .../images/os/MDR.gif | Bin .../images/os/MIN.gif | Bin .../images/os/NBS.gif | Bin .../images/os/NDS.gif | Bin .../images/os/OBS.gif | Bin .../images/os/OS2.gif | Bin .../images/os/POS.gif | Bin .../images/os/PPY.gif | Bin .../images/os/PS3.gif | Bin .../images/os/PSP.gif | Bin .../images/os/PSV.gif | Bin .../images/os/QNX.gif | Bin .../images/os/RHT.gif | Bin .../images/os/ROS.gif | Bin .../images/os/SAF.gif | Bin .../images/os/SBA.gif | Bin .../images/os/SLW.gif | Bin .../images/os/SOS.gif | Bin .../images/os/SSE.gif | Bin .../images/os/SYL.gif | Bin .../images/os/SYM.gif | Bin .../images/os/T64.gif | Bin .../DevicesDetection/images/os/TDX.gif | Bin 0 -> 1001 bytes .../images/os/TIZ.gif | Bin .../images/os/UBT.gif | Bin .../images/os/UNK.gif | Bin .../images/os/VMS.gif | Bin .../images/os/WCE.gif | Bin .../images/os/WII.gif | Bin .../images/os/WIN.gif | Bin .../images/os/WIU.gif | Bin .../images/os/WMO.gif | Bin .../images/os/WOS.gif | Bin .../images/os/WPH.gif | Bin .../images/os/WRT.gif | Bin .../images/os/XBT.gif | Bin .../images/os/XBX.gif | Bin .../images/os/YNS.gif | Bin .../plugins/DevicesDetection/lang/am.json | 9 + .../plugins/DevicesDetection/lang/ar.json | 13 + .../plugins/DevicesDetection/lang/be.json | 15 + .../plugins/DevicesDetection/lang/bg.json | 17 +- .../plugins/DevicesDetection/lang/ca.json | 16 + .../plugins/DevicesDetection/lang/cs.json | 35 +- .../plugins/DevicesDetection/lang/da.json | 22 +- .../plugins/DevicesDetection/lang/de.json | 35 +- .../plugins/DevicesDetection/lang/el.json | 23 +- .../plugins/DevicesDetection/lang/en.json | 55 +- .../plugins/DevicesDetection/lang/es.json | 35 +- .../plugins/DevicesDetection/lang/et.json | 16 +- .../plugins/DevicesDetection/lang/eu.json | 11 + .../plugins/DevicesDetection/lang/fa.json | 15 +- .../plugins/DevicesDetection/lang/fi.json | 21 +- .../plugins/DevicesDetection/lang/fr.json | 33 +- .../plugins/DevicesDetection/lang/gl.json | 7 + .../plugins/DevicesDetection/lang/he.json | 9 + .../plugins/DevicesDetection/lang/hi.json | 26 + .../plugins/DevicesDetection/lang/hr.json | 9 + .../plugins/DevicesDetection/lang/hu.json | 12 + .../plugins/DevicesDetection/lang/id.json | 16 + .../plugins/DevicesDetection/lang/is.json | 12 + .../plugins/DevicesDetection/lang/it.json | 25 +- .../plugins/DevicesDetection/lang/ja.json | 23 +- .../plugins/DevicesDetection/lang/ka.json | 12 + .../plugins/DevicesDetection/lang/ko.json | 44 + .../plugins/DevicesDetection/lang/lt.json | 31 + .../plugins/DevicesDetection/lang/lv.json | 15 + .../plugins/DevicesDetection/lang/nb.json | 37 +- .../plugins/DevicesDetection/lang/nl.json | 25 +- .../plugins/DevicesDetection/lang/nn.json | 12 + .../plugins/DevicesDetection/lang/pl.json | 37 + .../plugins/DevicesDetection/lang/pt-br.json | 26 +- .../plugins/DevicesDetection/lang/pt.json | 24 + .../plugins/DevicesDetection/lang/ro.json | 19 +- .../plugins/DevicesDetection/lang/ru.json | 32 +- .../plugins/DevicesDetection/lang/sk.json | 12 + .../plugins/DevicesDetection/lang/sl.json | 20 + .../plugins/DevicesDetection/lang/sq.json | 15 + .../plugins/DevicesDetection/lang/sr.json | 34 +- .../plugins/DevicesDetection/lang/sv.json | 39 +- .../plugins/DevicesDetection/lang/ta.json | 5 + .../plugins/DevicesDetection/lang/te.json | 14 + .../plugins/DevicesDetection/lang/th.json | 13 + .../plugins/DevicesDetection/lang/tl.json | 37 + .../plugins/DevicesDetection/lang/tr.json | 37 + .../plugins/DevicesDetection/lang/uk.json | 12 + .../plugins/DevicesDetection/lang/vi.json | 16 + .../plugins/DevicesDetection/lang/zh-cn.json | 16 + .../plugins/DevicesDetection/lang/zh-tw.json | 13 + .../plugins/DevicesDetection/plugin.json | 8 + .../DevicesDetection/templates/detection.twig | 34 +- .../DevicesDetection/templates/devices.twig | 19 + .../DevicesDetection/templates/index.twig | 15 - .../DevicesDetection/templates/software.twig | 23 + .../Commands/AnalyzeArchiveTable.php | 82 + .../plugins/Diagnostics/Commands/Run.php | 89 + .../plugins/Diagnostics/ConfigReader.php | 193 + .../plugins/Diagnostics/Controller.php | 66 + .../Diagnostic/CronArchivingCheck.php | 48 + .../Diagnostics/Diagnostic/DbAdapterCheck.php | 106 + .../Diagnostics/Diagnostic/Diagnostic.php | 44 + .../Diagnostic/DiagnosticResult.php | 121 + .../Diagnostic/DiagnosticResultItem.php | 49 + .../Diagnostic/FileIntegrityCheck.php | 56 + .../Diagnostic/GdExtensionCheck.php | 45 + .../Diagnostic/HttpClientCheck.php | 50 + .../Diagnostic/LoadDataInfileCheck.php | 87 + .../Diagnostic/MemoryLimitCheck.php | 57 + .../Diagnostics/Diagnostic/NfsDiskCheck.php | 55 + .../Diagnostics/Diagnostic/PageSpeedCheck.php | 80 + .../Diagnostic/PhpExtensionsCheck.php | 85 + .../Diagnostic/PhpFunctionsCheck.php | 111 + .../Diagnostic/PhpSettingsCheck.php | 86 + .../Diagnostic/PhpVersionCheck.php | 58 + .../Diagnostic/RecommendedExtensionsCheck.php | 77 + .../Diagnostic/RecommendedFunctionsCheck.php | 76 + .../Diagnostics/Diagnostic/TimezoneCheck.php | 45 + .../Diagnostics/Diagnostic/TrackerCheck.php | 49 + .../Diagnostic/WriteAccessCheck.php | 99 + .../plugins/Diagnostics/DiagnosticReport.php | 118 + .../plugins/Diagnostics/DiagnosticService.php | 73 + .../plugins/Diagnostics/Diagnostics.php | 30 + www/analytics/plugins/Diagnostics/Menu.php | 28 + .../Commands/AnalyzeArchiveTableTest.php | 99 + .../Test/Integration/ConfigReaderTest.php | 261 + .../Test/Mock/DiagnosticWithError.php | 22 + .../Test/Mock/DiagnosticWithSuccess.php | 22 + .../Test/Mock/DiagnosticWithWarning.php | 22 + .../Unit/Diagnostic/DiagnosticResultTest.php | 37 + .../Test/Unit/DiagnosticReportTest.php | 47 + .../Test/Unit/DiagnosticServiceTest.php | 43 + .../plugins/Diagnostics/config/config.php | 39 + .../plugins/Diagnostics/lang/cs.json | 8 + .../plugins/Diagnostics/lang/de.json | 8 + .../plugins/Diagnostics/lang/el.json | 8 + .../plugins/Diagnostics/lang/en.json | 8 + .../plugins/Diagnostics/lang/it.json | 6 + .../plugins/Diagnostics/lang/pt-br.json | 8 + .../plugins/Diagnostics/lang/sv.json | 5 + www/analytics/plugins/Diagnostics/plugin.json | 3 + .../Diagnostics/stylesheets/configfile.less | 22 + .../Diagnostics/templates/configfile.twig | 55 + .../Ecommerce/Columns/BaseConversion.php | 36 + .../Ecommerce/Columns/ProductCategory.php | 20 + .../plugins/Ecommerce/Columns/ProductName.php | 20 + .../plugins/Ecommerce/Columns/ProductSku.php | 20 + .../plugins/Ecommerce/Columns/Revenue.php | 65 + .../Ecommerce/Columns/RevenueDiscount.php | 33 + .../Ecommerce/Columns/RevenueShipping.php | 33 + .../Ecommerce/Columns/RevenueSubtotal.php | 33 + .../plugins/Ecommerce/Columns/RevenueTax.php | 33 + .../plugins/Ecommerce/Controller.php | 124 + www/analytics/plugins/Ecommerce/Menu.php | 39 + .../plugins/Ecommerce/Reports/Base.php | 75 + .../plugins/Ecommerce/Reports/BaseItem.php | 134 + .../GetDaysToConversionAbandonedCart.php | 32 + .../GetDaysToConversionEcommerceOrder.php | 32 + .../Reports/GetEcommerceAbandonedCart.php | 36 + .../Ecommerce/Reports/GetEcommerceOrder.php | 45 + .../Ecommerce/Reports/GetItemsCategory.php | 25 + .../Ecommerce/Reports/GetItemsName.php | 25 + .../plugins/Ecommerce/Reports/GetItemsSku.php | 27 + .../GetVisitsUntilConversionAbandonedCart.php | 32 + ...GetVisitsUntilConversionEcommerceOrder.php | 32 + .../Tracker/EcommerceRequestProcessor.php | 87 + www/analytics/plugins/Ecommerce/Widgets.php | 35 + www/analytics/plugins/Ecommerce/lang/bg.json | 5 + www/analytics/plugins/Ecommerce/lang/cs.json | 8 + www/analytics/plugins/Ecommerce/lang/da.json | 5 + www/analytics/plugins/Ecommerce/lang/de.json | 8 + www/analytics/plugins/Ecommerce/lang/el.json | 8 + www/analytics/plugins/Ecommerce/lang/en.json | 8 + www/analytics/plugins/Ecommerce/lang/es.json | 8 + www/analytics/plugins/Ecommerce/lang/fr.json | 8 + www/analytics/plugins/Ecommerce/lang/hi.json | 8 + www/analytics/plugins/Ecommerce/lang/it.json | 8 + www/analytics/plugins/Ecommerce/lang/ja.json | 8 + www/analytics/plugins/Ecommerce/lang/nb.json | 8 + www/analytics/plugins/Ecommerce/lang/nl.json | 8 + .../plugins/Ecommerce/lang/pt-br.json | 8 + www/analytics/plugins/Ecommerce/lang/pt.json | 5 + www/analytics/plugins/Ecommerce/lang/ru.json | 5 + www/analytics/plugins/Ecommerce/lang/sk.json | 5 + www/analytics/plugins/Ecommerce/lang/sr.json | 8 + www/analytics/plugins/Ecommerce/lang/sv.json | 7 + www/analytics/plugins/Ecommerce/lang/ta.json | 7 + www/analytics/plugins/Ecommerce/plugin.json | 3 + .../Ecommerce/templates/ecommerceLog.twig | 3 + .../plugins/Ecommerce/templates/products.twig | 3 + .../plugins/Ecommerce/templates/sales.twig | 3 + www/analytics/plugins/Events/API.php | 100 +- .../plugins/Events/Actions/ActionEvent.php | 96 + www/analytics/plugins/Events/Archiver.php | 23 +- .../plugins/Events/Columns/EventAction.php | 56 + .../plugins/Events/Columns/EventCategory.php | 56 + .../plugins/Events/Columns/EventName.php | 55 + .../Columns/Metrics/AverageEventValue.php | 45 + .../plugins/Events/Columns/TotalEvents.php | 77 + www/analytics/plugins/Events/Controller.php | 29 +- .../Filter/ReplaceEventNameNotSet.php | 39 + www/analytics/plugins/Events/Events.php | 192 +- www/analytics/plugins/Events/Menu.php | 21 + www/analytics/plugins/Events/Reports/Base.php | 28 + .../plugins/Events/Reports/GetAction.php | 32 + .../Reports/GetActionFromCategoryId.php | 29 + .../Events/Reports/GetActionFromNameId.php | 29 + .../plugins/Events/Reports/GetCategory.php | 32 + .../Reports/GetCategoryFromActionId.php | 29 + .../Events/Reports/GetCategoryFromNameId.php | 29 + .../plugins/Events/Reports/GetName.php | 32 + .../Events/Reports/GetNameFromActionId.php | 29 + .../Events/Reports/GetNameFromCategoryId.php | 29 + www/analytics/plugins/Events/Segment.php | 22 + www/analytics/plugins/Events/lang/bg.json | 15 +- www/analytics/plugins/Events/lang/ca.json | 16 + www/analytics/plugins/Events/lang/cs.json | 32 + www/analytics/plugins/Events/lang/da.json | 23 +- www/analytics/plugins/Events/lang/de.json | 13 +- www/analytics/plugins/Events/lang/el.json | 24 +- www/analytics/plugins/Events/lang/en.json | 35 +- www/analytics/plugins/Events/lang/es.json | 21 +- www/analytics/plugins/Events/lang/fa.json | 5 +- www/analytics/plugins/Events/lang/fi.json | 21 +- www/analytics/plugins/Events/lang/fr.json | 24 +- www/analytics/plugins/Events/lang/hi.json | 22 + www/analytics/plugins/Events/lang/it.json | 13 +- www/analytics/plugins/Events/lang/ja.json | 24 +- www/analytics/plugins/Events/lang/lt.json | 9 + www/analytics/plugins/Events/lang/nb.json | 31 + www/analytics/plugins/Events/lang/nl.json | 24 +- www/analytics/plugins/Events/lang/pl.json | 29 + www/analytics/plugins/Events/lang/pt-br.json | 25 +- www/analytics/plugins/Events/lang/pt.json | 6 + www/analytics/plugins/Events/lang/ro.json | 12 +- www/analytics/plugins/Events/lang/ru.json | 30 + www/analytics/plugins/Events/lang/sk.json | 14 + www/analytics/plugins/Events/lang/sl.json | 6 + www/analytics/plugins/Events/lang/sr.json | 21 +- www/analytics/plugins/Events/lang/sv.json | 24 +- www/analytics/plugins/Events/lang/tl.json | 31 + www/analytics/plugins/Events/lang/tr.json | 30 + www/analytics/plugins/Events/plugin.json | 6 - .../plugins/Events/stylesheets/datatable.less | 7 + www/analytics/plugins/ExampleAPI/API.php | 8 +- .../plugins/ExampleAPI/ExampleAPI.php | 2 +- www/analytics/plugins/ExampleAPI/plugin.json | 2 +- .../ExampleCommand/Commands/HelloWorld.php | 27 +- .../plugins/ExampleCommand/plugin.json | 2 +- .../plugins/ExamplePlugin/.gitignore | 1 - .../plugins/ExamplePlugin/.travis.yml | 42 - www/analytics/plugins/ExamplePlugin/API.php | 29 +- .../plugins/ExamplePlugin/Archiver.php | 62 + .../plugins/ExamplePlugin/Controller.php | 19 +- .../plugins/ExamplePlugin/ExamplePlugin.php | 20 +- www/analytics/plugins/ExamplePlugin/Menu.php | 58 + www/analytics/plugins/ExamplePlugin/README.md | 3 +- www/analytics/plugins/ExamplePlugin/Tasks.php | 37 + .../plugins/ExamplePlugin/Updates/0.0.2.php | 63 + .../plugins/ExamplePlugin/Widgets.php | 67 + .../component.controller.js | 23 + .../component.directive.html | 3 + .../component.directive.js | 44 + .../component.directive.less | 3 + .../ExamplePlugin/javascripts/plugin.js | 4 +- .../plugins/ExamplePlugin/plugin.json | 28 +- .../ExamplePlugin/screenshots/.gitkeep | 0 .../ExamplePlugin/templates/index.twig | 10 +- www/analytics/plugins/ExampleReport/API.php | 38 + .../plugins/ExampleReport/ExampleReport.php | 13 + .../plugins/ExampleReport/Reports/Base.php | 19 + .../Reports/GetExampleReport.php | 116 + .../plugins/ExampleReport/plugin.json | 13 + .../plugins/ExampleRssWidget/Controller.php | 53 - .../ExampleRssWidget/ExampleRssWidget.php | 17 +- .../plugins/ExampleRssWidget/RssRenderer.php | 4 +- .../plugins/ExampleRssWidget/Widgets.php | 64 + .../plugins/ExampleRssWidget/plugin.json | 2 +- .../ExampleSettingsPlugin/Settings.php | 11 +- .../plugins/ExampleSettingsPlugin/plugin.json | 2 +- www/analytics/plugins/ExampleTheme/README.md | 2 - .../plugins/ExampleTheme/plugin.json | 14 +- .../ExampleTheme/stylesheets/theme.less | 38 + .../Columns/ExampleActionDimension.php | 136 + .../Columns/ExampleConversionDimension.php | 133 + .../Columns/ExampleDimension.php | 30 + .../Columns/ExampleVisitDimension.php | 157 + .../plugins/ExampleTracker/ExampleTracker.php | 13 + .../plugins/ExampleTracker/lang/en.json | 5 + .../plugins/ExampleTracker/plugin.json | 13 + www/analytics/plugins/ExampleUI/API.php | 2 +- .../plugins/ExampleUI/Controller.php | 17 +- www/analytics/plugins/ExampleUI/ExampleUI.php | 55 - www/analytics/plugins/ExampleUI/Menu.php | 44 + www/analytics/plugins/ExampleUI/plugin.json | 2 +- .../ExampleUI/templates/notifications.twig | 13 +- .../ExampleVisualization.php | 16 +- .../{ => Visualizations}/SimpleTable.php | 4 +- .../ExampleVisualization/images/table.png | Bin 151 -> 1056 bytes .../plugins/ExampleVisualization/plugin.json | 2 +- www/analytics/plugins/Feedback/API.php | 51 +- www/analytics/plugins/Feedback/Controller.php | 10 +- www/analytics/plugins/Feedback/Feedback.php | 32 +- www/analytics/plugins/Feedback/Menu.php | 25 + .../ratefeature/ratefeature-controller.js | 23 - .../ratefeature/ratefeature-directive.js | 22 - .../ratefeature/ratefeature-model.js | 22 - .../ratefeature/ratefeature-model.service.js | 29 + .../ratefeature/ratefeature.controller.js | 33 + .../ratefeature/ratefeature.directive.html | 38 + .../ratefeature/ratefeature.directive.js | 29 + ...eature.less => ratefeature.directive.less} | 0 .../angularjs/ratefeature/ratefeature.html | 38 - www/analytics/plugins/Feedback/lang/ar.json | 11 + www/analytics/plugins/Feedback/lang/be.json | 11 + www/analytics/plugins/Feedback/lang/bg.json | 13 + www/analytics/plugins/Feedback/lang/bn.json | 5 + www/analytics/plugins/Feedback/lang/ca.json | 12 + www/analytics/plugins/Feedback/lang/cs.json | 34 + www/analytics/plugins/Feedback/lang/da.json | 22 + www/analytics/plugins/Feedback/lang/de.json | 34 + www/analytics/plugins/Feedback/lang/el.json | 34 + www/analytics/plugins/Feedback/lang/en.json | 34 + www/analytics/plugins/Feedback/lang/es.json | 34 + www/analytics/plugins/Feedback/lang/et.json | 6 + www/analytics/plugins/Feedback/lang/fa.json | 12 + www/analytics/plugins/Feedback/lang/fi.json | 16 + www/analytics/plugins/Feedback/lang/fr.json | 34 + www/analytics/plugins/Feedback/lang/gl.json | 8 + www/analytics/plugins/Feedback/lang/hi.json | 16 + www/analytics/plugins/Feedback/lang/hu.json | 11 + www/analytics/plugins/Feedback/lang/id.json | 12 + www/analytics/plugins/Feedback/lang/it.json | 34 + www/analytics/plugins/Feedback/lang/ja.json | 34 + www/analytics/plugins/Feedback/lang/ka.json | 11 + www/analytics/plugins/Feedback/lang/ko.json | 34 + www/analytics/plugins/Feedback/lang/lt.json | 18 + www/analytics/plugins/Feedback/lang/lv.json | 10 + www/analytics/plugins/Feedback/lang/nb.json | 21 + www/analytics/plugins/Feedback/lang/nl.json | 34 + www/analytics/plugins/Feedback/lang/nn.json | 11 + www/analytics/plugins/Feedback/lang/pl.json | 18 + .../plugins/Feedback/lang/pt-br.json | 34 + www/analytics/plugins/Feedback/lang/pt.json | 11 + www/analytics/plugins/Feedback/lang/ro.json | 20 + www/analytics/plugins/Feedback/lang/ru.json | 32 + www/analytics/plugins/Feedback/lang/sk.json | 6 + www/analytics/plugins/Feedback/lang/sl.json | 11 + www/analytics/plugins/Feedback/lang/sq.json | 34 + www/analytics/plugins/Feedback/lang/sr.json | 34 + www/analytics/plugins/Feedback/lang/sv.json | 26 + www/analytics/plugins/Feedback/lang/ta.json | 9 + www/analytics/plugins/Feedback/lang/th.json | 11 + www/analytics/plugins/Feedback/lang/tl.json | 21 + www/analytics/plugins/Feedback/lang/tr.json | 15 + www/analytics/plugins/Feedback/lang/uk.json | 11 + www/analytics/plugins/Feedback/lang/vi.json | 12 + .../plugins/Feedback/lang/zh-cn.json | 12 + .../plugins/Feedback/lang/zh-tw.json | 12 + .../Feedback/stylesheets/feedback.less | 18 +- .../plugins/Feedback/templates/index.twig | 78 +- www/analytics/plugins/Goals/API.php | 344 +- www/analytics/plugins/Goals/Archiver.php | 65 +- .../Goals/Columns/DaysToConversion.php | 20 + .../plugins/Goals/Columns/IdGoal.php | 33 + .../Columns/Metrics/AverageOrderRevenue.php | 61 + .../Goals/Columns/Metrics/AveragePrice.php | 64 + .../Goals/Columns/Metrics/AverageQuantity.php | 47 + .../GoalSpecific/AverageOrderRevenue.php | 68 + .../Metrics/GoalSpecific/ConversionRate.php | 63 + .../Metrics/GoalSpecific/Conversions.php | 43 + .../Metrics/GoalSpecific/ItemsCount.php | 48 + .../Columns/Metrics/GoalSpecific/Revenue.php | 61 + .../Metrics/GoalSpecific/RevenuePerVisit.php | 76 + .../Metrics/GoalSpecificProcessedMetric.php | 104 + .../Columns/Metrics/ProductConversionRate.php | 54 + .../Goals/Columns/Metrics/RevenuePerVisit.php | 84 + .../Goals/Columns/VisitsUntilConversion.php | 20 + www/analytics/plugins/Goals/Controller.php | 248 +- .../Filter/AppendNameToColumnNames.php | 57 + www/analytics/plugins/Goals/Goals.php | 557 +- www/analytics/plugins/Goals/Menu.php | 80 + www/analytics/plugins/Goals/Model.php | 95 + www/analytics/plugins/Goals/Reports/Base.php | 63 + www/analytics/plugins/Goals/Reports/Get.php | 40 + .../Goals/Reports/GetDaysToConversion.php | 68 + .../plugins/Goals/Reports/GetMetrics.php | 32 + .../Reports/GetVisitsUntilConversion.php | 68 + .../Goals/Tracker/GoalsRequestProcessor.php | 136 + .../plugins/Goals/TranslationHelper.php | 114 + .../plugins/Goals/Visualizations/Goals.php | 168 +- www/analytics/plugins/Goals/Widgets.php | 39 + .../plugins/Goals/javascripts/goalsForm.js | 88 +- www/analytics/plugins/Goals/lang/am.json | 5 + www/analytics/plugins/Goals/lang/ar.json | 56 + www/analytics/plugins/Goals/lang/be.json | 85 + www/analytics/plugins/Goals/lang/bg.json | 89 + www/analytics/plugins/Goals/lang/bs.json | 14 + www/analytics/plugins/Goals/lang/ca.json | 89 + www/analytics/plugins/Goals/lang/cs.json | 107 + www/analytics/plugins/Goals/lang/da.json | 99 + www/analytics/plugins/Goals/lang/de.json | 107 + www/analytics/plugins/Goals/lang/el.json | 107 + www/analytics/plugins/Goals/lang/en.json | 107 + www/analytics/plugins/Goals/lang/es.json | 105 + www/analytics/plugins/Goals/lang/et.json | 65 + www/analytics/plugins/Goals/lang/eu.json | 23 + www/analytics/plugins/Goals/lang/fa.json | 79 + www/analytics/plugins/Goals/lang/fi.json | 92 + www/analytics/plugins/Goals/lang/fr.json | 107 + www/analytics/plugins/Goals/lang/gl.json | 11 + www/analytics/plugins/Goals/lang/he.json | 5 + www/analytics/plugins/Goals/lang/hi.json | 99 + www/analytics/plugins/Goals/lang/hu.json | 48 + www/analytics/plugins/Goals/lang/id.json | 90 + www/analytics/plugins/Goals/lang/is.json | 38 + www/analytics/plugins/Goals/lang/it.json | 107 + www/analytics/plugins/Goals/lang/ja.json | 107 + www/analytics/plugins/Goals/lang/ka.json | 48 + www/analytics/plugins/Goals/lang/ko.json | 107 + www/analytics/plugins/Goals/lang/lt.json | 46 + www/analytics/plugins/Goals/lang/lv.json | 49 + www/analytics/plugins/Goals/lang/nb.json | 57 + www/analytics/plugins/Goals/lang/nl.json | 105 + www/analytics/plugins/Goals/lang/nn.json | 44 + www/analytics/plugins/Goals/lang/pl.json | 75 + www/analytics/plugins/Goals/lang/pt-br.json | 107 + www/analytics/plugins/Goals/lang/pt.json | 89 + www/analytics/plugins/Goals/lang/ro.json | 90 + www/analytics/plugins/Goals/lang/ru.json | 103 + www/analytics/plugins/Goals/lang/sk.json | 54 + www/analytics/plugins/Goals/lang/sl.json | 42 + www/analytics/plugins/Goals/lang/sq.json | 107 + www/analytics/plugins/Goals/lang/sr.json | 107 + www/analytics/plugins/Goals/lang/sv.json | 105 + www/analytics/plugins/Goals/lang/ta.json | 25 + www/analytics/plugins/Goals/lang/te.json | 8 + www/analytics/plugins/Goals/lang/th.json | 58 + www/analytics/plugins/Goals/lang/tr.json | 54 + www/analytics/plugins/Goals/lang/uk.json | 48 + www/analytics/plugins/Goals/lang/vi.json | 90 + www/analytics/plugins/Goals/lang/zh-cn.json | 93 + www/analytics/plugins/Goals/lang/zh-tw.json | 47 + .../plugins/Goals/stylesheets/goals.css | 8 + .../plugins/Goals/templates/_addEditGoal.twig | 94 +- .../plugins/Goals/templates/_formAddGoal.twig | 42 +- .../Goals/templates/_listGoalEdit.twig | 78 +- .../Goals/templates/_listTopDimension.twig | 4 +- .../templates/_titleAndEvolutionGraph.twig | 33 +- .../plugins/Goals/templates/addNewGoal.twig | 12 +- .../plugins/Goals/templates/editGoals.twig | 16 + .../Goals/templates/getGoalReportView.twig | 6 +- .../Goals/templates/getOverviewView.twig | 46 +- .../plugins/Goals/templates/manageGoals.twig | 40 + www/analytics/plugins/Heartbeat/Heartbeat.php | 19 + .../Tracker/PingRequestProcessor.php | 46 + www/analytics/plugins/Heartbeat/plugin.json | 3 + www/analytics/plugins/ImageGraph/API.php | 101 +- .../plugins/ImageGraph/Controller.php | 7 +- .../plugins/ImageGraph/ImageGraph.php | 68 +- .../plugins/ImageGraph/StaticGraph.php | 70 +- .../ImageGraph/StaticGraph/Evolution.php | 3 +- .../ImageGraph/StaticGraph/Exception.php | 2 +- .../ImageGraph/StaticGraph/GridGraph.php | 12 +- .../ImageGraph/StaticGraph/HorizontalBar.php | 3 +- .../plugins/ImageGraph/StaticGraph/Pie.php | 3 +- .../plugins/ImageGraph/StaticGraph/Pie3D.php | 2 +- .../ImageGraph/StaticGraph/PieGraph.php | 4 +- .../ImageGraph/StaticGraph/VerticalBar.php | 3 +- www/analytics/plugins/ImageGraph/lang/bg.json | 5 + www/analytics/plugins/ImageGraph/lang/ca.json | 5 + www/analytics/plugins/ImageGraph/lang/cs.json | 6 + www/analytics/plugins/ImageGraph/lang/da.json | 6 + www/analytics/plugins/ImageGraph/lang/de.json | 6 + www/analytics/plugins/ImageGraph/lang/el.json | 6 + www/analytics/plugins/ImageGraph/lang/en.json | 6 + www/analytics/plugins/ImageGraph/lang/es.json | 6 + www/analytics/plugins/ImageGraph/lang/fa.json | 5 + www/analytics/plugins/ImageGraph/lang/fi.json | 5 + www/analytics/plugins/ImageGraph/lang/fr.json | 6 + www/analytics/plugins/ImageGraph/lang/hi.json | 6 + www/analytics/plugins/ImageGraph/lang/id.json | 5 + www/analytics/plugins/ImageGraph/lang/it.json | 6 + www/analytics/plugins/ImageGraph/lang/ja.json | 6 + www/analytics/plugins/ImageGraph/lang/ko.json | 5 + www/analytics/plugins/ImageGraph/lang/lv.json | 5 + www/analytics/plugins/ImageGraph/lang/nb.json | 6 + www/analytics/plugins/ImageGraph/lang/nl.json | 6 + www/analytics/plugins/ImageGraph/lang/nn.json | 5 + .../plugins/ImageGraph/lang/pt-br.json | 6 + www/analytics/plugins/ImageGraph/lang/pt.json | 5 + www/analytics/plugins/ImageGraph/lang/ro.json | 5 + www/analytics/plugins/ImageGraph/lang/ru.json | 5 + www/analytics/plugins/ImageGraph/lang/sl.json | 5 + www/analytics/plugins/ImageGraph/lang/sq.json | 5 + www/analytics/plugins/ImageGraph/lang/sr.json | 6 + www/analytics/plugins/ImageGraph/lang/sv.json | 6 + www/analytics/plugins/ImageGraph/lang/vi.json | 5 + .../plugins/ImageGraph/lang/zh-cn.json | 5 + .../ImageGraph/templates/testAllSizes.twig | 8 +- www/analytics/plugins/Insights/API.php | 25 +- www/analytics/plugins/Insights/Controller.php | 12 +- .../DataTable/Filter/ExcludeLowValue.php | 2 +- .../Insights/DataTable/Filter/Insight.php | 2 +- .../Insights/DataTable/Filter/Limit.php | 2 +- .../Insights/DataTable/Filter/MinGrowth.php | 4 +- .../Insights/DataTable/Filter/OrderBy.php | 2 +- .../plugins/Insights/InsightReport.php | 2 +- www/analytics/plugins/Insights/Insights.php | 23 +- www/analytics/plugins/Insights/Model.php | 8 +- .../Insights/Visualizations/Insight.php | 14 +- .../Visualizations/Insight/RequestConfig.php | 3 +- www/analytics/plugins/Insights/Widgets.php | 20 + .../Insights/javascripts/insightsDataTable.js | 4 +- www/analytics/plugins/Insights/lang/bg.json | 15 + www/analytics/plugins/Insights/lang/cs.json | 35 + www/analytics/plugins/Insights/lang/da.json | 34 + www/analytics/plugins/Insights/lang/de.json | 35 + www/analytics/plugins/Insights/lang/el.json | 35 + www/analytics/plugins/Insights/lang/en.json | 35 + www/analytics/plugins/Insights/lang/es.json | 35 + www/analytics/plugins/Insights/lang/et.json | 10 + www/analytics/plugins/Insights/lang/fa.json | 13 + www/analytics/plugins/Insights/lang/fi.json | 23 + www/analytics/plugins/Insights/lang/fr.json | 35 + www/analytics/plugins/Insights/lang/gl.json | 10 + www/analytics/plugins/Insights/lang/hi.json | 23 + www/analytics/plugins/Insights/lang/it.json | 35 + www/analytics/plugins/Insights/lang/ja.json | 35 + www/analytics/plugins/Insights/lang/nb.json | 16 + www/analytics/plugins/Insights/lang/nl.json | 35 + www/analytics/plugins/Insights/lang/pl.json | 18 + .../plugins/Insights/lang/pt-br.json | 35 + www/analytics/plugins/Insights/lang/pt.json | 10 + www/analytics/plugins/Insights/lang/ro.json | 34 + www/analytics/plugins/Insights/lang/ru.json | 17 + www/analytics/plugins/Insights/lang/sq.json | 11 + www/analytics/plugins/Insights/lang/sr.json | 35 + www/analytics/plugins/Insights/lang/sv.json | 34 + www/analytics/plugins/Insights/lang/ta.json | 10 + www/analytics/plugins/Insights/lang/tl.json | 18 + www/analytics/plugins/Insights/lang/tr.json | 13 + www/analytics/plugins/Insights/lang/vi.json | 5 + .../plugins/Insights/lang/zh-cn.json | 12 + www/analytics/plugins/Insights/plugin.json | 15 - .../stylesheets/insightVisualization.less | 2 + .../Insights/templates/insightControls.twig | 2 +- .../templates/insightsOverviewWidget.twig | 15 +- .../moversAndShakersOverviewWidget.twig | 13 +- .../Insights/templates/table_header.twig | 4 +- .../plugins/Installation/Controller.php | 1004 +- .../DatabaseConnectionFailedException.php | 15 + .../Installation/FormDatabaseSetup.php | 16 +- .../Installation/FormDefaultSettings.php | 23 + .../Installation/FormFirstWebsiteSetup.php | 11 +- .../plugins/Installation/FormGeneralSetup.php | 105 - .../plugins/Installation/FormSuperUser.php | 121 + .../plugins/Installation/Installation.php | 46 +- www/analytics/plugins/Installation/Menu.php | 24 + .../Installation/ServerFilesGenerator.php | 217 +- www/analytics/plugins/Installation/View.php | 3 +- .../Installation/javascripts/installation.js | 7 +- .../plugins/Installation/lang/am.json | 35 + .../plugins/Installation/lang/ar.json | 138 + .../plugins/Installation/lang/be.json | 84 + .../plugins/Installation/lang/bg.json | 107 + .../plugins/Installation/lang/bs.json | 6 + .../plugins/Installation/lang/ca.json | 99 + .../plugins/Installation/lang/cs.json | 140 + .../plugins/Installation/lang/da.json | 127 + .../plugins/Installation/lang/de.json | 140 + .../plugins/Installation/lang/el.json | 140 + .../plugins/Installation/lang/en.json | 142 + .../plugins/Installation/lang/es.json | 139 + .../plugins/Installation/lang/et.json | 89 + .../plugins/Installation/lang/eu.json | 62 + .../plugins/Installation/lang/fa.json | 101 + .../plugins/Installation/lang/fi.json | 118 + .../plugins/Installation/lang/fr.json | 139 + .../plugins/Installation/lang/gl.json | 31 + .../plugins/Installation/lang/he.json | 22 + .../plugins/Installation/lang/hi.json | 118 + .../plugins/Installation/lang/hu.json | 84 + .../plugins/Installation/lang/id.json | 102 + .../plugins/Installation/lang/is.json | 5 + .../plugins/Installation/lang/it.json | 139 + .../plugins/Installation/lang/ja.json | 140 + .../plugins/Installation/lang/ka.json | 88 + .../plugins/Installation/lang/ko.json | 140 + .../plugins/Installation/lang/lt.json | 102 + .../plugins/Installation/lang/lv.json | 56 + .../plugins/Installation/lang/nb.json | 140 + .../plugins/Installation/lang/nl.json | 140 + .../plugins/Installation/lang/nn.json | 73 + .../plugins/Installation/lang/pl.json | 106 + .../plugins/Installation/lang/pt-br.json | 140 + .../plugins/Installation/lang/pt.json | 84 + .../plugins/Installation/lang/ro.json | 118 + .../plugins/Installation/lang/ru.json | 131 + .../plugins/Installation/lang/sk.json | 69 + .../plugins/Installation/lang/sl.json | 28 + .../plugins/Installation/lang/sq.json | 84 + .../plugins/Installation/lang/sr.json | 140 + .../plugins/Installation/lang/sv.json | 138 + .../plugins/Installation/lang/ta.json | 61 + .../plugins/Installation/lang/te.json | 13 + .../plugins/Installation/lang/th.json | 87 + .../plugins/Installation/lang/tl.json | 118 + .../plugins/Installation/lang/tr.json | 62 + .../plugins/Installation/lang/uk.json | 81 + .../plugins/Installation/lang/vi.json | 103 + .../plugins/Installation/lang/zh-cn.json | 140 + .../plugins/Installation/lang/zh-tw.json | 70 + .../Installation/stylesheets/installation.css | 308 +- .../stylesheets/systemCheckPage.less | 41 +- .../Installation/templates/_allSteps.twig | 11 - .../templates/_integrityDetails.twig | 3 - .../templates/_systemCheckLegend.twig | 27 +- .../templates/_systemCheckSection.twig | 359 +- .../templates/cannotConnectToDb.twig | 5 + .../Installation/templates/databaseCheck.twig | 36 - .../Installation/templates/databaseSetup.twig | 24 +- .../Installation/templates/finished.twig | 50 +- .../templates/firstWebsiteSetup.twig | 44 +- .../Installation/templates/generalSetup.twig | 18 - .../Installation/templates/layout.twig | 106 +- .../Installation/templates/reuseTables.twig | 126 +- .../templates/setupSuperUser.twig | 18 + .../Installation/templates/systemCheck.twig | 42 +- .../templates/systemCheckPage.twig | 35 +- .../templates/tablesCreation.twig | 91 +- .../Installation/templates/trackingCode.twig | 28 +- .../Installation/templates/welcome.twig | 63 +- .../plugins/Intl/Commands/GenerateIntl.php | 415 + .../plugins/Intl/DateTimeFormatProvider.php | 133 + www/analytics/plugins/Intl/Intl.php | 14 + www/analytics/plugins/Intl/config/config.php | 5 + www/analytics/plugins/Intl/lang/am.json | 571 + www/analytics/plugins/Intl/lang/ar.json | 587 + www/analytics/plugins/Intl/lang/be.json | 548 + www/analytics/plugins/Intl/lang/bg.json | 587 + www/analytics/plugins/Intl/lang/bn.json | 587 + www/analytics/plugins/Intl/lang/bs.json | 587 + www/analytics/plugins/Intl/lang/ca.json | 587 + www/analytics/plugins/Intl/lang/cs.json | 587 + www/analytics/plugins/Intl/lang/cy.json | 581 + www/analytics/plugins/Intl/lang/da.json | 587 + www/analytics/plugins/Intl/lang/de.json | 587 + www/analytics/plugins/Intl/lang/dev.json | 6 + www/analytics/plugins/Intl/lang/el.json | 587 + www/analytics/plugins/Intl/lang/en.json | 587 + www/analytics/plugins/Intl/lang/es.json | 587 + www/analytics/plugins/Intl/lang/et.json | 587 + www/analytics/plugins/Intl/lang/eu.json | 557 + www/analytics/plugins/Intl/lang/fa.json | 587 + www/analytics/plugins/Intl/lang/fi.json | 587 + www/analytics/plugins/Intl/lang/fr.json | 587 + www/analytics/plugins/Intl/lang/gl.json | 555 + www/analytics/plugins/Intl/lang/he.json | 587 + www/analytics/plugins/Intl/lang/hi.json | 587 + www/analytics/plugins/Intl/lang/hr.json | 587 + www/analytics/plugins/Intl/lang/hu.json | 587 + www/analytics/plugins/Intl/lang/id.json | 587 + www/analytics/plugins/Intl/lang/is.json | 585 + www/analytics/plugins/Intl/lang/it.json | 587 + www/analytics/plugins/Intl/lang/ja.json | 587 + www/analytics/plugins/Intl/lang/ka.json | 568 + www/analytics/plugins/Intl/lang/ko.json | 587 + www/analytics/plugins/Intl/lang/lt.json | 587 + www/analytics/plugins/Intl/lang/lv.json | 587 + www/analytics/plugins/Intl/lang/nb.json | 587 + www/analytics/plugins/Intl/lang/nl.json | 587 + www/analytics/plugins/Intl/lang/nn.json | 583 + www/analytics/plugins/Intl/lang/pl.json | 587 + www/analytics/plugins/Intl/lang/pt-br.json | 587 + www/analytics/plugins/Intl/lang/pt.json | 587 + www/analytics/plugins/Intl/lang/ro.json | 587 + www/analytics/plugins/Intl/lang/ru.json | 587 + www/analytics/plugins/Intl/lang/sk.json | 587 + www/analytics/plugins/Intl/lang/sl.json | 585 + www/analytics/plugins/Intl/lang/sq.json | 539 + www/analytics/plugins/Intl/lang/sr.json | 587 + www/analytics/plugins/Intl/lang/sv.json | 587 + www/analytics/plugins/Intl/lang/ta.json | 587 + www/analytics/plugins/Intl/lang/te.json | 587 + www/analytics/plugins/Intl/lang/th.json | 587 + www/analytics/plugins/Intl/lang/tl.json | 557 + www/analytics/plugins/Intl/lang/tr.json | 587 + www/analytics/plugins/Intl/lang/uk.json | 587 + www/analytics/plugins/Intl/lang/vi.json | 587 + www/analytics/plugins/Intl/lang/zh-cn.json | 587 + www/analytics/plugins/Intl/lang/zh-tw.json | 587 + .../plugins/LanguagesManager/API.php | 148 +- .../LanguagesManager/Commands/CreatePull.php | 35 +- .../Commands/FetchFromOTrance.php | 172 - .../Commands/FetchTranslations.php | 123 + .../Commands/LanguageCodes.php | 7 +- .../Commands/LanguageNames.php | 7 +- .../Commands/PluginsWithTranslations.php | 7 +- .../Commands/SetTranslations.php | 25 +- .../Commands/TranslationBase.php | 27 + .../LanguagesManager/Commands/Update.php | 207 +- .../plugins/LanguagesManager/Controller.php | 12 +- .../LanguagesManager/LanguagesManager.php | 99 +- .../plugins/LanguagesManager/Menu.php | 34 + .../plugins/LanguagesManager/Model.php | 103 + .../Test/Integration/LanguagesManagerTest.php | 187 + .../Test/Integration/ModelTest.php | 136 + .../Filter/ByBaseTranslationsTest.php | 162 + .../Filter/ByParameterCountTest.php | 121 + .../Filter/EmptyTranslationsTest.php | 99 + .../Filter/EncodedEntitiesTest.php | 113 + .../Filter/UnnecassaryWhitespacesTest.php | 158 + .../Validate/CoreTranslationsTest.php | 104 + .../Validate/NoScriptsTest.php | 113 + .../Unit/TranslationWriter/WriterTest.php | 267 + .../Filter/ByBaseTranslations.php | 9 +- .../Filter/ByParameterCount.php | 14 +- .../Filter/EmptyTranslations.php | 9 +- .../Filter/EncodedEntities.php | 11 +- .../Filter/FilterAbstract.php | 6 +- .../Filter/UnnecassaryWhitespaces.php | 11 +- .../Validate/CoreTranslations.php | 84 + .../TranslationWriter}/Validate/NoScripts.php | 8 +- .../Validate/ValidateAbstract.php | 6 +- .../TranslationWriter}/Writer.php | 32 +- .../LanguagesManager/Updates/2.15.1-b1.php | 32 + .../languageselector.directive.js | 36 + .../translationsearch.controller.js | 31 + .../translationsearch.directive.html | 29 + .../translationsearch.directive.js | 31 + .../javascripts/languageSelector.js | 72 - .../plugins/LanguagesManager/lang/ar.json | 5 + .../plugins/LanguagesManager/lang/be.json | 5 + .../plugins/LanguagesManager/lang/bg.json | 5 + .../plugins/LanguagesManager/lang/ca.json | 6 + .../plugins/LanguagesManager/lang/cs.json | 6 + .../plugins/LanguagesManager/lang/da.json | 6 + .../plugins/LanguagesManager/lang/de.json | 6 + .../plugins/LanguagesManager/lang/el.json | 6 + .../plugins/LanguagesManager/lang/en.json | 6 + .../plugins/LanguagesManager/lang/es.json | 6 + .../plugins/LanguagesManager/lang/et.json | 5 + .../plugins/LanguagesManager/lang/fa.json | 6 + .../plugins/LanguagesManager/lang/fi.json | 6 + .../plugins/LanguagesManager/lang/fr.json | 6 + .../plugins/LanguagesManager/lang/hi.json | 6 + .../plugins/LanguagesManager/lang/hu.json | 5 + .../plugins/LanguagesManager/lang/id.json | 5 + .../plugins/LanguagesManager/lang/is.json | 5 + .../plugins/LanguagesManager/lang/it.json | 6 + .../plugins/LanguagesManager/lang/ja.json | 6 + .../plugins/LanguagesManager/lang/ka.json | 5 + .../plugins/LanguagesManager/lang/ko.json | 5 + .../plugins/LanguagesManager/lang/lt.json | 5 + .../plugins/LanguagesManager/lang/lv.json | 5 + .../plugins/LanguagesManager/lang/nb.json | 6 + .../plugins/LanguagesManager/lang/nl.json | 6 + .../plugins/LanguagesManager/lang/nn.json | 5 + .../plugins/LanguagesManager/lang/pl.json | 5 + .../plugins/LanguagesManager/lang/pt-br.json | 6 + .../plugins/LanguagesManager/lang/pt.json | 5 + .../plugins/LanguagesManager/lang/ro.json | 5 + .../plugins/LanguagesManager/lang/ru.json | 6 + .../plugins/LanguagesManager/lang/sk.json | 5 + .../plugins/LanguagesManager/lang/sl.json | 5 + .../plugins/LanguagesManager/lang/sq.json | 5 + .../plugins/LanguagesManager/lang/sr.json | 6 + .../plugins/LanguagesManager/lang/sv.json | 6 + .../plugins/LanguagesManager/lang/te.json | 5 + .../plugins/LanguagesManager/lang/th.json | 5 + .../plugins/LanguagesManager/lang/tl.json | 6 + .../plugins/LanguagesManager/lang/tr.json | 5 + .../plugins/LanguagesManager/lang/uk.json | 5 + .../plugins/LanguagesManager/lang/vi.json | 5 + .../plugins/LanguagesManager/lang/zh-cn.json | 6 + .../plugins/LanguagesManager/lang/zh-tw.json | 5 + .../templates/getLanguagesSelector.twig | 27 +- .../templates/searchTranslation.twig | 11 + www/analytics/plugins/LeftMenu/plugin.json | 5 - .../plugins/LeftMenu/stylesheets/theme.less | 139 - www/analytics/plugins/Live/API.php | 617 +- www/analytics/plugins/Live/Controller.php | 55 +- www/analytics/plugins/Live/Live.php | 42 +- www/analytics/plugins/Live/Model.php | 506 + www/analytics/plugins/Live/Reports/Base.php | 21 + .../plugins/Live/Reports/GetLastVisits.php | 22 + .../Live/Reports/GetLastVisitsDetails.php | 50 + .../Live/Reports/GetSimpleLastVisitCount.php | 56 + www/analytics/plugins/Live/Visitor.php | 790 +- www/analytics/plugins/Live/VisitorFactory.php | 49 + .../plugins/Live/VisitorInterface.php | 22 + www/analytics/plugins/Live/VisitorLog.php | 107 - www/analytics/plugins/Live/VisitorProfile.php | 398 + .../Live/Visualizations/VisitorLog.php | 101 + .../Live/Visualizations/VisitorLog/Config.php | 39 + www/analytics/plugins/Live/Widgets.php | 27 + www/analytics/plugins/Live/images/pause.gif | Bin 669 -> 1142 bytes .../plugins/Live/images/pause_disabled.gif | Bin 619 -> 0 bytes www/analytics/plugins/Live/images/play.gif | Bin 666 -> 1184 bytes .../plugins/Live/images/play_disabled.gif | Bin 407 -> 0 bytes .../plugins/Live/images/visitorlog-hover.png | Bin 0 -> 1275 bytes .../plugins/Live/images/visitorlog.png | Bin 0 -> 1273 bytes .../Live/javascripts/SegmentedVisitorLog.js | 142 + .../plugins/Live/javascripts/live.js | 76 +- .../plugins/Live/javascripts/rowaction.js | 166 + .../plugins/Live/javascripts/visitorLog.js | 52 +- .../Live/javascripts/visitorProfile.js | 32 +- www/analytics/plugins/Live/lang/ar.json | 12 + www/analytics/plugins/Live/lang/be.json | 13 + www/analytics/plugins/Live/lang/bg.json | 34 + www/analytics/plugins/Live/lang/ca.json | 16 + www/analytics/plugins/Live/lang/cs.json | 47 + www/analytics/plugins/Live/lang/da.json | 40 + www/analytics/plugins/Live/lang/de.json | 44 + www/analytics/plugins/Live/lang/el.json | 47 + www/analytics/plugins/Live/lang/en.json | 47 + www/analytics/plugins/Live/lang/es.json | 43 + www/analytics/plugins/Live/lang/et.json | 34 + www/analytics/plugins/Live/lang/eu.json | 6 + www/analytics/plugins/Live/lang/fa.json | 35 + www/analytics/plugins/Live/lang/fi.json | 36 + www/analytics/plugins/Live/lang/fr.json | 43 + www/analytics/plugins/Live/lang/hi.json | 33 + www/analytics/plugins/Live/lang/hr.json | 15 + www/analytics/plugins/Live/lang/hu.json | 13 + www/analytics/plugins/Live/lang/id.json | 20 + www/analytics/plugins/Live/lang/is.json | 11 + www/analytics/plugins/Live/lang/it.json | 43 + www/analytics/plugins/Live/lang/ja.json | 43 + www/analytics/plugins/Live/lang/ka.json | 11 + www/analytics/plugins/Live/lang/ko.json | 35 + www/analytics/plugins/Live/lang/lt.json | 24 + www/analytics/plugins/Live/lang/lv.json | 14 + www/analytics/plugins/Live/lang/nb.json | 29 + www/analytics/plugins/Live/lang/nl.json | 43 + www/analytics/plugins/Live/lang/nn.json | 14 + www/analytics/plugins/Live/lang/pl.json | 28 + www/analytics/plugins/Live/lang/pt-br.json | 47 + www/analytics/plugins/Live/lang/pt.json | 14 + www/analytics/plugins/Live/lang/ro.json | 36 + www/analytics/plugins/Live/lang/ru.json | 40 + www/analytics/plugins/Live/lang/sk.json | 22 + www/analytics/plugins/Live/lang/sl.json | 13 + www/analytics/plugins/Live/lang/sq.json | 15 + www/analytics/plugins/Live/lang/sr.json | 43 + www/analytics/plugins/Live/lang/sv.json | 37 + www/analytics/plugins/Live/lang/ta.json | 10 + www/analytics/plugins/Live/lang/te.json | 8 + www/analytics/plugins/Live/lang/th.json | 13 + www/analytics/plugins/Live/lang/tl.json | 35 + www/analytics/plugins/Live/lang/tr.json | 24 + www/analytics/plugins/Live/lang/uk.json | 11 + www/analytics/plugins/Live/lang/vi.json | 36 + www/analytics/plugins/Live/lang/zh-cn.json | 36 + www/analytics/plugins/Live/lang/zh-tw.json | 11 + .../plugins/Live/stylesheets/live.less | 179 +- .../Live/stylesheets/visitor_profile.less | 29 +- .../plugins/Live/templates/_actionsList.twig | 180 +- .../templates/_dataTableViz_visitorLog.twig | 233 +- .../Live/templates/_totalVisitors.twig | 16 +- .../Live/templates/getLastVisitsStart.twig | 86 +- .../templates/getSimpleLastVisitCount.twig | 2 +- .../Live/templates/getSingleVisitSummary.twig | 20 +- .../plugins/Live/templates/getVisitList.twig | 16 +- .../templates/getVisitorProfilePopup.twig | 56 +- .../plugins/Live/templates/index.twig | 10 +- .../Live/templates/indexVisitorLog.twig | 2 +- www/analytics/plugins/Login/Auth.php | 168 +- www/analytics/plugins/Login/Controller.php | 317 +- www/analytics/plugins/Login/FormLogin.php | 2 +- .../plugins/Login/FormResetPassword.php | 2 +- www/analytics/plugins/Login/Login.php | 91 +- .../plugins/Login/PasswordResetter.php | 478 + .../plugins/Login/SessionInitializer.php | 239 + www/analytics/plugins/Login/config/config.php | 5 + .../plugins/Login/javascripts/login.js | 2 +- www/analytics/plugins/Login/lang/am.json | 11 + www/analytics/plugins/Login/lang/ar.json | 16 + www/analytics/plugins/Login/lang/be.json | 16 + www/analytics/plugins/Login/lang/bg.json | 20 + www/analytics/plugins/Login/lang/ca.json | 22 + www/analytics/plugins/Login/lang/cs.json | 24 + www/analytics/plugins/Login/lang/da.json | 23 + www/analytics/plugins/Login/lang/de.json | 23 + www/analytics/plugins/Login/lang/el.json | 24 + www/analytics/plugins/Login/lang/en.json | 24 + www/analytics/plugins/Login/lang/es.json | 23 + www/analytics/plugins/Login/lang/et.json | 17 + www/analytics/plugins/Login/lang/eu.json | 13 + www/analytics/plugins/Login/lang/fa.json | 19 + www/analytics/plugins/Login/lang/fi.json | 22 + www/analytics/plugins/Login/lang/fr.json | 23 + www/analytics/plugins/Login/lang/gl.json | 10 + www/analytics/plugins/Login/lang/he.json | 12 + www/analytics/plugins/Login/lang/hi.json | 22 + www/analytics/plugins/Login/lang/hu.json | 23 + www/analytics/plugins/Login/lang/id.json | 21 + www/analytics/plugins/Login/lang/is.json | 14 + www/analytics/plugins/Login/lang/it.json | 23 + www/analytics/plugins/Login/lang/ja.json | 23 + www/analytics/plugins/Login/lang/ka.json | 15 + www/analytics/plugins/Login/lang/ko.json | 24 + www/analytics/plugins/Login/lang/lt.json | 15 + www/analytics/plugins/Login/lang/lv.json | 16 + www/analytics/plugins/Login/lang/nb.json | 23 + www/analytics/plugins/Login/lang/nl.json | 23 + www/analytics/plugins/Login/lang/nn.json | 12 + www/analytics/plugins/Login/lang/pl.json | 20 + www/analytics/plugins/Login/lang/pt-br.json | 24 + www/analytics/plugins/Login/lang/pt.json | 16 + www/analytics/plugins/Login/lang/ro.json | 22 + www/analytics/plugins/Login/lang/ru.json | 23 + www/analytics/plugins/Login/lang/sk.json | 20 + www/analytics/plugins/Login/lang/sl.json | 19 + www/analytics/plugins/Login/lang/sq.json | 16 + www/analytics/plugins/Login/lang/sr.json | 23 + www/analytics/plugins/Login/lang/sv.json | 23 + www/analytics/plugins/Login/lang/ta.json | 13 + www/analytics/plugins/Login/lang/te.json | 7 + www/analytics/plugins/Login/lang/th.json | 19 + www/analytics/plugins/Login/lang/tl.json | 21 + www/analytics/plugins/Login/lang/tr.json | 18 + www/analytics/plugins/Login/lang/uk.json | 15 + www/analytics/plugins/Login/lang/vi.json | 21 + www/analytics/plugins/Login/lang/zh-cn.json | 21 + www/analytics/plugins/Login/lang/zh-tw.json | 15 + .../plugins/Login/stylesheets/login.css | 197 - .../plugins/Login/stylesheets/login.less | 190 + .../plugins/Login/stylesheets/variables.less | 1 + .../plugins/Login/templates/_formErrors.twig | 9 + .../plugins/Login/templates/login.twig | 68 +- .../Login/templates/resetPassword.twig | 10 +- .../MobileAppMeasurable.php | 13 + .../plugins/MobileAppMeasurable/Type.php | 21 + .../MobileAppMeasurable/config/test.php | 7 + .../plugins/MobileAppMeasurable/lang/cs.json | 7 + .../plugins/MobileAppMeasurable/lang/de.json | 7 + .../plugins/MobileAppMeasurable/lang/el.json | 7 + .../plugins/MobileAppMeasurable/lang/en.json | 7 + .../plugins/MobileAppMeasurable/lang/es.json | 7 + .../plugins/MobileAppMeasurable/lang/fr.json | 7 + .../plugins/MobileAppMeasurable/lang/hi.json | 7 + .../plugins/MobileAppMeasurable/lang/hu.json | 7 + .../plugins/MobileAppMeasurable/lang/it.json | 7 + .../plugins/MobileAppMeasurable/lang/ja.json | 7 + .../plugins/MobileAppMeasurable/lang/lt.json | 5 + .../plugins/MobileAppMeasurable/lang/nb.json | 7 + .../plugins/MobileAppMeasurable/lang/nl.json | 7 + .../MobileAppMeasurable/lang/pt-br.json | 7 + .../plugins/MobileAppMeasurable/lang/sk.json | 5 + .../plugins/MobileAppMeasurable/lang/sr.json | 7 + .../plugins/MobileAppMeasurable/lang/sv.json | 7 + .../plugins/MobileAppMeasurable/plugin.json | 4 + www/analytics/plugins/MobileMessaging/API.php | 22 +- .../plugins/MobileMessaging/APIException.php | 2 +- .../plugins/MobileMessaging/Controller.php | 82 +- .../MobileMessaging/CountryCallingCodes.php | 2 +- .../plugins/MobileMessaging/GSMCharset.php | 2 +- .../plugins/MobileMessaging/Menu.php | 30 + .../MobileMessaging/MobileMessaging.php | 30 +- .../ReportRendererException.php | 15 +- .../MobileMessaging/ReportRenderer/Sms.php | 18 +- .../plugins/MobileMessaging/SMSProvider.php | 170 +- .../MobileMessaging/SMSProvider/Clockwork.php | 26 +- .../SMSProvider/Development.php | 57 + .../SMSProvider/StubbedProvider.php | 18 +- .../javascripts/MobileMessagingSettings.js | 2 +- .../plugins/MobileMessaging/lang/bg.json | 34 + .../plugins/MobileMessaging/lang/ca.json | 29 + .../plugins/MobileMessaging/lang/cs.json | 41 + .../plugins/MobileMessaging/lang/da.json | 41 + .../plugins/MobileMessaging/lang/de.json | 41 + .../plugins/MobileMessaging/lang/el.json | 41 + .../plugins/MobileMessaging/lang/en.json | 41 + .../plugins/MobileMessaging/lang/es.json | 41 + .../plugins/MobileMessaging/lang/et.json | 20 + .../plugins/MobileMessaging/lang/fa.json | 36 + .../plugins/MobileMessaging/lang/fi.json | 41 + .../plugins/MobileMessaging/lang/fr.json | 41 + .../plugins/MobileMessaging/lang/hi.json | 41 + .../plugins/MobileMessaging/lang/id.json | 41 + .../plugins/MobileMessaging/lang/it.json | 41 + .../plugins/MobileMessaging/lang/ja.json | 41 + .../plugins/MobileMessaging/lang/ko.json | 41 + .../plugins/MobileMessaging/lang/lt.json | 11 + .../plugins/MobileMessaging/lang/nb.json | 21 + .../plugins/MobileMessaging/lang/nl.json | 41 + .../plugins/MobileMessaging/lang/pl.json | 15 + .../plugins/MobileMessaging/lang/pt-br.json | 41 + .../plugins/MobileMessaging/lang/ro.json | 41 + .../plugins/MobileMessaging/lang/ru.json | 39 + .../plugins/MobileMessaging/lang/sk.json | 5 + .../plugins/MobileMessaging/lang/sl.json | 6 + .../plugins/MobileMessaging/lang/sr.json | 41 + .../plugins/MobileMessaging/lang/sv.json | 41 + .../plugins/MobileMessaging/lang/ta.json | 14 + .../plugins/MobileMessaging/lang/th.json | 12 + .../plugins/MobileMessaging/lang/tl.json | 41 + .../plugins/MobileMessaging/lang/tr.json | 22 + .../plugins/MobileMessaging/lang/vi.json | 41 + .../plugins/MobileMessaging/lang/zh-cn.json | 41 + .../MobileMessaging/templates/SMSReport.twig | 26 +- .../MobileMessaging/templates/index.twig | 211 +- .../MobileMessaging/templates/macros.twig | 33 + .../reportParametersScheduledReports.twig | 11 +- .../templates/userSettings.twig | 129 + .../Formatter/LineMessageFormatter.php | 95 + .../Monolog/Handler/DatabaseHandler.php | 39 + .../plugins/Monolog/Handler/EchoHandler.php | 24 + .../plugins/Monolog/Handler/FileHandler.php | 31 + .../Handler/WebNotificationHandler.php | 52 + www/analytics/plugins/Monolog/Monolog.php | 15 + .../Monolog/Processor/ClassNameProcessor.php | 81 + .../Processor/ExceptionToTextProcessor.php | 58 + .../Monolog/Processor/RequestIdProcessor.php | 34 + .../Monolog/Processor/SprintfProcessor.php | 42 + .../Monolog/Processor/TokenProcessor.php | 24 + www/analytics/plugins/Monolog/config/cli.php | 29 + .../plugins/Monolog/config/config.php | 108 + .../plugins/Monolog/config/tracker.php | 37 + www/analytics/plugins/Monolog/plugin.json | 3 + www/analytics/plugins/Morpheus/Controller.php | 25 + www/analytics/plugins/Morpheus/Menu.php | 52 + .../plugins/Morpheus/fonts/piwik.eot | Bin 0 -> 20640 bytes .../plugins/Morpheus/fonts/piwik.ttf | Bin 0 -> 20484 bytes .../images/affix-arrow.png | Bin .../{Zeitgeist => Morpheus}/images/arr_r.png | Bin .../images/background-submit.png | Bin .../plugins/Morpheus/images/cities.png | Bin 1038 -> 0 bytes .../images/collapsed_arrows.gif | Bin .../images/dashboard_h_bg.png | Bin .../images/data_table_footer_active_item.png | Bin .../{Zeitgeist => Morpheus}/images/delete.png | Bin .../images/download.png | Bin .../images/ecommerceAbandonedCart.gif | Bin .../images/ecommerceOrder.gif | Bin .../{Zeitgeist => Morpheus}/images/email.png | Bin .../{Zeitgeist => Morpheus}/images/error.png | Bin .../images/error_medium.png | Bin .../{Zeitgeist => Morpheus}/images/event.png | Bin .../images/expanded_arrows.gif | Bin .../{Zeitgeist => Morpheus}/images/feed.png | Bin .../images/fullscreen.png | Bin .../images/html_icon.png | Bin .../images/ico_alert.png | Bin .../images/ico_info.png | Bin .../{Zeitgeist => Morpheus}/images/inp_bg.png | Bin .../images/li_dbl_gray.gif | Bin .../images/login-sprite.png | Bin .../images/logo-marketplace.png | Bin .../plugins/Morpheus/images/logo.svg | 10 +- .../plugins/Morpheus/images/minus.png | Bin 0 -> 208 bytes .../{Zeitgeist => Morpheus}/images/newtab.png | Bin .../{Zeitgeist => Morpheus}/images/ok.png | Bin .../plugins/Morpheus/images/pause.gif | Bin 1142 -> 0 bytes .../Morpheus/images/pause_disabled.gif | Bin 1141 -> 0 bytes .../images/paypal_subscribe.gif | Bin .../plugins/Morpheus/images/play.gif | Bin 1184 -> 0 bytes .../plugins/Morpheus/images/play_disabled.gif | Bin 1155 -> 0 bytes .../plugins/Morpheus/images/plus.png | Bin 0 -> 214 bytes .../images/plus_blue.png | Bin .../plugins/Morpheus/images/regions.png | Bin 1265 -> 0 bytes .../{Zeitgeist => Morpheus}/images/reload.png | Bin .../images/row_evolution.png | Bin .../images/row_evolution_hover.png | Bin .../plugins/Morpheus/images/search_bg.png | Bin 0 -> 374 bytes .../plugins/Morpheus/images/select_arrow.png | Bin 0 -> 93 bytes .../plugins/Morpheus/images/signout.png | Bin 0 -> 345 bytes .../images/sites_selection.png | Bin .../images/smileyprog_0.png | Bin .../images/smileyprog_1.png | Bin .../images/smileyprog_2.png | Bin .../images/smileyprog_3.png | Bin .../images/smileyprog_4.png | Bin .../images/sort_subtable_asc.png | Bin .../images/sort_subtable_asc_light.png | Bin .../images/sort_subtable_desc_light.png | Bin .../{Zeitgeist => Morpheus}/images/star.png | Bin .../images/star_empty.png | Bin .../images/success_medium.png | Bin .../images/video_play.png | Bin .../images/warning.png | Bin .../images/warning_medium.png | Bin .../images/warning_small.png | Bin .../javascripts/ajaxHelper.js | 74 +- .../plugins/Morpheus/javascripts/layout.js | 32 + .../plugins/Morpheus/javascripts/morpheus.js | 14 +- .../javascripts/piwikHelper.js | 43 +- www/analytics/plugins/Morpheus/plugin.json | 3 +- .../plugins/Morpheus/stylesheets/admin.less | 118 - .../plugins/Morpheus/stylesheets/base.less | 46 + .../Morpheus/stylesheets/base/bootstrap.css | 1013 + .../Morpheus/stylesheets/base/colors.less | 44 + .../Morpheus/stylesheets/base/icons.css | 302 + .../stylesheets/{ => base}/mixins.less | 26 +- .../plugins/Morpheus/stylesheets/charts.less | 158 - .../plugins/Morpheus/stylesheets/colors.less | 58 - .../Morpheus/stylesheets/components.less | 362 - .../plugins/Morpheus/stylesheets/forms.less | 300 - .../Morpheus/stylesheets/general/_admin.less | 68 + .../stylesheets/general/_default.less | 72 + .../Morpheus/stylesheets/general/_form.less | 135 + .../Morpheus/stylesheets/general/_forms.less | 491 + .../stylesheets/general/_jqueryUI.less | 283 + .../stylesheets/general/_misc.less | 4 - .../stylesheets/general/_typography.less | 116 + .../stylesheets/general/_utils.less | 2 +- .../stylesheets/ieonly.css | 12 +- .../plugins/Morpheus/stylesheets/main.less | 788 + .../plugins/Morpheus/stylesheets/map.less | 71 - .../plugins/Morpheus/stylesheets/popups.less | 46 - .../Morpheus/stylesheets/simple_structure.css | 179 + .../Morpheus/stylesheets/theme-advanced.less | 13 + .../plugins/Morpheus/stylesheets/theme.less | 931 +- .../plugins/Morpheus/stylesheets/tooltip.less | 30 - .../Morpheus/stylesheets/typography.less | 124 - .../Morpheus/stylesheets/ui/_alerts.less | 64 + .../Morpheus/stylesheets/ui/_buttons.less | 81 + .../Morpheus/stylesheets/ui/_cards.less | 15 + .../Morpheus/stylesheets/ui/_charts.less | 158 + .../Morpheus/stylesheets/ui/_code.less | 20 + .../Morpheus/stylesheets/ui/_components.less | 305 + .../Morpheus/stylesheets/ui/_list-group.less | 47 + .../plugins/Morpheus/stylesheets/ui/_map.less | 69 + .../Morpheus/stylesheets/ui/_navs.less | 59 + .../Morpheus/stylesheets/ui/_panels.less | 71 + .../Morpheus/stylesheets/ui/_popups.less | 41 + .../stylesheets/ui/_progress-bars.less | 24 + .../Morpheus/stylesheets/ui/_tables.less | 16 + .../Morpheus/stylesheets/ui/_tooltip.less | 30 + .../stylesheets/uibase}/_dataTable.less | 0 .../Morpheus/stylesheets/uibase/_header.less | 45 + .../stylesheets/uibase/_headerMessage.less | 55 + .../stylesheets/uibase/_languageSelect.less | 24 + .../Morpheus/stylesheets/uibase/_loading.less | 29 + .../stylesheets/uibase/_periodSelect.less | 66 + .../templates/_iframeBuster.twig | 0 .../Morpheus/templates/_jsCssIncludes.twig | 4 + .../templates/_jsGlobalVariables.twig | 51 + .../templates/_sparklineFooter.twig | 0 .../plugins/Morpheus/templates/admin.twig | 58 + .../Morpheus/templates/ajaxMacros.twig | 38 + .../plugins/Morpheus/templates/dashboard.twig | 60 + .../plugins/Morpheus/templates/demo.twig | 599 + .../templates/empty.twig | 0 .../Morpheus/templates/genericForm.twig | 36 + .../Morpheus/templates/javascriptCode.tpl | 15 + .../plugins/Morpheus/templates/layout.twig | 53 + .../plugins/Morpheus/templates/macros.twig | 24 + .../Morpheus/templates/maintenance.tpl | 36 + .../Morpheus/templates/settingsMacros.twig | 137 + .../Morpheus/templates/simpleLayoutFooter.tpl | 9 + .../Morpheus/templates/simpleLayoutHeader.tpl | 24 + .../plugins/Morpheus/templates/user.twig | 56 + www/analytics/plugins/MultiSites/API.php | 340 +- .../Metrics/EcommerceOnlyEvolutionMetric.php | 50 + .../plugins/MultiSites/Columns/Website.php | 20 + .../plugins/MultiSites/Controller.php | 51 +- .../plugins/MultiSites/Dashboard.php | 342 + .../DataTable/Filter/NestedSitesLimiter.php | 138 + www/analytics/plugins/MultiSites/Menu.php | 23 + .../plugins/MultiSites/MultiSites.php | 100 +- .../plugins/MultiSites/Reports/Base.php | 37 + .../plugins/MultiSites/Reports/GetAll.php | 25 + .../plugins/MultiSites/Reports/GetOne.php | 26 + .../dashboard/dashboard-controller.js | 34 - .../dashboard/dashboard-directive.js | 40 - .../angularjs/dashboard/dashboard-filter.js | 63 - .../angularjs/dashboard/dashboard-model.js | 273 - .../dashboard/dashboard-model.service.js | 179 + .../dashboard/dashboard.controller.js | 29 + .../dashboard/dashboard.directive.html | 131 + .../dashboard/dashboard.directive.js | 46 + .../dashboard/dashboard.directive.less | 263 + .../angularjs/dashboard/dashboard.html | 128 - .../angularjs/dashboard/dashboard.less | 185 - .../angularjs/site/site-directive.js | 55 - .../angularjs/site/site.controller.js | 47 + .../angularjs/site/site.directive.html | 39 + .../angularjs/site/site.directive.js | 42 + .../MultiSites/angularjs/site/site.html | 39 - .../plugins/MultiSites/images/link.gif | Bin 75 -> 0 bytes .../MultiSites/images/loading-blue.gif | Bin 1849 -> 0 bytes www/analytics/plugins/MultiSites/lang/ar.json | 5 + www/analytics/plugins/MultiSites/lang/be.json | 5 + www/analytics/plugins/MultiSites/lang/bg.json | 8 + www/analytics/plugins/MultiSites/lang/ca.json | 7 + www/analytics/plugins/MultiSites/lang/cs.json | 9 + www/analytics/plugins/MultiSites/lang/da.json | 8 + www/analytics/plugins/MultiSites/lang/de.json | 9 + www/analytics/plugins/MultiSites/lang/el.json | 9 + www/analytics/plugins/MultiSites/lang/en.json | 9 + www/analytics/plugins/MultiSites/lang/es.json | 9 + www/analytics/plugins/MultiSites/lang/et.json | 5 + www/analytics/plugins/MultiSites/lang/fa.json | 7 + www/analytics/plugins/MultiSites/lang/fi.json | 8 + www/analytics/plugins/MultiSites/lang/fr.json | 9 + www/analytics/plugins/MultiSites/lang/hi.json | 9 + www/analytics/plugins/MultiSites/lang/hu.json | 5 + www/analytics/plugins/MultiSites/lang/id.json | 6 + www/analytics/plugins/MultiSites/lang/it.json | 9 + www/analytics/plugins/MultiSites/lang/ja.json | 9 + www/analytics/plugins/MultiSites/lang/ka.json | 5 + www/analytics/plugins/MultiSites/lang/ko.json | 7 + www/analytics/plugins/MultiSites/lang/lt.json | 7 + www/analytics/plugins/MultiSites/lang/nb.json | 9 + www/analytics/plugins/MultiSites/lang/nl.json | 9 + www/analytics/plugins/MultiSites/lang/nn.json | 5 + www/analytics/plugins/MultiSites/lang/pl.json | 7 + .../plugins/MultiSites/lang/pt-br.json | 9 + www/analytics/plugins/MultiSites/lang/pt.json | 9 + www/analytics/plugins/MultiSites/lang/ro.json | 8 + www/analytics/plugins/MultiSites/lang/ru.json | 8 + www/analytics/plugins/MultiSites/lang/sk.json | 5 + www/analytics/plugins/MultiSites/lang/sl.json | 7 + www/analytics/plugins/MultiSites/lang/sq.json | 6 + www/analytics/plugins/MultiSites/lang/sr.json | 9 + www/analytics/plugins/MultiSites/lang/sv.json | 9 + www/analytics/plugins/MultiSites/lang/ta.json | 5 + www/analytics/plugins/MultiSites/lang/th.json | 5 + www/analytics/plugins/MultiSites/lang/tl.json | 8 + www/analytics/plugins/MultiSites/lang/uk.json | 5 + www/analytics/plugins/MultiSites/lang/vi.json | 9 + .../plugins/MultiSites/lang/zh-cn.json | 6 + .../plugins/MultiSites/lang/zh-tw.json | 5 + www/analytics/plugins/MultiSites/plugin.json | 8 + .../MultiSites/templates/getSitesInfo.twig | 20 +- www/analytics/plugins/Overlay/API.php | 23 +- www/analytics/plugins/Overlay/Controller.php | 138 +- www/analytics/plugins/Overlay/Overlay.php | 15 +- .../plugins/Overlay/client/client.js | 11 +- .../plugins/Overlay/client/followingpages.js | 22 +- .../plugins/Overlay/client/linktags.eps | 5251 --- .../plugins/Overlay/client/linktags.psd | Bin 38518 -> 0 bytes .../plugins/Overlay/client/urlnormalizer.js | 2 + .../plugins/Overlay/config/ui-test.php | 14 + www/analytics/plugins/Overlay/images/info.png | Bin 778 -> 0 bytes .../Overlay/javascripts/Overlay_Helper.js | 16 +- .../Overlay/javascripts/Piwik_Overlay.js | 98 +- .../plugins/Overlay/javascripts/rowaction.js | 57 +- www/analytics/plugins/Overlay/lang/ar.json | 5 + www/analytics/plugins/Overlay/lang/be.json | 5 + www/analytics/plugins/Overlay/lang/bg.json | 16 + www/analytics/plugins/Overlay/lang/ca.json | 20 + www/analytics/plugins/Overlay/lang/cs.json | 21 + www/analytics/plugins/Overlay/lang/da.json | 20 + www/analytics/plugins/Overlay/lang/de.json | 21 + www/analytics/plugins/Overlay/lang/el.json | 21 + www/analytics/plugins/Overlay/lang/en.json | 21 + www/analytics/plugins/Overlay/lang/es.json | 21 + www/analytics/plugins/Overlay/lang/et.json | 12 + www/analytics/plugins/Overlay/lang/fa.json | 17 + www/analytics/plugins/Overlay/lang/fi.json | 20 + www/analytics/plugins/Overlay/lang/fr.json | 21 + www/analytics/plugins/Overlay/lang/he.json | 7 + www/analytics/plugins/Overlay/lang/hi.json | 21 + www/analytics/plugins/Overlay/lang/hr.json | 5 + www/analytics/plugins/Overlay/lang/hu.json | 5 + www/analytics/plugins/Overlay/lang/id.json | 20 + www/analytics/plugins/Overlay/lang/is.json | 5 + www/analytics/plugins/Overlay/lang/it.json | 21 + www/analytics/plugins/Overlay/lang/ja.json | 21 + www/analytics/plugins/Overlay/lang/ka.json | 5 + www/analytics/plugins/Overlay/lang/ko.json | 21 + www/analytics/plugins/Overlay/lang/lt.json | 6 + www/analytics/plugins/Overlay/lang/lv.json | 5 + www/analytics/plugins/Overlay/lang/nb.json | 10 + www/analytics/plugins/Overlay/lang/nl.json | 21 + www/analytics/plugins/Overlay/lang/nn.json | 5 + www/analytics/plugins/Overlay/lang/pl.json | 10 + www/analytics/plugins/Overlay/lang/pt-br.json | 21 + www/analytics/plugins/Overlay/lang/pt.json | 5 + www/analytics/plugins/Overlay/lang/ro.json | 20 + www/analytics/plugins/Overlay/lang/ru.json | 15 + www/analytics/plugins/Overlay/lang/sk.json | 8 + www/analytics/plugins/Overlay/lang/sl.json | 5 + www/analytics/plugins/Overlay/lang/sq.json | 5 + www/analytics/plugins/Overlay/lang/sr.json | 21 + www/analytics/plugins/Overlay/lang/sv.json | 20 + www/analytics/plugins/Overlay/lang/ta.json | 8 + www/analytics/plugins/Overlay/lang/te.json | 7 + www/analytics/plugins/Overlay/lang/th.json | 9 + www/analytics/plugins/Overlay/lang/tl.json | 19 + www/analytics/plugins/Overlay/lang/tr.json | 5 + www/analytics/plugins/Overlay/lang/uk.json | 5 + www/analytics/plugins/Overlay/lang/vi.json | 20 + www/analytics/plugins/Overlay/lang/zh-cn.json | 21 + www/analytics/plugins/Overlay/lang/zh-tw.json | 5 + .../plugins/Overlay/stylesheets/overlay.css | 118 +- .../plugins/Overlay/templates/index.twig | 50 +- .../Overlay/templates/index_noframe.twig | 4 +- .../Overlay/templates/notifyParentIframe.twig | 1 + .../Overlay/templates/renderSidebar.twig | 22 +- .../templates/showErrorWrongDomain.twig | 5 +- .../templates/startOverlaySession.twig | 50 + www/analytics/plugins/PiwikPro/PiwikPro.php | 27 + www/analytics/plugins/PiwikPro/Promo.php | 65 + www/analytics/plugins/PiwikPro/Widgets.php | 82 + .../plugins/PiwikPro/config/test.php | 13 + .../plugins/PiwikPro/images/promo.png | Bin 0 -> 2349 bytes www/analytics/plugins/PiwikPro/lang/en.json | 6 + www/analytics/plugins/PiwikPro/plugin.json | 5 + .../plugins/PiwikPro/stylesheets/widget.less | 29 + .../templates/promoPiwikProWidget.twig | 12 + .../plugins/PrivacyManager/Config.php | 9 +- .../plugins/PrivacyManager/Controller.php | 45 +- .../DoNotTrackHeaderChecker.php | 121 +- .../plugins/PrivacyManager/IPAnonymizer.php | 44 +- .../plugins/PrivacyManager/LogDataPurger.php | 238 +- www/analytics/plugins/PrivacyManager/Menu.php | 24 + .../plugins/PrivacyManager/PrivacyManager.php | 109 +- .../plugins/PrivacyManager/ReportsPurger.php | 114 +- .../plugins/PrivacyManager/Tasks.php | 31 + .../javascripts/privacySettings.js | 15 +- .../plugins/PrivacyManager/lang/ar.json | 5 + .../plugins/PrivacyManager/lang/be.json | 20 + .../plugins/PrivacyManager/lang/bg.json | 56 + .../plugins/PrivacyManager/lang/ca.json | 58 + .../plugins/PrivacyManager/lang/cs.json | 68 + .../plugins/PrivacyManager/lang/da.json | 62 + .../plugins/PrivacyManager/lang/de.json | 68 + .../plugins/PrivacyManager/lang/el.json | 68 + .../plugins/PrivacyManager/lang/en.json | 68 + .../plugins/PrivacyManager/lang/es.json | 68 + .../plugins/PrivacyManager/lang/et.json | 17 + .../plugins/PrivacyManager/lang/fa.json | 48 + .../plugins/PrivacyManager/lang/fi.json | 61 + .../plugins/PrivacyManager/lang/fr.json | 68 + .../plugins/PrivacyManager/lang/he.json | 5 + .../plugins/PrivacyManager/lang/hi.json | 60 + .../plugins/PrivacyManager/lang/hr.json | 5 + .../plugins/PrivacyManager/lang/hu.json | 5 + .../plugins/PrivacyManager/lang/id.json | 58 + .../plugins/PrivacyManager/lang/is.json | 5 + .../plugins/PrivacyManager/lang/it.json | 68 + .../plugins/PrivacyManager/lang/ja.json | 68 + .../plugins/PrivacyManager/lang/ka.json | 5 + .../plugins/PrivacyManager/lang/ko.json | 68 + .../plugins/PrivacyManager/lang/lt.json | 13 + .../plugins/PrivacyManager/lang/lv.json | 16 + .../plugins/PrivacyManager/lang/nb.json | 24 + .../plugins/PrivacyManager/lang/nl.json | 64 + .../plugins/PrivacyManager/lang/nn.json | 6 + .../plugins/PrivacyManager/lang/pl.json | 39 + .../plugins/PrivacyManager/lang/pt-br.json | 68 + .../plugins/PrivacyManager/lang/pt.json | 20 + .../plugins/PrivacyManager/lang/ro.json | 61 + .../plugins/PrivacyManager/lang/ru.json | 60 + .../plugins/PrivacyManager/lang/sk.json | 11 + .../plugins/PrivacyManager/lang/sl.json | 14 + .../plugins/PrivacyManager/lang/sq.json | 22 + .../plugins/PrivacyManager/lang/sr.json | 68 + .../plugins/PrivacyManager/lang/sv.json | 65 + .../plugins/PrivacyManager/lang/te.json | 7 + .../plugins/PrivacyManager/lang/th.json | 21 + .../plugins/PrivacyManager/lang/tl.json | 59 + .../plugins/PrivacyManager/lang/tr.json | 13 + .../plugins/PrivacyManager/lang/uk.json | 5 + .../plugins/PrivacyManager/lang/vi.json | 59 + .../plugins/PrivacyManager/lang/zh-cn.json | 60 + .../plugins/PrivacyManager/lang/zh-tw.json | 5 + .../templates/privacySettings.twig | 572 +- www/analytics/plugins/Provider/API.php | 8 +- www/analytics/plugins/Provider/Archiver.php | 14 +- .../plugins/Provider/Columns/Provider.php | 107 + www/analytics/plugins/Provider/Controller.php | 12 +- www/analytics/plugins/Provider/Provider.php | 152 +- .../plugins/Provider/Reports/GetProvider.php | 43 + www/analytics/plugins/Provider/Visitor.php | 41 + www/analytics/plugins/Provider/functions.php | 17 +- www/analytics/plugins/Provider/lang/am.json | 6 + www/analytics/plugins/Provider/lang/ar.json | 9 + www/analytics/plugins/Provider/lang/be.json | 7 + www/analytics/plugins/Provider/lang/bg.json | 7 + www/analytics/plugins/Provider/lang/ca.json | 7 + www/analytics/plugins/Provider/lang/cs.json | 9 + www/analytics/plugins/Provider/lang/da.json | 9 + www/analytics/plugins/Provider/lang/de.json | 9 + www/analytics/plugins/Provider/lang/el.json | 9 + www/analytics/plugins/Provider/lang/en.json | 9 + www/analytics/plugins/Provider/lang/es.json | 9 + www/analytics/plugins/Provider/lang/et.json | 6 + www/analytics/plugins/Provider/lang/eu.json | 6 + www/analytics/plugins/Provider/lang/fa.json | 6 + www/analytics/plugins/Provider/lang/fi.json | 7 + www/analytics/plugins/Provider/lang/fr.json | 9 + www/analytics/plugins/Provider/lang/gl.json | 5 + www/analytics/plugins/Provider/lang/hi.json | 9 + www/analytics/plugins/Provider/lang/hu.json | 6 + www/analytics/plugins/Provider/lang/id.json | 7 + www/analytics/plugins/Provider/lang/is.json | 6 + www/analytics/plugins/Provider/lang/it.json | 9 + www/analytics/plugins/Provider/lang/ja.json | 9 + www/analytics/plugins/Provider/lang/ka.json | 6 + www/analytics/plugins/Provider/lang/ko.json | 9 + www/analytics/plugins/Provider/lang/lt.json | 6 + www/analytics/plugins/Provider/lang/lv.json | 6 + www/analytics/plugins/Provider/lang/nb.json | 9 + www/analytics/plugins/Provider/lang/nl.json | 9 + www/analytics/plugins/Provider/lang/pl.json | 6 + .../plugins/Provider/lang/pt-br.json | 9 + www/analytics/plugins/Provider/lang/pt.json | 7 + www/analytics/plugins/Provider/lang/ro.json | 7 + www/analytics/plugins/Provider/lang/ru.json | 8 + www/analytics/plugins/Provider/lang/sk.json | 6 + www/analytics/plugins/Provider/lang/sl.json | 9 + www/analytics/plugins/Provider/lang/sq.json | 7 + www/analytics/plugins/Provider/lang/sr.json | 9 + www/analytics/plugins/Provider/lang/sv.json | 8 + www/analytics/plugins/Provider/lang/ta.json | 6 + www/analytics/plugins/Provider/lang/th.json | 6 + www/analytics/plugins/Provider/lang/tl.json | 8 + www/analytics/plugins/Provider/lang/tr.json | 6 + www/analytics/plugins/Provider/lang/uk.json | 6 + www/analytics/plugins/Provider/lang/vi.json | 7 + .../plugins/Provider/lang/zh-cn.json | 7 + .../plugins/Provider/lang/zh-tw.json | 6 + www/analytics/plugins/Proxy/Controller.php | 13 +- www/analytics/plugins/Proxy/Proxy.php | 20 +- www/analytics/plugins/Proxy/plugin.json | 3 + www/analytics/plugins/Referrers/API.php | 230 +- www/analytics/plugins/Referrers/Archiver.php | 22 +- .../plugins/Referrers/Columns/Base.php | 514 + .../plugins/Referrers/Columns/Campaign.php | 65 + .../plugins/Referrers/Columns/Keyword.php | 64 + .../plugins/Referrers/Columns/Referrer.php | 20 + .../Referrers/Columns/ReferrerName.php | 57 + .../Referrers/Columns/ReferrerType.php | 60 + .../plugins/Referrers/Columns/ReferrerUrl.php | 42 + .../Referrers/Columns/SearchEngine.php | 20 + .../Referrers/Columns/SocialNetwork.php | 20 + .../plugins/Referrers/Columns/Website.php | 57 + .../plugins/Referrers/Columns/WebsitePage.php | 20 + .../plugins/Referrers/Controller.php | 218 +- .../DataTable/Filter/KeywordNotDefined.php | 25 + .../Filter/KeywordsFromSearchEngineId.php | 62 + .../Filter/SearchEnginesFromKeywordId.php | 64 + .../Filter/SetGetReferrerTypeSubtables.php | 91 + .../DataTable/Filter/UrlsForSocial.php | 48 + .../DataTable/Filter/UrlsFromWebsiteId.php | 46 + www/analytics/plugins/Referrers/Menu.php | 23 + www/analytics/plugins/Referrers/Referrers.php | 511 +- .../plugins/Referrers/Reports/Base.php | 18 + .../plugins/Referrers/Reports/GetAll.php | 52 + .../Referrers/Reports/GetCampaigns.php | 39 + .../plugins/Referrers/Reports/GetKeywords.php | 42 + .../Reports/GetKeywordsFromCampaignId.php | 35 + .../Reports/GetKeywordsFromSearchEngineId.php | 34 + .../Referrers/Reports/GetReferrerType.php | 76 + .../Referrers/Reports/GetSearchEngines.php | 43 + .../Reports/GetSearchEnginesFromKeywordId.php | 34 + .../plugins/Referrers/Reports/GetSocials.php | 55 + .../Referrers/Reports/GetUrlsForSocial.php | 36 + .../Reports/GetUrlsFromWebsiteId.php | 35 + .../plugins/Referrers/Reports/GetWebsites.php | 45 + .../plugins/Referrers/SearchEngine.php | 485 + www/analytics/plugins/Referrers/Segment.php | 21 + www/analytics/plugins/Referrers/Social.php | 179 + www/analytics/plugins/Referrers/Tasks.php | 54 + www/analytics/plugins/Referrers/Visitor.php | 130 + www/analytics/plugins/Referrers/Widgets.php | 24 + www/analytics/plugins/Referrers/functions.php | 191 +- .../searchEngines/chercherfr.aguea.com.png | Bin 0 -> 4303 bytes .../searchEngines/extern.peoplecheck.de.png | Bin 0 -> 347 bytes .../image.search.yahoo.co.jp.png | Bin 0 -> 522 bytes .../images.search.biglobe.ne.jp.png | Bin 0 -> 808 bytes .../images/searchEngines/k9safesearch.com.png | Bin 0 -> 562 bytes .../images/searchEngines/kwzf.net.png | Bin 0 -> 265 bytes .../images/searchEngines/m.sm.cn.png | Bin 0 -> 649 bytes .../images/searchEngines/search.auone.jp.png | Bin 0 -> 3609 bytes .../searchEngines/search.fooooo.com.png | Bin 0 -> 457 bytes .../searchEngines/search.genieo.com.png | Bin 0 -> 674 bytes .../images/searchEngines/search.seesaa.jp.png | Bin 0 -> 3368 bytes .../searchEngines/search.yahoo.co.jp.png | Bin 0 -> 522 bytes .../sp-image.search.auone.jp.png | Bin 0 -> 3609 bytes .../images/searchEngines/videa.seznam.cz.png | Bin 0 -> 553 bytes .../video.search.yahoo.co.jp.png | Bin 0 -> 522 bytes .../searchEngines/video.so-net.ne.jp.png | Bin 0 -> 3609 bytes .../searchEngines/videosearch.nifty.com.png | Bin 0 -> 565 bytes .../searchEngines/www.findhurtig.dk.png | Bin 0 -> 615 bytes .../images/searchEngines/www.haosou.com.png | Bin 0 -> 655 bytes .../images/searchEngines/www.kensaq.com.png | Bin 0 -> 278 bytes .../images/searchEngines/www.lookany.com.png | Bin 0 -> 635 bytes .../images/searchEngines/www.qwant.com.png | Bin 0 -> 991 bytes .../images/searchEngines/www.sm.de.png | Bin 0 -> 806 bytes .../images/searchEngines/www.so-net.ne.jp.png | Bin 0 -> 3609 bytes .../images/searchEngines/www.sputnik.ru.png | Bin 0 -> 372 bytes .../images/searchEngines/www.toppreise.ch.png | Bin 0 -> 599 bytes .../images/searchEngines/www.woopie.jp.png | Bin 0 -> 319 bytes .../images/searchEngines/www.zxuso.com.png | Bin 0 -> 814 bytes .../Referrers/images/socials/dribbble.com.png | Bin 0 -> 3468 bytes www/analytics/plugins/Referrers/lang/am.json | 25 + www/analytics/plugins/Referrers/lang/ar.json | 28 + www/analytics/plugins/Referrers/lang/be.json | 40 + www/analytics/plugins/Referrers/lang/bg.json | 50 + www/analytics/plugins/Referrers/lang/bn.json | 5 + www/analytics/plugins/Referrers/lang/bs.json | 5 + www/analytics/plugins/Referrers/lang/ca.json | 51 + www/analytics/plugins/Referrers/lang/cs.json | 55 + www/analytics/plugins/Referrers/lang/cy.json | 5 + www/analytics/plugins/Referrers/lang/da.json | 55 + www/analytics/plugins/Referrers/lang/de.json | 55 + www/analytics/plugins/Referrers/lang/el.json | 55 + www/analytics/plugins/Referrers/lang/en.json | 55 + www/analytics/plugins/Referrers/lang/es.json | 55 + www/analytics/plugins/Referrers/lang/et.json | 34 + www/analytics/plugins/Referrers/lang/eu.json | 27 + www/analytics/plugins/Referrers/lang/fa.json | 41 + www/analytics/plugins/Referrers/lang/fi.json | 53 + www/analytics/plugins/Referrers/lang/fr.json | 55 + www/analytics/plugins/Referrers/lang/gl.json | 18 + www/analytics/plugins/Referrers/lang/he.json | 17 + www/analytics/plugins/Referrers/lang/hi.json | 52 + www/analytics/plugins/Referrers/lang/hr.json | 5 + www/analytics/plugins/Referrers/lang/hu.json | 28 + www/analytics/plugins/Referrers/lang/id.json | 52 + www/analytics/plugins/Referrers/lang/is.json | 28 + www/analytics/plugins/Referrers/lang/it.json | 55 + www/analytics/plugins/Referrers/lang/ja.json | 55 + www/analytics/plugins/Referrers/lang/ka.json | 28 + www/analytics/plugins/Referrers/lang/ko.json | 55 + www/analytics/plugins/Referrers/lang/lt.json | 30 + www/analytics/plugins/Referrers/lang/lv.json | 23 + www/analytics/plugins/Referrers/lang/nb.json | 37 + www/analytics/plugins/Referrers/lang/nl.json | 52 + www/analytics/plugins/Referrers/lang/nn.json | 27 + www/analytics/plugins/Referrers/lang/pl.json | 29 + .../plugins/Referrers/lang/pt-br.json | 55 + www/analytics/plugins/Referrers/lang/pt.json | 40 + www/analytics/plugins/Referrers/lang/ro.json | 53 + www/analytics/plugins/Referrers/lang/ru.json | 53 + www/analytics/plugins/Referrers/lang/sk.json | 28 + www/analytics/plugins/Referrers/lang/sl.json | 28 + www/analytics/plugins/Referrers/lang/sq.json | 55 + www/analytics/plugins/Referrers/lang/sr.json | 55 + www/analytics/plugins/Referrers/lang/sv.json | 55 + www/analytics/plugins/Referrers/lang/ta.json | 5 + www/analytics/plugins/Referrers/lang/te.json | 15 + www/analytics/plugins/Referrers/lang/th.json | 29 + www/analytics/plugins/Referrers/lang/tl.json | 53 + www/analytics/plugins/Referrers/lang/tr.json | 19 + www/analytics/plugins/Referrers/lang/uk.json | 28 + www/analytics/plugins/Referrers/lang/vi.json | 52 + .../plugins/Referrers/lang/zh-cn.json | 55 + .../plugins/Referrers/lang/zh-tw.json | 29 + .../Referrers/templates/allReferrers.twig | 11 + .../getSearchEnginesAndKeywords.twig | 18 +- .../plugins/Referrers/templates/index.twig | 129 +- .../Referrers/templates/indexWebsites.twig | 18 +- www/analytics/plugins/Resolution/API.php | 50 + www/analytics/plugins/Resolution/Archiver.php | 76 + .../Resolution/Columns/Configuration.php | 20 + .../plugins/Resolution/Columns/Resolution.php | 53 + .../plugins/Resolution/Reports/Base.php | 32 + .../Resolution/Reports/GetConfiguration.php | 42 + .../Resolution/Reports/GetResolution.php | 40 + .../plugins/Resolution/Resolution.php | 43 + www/analytics/plugins/Resolution/Segment.php | 21 + www/analytics/plugins/Resolution/Visitor.php | 28 + .../plugins/Resolution/functions.php | 29 + www/analytics/plugins/Resolution/lang/am.json | 10 + www/analytics/plugins/Resolution/lang/ar.json | 10 + www/analytics/plugins/Resolution/lang/be.json | 11 + www/analytics/plugins/Resolution/lang/bg.json | 11 + www/analytics/plugins/Resolution/lang/ca.json | 11 + www/analytics/plugins/Resolution/lang/cs.json | 12 + www/analytics/plugins/Resolution/lang/da.json | 12 + www/analytics/plugins/Resolution/lang/de.json | 12 + www/analytics/plugins/Resolution/lang/el.json | 12 + www/analytics/plugins/Resolution/lang/en.json | 12 + www/analytics/plugins/Resolution/lang/es.json | 12 + www/analytics/plugins/Resolution/lang/et.json | 10 + www/analytics/plugins/Resolution/lang/eu.json | 10 + www/analytics/plugins/Resolution/lang/fa.json | 11 + www/analytics/plugins/Resolution/lang/fi.json | 11 + www/analytics/plugins/Resolution/lang/fr.json | 12 + www/analytics/plugins/Resolution/lang/gl.json | 12 + www/analytics/plugins/Resolution/lang/he.json | 5 + www/analytics/plugins/Resolution/lang/hi.json | 12 + www/analytics/plugins/Resolution/lang/hr.json | 10 + www/analytics/plugins/Resolution/lang/hu.json | 12 + www/analytics/plugins/Resolution/lang/id.json | 11 + www/analytics/plugins/Resolution/lang/is.json | 10 + www/analytics/plugins/Resolution/lang/it.json | 12 + www/analytics/plugins/Resolution/lang/ja.json | 12 + www/analytics/plugins/Resolution/lang/ka.json | 10 + www/analytics/plugins/Resolution/lang/ko.json | 12 + www/analytics/plugins/Resolution/lang/lt.json | 10 + www/analytics/plugins/Resolution/lang/lv.json | 11 + www/analytics/plugins/Resolution/lang/nb.json | 12 + www/analytics/plugins/Resolution/lang/nl.json | 12 + www/analytics/plugins/Resolution/lang/nn.json | 10 + www/analytics/plugins/Resolution/lang/pl.json | 10 + .../plugins/Resolution/lang/pt-br.json | 12 + www/analytics/plugins/Resolution/lang/pt.json | 11 + www/analytics/plugins/Resolution/lang/ro.json | 11 + www/analytics/plugins/Resolution/lang/ru.json | 12 + www/analytics/plugins/Resolution/lang/sk.json | 12 + www/analytics/plugins/Resolution/lang/sl.json | 9 + www/analytics/plugins/Resolution/lang/sq.json | 12 + www/analytics/plugins/Resolution/lang/sr.json | 12 + www/analytics/plugins/Resolution/lang/sv.json | 12 + www/analytics/plugins/Resolution/lang/te.json | 6 + www/analytics/plugins/Resolution/lang/th.json | 10 + www/analytics/plugins/Resolution/lang/tl.json | 11 + www/analytics/plugins/Resolution/lang/tr.json | 10 + www/analytics/plugins/Resolution/lang/uk.json | 10 + www/analytics/plugins/Resolution/lang/vi.json | 11 + .../plugins/Resolution/lang/zh-cn.json | 11 + .../plugins/Resolution/lang/zh-tw.json | 12 + www/analytics/plugins/SEO/API.php | 94 +- www/analytics/plugins/SEO/Controller.php | 47 - www/analytics/plugins/SEO/MajesticClient.php | 97 - .../plugins/SEO/Metric/Aggregator.php | 65 + www/analytics/plugins/SEO/Metric/Alexa.php | 53 + www/analytics/plugins/SEO/Metric/Bing.php | 56 + www/analytics/plugins/SEO/Metric/Dmoz.php | 63 + .../plugins/SEO/Metric/DomainAge.php | 141 + www/analytics/plugins/SEO/Metric/Google.php | 172 + www/analytics/plugins/SEO/Metric/Metric.php | 145 + .../plugins/SEO/Metric/MetricsProvider.php | 21 + .../plugins/SEO/Metric/ProviderCache.php | 48 + www/analytics/plugins/SEO/RankChecker.php | 377 - www/analytics/plugins/SEO/SEO.php | 32 +- www/analytics/plugins/SEO/Widgets.php | 55 + www/analytics/plugins/SEO/javascripts/rank.js | 2 +- www/analytics/plugins/SEO/lang/ar.json | 9 + www/analytics/plugins/SEO/lang/be.json | 9 + www/analytics/plugins/SEO/lang/bg.json | 13 + www/analytics/plugins/SEO/lang/ca.json | 12 + www/analytics/plugins/SEO/lang/cs.json | 13 + www/analytics/plugins/SEO/lang/da.json | 12 + www/analytics/plugins/SEO/lang/de.json | 13 + www/analytics/plugins/SEO/lang/el.json | 13 + www/analytics/plugins/SEO/lang/en.json | 13 + www/analytics/plugins/SEO/lang/es.json | 13 + www/analytics/plugins/SEO/lang/et.json | 11 + www/analytics/plugins/SEO/lang/fa.json | 12 + www/analytics/plugins/SEO/lang/fi.json | 12 + www/analytics/plugins/SEO/lang/fr.json | 13 + www/analytics/plugins/SEO/lang/he.json | 5 + www/analytics/plugins/SEO/lang/hi.json | 13 + www/analytics/plugins/SEO/lang/hu.json | 9 + www/analytics/plugins/SEO/lang/id.json | 12 + www/analytics/plugins/SEO/lang/it.json | 13 + www/analytics/plugins/SEO/lang/ja.json | 13 + www/analytics/plugins/SEO/lang/ka.json | 9 + www/analytics/plugins/SEO/lang/ko.json | 13 + www/analytics/plugins/SEO/lang/lt.json | 9 + www/analytics/plugins/SEO/lang/lv.json | 9 + www/analytics/plugins/SEO/lang/nb.json | 13 + www/analytics/plugins/SEO/lang/nl.json | 13 + www/analytics/plugins/SEO/lang/pl.json | 9 + www/analytics/plugins/SEO/lang/pt-br.json | 13 + www/analytics/plugins/SEO/lang/pt.json | 9 + www/analytics/plugins/SEO/lang/ro.json | 12 + www/analytics/plugins/SEO/lang/ru.json | 12 + www/analytics/plugins/SEO/lang/sk.json | 9 + www/analytics/plugins/SEO/lang/sl.json | 11 + www/analytics/plugins/SEO/lang/sq.json | 9 + www/analytics/plugins/SEO/lang/sr.json | 13 + www/analytics/plugins/SEO/lang/sv.json | 12 + www/analytics/plugins/SEO/lang/te.json | 6 + www/analytics/plugins/SEO/lang/th.json | 9 + www/analytics/plugins/SEO/lang/tl.json | 12 + www/analytics/plugins/SEO/lang/tr.json | 9 + www/analytics/plugins/SEO/lang/uk.json | 9 + www/analytics/plugins/SEO/lang/vi.json | 12 + www/analytics/plugins/SEO/lang/zh-cn.json | 13 + www/analytics/plugins/SEO/lang/zh-tw.json | 9 + .../plugins/SEO/templates/getRank.twig | 21 +- .../plugins/ScheduledReports/API.php | 309 +- .../plugins/ScheduledReports/Controller.php | 5 +- .../plugins/ScheduledReports/Menu.php | 76 + .../plugins/ScheduledReports/Model.php | 92 + .../ScheduledReports/ScheduledReports.php | 667 +- .../plugins/ScheduledReports/Tasks.php | 31 + .../ScheduledReports/config/tcpdf_config.php | 8 +- .../ScheduledReports/javascripts/pdf.js | 19 +- .../plugins/ScheduledReports/lang/ar.json | 29 + .../plugins/ScheduledReports/lang/be.json | 30 + .../plugins/ScheduledReports/lang/bg.json | 43 + .../plugins/ScheduledReports/lang/ca.json | 40 + .../plugins/ScheduledReports/lang/cs.json | 50 + .../plugins/ScheduledReports/lang/da.json | 47 + .../plugins/ScheduledReports/lang/de.json | 50 + .../plugins/ScheduledReports/lang/el.json | 50 + .../plugins/ScheduledReports/lang/en.json | 50 + .../plugins/ScheduledReports/lang/es.json | 48 + .../plugins/ScheduledReports/lang/et.json | 32 + .../plugins/ScheduledReports/lang/fa.json | 43 + .../plugins/ScheduledReports/lang/fi.json | 46 + .../plugins/ScheduledReports/lang/fr.json | 50 + .../plugins/ScheduledReports/lang/he.json | 8 + .../plugins/ScheduledReports/lang/hi.json | 44 + .../plugins/ScheduledReports/lang/hu.json | 15 + .../plugins/ScheduledReports/lang/id.json | 43 + .../plugins/ScheduledReports/lang/is.json | 15 + .../plugins/ScheduledReports/lang/it.json | 50 + .../plugins/ScheduledReports/lang/ja.json | 50 + .../plugins/ScheduledReports/lang/ka.json | 15 + .../plugins/ScheduledReports/lang/ko.json | 50 + .../plugins/ScheduledReports/lang/lt.json | 24 + .../plugins/ScheduledReports/lang/lv.json | 18 + .../plugins/ScheduledReports/lang/nb.json | 50 + .../plugins/ScheduledReports/lang/nl.json | 47 + .../plugins/ScheduledReports/lang/pl.json | 33 + .../plugins/ScheduledReports/lang/pt-br.json | 50 + .../plugins/ScheduledReports/lang/pt.json | 34 + .../plugins/ScheduledReports/lang/ro.json | 46 + .../plugins/ScheduledReports/lang/ru.json | 45 + .../plugins/ScheduledReports/lang/sk.json | 17 + .../plugins/ScheduledReports/lang/sl.json | 24 + .../plugins/ScheduledReports/lang/sq.json | 34 + .../plugins/ScheduledReports/lang/sr.json | 50 + .../plugins/ScheduledReports/lang/sv.json | 47 + .../plugins/ScheduledReports/lang/ta.json | 8 + .../plugins/ScheduledReports/lang/te.json | 7 + .../plugins/ScheduledReports/lang/th.json | 22 + .../plugins/ScheduledReports/lang/tl.json | 46 + .../plugins/ScheduledReports/lang/tr.json | 48 + .../plugins/ScheduledReports/lang/uk.json | 15 + .../plugins/ScheduledReports/lang/vi.json | 46 + .../plugins/ScheduledReports/lang/zh-cn.json | 50 + .../plugins/ScheduledReports/lang/zh-tw.json | 15 + .../stylesheets/scheduledreports.less | 11 + .../templates/_addReport.twig | 11 +- .../templates/_listReports.twig | 65 +- .../ScheduledReports/templates/index.twig | 26 +- www/analytics/plugins/SegmentEditor/API.php | 227 +- .../plugins/SegmentEditor/Controller.php | 20 - www/analytics/plugins/SegmentEditor/Model.php | 117 +- .../plugins/SegmentEditor/SegmentEditor.php | 64 +- .../SegmentEditor/SegmentFormatter.php | 147 + .../plugins/SegmentEditor/SegmentList.php | 32 + .../SegmentEditor/SegmentQueryDecorator.php | 70 + .../SegmentEditor/SegmentSelectorControl.php | 35 +- .../Services/StoredSegmentService.php | 51 + .../plugins/SegmentEditor/config/config.php | 7 + .../SegmentEditor/images/edit_segment.png | Bin 0 -> 1138 bytes .../SegmentEditor/javascripts/Segmentation.js | 487 +- .../plugins/SegmentEditor/lang/bg.json | 25 + .../plugins/SegmentEditor/lang/cs.json | 36 + .../plugins/SegmentEditor/lang/da.json | 30 + .../plugins/SegmentEditor/lang/de.json | 35 + .../plugins/SegmentEditor/lang/el.json | 36 + .../plugins/SegmentEditor/lang/en.json | 38 + .../plugins/SegmentEditor/lang/es.json | 31 + .../plugins/SegmentEditor/lang/et.json | 21 + .../plugins/SegmentEditor/lang/fa.json | 24 + .../plugins/SegmentEditor/lang/fi.json | 27 + .../plugins/SegmentEditor/lang/fr.json | 36 + .../plugins/SegmentEditor/lang/he.json | 6 + .../plugins/SegmentEditor/lang/hi.json | 27 + .../plugins/SegmentEditor/lang/id.json | 24 + .../plugins/SegmentEditor/lang/it.json | 36 + .../plugins/SegmentEditor/lang/ja.json | 32 + .../plugins/SegmentEditor/lang/lt.json | 5 + .../plugins/SegmentEditor/lang/nb.json | 17 + .../plugins/SegmentEditor/lang/nl.json | 31 + .../plugins/SegmentEditor/lang/pl.json | 16 + .../plugins/SegmentEditor/lang/pt-br.json | 36 + .../plugins/SegmentEditor/lang/ro.json | 29 + .../plugins/SegmentEditor/lang/ru.json | 23 + .../plugins/SegmentEditor/lang/sk.json | 6 + .../plugins/SegmentEditor/lang/sl.json | 5 + .../plugins/SegmentEditor/lang/sr.json | 31 + .../plugins/SegmentEditor/lang/sv.json | 32 + .../plugins/SegmentEditor/lang/tl.json | 28 + .../plugins/SegmentEditor/lang/tr.json | 8 + .../plugins/SegmentEditor/lang/vi.json | 24 + .../plugins/SegmentEditor/lang/zh-cn.json | 24 + .../stylesheets/segmentation.less | 846 +- .../templates/_segmentSelector.twig | 46 +- www/analytics/plugins/SitesManager/API.php | 512 +- .../plugins/SitesManager/Controller.php | 131 +- www/analytics/plugins/SitesManager/Menu.php | 58 + www/analytics/plugins/SitesManager/Model.php | 428 + .../plugins/SitesManager/SiteUrls.php | 212 + .../plugins/SitesManager/SitesManager.php | 174 +- .../Tracker/SitesManagerRequestProcessor.php | 74 + .../sites-manager/api-core.service.js | 29 + .../sites-manager/api-helper.service.js | 74 + .../sites-manager/api-site.service.js | 44 + .../sites-manager/edit-trigger.directive.js | 30 + .../multiline-field.directive.html | 7 + .../multiline-field.directive.js | 49 + .../sites-manager-admin-sites-model.js | 115 + .../sites-manager-site.controller.js | 193 + .../sites-manager/sites-manager-type-model.js | 52 + .../sites-manager/sites-manager.controller.js | 283 + .../SitesManager/javascripts/SitesManager.js | 473 - .../plugins/SitesManager/lang/am.json | 18 + .../plugins/SitesManager/lang/ar.json | 53 + .../plugins/SitesManager/lang/be.json | 52 + .../plugins/SitesManager/lang/bg.json | 59 + .../plugins/SitesManager/lang/bn.json | 6 + .../plugins/SitesManager/lang/bs.json | 6 + .../plugins/SitesManager/lang/ca.json | 71 + .../plugins/SitesManager/lang/cs.json | 88 + .../plugins/SitesManager/lang/cy.json | 5 + .../plugins/SitesManager/lang/da.json | 77 + .../plugins/SitesManager/lang/de.json | 87 + .../plugins/SitesManager/lang/el.json | 88 + .../plugins/SitesManager/lang/en.json | 88 + .../plugins/SitesManager/lang/es.json | 82 + .../plugins/SitesManager/lang/et.json | 34 + .../plugins/SitesManager/lang/eu.json | 20 + .../plugins/SitesManager/lang/fa.json | 61 + .../plugins/SitesManager/lang/fi.json | 76 + .../plugins/SitesManager/lang/fr.json | 84 + .../plugins/SitesManager/lang/gl.json | 15 + .../plugins/SitesManager/lang/he.json | 7 + .../plugins/SitesManager/lang/hi.json | 76 + .../plugins/SitesManager/lang/hr.json | 8 + .../plugins/SitesManager/lang/hu.json | 46 + .../plugins/SitesManager/lang/id.json | 75 + .../plugins/SitesManager/lang/is.json | 13 + .../plugins/SitesManager/lang/it.json | 85 + .../plugins/SitesManager/lang/ja.json | 84 + .../plugins/SitesManager/lang/ka.json | 46 + .../plugins/SitesManager/lang/ko.json | 87 + .../plugins/SitesManager/lang/lt.json | 48 + .../plugins/SitesManager/lang/lv.json | 41 + .../plugins/SitesManager/lang/nb.json | 84 + .../plugins/SitesManager/lang/nl.json | 72 + .../plugins/SitesManager/lang/nn.json | 24 + .../plugins/SitesManager/lang/pl.json | 52 + .../plugins/SitesManager/lang/pt-br.json | 88 + .../plugins/SitesManager/lang/pt.json | 52 + .../plugins/SitesManager/lang/ro.json | 76 + .../plugins/SitesManager/lang/ru.json | 77 + .../plugins/SitesManager/lang/sk.json | 19 + .../plugins/SitesManager/lang/sl.json | 33 + .../plugins/SitesManager/lang/sq.json | 71 + .../plugins/SitesManager/lang/sr.json | 84 + .../plugins/SitesManager/lang/sv.json | 83 + .../plugins/SitesManager/lang/ta.json | 6 + .../plugins/SitesManager/lang/te.json | 8 + .../plugins/SitesManager/lang/th.json | 47 + .../plugins/SitesManager/lang/tl.json | 75 + .../plugins/SitesManager/lang/tr.json | 34 + .../plugins/SitesManager/lang/uk.json | 46 + .../plugins/SitesManager/lang/vi.json | 75 + .../plugins/SitesManager/lang/zh-cn.json | 75 + .../plugins/SitesManager/lang/zh-tw.json | 46 + .../stylesheets/SitesManager.less | 171 +- .../templates/_displayJavascriptCode.twig | 9 +- .../templates/dialogs/dialogs.html | 3 + .../templates/dialogs/edit-dialog.html | 6 + .../templates/dialogs/remove-dialog.html | 7 + .../templates/global-settings.html | 85 + .../templates/help/excluded-ip-help.html | 7 + .../help/excluded-query-parameters-help.html | 7 + .../help/excluded-user-agents-help.html | 7 + .../templates/help/timezone-help.html | 18 + .../plugins/SitesManager/templates/index.html | 17 + .../plugins/SitesManager/templates/index.twig | 423 +- .../SitesManager/templates/loading.html | 6 + .../templates/measurable_type_settings.twig | 7 + .../templates/siteWithoutData.twig | 51 + .../sites-list/add-entity-dialog.html | 16 + .../templates/sites-list/add-site-link.html | 33 + .../templates/sites-list/site-fields.html | 152 + .../sites-list/site-search-field.html | 64 + .../templates/sites-list/sites-list.html | 13 + .../templates/sites-manager-header.html | 18 + www/analytics/plugins/Transitions/API.php | 45 +- .../plugins/Transitions/Controller.php | 2 +- .../plugins/Transitions/Transitions.php | 6 +- .../Transitions/javascripts/transitions.js | 137 +- .../plugins/Transitions/lang/bg.json | 26 + .../plugins/Transitions/lang/ca.json | 27 + .../plugins/Transitions/lang/cs.json | 28 + .../plugins/Transitions/lang/da.json | 27 + .../plugins/Transitions/lang/de.json | 28 + .../plugins/Transitions/lang/el.json | 28 + .../plugins/Transitions/lang/en.json | 28 + .../plugins/Transitions/lang/es.json | 28 + .../plugins/Transitions/lang/et.json | 25 + .../plugins/Transitions/lang/fa.json | 26 + .../plugins/Transitions/lang/fi.json | 27 + .../plugins/Transitions/lang/fr.json | 28 + .../plugins/Transitions/lang/hi.json | 27 + .../plugins/Transitions/lang/id.json | 27 + .../plugins/Transitions/lang/it.json | 28 + .../plugins/Transitions/lang/ja.json | 28 + .../plugins/Transitions/lang/ko.json | 28 + .../plugins/Transitions/lang/lt.json | 5 + .../plugins/Transitions/lang/nb.json | 28 + .../plugins/Transitions/lang/nl.json | 28 + .../plugins/Transitions/lang/pl.json | 16 + .../plugins/Transitions/lang/pt-br.json | 28 + .../plugins/Transitions/lang/ro.json | 27 + .../plugins/Transitions/lang/ru.json | 27 + .../plugins/Transitions/lang/sl.json | 18 + .../plugins/Transitions/lang/sr.json | 28 + .../plugins/Transitions/lang/sv.json | 27 + .../plugins/Transitions/lang/th.json | 7 + .../plugins/Transitions/lang/tl.json | 27 + .../plugins/Transitions/lang/tr.json | 5 + .../plugins/Transitions/lang/vi.json | 27 + .../plugins/Transitions/lang/zh-cn.json | 28 + .../stylesheets/_transitionColors.less | 4 +- .../Transitions/stylesheets/transitions.less | 10 +- www/analytics/plugins/UserCountry/API.php | 15 +- .../plugins/UserCountry/Archiver.php | 13 +- .../plugins/UserCountry/Columns/Base.php | 92 + .../plugins/UserCountry/Columns/City.php | 77 + .../plugins/UserCountry/Columns/Continent.php | 20 + .../plugins/UserCountry/Columns/Country.php | 155 + .../plugins/UserCountry/Columns/Latitude.php | 79 + .../plugins/UserCountry/Columns/Longitude.php | 79 + .../plugins/UserCountry/Columns/Provider.php | 56 + .../plugins/UserCountry/Columns/Region.php | 77 + .../AttributeHistoricalDataWithLocations.php | 202 + .../plugins/UserCountry/Controller.php | 69 +- .../Diagnostic/GeolocationDiagnostic.php | 64 + .../plugins/UserCountry/GeoIPAutoUpdater.php | 49 +- .../plugins/UserCountry/LocationProvider.php | 55 +- .../UserCountry/LocationProvider/Default.php | 113 - .../LocationProvider/DefaultProvider.php | 113 + .../UserCountry/LocationProvider/GeoIp.php | 2 +- .../LocationProvider/GeoIp/Pecl.php | 6 +- .../LocationProvider/GeoIp/Php.php | 48 +- .../LocationProvider/GeoIp/ServerBased.php | 47 +- www/analytics/plugins/UserCountry/Menu.php | 30 + .../plugins/UserCountry/Reports/Base.php | 64 + .../plugins/UserCountry/Reports/GetCity.php | 41 + .../UserCountry/Reports/GetContinent.php | 41 + .../UserCountry/Reports/GetCountry.php | 49 + .../plugins/UserCountry/Reports/GetRegion.php | 41 + www/analytics/plugins/UserCountry/Segment.php | 21 + www/analytics/plugins/UserCountry/Tasks.php | 20 + .../plugins/UserCountry/UserCountry.php | 432 +- www/analytics/plugins/UserCountry/Visitor.php | 115 + .../plugins/UserCountry/VisitorGeolocator.php | 312 + .../plugins/UserCountry/config/config.php | 7 + .../plugins/UserCountry/functions.php | 23 +- .../UserCountry/javascripts/userCountry.js | 2 +- .../plugins/UserCountry/lang/am.json | 8 + .../plugins/UserCountry/lang/ar.json | 30 + .../plugins/UserCountry/lang/be.json | 13 + .../plugins/UserCountry/lang/bg.json | 86 + .../plugins/UserCountry/lang/ca.json | 86 + .../plugins/UserCountry/lang/cs.json | 98 + .../plugins/UserCountry/lang/da.json | 97 + .../plugins/UserCountry/lang/de.json | 98 + .../plugins/UserCountry/lang/el.json | 98 + .../plugins/UserCountry/lang/en.json | 98 + .../plugins/UserCountry/lang/es.json | 98 + .../plugins/UserCountry/lang/et.json | 34 + .../plugins/UserCountry/lang/eu.json | 8 + .../plugins/UserCountry/lang/fa.json | 50 + .../plugins/UserCountry/lang/fi.json | 97 + .../plugins/UserCountry/lang/fr.json | 98 + .../plugins/UserCountry/lang/gl.json | 18 + .../plugins/UserCountry/lang/he.json | 15 + .../plugins/UserCountry/lang/hi.json | 88 + .../plugins/UserCountry/lang/hr.json | 12 + .../plugins/UserCountry/lang/hu.json | 13 + .../plugins/UserCountry/lang/id.json | 91 + .../plugins/UserCountry/lang/is.json | 12 + .../plugins/UserCountry/lang/it.json | 98 + .../plugins/UserCountry/lang/ja.json | 98 + .../plugins/UserCountry/lang/ka.json | 12 + .../plugins/UserCountry/lang/ko.json | 98 + .../plugins/UserCountry/lang/lt.json | 20 + .../plugins/UserCountry/lang/lv.json | 12 + .../plugins/UserCountry/lang/nb.json | 38 + .../plugins/UserCountry/lang/nl.json | 83 + .../plugins/UserCountry/lang/nn.json | 13 + .../plugins/UserCountry/lang/pl.json | 64 + .../plugins/UserCountry/lang/pt-br.json | 98 + .../plugins/UserCountry/lang/pt.json | 13 + .../plugins/UserCountry/lang/ro.json | 96 + .../plugins/UserCountry/lang/ru.json | 92 + .../plugins/UserCountry/lang/sk.json | 11 + .../plugins/UserCountry/lang/sl.json | 15 + .../plugins/UserCountry/lang/sq.json | 13 + .../plugins/UserCountry/lang/sr.json | 98 + .../plugins/UserCountry/lang/sv.json | 97 + .../plugins/UserCountry/lang/ta.json | 8 + .../plugins/UserCountry/lang/te.json | 12 + .../plugins/UserCountry/lang/th.json | 19 + .../plugins/UserCountry/lang/tl.json | 91 + .../plugins/UserCountry/lang/tr.json | 17 + .../plugins/UserCountry/lang/uk.json | 12 + .../plugins/UserCountry/lang/vi.json | 92 + .../plugins/UserCountry/lang/zh-cn.json | 98 + .../plugins/UserCountry/lang/zh-tw.json | 10 + .../UserCountry/templates/_updaterManage.twig | 37 +- .../UserCountry/templates/adminIndex.twig | 36 +- .../plugins/UserCountry/templates/index.twig | 43 +- .../plugins/UserCountryMap/Controller.php | 123 +- www/analytics/plugins/UserCountryMap/Menu.php | 24 + .../plugins/UserCountryMap/UserCountryMap.php | 51 +- .../plugins/UserCountryMap/images/cities.png | Bin 267 -> 1038 bytes .../plugins/UserCountryMap/images/regions.png | Bin 296 -> 1265 bytes .../images/zoom-out-disabled.png | Bin 270 -> 1297 bytes .../javascripts/realtime-map.js | 45 +- .../javascripts/vendor/chroma.min.js | 33 - .../javascripts/vendor/kartograph.min.js | 8 +- .../UserCountryMap/javascripts/visitor-map.js | 123 +- .../plugins/UserCountryMap/lang/ar.json | 5 + .../plugins/UserCountryMap/lang/be.json | 5 + .../plugins/UserCountryMap/lang/bg.json | 22 + .../plugins/UserCountryMap/lang/ca.json | 5 + .../plugins/UserCountryMap/lang/cs.json | 25 + .../plugins/UserCountryMap/lang/da.json | 24 + .../plugins/UserCountryMap/lang/de.json | 25 + .../plugins/UserCountryMap/lang/el.json | 25 + .../plugins/UserCountryMap/lang/en.json | 25 + .../plugins/UserCountryMap/lang/es.json | 25 + .../plugins/UserCountryMap/lang/et.json | 21 + .../plugins/UserCountryMap/lang/fa.json | 21 + .../plugins/UserCountryMap/lang/fi.json | 22 + .../plugins/UserCountryMap/lang/fr.json | 25 + .../plugins/UserCountryMap/lang/he.json | 10 + .../plugins/UserCountryMap/lang/hi.json | 23 + .../plugins/UserCountryMap/lang/hr.json | 5 + .../plugins/UserCountryMap/lang/hu.json | 5 + .../plugins/UserCountryMap/lang/id.json | 25 + .../plugins/UserCountryMap/lang/it.json | 25 + .../plugins/UserCountryMap/lang/ja.json | 25 + .../plugins/UserCountryMap/lang/ka.json | 5 + .../plugins/UserCountryMap/lang/ko.json | 25 + .../plugins/UserCountryMap/lang/lt.json | 9 + .../plugins/UserCountryMap/lang/lv.json | 5 + .../plugins/UserCountryMap/lang/nb.json | 25 + .../plugins/UserCountryMap/lang/nl.json | 25 + .../plugins/UserCountryMap/lang/pl.json | 21 + .../plugins/UserCountryMap/lang/pt-br.json | 25 + .../plugins/UserCountryMap/lang/pt.json | 5 + .../plugins/UserCountryMap/lang/ro.json | 22 + .../plugins/UserCountryMap/lang/ru.json | 24 + .../plugins/UserCountryMap/lang/sk.json | 12 + .../plugins/UserCountryMap/lang/sl.json | 9 + .../plugins/UserCountryMap/lang/sq.json | 5 + .../plugins/UserCountryMap/lang/sr.json | 25 + .../plugins/UserCountryMap/lang/sv.json | 24 + .../plugins/UserCountryMap/lang/te.json | 5 + .../plugins/UserCountryMap/lang/th.json | 5 + .../plugins/UserCountryMap/lang/tl.json | 24 + .../plugins/UserCountryMap/lang/tr.json | 6 + .../plugins/UserCountryMap/lang/uk.json | 5 + .../plugins/UserCountryMap/lang/vi.json | 22 + .../plugins/UserCountryMap/lang/zh-cn.json | 22 + .../plugins/UserCountryMap/lang/zh-tw.json | 5 + .../UserCountryMap/stylesheets/map.css | 1 - .../stylesheets/realtime-map.less | 23 +- .../stylesheets/visitor-map.less | 32 +- .../UserCountryMap/templates/realtimeMap.twig | 2 +- .../UserCountryMap/templates/visitorMap.twig | 22 +- www/analytics/plugins/UserLanguage/API.php | 61 + .../plugins/UserLanguage/Archiver.php | 92 + .../plugins/UserLanguage/Columns/Language.php | 64 + .../plugins/UserLanguage/Reports/Base.php | 19 + .../UserLanguage/Reports/GetLanguage.php | 44 + .../UserLanguage/Reports/GetLanguageCode.php | 33 + .../plugins/UserLanguage/UserLanguage.php | 55 + .../plugins/UserLanguage/Visitor.php | 31 + .../plugins/UserLanguage/functions.php | 82 + .../plugins/UserLanguage/lang/am.json | 5 + .../plugins/UserLanguage/lang/ar.json | 7 + .../plugins/UserLanguage/lang/be.json | 5 + .../plugins/UserLanguage/lang/bg.json | 6 + .../plugins/UserLanguage/lang/ca.json | 5 + .../plugins/UserLanguage/lang/cs.json | 7 + .../plugins/UserLanguage/lang/da.json | 7 + .../plugins/UserLanguage/lang/de.json | 7 + .../plugins/UserLanguage/lang/el.json | 7 + .../plugins/UserLanguage/lang/en.json | 7 + .../plugins/UserLanguage/lang/es.json | 7 + .../plugins/UserLanguage/lang/et.json | 6 + .../plugins/UserLanguage/lang/eu.json | 5 + .../plugins/UserLanguage/lang/fa.json | 6 + .../plugins/UserLanguage/lang/fi.json | 6 + .../plugins/UserLanguage/lang/fr.json | 7 + .../plugins/UserLanguage/lang/gl.json | 7 + .../plugins/UserLanguage/lang/he.json | 5 + .../plugins/UserLanguage/lang/hi.json | 7 + .../plugins/UserLanguage/lang/hr.json | 7 + .../plugins/UserLanguage/lang/hu.json | 5 + .../plugins/UserLanguage/lang/id.json | 7 + .../plugins/UserLanguage/lang/is.json | 5 + .../plugins/UserLanguage/lang/it.json | 7 + .../plugins/UserLanguage/lang/ja.json | 7 + .../plugins/UserLanguage/lang/ka.json | 5 + .../plugins/UserLanguage/lang/ko.json | 7 + .../plugins/UserLanguage/lang/lt.json | 6 + .../plugins/UserLanguage/lang/lv.json | 5 + .../plugins/UserLanguage/lang/nb.json | 7 + .../plugins/UserLanguage/lang/nl.json | 7 + .../plugins/UserLanguage/lang/nn.json | 5 + .../plugins/UserLanguage/lang/pl.json | 7 + .../plugins/UserLanguage/lang/pt-br.json | 7 + .../plugins/UserLanguage/lang/pt.json | 5 + .../plugins/UserLanguage/lang/ro.json | 6 + .../plugins/UserLanguage/lang/ru.json | 7 + .../plugins/UserLanguage/lang/sk.json | 7 + .../plugins/UserLanguage/lang/sl.json | 6 + .../plugins/UserLanguage/lang/sq.json | 5 + .../plugins/UserLanguage/lang/sr.json | 7 + .../plugins/UserLanguage/lang/sv.json | 7 + .../plugins/UserLanguage/lang/ta.json | 7 + .../plugins/UserLanguage/lang/te.json | 5 + .../plugins/UserLanguage/lang/th.json | 5 + .../plugins/UserLanguage/lang/tl.json | 6 + .../plugins/UserLanguage/lang/tr.json | 5 + .../plugins/UserLanguage/lang/uk.json | 5 + .../plugins/UserLanguage/lang/vi.json | 6 + .../plugins/UserLanguage/lang/zh-cn.json | 6 + .../plugins/UserLanguage/lang/zh-tw.json | 7 + www/analytics/plugins/UserSettings/API.php | 243 - .../plugins/UserSettings/Archiver.php | 171 - .../plugins/UserSettings/Controller.php | 89 - .../plugins/UserSettings/UserSettings.php | 465 - .../plugins/UserSettings/functions.php | 270 - .../plugins/UserSettings/images/os/IOS.gif | Bin 591 -> 0 bytes .../plugins/UserSettings/images/os/W2K.gif | Bin 185 -> 0 bytes .../plugins/UserSettings/images/os/W61.gif | Bin 1060 -> 0 bytes .../plugins/UserSettings/images/os/W65.gif | Bin 1060 -> 0 bytes .../plugins/UserSettings/images/os/W75.gif | Bin 1089 -> 0 bytes .../plugins/UserSettings/images/os/W95.gif | Bin 185 -> 0 bytes .../plugins/UserSettings/images/os/W98.gif | Bin 185 -> 0 bytes .../plugins/UserSettings/images/os/WI7.gif | Bin 191 -> 0 bytes .../plugins/UserSettings/images/os/WI8.gif | Bin 925 -> 0 bytes .../plugins/UserSettings/images/os/WME.gif | Bin 1025 -> 0 bytes .../plugins/UserSettings/images/os/WNT.gif | Bin 185 -> 0 bytes .../plugins/UserSettings/images/os/WP7.gif | Bin 1089 -> 0 bytes .../plugins/UserSettings/images/os/WS3.gif | Bin 191 -> 0 bytes .../plugins/UserSettings/images/os/WVI.gif | Bin 191 -> 0 bytes .../plugins/UserSettings/images/os/WXP.gif | Bin 191 -> 0 bytes .../UserSettings/images/screens/dual.gif | Bin 1082 -> 0 bytes .../UserSettings/images/screens/mobile.gif | Bin 324 -> 0 bytes .../UserSettings/images/screens/normal.gif | Bin 1088 -> 0 bytes .../UserSettings/images/screens/unknown.gif | Bin 80 -> 0 bytes .../UserSettings/images/screens/wide.gif | Bin 1025 -> 0 bytes .../plugins/UserSettings/templates/index.twig | 27 - www/analytics/plugins/UsersManager/API.php | 219 +- .../plugins/UsersManager/Controller.php | 210 +- .../UsersManager/LastSeenTimeLogger.php | 5 +- www/analytics/plugins/UsersManager/Menu.php | 34 + www/analytics/plugins/UsersManager/Model.php | 118 +- www/analytics/plugins/UsersManager/Tasks.php | 49 + .../plugins/UsersManager/UserAccessFilter.php | 183 + .../plugins/UsersManager/UserPreferences.php | 172 + .../plugins/UsersManager/UsersManager.php | 80 +- .../plugins/UsersManager/images/add.png | Bin 1366 -> 0 bytes .../javascripts/giveViewAccess.js | 176 + .../UsersManager/javascripts/usersManager.js | 33 +- .../UsersManager/javascripts/usersSettings.js | 3 +- .../plugins/UsersManager/lang/am.json | 24 + .../plugins/UsersManager/lang/ar.json | 42 + .../plugins/UsersManager/lang/be.json | 43 + .../plugins/UsersManager/lang/bg.json | 57 + .../plugins/UsersManager/lang/bs.json | 6 + .../plugins/UsersManager/lang/ca.json | 46 + .../plugins/UsersManager/lang/cs.json | 75 + .../plugins/UsersManager/lang/da.json | 63 + .../plugins/UsersManager/lang/de.json | 67 + .../plugins/UsersManager/lang/el.json | 75 + .../plugins/UsersManager/lang/en.json | 75 + .../plugins/UsersManager/lang/es.json | 63 + .../plugins/UsersManager/lang/et.json | 39 + .../plugins/UsersManager/lang/eu.json | 25 + .../plugins/UsersManager/lang/fa.json | 49 + .../plugins/UsersManager/lang/fi.json | 60 + .../plugins/UsersManager/lang/fr.json | 63 + .../plugins/UsersManager/lang/gl.json | 22 + .../plugins/UsersManager/lang/he.json | 12 + .../plugins/UsersManager/lang/hi.json | 26 + .../plugins/UsersManager/lang/hr.json | 10 + .../plugins/UsersManager/lang/hu.json | 41 + .../plugins/UsersManager/lang/id.json | 49 + .../plugins/UsersManager/lang/is.json | 20 + .../plugins/UsersManager/lang/it.json | 67 + .../plugins/UsersManager/lang/ja.json | 63 + .../plugins/UsersManager/lang/ka.json | 45 + .../plugins/UsersManager/lang/ko.json | 68 + .../plugins/UsersManager/lang/lt.json | 51 + .../plugins/UsersManager/lang/lv.json | 43 + .../plugins/UsersManager/lang/nb.json | 63 + .../plugins/UsersManager/lang/nl.json | 58 + .../plugins/UsersManager/lang/nn.json | 37 + .../plugins/UsersManager/lang/pl.json | 49 + .../plugins/UsersManager/lang/pt-br.json | 75 + .../plugins/UsersManager/lang/pt.json | 48 + .../plugins/UsersManager/lang/ro.json | 60 + .../plugins/UsersManager/lang/ru.json | 63 + .../plugins/UsersManager/lang/sk.json | 32 + .../plugins/UsersManager/lang/sl.json | 32 + .../plugins/UsersManager/lang/sq.json | 63 + .../plugins/UsersManager/lang/sr.json | 63 + .../plugins/UsersManager/lang/sv.json | 71 + .../plugins/UsersManager/lang/ta.json | 7 + .../plugins/UsersManager/lang/te.json | 17 + .../plugins/UsersManager/lang/th.json | 41 + .../plugins/UsersManager/lang/tl.json | 60 + .../plugins/UsersManager/lang/tr.json | 35 + .../plugins/UsersManager/lang/uk.json | 41 + .../plugins/UsersManager/lang/vi.json | 49 + .../plugins/UsersManager/lang/zh-cn.json | 49 + .../plugins/UsersManager/lang/zh-tw.json | 63 + .../stylesheets/usersManager.less | 47 +- .../templates/anonymousSettings.twig | 78 + .../plugins/UsersManager/templates/index.twig | 73 +- .../templates/noWebsiteAdminAccess.twig | 9 + .../UsersManager/templates/userSettings.twig | 265 +- www/analytics/plugins/VisitFrequency/API.php | 11 +- .../Columns/Metrics/ReturningMetric.php | 64 + .../plugins/VisitFrequency/Controller.php | 46 +- www/analytics/plugins/VisitFrequency/Menu.php | 19 + .../plugins/VisitFrequency/Reports/Get.php | 39 + .../plugins/VisitFrequency/VisitFrequency.php | 67 +- .../plugins/VisitFrequency/Widgets.php | 23 + .../plugins/VisitFrequency/lang/am.json | 13 + .../plugins/VisitFrequency/lang/ar.json | 17 + .../plugins/VisitFrequency/lang/be.json | 19 + .../plugins/VisitFrequency/lang/bg.json | 24 + .../plugins/VisitFrequency/lang/ca.json | 24 + .../plugins/VisitFrequency/lang/cs.json | 26 + .../plugins/VisitFrequency/lang/da.json | 26 + .../plugins/VisitFrequency/lang/de.json | 26 + .../plugins/VisitFrequency/lang/el.json | 26 + .../plugins/VisitFrequency/lang/en.json | 26 + .../plugins/VisitFrequency/lang/es.json | 26 + .../plugins/VisitFrequency/lang/et.json | 23 + .../plugins/VisitFrequency/lang/eu.json | 13 + .../plugins/VisitFrequency/lang/fa.json | 23 + .../plugins/VisitFrequency/lang/fi.json | 24 + .../plugins/VisitFrequency/lang/fr.json | 26 + .../plugins/VisitFrequency/lang/gl.json | 9 + .../plugins/VisitFrequency/lang/he.json | 20 + .../plugins/VisitFrequency/lang/hi.json | 19 + .../plugins/VisitFrequency/lang/hu.json | 26 + .../plugins/VisitFrequency/lang/id.json | 26 + .../plugins/VisitFrequency/lang/is.json | 17 + .../plugins/VisitFrequency/lang/it.json | 26 + .../plugins/VisitFrequency/lang/ja.json | 26 + .../plugins/VisitFrequency/lang/ka.json | 17 + .../plugins/VisitFrequency/lang/ko.json | 26 + .../plugins/VisitFrequency/lang/lt.json | 17 + .../plugins/VisitFrequency/lang/lv.json | 9 + .../plugins/VisitFrequency/lang/nb.json | 26 + .../plugins/VisitFrequency/lang/nl.json | 26 + .../plugins/VisitFrequency/lang/nn.json | 15 + .../plugins/VisitFrequency/lang/pl.json | 20 + .../plugins/VisitFrequency/lang/pt-br.json | 26 + .../plugins/VisitFrequency/lang/pt.json | 24 + .../plugins/VisitFrequency/lang/ro.json | 24 + .../plugins/VisitFrequency/lang/ru.json | 25 + .../plugins/VisitFrequency/lang/sk.json | 17 + .../plugins/VisitFrequency/lang/sl.json | 14 + .../plugins/VisitFrequency/lang/sq.json | 26 + .../plugins/VisitFrequency/lang/sr.json | 26 + .../plugins/VisitFrequency/lang/sv.json | 26 + .../plugins/VisitFrequency/lang/te.json | 6 + .../plugins/VisitFrequency/lang/th.json | 17 + .../plugins/VisitFrequency/lang/tl.json | 24 + .../plugins/VisitFrequency/lang/uk.json | 17 + .../plugins/VisitFrequency/lang/vi.json | 24 + .../plugins/VisitFrequency/lang/zh-cn.json | 24 + .../plugins/VisitFrequency/lang/zh-tw.json | 17 + .../VisitFrequency/templates/_sparklines.twig | 59 +- www/analytics/plugins/VisitTime/API.php | 17 +- www/analytics/plugins/VisitTime/Archiver.php | 12 +- .../VisitTime/Columns/DayOfTheWeek.php | 20 + .../plugins/VisitTime/Columns/LocalTime.php | 48 + .../plugins/VisitTime/Columns/ServerTime.php | 33 + .../plugins/VisitTime/Controller.php | 22 +- .../Filter/AddSegmentByLabelInUTC.php | 55 + www/analytics/plugins/VisitTime/Menu.php | 19 + .../plugins/VisitTime/Reports/Base.php | 40 + .../VisitTime/Reports/GetByDayOfWeek.php | 76 + .../GetVisitInformationPerLocalTime.php | 51 + .../GetVisitInformationPerServerTime.php | 46 + www/analytics/plugins/VisitTime/Segment.php | 21 + www/analytics/plugins/VisitTime/VisitTime.php | 222 +- www/analytics/plugins/VisitTime/functions.php | 6 +- www/analytics/plugins/VisitTime/lang/am.json | 11 + www/analytics/plugins/VisitTime/lang/ar.json | 12 + www/analytics/plugins/VisitTime/lang/be.json | 14 + www/analytics/plugins/VisitTime/lang/bg.json | 17 + www/analytics/plugins/VisitTime/lang/ca.json | 18 + www/analytics/plugins/VisitTime/lang/cs.json | 18 + www/analytics/plugins/VisitTime/lang/da.json | 17 + www/analytics/plugins/VisitTime/lang/de.json | 18 + www/analytics/plugins/VisitTime/lang/el.json | 18 + www/analytics/plugins/VisitTime/lang/en.json | 18 + www/analytics/plugins/VisitTime/lang/es.json | 18 + www/analytics/plugins/VisitTime/lang/et.json | 14 + www/analytics/plugins/VisitTime/lang/eu.json | 12 + www/analytics/plugins/VisitTime/lang/fa.json | 15 + www/analytics/plugins/VisitTime/lang/fi.json | 17 + www/analytics/plugins/VisitTime/lang/fr.json | 18 + www/analytics/plugins/VisitTime/lang/gl.json | 9 + www/analytics/plugins/VisitTime/lang/he.json | 15 + www/analytics/plugins/VisitTime/lang/hi.json | 15 + www/analytics/plugins/VisitTime/lang/hr.json | 7 + www/analytics/plugins/VisitTime/lang/hu.json | 12 + www/analytics/plugins/VisitTime/lang/id.json | 17 + www/analytics/plugins/VisitTime/lang/is.json | 12 + www/analytics/plugins/VisitTime/lang/it.json | 18 + www/analytics/plugins/VisitTime/lang/ja.json | 18 + www/analytics/plugins/VisitTime/lang/ka.json | 11 + www/analytics/plugins/VisitTime/lang/ko.json | 18 + www/analytics/plugins/VisitTime/lang/lt.json | 12 + www/analytics/plugins/VisitTime/lang/lv.json | 11 + www/analytics/plugins/VisitTime/lang/nb.json | 18 + www/analytics/plugins/VisitTime/lang/nl.json | 18 + www/analytics/plugins/VisitTime/lang/nn.json | 12 + www/analytics/plugins/VisitTime/lang/pl.json | 17 + .../plugins/VisitTime/lang/pt-br.json | 18 + www/analytics/plugins/VisitTime/lang/pt.json | 14 + www/analytics/plugins/VisitTime/lang/ro.json | 17 + www/analytics/plugins/VisitTime/lang/ru.json | 18 + www/analytics/plugins/VisitTime/lang/sk.json | 18 + www/analytics/plugins/VisitTime/lang/sl.json | 12 + www/analytics/plugins/VisitTime/lang/sq.json | 18 + www/analytics/plugins/VisitTime/lang/sr.json | 18 + www/analytics/plugins/VisitTime/lang/sv.json | 17 + www/analytics/plugins/VisitTime/lang/te.json | 6 + www/analytics/plugins/VisitTime/lang/th.json | 13 + www/analytics/plugins/VisitTime/lang/tl.json | 17 + www/analytics/plugins/VisitTime/lang/tr.json | 13 + www/analytics/plugins/VisitTime/lang/uk.json | 12 + www/analytics/plugins/VisitTime/lang/vi.json | 17 + .../plugins/VisitTime/lang/zh-cn.json | 18 + .../plugins/VisitTime/lang/zh-tw.json | 12 + .../plugins/VisitTime/templates/index.twig | 18 +- www/analytics/plugins/VisitorInterest/API.php | 34 +- .../plugins/VisitorInterest/Archiver.php | 12 +- .../VisitorInterest/Columns/PagesPerVisit.php | 20 + .../VisitorInterest/Columns/VisitDuration.php | 20 + .../Columns/VisitsByDaysSinceLastVisit.php | 49 + .../Columns/VisitsbyVisitNumber.php | 20 + .../plugins/VisitorInterest/Controller.php | 47 +- .../plugins/VisitorInterest/Menu.php | 20 + .../plugins/VisitorInterest/Reports/Base.php | 18 + .../GetNumberOfVisitsByDaysSinceLast.php | 49 + .../Reports/GetNumberOfVisitsByVisitCount.php | 60 + .../Reports/GetNumberOfVisitsPerPage.php | 62 + .../GetNumberOfVisitsPerVisitDuration.php | 62 + .../VisitorInterest/VisitorInterest.php | 215 +- .../plugins/VisitorInterest/lang/am.json | 12 + .../plugins/VisitorInterest/lang/ar.json | 15 + .../plugins/VisitorInterest/lang/be.json | 17 + .../plugins/VisitorInterest/lang/bg.json | 24 + .../plugins/VisitorInterest/lang/ca.json | 23 + .../plugins/VisitorInterest/lang/cs.json | 24 + .../plugins/VisitorInterest/lang/da.json | 23 + .../plugins/VisitorInterest/lang/de.json | 24 + .../plugins/VisitorInterest/lang/el.json | 24 + .../plugins/VisitorInterest/lang/en.json | 24 + .../plugins/VisitorInterest/lang/es.json | 24 + .../plugins/VisitorInterest/lang/et.json | 19 + .../plugins/VisitorInterest/lang/eu.json | 12 + .../plugins/VisitorInterest/lang/fa.json | 23 + .../plugins/VisitorInterest/lang/fi.json | 23 + .../plugins/VisitorInterest/lang/fr.json | 24 + .../plugins/VisitorInterest/lang/gl.json | 8 + .../plugins/VisitorInterest/lang/he.json | 19 + .../plugins/VisitorInterest/lang/hi.json | 19 + .../plugins/VisitorInterest/lang/hu.json | 15 + .../plugins/VisitorInterest/lang/id.json | 23 + .../plugins/VisitorInterest/lang/is.json | 16 + .../plugins/VisitorInterest/lang/it.json | 24 + .../plugins/VisitorInterest/lang/ja.json | 24 + .../plugins/VisitorInterest/lang/ka.json | 15 + .../plugins/VisitorInterest/lang/ko.json | 24 + .../plugins/VisitorInterest/lang/lt.json | 15 + .../plugins/VisitorInterest/lang/lv.json | 16 + .../plugins/VisitorInterest/lang/nb.json | 15 + .../plugins/VisitorInterest/lang/nl.json | 24 + .../plugins/VisitorInterest/lang/nn.json | 13 + .../plugins/VisitorInterest/lang/pl.json | 19 + .../plugins/VisitorInterest/lang/pt-br.json | 24 + .../plugins/VisitorInterest/lang/pt.json | 22 + .../plugins/VisitorInterest/lang/ro.json | 24 + .../plugins/VisitorInterest/lang/ru.json | 23 + .../plugins/VisitorInterest/lang/sk.json | 15 + .../plugins/VisitorInterest/lang/sl.json | 13 + .../plugins/VisitorInterest/lang/sq.json | 24 + .../plugins/VisitorInterest/lang/sr.json | 24 + .../plugins/VisitorInterest/lang/sv.json | 23 + .../plugins/VisitorInterest/lang/te.json | 14 + .../plugins/VisitorInterest/lang/th.json | 15 + .../plugins/VisitorInterest/lang/tl.json | 22 + .../plugins/VisitorInterest/lang/tr.json | 5 + .../plugins/VisitorInterest/lang/uk.json | 15 + .../plugins/VisitorInterest/lang/vi.json | 23 + .../plugins/VisitorInterest/lang/zh-cn.json | 24 + .../plugins/VisitorInterest/lang/zh-tw.json | 15 + .../VisitorInterest/templates/index.twig | 30 +- www/analytics/plugins/VisitsSummary/API.php | 84 +- .../plugins/VisitsSummary/Controller.php | 103 +- www/analytics/plugins/VisitsSummary/Menu.php | 20 + .../plugins/VisitsSummary/Reports/Get.php | 82 + .../plugins/VisitsSummary/VisitsSummary.php | 82 +- .../plugins/VisitsSummary/Widgets.php | 22 + .../plugins/VisitsSummary/lang/am.json | 12 + .../plugins/VisitsSummary/lang/ar.json | 16 + .../plugins/VisitsSummary/lang/be.json | 17 + .../plugins/VisitsSummary/lang/bg.json | 26 + .../plugins/VisitsSummary/lang/ca.json | 25 + .../plugins/VisitsSummary/lang/cs.json | 27 + .../plugins/VisitsSummary/lang/da.json | 27 + .../plugins/VisitsSummary/lang/de.json | 27 + .../plugins/VisitsSummary/lang/el.json | 27 + .../plugins/VisitsSummary/lang/en.json | 27 + .../plugins/VisitsSummary/lang/es.json | 27 + .../plugins/VisitsSummary/lang/et.json | 26 + .../plugins/VisitsSummary/lang/eu.json | 13 + .../plugins/VisitsSummary/lang/fa.json | 26 + .../plugins/VisitsSummary/lang/fi.json | 26 + .../plugins/VisitsSummary/lang/fr.json | 27 + .../plugins/VisitsSummary/lang/gl.json | 11 + .../plugins/VisitsSummary/lang/he.json | 26 + .../plugins/VisitsSummary/lang/hi.json | 17 + .../plugins/VisitsSummary/lang/hr.json | 8 + .../plugins/VisitsSummary/lang/hu.json | 17 + .../plugins/VisitsSummary/lang/id.json | 26 + .../plugins/VisitsSummary/lang/is.json | 16 + .../plugins/VisitsSummary/lang/it.json | 27 + .../plugins/VisitsSummary/lang/ja.json | 27 + .../plugins/VisitsSummary/lang/ka.json | 16 + .../plugins/VisitsSummary/lang/ko.json | 27 + .../plugins/VisitsSummary/lang/lt.json | 16 + .../plugins/VisitsSummary/lang/lv.json | 17 + .../plugins/VisitsSummary/lang/nb.json | 27 + .../plugins/VisitsSummary/lang/nl.json | 27 + .../plugins/VisitsSummary/lang/nn.json | 12 + .../plugins/VisitsSummary/lang/pl.json | 25 + .../plugins/VisitsSummary/lang/pt-br.json | 27 + .../plugins/VisitsSummary/lang/pt.json | 23 + .../plugins/VisitsSummary/lang/ro.json | 26 + .../plugins/VisitsSummary/lang/ru.json | 26 + .../plugins/VisitsSummary/lang/sk.json | 25 + .../plugins/VisitsSummary/lang/sl.json | 27 + .../plugins/VisitsSummary/lang/sq.json | 27 + .../plugins/VisitsSummary/lang/sr.json | 27 + .../plugins/VisitsSummary/lang/sv.json | 26 + .../plugins/VisitsSummary/lang/te.json | 8 + .../plugins/VisitsSummary/lang/th.json | 23 + .../plugins/VisitsSummary/lang/tl.json | 26 + .../plugins/VisitsSummary/lang/tr.json | 16 + .../plugins/VisitsSummary/lang/uk.json | 16 + .../plugins/VisitsSummary/lang/vi.json | 26 + .../plugins/VisitsSummary/lang/zh-cn.json | 27 + .../plugins/VisitsSummary/lang/zh-tw.json | 16 + .../VisitsSummary/stylesheets/datatable.less | 9 +- .../VisitsSummary/templates/_sparklines.twig | 137 +- .../VisitsSummary/templates/index.twig | 9 +- .../plugins/WebsiteMeasurable/Type.php | 19 + .../WebsiteMeasurable/WebsiteMeasurable.php | 13 + .../plugins/WebsiteMeasurable/lang/ar.json | 7 + .../plugins/WebsiteMeasurable/lang/cs.json | 7 + .../plugins/WebsiteMeasurable/lang/de.json | 7 + .../plugins/WebsiteMeasurable/lang/el.json | 7 + .../plugins/WebsiteMeasurable/lang/en.json | 7 + .../plugins/WebsiteMeasurable/lang/es.json | 7 + .../plugins/WebsiteMeasurable/lang/fr.json | 7 + .../plugins/WebsiteMeasurable/lang/hi.json | 7 + .../plugins/WebsiteMeasurable/lang/it.json | 7 + .../plugins/WebsiteMeasurable/lang/ja.json | 7 + .../plugins/WebsiteMeasurable/lang/ko.json | 7 + .../plugins/WebsiteMeasurable/lang/lt.json | 5 + .../plugins/WebsiteMeasurable/lang/nb.json | 7 + .../plugins/WebsiteMeasurable/lang/nl.json | 7 + .../plugins/WebsiteMeasurable/lang/pt-br.json | 7 + .../plugins/WebsiteMeasurable/lang/sk.json | 5 + .../plugins/WebsiteMeasurable/lang/sq.json | 6 + .../plugins/WebsiteMeasurable/lang/sr.json | 7 + .../plugins/WebsiteMeasurable/lang/sv.json | 7 + .../plugins/WebsiteMeasurable/lang/zh-tw.json | 7 + .../plugins/WebsiteMeasurable/plugin.json | 4 + .../plugins/Widgetize/Controller.php | 34 +- www/analytics/plugins/Widgetize/Menu.php | 24 + www/analytics/plugins/Widgetize/Widgetize.php | 26 +- .../Widgetize/javascripts/widgetize.js | 4 +- www/analytics/plugins/Widgetize/lang/ar.json | 5 + www/analytics/plugins/Widgetize/lang/be.json | 5 + www/analytics/plugins/Widgetize/lang/bg.json | 6 + www/analytics/plugins/Widgetize/lang/bn.json | 5 + www/analytics/plugins/Widgetize/lang/bs.json | 5 + www/analytics/plugins/Widgetize/lang/ca.json | 6 + www/analytics/plugins/Widgetize/lang/cs.json | 7 + www/analytics/plugins/Widgetize/lang/cy.json | 5 + www/analytics/plugins/Widgetize/lang/da.json | 7 + www/analytics/plugins/Widgetize/lang/de.json | 7 + www/analytics/plugins/Widgetize/lang/el.json | 7 + www/analytics/plugins/Widgetize/lang/en.json | 7 + www/analytics/plugins/Widgetize/lang/es.json | 7 + www/analytics/plugins/Widgetize/lang/et.json | 5 + www/analytics/plugins/Widgetize/lang/fa.json | 5 + www/analytics/plugins/Widgetize/lang/fi.json | 6 + www/analytics/plugins/Widgetize/lang/fr.json | 7 + www/analytics/plugins/Widgetize/lang/gl.json | 5 + www/analytics/plugins/Widgetize/lang/he.json | 6 + www/analytics/plugins/Widgetize/lang/hi.json | 7 + www/analytics/plugins/Widgetize/lang/hr.json | 5 + www/analytics/plugins/Widgetize/lang/hu.json | 5 + www/analytics/plugins/Widgetize/lang/id.json | 6 + www/analytics/plugins/Widgetize/lang/is.json | 5 + www/analytics/plugins/Widgetize/lang/it.json | 7 + www/analytics/plugins/Widgetize/lang/ja.json | 7 + www/analytics/plugins/Widgetize/lang/ka.json | 5 + www/analytics/plugins/Widgetize/lang/ko.json | 6 + www/analytics/plugins/Widgetize/lang/lt.json | 5 + www/analytics/plugins/Widgetize/lang/lv.json | 5 + www/analytics/plugins/Widgetize/lang/nb.json | 7 + www/analytics/plugins/Widgetize/lang/nl.json | 7 + www/analytics/plugins/Widgetize/lang/nn.json | 5 + www/analytics/plugins/Widgetize/lang/pl.json | 5 + .../plugins/Widgetize/lang/pt-br.json | 7 + www/analytics/plugins/Widgetize/lang/pt.json | 5 + www/analytics/plugins/Widgetize/lang/ro.json | 6 + www/analytics/plugins/Widgetize/lang/ru.json | 7 + www/analytics/plugins/Widgetize/lang/sk.json | 5 + www/analytics/plugins/Widgetize/lang/sl.json | 5 + www/analytics/plugins/Widgetize/lang/sq.json | 5 + www/analytics/plugins/Widgetize/lang/sr.json | 7 + www/analytics/plugins/Widgetize/lang/sv.json | 6 + www/analytics/plugins/Widgetize/lang/th.json | 5 + www/analytics/plugins/Widgetize/lang/tl.json | 6 + www/analytics/plugins/Widgetize/lang/tr.json | 5 + www/analytics/plugins/Widgetize/lang/uk.json | 5 + www/analytics/plugins/Widgetize/lang/vi.json | 6 + .../plugins/Widgetize/lang/zh-cn.json | 6 + .../plugins/Widgetize/lang/zh-tw.json | 5 + .../Widgetize/stylesheets/widgetize.less | 20 +- .../plugins/Widgetize/templates/iframe.twig | 12 +- .../plugins/Widgetize/templates/index.twig | 38 +- .../Widgetize/templates/testJsInclude1.twig | 22 - .../Widgetize/templates/testJsInclude2.twig | 27 - .../plugins/Zeitgeist/images/annotations.png | Bin 1627 -> 0 bytes .../Zeitgeist/images/annotations_starred.png | Bin 1618 -> 0 bytes .../plugins/Zeitgeist/images/chart_bar.png | Bin 170 -> 0 bytes .../Zeitgeist/images/chart_line_edit.png | Bin 993 -> 0 bytes .../plugins/Zeitgeist/images/chart_pie.png | Bin 355 -> 0 bytes .../plugins/Zeitgeist/images/close.png | Bin 1089 -> 0 bytes .../Zeitgeist/images/configure-highlight.png | Bin 933 -> 0 bytes .../plugins/Zeitgeist/images/configure.png | Bin 387 -> 0 bytes .../Zeitgeist/images/dashboard_h_bg_hover.png | Bin 333 -> 0 bytes .../Zeitgeist/images/datepicker_arr_l.png | Bin 191 -> 0 bytes .../Zeitgeist/images/datepicker_arr_r.png | Bin 196 -> 0 bytes .../plugins/Zeitgeist/images/export.png | Bin 219 -> 0 bytes .../plugins/Zeitgeist/images/goal.png | Bin 270 -> 0 bytes .../plugins/Zeitgeist/images/help.png | Bin 942 -> 0 bytes .../plugins/Zeitgeist/images/ico_delete.png | Bin 231 -> 0 bytes .../plugins/Zeitgeist/images/ico_edit.png | Bin 255 -> 0 bytes .../Zeitgeist/images/icon-calendar.gif | Bin 331 -> 0 bytes .../plugins/Zeitgeist/images/image.png | Bin 306 -> 0 bytes .../plugins/Zeitgeist/images/link.gif | Bin 75 -> 0 bytes .../plugins/Zeitgeist/images/loading-blue.gif | Bin 723 -> 0 bytes .../plugins/Zeitgeist/images/logo-header.png | Bin 3682 -> 0 bytes .../plugins/Zeitgeist/images/logo.png | Bin 11152 -> 0 bytes .../plugins/Zeitgeist/images/logo.svg | 9 - .../plugins/Zeitgeist/images/maximise.png | Bin 3182 -> 0 bytes .../plugins/Zeitgeist/images/minimise.png | Bin 2869 -> 0 bytes .../plugins/Zeitgeist/images/minus.png | Bin 176 -> 0 bytes .../plugins/Zeitgeist/images/plus.png | Bin 174 -> 0 bytes .../plugins/Zeitgeist/images/refresh.png | Bin 2978 -> 0 bytes .../plugins/Zeitgeist/images/search_bg.png | Bin 384 -> 0 bytes .../plugins/Zeitgeist/images/search_ico.png | Bin 175 -> 0 bytes .../Zeitgeist/images/sort_subtable_desc.png | Bin 171 -> 0 bytes .../plugins/Zeitgeist/images/sortasc.png | Bin 173 -> 0 bytes .../plugins/Zeitgeist/images/sortdesc.png | Bin 171 -> 0 bytes .../plugins/Zeitgeist/images/table.png | Bin 151 -> 0 bytes .../plugins/Zeitgeist/images/table_more.png | Bin 200 -> 0 bytes .../plugins/Zeitgeist/images/tagcloud.png | Bin 202 -> 0 bytes .../plugins/Zeitgeist/images/zoom-out.png | Bin 289 -> 0 bytes www/analytics/plugins/Zeitgeist/plugin.json | 5 - .../plugins/Zeitgeist/stylesheets/base.less | 28 - .../stylesheets/general/_default.less | 115 - .../Zeitgeist/stylesheets/general/_form.less | 126 - .../stylesheets/general/_jqueryUI.less | 235 - .../plugins/Zeitgeist/stylesheets/rtl.css | 1 - .../stylesheets/simple_structure.css | 100 - .../Zeitgeist/stylesheets/ui/_header.less | 59 - .../stylesheets/ui/_headerMessage.less | 67 - .../stylesheets/ui/_languageSelect.less | 39 - .../Zeitgeist/stylesheets/ui/_loading.less | 26 - .../stylesheets/ui/_periodSelect.less | 81 - .../Zeitgeist/stylesheets/ui/_siteSelect.less | 176 - .../Zeitgeist/templates/_jsCssIncludes.twig | 7 - .../templates/_jsGlobalVariables.twig | 39 - .../Zeitgeist/templates/_piwikTag.twig | 30 - .../plugins/Zeitgeist/templates/admin.twig | 59 - .../Zeitgeist/templates/ajaxMacros.twig | 18 - .../Zeitgeist/templates/dashboard.twig | 50 - .../Zeitgeist/templates/genericForm.twig | 36 - .../Zeitgeist/templates/javascriptCode.tpl | 15 - .../plugins/Zeitgeist/templates/macros.twig | 23 - .../templates/simpleLayoutFooter.tpl | 3 - .../templates/simpleLayoutHeader.tpl | 11 - www/analytics/robots.txt | 0 www/analytics/tests/README.md | 173 +- www/analytics/tmp/.htaccess | 17 +- www/analytics/vendor/.htaccess | 39 +- www/analytics/vendor/autoload.php | 2 +- www/analytics/vendor/composer/ClassLoader.php | 51 +- www/analytics/vendor/composer/LICENSE | 21 + .../vendor/composer/autoload_classmap.php | 2319 ++ .../vendor/composer/autoload_files.php | 4 +- .../vendor/composer/autoload_namespaces.php | 16 + .../vendor/composer/autoload_psr4.php | 11 + .../vendor/composer/autoload_real.php | 23 +- .../vendor/composer/include_paths.php | 13 + www/analytics/vendor/composer/installed.json | 1266 +- .../container-interop/LICENSE | 20 + .../container-interop/README.md | 85 + .../container-interop/composer.json | 11 + .../docs/ContainerInterface-meta.md | 114 + .../docs/ContainerInterface.md | 153 + .../docs/Delegate-lookup-meta.md | 259 + .../container-interop/docs/Delegate-lookup.md | 60 + .../docs/images/interoperating_containers.png | Bin 0 -> 35971 bytes .../docs/images/priority.png | Bin 0 -> 22949 bytes .../docs/images/side_by_side_containers.png | Bin 0 -> 22519 bytes .../Interop/Container/ContainerInterface.php | 37 + .../Exception/ContainerException.php | 13 + .../Container/Exception/NotFoundException.php | 13 + .../vendor/doctrine/annotations/LICENSE | 19 + .../vendor/doctrine/annotations/README.md | 19 + .../vendor/doctrine/annotations/composer.json | 31 + .../Common/Annotations/Annotation.php | 79 + .../Annotations/Annotation/Attribute.php | 47 + .../Annotations/Annotation/Attributes.php | 37 + .../Common/Annotations/Annotation/Enum.php | 84 + .../Annotation/IgnoreAnnotation.php | 54 + .../Annotations/Annotation/Required.php | 33 + .../Common/Annotations/Annotation/Target.php | 107 + .../Annotations/AnnotationException.php | 197 + .../Common/Annotations/AnnotationReader.php | 394 + .../Common/Annotations/AnnotationRegistry.php | 151 + .../Common/Annotations/CachedReader.php | 235 + .../Doctrine/Common/Annotations/DocLexer.php | 134 + .../Doctrine/Common/Annotations/DocParser.php | 1138 + .../Common/Annotations/FileCacheReader.php | 288 + .../Common/Annotations/IndexedReader.php | 119 + .../Doctrine/Common/Annotations/PhpParser.php | 91 + .../Doctrine/Common/Annotations/Reader.php | 89 + .../Annotations/SimpleAnnotationReader.php | 127 + .../Common/Annotations/TokenParser.php | 187 + www/analytics/vendor/doctrine/cache/LICENSE | 19 + www/analytics/vendor/doctrine/cache/README.md | 14 + .../vendor/doctrine/cache/UPGRADE.md | 16 + .../vendor/doctrine/cache/build.properties | 3 + www/analytics/vendor/doctrine/cache/build.xml | 110 + .../vendor/doctrine/cache/composer.json | 34 + .../lib/Doctrine/Common/Cache/ApcCache.php | 106 + .../lib/Doctrine/Common/Cache/ArrayCache.php | 94 + .../cache/lib/Doctrine/Common/Cache/Cache.php | 112 + .../Doctrine/Common/Cache/CacheProvider.php | 277 + .../lib/Doctrine/Common/Cache/ChainCache.php | 147 + .../Doctrine/Common/Cache/ClearableCache.php | 40 + .../Doctrine/Common/Cache/CouchbaseCache.php | 121 + .../lib/Doctrine/Common/Cache/FileCache.php | 237 + .../Doctrine/Common/Cache/FilesystemCache.php | 111 + .../Doctrine/Common/Cache/FlushableCache.php | 37 + .../Doctrine/Common/Cache/MemcacheCache.php | 125 + .../Doctrine/Common/Cache/MemcachedCache.php | 132 + .../Doctrine/Common/Cache/MongoDBCache.php | 191 + .../Doctrine/Common/Cache/MultiGetCache.php | 39 + .../Doctrine/Common/Cache/PhpFileCache.php | 120 + .../lib/Doctrine/Common/Cache/PredisCache.php | 107 + .../lib/Doctrine/Common/Cache/RedisCache.php | 142 + .../lib/Doctrine/Common/Cache/RiakCache.php | 250 + .../Doctrine/Common/Cache/SQLite3Cache.php | 219 + .../lib/Doctrine/Common/Cache/Version.php | 25 + .../lib/Doctrine/Common/Cache/VoidCache.php | 78 + .../Doctrine/Common/Cache/WinCacheCache.php | 91 + .../lib/Doctrine/Common/Cache/XcacheCache.php | 112 + .../Doctrine/Common/Cache/ZendDataCache.php | 83 + .../vendor/doctrine/cache/phpunit.xml.dist | 25 + www/analytics/vendor/doctrine/lexer/LICENSE | 19 + www/analytics/vendor/doctrine/lexer/README.md | 5 + .../vendor/doctrine/lexer/composer.json | 24 + .../Doctrine/Common/Lexer/AbstractLexer.php | 327 + www/analytics/vendor/leafo/lessphp/.gitignore | 8 - .../vendor/leafo/lessphp/.travis.yml | 5 - www/analytics/vendor/leafo/lessphp/LICENSE | 10 +- www/analytics/vendor/leafo/lessphp/Makefile | 2 +- www/analytics/vendor/leafo/lessphp/README.md | 10 +- .../vendor/leafo/lessphp/composer.json | 2 +- .../vendor/leafo/lessphp/docs/docs.md | 10 +- .../vendor/leafo/lessphp/lessc.inc.php | 201 +- www/analytics/vendor/leafo/lessphp/lessify | 2 +- .../vendor/leafo/lessphp/lessify.inc.php | 6 +- www/analytics/vendor/leafo/lessphp/package.sh | 1 + www/analytics/vendor/leafo/lessphp/plessc | 2 +- .../vendor/mnapoli/phpdocreader/LICENSE | 16 + .../vendor/mnapoli/phpdocreader/README.md | 57 + .../vendor/mnapoli/phpdocreader/composer.json | 16 + .../mnapoli/phpdocreader/phpunit.xml.dist | 19 + .../src/PhpDocReader/AnnotationException.php | 10 + .../src/PhpDocReader/PhpDocReader.php | 270 + .../vendor/monolog/monolog/CHANGELOG.mdown | 265 + www/analytics/vendor/monolog/monolog/LICENSE | 19 + .../vendor/monolog/monolog/README.mdown | 95 + .../vendor/monolog/monolog/composer.json | 65 + .../vendor/monolog/monolog/doc/01-usage.md | 228 + .../doc/02-handlers-formatters-processors.md | 142 + .../monolog/monolog/doc/03-utilities.md | 13 + .../monolog/monolog/doc/04-extending.md | 76 + .../vendor/monolog/monolog/doc/sockets.md | 39 + .../vendor/monolog/monolog/phpunit.xml.dist | 19 + .../monolog/src/Monolog/ErrorHandler.php | 224 + .../Monolog/Formatter/ChromePHPFormatter.php | 79 + .../Monolog/Formatter/ElasticaFormatter.php | 87 + .../Monolog/Formatter/FlowdockFormatter.php | 116 + .../Monolog/Formatter/FormatterInterface.php | 36 + .../Formatter/GelfMessageFormatter.php | 111 + .../src/Monolog/Formatter/HtmlFormatter.php | 140 + .../src/Monolog/Formatter/JsonFormatter.php | 116 + .../src/Monolog/Formatter/LineFormatter.php | 159 + .../src/Monolog/Formatter/LogglyFormatter.php | 47 + .../Monolog/Formatter/LogstashFormatter.php | 166 + .../Monolog/Formatter/MongoDBFormatter.php | 105 + .../Monolog/Formatter/NormalizerFormatter.php | 193 + .../src/Monolog/Formatter/ScalarFormatter.php | 48 + .../Monolog/Formatter/WildfireFormatter.php | 113 + .../src/Monolog/Handler/AbstractHandler.php | 184 + .../Handler/AbstractProcessingHandler.php | 66 + .../Monolog/Handler/AbstractSyslogHandler.php | 92 + .../src/Monolog/Handler/AmqpHandler.php | 98 + .../Monolog/Handler/BrowserConsoleHandler.php | 192 + .../src/Monolog/Handler/BufferHandler.php | 117 + .../src/Monolog/Handler/ChromePHPHandler.php | 204 + .../src/Monolog/Handler/CouchDBHandler.php | 72 + .../src/Monolog/Handler/CubeHandler.php | 149 + .../monolog/src/Monolog/Handler/Curl/Util.php | 57 + .../Handler/DoctrineCouchDBHandler.php | 45 + .../src/Monolog/Handler/DynamoDbHandler.php | 89 + .../Monolog/Handler/ElasticSearchHandler.php | 128 + .../src/Monolog/Handler/ErrorLogHandler.php | 82 + .../src/Monolog/Handler/FilterHandler.php | 140 + .../ActivationStrategyInterface.php | 28 + .../ChannelLevelActivationStrategy.php | 59 + .../ErrorLevelActivationStrategy.php | 34 + .../Monolog/Handler/FingersCrossedHandler.php | 153 + .../src/Monolog/Handler/FirePHPHandler.php | 195 + .../src/Monolog/Handler/FleepHookHandler.php | 126 + .../src/Monolog/Handler/FlowdockHandler.php | 127 + .../src/Monolog/Handler/GelfHandler.php | 73 + .../src/Monolog/Handler/GroupHandler.php | 80 + .../src/Monolog/Handler/HandlerInterface.php | 90 + .../src/Monolog/Handler/HipChatHandler.php | 337 + .../src/Monolog/Handler/IFTTTHandler.php | 69 + .../src/Monolog/Handler/LogEntriesHandler.php | 55 + .../src/Monolog/Handler/LogglyHandler.php | 102 + .../src/Monolog/Handler/MailHandler.php | 55 + .../src/Monolog/Handler/MandrillHandler.php | 68 + .../Handler/MissingExtensionException.php | 21 + .../src/Monolog/Handler/MongoDBHandler.php | 55 + .../Monolog/Handler/NativeMailerHandler.php | 176 + .../src/Monolog/Handler/NewRelicHandler.php | 198 + .../src/Monolog/Handler/NullHandler.php | 45 + .../src/Monolog/Handler/PHPConsoleHandler.php | 242 + .../src/Monolog/Handler/PsrHandler.php | 56 + .../src/Monolog/Handler/PushoverHandler.php | 185 + .../src/Monolog/Handler/RavenHandler.php | 207 + .../src/Monolog/Handler/RedisHandler.php | 97 + .../src/Monolog/Handler/RollbarHandler.php | 95 + .../Monolog/Handler/RotatingFileHandler.php | 155 + .../src/Monolog/Handler/SamplingHandler.php | 82 + .../src/Monolog/Handler/SlackHandler.php | 284 + .../src/Monolog/Handler/SocketHandler.php | 284 + .../src/Monolog/Handler/StreamHandler.php | 146 + .../Monolog/Handler/SwiftMailerHandler.php | 88 + .../src/Monolog/Handler/SyslogHandler.php | 67 + .../Monolog/Handler/SyslogUdp/UdpSocket.php | 56 + .../src/Monolog/Handler/SyslogUdpHandler.php | 82 + .../src/Monolog/Handler/TestHandler.php | 148 + .../Handler/WhatFailureGroupHandler.php | 57 + .../Monolog/Handler/ZendMonitorHandler.php | 95 + .../monolog/monolog/src/Monolog/Logger.php | 649 + .../src/Monolog/Processor/GitProcessor.php | 64 + .../Processor/IntrospectionProcessor.php | 102 + .../Processor/MemoryPeakUsageProcessor.php | 35 + .../src/Monolog/Processor/MemoryProcessor.php | 63 + .../Processor/MemoryUsageProcessor.php | 35 + .../Monolog/Processor/ProcessIdProcessor.php | 31 + .../Processor/PsrLogMessageProcessor.php | 48 + .../src/Monolog/Processor/TagProcessor.php | 44 + .../src/Monolog/Processor/UidProcessor.php | 46 + .../src/Monolog/Processor/WebProcessor.php | 105 + .../monolog/monolog/src/Monolog/Registry.php | 134 + .../vendor/pear/archive_tar/Archive/Tar.php | 2379 ++ .../vendor/pear/archive_tar/README.md | 33 + .../vendor/pear/archive_tar/composer.json | 54 + .../pear/archive_tar/docs/Archive_Tar.txt | 475 + .../vendor/pear/archive_tar/package.xml | 496 + .../vendor/pear/archive_tar/scripts/phptar.in | 236 + .../vendor/pear/archive_tar/sync-php4 | 6 + .../pear/console_getopt/Console/Getopt.php | 360 + .../vendor/pear/console_getopt/LICENSE | 25 + .../vendor/pear/console_getopt/README.rst | 26 + .../vendor/pear/console_getopt/composer.json | 35 + .../vendor/pear/console_getopt/package.xml | 269 + .../vendor/pear/pear-core-minimal/README.rst | 26 + .../pear/pear-core-minimal/composer.json | 32 + .../pear-core-minimal/copy-from-pear-core.sh | 5 + .../pear/pear-core-minimal/src/OS/Guess.php | 337 + .../pear/pear-core-minimal/src}/PEAR.php | 187 +- .../pear/pear-core-minimal/src/PEAR/Error.php | 14 + .../pear-core-minimal/src/PEAR/ErrorStack.php | 979 + .../pear/pear-core-minimal/src/System.php | 622 + .../pear/pear_exception}/LICENSE | 0 .../pear/pear_exception/PEAR/Exception.php | 456 + .../vendor/pear/pear_exception/composer.json | 43 + .../vendor/pear/pear_exception/package.xml | 120 + .../vendor/php-di/invoker/CONTRIBUTING.md | 15 + www/analytics/vendor/php-di/invoker/LICENSE | 21 + www/analytics/vendor/php-di/invoker/README.md | 234 + .../vendor/php-di/invoker/composer.json | 25 + .../php-di/invoker/doc/parameter-resolvers.md | 109 + .../php-di/invoker/src/CallableResolver.php | 131 + .../src/Exception/InvocationException.php | 12 + .../src/Exception/NotCallableException.php | 12 + .../NotEnoughParametersException.php | 12 + .../vendor/php-di/invoker/src/Invoker.php | 122 + .../php-di/invoker/src/InvokerInterface.php | 29 + .../AssociativeArrayResolver.php | 39 + .../ParameterNameContainerResolver.php | 51 + .../Container/TypeHintContainerResolver.php | 51 + .../DefaultValueResolver.php | 40 + .../NumericArrayResolver.php | 39 + .../ParameterResolver/ParameterResolver.php | 33 + .../src/ParameterResolver/ResolverChain.php | 69 + .../src/Reflection/CallableReflection.php | 57 + www/analytics/vendor/php-di/php-di/404.md | 3 + .../vendor/php-di/php-di/CONTRIBUTING.md | 52 + www/analytics/vendor/php-di/php-di/LICENSE | 18 + www/analytics/vendor/php-di/php-di/README.md | 23 + .../vendor/php-di/php-di/change-log.md | 279 + .../vendor/php-di/php-di/composer.json | 43 + .../vendor/php-di/php-di/couscous.yml | 95 + .../vendor/php-di/php-di/phpunit.xml.dist | 30 + .../php-di/src/DI/Annotation/Inject.php | 95 + .../php-di/src/DI/Annotation/Injectable.php | 74 + .../vendor/php-di/php-di/src/DI/Container.php | 330 + .../php-di/php-di/src/DI/ContainerBuilder.php | 258 + .../vendor/php-di/php-di/src/DI/Debug.php | 39 + .../AbstractFunctionCallDefinition.php | 82 + .../src/DI/Definition/AliasDefinition.php | 66 + .../src/DI/Definition/ArrayDefinition.php | 66 + .../Definition/ArrayDefinitionExtension.php | 61 + .../src/DI/Definition/CacheableDefinition.php | 19 + .../src/DI/Definition/DecoratorDefinition.php | 48 + .../php-di/src/DI/Definition/Definition.php | 32 + .../Dumper/AliasDefinitionDumper.php | 48 + .../Dumper/ArrayDefinitionDumper.php | 65 + .../Dumper/DecoratorDefinitionDumper.php | 37 + .../DI/Definition/Dumper/DefinitionDumper.php | 30 + .../Dumper/DefinitionDumperDispatcher.php | 68 + .../EnvironmentVariableDefinitionDumper.php | 63 + .../Dumper/FactoryDefinitionDumper.php | 37 + .../Dumper/ObjectDefinitionDumper.php | 156 + .../Dumper/StringDefinitionDumper.php | 37 + .../Dumper/ValueDefinitionDumper.php | 44 + .../src/DI/Definition/EntryReference.php | 52 + .../EnvironmentVariableDefinition.php | 116 + .../Exception/AnnotationException.php | 19 + .../Exception/DefinitionException.php | 30 + .../src/DI/Definition/FactoryDefinition.php | 75 + .../src/DI/Definition/HasSubDefinition.php | 28 + .../Helper/ArrayDefinitionExtensionHelper.php | 46 + .../DI/Definition/Helper/DefinitionHelper.php | 24 + .../EnvironmentVariableDefinitionHelper.php | 64 + .../Helper/FactoryDefinitionHelper.php | 72 + .../Helper/ObjectDefinitionHelper.php | 273 + .../Helper/StringDefinitionHelper.php | 39 + .../Helper/ValueDefinitionHelper.php | 42 + .../src/DI/Definition/InstanceDefinition.php | 76 + .../src/DI/Definition/ObjectDefinition.php | 294 + .../ObjectDefinition/MethodInjection.php | 54 + .../ObjectDefinition/PropertyInjection.php | 56 + .../DI/Definition/Resolver/AliasResolver.php | 77 + .../DI/Definition/Resolver/ArrayResolver.php | 105 + .../Definition/Resolver/DecoratorResolver.php | 107 + .../Resolver/DefinitionResolver.php | 44 + .../Resolver/EnvironmentVariableResolver.php | 94 + .../Definition/Resolver/FactoryResolver.php | 85 + .../Definition/Resolver/InstanceInjector.php | 67 + .../DI/Definition/Resolver/ObjectCreator.php | 271 + .../Definition/Resolver/ParameterResolver.php | 138 + .../Resolver/ResolverDispatcher.php | 141 + .../DI/Definition/Resolver/StringResolver.php | 98 + .../DI/Definition/Resolver/ValueResolver.php | 58 + .../DI/Definition/Source/AnnotationReader.php | 298 + .../src/DI/Definition/Source/Autowiring.php | 68 + .../Source/CachedDefinitionSource.php | 104 + .../DI/Definition/Source/DefinitionArray.php | 142 + .../DI/Definition/Source/DefinitionFile.php | 74 + .../DI/Definition/Source/DefinitionSource.php | 31 + .../Source/MutableDefinitionSource.php | 15 + .../src/DI/Definition/Source/SourceChain.php | 108 + .../src/DI/Definition/StringDefinition.php | 66 + .../src/DI/Definition/ValueDefinition.php | 67 + .../php-di/src/DI/DependencyException.php | 19 + .../php-di/php-di/src/DI/FactoryInterface.php | 34 + .../Invoker/DefinitionParameterResolver.php | 45 + .../php-di/php-di/src/DI/InvokerInterface.php | 19 + .../php-di/src/DI/NotFoundException.php | 19 + .../php-di/src/DI/Proxy/ProxyFactory.php | 95 + .../Reflection/CallableReflectionFactory.php | 53 + .../vendor/php-di/php-di/src/DI/Scope.php | 54 + .../vendor/php-di/php-di/src/DI/functions.php | 181 + www/analytics/vendor/piwik/cache/README.md | 172 + .../vendor/piwik/cache/composer.json | 31 + www/analytics/vendor/piwik/cache/phpunit.xml | 19 + .../vendor/piwik/cache/src/Backend.php | 62 + .../piwik/cache/src/Backend/ArrayCache.php | 46 + .../piwik/cache/src/Backend/Chained.php | 103 + .../piwik/cache/src/Backend/Factory.php | 111 + .../Factory/BackendNotFoundException.php | 15 + .../vendor/piwik/cache/src/Backend/File.php | 155 + .../piwik/cache/src/Backend/NullCache.php | 44 + .../vendor/piwik/cache/src/Backend/Redis.php | 41 + .../vendor/piwik/cache/src/Cache.php | 55 + .../vendor/piwik/cache/src/Eager.php | 138 + www/analytics/vendor/piwik/cache/src/Lazy.php | 121 + .../vendor/piwik/cache/src/Transient.php | 89 + .../vendor/piwik/decompress/README.md | 50 + .../vendor/piwik/decompress/composer.json | 23 + .../piwik/decompress/libs/PclZip/gnu-lgpl.txt | 504 + .../decompress/libs/PclZip/pclzip.lib.php | 5414 +++ .../piwik/decompress/libs/PclZip/readme.txt | 421 + .../vendor/piwik/decompress/libs/README.md | 10 + .../vendor/piwik/decompress/phpunit.xml | 29 + .../decompress/src/DecompressInterface.php | 38 + .../piwik/decompress/src}/Gzip.php | 14 +- .../piwik/decompress/src}/PclZip.php | 21 +- .../vendor/piwik/decompress/src/Tar.php | 78 + .../piwik/decompress/src/ZipArchive.php | 137 + .../vendor/piwik/device-detector/.gitignore | 3 - .../vendor/piwik/device-detector/.travis.yml | 13 - .../piwik/device-detector/Cache/Cache.php | 22 + .../device-detector/Cache/StaticCache.php | 53 + .../piwik/device-detector/DeviceDetector.php | 1492 +- .../piwik/device-detector/Parser/Bot.php | 72 + .../device-detector/Parser/Client/Browser.php | 263 + .../Parser/Client/Browser/Engine.php | 76 + .../Parser/Client/ClientParserAbstract.php | 74 + .../Parser/Client/FeedReader.php | 21 + .../device-detector/Parser/Client/Library.php | 21 + .../Parser/Client/MediaPlayer.php | 21 + .../Parser/Client/MobileApp.php | 21 + .../device-detector/Parser/Client/PIM.php | 21 + .../device-detector/Parser/Device/Camera.php | 30 + .../Parser/Device/CarBrowser.php | 30 + .../device-detector/Parser/Device/Console.php | 30 + .../Parser/Device/DeviceParserAbstract.php | 504 + .../device-detector/Parser/Device/HbbTv.php | 53 + .../device-detector/Parser/Device/Mobile.php | 21 + .../Parser/Device/PortableMediaPlayer.php | 30 + .../Parser/OperatingSystem.php | 245 + .../device-detector/Parser/ParserAbstract.php | 283 + .../device-detector/Parser/VendorFragment.php | 44 + .../vendor/piwik/device-detector/README.md | 145 +- .../piwik/device-detector/composer.json | 17 +- .../piwik/device-detector/regexes/bots.yml | 1178 + .../device-detector/regexes/browsers.yml | 540 - .../regexes/client/browser_engine.yml | 30 + .../regexes/client/browsers.yml | 844 + .../regexes/client/feed_readers.yml | 108 + .../regexes/client/libraries.yml | 34 + .../regexes/client/mediaplayers.yml | 78 + .../regexes/client/mobile_apps.yml | 51 + .../device-detector/regexes/client/pim.yml | 38 + .../regexes/device/cameras.yml | 28 + .../regexes/device/car_browsers.yml | 12 + .../regexes/device/consoles.yml | 40 + .../regexes/device/mobiles.yml | 4569 +++ .../regexes/device/portable_media_player.yml | 61 + .../regexes/device/televisions.yml | 282 + .../piwik/device-detector/regexes/mobiles.yml | 1322 - .../piwik/device-detector/regexes/oss.yml | 436 +- .../device-detector/regexes/televisions.yml | 242 - .../regexes/vendorfragments.yml | 71 + www/analytics/vendor/piwik/ini/README.md | 74 + www/analytics/vendor/piwik/ini/composer.json | 22 + www/analytics/vendor/piwik/ini/phpunit.xml | 19 + .../vendor/piwik/ini/src/IniReader.php | 390 + .../piwik/ini/src/IniReadingException.php | 16 + .../vendor/piwik/ini/src/IniWriter.php | 120 + .../piwik/ini/src/IniWritingException.php | 16 + www/analytics/vendor/piwik/network/README.md | 69 + .../vendor/piwik/network/composer.json | 21 + .../vendor/piwik/network/phpunit.xml | 22 + www/analytics/vendor/piwik/network/src/IP.php | 216 + .../vendor/piwik/network/src/IPUtils.php | 178 + .../vendor/piwik/network/src/IPv4.php | 45 + .../vendor/piwik/network/src/IPv6.php | 77 + .../vendor/piwik/piwik-php-tracker/LICENSE | 27 + .../piwik/piwik-php-tracker/PiwikTracker.php | 1847 + .../vendor/piwik/piwik-php-tracker/README.md | 16 + .../piwik/piwik-php-tracker/composer.json | 22 + .../referrer-spam-blacklist/CONTRIBUTING.md | 13 + .../piwik/referrer-spam-blacklist/README.md | 89 + .../referrer-spam-blacklist/composer.json | 5 + .../referrer-spam-blacklist/spammers.txt | 329 + .../searchengine-and-social-list/README.md | 145 + .../SearchEngines.yml | 2304 ++ .../searchengine-and-social-list/Socials.yml | 217 + .../composer.json | 5 + www/analytics/vendor/psr/log/LICENSE | 19 + .../vendor/psr/log/Psr/Log/AbstractLogger.php | 120 + .../log/Psr/Log/InvalidArgumentException.php | 7 + .../vendor/psr/log/Psr/Log/LogLevel.php | 18 + .../psr/log/Psr/Log/LoggerAwareInterface.php | 17 + .../psr/log/Psr/Log/LoggerAwareTrait.php | 22 + .../psr/log/Psr/Log/LoggerInterface.php | 114 + .../vendor/psr/log/Psr/Log/LoggerTrait.php | 131 + .../vendor/psr/log/Psr/Log/NullLogger.php | 27 + .../log/Psr/Log/Test/LoggerInterfaceTest.php | 116 + www/analytics/vendor/psr/log/README.md | 45 + www/analytics/vendor/psr/log/composer.json | 17 + .../Symfony/Component/Console/.gitignore | 3 - .../Symfony/Component/Console/Application.php | 264 +- .../Symfony/Component/Console/CHANGELOG.md | 17 + .../Component/Console/Command/Command.php | 81 +- .../Component/Console/Command/HelpCommand.php | 4 +- .../Component/Console/Command/ListCommand.php | 4 +- .../Component/Console/ConsoleEvents.php | 6 + .../Descriptor/ApplicationDescription.php | 8 +- .../Console/Descriptor/Descriptor.php | 6 +- .../Console/Descriptor/JsonDescriptor.php | 30 +- .../Console/Descriptor/MarkdownDescriptor.php | 6 +- .../Console/Descriptor/TextDescriptor.php | 38 +- .../Console/Descriptor/XmlDescriptor.php | 2 + .../Console/Event/ConsoleCommandEvent.php | 43 +- .../Console/Event/ConsoleExceptionEvent.php | 2 +- .../Console/Event/ConsoleTerminateEvent.php | 6 +- .../Console/Formatter/OutputFormatter.php | 32 +- .../Formatter/OutputFormatterInterface.php | 6 +- .../Formatter/OutputFormatterStyle.php | 65 +- .../Formatter/OutputFormatterStyleStack.php | 4 +- .../Console/Helper/DebugFormatterHelper.php | 127 + .../Console/Helper/DescriptorHelper.php | 12 +- .../Component/Console/Helper/DialogHelper.php | 77 +- .../Console/Helper/FormatterHelper.php | 16 +- .../Component/Console/Helper/Helper.php | 69 +- .../Component/Console/Helper/HelperSet.php | 2 +- .../Console/Helper/InputAwareHelper.php | 2 +- .../Console/Helper/ProcessHelper.php | 142 + .../Component/Console/Helper/ProgressBar.php | 611 + .../Console/Helper/ProgressHelper.php | 83 +- .../Console/Helper/QuestionHelper.php | 418 + .../Component/Console/Helper/Table.php | 410 + .../Component/Console/Helper/TableHelper.php | 325 +- .../Console/Helper/TableSeparator.php | 21 + .../Component/Console/Helper/TableStyle.php | 251 + .../Component/Console/Input/ArgvInput.php | 22 +- .../Component/Console/Input/ArrayInput.php | 12 +- .../Symfony/Component/Console/Input/Input.php | 18 +- .../Component/Console/Input/InputArgument.php | 16 +- .../Console/Input/InputDefinition.php | 27 +- .../Console/Input/InputInterface.php | 16 +- .../Component/Console/Input/InputOption.php | 23 +- .../Component/Console/Input/StringInput.php | 2 - .../console/Symfony/Component/Console/LICENSE | 2 +- .../Console/Logger/ConsoleLogger.php | 118 + .../Console/Output/ConsoleOutput.php | 66 +- .../Component/Console/Output/NullOutput.php | 20 + .../Component/Console/Output/Output.php | 8 +- .../Console/Output/OutputInterface.php | 28 +- .../Component/Console/Output/StreamOutput.php | 12 +- .../Console/Question/ChoiceQuestion.php | 151 + .../Console/Question/ConfirmationQuestion.php | 55 + .../Component/Console/Question/Question.php | 238 + .../Symfony/Component/Console/README.md | 46 +- .../Symfony/Component/Console/Shell.php | 6 +- .../Console/Tester/ApplicationTester.php | 6 +- .../Console/Tester/CommandTester.php | 14 +- .../Symfony/Component/Console/composer.json | 15 +- .../Component/Console/phpunit.xml.dist | 14 +- .../Component/EventDispatcher/CHANGELOG.md | 23 + .../ContainerAwareEventDispatcher.php | 202 + .../Debug/TraceableEventDispatcher.php | 335 + .../TraceableEventDispatcherInterface.php | 34 + .../EventDispatcher/Debug/WrappedListener.php | 71 + .../RegisterListenersPass.php | 110 + .../Component/EventDispatcher/Event.php | 130 + .../EventDispatcher/EventDispatcher.php | 185 + .../EventDispatcherInterface.php | 96 + .../EventSubscriberInterface.php | 50 + .../EventDispatcher/GenericEvent.php | 186 + .../ImmutableEventDispatcher.php | 93 + .../Symfony/Component/EventDispatcher/LICENSE | 19 + .../Component/EventDispatcher/README.md | 27 + .../Component/EventDispatcher/composer.json | 43 + .../EventDispatcher/phpunit.xml.dist | 28 + .../Symfony/Bridge/Monolog/CHANGELOG.md | 13 + .../Monolog/Formatter/ConsoleFormatter.php | 55 + .../Monolog/Handler/ChromePhpHandler.php | 81 + .../Bridge/Monolog/Handler/ConsoleHandler.php | 186 + .../Bridge/Monolog/Handler/DebugHandler.php | 59 + .../NotFoundActivationStrategy.php | 53 + .../Bridge/Monolog/Handler/FirePHPHandler.php | 82 + .../Monolog/Handler/SwiftMailerHandler.php | 90 + .../Symfony/Bridge/Monolog/LICENSE | 19 + .../Symfony/Bridge/Monolog/Logger.php | 94 + .../Bridge/Monolog/Processor/WebProcessor.php | 36 + .../Symfony/Bridge/Monolog/README.md | 13 + .../Symfony/Bridge/Monolog/composer.json | 43 + .../Symfony/Bridge/Monolog/phpunit.xml.dist | 27 + .../vendor/tecnickcom/tcpdf/CHANGELOG.TXT | 2943 ++ .../tecnickcom}/tcpdf/LICENSE.TXT | 0 .../tecnickcom}/tcpdf/README.TXT | 18 +- .../vendor/tecnickcom/tcpdf/composer.json | 40 + .../tecnickcom}/tcpdf/config/tcpdf_config.php | 20 +- .../tecnickcom/tcpdf/fonts/aealarabiya.ctg.z | Bin 0 -> 1849 bytes .../tecnickcom/tcpdf/fonts/aealarabiya.php | 16 + .../tecnickcom/tcpdf/fonts/aealarabiya.z | Bin 0 -> 56189 bytes .../tecnickcom/tcpdf/fonts/dejavusans.ctg.z | Bin 0 -> 10454 bytes .../tecnickcom/tcpdf/fonts/dejavusans.php | 16 + .../tecnickcom/tcpdf/fonts/dejavusans.z | Bin 0 -> 375806 bytes .../tecnickcom/tcpdf/fonts/freesans.ctg.z | Bin 0 -> 8661 bytes .../tecnickcom/tcpdf/fonts/freesans.php | 16 + .../vendor/tecnickcom/tcpdf/fonts/freesans.z | Bin 0 -> 807705 bytes .../tecnickcom/tcpdf/fonts/freeserif.ctg.z | Bin 0 -> 12610 bytes .../tecnickcom/tcpdf/fonts/freeserif.php | 16 + .../vendor/tecnickcom/tcpdf/fonts/freeserif.z | Bin 0 -> 1835770 bytes .../tecnickcom}/tcpdf/fonts/helvetica.php | 0 .../tecnickcom}/tcpdf/fonts/helveticab.php | 0 .../tecnickcom}/tcpdf/fonts/helveticabi.php | 0 .../tecnickcom}/tcpdf/fonts/helveticai.php | 0 .../tcpdf/fonts/hysmyeongjostdmedium.php | 0 .../tcpdf/fonts/kozgopromedium.php | 0 .../tcpdf/fonts/kozminproregular.php | 0 .../tecnickcom}/tcpdf/fonts/msungstdlight.php | 0 .../tcpdf/fonts/stsongstdlight.php | 0 .../tecnickcom}/tcpdf/fonts/symbol.php | 0 .../tecnickcom/tcpdf/fonts/zapfdingbats.php | 12 + .../tcpdf/include/barcodes/datamatrix.php | 1176 + .../tcpdf/include/barcodes}/pdf417.php | 35 +- .../tcpdf/include/barcodes}/qrcode.php | 27 +- .../tecnickcom}/tcpdf/include/sRGB.icc | Bin .../tcpdf/include/tcpdf_colors.php | 10 +- .../tcpdf/include/tcpdf_filters.php | 16 +- .../tcpdf/include/tcpdf_font_data.php | 0 .../tecnickcom}/tcpdf/include/tcpdf_fonts.php | 329 +- .../tcpdf/include/tcpdf_images.php | 75 +- .../tecnickcom/tcpdf/include/tcpdf_static.php | 2606 ++ .../tecnickcom}/tcpdf/tcpdf.php | 1843 +- .../tecnickcom}/tcpdf/tcpdf_autoconfig.php | 22 +- .../tecnickcom/tcpdf/tcpdf_barcodes_1d.php | 2357 ++ .../tecnickcom/tcpdf/tcpdf_barcodes_2d.php | 349 + .../tecnickcom}/tcpdf/tcpdf_import.php | 0 .../tecnickcom}/tcpdf/tcpdf_parser.php | 61 +- .../vendor/tecnickcom/tcpdf/tools/.htaccess | 1 + .../tcpdf/tools/convert_fonts_examples.txt | 28 + .../tecnickcom/tcpdf/tools/tcpdf_addfont.php | 269 + .../vendor/tedivm/jshrink/.gitignore | 7 - .../vendor/tedivm/jshrink/.travis.yml | 5 - www/analytics/vendor/tedivm/jshrink/README.md | 56 +- .../vendor/tedivm/jshrink/package.xml | 129 + .../vendor/tedivm/jshrink/phpunit.xml.dist | 15 + www/analytics/vendor/twig/twig/.editorconfig | 18 - www/analytics/vendor/twig/twig/.gitignore | 2 - www/analytics/vendor/twig/twig/.travis.yml | 21 - www/analytics/vendor/twig/twig/CHANGELOG | 124 +- www/analytics/vendor/twig/twig/composer.json | 10 +- .../vendor/twig/twig/ext/twig/.gitignore | 30 - .../vendor/twig/twig/ext/twig/LICENSE | 31 - .../vendor/twig/twig/ext/twig/php_twig.h | 6 +- .../vendor/twig/twig/ext/twig/twig.c | 248 +- .../vendor/twig/twig/lib/Twig/Autoloader.php | 14 +- .../twig/twig/lib/Twig/BaseNodeVisitor.php | 62 + .../twig/twig/lib/Twig/Cache/Filesystem.php | 96 + .../vendor/twig/twig/lib/Twig/Cache/Null.php | 48 + .../twig/twig/lib/Twig/CacheInterface.php | 56 + .../vendor/twig/twig/lib/Twig/Compiler.php | 21 +- .../twig/twig/lib/Twig/CompilerInterface.php | 3 +- .../vendor/twig/twig/lib/Twig/Environment.php | 319 +- .../vendor/twig/twig/lib/Twig/Error.php | 12 +- .../twig/lib/Twig/ExistsLoaderInterface.php | 5 +- .../twig/twig/lib/Twig/ExpressionParser.php | 59 +- .../vendor/twig/twig/lib/Twig/Extension.php | 34 +- .../twig/twig/lib/Twig/Extension/Core.php | 447 +- .../twig/twig/lib/Twig/Extension/Debug.php | 2 +- .../twig/twig/lib/Twig/Extension/Escaper.php | 21 +- .../twig/twig/lib/Twig/Extension/Profiler.php | 52 + .../twig/twig/lib/Twig/Extension/Sandbox.php | 4 +- .../twig/twig/lib/Twig/Extension/Staging.php | 2 + .../twig/lib/Twig/Extension/StringLoader.php | 21 +- .../twig/twig/lib/Twig/ExtensionInterface.php | 8 +- .../Twig/FileExtensionEscapingStrategy.php | 58 + .../vendor/twig/twig/lib/Twig/Filter.php | 11 +- .../twig/twig/lib/Twig/Filter/Function.php | 3 + .../twig/twig/lib/Twig/Filter/Method.php | 3 + .../vendor/twig/twig/lib/Twig/Filter/Node.php | 3 + .../twig/lib/Twig/FilterCallableInterface.php | 1 + .../twig/twig/lib/Twig/FilterInterface.php | 1 + .../vendor/twig/twig/lib/Twig/Function.php | 7 +- .../twig/twig/lib/Twig/Function/Function.php | 3 + .../twig/twig/lib/Twig/Function/Method.php | 3 + .../twig/twig/lib/Twig/Function/Node.php | 3 + .../lib/Twig/FunctionCallableInterface.php | 1 + .../twig/twig/lib/Twig/FunctionInterface.php | 1 + .../vendor/twig/twig/lib/Twig/Lexer.php | 52 +- .../twig/twig/lib/Twig/LexerInterface.php | 3 +- .../twig/twig/lib/Twig/Loader/Array.php | 4 +- .../twig/twig/lib/Twig/Loader/Chain.php | 6 +- .../twig/twig/lib/Twig/Loader/Filesystem.php | 69 +- .../twig/twig/lib/Twig/Loader/String.php | 10 +- .../twig/twig/lib/Twig/LoaderInterface.php | 7 +- .../vendor/twig/twig/lib/Twig/Node.php | 49 +- .../twig/twig/lib/Twig/Node/AutoEscape.php | 2 +- .../vendor/twig/twig/lib/Twig/Node/Block.php | 2 +- .../twig/lib/Twig/Node/BlockReference.php | 2 +- .../twig/twig/lib/Twig/Node/CheckSecurity.php | 78 + .../vendor/twig/twig/lib/Twig/Node/Do.php | 2 +- .../vendor/twig/twig/lib/Twig/Node/Embed.php | 8 +- .../twig/lib/Twig/Node/Expression/Array.php | 2 +- .../lib/Twig/Node/Expression/AssignName.php | 2 +- .../twig/lib/Twig/Node/Expression/Binary.php | 2 +- .../Twig/Node/Expression/Binary/EndsWith.php | 10 +- .../Twig/Node/Expression/Binary/FloorDiv.php | 2 +- .../lib/Twig/Node/Expression/Binary/In.php | 2 +- .../lib/Twig/Node/Expression/Binary/NotIn.php | 2 +- .../lib/Twig/Node/Expression/Binary/Power.php | 2 +- .../lib/Twig/Node/Expression/Binary/Range.php | 2 +- .../Node/Expression/Binary/StartsWith.php | 8 +- .../Twig/Node/Expression/BlockReference.php | 8 +- .../twig/lib/Twig/Node/Expression/Call.php | 93 +- .../Node/Expression/ExtensionReference.php | 2 +- .../twig/lib/Twig/Node/Expression/Filter.php | 3 + .../lib/Twig/Node/Expression/Function.php | 3 + .../twig/lib/Twig/Node/Expression/GetAttr.php | 32 +- .../twig/lib/Twig/Node/Expression/Name.php | 14 +- .../twig/lib/Twig/Node/Expression/Parent.php | 8 +- .../twig/lib/Twig/Node/Expression/Test.php | 3 + .../Twig/Node/Expression/Test/Divisibleby.php | 2 +- .../twig/lib/Twig/Node/Expression/Unary.php | 7 +- .../vendor/twig/twig/lib/Twig/Node/Flush.php | 2 +- .../vendor/twig/twig/lib/Twig/Node/For.php | 7 +- .../twig/twig/lib/Twig/Node/ForLoop.php | 2 +- .../vendor/twig/twig/lib/Twig/Node/If.php | 4 +- .../vendor/twig/twig/lib/Twig/Node/Import.php | 12 +- .../twig/twig/lib/Twig/Node/Include.php | 51 +- .../vendor/twig/twig/lib/Twig/Node/Macro.php | 69 +- .../vendor/twig/twig/lib/Twig/Node/Module.php | 143 +- .../vendor/twig/twig/lib/Twig/Node/Print.php | 2 +- .../twig/twig/lib/Twig/Node/Sandbox.php | 2 +- .../twig/lib/Twig/Node/SandboxedModule.php | 60 - .../twig/lib/Twig/Node/SandboxedPrint.php | 4 +- .../vendor/twig/twig/lib/Twig/Node/Set.php | 2 +- .../twig/twig/lib/Twig/Node/Spaceless.php | 2 +- .../vendor/twig/twig/lib/Twig/Node/Text.php | 2 +- .../twig/twig/lib/Twig/NodeInterface.php | 5 +- .../twig/twig/lib/Twig/NodeTraverser.php | 7 +- .../twig/lib/Twig/NodeVisitor/Escaper.php | 20 +- .../twig/lib/Twig/NodeVisitor/Optimizer.php | 45 +- .../lib/Twig/NodeVisitor/SafeAnalysis.php | 21 +- .../twig/lib/Twig/NodeVisitor/Sandbox.php | 34 +- .../twig/lib/Twig/NodeVisitorInterface.php | 2 +- .../vendor/twig/twig/lib/Twig/Parser.php | 29 +- .../twig/twig/lib/Twig/ParserInterface.php | 3 +- .../lib/Twig/Profiler/Dumper/Blackfire.php | 68 + .../twig/lib/Twig/Profiler/Dumper/Html.php | 43 + .../twig/lib/Twig/Profiler/Dumper/Text.php | 68 + .../lib/Twig/Profiler/Node/EnterProfile.php | 40 + .../lib/Twig/Profiler/Node/LeaveProfile.php | 34 + .../Twig/Profiler/NodeVisitor/Profiler.php | 72 + .../twig/twig/lib/Twig/Profiler/Profile.php | 160 + .../Sandbox/SecurityNotAllowedFilterError.php | 31 + .../SecurityNotAllowedFunctionError.php | 31 + .../Sandbox/SecurityNotAllowedTagError.php | 31 + .../twig/lib/Twig/Sandbox/SecurityPolicy.php | 6 +- .../twig/twig/lib/Twig/SimpleFilter.php | 30 +- .../twig/twig/lib/Twig/SimpleFunction.php | 26 +- .../vendor/twig/twig/lib/Twig/SimpleTest.php | 18 + .../vendor/twig/twig/lib/Twig/Template.php | 260 +- .../twig/twig/lib/Twig/TemplateInterface.php | 7 +- .../vendor/twig/twig/lib/Twig/Test.php | 3 + .../twig/twig/lib/Twig/Test/Function.php | 3 + .../lib/Twig/Test/IntegrationTestCase.php | 100 +- .../vendor/twig/twig/lib/Twig/Test/Method.php | 3 + .../vendor/twig/twig/lib/Twig/Test/Node.php | 3 + .../twig/twig/lib/Twig/Test/NodeTestCase.php | 14 +- .../twig/lib/Twig/TestCallableInterface.php | 1 + .../twig/twig/lib/Twig/TestInterface.php | 1 + .../vendor/twig/twig/lib/Twig/Token.php | 50 +- .../vendor/twig/twig/lib/Twig/TokenParser.php | 4 +- .../twig/lib/Twig/TokenParser/AutoEscape.php | 2 + .../twig/twig/lib/Twig/TokenParser/Block.php | 2 +- .../twig/twig/lib/Twig/TokenParser/From.php | 4 + .../twig/twig/lib/Twig/TokenParser/Macro.php | 2 +- .../twig/twig/lib/Twig/TokenParser/Set.php | 4 +- .../twig/twig/lib/Twig/TokenParserBroker.php | 7 +- .../lib/Twig/TokenParserBrokerInterface.php | 1 + .../twig/lib/Twig/TokenParserInterface.php | 4 +- .../vendor/twig/twig/lib/Twig/TokenStream.php | 21 +- .../lib/Twig/Util/DeprecationCollector.php | 82 + .../lib/Twig/Util/TemplateDirIterator.php | 26 + .../vendor/twig/twig/phpunit.xml.dist | 2 +- 5833 files changed, 418669 insertions(+), 226797 deletions(-) create mode 100644 www/analytics/CHANGELOG.md create mode 100644 www/analytics/CONTRIBUTING.md create mode 100644 www/analytics/PRIVACY.md create mode 100644 www/analytics/SECURITY.md create mode 100644 www/analytics/bower.json create mode 100644 www/analytics/config/environment/dev.php create mode 100644 www/analytics/config/environment/test.php create mode 100644 www/analytics/config/environment/ui-test.php create mode 100644 www/analytics/config/global.php create mode 100644 www/analytics/core/API/ApiRenderer.php create mode 100644 www/analytics/core/API/CORSHandler.php create mode 100644 www/analytics/core/API/DataTablePostProcessor.php create mode 100644 www/analytics/core/API/Inconsistencies.php create mode 100644 www/analytics/core/Application/Environment.php create mode 100644 www/analytics/core/Application/EnvironmentManipulator.php create mode 100644 www/analytics/core/Application/Kernel/EnvironmentValidator.php create mode 100644 www/analytics/core/Application/Kernel/GlobalSettingsProvider.php create mode 100644 www/analytics/core/Application/Kernel/PluginList.php create mode 100644 www/analytics/core/Archive/ArchiveInvalidator.php create mode 100644 www/analytics/core/Archive/ArchiveInvalidator/InvalidationResult.php create mode 100644 www/analytics/core/Archive/ArchivePurger.php create mode 100644 www/analytics/core/Archive/Chunk.php create mode 100644 www/analytics/core/Archiver/Request.php create mode 100644 www/analytics/core/BaseFactory.php create mode 100644 www/analytics/core/Cache.php delete mode 100644 www/analytics/core/CacheFile.php create mode 100644 www/analytics/core/CacheId.php create mode 100644 www/analytics/core/CliMulti/CliPhp.php create mode 100644 www/analytics/core/Columns/Dimension.php create mode 100644 www/analytics/core/Columns/Updater.php create mode 100644 www/analytics/core/Composer/ScriptHandler.php create mode 100644 www/analytics/core/Concurrency/DistributedList.php create mode 100644 www/analytics/core/Config/ConfigNotFoundException.php create mode 100644 www/analytics/core/Config/IniFileChain.php create mode 100644 www/analytics/core/Container/ContainerDoesNotExistException.php create mode 100644 www/analytics/core/Container/ContainerFactory.php create mode 100644 www/analytics/core/Container/IniConfigDefinitionSource.php create mode 100644 www/analytics/core/Container/StaticContainer.php create mode 100644 www/analytics/core/CronArchive/SegmentArchivingRequestUrlProvider.php create mode 100644 www/analytics/core/CronArchive/SitesToReprocessDistributedList.php create mode 100644 www/analytics/core/DataAccess/Actions.php create mode 100644 www/analytics/core/DataAccess/ArchiveTableDao.php create mode 100644 www/analytics/core/DataAccess/LogQueryBuilder.php create mode 100644 www/analytics/core/DataAccess/Model.php create mode 100644 www/analytics/core/DataAccess/RawLogDao.php create mode 100644 www/analytics/core/DataAccess/TableMetadata.php delete mode 100644 www/analytics/core/DataFiles/Countries.php delete mode 100644 www/analytics/core/DataFiles/Currencies.php delete mode 100644 www/analytics/core/DataFiles/LanguageToCountry.php delete mode 100644 www/analytics/core/DataFiles/Languages.php delete mode 100644 www/analytics/core/DataFiles/SearchEngines.php delete mode 100755 www/analytics/core/DataFiles/Socials.php create mode 100644 www/analytics/core/DataFiles/cacert.pem create mode 100644 www/analytics/core/DataTable/Filter/AddSegmentByLabel.php create mode 100644 www/analytics/core/DataTable/Filter/AddSegmentByLabelMapping.php create mode 100644 www/analytics/core/DataTable/Filter/AddSegmentBySegmentValue.php create mode 100644 www/analytics/core/DataTable/Filter/AddSegmentValue.php create mode 100644 www/analytics/core/DataTable/Filter/ColumnCallbackDeleteMetadata.php create mode 100644 www/analytics/core/DataTable/Filter/PivotByDimension.php create mode 100644 www/analytics/core/DataTable/Filter/PrependSegment.php create mode 100644 www/analytics/core/DataTable/Filter/PrependValueToMetadata.php create mode 100644 www/analytics/core/Db/Settings.php create mode 100644 www/analytics/core/Development.php create mode 100644 www/analytics/core/DeviceDetectorCache.php create mode 100644 www/analytics/core/DeviceDetectorFactory.php delete mode 100644 www/analytics/core/Error.php create mode 100644 www/analytics/core/ErrorHandler.php create mode 100644 www/analytics/core/Exception/AuthenticationFailedException.php create mode 100644 www/analytics/core/Exception/DatabaseSchemaIsNewerThanCodebaseException.php create mode 100644 www/analytics/core/Exception/ErrorException.php create mode 100644 www/analytics/core/Exception/Exception.php create mode 100644 www/analytics/core/Exception/InvalidRequestParameterException.php create mode 100644 www/analytics/core/Exception/MissingFilePermissionException.php create mode 100644 www/analytics/core/Exception/NoPrivilegesException.php create mode 100644 www/analytics/core/Exception/NoWebsiteFoundException.php create mode 100644 www/analytics/core/Exception/UnexpectedWebsiteFoundException.php create mode 100644 www/analytics/core/Http/ControllerResolver.php create mode 100644 www/analytics/core/Http/Router.php create mode 100644 www/analytics/core/Intl/Data/Provider/CurrencyDataProvider.php create mode 100644 www/analytics/core/Intl/Data/Provider/DateTimeFormatProvider.php create mode 100644 www/analytics/core/Intl/Data/Provider/LanguageDataProvider.php create mode 100644 www/analytics/core/Intl/Data/Provider/RegionDataProvider.php create mode 100644 www/analytics/core/Intl/Data/Resources/continents.php create mode 100644 www/analytics/core/Intl/Data/Resources/countries-extra.php create mode 100644 www/analytics/core/Intl/Data/Resources/countries.php create mode 100644 www/analytics/core/Intl/Data/Resources/currencies.php create mode 100644 www/analytics/core/Intl/Data/Resources/languages-to-countries.php create mode 100644 www/analytics/core/Intl/Data/Resources/languages.php create mode 100644 www/analytics/core/Intl/Locale.php delete mode 100644 www/analytics/core/Loader.php create mode 100644 www/analytics/core/LogDeleter.php create mode 100644 www/analytics/core/Measurable/Measurable.php create mode 100644 www/analytics/core/Measurable/MeasurableSetting.php create mode 100644 www/analytics/core/Measurable/MeasurableSettings.php create mode 100644 www/analytics/core/Measurable/Settings/Storage.php create mode 100644 www/analytics/core/Measurable/Type.php create mode 100644 www/analytics/core/Measurable/Type/TypeManager.php create mode 100644 www/analytics/core/Menu/Group.php create mode 100644 www/analytics/core/Menu/MenuReporting.php create mode 100755 www/analytics/core/Menu/MenuUser.php create mode 100644 www/analytics/core/Metrics/Formatter.php create mode 100644 www/analytics/core/Metrics/Formatter/Html.php create mode 100644 www/analytics/core/NumberFormatter.php create mode 100644 www/analytics/core/Period/Factory.php create mode 100644 www/analytics/core/Period/PeriodValidator.php create mode 100644 www/analytics/core/PiwikPro/Advertising.php create mode 100644 www/analytics/core/Plugin/AggregatedMetric.php create mode 100644 www/analytics/core/Plugin/ComponentFactory.php create mode 100644 www/analytics/core/Plugin/Dimension/ActionDimension.php create mode 100644 www/analytics/core/Plugin/Dimension/ConversionDimension.php create mode 100644 www/analytics/core/Plugin/Dimension/DimensionMetadataProvider.php create mode 100644 www/analytics/core/Plugin/Dimension/VisitDimension.php create mode 100644 www/analytics/core/Plugin/Menu.php create mode 100644 www/analytics/core/Plugin/Metric.php create mode 100644 www/analytics/core/Plugin/PluginException.php create mode 100644 www/analytics/core/Plugin/ProcessedMetric.php create mode 100644 www/analytics/core/Plugin/ReleaseChannels.php create mode 100644 www/analytics/core/Plugin/Report.php create mode 100644 www/analytics/core/Plugin/RequestProcessors.php create mode 100644 www/analytics/core/Plugin/Segment.php create mode 100644 www/analytics/core/Plugin/Tasks.php create mode 100644 www/analytics/core/Plugin/Widgets.php create mode 100644 www/analytics/core/PluginDeactivatedException.php delete mode 100644 www/analytics/core/ScheduledTaskTimetable.php delete mode 100644 www/analytics/core/ScheduledTime.php rename www/analytics/core/{ScheduledTime => Scheduler/Schedule}/Daily.php (83%) rename www/analytics/core/{ScheduledTime => Scheduler/Schedule}/Hourly.php (79%) rename www/analytics/core/{ScheduledTime => Scheduler/Schedule}/Monthly.php (90%) create mode 100644 www/analytics/core/Scheduler/Schedule/Schedule.php rename www/analytics/core/{ScheduledTime => Scheduler/Schedule}/Weekly.php (88%) create mode 100644 www/analytics/core/Scheduler/Scheduler.php create mode 100644 www/analytics/core/Scheduler/Task.php create mode 100644 www/analytics/core/Scheduler/TaskLoader.php create mode 100644 www/analytics/core/Scheduler/Timetable.php create mode 100644 www/analytics/core/Segment/SegmentExpression.php delete mode 100644 www/analytics/core/SegmentExpression.php create mode 100644 www/analytics/core/Sequence.php create mode 100644 www/analytics/core/Settings/Storage.php create mode 100644 www/analytics/core/Settings/Storage/Factory.php create mode 100644 www/analytics/core/Settings/Storage/StaticStorage.php delete mode 100644 www/analytics/core/Tracker/ActionClickUrl.php delete mode 100644 www/analytics/core/Tracker/ActionEvent.php create mode 100644 www/analytics/core/Tracker/Handler.php create mode 100644 www/analytics/core/Tracker/Handler/Factory.php create mode 100644 www/analytics/core/Tracker/Model.php delete mode 100644 www/analytics/core/Tracker/Referrer.php create mode 100644 www/analytics/core/Tracker/RequestProcessor.php create mode 100644 www/analytics/core/Tracker/RequestSet.php create mode 100644 www/analytics/core/Tracker/Response.php create mode 100644 www/analytics/core/Tracker/ScheduledTasksRunner.php create mode 100644 www/analytics/core/Tracker/Settings.php create mode 100644 www/analytics/core/Tracker/SettingsStorage.php create mode 100644 www/analytics/core/Tracker/TableLogAction/Cache.php create mode 100644 www/analytics/core/Tracker/TrackerCodeGenerator.php create mode 100644 www/analytics/core/Tracker/TrackerConfig.php create mode 100644 www/analytics/core/Tracker/Visit/Factory.php create mode 100644 www/analytics/core/Tracker/Visit/ReferrerSpamFilter.php create mode 100644 www/analytics/core/Tracker/Visit/VisitProperties.php create mode 100644 www/analytics/core/Tracker/Visitor.php create mode 100644 www/analytics/core/Tracker/VisitorRecognizer.php delete mode 100644 www/analytics/core/Translate/Validate/CoreTranslations.php create mode 100644 www/analytics/core/Translation/Loader/DevelopmentLoader.php create mode 100644 www/analytics/core/Translation/Loader/JsonFileLoader.php create mode 100644 www/analytics/core/Translation/Loader/LoaderCache.php create mode 100644 www/analytics/core/Translation/Loader/LoaderInterface.php create mode 100644 www/analytics/core/Translation/Transifex/API.php create mode 100644 www/analytics/core/Translation/Translator.php mode change 100644 => 100755 www/analytics/core/Twig.php delete mode 100755 www/analytics/core/Unzip/Tar.php delete mode 100644 www/analytics/core/Unzip/UncompressInterface.php delete mode 100644 www/analytics/core/Unzip/ZipArchive.php create mode 100644 www/analytics/core/UpdateCheck/ReleaseChannel.php create mode 100644 www/analytics/core/Updater/UpdateObserver.php delete mode 100644 www/analytics/core/Updates/0.2.34.php delete mode 100644 www/analytics/core/Updates/0.6.2.php create mode 100644 www/analytics/core/Updates/2.10.0-b10.php create mode 100644 www/analytics/core/Updates/2.10.0-b4.php create mode 100644 www/analytics/core/Updates/2.10.0-b5.php create mode 100644 www/analytics/core/Updates/2.10.0-b7.php create mode 100644 www/analytics/core/Updates/2.10.0-b8.php create mode 100644 www/analytics/core/Updates/2.11.0-b2.php create mode 100644 www/analytics/core/Updates/2.11.0-b4.php create mode 100644 www/analytics/core/Updates/2.11.0-b5.php create mode 100644 www/analytics/core/Updates/2.11.1-b4.php create mode 100644 www/analytics/core/Updates/2.13.0-b3.php create mode 100644 www/analytics/core/Updates/2.13.1.php create mode 100644 www/analytics/core/Updates/2.14.0-b1.php create mode 100644 www/analytics/core/Updates/2.14.0-b2.php create mode 100644 www/analytics/core/Updates/2.14.2.php create mode 100644 www/analytics/core/Updates/2.15.0-b12.php create mode 100644 www/analytics/core/Updates/2.15.0-b16.php create mode 100644 www/analytics/core/Updates/2.15.0-b17.php create mode 100644 www/analytics/core/Updates/2.15.0-b20.php create mode 100644 www/analytics/core/Updates/2.15.0-b3.php create mode 100644 www/analytics/core/Updates/2.15.0-b4.php create mode 100644 www/analytics/core/Updates/2.15.0.php create mode 100644 www/analytics/core/Updates/2.16.0-rc2.php create mode 100644 www/analytics/core/Updates/2.2.3-b6.php create mode 100644 www/analytics/core/Updates/2.3.0-rc2.php create mode 100644 www/analytics/core/Updates/2.4.0-b1.php create mode 100644 www/analytics/core/Updates/2.4.0-b2.php create mode 100644 www/analytics/core/Updates/2.4.0-b3.php create mode 100644 www/analytics/core/Updates/2.4.0-b4.php create mode 100644 www/analytics/core/Updates/2.4.0-b6.php create mode 100644 www/analytics/core/Updates/2.4.0-b8.php create mode 100644 www/analytics/core/Updates/2.5.0-b1.php create mode 100644 www/analytics/core/Updates/2.5.0-rc2.php create mode 100644 www/analytics/core/Updates/2.5.0-rc4.php create mode 100644 www/analytics/core/Updates/2.6.0-b1.php create mode 100644 www/analytics/core/Updates/2.7.0-b2.php create mode 100644 www/analytics/core/Updates/2.7.0-b4.php create mode 100644 www/analytics/core/Updates/2.9.0-b1.php create mode 100644 www/analytics/core/Updates/2.9.0-b7.php create mode 100644 www/analytics/core/bootstrap.php delete mode 100644 www/analytics/favicon.ico create mode 100644 www/analytics/js/tracker.php create mode 100644 www/analytics/lang/dev.json create mode 100644 www/analytics/lang/tl.json delete mode 100755 www/analytics/libs/Archive_Tar/Tar.php delete mode 100644 www/analytics/libs/PEAR/Exception.php delete mode 100644 www/analytics/libs/PEAR/FixPHP5PEARWarnings.php delete mode 100644 www/analytics/libs/PEAR5.php delete mode 100644 www/analytics/libs/PclZip/lgpl-2.1.txt delete mode 100644 www/analytics/libs/PclZip/pclzip.lib.php delete mode 100644 www/analytics/libs/PiwikTracker/LICENSE.txt delete mode 100644 www/analytics/libs/UserAgentParser/README.md delete mode 100644 www/analytics/libs/UserAgentParser/UserAgentParser.php delete mode 100644 www/analytics/libs/UserAgentParser/UserAgentParser.test.php delete mode 100755 www/analytics/libs/angularjs/LICENSE delete mode 100755 www/analytics/libs/angularjs/angular-animate.min.js delete mode 100755 www/analytics/libs/angularjs/angular-cookies.min.js delete mode 100755 www/analytics/libs/angularjs/angular-csp.css delete mode 100755 www/analytics/libs/angularjs/angular-loader.js delete mode 100755 www/analytics/libs/angularjs/angular-loader.min.js delete mode 100755 www/analytics/libs/angularjs/angular-resource.js delete mode 100755 www/analytics/libs/angularjs/angular-resource.min.js delete mode 100755 www/analytics/libs/angularjs/angular-route.js delete mode 100755 www/analytics/libs/angularjs/angular-route.min.js delete mode 100755 www/analytics/libs/angularjs/angular-sanitize.min.js delete mode 100755 www/analytics/libs/angularjs/angular-scenario.js delete mode 100755 www/analytics/libs/angularjs/angular-touch.js delete mode 100755 www/analytics/libs/angularjs/angular-touch.min.js delete mode 100755 www/analytics/libs/angularjs/angular.min.js delete mode 100755 www/analytics/libs/angularjs/errors.json delete mode 100755 www/analytics/libs/angularjs/version.json delete mode 100755 www/analytics/libs/angularjs/version.txt create mode 100644 www/analytics/libs/bower_components/angular-animate/README.md rename www/analytics/libs/{angularjs => bower_components/angular-animate}/angular-animate.js (79%) mode change 100755 => 100644 create mode 100644 www/analytics/libs/bower_components/angular-animate/angular-animate.min.js create mode 100644 www/analytics/libs/bower_components/angular-animate/angular-animate.min.js.map create mode 100644 www/analytics/libs/bower_components/angular-animate/bower.json create mode 100644 www/analytics/libs/bower_components/angular-animate/package.json create mode 100644 www/analytics/libs/bower_components/angular-cookies/README.md rename www/analytics/libs/{angularjs => bower_components/angular-cookies}/angular-cookies.js (78%) mode change 100755 => 100644 create mode 100644 www/analytics/libs/bower_components/angular-cookies/angular-cookies.min.js create mode 100644 www/analytics/libs/bower_components/angular-cookies/angular-cookies.min.js.map create mode 100644 www/analytics/libs/bower_components/angular-cookies/bower.json create mode 100644 www/analytics/libs/bower_components/angular-cookies/package.json create mode 100644 www/analytics/libs/bower_components/angular-mocks/README.md rename www/analytics/libs/{angularjs => bower_components/angular-mocks}/angular-mocks.js (86%) mode change 100755 => 100644 create mode 100644 www/analytics/libs/bower_components/angular-mocks/bower.json create mode 100644 www/analytics/libs/bower_components/angular-mocks/package.json create mode 100644 www/analytics/libs/bower_components/angular-sanitize/README.md rename www/analytics/libs/{angularjs => bower_components/angular-sanitize}/angular-sanitize.js (86%) mode change 100755 => 100644 create mode 100644 www/analytics/libs/bower_components/angular-sanitize/angular-sanitize.min.js create mode 100644 www/analytics/libs/bower_components/angular-sanitize/angular-sanitize.min.js.map create mode 100644 www/analytics/libs/bower_components/angular-sanitize/bower.json create mode 100644 www/analytics/libs/bower_components/angular-sanitize/package.json create mode 100644 www/analytics/libs/bower_components/angular/README.md create mode 100644 www/analytics/libs/bower_components/angular/angular-csp.css rename www/analytics/libs/{angularjs => bower_components/angular}/angular.js (79%) mode change 100755 => 100644 create mode 100644 www/analytics/libs/bower_components/angular/angular.min.js create mode 100644 www/analytics/libs/bower_components/angular/angular.min.js.gzip create mode 100644 www/analytics/libs/bower_components/angular/angular.min.js.map create mode 100644 www/analytics/libs/bower_components/angular/bower.json create mode 100644 www/analytics/libs/bower_components/angular/package.json create mode 100644 www/analytics/libs/bower_components/chroma-js/LICENSE create mode 100644 www/analytics/libs/bower_components/chroma-js/LICENSE-colors create mode 100644 www/analytics/libs/bower_components/chroma-js/Makefile create mode 100644 www/analytics/libs/bower_components/chroma-js/bower.json create mode 100644 www/analytics/libs/bower_components/chroma-js/chroma.js create mode 100644 www/analytics/libs/bower_components/chroma-js/chroma.min.js create mode 100644 www/analytics/libs/bower_components/chroma-js/package.json create mode 100644 www/analytics/libs/bower_components/chroma-js/readme.md create mode 100644 www/analytics/libs/bower_components/html5shiv/Gruntfile.js create mode 100644 www/analytics/libs/bower_components/html5shiv/bower.json create mode 100644 www/analytics/libs/bower_components/html5shiv/dist/html5shiv-printshiv.js create mode 100644 www/analytics/libs/bower_components/html5shiv/dist/html5shiv-printshiv.min.js create mode 100644 www/analytics/libs/bower_components/html5shiv/dist/html5shiv.js create mode 100644 www/analytics/libs/bower_components/html5shiv/dist/html5shiv.min.js create mode 100644 www/analytics/libs/bower_components/html5shiv/package.json create mode 100644 www/analytics/libs/bower_components/html5shiv/readme.md create mode 100644 www/analytics/libs/bower_components/jQuery.dotdotdot/bower.json create mode 100644 www/analytics/libs/bower_components/jQuery.dotdotdot/src/js/jquery.dotdotdot.js create mode 100644 www/analytics/libs/bower_components/jQuery.dotdotdot/src/js/jquery.dotdotdot.min.js create mode 100644 www/analytics/libs/bower_components/jScrollPane/GPL-LICENSE.txt create mode 100644 www/analytics/libs/bower_components/jScrollPane/MIT-LICENSE.txt create mode 100644 www/analytics/libs/bower_components/jScrollPane/README.md create mode 100644 www/analytics/libs/bower_components/jScrollPane/ajax.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/ajax_content.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/anchors.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/api.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/arrow_hover.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/arrow_positions.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/arrows.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/auto_reinitialise.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/basic.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/caps.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/changelog.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/destroy.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/drag_size.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/dynamic_content.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/dynamic_height.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/dynamic_width.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/events.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/faqs.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/fixed_width.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/focus.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/fullpage_scroll.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/iframe.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/iframe2.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/iframe_content1.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/iframe_content2.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/iframe_content3.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/iframe_content4.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/image.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/image/logo.png create mode 100644 www/analytics/libs/bower_components/jScrollPane/image2.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/index.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/invisibles.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/11/after.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/11/before.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/11/brs_main.css create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/11/index.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/11/jquery.mousewheel.js create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/11/jscrollpane-2b3.css create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/11/jscrollpane-2b3.js create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/11/native.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/12/after.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/12/after_reinit.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/12/before.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/12/before_reinit.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/12/brs_main.css create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/12/index.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/12/jquery.mousewheel.js create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/12/jscrollpane-2b3.css create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/12/jscrollpane-2b3.js create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/12/native.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/12/wrapped.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/7/after.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/7/before.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/7/index.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/7/jscrollpane-2b1.css create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/7/jscrollpane-2b2.js create mode 100644 www/analytics/libs/bower_components/jScrollPane/issues/7/native.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/known_issues.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/less_basic.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/mwheel_intent.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/override_animate.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/runeimp.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/runeimp2.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/script/demo.js create mode 100644 www/analytics/libs/bower_components/jScrollPane/script/jquery.jscrollpane.js create mode 100644 www/analytics/libs/bower_components/jScrollPane/script/jquery.jscrollpane.min.js rename www/analytics/libs/{jquery => bower_components/jScrollPane/script}/jquery.mousewheel.js (100%) create mode 100644 www/analytics/libs/bower_components/jScrollPane/script/mwheelIntent.js create mode 100644 www/analytics/libs/bower_components/jScrollPane/scroll_on_left.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/scroll_to.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/scroll_to_animate.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/settings.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/short.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/style/demo.css create mode 100644 www/analytics/libs/bower_components/jScrollPane/style/jquery.jscrollpane.css create mode 100755 www/analytics/libs/bower_components/jScrollPane/themes/lozenge/image/ui-icons_222222_256x240.png create mode 100755 www/analytics/libs/bower_components/jScrollPane/themes/lozenge/image/ui-icons_888888_256x240.png create mode 100755 www/analytics/libs/bower_components/jScrollPane/themes/lozenge/image/ui-icons_cd0a0a_256x240.png create mode 100644 www/analytics/libs/bower_components/jScrollPane/themes/lozenge/index.html create mode 100644 www/analytics/libs/bower_components/jScrollPane/themes/lozenge/style/jquery.jscrollpane.lozenge.css create mode 100644 www/analytics/libs/bower_components/jScrollPane/v1.html create mode 100644 www/analytics/libs/bower_components/jquery-mousewheel/ChangeLog.md create mode 100644 www/analytics/libs/bower_components/jquery-mousewheel/LICENSE.txt create mode 100644 www/analytics/libs/bower_components/jquery-mousewheel/README.md create mode 100644 www/analytics/libs/bower_components/jquery-mousewheel/bower.json create mode 100755 www/analytics/libs/bower_components/jquery-mousewheel/jquery.mousewheel.js create mode 100644 www/analytics/libs/bower_components/jquery-mousewheel/jquery.mousewheel.min.js rename www/analytics/libs/{jquery/MIT-LICENSE-placeholder.txt => bower_components/jquery-placeholder/LICENSE-MIT.txt} (100%) create mode 100644 www/analytics/libs/bower_components/jquery-placeholder/README.md create mode 100644 www/analytics/libs/bower_components/jquery-placeholder/bower.json create mode 100644 www/analytics/libs/bower_components/jquery-placeholder/demo.html create mode 100644 www/analytics/libs/bower_components/jquery-placeholder/jquery.placeholder.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/AUTHORS.txt create mode 100644 www/analytics/libs/bower_components/jquery-ui/MIT-LICENSE.txt create mode 100644 www/analytics/libs/bower_components/jquery-ui/README.md create mode 100644 www/analytics/libs/bower_components/jquery-ui/bower.json create mode 100644 www/analytics/libs/bower_components/jquery-ui/component.json create mode 100644 www/analytics/libs/bower_components/jquery-ui/composer.json create mode 100644 www/analytics/libs/bower_components/jquery-ui/package.json create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery-ui-i18n.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-af.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-ar-DZ.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-ar.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-az.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-be.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-bg.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-bs.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-ca.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-cs.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-cy-GB.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-da.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-de.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-el.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-en-AU.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-en-GB.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-en-NZ.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-eo.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-es.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-et.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-eu.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-fa.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-fi.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-fo.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-fr-CA.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-fr-CH.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-fr.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-gl.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-he.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-hi.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-hr.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-hu.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-hy.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-id.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-is.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-it.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-ja.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-ka.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-kk.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-km.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-ko.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-ky.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-lb.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-lt.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-lv.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-mk.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-ml.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-ms.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-nb.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-nl-BE.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-nl.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-nn.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-no.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-pl.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-pt-BR.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-pt.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-rm.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-ro.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-ru.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-sk.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-sl.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-sq.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-sr-SR.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-sr.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-sv.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-ta.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-th.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-tj.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-tr.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-uk.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-vi.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-zh-CN.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-zh-HK.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-zh-TW.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery-ui.custom.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery-ui.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.accordion.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.autocomplete.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.button.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.core.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.datepicker.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.dialog.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.draggable.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.droppable.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.effect-blind.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.effect-bounce.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.effect-clip.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.effect-drop.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.effect-explode.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.effect-fade.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.effect-fold.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.effect-highlight.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.effect-pulsate.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.effect-scale.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.effect-shake.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.effect-slide.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.effect-transfer.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.effect.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.menu.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.mouse.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.position.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.progressbar.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.resizable.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.selectable.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.slider.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.sortable.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.spinner.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.tabs.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.tooltip.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/jquery.ui.widget.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery-ui-i18n.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-af.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-ar-DZ.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-ar.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-az.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-be.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-bg.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-bs.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-ca.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-cs.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-cy-GB.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-da.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-de.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-el.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-en-AU.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-en-GB.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-en-NZ.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-eo.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-es.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-et.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-eu.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-fa.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-fi.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-fo.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-fr-CA.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-fr-CH.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-fr.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-gl.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-he.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-hi.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-hr.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-hu.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-hy.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-id.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-is.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-it.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-ja.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-ka.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-kk.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-km.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-ko.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-ky.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-lb.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-lt.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-lv.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-mk.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-ml.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-ms.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-nb.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-nl-BE.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-nl.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-nn.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-no.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-pl.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-pt-BR.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-pt.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-rm.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-ro.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-ru.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-sk.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-sl.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-sq.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-sr-SR.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-sr.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-sv.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-ta.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-th.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-tj.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-tr.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-uk.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-vi.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-zh-CN.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-zh-HK.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-zh-TW.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery-ui.custom.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery-ui.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.accordion.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.autocomplete.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.button.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.core.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.datepicker.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.dialog.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.draggable.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.droppable.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-blind.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-bounce.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-clip.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-drop.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-explode.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-fade.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-fold.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-highlight.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-pulsate.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-scale.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-shake.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-slide.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-transfer.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.menu.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.mouse.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.position.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.progressbar.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.resizable.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.selectable.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.slider.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.sortable.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.spinner.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.tabs.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.tooltip.min.js create mode 100644 www/analytics/libs/bower_components/jquery-ui/ui/minified/jquery.ui.widget.min.js create mode 100644 www/analytics/libs/bower_components/jquery.scrollTo/LICENSE create mode 100644 www/analytics/libs/bower_components/jquery.scrollTo/README.md create mode 100644 www/analytics/libs/bower_components/jquery.scrollTo/bower.json create mode 100644 www/analytics/libs/bower_components/jquery.scrollTo/jquery.scrollTo.js create mode 100644 www/analytics/libs/bower_components/jquery.scrollTo/jquery.scrollTo.min.js create mode 100644 www/analytics/libs/bower_components/jquery.scrollTo/package.json create mode 100644 www/analytics/libs/bower_components/jquery.scrollTo/scrollTo.jquery.json create mode 100644 www/analytics/libs/bower_components/jquery/MIT-LICENSE.txt create mode 100644 www/analytics/libs/bower_components/jquery/bower.json create mode 100644 www/analytics/libs/bower_components/jquery/dist/jquery.js create mode 100644 www/analytics/libs/bower_components/jquery/dist/jquery.min.js create mode 100644 www/analytics/libs/bower_components/jquery/dist/jquery.min.map create mode 100644 www/analytics/libs/bower_components/jquery/src/ajax.js create mode 100644 www/analytics/libs/bower_components/jquery/src/ajax/jsonp.js create mode 100644 www/analytics/libs/bower_components/jquery/src/ajax/load.js create mode 100644 www/analytics/libs/bower_components/jquery/src/ajax/parseJSON.js create mode 100644 www/analytics/libs/bower_components/jquery/src/ajax/parseXML.js create mode 100644 www/analytics/libs/bower_components/jquery/src/ajax/script.js create mode 100644 www/analytics/libs/bower_components/jquery/src/ajax/var/nonce.js create mode 100644 www/analytics/libs/bower_components/jquery/src/ajax/var/rquery.js create mode 100644 www/analytics/libs/bower_components/jquery/src/ajax/xhr.js create mode 100644 www/analytics/libs/bower_components/jquery/src/attributes.js create mode 100644 www/analytics/libs/bower_components/jquery/src/attributes/attr.js create mode 100644 www/analytics/libs/bower_components/jquery/src/attributes/classes.js create mode 100644 www/analytics/libs/bower_components/jquery/src/attributes/prop.js create mode 100644 www/analytics/libs/bower_components/jquery/src/attributes/support.js create mode 100644 www/analytics/libs/bower_components/jquery/src/attributes/val.js create mode 100644 www/analytics/libs/bower_components/jquery/src/callbacks.js create mode 100644 www/analytics/libs/bower_components/jquery/src/core.js create mode 100644 www/analytics/libs/bower_components/jquery/src/core/access.js create mode 100644 www/analytics/libs/bower_components/jquery/src/core/init.js create mode 100644 www/analytics/libs/bower_components/jquery/src/core/parseHTML.js create mode 100644 www/analytics/libs/bower_components/jquery/src/core/ready.js create mode 100644 www/analytics/libs/bower_components/jquery/src/core/var/rsingleTag.js create mode 100644 www/analytics/libs/bower_components/jquery/src/css.js create mode 100644 www/analytics/libs/bower_components/jquery/src/css/addGetHookIf.js create mode 100644 www/analytics/libs/bower_components/jquery/src/css/curCSS.js create mode 100644 www/analytics/libs/bower_components/jquery/src/css/defaultDisplay.js create mode 100644 www/analytics/libs/bower_components/jquery/src/css/hiddenVisibleSelectors.js create mode 100644 www/analytics/libs/bower_components/jquery/src/css/support.js create mode 100644 www/analytics/libs/bower_components/jquery/src/css/swap.js create mode 100644 www/analytics/libs/bower_components/jquery/src/css/var/cssExpand.js create mode 100644 www/analytics/libs/bower_components/jquery/src/css/var/isHidden.js create mode 100644 www/analytics/libs/bower_components/jquery/src/css/var/rmargin.js create mode 100644 www/analytics/libs/bower_components/jquery/src/css/var/rnumnonpx.js create mode 100644 www/analytics/libs/bower_components/jquery/src/data.js create mode 100644 www/analytics/libs/bower_components/jquery/src/data/accepts.js create mode 100644 www/analytics/libs/bower_components/jquery/src/data/support.js create mode 100644 www/analytics/libs/bower_components/jquery/src/deferred.js create mode 100644 www/analytics/libs/bower_components/jquery/src/deprecated.js create mode 100644 www/analytics/libs/bower_components/jquery/src/dimensions.js create mode 100644 www/analytics/libs/bower_components/jquery/src/effects.js create mode 100644 www/analytics/libs/bower_components/jquery/src/effects/Tween.js create mode 100644 www/analytics/libs/bower_components/jquery/src/effects/animatedSelector.js create mode 100644 www/analytics/libs/bower_components/jquery/src/effects/support.js create mode 100644 www/analytics/libs/bower_components/jquery/src/event.js create mode 100644 www/analytics/libs/bower_components/jquery/src/event/alias.js create mode 100644 www/analytics/libs/bower_components/jquery/src/event/support.js create mode 100644 www/analytics/libs/bower_components/jquery/src/exports/amd.js create mode 100644 www/analytics/libs/bower_components/jquery/src/exports/global.js create mode 100644 www/analytics/libs/bower_components/jquery/src/intro.js create mode 100644 www/analytics/libs/bower_components/jquery/src/jquery.js create mode 100644 www/analytics/libs/bower_components/jquery/src/manipulation.js create mode 100644 www/analytics/libs/bower_components/jquery/src/manipulation/_evalUrl.js create mode 100644 www/analytics/libs/bower_components/jquery/src/manipulation/support.js create mode 100644 www/analytics/libs/bower_components/jquery/src/manipulation/var/rcheckableType.js create mode 100644 www/analytics/libs/bower_components/jquery/src/offset.js create mode 100644 www/analytics/libs/bower_components/jquery/src/outro.js create mode 100644 www/analytics/libs/bower_components/jquery/src/queue.js create mode 100644 www/analytics/libs/bower_components/jquery/src/queue/delay.js create mode 100644 www/analytics/libs/bower_components/jquery/src/selector-sizzle.js create mode 100644 www/analytics/libs/bower_components/jquery/src/selector.js create mode 100644 www/analytics/libs/bower_components/jquery/src/serialize.js create mode 100644 www/analytics/libs/bower_components/jquery/src/sizzle/dist/sizzle.js create mode 100644 www/analytics/libs/bower_components/jquery/src/sizzle/dist/sizzle.min.js create mode 100644 www/analytics/libs/bower_components/jquery/src/sizzle/dist/sizzle.min.map create mode 100644 www/analytics/libs/bower_components/jquery/src/support.js create mode 100644 www/analytics/libs/bower_components/jquery/src/traversing.js create mode 100644 www/analytics/libs/bower_components/jquery/src/traversing/findFilter.js create mode 100644 www/analytics/libs/bower_components/jquery/src/traversing/var/rneedsContext.js create mode 100644 www/analytics/libs/bower_components/jquery/src/var/class2type.js create mode 100644 www/analytics/libs/bower_components/jquery/src/var/concat.js create mode 100644 www/analytics/libs/bower_components/jquery/src/var/deletedIds.js create mode 100644 www/analytics/libs/bower_components/jquery/src/var/hasOwn.js create mode 100644 www/analytics/libs/bower_components/jquery/src/var/indexOf.js create mode 100644 www/analytics/libs/bower_components/jquery/src/var/pnum.js create mode 100644 www/analytics/libs/bower_components/jquery/src/var/push.js create mode 100644 www/analytics/libs/bower_components/jquery/src/var/rnotwhite.js create mode 100644 www/analytics/libs/bower_components/jquery/src/var/slice.js create mode 100644 www/analytics/libs/bower_components/jquery/src/var/strundefined.js create mode 100644 www/analytics/libs/bower_components/jquery/src/var/support.js create mode 100644 www/analytics/libs/bower_components/jquery/src/var/toString.js create mode 100644 www/analytics/libs/bower_components/jquery/src/wrap.js create mode 100644 www/analytics/libs/bower_components/mousetrap/Gruntfile.js create mode 100644 www/analytics/libs/bower_components/mousetrap/README.md create mode 100644 www/analytics/libs/bower_components/mousetrap/mousetrap.js create mode 100644 www/analytics/libs/bower_components/mousetrap/mousetrap.min.js create mode 100644 www/analytics/libs/bower_components/mousetrap/package.json create mode 100644 www/analytics/libs/bower_components/mousetrap/plugins/README.md create mode 100644 www/analytics/libs/bower_components/mousetrap/plugins/bind-dictionary/README.md create mode 100644 www/analytics/libs/bower_components/mousetrap/plugins/bind-dictionary/mousetrap-bind-dictionary.js create mode 100644 www/analytics/libs/bower_components/mousetrap/plugins/bind-dictionary/mousetrap-bind-dictionary.min.js create mode 100644 www/analytics/libs/bower_components/mousetrap/plugins/global-bind/README.md create mode 100644 www/analytics/libs/bower_components/mousetrap/plugins/global-bind/mousetrap-global-bind.js create mode 100644 www/analytics/libs/bower_components/mousetrap/plugins/global-bind/mousetrap-global-bind.min.js create mode 100644 www/analytics/libs/bower_components/mousetrap/plugins/pause/README.md create mode 100644 www/analytics/libs/bower_components/mousetrap/plugins/pause/mousetrap-pause.js create mode 100644 www/analytics/libs/bower_components/mousetrap/plugins/pause/mousetrap-pause.min.js create mode 100644 www/analytics/libs/bower_components/mousetrap/plugins/record/README.md create mode 100644 www/analytics/libs/bower_components/mousetrap/plugins/record/mousetrap-record.js create mode 100644 www/analytics/libs/bower_components/mousetrap/plugins/record/mousetrap-record.min.js create mode 100644 www/analytics/libs/bower_components/ngDialog/README.md create mode 100644 www/analytics/libs/bower_components/ngDialog/bower.json create mode 100644 www/analytics/libs/bower_components/ngDialog/css/ngDialog-theme-default.css create mode 100644 www/analytics/libs/bower_components/ngDialog/css/ngDialog-theme-default.min.css create mode 100644 www/analytics/libs/bower_components/ngDialog/css/ngDialog-theme-plain.css create mode 100644 www/analytics/libs/bower_components/ngDialog/css/ngDialog-theme-plain.min.css create mode 100644 www/analytics/libs/bower_components/ngDialog/css/ngDialog.css create mode 100644 www/analytics/libs/bower_components/ngDialog/css/ngDialog.min.css create mode 100644 www/analytics/libs/bower_components/ngDialog/js/ngDialog.js create mode 100644 www/analytics/libs/bower_components/ngDialog/js/ngDialog.min.js create mode 100644 www/analytics/libs/bower_components/ngDialog/package.json create mode 100644 www/analytics/libs/bower_components/sprintf/LICENSE create mode 100644 www/analytics/libs/bower_components/sprintf/README.md create mode 100644 www/analytics/libs/bower_components/sprintf/bower.json create mode 100644 www/analytics/libs/bower_components/sprintf/demo/angular.html create mode 100644 www/analytics/libs/bower_components/sprintf/dist/angular-sprintf.min.js create mode 100644 www/analytics/libs/bower_components/sprintf/dist/angular-sprintf.min.map create mode 100644 www/analytics/libs/bower_components/sprintf/dist/sprintf.min.js create mode 100644 www/analytics/libs/bower_components/sprintf/dist/sprintf.min.map create mode 100644 www/analytics/libs/bower_components/sprintf/gruntfile.js create mode 100644 www/analytics/libs/bower_components/sprintf/package.json create mode 100644 www/analytics/libs/bower_components/sprintf/src/angular-sprintf.js create mode 100644 www/analytics/libs/bower_components/sprintf/src/sprintf.js create mode 100644 www/analytics/libs/bower_components/sprintf/test/test.js create mode 100644 www/analytics/libs/bower_components/visibilityjs/ChangeLog.md create mode 100644 www/analytics/libs/bower_components/visibilityjs/LICENSE create mode 100644 www/analytics/libs/bower_components/visibilityjs/README.md create mode 100644 www/analytics/libs/bower_components/visibilityjs/bower.json create mode 100644 www/analytics/libs/bower_components/visibilityjs/index.js create mode 100644 www/analytics/libs/bower_components/visibilityjs/lib/visibility.core.js create mode 100644 www/analytics/libs/bower_components/visibilityjs/lib/visibility.fallback.js create mode 100644 www/analytics/libs/bower_components/visibilityjs/lib/visibility.js create mode 100644 www/analytics/libs/bower_components/visibilityjs/lib/visibility.timers.js create mode 100644 www/analytics/libs/bower_components/visibilityjs/logo.svg delete mode 100644 www/analytics/libs/html5shiv/html5shiv.js delete mode 100644 www/analytics/libs/javascript/sprintf.js delete mode 100644 www/analytics/libs/jquery/MIT-LICENSE-jquery.txt delete mode 100644 www/analytics/libs/jquery/MIT-LICENSE-jqueryui.txt delete mode 100644 www/analytics/libs/jquery/MIT-LICENSE-scrollto.txt delete mode 100644 www/analytics/libs/jquery/jquery-ui.js delete mode 100644 www/analytics/libs/jquery/jquery.history.js delete mode 100644 www/analytics/libs/jquery/jquery.js delete mode 100644 www/analytics/libs/jquery/jquery.jscrollpane.js delete mode 100644 www/analytics/libs/jquery/jquery.placeholder.js delete mode 100644 www/analytics/libs/jquery/jquery.scrollTo.js rename www/analytics/libs/jquery/themes/base/{jquery-ui.css => jquery-ui.min.css} (100%) rename www/analytics/libs/{pChart2.1.3 => pChart}/GPLv3.txt (98%) mode change 100644 => 100755 create mode 100755 www/analytics/libs/pChart/change.log rename www/analytics/libs/{pChart2.1.3 => pChart}/class/pData.class.php (99%) mode change 100644 => 100755 rename www/analytics/libs/{pChart2.1.3 => pChart}/class/pDraw.class.php (96%) mode change 100644 => 100755 rename www/analytics/libs/{pChart2.1.3 => pChart}/class/pImage.class.php (97%) mode change 100644 => 100755 rename www/analytics/libs/{pChart2.1.3 => pChart}/class/pPie.class.php (99%) mode change 100644 => 100755 create mode 100755 www/analytics/libs/pChart/readme.txt delete mode 100644 www/analytics/libs/pChart2.1.3/change.log delete mode 100644 www/analytics/libs/pChart2.1.3/readme.txt delete mode 100644 www/analytics/libs/tcpdf/2dbarcodes.php delete mode 100644 www/analytics/libs/tcpdf/barcodes.php delete mode 100644 www/analytics/libs/tcpdf/composer.json delete mode 100644 www/analytics/libs/tcpdf/config/lang/eng.php delete mode 100644 www/analytics/libs/tcpdf/config/tcpdf_config_alt.php delete mode 100644 www/analytics/libs/tcpdf/fonts/almohanad.ctg.z delete mode 100644 www/analytics/libs/tcpdf/fonts/almohanad.php delete mode 100644 www/analytics/libs/tcpdf/fonts/almohanad.z delete mode 100644 www/analytics/libs/tcpdf/fonts/dejavusans.ctg.z delete mode 100644 www/analytics/libs/tcpdf/fonts/dejavusans.php delete mode 100644 www/analytics/libs/tcpdf/fonts/dejavusans.z delete mode 100644 www/analytics/libs/tcpdf/fonts/times.php delete mode 100644 www/analytics/libs/tcpdf/fonts/timesb.php delete mode 100644 www/analytics/libs/tcpdf/fonts/timesbi.php delete mode 100644 www/analytics/libs/tcpdf/fonts/timesi.php delete mode 100644 www/analytics/libs/tcpdf/gpl.txt delete mode 100644 www/analytics/libs/tcpdf/htmlcolors.php delete mode 100644 www/analytics/libs/tcpdf/include/tcpdf_static.php delete mode 100644 www/analytics/libs/tcpdf/lgpl-3.0.txt delete mode 100644 www/analytics/libs/tcpdf/spotcolors.php delete mode 100644 www/analytics/libs/tcpdf/tcpdf.crt delete mode 100644 www/analytics/libs/tcpdf/tcpdf.fdf delete mode 100644 www/analytics/libs/tcpdf/tcpdf.p12 delete mode 100644 www/analytics/libs/tcpdf/unicode_data.php create mode 100644 www/analytics/misc/.htaccess create mode 100755 www/analytics/misc/composer/build-xhprof.sh create mode 100755 www/analytics/misc/composer/clean-xhprof.sh create mode 100644 www/analytics/misc/cron/.htaccess create mode 100644 www/analytics/misc/internal-docs/content-tracking.md create mode 100644 www/analytics/misc/log-analytics/CONTRIBUTING.md delete mode 100644 www/analytics/misc/others/cli-script-bootstrap.php mode change 100755 => 100644 www/analytics/misc/others/geoipUpdateRows.php delete mode 100644 www/analytics/misc/others/phpstorm-codestyles/Piwik_codestyle.xml delete mode 100644 www/analytics/misc/others/phpstorm-codestyles/README.md delete mode 100644 www/analytics/misc/others/test_cookies_GenerateHundredsWebsitesAndVisits.php delete mode 100644 www/analytics/misc/others/test_generateLotsVisitsWebsites.php delete mode 100644 www/analytics/misc/proxy-hide-piwik-url/piwik.php delete mode 100755 www/analytics/misc/user/.gitkeep delete mode 100644 www/analytics/misc/user/.htaccess create mode 100644 www/analytics/plugins/API/DataTable/MergeDataTables.php create mode 100644 www/analytics/plugins/API/Glossary.php create mode 100644 www/analytics/plugins/API/Menu.php create mode 100644 www/analytics/plugins/API/Renderer/Console.php create mode 100644 www/analytics/plugins/API/Renderer/Csv.php create mode 100644 www/analytics/plugins/API/Renderer/Html.php create mode 100644 www/analytics/plugins/API/Renderer/Json.php create mode 100644 www/analytics/plugins/API/Renderer/Json2.php create mode 100644 www/analytics/plugins/API/Renderer/Original.php create mode 100644 www/analytics/plugins/API/Renderer/Php.php create mode 100644 www/analytics/plugins/API/Renderer/Rss.php create mode 100644 www/analytics/plugins/API/Renderer/Tsv.php create mode 100644 www/analytics/plugins/API/Renderer/Xml.php create mode 100644 www/analytics/plugins/API/Reports/Get.php create mode 100644 www/analytics/plugins/API/lang/am.json create mode 100644 www/analytics/plugins/API/lang/ar.json create mode 100644 www/analytics/plugins/API/lang/be.json create mode 100644 www/analytics/plugins/API/lang/bg.json create mode 100644 www/analytics/plugins/API/lang/bs.json create mode 100644 www/analytics/plugins/API/lang/ca.json create mode 100644 www/analytics/plugins/API/lang/cs.json create mode 100644 www/analytics/plugins/API/lang/da.json create mode 100644 www/analytics/plugins/API/lang/de.json create mode 100644 www/analytics/plugins/API/lang/el.json create mode 100644 www/analytics/plugins/API/lang/en.json create mode 100644 www/analytics/plugins/API/lang/es.json create mode 100644 www/analytics/plugins/API/lang/et.json create mode 100644 www/analytics/plugins/API/lang/eu.json create mode 100644 www/analytics/plugins/API/lang/fa.json create mode 100644 www/analytics/plugins/API/lang/fi.json create mode 100644 www/analytics/plugins/API/lang/fr.json create mode 100644 www/analytics/plugins/API/lang/gl.json create mode 100644 www/analytics/plugins/API/lang/he.json create mode 100644 www/analytics/plugins/API/lang/hi.json create mode 100644 www/analytics/plugins/API/lang/hr.json create mode 100644 www/analytics/plugins/API/lang/hu.json create mode 100644 www/analytics/plugins/API/lang/id.json create mode 100644 www/analytics/plugins/API/lang/is.json create mode 100644 www/analytics/plugins/API/lang/it.json create mode 100644 www/analytics/plugins/API/lang/ja.json create mode 100644 www/analytics/plugins/API/lang/ka.json create mode 100644 www/analytics/plugins/API/lang/ko.json create mode 100644 www/analytics/plugins/API/lang/lt.json create mode 100644 www/analytics/plugins/API/lang/lv.json create mode 100644 www/analytics/plugins/API/lang/nb.json create mode 100644 www/analytics/plugins/API/lang/nl.json create mode 100644 www/analytics/plugins/API/lang/nn.json create mode 100644 www/analytics/plugins/API/lang/pl.json create mode 100644 www/analytics/plugins/API/lang/pt-br.json create mode 100644 www/analytics/plugins/API/lang/pt.json create mode 100644 www/analytics/plugins/API/lang/ro.json create mode 100644 www/analytics/plugins/API/lang/ru.json create mode 100644 www/analytics/plugins/API/lang/sk.json create mode 100644 www/analytics/plugins/API/lang/sl.json create mode 100644 www/analytics/plugins/API/lang/sq.json create mode 100644 www/analytics/plugins/API/lang/sr.json create mode 100644 www/analytics/plugins/API/lang/sv.json create mode 100644 www/analytics/plugins/API/lang/ta.json create mode 100644 www/analytics/plugins/API/lang/te.json create mode 100644 www/analytics/plugins/API/lang/th.json create mode 100644 www/analytics/plugins/API/lang/tl.json create mode 100644 www/analytics/plugins/API/lang/tr.json create mode 100644 www/analytics/plugins/API/lang/uk.json create mode 100644 www/analytics/plugins/API/lang/vi.json create mode 100644 www/analytics/plugins/API/lang/zh-cn.json create mode 100644 www/analytics/plugins/API/lang/zh-tw.json create mode 100644 www/analytics/plugins/API/templates/glossary.twig create mode 100644 www/analytics/plugins/Actions/Actions/ActionClickUrl.php create mode 100644 www/analytics/plugins/Actions/Actions/ActionDownloadUrl.php rename www/analytics/{core/Tracker => plugins/Actions/Actions}/ActionSiteSearch.php (89%) create mode 100644 www/analytics/plugins/Actions/Columns/ActionType.php create mode 100644 www/analytics/plugins/Actions/Columns/ActionUrl.php create mode 100644 www/analytics/plugins/Actions/Columns/ClickedUrl.php create mode 100644 www/analytics/plugins/Actions/Columns/DestinationPage.php create mode 100644 www/analytics/plugins/Actions/Columns/DownloadUrl.php create mode 100644 www/analytics/plugins/Actions/Columns/EntryPageTitle.php create mode 100644 www/analytics/plugins/Actions/Columns/EntryPageUrl.php create mode 100644 www/analytics/plugins/Actions/Columns/ExitPageTitle.php create mode 100644 www/analytics/plugins/Actions/Columns/ExitPageUrl.php create mode 100644 www/analytics/plugins/Actions/Columns/Keyword.php create mode 100644 www/analytics/plugins/Actions/Columns/KeywordwithNoSearchResult.php create mode 100644 www/analytics/plugins/Actions/Columns/Metrics/AveragePageGenerationTime.php create mode 100644 www/analytics/plugins/Actions/Columns/Metrics/AverageTimeOnPage.php create mode 100644 www/analytics/plugins/Actions/Columns/Metrics/BounceRate.php create mode 100644 www/analytics/plugins/Actions/Columns/Metrics/ExitRate.php create mode 100644 www/analytics/plugins/Actions/Columns/PageTitle.php create mode 100644 www/analytics/plugins/Actions/Columns/PageUrl.php create mode 100644 www/analytics/plugins/Actions/Columns/SearchCategory.php create mode 100644 www/analytics/plugins/Actions/Columns/SearchDestinationPage.php create mode 100644 www/analytics/plugins/Actions/Columns/SearchKeyword.php create mode 100644 www/analytics/plugins/Actions/Columns/SearchNoResultKeyword.php create mode 100644 www/analytics/plugins/Actions/Columns/TimeSpentRefAction.php create mode 100644 www/analytics/plugins/Actions/Columns/VisitTotalActions.php create mode 100644 www/analytics/plugins/Actions/Columns/VisitTotalSearches.php create mode 100644 www/analytics/plugins/Actions/DataTable/Filter/Actions.php create mode 100644 www/analytics/plugins/Actions/Menu.php create mode 100644 www/analytics/plugins/Actions/Metrics.php create mode 100644 www/analytics/plugins/Actions/Reports/Base.php create mode 100644 www/analytics/plugins/Actions/Reports/Get.php create mode 100644 www/analytics/plugins/Actions/Reports/GetDownloads.php create mode 100644 www/analytics/plugins/Actions/Reports/GetEntryPageTitles.php create mode 100644 www/analytics/plugins/Actions/Reports/GetEntryPageUrls.php create mode 100644 www/analytics/plugins/Actions/Reports/GetExitPageTitles.php create mode 100644 www/analytics/plugins/Actions/Reports/GetExitPageUrls.php create mode 100644 www/analytics/plugins/Actions/Reports/GetOutlinks.php create mode 100644 www/analytics/plugins/Actions/Reports/GetPageTitles.php create mode 100644 www/analytics/plugins/Actions/Reports/GetPageTitlesFollowingSiteSearch.php create mode 100644 www/analytics/plugins/Actions/Reports/GetPageUrls.php create mode 100644 www/analytics/plugins/Actions/Reports/GetPageUrlsFollowingSiteSearch.php create mode 100644 www/analytics/plugins/Actions/Reports/GetSiteSearchCategories.php create mode 100644 www/analytics/plugins/Actions/Reports/GetSiteSearchKeywords.php create mode 100644 www/analytics/plugins/Actions/Reports/GetSiteSearchNoResultKeywords.php create mode 100644 www/analytics/plugins/Actions/Reports/SiteSearchBase.php create mode 100644 www/analytics/plugins/Actions/Segment.php create mode 100644 www/analytics/plugins/Actions/Tracker/ActionsRequestProcessor.php create mode 100644 www/analytics/plugins/Actions/javascripts/rowactions.js create mode 100644 www/analytics/plugins/Actions/lang/am.json create mode 100644 www/analytics/plugins/Actions/lang/ar.json create mode 100644 www/analytics/plugins/Actions/lang/be.json create mode 100644 www/analytics/plugins/Actions/lang/bg.json create mode 100644 www/analytics/plugins/Actions/lang/bs.json create mode 100644 www/analytics/plugins/Actions/lang/ca.json create mode 100644 www/analytics/plugins/Actions/lang/cs.json create mode 100644 www/analytics/plugins/Actions/lang/da.json create mode 100644 www/analytics/plugins/Actions/lang/de.json create mode 100644 www/analytics/plugins/Actions/lang/el.json create mode 100644 www/analytics/plugins/Actions/lang/en.json create mode 100644 www/analytics/plugins/Actions/lang/es.json create mode 100644 www/analytics/plugins/Actions/lang/et.json create mode 100644 www/analytics/plugins/Actions/lang/eu.json create mode 100644 www/analytics/plugins/Actions/lang/fa.json create mode 100644 www/analytics/plugins/Actions/lang/fi.json create mode 100644 www/analytics/plugins/Actions/lang/fr.json create mode 100644 www/analytics/plugins/Actions/lang/gl.json create mode 100644 www/analytics/plugins/Actions/lang/he.json create mode 100644 www/analytics/plugins/Actions/lang/hi.json create mode 100644 www/analytics/plugins/Actions/lang/hr.json create mode 100644 www/analytics/plugins/Actions/lang/hu.json create mode 100644 www/analytics/plugins/Actions/lang/id.json create mode 100644 www/analytics/plugins/Actions/lang/is.json create mode 100644 www/analytics/plugins/Actions/lang/it.json create mode 100644 www/analytics/plugins/Actions/lang/ja.json create mode 100644 www/analytics/plugins/Actions/lang/ka.json create mode 100644 www/analytics/plugins/Actions/lang/ko.json create mode 100644 www/analytics/plugins/Actions/lang/lt.json create mode 100644 www/analytics/plugins/Actions/lang/lv.json create mode 100644 www/analytics/plugins/Actions/lang/nb.json create mode 100644 www/analytics/plugins/Actions/lang/nl.json create mode 100644 www/analytics/plugins/Actions/lang/nn.json create mode 100644 www/analytics/plugins/Actions/lang/pl.json create mode 100644 www/analytics/plugins/Actions/lang/pt-br.json create mode 100644 www/analytics/plugins/Actions/lang/pt.json create mode 100644 www/analytics/plugins/Actions/lang/ro.json create mode 100644 www/analytics/plugins/Actions/lang/ru.json create mode 100644 www/analytics/plugins/Actions/lang/sk.json create mode 100644 www/analytics/plugins/Actions/lang/sl.json create mode 100644 www/analytics/plugins/Actions/lang/sq.json create mode 100644 www/analytics/plugins/Actions/lang/sr.json create mode 100644 www/analytics/plugins/Actions/lang/sv.json create mode 100644 www/analytics/plugins/Actions/lang/ta.json create mode 100644 www/analytics/plugins/Actions/lang/te.json create mode 100644 www/analytics/plugins/Actions/lang/th.json create mode 100644 www/analytics/plugins/Actions/lang/tl.json create mode 100644 www/analytics/plugins/Actions/lang/tr.json create mode 100644 www/analytics/plugins/Actions/lang/uk.json create mode 100644 www/analytics/plugins/Actions/lang/vi.json create mode 100644 www/analytics/plugins/Actions/lang/zh-cn.json create mode 100644 www/analytics/plugins/Actions/lang/zh-tw.json create mode 100644 www/analytics/plugins/Annotations/lang/ar.json create mode 100644 www/analytics/plugins/Annotations/lang/bg.json create mode 100644 www/analytics/plugins/Annotations/lang/bs.json create mode 100644 www/analytics/plugins/Annotations/lang/ca.json create mode 100644 www/analytics/plugins/Annotations/lang/cs.json create mode 100644 www/analytics/plugins/Annotations/lang/da.json create mode 100644 www/analytics/plugins/Annotations/lang/de.json create mode 100644 www/analytics/plugins/Annotations/lang/el.json create mode 100644 www/analytics/plugins/Annotations/lang/en.json create mode 100644 www/analytics/plugins/Annotations/lang/es.json create mode 100644 www/analytics/plugins/Annotations/lang/et.json create mode 100644 www/analytics/plugins/Annotations/lang/fa.json create mode 100644 www/analytics/plugins/Annotations/lang/fi.json create mode 100644 www/analytics/plugins/Annotations/lang/fr.json create mode 100644 www/analytics/plugins/Annotations/lang/gl.json create mode 100644 www/analytics/plugins/Annotations/lang/he.json create mode 100644 www/analytics/plugins/Annotations/lang/hi.json create mode 100644 www/analytics/plugins/Annotations/lang/hr.json create mode 100644 www/analytics/plugins/Annotations/lang/id.json create mode 100644 www/analytics/plugins/Annotations/lang/it.json create mode 100644 www/analytics/plugins/Annotations/lang/ja.json create mode 100644 www/analytics/plugins/Annotations/lang/ko.json create mode 100644 www/analytics/plugins/Annotations/lang/nb.json create mode 100644 www/analytics/plugins/Annotations/lang/nl.json create mode 100644 www/analytics/plugins/Annotations/lang/pl.json create mode 100644 www/analytics/plugins/Annotations/lang/pt-br.json create mode 100644 www/analytics/plugins/Annotations/lang/pt.json create mode 100644 www/analytics/plugins/Annotations/lang/ro.json create mode 100644 www/analytics/plugins/Annotations/lang/ru.json create mode 100644 www/analytics/plugins/Annotations/lang/sk.json create mode 100644 www/analytics/plugins/Annotations/lang/sl.json create mode 100644 www/analytics/plugins/Annotations/lang/sq.json create mode 100644 www/analytics/plugins/Annotations/lang/sr.json create mode 100644 www/analytics/plugins/Annotations/lang/sv.json create mode 100644 www/analytics/plugins/Annotations/lang/ta.json create mode 100644 www/analytics/plugins/Annotations/lang/te.json create mode 100644 www/analytics/plugins/Annotations/lang/th.json create mode 100644 www/analytics/plugins/Annotations/lang/tl.json create mode 100644 www/analytics/plugins/Annotations/lang/tr.json create mode 100644 www/analytics/plugins/Annotations/lang/vi.json create mode 100644 www/analytics/plugins/Annotations/lang/zh-cn.json create mode 100644 www/analytics/plugins/Annotations/lang/zh-tw.json create mode 100644 www/analytics/plugins/BulkTracking/BulkTracking.php create mode 100644 www/analytics/plugins/BulkTracking/Tracker/Handler.php create mode 100644 www/analytics/plugins/BulkTracking/Tracker/Requests.php create mode 100644 www/analytics/plugins/BulkTracking/Tracker/Response.php create mode 100644 www/analytics/plugins/BulkTracking/plugin.json create mode 100644 www/analytics/plugins/Contents/API.php create mode 100644 www/analytics/plugins/Contents/Actions/ActionContent.php create mode 100644 www/analytics/plugins/Contents/Archiver.php create mode 100644 www/analytics/plugins/Contents/Columns/ContentInteraction.php create mode 100644 www/analytics/plugins/Contents/Columns/ContentName.php create mode 100644 www/analytics/plugins/Contents/Columns/ContentPiece.php create mode 100644 www/analytics/plugins/Contents/Columns/ContentTarget.php create mode 100644 www/analytics/plugins/Contents/Columns/Metrics/InteractionRate.php create mode 100644 www/analytics/plugins/Contents/Contents.php create mode 100644 www/analytics/plugins/Contents/Controller.php create mode 100644 www/analytics/plugins/Contents/DataArray.php create mode 100644 www/analytics/plugins/Contents/Dimensions.php create mode 100644 www/analytics/plugins/Contents/Menu.php create mode 100644 www/analytics/plugins/Contents/README.md create mode 100644 www/analytics/plugins/Contents/Reports/Base.php create mode 100644 www/analytics/plugins/Contents/Reports/GetContentNames.php create mode 100644 www/analytics/plugins/Contents/Reports/GetContentPieces.php create mode 100644 www/analytics/plugins/Contents/javascripts/contentsDataTable.js create mode 100644 www/analytics/plugins/Contents/lang/ar.json create mode 100644 www/analytics/plugins/Contents/lang/bg.json create mode 100644 www/analytics/plugins/Contents/lang/ca.json create mode 100644 www/analytics/plugins/Contents/lang/cs.json create mode 100644 www/analytics/plugins/Contents/lang/da.json create mode 100644 www/analytics/plugins/Contents/lang/de.json create mode 100644 www/analytics/plugins/Contents/lang/el.json create mode 100644 www/analytics/plugins/Contents/lang/en.json create mode 100644 www/analytics/plugins/Contents/lang/es.json create mode 100644 www/analytics/plugins/Contents/lang/et.json create mode 100644 www/analytics/plugins/Contents/lang/fi.json create mode 100644 www/analytics/plugins/Contents/lang/fr.json create mode 100644 www/analytics/plugins/Contents/lang/gl.json create mode 100644 www/analytics/plugins/Contents/lang/hi.json create mode 100644 www/analytics/plugins/Contents/lang/it.json create mode 100644 www/analytics/plugins/Contents/lang/ja.json create mode 100644 www/analytics/plugins/Contents/lang/ko.json create mode 100644 www/analytics/plugins/Contents/lang/nb.json create mode 100644 www/analytics/plugins/Contents/lang/nl.json create mode 100644 www/analytics/plugins/Contents/lang/pl.json create mode 100644 www/analytics/plugins/Contents/lang/pt-br.json create mode 100644 www/analytics/plugins/Contents/lang/pt.json create mode 100644 www/analytics/plugins/Contents/lang/ro.json create mode 100644 www/analytics/plugins/Contents/lang/ru.json create mode 100644 www/analytics/plugins/Contents/lang/sl.json create mode 100644 www/analytics/plugins/Contents/lang/sq.json create mode 100644 www/analytics/plugins/Contents/lang/sr.json create mode 100644 www/analytics/plugins/Contents/lang/sv.json create mode 100644 www/analytics/plugins/Contents/lang/ta.json create mode 100644 www/analytics/plugins/Contents/lang/tl.json create mode 100644 www/analytics/plugins/Contents/lang/tr.json create mode 100644 www/analytics/plugins/Contents/lang/vi.json create mode 100644 www/analytics/plugins/Contents/stylesheets/datatable.less create mode 100644 www/analytics/plugins/CoreAdminHome/Commands/DeleteLogsData.php create mode 100644 www/analytics/plugins/CoreAdminHome/Commands/FixDuplicateLogActions.php create mode 100644 www/analytics/plugins/CoreAdminHome/Commands/InvalidateReportData.php create mode 100644 www/analytics/plugins/CoreAdminHome/Commands/OptimizeArchiveTables.php create mode 100644 www/analytics/plugins/CoreAdminHome/Commands/PurgeOldArchiveData.php create mode 100644 www/analytics/plugins/CoreAdminHome/Commands/RunScheduledTasks.php create mode 100644 www/analytics/plugins/CoreAdminHome/Commands/SetConfig.php create mode 100644 www/analytics/plugins/CoreAdminHome/Commands/SetConfig/ConfigSettingManipulation.php create mode 100644 www/analytics/plugins/CoreAdminHome/Menu.php create mode 100644 www/analytics/plugins/CoreAdminHome/Model/DuplicateActionRemover.php create mode 100644 www/analytics/plugins/CoreAdminHome/OptOutManager.php create mode 100644 www/analytics/plugins/CoreAdminHome/Tasks.php create mode 100644 www/analytics/plugins/CoreAdminHome/Tasks/ArchivesToPurgeDistributedList.php create mode 100644 www/analytics/plugins/CoreAdminHome/javascripts/protocolCheck.js create mode 100644 www/analytics/plugins/CoreAdminHome/lang/ar.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/be.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/bg.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/bs.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/ca.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/cs.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/da.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/de.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/el.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/en.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/es.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/et.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/fa.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/fi.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/fr.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/he.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/hi.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/hr.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/hu.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/id.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/is.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/it.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/ja.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/ka.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/ko.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/lt.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/lv.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/nb.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/nl.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/nn.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/pl.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/pt-br.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/pt.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/ro.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/ru.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/sk.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/sl.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/sq.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/sr.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/sv.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/ta.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/te.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/th.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/tl.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/tr.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/uk.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/vi.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/zh-cn.json create mode 100644 www/analytics/plugins/CoreAdminHome/lang/zh-tw.json delete mode 100644 www/analytics/plugins/CoreAdminHome/stylesheets/menu.less delete mode 100644 www/analytics/plugins/CoreAdminHome/stylesheets/pluginSettings.less delete mode 100644 www/analytics/plugins/CoreAdminHome/templates/_menu.twig create mode 100644 www/analytics/plugins/CoreConsole/Commands/ClearCaches.php delete mode 100644 www/analytics/plugins/CoreConsole/Commands/CodeCoverage.php create mode 100644 www/analytics/plugins/CoreConsole/Commands/DevelopmentEnable.php create mode 100644 www/analytics/plugins/CoreConsole/Commands/DevelopmentManageTestFiles.php create mode 100644 www/analytics/plugins/CoreConsole/Commands/DevelopmentSyncProcessedSystemTests.php create mode 100644 www/analytics/plugins/CoreConsole/Commands/GenerateAngularDirective.php create mode 100644 www/analytics/plugins/CoreConsole/Commands/GenerateArchiver.php create mode 100644 www/analytics/plugins/CoreConsole/Commands/GenerateDimension.php create mode 100644 www/analytics/plugins/CoreConsole/Commands/GenerateMenu.php create mode 100644 www/analytics/plugins/CoreConsole/Commands/GenerateReport.php create mode 100644 www/analytics/plugins/CoreConsole/Commands/GenerateScheduledTask.php create mode 100644 www/analytics/plugins/CoreConsole/Commands/GenerateUpdate.php create mode 100644 www/analytics/plugins/CoreConsole/Commands/GenerateWidget.php delete mode 100644 www/analytics/plugins/CoreConsole/Commands/ManageTestFiles.php delete mode 100644 www/analytics/plugins/CoreConsole/Commands/RunTests.php delete mode 100644 www/analytics/plugins/CoreConsole/Commands/RunUITests.php delete mode 100644 www/analytics/plugins/CoreConsole/Commands/SetupFixture.php delete mode 100644 www/analytics/plugins/CoreConsole/Commands/SyncUITestScreenshots.php create mode 100644 www/analytics/plugins/CoreHome/Columns/IdSite.php create mode 100644 www/analytics/plugins/CoreHome/Columns/Metrics/ActionsPerVisit.php create mode 100644 www/analytics/plugins/CoreHome/Columns/Metrics/AverageTimeOnSite.php create mode 100644 www/analytics/plugins/CoreHome/Columns/Metrics/BounceRate.php create mode 100644 www/analytics/plugins/CoreHome/Columns/Metrics/CallableProcessedMetric.php create mode 100644 www/analytics/plugins/CoreHome/Columns/Metrics/ConversionRate.php create mode 100644 www/analytics/plugins/CoreHome/Columns/Metrics/EvolutionMetric.php create mode 100644 www/analytics/plugins/CoreHome/Columns/Metrics/VisitsPercent.php create mode 100644 www/analytics/plugins/CoreHome/Columns/ServerTime.php create mode 100644 www/analytics/plugins/CoreHome/Columns/UserId.php create mode 100644 www/analytics/plugins/CoreHome/Columns/VisitFirstActionTime.php create mode 100644 www/analytics/plugins/CoreHome/Columns/VisitGoalBuyer.php create mode 100644 www/analytics/plugins/CoreHome/Columns/VisitGoalConverted.php create mode 100644 www/analytics/plugins/CoreHome/Columns/VisitId.php create mode 100644 www/analytics/plugins/CoreHome/Columns/VisitIp.php create mode 100644 www/analytics/plugins/CoreHome/Columns/VisitLastActionTime.php create mode 100644 www/analytics/plugins/CoreHome/Columns/VisitTotalTime.php create mode 100644 www/analytics/plugins/CoreHome/Columns/VisitorDaysSinceFirst.php create mode 100644 www/analytics/plugins/CoreHome/Columns/VisitorDaysSinceOrder.php create mode 100644 www/analytics/plugins/CoreHome/Columns/VisitorId.php create mode 100644 www/analytics/plugins/CoreHome/Columns/VisitorReturning.php create mode 100644 www/analytics/plugins/CoreHome/Columns/VisitsCount.php create mode 100644 www/analytics/plugins/CoreHome/Menu.php create mode 100644 www/analytics/plugins/CoreHome/Segment.php create mode 100644 www/analytics/plugins/CoreHome/Tracker/VisitRequestProcessor.php create mode 100644 www/analytics/plugins/CoreHome/Visitor.php create mode 100644 www/analytics/plugins/CoreHome/Widgets.php create mode 100644 www/analytics/plugins/CoreHome/angularjs/ajax-form/ajax-form.controller.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/ajax-form/ajax-form.directive.js delete mode 100644 www/analytics/plugins/CoreHome/angularjs/common/directives/autocomplete-matched_spec.js delete mode 100644 www/analytics/plugins/CoreHome/angularjs/common/directives/directive.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/common/directives/directive.module.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/common/directives/translate.js delete mode 100644 www/analytics/plugins/CoreHome/angularjs/common/filters/filter.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/common/filters/filter.module.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/common/filters/htmldecode.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/common/filters/length.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/common/filters/pretty-url.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/common/filters/startfrom.spec.js delete mode 100644 www/analytics/plugins/CoreHome/angularjs/common/filters/startfrom_spec.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/common/filters/trim.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/common/filters/ucfirst.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/common/services/piwik-api.spec.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/common/services/piwik.spec.js delete mode 100644 www/analytics/plugins/CoreHome/angularjs/common/services/piwik_spec.js delete mode 100644 www/analytics/plugins/CoreHome/angularjs/common/services/service.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/common/services/service.module.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler-urllistener.service.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler.controller.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler.directive.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/dialogtoggler/ngdialog.less delete mode 100644 www/analytics/plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline-directive.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.directive.html create mode 100644 www/analytics/plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.directive.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.directive.less delete mode 100644 www/analytics/plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.html delete mode 100644 www/analytics/plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.less create mode 100644 www/analytics/plugins/CoreHome/angularjs/history/history.service.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/http404check.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/menudropdown/menudropdown.directive.html create mode 100644 www/analytics/plugins/CoreHome/angularjs/menudropdown/menudropdown.directive.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/menudropdown/menudropdown.directive.less create mode 100644 www/analytics/plugins/CoreHome/angularjs/notification/notification.controller.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/notification/notification.directive.html create mode 100644 www/analytics/plugins/CoreHome/angularjs/notification/notification.directive.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/notification/notification.directive.less create mode 100644 www/analytics/plugins/CoreHome/angularjs/piwikApp.config.js delete mode 100644 www/analytics/plugins/CoreHome/angularjs/piwikAppConfig.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/quick-access/quick-access.controller.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/quick-access/quick-access.directive.html create mode 100644 www/analytics/plugins/CoreHome/angularjs/quick-access/quick-access.directive.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/quick-access/quick-access.directive.less create mode 100644 www/analytics/plugins/CoreHome/angularjs/selector/selector.directive.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/selector/selector.directive.less delete mode 100644 www/analytics/plugins/CoreHome/angularjs/siteselector/siteselector-controller.js delete mode 100644 www/analytics/plugins/CoreHome/angularjs/siteselector/siteselector-directive.js delete mode 100644 www/analytics/plugins/CoreHome/angularjs/siteselector/siteselector-model.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/siteselector/siteselector-model.service.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/siteselector/siteselector.controller.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/siteselector/siteselector.directive.html create mode 100644 www/analytics/plugins/CoreHome/angularjs/siteselector/siteselector.directive.js create mode 100644 www/analytics/plugins/CoreHome/angularjs/siteselector/siteselector.directive.less delete mode 100644 www/analytics/plugins/CoreHome/angularjs/siteselector/siteselector.html delete mode 100644 www/analytics/plugins/CoreHome/angularjs/siteselector/siteselector.less create mode 100644 www/analytics/plugins/CoreHome/config/config.php create mode 100644 www/analytics/plugins/CoreHome/images/favicon.png create mode 100755 www/analytics/plugins/CoreHome/images/navigation_collapse.png create mode 100755 www/analytics/plugins/CoreHome/images/navigation_expand.png create mode 100644 www/analytics/plugins/CoreHome/javascripts/numberFormatter.js delete mode 100644 www/analytics/plugins/CoreHome/javascripts/promo.js create mode 100644 www/analytics/plugins/CoreHome/lang/am.json create mode 100644 www/analytics/plugins/CoreHome/lang/ar.json create mode 100644 www/analytics/plugins/CoreHome/lang/be.json create mode 100644 www/analytics/plugins/CoreHome/lang/bg.json create mode 100644 www/analytics/plugins/CoreHome/lang/bs.json create mode 100644 www/analytics/plugins/CoreHome/lang/ca.json create mode 100644 www/analytics/plugins/CoreHome/lang/cs.json create mode 100644 www/analytics/plugins/CoreHome/lang/da.json create mode 100644 www/analytics/plugins/CoreHome/lang/de.json create mode 100644 www/analytics/plugins/CoreHome/lang/el.json create mode 100644 www/analytics/plugins/CoreHome/lang/en.json create mode 100644 www/analytics/plugins/CoreHome/lang/es.json create mode 100644 www/analytics/plugins/CoreHome/lang/et.json create mode 100644 www/analytics/plugins/CoreHome/lang/eu.json create mode 100644 www/analytics/plugins/CoreHome/lang/fa.json create mode 100644 www/analytics/plugins/CoreHome/lang/fi.json create mode 100644 www/analytics/plugins/CoreHome/lang/fr.json create mode 100644 www/analytics/plugins/CoreHome/lang/gl.json create mode 100644 www/analytics/plugins/CoreHome/lang/he.json create mode 100644 www/analytics/plugins/CoreHome/lang/hi.json create mode 100644 www/analytics/plugins/CoreHome/lang/hr.json create mode 100644 www/analytics/plugins/CoreHome/lang/hu.json create mode 100644 www/analytics/plugins/CoreHome/lang/id.json create mode 100644 www/analytics/plugins/CoreHome/lang/is.json create mode 100644 www/analytics/plugins/CoreHome/lang/it.json create mode 100644 www/analytics/plugins/CoreHome/lang/ja.json create mode 100644 www/analytics/plugins/CoreHome/lang/ka.json create mode 100644 www/analytics/plugins/CoreHome/lang/ko.json create mode 100644 www/analytics/plugins/CoreHome/lang/lt.json create mode 100644 www/analytics/plugins/CoreHome/lang/lv.json create mode 100644 www/analytics/plugins/CoreHome/lang/nb.json create mode 100644 www/analytics/plugins/CoreHome/lang/nl.json create mode 100644 www/analytics/plugins/CoreHome/lang/nn.json create mode 100644 www/analytics/plugins/CoreHome/lang/pl.json create mode 100644 www/analytics/plugins/CoreHome/lang/pt-br.json create mode 100644 www/analytics/plugins/CoreHome/lang/pt.json create mode 100644 www/analytics/plugins/CoreHome/lang/ro.json create mode 100644 www/analytics/plugins/CoreHome/lang/ru.json create mode 100644 www/analytics/plugins/CoreHome/lang/sk.json create mode 100644 www/analytics/plugins/CoreHome/lang/sl.json create mode 100644 www/analytics/plugins/CoreHome/lang/sq.json create mode 100644 www/analytics/plugins/CoreHome/lang/sr.json create mode 100644 www/analytics/plugins/CoreHome/lang/sv.json create mode 100644 www/analytics/plugins/CoreHome/lang/ta.json create mode 100644 www/analytics/plugins/CoreHome/lang/te.json create mode 100644 www/analytics/plugins/CoreHome/lang/th.json create mode 100644 www/analytics/plugins/CoreHome/lang/tl.json create mode 100644 www/analytics/plugins/CoreHome/lang/tr.json create mode 100644 www/analytics/plugins/CoreHome/lang/uk.json create mode 100644 www/analytics/plugins/CoreHome/lang/vi.json create mode 100644 www/analytics/plugins/CoreHome/lang/zh-cn.json create mode 100644 www/analytics/plugins/CoreHome/lang/zh-tw.json create mode 100644 www/analytics/plugins/CoreHome/stylesheets/layout.less delete mode 100644 www/analytics/plugins/CoreHome/stylesheets/menu.less create mode 100644 www/analytics/plugins/CoreHome/stylesheets/zen-mode.less create mode 100644 www/analytics/plugins/CoreHome/templates/_adblockDetect.twig create mode 100644 www/analytics/plugins/CoreHome/templates/_favicon.twig delete mode 100644 www/analytics/plugins/CoreHome/templates/_indexContent.twig delete mode 100644 www/analytics/plugins/CoreHome/templates/_topBarHelloMenu.twig delete mode 100644 www/analytics/plugins/CoreHome/templates/_topBarTopMenu.twig create mode 100644 www/analytics/plugins/CorePluginsAdmin/Commands/ActivatePlugin.php create mode 100644 www/analytics/plugins/CorePluginsAdmin/Commands/DeactivatePlugin.php create mode 100644 www/analytics/plugins/CorePluginsAdmin/Commands/ListPlugins.php create mode 100644 www/analytics/plugins/CorePluginsAdmin/Menu.php create mode 100644 www/analytics/plugins/CorePluginsAdmin/Tasks.php create mode 100644 www/analytics/plugins/CorePluginsAdmin/images/flattr.png create mode 100644 www/analytics/plugins/CorePluginsAdmin/images/paypal_donate.jpg create mode 100755 www/analytics/plugins/CorePluginsAdmin/javascripts/marketplace.js delete mode 100755 www/analytics/plugins/CorePluginsAdmin/javascripts/pluginDetail.js create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/am.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/ar.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/be.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/bg.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/bn.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/bs.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/ca.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/cs.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/da.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/de.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/el.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/en.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/es.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/et.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/eu.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/fa.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/fi.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/fr.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/gl.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/he.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/hi.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/hr.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/hu.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/id.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/is.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/it.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/ja.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/ka.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/ko.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/lt.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/lv.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/nb.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/nl.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/nn.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/pl.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/pt-br.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/pt.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/ro.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/ru.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/sk.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/sl.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/sq.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/sr.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/sv.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/ta.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/te.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/th.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/tl.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/tr.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/uk.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/vi.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/zh-cn.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/lang/zh-tw.json create mode 100644 www/analytics/plugins/CorePluginsAdmin/stylesheets/plugin-details.less delete mode 100644 www/analytics/plugins/CorePluginsAdmin/templates/browsePlugins.twig delete mode 100644 www/analytics/plugins/CorePluginsAdmin/templates/browsePluginsActions.twig delete mode 100644 www/analytics/plugins/CorePluginsAdmin/templates/browseThemes.twig delete mode 100644 www/analytics/plugins/CorePluginsAdmin/templates/extend.twig create mode 100644 www/analytics/plugins/CorePluginsAdmin/templates/marketplace.twig create mode 100644 www/analytics/plugins/CorePluginsAdmin/templates/marketplace/plugin-list.twig delete mode 100644 www/analytics/plugins/CorePluginsAdmin/templates/pluginMetadata.twig delete mode 100644 www/analytics/plugins/CorePluginsAdmin/templates/pluginOverview.twig delete mode 100644 www/analytics/plugins/CorePluginsAdmin/templates/themeOverview.twig create mode 100644 www/analytics/plugins/CoreUpdater/ArchiveDownloadException.php create mode 100644 www/analytics/plugins/CoreUpdater/Commands/Update/CliUpdateObserver.php create mode 100644 www/analytics/plugins/CoreUpdater/Diagnostic/HttpsUpdateCheck.php create mode 100644 www/analytics/plugins/CoreUpdater/Model.php create mode 100644 www/analytics/plugins/CoreUpdater/ReleaseChannel.php create mode 100644 www/analytics/plugins/CoreUpdater/ReleaseChannel/Latest2XBeta.php create mode 100644 www/analytics/plugins/CoreUpdater/ReleaseChannel/Latest2XStable.php create mode 100644 www/analytics/plugins/CoreUpdater/ReleaseChannel/LatestBeta.php create mode 100644 www/analytics/plugins/CoreUpdater/ReleaseChannel/LatestStable.php create mode 100644 www/analytics/plugins/CoreUpdater/Tasks.php create mode 100644 www/analytics/plugins/CoreUpdater/Test/Fixtures/DbUpdaterTestFixture.php create mode 100644 www/analytics/plugins/CoreUpdater/Test/Fixtures/FailUpdateHttpsFixture.php create mode 100644 www/analytics/plugins/CoreUpdater/Test/Integration/Commands/UpdateTest.php create mode 100644 www/analytics/plugins/CoreUpdater/Test/Integration/ReleaseChannelTest.php create mode 100644 www/analytics/plugins/CoreUpdater/Test/Integration/UpdateCommunicationTest.php create mode 100644 www/analytics/plugins/CoreUpdater/Test/Mock/UpdaterMock.php create mode 100644 www/analytics/plugins/CoreUpdater/Test/Unit/ModelTest.php create mode 100644 www/analytics/plugins/CoreUpdater/Updater.php create mode 100644 www/analytics/plugins/CoreUpdater/UpdaterException.php create mode 100644 www/analytics/plugins/CoreUpdater/config/config.php create mode 100644 www/analytics/plugins/CoreUpdater/lang/am.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/ar.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/be.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/bg.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/bs.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/ca.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/cs.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/da.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/de.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/el.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/en.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/es.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/et.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/eu.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/fa.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/fi.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/fr.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/gl.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/he.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/hi.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/hu.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/id.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/it.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/ja.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/ka.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/ko.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/lt.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/lv.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/nb.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/nl.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/nn.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/pl.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/pt-br.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/pt.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/ro.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/ru.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/sk.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/sl.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/sq.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/sr.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/sv.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/ta.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/te.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/th.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/tl.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/tr.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/uk.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/vi.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/zh-cn.json create mode 100644 www/analytics/plugins/CoreUpdater/lang/zh-tw.json delete mode 100644 www/analytics/plugins/CoreUpdater/templates/oneClickResults.twig delete mode 100644 www/analytics/plugins/CoreUpdater/templates/runUpdaterAndExit_done_cli.twig delete mode 100644 www/analytics/plugins/CoreUpdater/templates/runUpdaterAndExit_welcome_cli.twig create mode 100644 www/analytics/plugins/CoreUpdater/templates/updateHttpError.twig create mode 100644 www/analytics/plugins/CoreUpdater/templates/updateHttpsError.twig create mode 100644 www/analytics/plugins/CoreUpdater/templates/updateSuccess.twig create mode 100644 www/analytics/plugins/CoreVisualizations/Metrics/Formatter/Numeric.php create mode 100644 www/analytics/plugins/CoreVisualizations/Visualizations/HtmlTable/PivotBy.php create mode 100644 www/analytics/plugins/CustomVariables/Columns/Base.php create mode 100644 www/analytics/plugins/CustomVariables/Columns/CustomVariableName.php create mode 100644 www/analytics/plugins/CustomVariables/Columns/CustomVariableValue.php create mode 100644 www/analytics/plugins/CustomVariables/DataTable/Filter/CustomVariablesValuesFromNameId.php create mode 100644 www/analytics/plugins/CustomVariables/Menu.php create mode 100644 www/analytics/plugins/CustomVariables/Reports/Base.php create mode 100644 www/analytics/plugins/CustomVariables/Reports/GetCustomVariables.php create mode 100644 www/analytics/plugins/CustomVariables/Reports/GetCustomVariablesValuesFromNameId.php create mode 100644 www/analytics/plugins/CustomVariables/Tracker/CustomVariablesRequestProcessor.php create mode 100644 www/analytics/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.controller.js create mode 100644 www/analytics/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.directive.html create mode 100644 www/analytics/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.directive.js create mode 100644 www/analytics/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.directive.less create mode 100644 www/analytics/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.model.js create mode 100644 www/analytics/plugins/CustomVariables/config/config.php create mode 100644 www/analytics/plugins/CustomVariables/config/test.php create mode 100644 www/analytics/plugins/CustomVariables/lang/ar.json create mode 100644 www/analytics/plugins/CustomVariables/lang/be.json create mode 100644 www/analytics/plugins/CustomVariables/lang/bg.json create mode 100644 www/analytics/plugins/CustomVariables/lang/ca.json create mode 100644 www/analytics/plugins/CustomVariables/lang/cs.json create mode 100644 www/analytics/plugins/CustomVariables/lang/da.json create mode 100644 www/analytics/plugins/CustomVariables/lang/de.json create mode 100644 www/analytics/plugins/CustomVariables/lang/el.json create mode 100644 www/analytics/plugins/CustomVariables/lang/en.json create mode 100644 www/analytics/plugins/CustomVariables/lang/es.json create mode 100644 www/analytics/plugins/CustomVariables/lang/et.json create mode 100644 www/analytics/plugins/CustomVariables/lang/fa.json create mode 100644 www/analytics/plugins/CustomVariables/lang/fi.json create mode 100644 www/analytics/plugins/CustomVariables/lang/fr.json create mode 100644 www/analytics/plugins/CustomVariables/lang/he.json create mode 100644 www/analytics/plugins/CustomVariables/lang/hi.json create mode 100644 www/analytics/plugins/CustomVariables/lang/id.json create mode 100644 www/analytics/plugins/CustomVariables/lang/it.json create mode 100644 www/analytics/plugins/CustomVariables/lang/ja.json create mode 100644 www/analytics/plugins/CustomVariables/lang/ko.json create mode 100644 www/analytics/plugins/CustomVariables/lang/nb.json create mode 100644 www/analytics/plugins/CustomVariables/lang/nl.json create mode 100644 www/analytics/plugins/CustomVariables/lang/pl.json create mode 100644 www/analytics/plugins/CustomVariables/lang/pt-br.json create mode 100644 www/analytics/plugins/CustomVariables/lang/pt.json create mode 100644 www/analytics/plugins/CustomVariables/lang/ro.json create mode 100644 www/analytics/plugins/CustomVariables/lang/ru.json create mode 100644 www/analytics/plugins/CustomVariables/lang/sk.json create mode 100644 www/analytics/plugins/CustomVariables/lang/sl.json create mode 100644 www/analytics/plugins/CustomVariables/lang/sq.json create mode 100644 www/analytics/plugins/CustomVariables/lang/sr.json create mode 100644 www/analytics/plugins/CustomVariables/lang/sv.json create mode 100644 www/analytics/plugins/CustomVariables/lang/ta.json create mode 100644 www/analytics/plugins/CustomVariables/lang/th.json create mode 100644 www/analytics/plugins/CustomVariables/lang/tl.json create mode 100644 www/analytics/plugins/CustomVariables/lang/tr.json create mode 100644 www/analytics/plugins/CustomVariables/lang/vi.json create mode 100644 www/analytics/plugins/CustomVariables/lang/zh-cn.json create mode 100644 www/analytics/plugins/CustomVariables/templates/manage.twig delete mode 100644 www/analytics/plugins/DBStats/.gitignore create mode 100644 www/analytics/plugins/DBStats/Menu.php create mode 100644 www/analytics/plugins/DBStats/Reports/Base.php create mode 100644 www/analytics/plugins/DBStats/Reports/GetAdminDataSummary.php create mode 100644 www/analytics/plugins/DBStats/Reports/GetDatabaseUsageSummary.php create mode 100644 www/analytics/plugins/DBStats/Reports/GetIndividualMetricsSummary.php create mode 100644 www/analytics/plugins/DBStats/Reports/GetIndividualReportsSummary.php create mode 100644 www/analytics/plugins/DBStats/Reports/GetMetricDataSummary.php create mode 100644 www/analytics/plugins/DBStats/Reports/GetMetricDataSummaryByYear.php create mode 100644 www/analytics/plugins/DBStats/Reports/GetReportDataSummary.php create mode 100644 www/analytics/plugins/DBStats/Reports/GetReportDataSummaryByYear.php create mode 100644 www/analytics/plugins/DBStats/Reports/GetTrackerDataSummary.php create mode 100644 www/analytics/plugins/DBStats/Tasks.php create mode 100644 www/analytics/plugins/DBStats/config/test.php create mode 100644 www/analytics/plugins/DBStats/lang/cy.json create mode 100644 www/analytics/plugins/DBStats/lang/gl.json create mode 100644 www/analytics/plugins/DBStats/lang/hr.json create mode 100644 www/analytics/plugins/DBStats/lang/tl.json create mode 100644 www/analytics/plugins/Dashboard/Menu.php create mode 100644 www/analytics/plugins/Dashboard/Model.php create mode 100644 www/analytics/plugins/Dashboard/lang/am.json create mode 100644 www/analytics/plugins/Dashboard/lang/ar.json create mode 100644 www/analytics/plugins/Dashboard/lang/be.json create mode 100644 www/analytics/plugins/Dashboard/lang/bg.json create mode 100644 www/analytics/plugins/Dashboard/lang/bn.json create mode 100644 www/analytics/plugins/Dashboard/lang/bs.json create mode 100644 www/analytics/plugins/Dashboard/lang/ca.json create mode 100644 www/analytics/plugins/Dashboard/lang/cs.json create mode 100644 www/analytics/plugins/Dashboard/lang/cy.json create mode 100644 www/analytics/plugins/Dashboard/lang/da.json create mode 100644 www/analytics/plugins/Dashboard/lang/de.json create mode 100644 www/analytics/plugins/Dashboard/lang/el.json create mode 100644 www/analytics/plugins/Dashboard/lang/en.json create mode 100644 www/analytics/plugins/Dashboard/lang/es.json create mode 100644 www/analytics/plugins/Dashboard/lang/et.json create mode 100644 www/analytics/plugins/Dashboard/lang/eu.json create mode 100644 www/analytics/plugins/Dashboard/lang/fa.json create mode 100644 www/analytics/plugins/Dashboard/lang/fi.json create mode 100644 www/analytics/plugins/Dashboard/lang/fr.json create mode 100644 www/analytics/plugins/Dashboard/lang/gl.json create mode 100644 www/analytics/plugins/Dashboard/lang/he.json create mode 100644 www/analytics/plugins/Dashboard/lang/hi.json create mode 100644 www/analytics/plugins/Dashboard/lang/hr.json create mode 100644 www/analytics/plugins/Dashboard/lang/hu.json create mode 100644 www/analytics/plugins/Dashboard/lang/id.json create mode 100644 www/analytics/plugins/Dashboard/lang/is.json create mode 100644 www/analytics/plugins/Dashboard/lang/it.json create mode 100644 www/analytics/plugins/Dashboard/lang/ja.json create mode 100644 www/analytics/plugins/Dashboard/lang/ka.json create mode 100644 www/analytics/plugins/Dashboard/lang/ko.json create mode 100644 www/analytics/plugins/Dashboard/lang/lt.json create mode 100644 www/analytics/plugins/Dashboard/lang/lv.json create mode 100644 www/analytics/plugins/Dashboard/lang/nb.json create mode 100644 www/analytics/plugins/Dashboard/lang/nl.json create mode 100644 www/analytics/plugins/Dashboard/lang/nn.json create mode 100644 www/analytics/plugins/Dashboard/lang/pl.json create mode 100644 www/analytics/plugins/Dashboard/lang/pt-br.json create mode 100644 www/analytics/plugins/Dashboard/lang/pt.json create mode 100644 www/analytics/plugins/Dashboard/lang/ro.json create mode 100644 www/analytics/plugins/Dashboard/lang/ru.json create mode 100644 www/analytics/plugins/Dashboard/lang/sk.json create mode 100644 www/analytics/plugins/Dashboard/lang/sl.json create mode 100644 www/analytics/plugins/Dashboard/lang/sq.json create mode 100644 www/analytics/plugins/Dashboard/lang/sr.json create mode 100644 www/analytics/plugins/Dashboard/lang/sv.json create mode 100644 www/analytics/plugins/Dashboard/lang/ta.json create mode 100644 www/analytics/plugins/Dashboard/lang/te.json create mode 100644 www/analytics/plugins/Dashboard/lang/th.json create mode 100644 www/analytics/plugins/Dashboard/lang/tl.json create mode 100644 www/analytics/plugins/Dashboard/lang/tr.json create mode 100644 www/analytics/plugins/Dashboard/lang/uk.json create mode 100644 www/analytics/plugins/Dashboard/lang/vi.json create mode 100644 www/analytics/plugins/Dashboard/lang/zh-cn.json create mode 100644 www/analytics/plugins/Dashboard/lang/zh-tw.json create mode 100644 www/analytics/plugins/Dashboard/stylesheets/widget.less create mode 100644 www/analytics/plugins/DevicePlugins/API.php create mode 100644 www/analytics/plugins/DevicePlugins/Archiver.php create mode 100644 www/analytics/plugins/DevicePlugins/Columns/Plugin.php create mode 100644 www/analytics/plugins/DevicePlugins/Columns/PluginCookie.php create mode 100644 www/analytics/plugins/DevicePlugins/Columns/PluginDirector.php create mode 100644 www/analytics/plugins/DevicePlugins/Columns/PluginFlash.php create mode 100644 www/analytics/plugins/DevicePlugins/Columns/PluginGears.php create mode 100644 www/analytics/plugins/DevicePlugins/Columns/PluginJava.php create mode 100644 www/analytics/plugins/DevicePlugins/Columns/PluginPdf.php create mode 100644 www/analytics/plugins/DevicePlugins/Columns/PluginQuickTime.php create mode 100644 www/analytics/plugins/DevicePlugins/Columns/PluginRealPlayer.php create mode 100644 www/analytics/plugins/DevicePlugins/Columns/PluginSilverlight.php create mode 100644 www/analytics/plugins/DevicePlugins/Columns/PluginWindowsMedia.php create mode 100644 www/analytics/plugins/DevicePlugins/DevicePlugins.php create mode 100644 www/analytics/plugins/DevicePlugins/Reports/Base.php create mode 100644 www/analytics/plugins/DevicePlugins/Reports/GetPlugin.php create mode 100644 www/analytics/plugins/DevicePlugins/Visitor.php create mode 100644 www/analytics/plugins/DevicePlugins/functions.php rename www/analytics/plugins/{UserSettings => DevicePlugins}/images/plugins/cookie.gif (100%) rename www/analytics/plugins/{UserSettings => DevicePlugins}/images/plugins/director.gif (100%) rename www/analytics/plugins/{UserSettings => DevicePlugins}/images/plugins/flash.gif (100%) rename www/analytics/plugins/{UserSettings => DevicePlugins}/images/plugins/gears.gif (100%) rename www/analytics/plugins/{UserSettings => DevicePlugins}/images/plugins/java.gif (100%) rename www/analytics/plugins/{UserSettings => DevicePlugins}/images/plugins/pdf.gif (100%) rename www/analytics/plugins/{UserSettings => DevicePlugins}/images/plugins/quicktime.gif (100%) rename www/analytics/plugins/{UserSettings => DevicePlugins}/images/plugins/realplayer.gif (100%) rename www/analytics/plugins/{UserSettings => DevicePlugins}/images/plugins/silverlight.gif (100%) rename www/analytics/plugins/{UserSettings => DevicePlugins}/images/plugins/windowsmedia.gif (100%) create mode 100644 www/analytics/plugins/DevicePlugins/lang/am.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/ar.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/be.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/bg.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/ca.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/cs.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/da.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/de.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/el.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/en.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/es.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/et.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/eu.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/fa.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/fi.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/fr.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/gl.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/hi.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/hu.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/id.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/is.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/it.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/ja.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/ka.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/ko.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/lt.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/lv.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/nb.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/nl.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/nn.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/pl.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/pt-br.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/pt.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/ro.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/ru.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/sk.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/sl.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/sq.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/sr.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/sv.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/th.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/tl.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/tr.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/uk.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/vi.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/zh-cn.json create mode 100644 www/analytics/plugins/DevicePlugins/lang/zh-tw.json create mode 100644 www/analytics/plugins/DevicesDetection/Columns/Base.php create mode 100644 www/analytics/plugins/DevicesDetection/Columns/BrowserEngine.php create mode 100644 www/analytics/plugins/DevicesDetection/Columns/BrowserName.php create mode 100644 www/analytics/plugins/DevicesDetection/Columns/BrowserVersion.php create mode 100644 www/analytics/plugins/DevicesDetection/Columns/DeviceBrand.php create mode 100644 www/analytics/plugins/DevicesDetection/Columns/DeviceModel.php create mode 100644 www/analytics/plugins/DevicesDetection/Columns/DeviceType.php create mode 100644 www/analytics/plugins/DevicesDetection/Columns/Os.php create mode 100644 www/analytics/plugins/DevicesDetection/Columns/OsVersion.php create mode 100644 www/analytics/plugins/DevicesDetection/Menu.php create mode 100644 www/analytics/plugins/DevicesDetection/Reports/Base.php create mode 100644 www/analytics/plugins/DevicesDetection/Reports/GetBrand.php create mode 100644 www/analytics/plugins/DevicesDetection/Reports/GetBrowserEngines.php create mode 100644 www/analytics/plugins/DevicesDetection/Reports/GetBrowserVersions.php create mode 100644 www/analytics/plugins/DevicesDetection/Reports/GetBrowsers.php create mode 100644 www/analytics/plugins/DevicesDetection/Reports/GetModel.php create mode 100644 www/analytics/plugins/DevicesDetection/Reports/GetOsFamilies.php create mode 100644 www/analytics/plugins/DevicesDetection/Reports/GetOsVersions.php create mode 100644 www/analytics/plugins/DevicesDetection/Reports/GetType.php create mode 100644 www/analytics/plugins/DevicesDetection/Segment.php create mode 100644 www/analytics/plugins/DevicesDetection/Visitor.php create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/3Q.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/BBK.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Barnes_Noble.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Celkon.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Cherry_Mobile.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Compaq.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/ConCorde.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Coolpad.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Crius_Mea.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Crosscall.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Danew.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Easypix.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Evertek.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Fujitsu.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Gigabyte.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Gigaset.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Gionee.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Hyundai.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Le_Pan.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/MSI.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Nikon.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/OnePlus.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Quechua.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/SFR.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Sencor.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Smartfren.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Tecno_Mobile.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Tolino.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Tunisie_Telecom.ico rename www/analytics/plugins/DevicesDetection/images/brand/{unknown.ico => Unknown.ico} (100%) create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Wiko.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Wolder.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Woxter.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Yarvik.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/Zopo.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/bq.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/iBerry.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/brand/teXet.ico create mode 100644 www/analytics/plugins/DevicesDetection/images/browsers/36.gif rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/AA.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/AB.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/AG.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/AM.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/AN.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/AR.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/AV.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/AW.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/B2.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/BB.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/BD.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/BE.gif (100%) create mode 100644 www/analytics/plugins/DevicesDetection/images/browsers/BJ.gif rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/BP.gif (100%) create mode 100644 www/analytics/plugins/DevicesDetection/images/browsers/BS.gif rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/BX.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/CA.gif (100%) create mode 100644 www/analytics/plugins/DevicesDetection/images/browsers/CC.gif rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/CD.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/CF.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/CH.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/CK.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/CM.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/CN.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/CO.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/CP.gif (100%) create mode 100644 www/analytics/plugins/DevicesDetection/images/browsers/CR.gif rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/CS.gif (100%) create mode 100644 www/analytics/plugins/DevicesDetection/images/browsers/CX.gif create mode 100644 www/analytics/plugins/DevicesDetection/images/browsers/DE.gif rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/DF.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/DI.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/EL.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/EP.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/ES.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/FB.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/FD.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/FE.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/FF.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/FL.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/FN.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/GA.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/GE.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/HA.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/HJ.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/IA.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/IB.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/IC.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/ID.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/IE.gif (100%) create mode 100644 www/analytics/plugins/DevicesDetection/images/browsers/IM.gif rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/IR.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/IW.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/KI.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/KM.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/KO.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/KP.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/KZ.gif (100%) create mode 100644 www/analytics/plugins/DevicesDetection/images/browsers/LB.gif create mode 100644 www/analytics/plugins/DevicesDetection/images/browsers/LG.gif rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/LI.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/LS.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/LX.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/MC.gif (100%) rename www/analytics/plugins/{UserSettings/images/browsers/SF.gif => DevicesDetection/images/browsers/MF.gif} (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/MI.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/MO.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/MS.gif (100%) create mode 100644 www/analytics/plugins/DevicesDetection/images/browsers/MU.gif rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/MX.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/NB.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/NF.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/NL.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/NP.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/NS.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/OB.gif (100%) create mode 100644 www/analytics/plugins/DevicesDetection/images/browsers/OE.gif create mode 100644 www/analytics/plugins/DevicesDetection/images/browsers/OF.gif create mode 100644 www/analytics/plugins/DevicesDetection/images/browsers/OI.gif rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/ON.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/OP.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/OR.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/OV.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/OW.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/PL.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/PM.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/PO.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/PU.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/PW.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/PX.gif (100%) create mode 100644 www/analytics/plugins/DevicesDetection/images/browsers/QQ.gif rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/RK.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/SA.gif (100%) create mode 100644 www/analytics/plugins/DevicesDetection/images/browsers/SE.gif create mode 100644 www/analytics/plugins/DevicesDetection/images/browsers/SF.gif create mode 100644 www/analytics/plugins/DevicesDetection/images/browsers/SH.gif rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/SL.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/SM.gif (100%) create mode 100644 www/analytics/plugins/DevicesDetection/images/browsers/SR.gif rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/TB.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/TI.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/TZ.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/UC.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/UN.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/UNK.gif (100%) create mode 100644 www/analytics/plugins/DevicesDetection/images/browsers/VI.gif rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/WE.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/WO.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/WP.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/browsers/YA.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/3DS.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/AIX.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/AMG.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/AMI.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/AND.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/ARL.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/BBX.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/BEO.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/BLB.gif (100%) create mode 100644 www/analytics/plugins/DevicesDetection/images/os/BMP.gif rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/BSD.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/BTR.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/CES.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/COS.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/DFB.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/DSI.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/FED.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/FOS.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/GNT.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/GTV.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/HPX.gif (100%) create mode 100644 www/analytics/plugins/DevicesDetection/images/os/IOS.gif rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/IPA.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/IPD.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/IPH.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/IRI.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/KBT.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/KNO.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/LBT.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/LIN.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/MAC.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/MAE.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/MDR.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/MIN.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/NBS.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/NDS.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/OBS.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/OS2.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/POS.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/PPY.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/PS3.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/PSP.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/PSV.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/QNX.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/RHT.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/ROS.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/SAF.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/SBA.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/SLW.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/SOS.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/SSE.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/SYL.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/SYM.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/T64.gif (100%) create mode 100644 www/analytics/plugins/DevicesDetection/images/os/TDX.gif rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/TIZ.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/UBT.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/UNK.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/VMS.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/WCE.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/WII.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/WIN.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/WIU.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/WMO.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/WOS.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/WPH.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/WRT.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/XBT.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/XBX.gif (100%) rename www/analytics/plugins/{UserSettings => DevicesDetection}/images/os/YNS.gif (100%) create mode 100644 www/analytics/plugins/DevicesDetection/lang/am.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/ar.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/be.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/ca.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/eu.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/gl.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/he.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/hi.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/hr.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/hu.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/id.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/is.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/ka.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/ko.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/lt.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/lv.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/nn.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/pl.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/pt.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/sk.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/sl.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/sq.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/ta.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/te.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/th.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/tl.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/tr.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/uk.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/vi.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/zh-cn.json create mode 100644 www/analytics/plugins/DevicesDetection/lang/zh-tw.json create mode 100644 www/analytics/plugins/DevicesDetection/plugin.json create mode 100644 www/analytics/plugins/DevicesDetection/templates/devices.twig delete mode 100644 www/analytics/plugins/DevicesDetection/templates/index.twig create mode 100644 www/analytics/plugins/DevicesDetection/templates/software.twig create mode 100644 www/analytics/plugins/Diagnostics/Commands/AnalyzeArchiveTable.php create mode 100644 www/analytics/plugins/Diagnostics/Commands/Run.php create mode 100644 www/analytics/plugins/Diagnostics/ConfigReader.php create mode 100644 www/analytics/plugins/Diagnostics/Controller.php create mode 100644 www/analytics/plugins/Diagnostics/Diagnostic/CronArchivingCheck.php create mode 100644 www/analytics/plugins/Diagnostics/Diagnostic/DbAdapterCheck.php create mode 100644 www/analytics/plugins/Diagnostics/Diagnostic/Diagnostic.php create mode 100644 www/analytics/plugins/Diagnostics/Diagnostic/DiagnosticResult.php create mode 100644 www/analytics/plugins/Diagnostics/Diagnostic/DiagnosticResultItem.php create mode 100644 www/analytics/plugins/Diagnostics/Diagnostic/FileIntegrityCheck.php create mode 100644 www/analytics/plugins/Diagnostics/Diagnostic/GdExtensionCheck.php create mode 100644 www/analytics/plugins/Diagnostics/Diagnostic/HttpClientCheck.php create mode 100644 www/analytics/plugins/Diagnostics/Diagnostic/LoadDataInfileCheck.php create mode 100644 www/analytics/plugins/Diagnostics/Diagnostic/MemoryLimitCheck.php create mode 100644 www/analytics/plugins/Diagnostics/Diagnostic/NfsDiskCheck.php create mode 100644 www/analytics/plugins/Diagnostics/Diagnostic/PageSpeedCheck.php create mode 100644 www/analytics/plugins/Diagnostics/Diagnostic/PhpExtensionsCheck.php create mode 100644 www/analytics/plugins/Diagnostics/Diagnostic/PhpFunctionsCheck.php create mode 100644 www/analytics/plugins/Diagnostics/Diagnostic/PhpSettingsCheck.php create mode 100644 www/analytics/plugins/Diagnostics/Diagnostic/PhpVersionCheck.php create mode 100644 www/analytics/plugins/Diagnostics/Diagnostic/RecommendedExtensionsCheck.php create mode 100644 www/analytics/plugins/Diagnostics/Diagnostic/RecommendedFunctionsCheck.php create mode 100644 www/analytics/plugins/Diagnostics/Diagnostic/TimezoneCheck.php create mode 100644 www/analytics/plugins/Diagnostics/Diagnostic/TrackerCheck.php create mode 100644 www/analytics/plugins/Diagnostics/Diagnostic/WriteAccessCheck.php create mode 100644 www/analytics/plugins/Diagnostics/DiagnosticReport.php create mode 100644 www/analytics/plugins/Diagnostics/DiagnosticService.php create mode 100644 www/analytics/plugins/Diagnostics/Diagnostics.php create mode 100644 www/analytics/plugins/Diagnostics/Menu.php create mode 100644 www/analytics/plugins/Diagnostics/Test/Integration/Commands/AnalyzeArchiveTableTest.php create mode 100644 www/analytics/plugins/Diagnostics/Test/Integration/ConfigReaderTest.php create mode 100644 www/analytics/plugins/Diagnostics/Test/Mock/DiagnosticWithError.php create mode 100644 www/analytics/plugins/Diagnostics/Test/Mock/DiagnosticWithSuccess.php create mode 100644 www/analytics/plugins/Diagnostics/Test/Mock/DiagnosticWithWarning.php create mode 100644 www/analytics/plugins/Diagnostics/Test/Unit/Diagnostic/DiagnosticResultTest.php create mode 100644 www/analytics/plugins/Diagnostics/Test/Unit/DiagnosticReportTest.php create mode 100644 www/analytics/plugins/Diagnostics/Test/Unit/DiagnosticServiceTest.php create mode 100644 www/analytics/plugins/Diagnostics/config/config.php create mode 100644 www/analytics/plugins/Diagnostics/lang/cs.json create mode 100644 www/analytics/plugins/Diagnostics/lang/de.json create mode 100644 www/analytics/plugins/Diagnostics/lang/el.json create mode 100644 www/analytics/plugins/Diagnostics/lang/en.json create mode 100644 www/analytics/plugins/Diagnostics/lang/it.json create mode 100644 www/analytics/plugins/Diagnostics/lang/pt-br.json create mode 100644 www/analytics/plugins/Diagnostics/lang/sv.json create mode 100644 www/analytics/plugins/Diagnostics/plugin.json create mode 100644 www/analytics/plugins/Diagnostics/stylesheets/configfile.less create mode 100644 www/analytics/plugins/Diagnostics/templates/configfile.twig create mode 100644 www/analytics/plugins/Ecommerce/Columns/BaseConversion.php create mode 100644 www/analytics/plugins/Ecommerce/Columns/ProductCategory.php create mode 100644 www/analytics/plugins/Ecommerce/Columns/ProductName.php create mode 100644 www/analytics/plugins/Ecommerce/Columns/ProductSku.php create mode 100644 www/analytics/plugins/Ecommerce/Columns/Revenue.php create mode 100644 www/analytics/plugins/Ecommerce/Columns/RevenueDiscount.php create mode 100644 www/analytics/plugins/Ecommerce/Columns/RevenueShipping.php create mode 100644 www/analytics/plugins/Ecommerce/Columns/RevenueSubtotal.php create mode 100644 www/analytics/plugins/Ecommerce/Columns/RevenueTax.php create mode 100644 www/analytics/plugins/Ecommerce/Controller.php create mode 100644 www/analytics/plugins/Ecommerce/Menu.php create mode 100644 www/analytics/plugins/Ecommerce/Reports/Base.php create mode 100644 www/analytics/plugins/Ecommerce/Reports/BaseItem.php create mode 100644 www/analytics/plugins/Ecommerce/Reports/GetDaysToConversionAbandonedCart.php create mode 100644 www/analytics/plugins/Ecommerce/Reports/GetDaysToConversionEcommerceOrder.php create mode 100644 www/analytics/plugins/Ecommerce/Reports/GetEcommerceAbandonedCart.php create mode 100644 www/analytics/plugins/Ecommerce/Reports/GetEcommerceOrder.php create mode 100644 www/analytics/plugins/Ecommerce/Reports/GetItemsCategory.php create mode 100644 www/analytics/plugins/Ecommerce/Reports/GetItemsName.php create mode 100644 www/analytics/plugins/Ecommerce/Reports/GetItemsSku.php create mode 100644 www/analytics/plugins/Ecommerce/Reports/GetVisitsUntilConversionAbandonedCart.php create mode 100644 www/analytics/plugins/Ecommerce/Reports/GetVisitsUntilConversionEcommerceOrder.php create mode 100644 www/analytics/plugins/Ecommerce/Tracker/EcommerceRequestProcessor.php create mode 100644 www/analytics/plugins/Ecommerce/Widgets.php create mode 100644 www/analytics/plugins/Ecommerce/lang/bg.json create mode 100644 www/analytics/plugins/Ecommerce/lang/cs.json create mode 100644 www/analytics/plugins/Ecommerce/lang/da.json create mode 100644 www/analytics/plugins/Ecommerce/lang/de.json create mode 100644 www/analytics/plugins/Ecommerce/lang/el.json create mode 100644 www/analytics/plugins/Ecommerce/lang/en.json create mode 100644 www/analytics/plugins/Ecommerce/lang/es.json create mode 100644 www/analytics/plugins/Ecommerce/lang/fr.json create mode 100644 www/analytics/plugins/Ecommerce/lang/hi.json create mode 100644 www/analytics/plugins/Ecommerce/lang/it.json create mode 100644 www/analytics/plugins/Ecommerce/lang/ja.json create mode 100644 www/analytics/plugins/Ecommerce/lang/nb.json create mode 100644 www/analytics/plugins/Ecommerce/lang/nl.json create mode 100644 www/analytics/plugins/Ecommerce/lang/pt-br.json create mode 100644 www/analytics/plugins/Ecommerce/lang/pt.json create mode 100644 www/analytics/plugins/Ecommerce/lang/ru.json create mode 100644 www/analytics/plugins/Ecommerce/lang/sk.json create mode 100644 www/analytics/plugins/Ecommerce/lang/sr.json create mode 100644 www/analytics/plugins/Ecommerce/lang/sv.json create mode 100644 www/analytics/plugins/Ecommerce/lang/ta.json create mode 100644 www/analytics/plugins/Ecommerce/plugin.json create mode 100644 www/analytics/plugins/Ecommerce/templates/ecommerceLog.twig create mode 100644 www/analytics/plugins/Ecommerce/templates/products.twig create mode 100644 www/analytics/plugins/Ecommerce/templates/sales.twig create mode 100644 www/analytics/plugins/Events/Actions/ActionEvent.php create mode 100644 www/analytics/plugins/Events/Columns/EventAction.php create mode 100644 www/analytics/plugins/Events/Columns/EventCategory.php create mode 100644 www/analytics/plugins/Events/Columns/EventName.php create mode 100644 www/analytics/plugins/Events/Columns/Metrics/AverageEventValue.php create mode 100644 www/analytics/plugins/Events/Columns/TotalEvents.php create mode 100644 www/analytics/plugins/Events/DataTable/Filter/ReplaceEventNameNotSet.php create mode 100644 www/analytics/plugins/Events/Menu.php create mode 100644 www/analytics/plugins/Events/Reports/Base.php create mode 100644 www/analytics/plugins/Events/Reports/GetAction.php create mode 100644 www/analytics/plugins/Events/Reports/GetActionFromCategoryId.php create mode 100644 www/analytics/plugins/Events/Reports/GetActionFromNameId.php create mode 100644 www/analytics/plugins/Events/Reports/GetCategory.php create mode 100644 www/analytics/plugins/Events/Reports/GetCategoryFromActionId.php create mode 100644 www/analytics/plugins/Events/Reports/GetCategoryFromNameId.php create mode 100644 www/analytics/plugins/Events/Reports/GetName.php create mode 100644 www/analytics/plugins/Events/Reports/GetNameFromActionId.php create mode 100644 www/analytics/plugins/Events/Reports/GetNameFromCategoryId.php create mode 100644 www/analytics/plugins/Events/Segment.php create mode 100644 www/analytics/plugins/Events/lang/ca.json create mode 100644 www/analytics/plugins/Events/lang/cs.json create mode 100644 www/analytics/plugins/Events/lang/hi.json create mode 100644 www/analytics/plugins/Events/lang/lt.json create mode 100644 www/analytics/plugins/Events/lang/nb.json create mode 100644 www/analytics/plugins/Events/lang/pl.json create mode 100644 www/analytics/plugins/Events/lang/pt.json create mode 100644 www/analytics/plugins/Events/lang/ru.json create mode 100644 www/analytics/plugins/Events/lang/sk.json create mode 100644 www/analytics/plugins/Events/lang/sl.json create mode 100644 www/analytics/plugins/Events/lang/tl.json create mode 100644 www/analytics/plugins/Events/lang/tr.json delete mode 100644 www/analytics/plugins/Events/plugin.json create mode 100644 www/analytics/plugins/Events/stylesheets/datatable.less delete mode 100644 www/analytics/plugins/ExamplePlugin/.gitignore delete mode 100644 www/analytics/plugins/ExamplePlugin/.travis.yml create mode 100644 www/analytics/plugins/ExamplePlugin/Archiver.php create mode 100644 www/analytics/plugins/ExamplePlugin/Menu.php create mode 100644 www/analytics/plugins/ExamplePlugin/Tasks.php create mode 100644 www/analytics/plugins/ExamplePlugin/Updates/0.0.2.php create mode 100644 www/analytics/plugins/ExamplePlugin/Widgets.php create mode 100644 www/analytics/plugins/ExamplePlugin/angularjs/directive-component/component.controller.js create mode 100644 www/analytics/plugins/ExamplePlugin/angularjs/directive-component/component.directive.html create mode 100644 www/analytics/plugins/ExamplePlugin/angularjs/directive-component/component.directive.js create mode 100644 www/analytics/plugins/ExamplePlugin/angularjs/directive-component/component.directive.less delete mode 100644 www/analytics/plugins/ExamplePlugin/screenshots/.gitkeep create mode 100644 www/analytics/plugins/ExampleReport/API.php create mode 100644 www/analytics/plugins/ExampleReport/ExampleReport.php create mode 100644 www/analytics/plugins/ExampleReport/Reports/Base.php create mode 100644 www/analytics/plugins/ExampleReport/Reports/GetExampleReport.php create mode 100644 www/analytics/plugins/ExampleReport/plugin.json delete mode 100644 www/analytics/plugins/ExampleRssWidget/Controller.php create mode 100644 www/analytics/plugins/ExampleRssWidget/Widgets.php create mode 100644 www/analytics/plugins/ExampleTracker/Columns/ExampleActionDimension.php create mode 100644 www/analytics/plugins/ExampleTracker/Columns/ExampleConversionDimension.php create mode 100644 www/analytics/plugins/ExampleTracker/Columns/ExampleDimension.php create mode 100644 www/analytics/plugins/ExampleTracker/Columns/ExampleVisitDimension.php create mode 100644 www/analytics/plugins/ExampleTracker/ExampleTracker.php create mode 100644 www/analytics/plugins/ExampleTracker/lang/en.json create mode 100644 www/analytics/plugins/ExampleTracker/plugin.json delete mode 100644 www/analytics/plugins/ExampleUI/ExampleUI.php create mode 100644 www/analytics/plugins/ExampleUI/Menu.php rename www/analytics/plugins/ExampleVisualization/{ => Visualizations}/SimpleTable.php (95%) create mode 100644 www/analytics/plugins/Feedback/Menu.php delete mode 100644 www/analytics/plugins/Feedback/angularjs/ratefeature/ratefeature-controller.js delete mode 100644 www/analytics/plugins/Feedback/angularjs/ratefeature/ratefeature-directive.js delete mode 100644 www/analytics/plugins/Feedback/angularjs/ratefeature/ratefeature-model.js create mode 100644 www/analytics/plugins/Feedback/angularjs/ratefeature/ratefeature-model.service.js create mode 100644 www/analytics/plugins/Feedback/angularjs/ratefeature/ratefeature.controller.js create mode 100644 www/analytics/plugins/Feedback/angularjs/ratefeature/ratefeature.directive.html create mode 100644 www/analytics/plugins/Feedback/angularjs/ratefeature/ratefeature.directive.js rename www/analytics/plugins/Feedback/angularjs/ratefeature/{ratefeature.less => ratefeature.directive.less} (100%) delete mode 100644 www/analytics/plugins/Feedback/angularjs/ratefeature/ratefeature.html create mode 100644 www/analytics/plugins/Feedback/lang/ar.json create mode 100644 www/analytics/plugins/Feedback/lang/be.json create mode 100644 www/analytics/plugins/Feedback/lang/bg.json create mode 100644 www/analytics/plugins/Feedback/lang/bn.json create mode 100644 www/analytics/plugins/Feedback/lang/ca.json create mode 100644 www/analytics/plugins/Feedback/lang/cs.json create mode 100644 www/analytics/plugins/Feedback/lang/da.json create mode 100644 www/analytics/plugins/Feedback/lang/de.json create mode 100644 www/analytics/plugins/Feedback/lang/el.json create mode 100644 www/analytics/plugins/Feedback/lang/en.json create mode 100644 www/analytics/plugins/Feedback/lang/es.json create mode 100644 www/analytics/plugins/Feedback/lang/et.json create mode 100644 www/analytics/plugins/Feedback/lang/fa.json create mode 100644 www/analytics/plugins/Feedback/lang/fi.json create mode 100644 www/analytics/plugins/Feedback/lang/fr.json create mode 100644 www/analytics/plugins/Feedback/lang/gl.json create mode 100644 www/analytics/plugins/Feedback/lang/hi.json create mode 100644 www/analytics/plugins/Feedback/lang/hu.json create mode 100644 www/analytics/plugins/Feedback/lang/id.json create mode 100644 www/analytics/plugins/Feedback/lang/it.json create mode 100644 www/analytics/plugins/Feedback/lang/ja.json create mode 100644 www/analytics/plugins/Feedback/lang/ka.json create mode 100644 www/analytics/plugins/Feedback/lang/ko.json create mode 100644 www/analytics/plugins/Feedback/lang/lt.json create mode 100644 www/analytics/plugins/Feedback/lang/lv.json create mode 100644 www/analytics/plugins/Feedback/lang/nb.json create mode 100644 www/analytics/plugins/Feedback/lang/nl.json create mode 100644 www/analytics/plugins/Feedback/lang/nn.json create mode 100644 www/analytics/plugins/Feedback/lang/pl.json create mode 100644 www/analytics/plugins/Feedback/lang/pt-br.json create mode 100644 www/analytics/plugins/Feedback/lang/pt.json create mode 100644 www/analytics/plugins/Feedback/lang/ro.json create mode 100644 www/analytics/plugins/Feedback/lang/ru.json create mode 100644 www/analytics/plugins/Feedback/lang/sk.json create mode 100644 www/analytics/plugins/Feedback/lang/sl.json create mode 100644 www/analytics/plugins/Feedback/lang/sq.json create mode 100644 www/analytics/plugins/Feedback/lang/sr.json create mode 100644 www/analytics/plugins/Feedback/lang/sv.json create mode 100644 www/analytics/plugins/Feedback/lang/ta.json create mode 100644 www/analytics/plugins/Feedback/lang/th.json create mode 100644 www/analytics/plugins/Feedback/lang/tl.json create mode 100644 www/analytics/plugins/Feedback/lang/tr.json create mode 100644 www/analytics/plugins/Feedback/lang/uk.json create mode 100644 www/analytics/plugins/Feedback/lang/vi.json create mode 100644 www/analytics/plugins/Feedback/lang/zh-cn.json create mode 100644 www/analytics/plugins/Feedback/lang/zh-tw.json create mode 100644 www/analytics/plugins/Goals/Columns/DaysToConversion.php create mode 100644 www/analytics/plugins/Goals/Columns/IdGoal.php create mode 100644 www/analytics/plugins/Goals/Columns/Metrics/AverageOrderRevenue.php create mode 100644 www/analytics/plugins/Goals/Columns/Metrics/AveragePrice.php create mode 100644 www/analytics/plugins/Goals/Columns/Metrics/AverageQuantity.php create mode 100644 www/analytics/plugins/Goals/Columns/Metrics/GoalSpecific/AverageOrderRevenue.php create mode 100644 www/analytics/plugins/Goals/Columns/Metrics/GoalSpecific/ConversionRate.php create mode 100644 www/analytics/plugins/Goals/Columns/Metrics/GoalSpecific/Conversions.php create mode 100644 www/analytics/plugins/Goals/Columns/Metrics/GoalSpecific/ItemsCount.php create mode 100644 www/analytics/plugins/Goals/Columns/Metrics/GoalSpecific/Revenue.php create mode 100644 www/analytics/plugins/Goals/Columns/Metrics/GoalSpecific/RevenuePerVisit.php create mode 100644 www/analytics/plugins/Goals/Columns/Metrics/GoalSpecificProcessedMetric.php create mode 100644 www/analytics/plugins/Goals/Columns/Metrics/ProductConversionRate.php create mode 100644 www/analytics/plugins/Goals/Columns/Metrics/RevenuePerVisit.php create mode 100644 www/analytics/plugins/Goals/Columns/VisitsUntilConversion.php create mode 100644 www/analytics/plugins/Goals/DataTable/Filter/AppendNameToColumnNames.php create mode 100644 www/analytics/plugins/Goals/Menu.php create mode 100644 www/analytics/plugins/Goals/Model.php create mode 100644 www/analytics/plugins/Goals/Reports/Base.php create mode 100644 www/analytics/plugins/Goals/Reports/Get.php create mode 100644 www/analytics/plugins/Goals/Reports/GetDaysToConversion.php create mode 100644 www/analytics/plugins/Goals/Reports/GetMetrics.php create mode 100644 www/analytics/plugins/Goals/Reports/GetVisitsUntilConversion.php create mode 100644 www/analytics/plugins/Goals/Tracker/GoalsRequestProcessor.php create mode 100644 www/analytics/plugins/Goals/TranslationHelper.php create mode 100644 www/analytics/plugins/Goals/Widgets.php create mode 100644 www/analytics/plugins/Goals/lang/am.json create mode 100644 www/analytics/plugins/Goals/lang/ar.json create mode 100644 www/analytics/plugins/Goals/lang/be.json create mode 100644 www/analytics/plugins/Goals/lang/bg.json create mode 100644 www/analytics/plugins/Goals/lang/bs.json create mode 100644 www/analytics/plugins/Goals/lang/ca.json create mode 100644 www/analytics/plugins/Goals/lang/cs.json create mode 100644 www/analytics/plugins/Goals/lang/da.json create mode 100644 www/analytics/plugins/Goals/lang/de.json create mode 100644 www/analytics/plugins/Goals/lang/el.json create mode 100644 www/analytics/plugins/Goals/lang/en.json create mode 100644 www/analytics/plugins/Goals/lang/es.json create mode 100644 www/analytics/plugins/Goals/lang/et.json create mode 100644 www/analytics/plugins/Goals/lang/eu.json create mode 100644 www/analytics/plugins/Goals/lang/fa.json create mode 100644 www/analytics/plugins/Goals/lang/fi.json create mode 100644 www/analytics/plugins/Goals/lang/fr.json create mode 100644 www/analytics/plugins/Goals/lang/gl.json create mode 100644 www/analytics/plugins/Goals/lang/he.json create mode 100644 www/analytics/plugins/Goals/lang/hi.json create mode 100644 www/analytics/plugins/Goals/lang/hu.json create mode 100644 www/analytics/plugins/Goals/lang/id.json create mode 100644 www/analytics/plugins/Goals/lang/is.json create mode 100644 www/analytics/plugins/Goals/lang/it.json create mode 100644 www/analytics/plugins/Goals/lang/ja.json create mode 100644 www/analytics/plugins/Goals/lang/ka.json create mode 100644 www/analytics/plugins/Goals/lang/ko.json create mode 100644 www/analytics/plugins/Goals/lang/lt.json create mode 100644 www/analytics/plugins/Goals/lang/lv.json create mode 100644 www/analytics/plugins/Goals/lang/nb.json create mode 100644 www/analytics/plugins/Goals/lang/nl.json create mode 100644 www/analytics/plugins/Goals/lang/nn.json create mode 100644 www/analytics/plugins/Goals/lang/pl.json create mode 100644 www/analytics/plugins/Goals/lang/pt-br.json create mode 100644 www/analytics/plugins/Goals/lang/pt.json create mode 100644 www/analytics/plugins/Goals/lang/ro.json create mode 100644 www/analytics/plugins/Goals/lang/ru.json create mode 100644 www/analytics/plugins/Goals/lang/sk.json create mode 100644 www/analytics/plugins/Goals/lang/sl.json create mode 100644 www/analytics/plugins/Goals/lang/sq.json create mode 100644 www/analytics/plugins/Goals/lang/sr.json create mode 100644 www/analytics/plugins/Goals/lang/sv.json create mode 100644 www/analytics/plugins/Goals/lang/ta.json create mode 100644 www/analytics/plugins/Goals/lang/te.json create mode 100644 www/analytics/plugins/Goals/lang/th.json create mode 100644 www/analytics/plugins/Goals/lang/tr.json create mode 100644 www/analytics/plugins/Goals/lang/uk.json create mode 100644 www/analytics/plugins/Goals/lang/vi.json create mode 100644 www/analytics/plugins/Goals/lang/zh-cn.json create mode 100644 www/analytics/plugins/Goals/lang/zh-tw.json create mode 100644 www/analytics/plugins/Goals/templates/editGoals.twig create mode 100644 www/analytics/plugins/Goals/templates/manageGoals.twig create mode 100644 www/analytics/plugins/Heartbeat/Heartbeat.php create mode 100644 www/analytics/plugins/Heartbeat/Tracker/PingRequestProcessor.php create mode 100644 www/analytics/plugins/Heartbeat/plugin.json create mode 100644 www/analytics/plugins/ImageGraph/lang/bg.json create mode 100644 www/analytics/plugins/ImageGraph/lang/ca.json create mode 100644 www/analytics/plugins/ImageGraph/lang/cs.json create mode 100644 www/analytics/plugins/ImageGraph/lang/da.json create mode 100644 www/analytics/plugins/ImageGraph/lang/de.json create mode 100644 www/analytics/plugins/ImageGraph/lang/el.json create mode 100644 www/analytics/plugins/ImageGraph/lang/en.json create mode 100644 www/analytics/plugins/ImageGraph/lang/es.json create mode 100644 www/analytics/plugins/ImageGraph/lang/fa.json create mode 100644 www/analytics/plugins/ImageGraph/lang/fi.json create mode 100644 www/analytics/plugins/ImageGraph/lang/fr.json create mode 100644 www/analytics/plugins/ImageGraph/lang/hi.json create mode 100644 www/analytics/plugins/ImageGraph/lang/id.json create mode 100644 www/analytics/plugins/ImageGraph/lang/it.json create mode 100644 www/analytics/plugins/ImageGraph/lang/ja.json create mode 100644 www/analytics/plugins/ImageGraph/lang/ko.json create mode 100644 www/analytics/plugins/ImageGraph/lang/lv.json create mode 100644 www/analytics/plugins/ImageGraph/lang/nb.json create mode 100644 www/analytics/plugins/ImageGraph/lang/nl.json create mode 100644 www/analytics/plugins/ImageGraph/lang/nn.json create mode 100644 www/analytics/plugins/ImageGraph/lang/pt-br.json create mode 100644 www/analytics/plugins/ImageGraph/lang/pt.json create mode 100644 www/analytics/plugins/ImageGraph/lang/ro.json create mode 100644 www/analytics/plugins/ImageGraph/lang/ru.json create mode 100644 www/analytics/plugins/ImageGraph/lang/sl.json create mode 100644 www/analytics/plugins/ImageGraph/lang/sq.json create mode 100644 www/analytics/plugins/ImageGraph/lang/sr.json create mode 100644 www/analytics/plugins/ImageGraph/lang/sv.json create mode 100644 www/analytics/plugins/ImageGraph/lang/vi.json create mode 100644 www/analytics/plugins/ImageGraph/lang/zh-cn.json create mode 100644 www/analytics/plugins/Insights/Widgets.php create mode 100644 www/analytics/plugins/Insights/lang/bg.json create mode 100644 www/analytics/plugins/Insights/lang/cs.json create mode 100644 www/analytics/plugins/Insights/lang/da.json create mode 100644 www/analytics/plugins/Insights/lang/de.json create mode 100644 www/analytics/plugins/Insights/lang/el.json create mode 100644 www/analytics/plugins/Insights/lang/en.json create mode 100644 www/analytics/plugins/Insights/lang/es.json create mode 100644 www/analytics/plugins/Insights/lang/et.json create mode 100644 www/analytics/plugins/Insights/lang/fa.json create mode 100644 www/analytics/plugins/Insights/lang/fi.json create mode 100644 www/analytics/plugins/Insights/lang/fr.json create mode 100644 www/analytics/plugins/Insights/lang/gl.json create mode 100644 www/analytics/plugins/Insights/lang/hi.json create mode 100644 www/analytics/plugins/Insights/lang/it.json create mode 100644 www/analytics/plugins/Insights/lang/ja.json create mode 100644 www/analytics/plugins/Insights/lang/nb.json create mode 100644 www/analytics/plugins/Insights/lang/nl.json create mode 100644 www/analytics/plugins/Insights/lang/pl.json create mode 100644 www/analytics/plugins/Insights/lang/pt-br.json create mode 100644 www/analytics/plugins/Insights/lang/pt.json create mode 100644 www/analytics/plugins/Insights/lang/ro.json create mode 100644 www/analytics/plugins/Insights/lang/ru.json create mode 100644 www/analytics/plugins/Insights/lang/sq.json create mode 100644 www/analytics/plugins/Insights/lang/sr.json create mode 100644 www/analytics/plugins/Insights/lang/sv.json create mode 100644 www/analytics/plugins/Insights/lang/ta.json create mode 100644 www/analytics/plugins/Insights/lang/tl.json create mode 100644 www/analytics/plugins/Insights/lang/tr.json create mode 100644 www/analytics/plugins/Insights/lang/vi.json create mode 100644 www/analytics/plugins/Insights/lang/zh-cn.json delete mode 100644 www/analytics/plugins/Insights/plugin.json create mode 100644 www/analytics/plugins/Installation/Exception/DatabaseConnectionFailedException.php create mode 100644 www/analytics/plugins/Installation/FormDefaultSettings.php delete mode 100644 www/analytics/plugins/Installation/FormGeneralSetup.php create mode 100644 www/analytics/plugins/Installation/FormSuperUser.php create mode 100644 www/analytics/plugins/Installation/Menu.php create mode 100644 www/analytics/plugins/Installation/lang/am.json create mode 100644 www/analytics/plugins/Installation/lang/ar.json create mode 100644 www/analytics/plugins/Installation/lang/be.json create mode 100644 www/analytics/plugins/Installation/lang/bg.json create mode 100644 www/analytics/plugins/Installation/lang/bs.json create mode 100644 www/analytics/plugins/Installation/lang/ca.json create mode 100644 www/analytics/plugins/Installation/lang/cs.json create mode 100644 www/analytics/plugins/Installation/lang/da.json create mode 100644 www/analytics/plugins/Installation/lang/de.json create mode 100644 www/analytics/plugins/Installation/lang/el.json create mode 100644 www/analytics/plugins/Installation/lang/en.json create mode 100644 www/analytics/plugins/Installation/lang/es.json create mode 100644 www/analytics/plugins/Installation/lang/et.json create mode 100644 www/analytics/plugins/Installation/lang/eu.json create mode 100644 www/analytics/plugins/Installation/lang/fa.json create mode 100644 www/analytics/plugins/Installation/lang/fi.json create mode 100644 www/analytics/plugins/Installation/lang/fr.json create mode 100644 www/analytics/plugins/Installation/lang/gl.json create mode 100644 www/analytics/plugins/Installation/lang/he.json create mode 100644 www/analytics/plugins/Installation/lang/hi.json create mode 100644 www/analytics/plugins/Installation/lang/hu.json create mode 100644 www/analytics/plugins/Installation/lang/id.json create mode 100644 www/analytics/plugins/Installation/lang/is.json create mode 100644 www/analytics/plugins/Installation/lang/it.json create mode 100644 www/analytics/plugins/Installation/lang/ja.json create mode 100644 www/analytics/plugins/Installation/lang/ka.json create mode 100644 www/analytics/plugins/Installation/lang/ko.json create mode 100644 www/analytics/plugins/Installation/lang/lt.json create mode 100644 www/analytics/plugins/Installation/lang/lv.json create mode 100644 www/analytics/plugins/Installation/lang/nb.json create mode 100644 www/analytics/plugins/Installation/lang/nl.json create mode 100644 www/analytics/plugins/Installation/lang/nn.json create mode 100644 www/analytics/plugins/Installation/lang/pl.json create mode 100644 www/analytics/plugins/Installation/lang/pt-br.json create mode 100644 www/analytics/plugins/Installation/lang/pt.json create mode 100644 www/analytics/plugins/Installation/lang/ro.json create mode 100644 www/analytics/plugins/Installation/lang/ru.json create mode 100644 www/analytics/plugins/Installation/lang/sk.json create mode 100644 www/analytics/plugins/Installation/lang/sl.json create mode 100644 www/analytics/plugins/Installation/lang/sq.json create mode 100644 www/analytics/plugins/Installation/lang/sr.json create mode 100644 www/analytics/plugins/Installation/lang/sv.json create mode 100644 www/analytics/plugins/Installation/lang/ta.json create mode 100644 www/analytics/plugins/Installation/lang/te.json create mode 100644 www/analytics/plugins/Installation/lang/th.json create mode 100644 www/analytics/plugins/Installation/lang/tl.json create mode 100644 www/analytics/plugins/Installation/lang/tr.json create mode 100644 www/analytics/plugins/Installation/lang/uk.json create mode 100644 www/analytics/plugins/Installation/lang/vi.json create mode 100644 www/analytics/plugins/Installation/lang/zh-cn.json create mode 100644 www/analytics/plugins/Installation/lang/zh-tw.json delete mode 100644 www/analytics/plugins/Installation/templates/_allSteps.twig create mode 100644 www/analytics/plugins/Installation/templates/cannotConnectToDb.twig delete mode 100644 www/analytics/plugins/Installation/templates/databaseCheck.twig delete mode 100644 www/analytics/plugins/Installation/templates/generalSetup.twig create mode 100644 www/analytics/plugins/Installation/templates/setupSuperUser.twig create mode 100644 www/analytics/plugins/Intl/Commands/GenerateIntl.php create mode 100644 www/analytics/plugins/Intl/DateTimeFormatProvider.php create mode 100644 www/analytics/plugins/Intl/Intl.php create mode 100644 www/analytics/plugins/Intl/config/config.php create mode 100644 www/analytics/plugins/Intl/lang/am.json create mode 100644 www/analytics/plugins/Intl/lang/ar.json create mode 100644 www/analytics/plugins/Intl/lang/be.json create mode 100644 www/analytics/plugins/Intl/lang/bg.json create mode 100644 www/analytics/plugins/Intl/lang/bn.json create mode 100644 www/analytics/plugins/Intl/lang/bs.json create mode 100644 www/analytics/plugins/Intl/lang/ca.json create mode 100644 www/analytics/plugins/Intl/lang/cs.json create mode 100644 www/analytics/plugins/Intl/lang/cy.json create mode 100644 www/analytics/plugins/Intl/lang/da.json create mode 100644 www/analytics/plugins/Intl/lang/de.json create mode 100644 www/analytics/plugins/Intl/lang/dev.json create mode 100644 www/analytics/plugins/Intl/lang/el.json create mode 100644 www/analytics/plugins/Intl/lang/en.json create mode 100644 www/analytics/plugins/Intl/lang/es.json create mode 100644 www/analytics/plugins/Intl/lang/et.json create mode 100644 www/analytics/plugins/Intl/lang/eu.json create mode 100644 www/analytics/plugins/Intl/lang/fa.json create mode 100644 www/analytics/plugins/Intl/lang/fi.json create mode 100644 www/analytics/plugins/Intl/lang/fr.json create mode 100644 www/analytics/plugins/Intl/lang/gl.json create mode 100644 www/analytics/plugins/Intl/lang/he.json create mode 100644 www/analytics/plugins/Intl/lang/hi.json create mode 100644 www/analytics/plugins/Intl/lang/hr.json create mode 100644 www/analytics/plugins/Intl/lang/hu.json create mode 100644 www/analytics/plugins/Intl/lang/id.json create mode 100644 www/analytics/plugins/Intl/lang/is.json create mode 100644 www/analytics/plugins/Intl/lang/it.json create mode 100644 www/analytics/plugins/Intl/lang/ja.json create mode 100644 www/analytics/plugins/Intl/lang/ka.json create mode 100644 www/analytics/plugins/Intl/lang/ko.json create mode 100644 www/analytics/plugins/Intl/lang/lt.json create mode 100644 www/analytics/plugins/Intl/lang/lv.json create mode 100644 www/analytics/plugins/Intl/lang/nb.json create mode 100644 www/analytics/plugins/Intl/lang/nl.json create mode 100644 www/analytics/plugins/Intl/lang/nn.json create mode 100644 www/analytics/plugins/Intl/lang/pl.json create mode 100644 www/analytics/plugins/Intl/lang/pt-br.json create mode 100644 www/analytics/plugins/Intl/lang/pt.json create mode 100644 www/analytics/plugins/Intl/lang/ro.json create mode 100644 www/analytics/plugins/Intl/lang/ru.json create mode 100644 www/analytics/plugins/Intl/lang/sk.json create mode 100644 www/analytics/plugins/Intl/lang/sl.json create mode 100644 www/analytics/plugins/Intl/lang/sq.json create mode 100644 www/analytics/plugins/Intl/lang/sr.json create mode 100644 www/analytics/plugins/Intl/lang/sv.json create mode 100644 www/analytics/plugins/Intl/lang/ta.json create mode 100644 www/analytics/plugins/Intl/lang/te.json create mode 100644 www/analytics/plugins/Intl/lang/th.json create mode 100644 www/analytics/plugins/Intl/lang/tl.json create mode 100644 www/analytics/plugins/Intl/lang/tr.json create mode 100644 www/analytics/plugins/Intl/lang/uk.json create mode 100644 www/analytics/plugins/Intl/lang/vi.json create mode 100644 www/analytics/plugins/Intl/lang/zh-cn.json create mode 100644 www/analytics/plugins/Intl/lang/zh-tw.json delete mode 100644 www/analytics/plugins/LanguagesManager/Commands/FetchFromOTrance.php create mode 100644 www/analytics/plugins/LanguagesManager/Commands/FetchTranslations.php create mode 100644 www/analytics/plugins/LanguagesManager/Commands/TranslationBase.php create mode 100644 www/analytics/plugins/LanguagesManager/Menu.php create mode 100644 www/analytics/plugins/LanguagesManager/Model.php create mode 100755 www/analytics/plugins/LanguagesManager/Test/Integration/LanguagesManagerTest.php create mode 100644 www/analytics/plugins/LanguagesManager/Test/Integration/ModelTest.php create mode 100644 www/analytics/plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/ByBaseTranslationsTest.php create mode 100644 www/analytics/plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/ByParameterCountTest.php create mode 100644 www/analytics/plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/EmptyTranslationsTest.php create mode 100644 www/analytics/plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/EncodedEntitiesTest.php create mode 100644 www/analytics/plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/UnnecassaryWhitespacesTest.php create mode 100644 www/analytics/plugins/LanguagesManager/Test/Unit/TranslationWriter/Validate/CoreTranslationsTest.php create mode 100644 www/analytics/plugins/LanguagesManager/Test/Unit/TranslationWriter/Validate/NoScriptsTest.php create mode 100644 www/analytics/plugins/LanguagesManager/Test/Unit/TranslationWriter/WriterTest.php rename www/analytics/{core/Translate => plugins/LanguagesManager/TranslationWriter}/Filter/ByBaseTranslations.php (90%) rename www/analytics/{core/Translate => plugins/LanguagesManager/TranslationWriter}/Filter/ByParameterCount.php (86%) rename www/analytics/{core/Translate => plugins/LanguagesManager/TranslationWriter}/Filter/EmptyTranslations.php (85%) rename www/analytics/{core/Translate => plugins/LanguagesManager/TranslationWriter}/Filter/EncodedEntities.php (77%) rename www/analytics/{core/Translate => plugins/LanguagesManager/TranslationWriter}/Filter/FilterAbstract.php (84%) rename www/analytics/{core/Translate => plugins/LanguagesManager/TranslationWriter}/Filter/UnnecassaryWhitespaces.php (90%) create mode 100644 www/analytics/plugins/LanguagesManager/TranslationWriter/Validate/CoreTranslations.php rename www/analytics/{core/Translate => plugins/LanguagesManager/TranslationWriter}/Validate/NoScripts.php (89%) rename www/analytics/{core/Translate => plugins/LanguagesManager/TranslationWriter}/Validate/ValidateAbstract.php (84%) rename www/analytics/{core/Translate => plugins/LanguagesManager/TranslationWriter}/Writer.php (92%) create mode 100644 www/analytics/plugins/LanguagesManager/Updates/2.15.1-b1.php create mode 100644 www/analytics/plugins/LanguagesManager/angularjs/languageselector/languageselector.directive.js create mode 100644 www/analytics/plugins/LanguagesManager/angularjs/translationsearch/translationsearch.controller.js create mode 100644 www/analytics/plugins/LanguagesManager/angularjs/translationsearch/translationsearch.directive.html create mode 100644 www/analytics/plugins/LanguagesManager/angularjs/translationsearch/translationsearch.directive.js delete mode 100644 www/analytics/plugins/LanguagesManager/javascripts/languageSelector.js create mode 100644 www/analytics/plugins/LanguagesManager/lang/ar.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/be.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/bg.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/ca.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/cs.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/da.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/de.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/el.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/en.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/es.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/et.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/fa.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/fi.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/fr.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/hi.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/hu.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/id.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/is.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/it.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/ja.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/ka.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/ko.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/lt.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/lv.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/nb.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/nl.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/nn.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/pl.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/pt-br.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/pt.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/ro.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/ru.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/sk.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/sl.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/sq.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/sr.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/sv.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/te.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/th.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/tl.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/tr.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/uk.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/vi.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/zh-cn.json create mode 100644 www/analytics/plugins/LanguagesManager/lang/zh-tw.json create mode 100644 www/analytics/plugins/LanguagesManager/templates/searchTranslation.twig delete mode 100644 www/analytics/plugins/LeftMenu/plugin.json delete mode 100644 www/analytics/plugins/LeftMenu/stylesheets/theme.less create mode 100644 www/analytics/plugins/Live/Model.php create mode 100644 www/analytics/plugins/Live/Reports/Base.php create mode 100644 www/analytics/plugins/Live/Reports/GetLastVisits.php create mode 100644 www/analytics/plugins/Live/Reports/GetLastVisitsDetails.php create mode 100644 www/analytics/plugins/Live/Reports/GetSimpleLastVisitCount.php create mode 100644 www/analytics/plugins/Live/VisitorFactory.php create mode 100644 www/analytics/plugins/Live/VisitorInterface.php delete mode 100644 www/analytics/plugins/Live/VisitorLog.php create mode 100644 www/analytics/plugins/Live/VisitorProfile.php create mode 100644 www/analytics/plugins/Live/Visualizations/VisitorLog.php create mode 100644 www/analytics/plugins/Live/Visualizations/VisitorLog/Config.php create mode 100644 www/analytics/plugins/Live/Widgets.php delete mode 100644 www/analytics/plugins/Live/images/pause_disabled.gif delete mode 100644 www/analytics/plugins/Live/images/play_disabled.gif create mode 100644 www/analytics/plugins/Live/images/visitorlog-hover.png create mode 100644 www/analytics/plugins/Live/images/visitorlog.png create mode 100644 www/analytics/plugins/Live/javascripts/SegmentedVisitorLog.js create mode 100644 www/analytics/plugins/Live/javascripts/rowaction.js create mode 100644 www/analytics/plugins/Live/lang/ar.json create mode 100644 www/analytics/plugins/Live/lang/be.json create mode 100644 www/analytics/plugins/Live/lang/bg.json create mode 100644 www/analytics/plugins/Live/lang/ca.json create mode 100644 www/analytics/plugins/Live/lang/cs.json create mode 100644 www/analytics/plugins/Live/lang/da.json create mode 100644 www/analytics/plugins/Live/lang/de.json create mode 100644 www/analytics/plugins/Live/lang/el.json create mode 100644 www/analytics/plugins/Live/lang/en.json create mode 100644 www/analytics/plugins/Live/lang/es.json create mode 100644 www/analytics/plugins/Live/lang/et.json create mode 100644 www/analytics/plugins/Live/lang/eu.json create mode 100644 www/analytics/plugins/Live/lang/fa.json create mode 100644 www/analytics/plugins/Live/lang/fi.json create mode 100644 www/analytics/plugins/Live/lang/fr.json create mode 100644 www/analytics/plugins/Live/lang/hi.json create mode 100644 www/analytics/plugins/Live/lang/hr.json create mode 100644 www/analytics/plugins/Live/lang/hu.json create mode 100644 www/analytics/plugins/Live/lang/id.json create mode 100644 www/analytics/plugins/Live/lang/is.json create mode 100644 www/analytics/plugins/Live/lang/it.json create mode 100644 www/analytics/plugins/Live/lang/ja.json create mode 100644 www/analytics/plugins/Live/lang/ka.json create mode 100644 www/analytics/plugins/Live/lang/ko.json create mode 100644 www/analytics/plugins/Live/lang/lt.json create mode 100644 www/analytics/plugins/Live/lang/lv.json create mode 100644 www/analytics/plugins/Live/lang/nb.json create mode 100644 www/analytics/plugins/Live/lang/nl.json create mode 100644 www/analytics/plugins/Live/lang/nn.json create mode 100644 www/analytics/plugins/Live/lang/pl.json create mode 100644 www/analytics/plugins/Live/lang/pt-br.json create mode 100644 www/analytics/plugins/Live/lang/pt.json create mode 100644 www/analytics/plugins/Live/lang/ro.json create mode 100644 www/analytics/plugins/Live/lang/ru.json create mode 100644 www/analytics/plugins/Live/lang/sk.json create mode 100644 www/analytics/plugins/Live/lang/sl.json create mode 100644 www/analytics/plugins/Live/lang/sq.json create mode 100644 www/analytics/plugins/Live/lang/sr.json create mode 100644 www/analytics/plugins/Live/lang/sv.json create mode 100644 www/analytics/plugins/Live/lang/ta.json create mode 100644 www/analytics/plugins/Live/lang/te.json create mode 100644 www/analytics/plugins/Live/lang/th.json create mode 100644 www/analytics/plugins/Live/lang/tl.json create mode 100644 www/analytics/plugins/Live/lang/tr.json create mode 100644 www/analytics/plugins/Live/lang/uk.json create mode 100644 www/analytics/plugins/Live/lang/vi.json create mode 100644 www/analytics/plugins/Live/lang/zh-cn.json create mode 100644 www/analytics/plugins/Live/lang/zh-tw.json create mode 100644 www/analytics/plugins/Login/PasswordResetter.php create mode 100644 www/analytics/plugins/Login/SessionInitializer.php create mode 100644 www/analytics/plugins/Login/config/config.php create mode 100644 www/analytics/plugins/Login/lang/am.json create mode 100644 www/analytics/plugins/Login/lang/ar.json create mode 100644 www/analytics/plugins/Login/lang/be.json create mode 100644 www/analytics/plugins/Login/lang/bg.json create mode 100644 www/analytics/plugins/Login/lang/ca.json create mode 100644 www/analytics/plugins/Login/lang/cs.json create mode 100644 www/analytics/plugins/Login/lang/da.json create mode 100644 www/analytics/plugins/Login/lang/de.json create mode 100644 www/analytics/plugins/Login/lang/el.json create mode 100644 www/analytics/plugins/Login/lang/en.json create mode 100644 www/analytics/plugins/Login/lang/es.json create mode 100644 www/analytics/plugins/Login/lang/et.json create mode 100644 www/analytics/plugins/Login/lang/eu.json create mode 100644 www/analytics/plugins/Login/lang/fa.json create mode 100644 www/analytics/plugins/Login/lang/fi.json create mode 100644 www/analytics/plugins/Login/lang/fr.json create mode 100644 www/analytics/plugins/Login/lang/gl.json create mode 100644 www/analytics/plugins/Login/lang/he.json create mode 100644 www/analytics/plugins/Login/lang/hi.json create mode 100644 www/analytics/plugins/Login/lang/hu.json create mode 100644 www/analytics/plugins/Login/lang/id.json create mode 100644 www/analytics/plugins/Login/lang/is.json create mode 100644 www/analytics/plugins/Login/lang/it.json create mode 100644 www/analytics/plugins/Login/lang/ja.json create mode 100644 www/analytics/plugins/Login/lang/ka.json create mode 100644 www/analytics/plugins/Login/lang/ko.json create mode 100644 www/analytics/plugins/Login/lang/lt.json create mode 100644 www/analytics/plugins/Login/lang/lv.json create mode 100644 www/analytics/plugins/Login/lang/nb.json create mode 100644 www/analytics/plugins/Login/lang/nl.json create mode 100644 www/analytics/plugins/Login/lang/nn.json create mode 100644 www/analytics/plugins/Login/lang/pl.json create mode 100644 www/analytics/plugins/Login/lang/pt-br.json create mode 100644 www/analytics/plugins/Login/lang/pt.json create mode 100644 www/analytics/plugins/Login/lang/ro.json create mode 100644 www/analytics/plugins/Login/lang/ru.json create mode 100644 www/analytics/plugins/Login/lang/sk.json create mode 100644 www/analytics/plugins/Login/lang/sl.json create mode 100644 www/analytics/plugins/Login/lang/sq.json create mode 100644 www/analytics/plugins/Login/lang/sr.json create mode 100644 www/analytics/plugins/Login/lang/sv.json create mode 100644 www/analytics/plugins/Login/lang/ta.json create mode 100644 www/analytics/plugins/Login/lang/te.json create mode 100644 www/analytics/plugins/Login/lang/th.json create mode 100644 www/analytics/plugins/Login/lang/tl.json create mode 100644 www/analytics/plugins/Login/lang/tr.json create mode 100644 www/analytics/plugins/Login/lang/uk.json create mode 100644 www/analytics/plugins/Login/lang/vi.json create mode 100644 www/analytics/plugins/Login/lang/zh-cn.json create mode 100644 www/analytics/plugins/Login/lang/zh-tw.json delete mode 100644 www/analytics/plugins/Login/stylesheets/login.css create mode 100644 www/analytics/plugins/Login/stylesheets/login.less create mode 100644 www/analytics/plugins/Login/stylesheets/variables.less create mode 100644 www/analytics/plugins/Login/templates/_formErrors.twig create mode 100644 www/analytics/plugins/MobileAppMeasurable/MobileAppMeasurable.php create mode 100644 www/analytics/plugins/MobileAppMeasurable/Type.php create mode 100644 www/analytics/plugins/MobileAppMeasurable/config/test.php create mode 100644 www/analytics/plugins/MobileAppMeasurable/lang/cs.json create mode 100644 www/analytics/plugins/MobileAppMeasurable/lang/de.json create mode 100644 www/analytics/plugins/MobileAppMeasurable/lang/el.json create mode 100644 www/analytics/plugins/MobileAppMeasurable/lang/en.json create mode 100644 www/analytics/plugins/MobileAppMeasurable/lang/es.json create mode 100644 www/analytics/plugins/MobileAppMeasurable/lang/fr.json create mode 100644 www/analytics/plugins/MobileAppMeasurable/lang/hi.json create mode 100644 www/analytics/plugins/MobileAppMeasurable/lang/hu.json create mode 100644 www/analytics/plugins/MobileAppMeasurable/lang/it.json create mode 100644 www/analytics/plugins/MobileAppMeasurable/lang/ja.json create mode 100644 www/analytics/plugins/MobileAppMeasurable/lang/lt.json create mode 100644 www/analytics/plugins/MobileAppMeasurable/lang/nb.json create mode 100644 www/analytics/plugins/MobileAppMeasurable/lang/nl.json create mode 100644 www/analytics/plugins/MobileAppMeasurable/lang/pt-br.json create mode 100644 www/analytics/plugins/MobileAppMeasurable/lang/sk.json create mode 100644 www/analytics/plugins/MobileAppMeasurable/lang/sr.json create mode 100644 www/analytics/plugins/MobileAppMeasurable/lang/sv.json create mode 100644 www/analytics/plugins/MobileAppMeasurable/plugin.json create mode 100644 www/analytics/plugins/MobileMessaging/Menu.php create mode 100644 www/analytics/plugins/MobileMessaging/SMSProvider/Development.php create mode 100644 www/analytics/plugins/MobileMessaging/lang/bg.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/ca.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/cs.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/da.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/de.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/el.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/en.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/es.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/et.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/fa.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/fi.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/fr.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/hi.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/id.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/it.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/ja.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/ko.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/lt.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/nb.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/nl.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/pl.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/pt-br.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/ro.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/ru.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/sk.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/sl.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/sr.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/sv.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/ta.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/th.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/tl.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/tr.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/vi.json create mode 100644 www/analytics/plugins/MobileMessaging/lang/zh-cn.json create mode 100644 www/analytics/plugins/MobileMessaging/templates/macros.twig create mode 100644 www/analytics/plugins/MobileMessaging/templates/userSettings.twig create mode 100644 www/analytics/plugins/Monolog/Formatter/LineMessageFormatter.php create mode 100644 www/analytics/plugins/Monolog/Handler/DatabaseHandler.php create mode 100644 www/analytics/plugins/Monolog/Handler/EchoHandler.php create mode 100644 www/analytics/plugins/Monolog/Handler/FileHandler.php create mode 100644 www/analytics/plugins/Monolog/Handler/WebNotificationHandler.php create mode 100644 www/analytics/plugins/Monolog/Monolog.php create mode 100644 www/analytics/plugins/Monolog/Processor/ClassNameProcessor.php create mode 100644 www/analytics/plugins/Monolog/Processor/ExceptionToTextProcessor.php create mode 100644 www/analytics/plugins/Monolog/Processor/RequestIdProcessor.php create mode 100644 www/analytics/plugins/Monolog/Processor/SprintfProcessor.php create mode 100644 www/analytics/plugins/Monolog/Processor/TokenProcessor.php create mode 100644 www/analytics/plugins/Monolog/config/cli.php create mode 100644 www/analytics/plugins/Monolog/config/config.php create mode 100644 www/analytics/plugins/Monolog/config/tracker.php create mode 100644 www/analytics/plugins/Monolog/plugin.json create mode 100644 www/analytics/plugins/Morpheus/Controller.php create mode 100644 www/analytics/plugins/Morpheus/Menu.php create mode 100755 www/analytics/plugins/Morpheus/fonts/piwik.eot create mode 100755 www/analytics/plugins/Morpheus/fonts/piwik.ttf rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/affix-arrow.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/arr_r.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/background-submit.png (100%) delete mode 100644 www/analytics/plugins/Morpheus/images/cities.png rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/collapsed_arrows.gif (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/dashboard_h_bg.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/data_table_footer_active_item.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/delete.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/download.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/ecommerceAbandonedCart.gif (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/ecommerceOrder.gif (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/email.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/error.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/error_medium.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/event.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/expanded_arrows.gif (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/feed.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/fullscreen.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/html_icon.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/ico_alert.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/ico_info.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/inp_bg.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/li_dbl_gray.gif (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/login-sprite.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/logo-marketplace.png (100%) create mode 100644 www/analytics/plugins/Morpheus/images/minus.png rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/newtab.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/ok.png (100%) delete mode 100644 www/analytics/plugins/Morpheus/images/pause.gif delete mode 100644 www/analytics/plugins/Morpheus/images/pause_disabled.gif rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/paypal_subscribe.gif (100%) delete mode 100644 www/analytics/plugins/Morpheus/images/play.gif delete mode 100644 www/analytics/plugins/Morpheus/images/play_disabled.gif create mode 100644 www/analytics/plugins/Morpheus/images/plus.png rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/plus_blue.png (100%) delete mode 100644 www/analytics/plugins/Morpheus/images/regions.png rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/reload.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/row_evolution.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/row_evolution_hover.png (100%) create mode 100644 www/analytics/plugins/Morpheus/images/search_bg.png create mode 100644 www/analytics/plugins/Morpheus/images/select_arrow.png create mode 100644 www/analytics/plugins/Morpheus/images/signout.png rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/sites_selection.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/smileyprog_0.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/smileyprog_1.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/smileyprog_2.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/smileyprog_3.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/smileyprog_4.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/sort_subtable_asc.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/sort_subtable_asc_light.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/sort_subtable_desc_light.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/star.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/star_empty.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/success_medium.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/video_play.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/warning.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/warning_medium.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/images/warning_small.png (100%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/javascripts/ajaxHelper.js (86%) create mode 100644 www/analytics/plugins/Morpheus/javascripts/layout.js rename www/analytics/plugins/{Zeitgeist => Morpheus}/javascripts/piwikHelper.js (92%) delete mode 100644 www/analytics/plugins/Morpheus/stylesheets/admin.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/base.less create mode 100755 www/analytics/plugins/Morpheus/stylesheets/base/bootstrap.css create mode 100644 www/analytics/plugins/Morpheus/stylesheets/base/colors.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/base/icons.css rename www/analytics/plugins/Morpheus/stylesheets/{ => base}/mixins.less (76%) delete mode 100644 www/analytics/plugins/Morpheus/stylesheets/charts.less delete mode 100644 www/analytics/plugins/Morpheus/stylesheets/colors.less delete mode 100644 www/analytics/plugins/Morpheus/stylesheets/components.less delete mode 100644 www/analytics/plugins/Morpheus/stylesheets/forms.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/general/_admin.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/general/_default.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/general/_form.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/general/_forms.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/general/_jqueryUI.less rename www/analytics/plugins/{Zeitgeist => Morpheus}/stylesheets/general/_misc.less (92%) create mode 100644 www/analytics/plugins/Morpheus/stylesheets/general/_typography.less rename www/analytics/plugins/{Zeitgeist => Morpheus}/stylesheets/general/_utils.less (93%) rename www/analytics/plugins/{Zeitgeist => Morpheus}/stylesheets/ieonly.css (78%) create mode 100644 www/analytics/plugins/Morpheus/stylesheets/main.less delete mode 100644 www/analytics/plugins/Morpheus/stylesheets/map.less delete mode 100644 www/analytics/plugins/Morpheus/stylesheets/popups.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/simple_structure.css create mode 100644 www/analytics/plugins/Morpheus/stylesheets/theme-advanced.less delete mode 100644 www/analytics/plugins/Morpheus/stylesheets/tooltip.less delete mode 100644 www/analytics/plugins/Morpheus/stylesheets/typography.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/ui/_alerts.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/ui/_buttons.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/ui/_cards.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/ui/_charts.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/ui/_code.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/ui/_components.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/ui/_list-group.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/ui/_map.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/ui/_navs.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/ui/_panels.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/ui/_popups.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/ui/_progress-bars.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/ui/_tables.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/ui/_tooltip.less rename www/analytics/plugins/{Zeitgeist/stylesheets/ui => Morpheus/stylesheets/uibase}/_dataTable.less (100%) create mode 100644 www/analytics/plugins/Morpheus/stylesheets/uibase/_header.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/uibase/_headerMessage.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/uibase/_languageSelect.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/uibase/_loading.less create mode 100644 www/analytics/plugins/Morpheus/stylesheets/uibase/_periodSelect.less rename www/analytics/plugins/{Zeitgeist => Morpheus}/templates/_iframeBuster.twig (100%) create mode 100644 www/analytics/plugins/Morpheus/templates/_jsCssIncludes.twig create mode 100644 www/analytics/plugins/Morpheus/templates/_jsGlobalVariables.twig rename www/analytics/plugins/{Zeitgeist => Morpheus}/templates/_sparklineFooter.twig (100%) create mode 100644 www/analytics/plugins/Morpheus/templates/admin.twig create mode 100644 www/analytics/plugins/Morpheus/templates/ajaxMacros.twig create mode 100644 www/analytics/plugins/Morpheus/templates/dashboard.twig create mode 100644 www/analytics/plugins/Morpheus/templates/demo.twig rename www/analytics/plugins/{Zeitgeist => Morpheus}/templates/empty.twig (100%) create mode 100644 www/analytics/plugins/Morpheus/templates/genericForm.twig create mode 100644 www/analytics/plugins/Morpheus/templates/javascriptCode.tpl create mode 100644 www/analytics/plugins/Morpheus/templates/layout.twig create mode 100644 www/analytics/plugins/Morpheus/templates/macros.twig create mode 100644 www/analytics/plugins/Morpheus/templates/maintenance.tpl create mode 100644 www/analytics/plugins/Morpheus/templates/settingsMacros.twig create mode 100644 www/analytics/plugins/Morpheus/templates/simpleLayoutFooter.tpl create mode 100644 www/analytics/plugins/Morpheus/templates/simpleLayoutHeader.tpl create mode 100644 www/analytics/plugins/Morpheus/templates/user.twig create mode 100644 www/analytics/plugins/MultiSites/Columns/Metrics/EcommerceOnlyEvolutionMetric.php create mode 100644 www/analytics/plugins/MultiSites/Columns/Website.php create mode 100644 www/analytics/plugins/MultiSites/Dashboard.php create mode 100644 www/analytics/plugins/MultiSites/DataTable/Filter/NestedSitesLimiter.php create mode 100644 www/analytics/plugins/MultiSites/Menu.php create mode 100644 www/analytics/plugins/MultiSites/Reports/Base.php create mode 100644 www/analytics/plugins/MultiSites/Reports/GetAll.php create mode 100644 www/analytics/plugins/MultiSites/Reports/GetOne.php delete mode 100644 www/analytics/plugins/MultiSites/angularjs/dashboard/dashboard-controller.js delete mode 100644 www/analytics/plugins/MultiSites/angularjs/dashboard/dashboard-directive.js delete mode 100644 www/analytics/plugins/MultiSites/angularjs/dashboard/dashboard-filter.js delete mode 100644 www/analytics/plugins/MultiSites/angularjs/dashboard/dashboard-model.js create mode 100644 www/analytics/plugins/MultiSites/angularjs/dashboard/dashboard-model.service.js create mode 100644 www/analytics/plugins/MultiSites/angularjs/dashboard/dashboard.controller.js create mode 100644 www/analytics/plugins/MultiSites/angularjs/dashboard/dashboard.directive.html create mode 100644 www/analytics/plugins/MultiSites/angularjs/dashboard/dashboard.directive.js create mode 100644 www/analytics/plugins/MultiSites/angularjs/dashboard/dashboard.directive.less delete mode 100644 www/analytics/plugins/MultiSites/angularjs/dashboard/dashboard.html delete mode 100644 www/analytics/plugins/MultiSites/angularjs/dashboard/dashboard.less delete mode 100644 www/analytics/plugins/MultiSites/angularjs/site/site-directive.js create mode 100644 www/analytics/plugins/MultiSites/angularjs/site/site.controller.js create mode 100644 www/analytics/plugins/MultiSites/angularjs/site/site.directive.html create mode 100644 www/analytics/plugins/MultiSites/angularjs/site/site.directive.js delete mode 100644 www/analytics/plugins/MultiSites/angularjs/site/site.html delete mode 100644 www/analytics/plugins/MultiSites/images/link.gif delete mode 100644 www/analytics/plugins/MultiSites/images/loading-blue.gif create mode 100644 www/analytics/plugins/MultiSites/lang/ar.json create mode 100644 www/analytics/plugins/MultiSites/lang/be.json create mode 100644 www/analytics/plugins/MultiSites/lang/bg.json create mode 100644 www/analytics/plugins/MultiSites/lang/ca.json create mode 100644 www/analytics/plugins/MultiSites/lang/cs.json create mode 100644 www/analytics/plugins/MultiSites/lang/da.json create mode 100644 www/analytics/plugins/MultiSites/lang/de.json create mode 100644 www/analytics/plugins/MultiSites/lang/el.json create mode 100644 www/analytics/plugins/MultiSites/lang/en.json create mode 100644 www/analytics/plugins/MultiSites/lang/es.json create mode 100644 www/analytics/plugins/MultiSites/lang/et.json create mode 100644 www/analytics/plugins/MultiSites/lang/fa.json create mode 100644 www/analytics/plugins/MultiSites/lang/fi.json create mode 100644 www/analytics/plugins/MultiSites/lang/fr.json create mode 100644 www/analytics/plugins/MultiSites/lang/hi.json create mode 100644 www/analytics/plugins/MultiSites/lang/hu.json create mode 100644 www/analytics/plugins/MultiSites/lang/id.json create mode 100644 www/analytics/plugins/MultiSites/lang/it.json create mode 100644 www/analytics/plugins/MultiSites/lang/ja.json create mode 100644 www/analytics/plugins/MultiSites/lang/ka.json create mode 100644 www/analytics/plugins/MultiSites/lang/ko.json create mode 100644 www/analytics/plugins/MultiSites/lang/lt.json create mode 100644 www/analytics/plugins/MultiSites/lang/nb.json create mode 100644 www/analytics/plugins/MultiSites/lang/nl.json create mode 100644 www/analytics/plugins/MultiSites/lang/nn.json create mode 100644 www/analytics/plugins/MultiSites/lang/pl.json create mode 100644 www/analytics/plugins/MultiSites/lang/pt-br.json create mode 100644 www/analytics/plugins/MultiSites/lang/pt.json create mode 100644 www/analytics/plugins/MultiSites/lang/ro.json create mode 100644 www/analytics/plugins/MultiSites/lang/ru.json create mode 100644 www/analytics/plugins/MultiSites/lang/sk.json create mode 100644 www/analytics/plugins/MultiSites/lang/sl.json create mode 100644 www/analytics/plugins/MultiSites/lang/sq.json create mode 100644 www/analytics/plugins/MultiSites/lang/sr.json create mode 100644 www/analytics/plugins/MultiSites/lang/sv.json create mode 100644 www/analytics/plugins/MultiSites/lang/ta.json create mode 100644 www/analytics/plugins/MultiSites/lang/th.json create mode 100644 www/analytics/plugins/MultiSites/lang/tl.json create mode 100644 www/analytics/plugins/MultiSites/lang/uk.json create mode 100644 www/analytics/plugins/MultiSites/lang/vi.json create mode 100644 www/analytics/plugins/MultiSites/lang/zh-cn.json create mode 100644 www/analytics/plugins/MultiSites/lang/zh-tw.json create mode 100644 www/analytics/plugins/MultiSites/plugin.json delete mode 100644 www/analytics/plugins/Overlay/client/linktags.eps delete mode 100644 www/analytics/plugins/Overlay/client/linktags.psd create mode 100644 www/analytics/plugins/Overlay/config/ui-test.php delete mode 100644 www/analytics/plugins/Overlay/images/info.png create mode 100644 www/analytics/plugins/Overlay/lang/ar.json create mode 100644 www/analytics/plugins/Overlay/lang/be.json create mode 100644 www/analytics/plugins/Overlay/lang/bg.json create mode 100644 www/analytics/plugins/Overlay/lang/ca.json create mode 100644 www/analytics/plugins/Overlay/lang/cs.json create mode 100644 www/analytics/plugins/Overlay/lang/da.json create mode 100644 www/analytics/plugins/Overlay/lang/de.json create mode 100644 www/analytics/plugins/Overlay/lang/el.json create mode 100644 www/analytics/plugins/Overlay/lang/en.json create mode 100644 www/analytics/plugins/Overlay/lang/es.json create mode 100644 www/analytics/plugins/Overlay/lang/et.json create mode 100644 www/analytics/plugins/Overlay/lang/fa.json create mode 100644 www/analytics/plugins/Overlay/lang/fi.json create mode 100644 www/analytics/plugins/Overlay/lang/fr.json create mode 100644 www/analytics/plugins/Overlay/lang/he.json create mode 100644 www/analytics/plugins/Overlay/lang/hi.json create mode 100644 www/analytics/plugins/Overlay/lang/hr.json create mode 100644 www/analytics/plugins/Overlay/lang/hu.json create mode 100644 www/analytics/plugins/Overlay/lang/id.json create mode 100644 www/analytics/plugins/Overlay/lang/is.json create mode 100644 www/analytics/plugins/Overlay/lang/it.json create mode 100644 www/analytics/plugins/Overlay/lang/ja.json create mode 100644 www/analytics/plugins/Overlay/lang/ka.json create mode 100644 www/analytics/plugins/Overlay/lang/ko.json create mode 100644 www/analytics/plugins/Overlay/lang/lt.json create mode 100644 www/analytics/plugins/Overlay/lang/lv.json create mode 100644 www/analytics/plugins/Overlay/lang/nb.json create mode 100644 www/analytics/plugins/Overlay/lang/nl.json create mode 100644 www/analytics/plugins/Overlay/lang/nn.json create mode 100644 www/analytics/plugins/Overlay/lang/pl.json create mode 100644 www/analytics/plugins/Overlay/lang/pt-br.json create mode 100644 www/analytics/plugins/Overlay/lang/pt.json create mode 100644 www/analytics/plugins/Overlay/lang/ro.json create mode 100644 www/analytics/plugins/Overlay/lang/ru.json create mode 100644 www/analytics/plugins/Overlay/lang/sk.json create mode 100644 www/analytics/plugins/Overlay/lang/sl.json create mode 100644 www/analytics/plugins/Overlay/lang/sq.json create mode 100644 www/analytics/plugins/Overlay/lang/sr.json create mode 100644 www/analytics/plugins/Overlay/lang/sv.json create mode 100644 www/analytics/plugins/Overlay/lang/ta.json create mode 100644 www/analytics/plugins/Overlay/lang/te.json create mode 100644 www/analytics/plugins/Overlay/lang/th.json create mode 100644 www/analytics/plugins/Overlay/lang/tl.json create mode 100644 www/analytics/plugins/Overlay/lang/tr.json create mode 100644 www/analytics/plugins/Overlay/lang/uk.json create mode 100644 www/analytics/plugins/Overlay/lang/vi.json create mode 100644 www/analytics/plugins/Overlay/lang/zh-cn.json create mode 100644 www/analytics/plugins/Overlay/lang/zh-tw.json create mode 100644 www/analytics/plugins/Overlay/templates/startOverlaySession.twig create mode 100644 www/analytics/plugins/PiwikPro/PiwikPro.php create mode 100644 www/analytics/plugins/PiwikPro/Promo.php create mode 100644 www/analytics/plugins/PiwikPro/Widgets.php create mode 100644 www/analytics/plugins/PiwikPro/config/test.php create mode 100644 www/analytics/plugins/PiwikPro/images/promo.png create mode 100644 www/analytics/plugins/PiwikPro/lang/en.json create mode 100644 www/analytics/plugins/PiwikPro/plugin.json create mode 100644 www/analytics/plugins/PiwikPro/stylesheets/widget.less create mode 100644 www/analytics/plugins/PiwikPro/templates/promoPiwikProWidget.twig create mode 100644 www/analytics/plugins/PrivacyManager/Menu.php create mode 100644 www/analytics/plugins/PrivacyManager/Tasks.php create mode 100644 www/analytics/plugins/PrivacyManager/lang/ar.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/be.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/bg.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/ca.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/cs.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/da.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/de.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/el.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/en.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/es.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/et.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/fa.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/fi.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/fr.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/he.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/hi.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/hr.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/hu.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/id.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/is.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/it.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/ja.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/ka.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/ko.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/lt.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/lv.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/nb.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/nl.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/nn.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/pl.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/pt-br.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/pt.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/ro.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/ru.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/sk.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/sl.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/sq.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/sr.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/sv.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/te.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/th.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/tl.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/tr.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/uk.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/vi.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/zh-cn.json create mode 100644 www/analytics/plugins/PrivacyManager/lang/zh-tw.json create mode 100644 www/analytics/plugins/Provider/Columns/Provider.php create mode 100644 www/analytics/plugins/Provider/Reports/GetProvider.php create mode 100644 www/analytics/plugins/Provider/Visitor.php create mode 100644 www/analytics/plugins/Provider/lang/am.json create mode 100644 www/analytics/plugins/Provider/lang/ar.json create mode 100644 www/analytics/plugins/Provider/lang/be.json create mode 100644 www/analytics/plugins/Provider/lang/bg.json create mode 100644 www/analytics/plugins/Provider/lang/ca.json create mode 100644 www/analytics/plugins/Provider/lang/cs.json create mode 100644 www/analytics/plugins/Provider/lang/da.json create mode 100644 www/analytics/plugins/Provider/lang/de.json create mode 100644 www/analytics/plugins/Provider/lang/el.json create mode 100644 www/analytics/plugins/Provider/lang/en.json create mode 100644 www/analytics/plugins/Provider/lang/es.json create mode 100644 www/analytics/plugins/Provider/lang/et.json create mode 100644 www/analytics/plugins/Provider/lang/eu.json create mode 100644 www/analytics/plugins/Provider/lang/fa.json create mode 100644 www/analytics/plugins/Provider/lang/fi.json create mode 100644 www/analytics/plugins/Provider/lang/fr.json create mode 100644 www/analytics/plugins/Provider/lang/gl.json create mode 100644 www/analytics/plugins/Provider/lang/hi.json create mode 100644 www/analytics/plugins/Provider/lang/hu.json create mode 100644 www/analytics/plugins/Provider/lang/id.json create mode 100644 www/analytics/plugins/Provider/lang/is.json create mode 100644 www/analytics/plugins/Provider/lang/it.json create mode 100644 www/analytics/plugins/Provider/lang/ja.json create mode 100644 www/analytics/plugins/Provider/lang/ka.json create mode 100644 www/analytics/plugins/Provider/lang/ko.json create mode 100644 www/analytics/plugins/Provider/lang/lt.json create mode 100644 www/analytics/plugins/Provider/lang/lv.json create mode 100644 www/analytics/plugins/Provider/lang/nb.json create mode 100644 www/analytics/plugins/Provider/lang/nl.json create mode 100644 www/analytics/plugins/Provider/lang/pl.json create mode 100644 www/analytics/plugins/Provider/lang/pt-br.json create mode 100644 www/analytics/plugins/Provider/lang/pt.json create mode 100644 www/analytics/plugins/Provider/lang/ro.json create mode 100644 www/analytics/plugins/Provider/lang/ru.json create mode 100644 www/analytics/plugins/Provider/lang/sk.json create mode 100644 www/analytics/plugins/Provider/lang/sl.json create mode 100644 www/analytics/plugins/Provider/lang/sq.json create mode 100644 www/analytics/plugins/Provider/lang/sr.json create mode 100644 www/analytics/plugins/Provider/lang/sv.json create mode 100644 www/analytics/plugins/Provider/lang/ta.json create mode 100644 www/analytics/plugins/Provider/lang/th.json create mode 100644 www/analytics/plugins/Provider/lang/tl.json create mode 100644 www/analytics/plugins/Provider/lang/tr.json create mode 100644 www/analytics/plugins/Provider/lang/uk.json create mode 100644 www/analytics/plugins/Provider/lang/vi.json create mode 100644 www/analytics/plugins/Provider/lang/zh-cn.json create mode 100644 www/analytics/plugins/Provider/lang/zh-tw.json create mode 100644 www/analytics/plugins/Proxy/plugin.json create mode 100644 www/analytics/plugins/Referrers/Columns/Base.php create mode 100644 www/analytics/plugins/Referrers/Columns/Campaign.php create mode 100644 www/analytics/plugins/Referrers/Columns/Keyword.php create mode 100644 www/analytics/plugins/Referrers/Columns/Referrer.php create mode 100644 www/analytics/plugins/Referrers/Columns/ReferrerName.php create mode 100644 www/analytics/plugins/Referrers/Columns/ReferrerType.php create mode 100644 www/analytics/plugins/Referrers/Columns/ReferrerUrl.php create mode 100644 www/analytics/plugins/Referrers/Columns/SearchEngine.php create mode 100644 www/analytics/plugins/Referrers/Columns/SocialNetwork.php create mode 100644 www/analytics/plugins/Referrers/Columns/Website.php create mode 100644 www/analytics/plugins/Referrers/Columns/WebsitePage.php create mode 100644 www/analytics/plugins/Referrers/DataTable/Filter/KeywordNotDefined.php create mode 100644 www/analytics/plugins/Referrers/DataTable/Filter/KeywordsFromSearchEngineId.php create mode 100644 www/analytics/plugins/Referrers/DataTable/Filter/SearchEnginesFromKeywordId.php create mode 100644 www/analytics/plugins/Referrers/DataTable/Filter/SetGetReferrerTypeSubtables.php create mode 100644 www/analytics/plugins/Referrers/DataTable/Filter/UrlsForSocial.php create mode 100644 www/analytics/plugins/Referrers/DataTable/Filter/UrlsFromWebsiteId.php create mode 100644 www/analytics/plugins/Referrers/Menu.php create mode 100644 www/analytics/plugins/Referrers/Reports/Base.php create mode 100644 www/analytics/plugins/Referrers/Reports/GetAll.php create mode 100644 www/analytics/plugins/Referrers/Reports/GetCampaigns.php create mode 100644 www/analytics/plugins/Referrers/Reports/GetKeywords.php create mode 100644 www/analytics/plugins/Referrers/Reports/GetKeywordsFromCampaignId.php create mode 100644 www/analytics/plugins/Referrers/Reports/GetKeywordsFromSearchEngineId.php create mode 100644 www/analytics/plugins/Referrers/Reports/GetReferrerType.php create mode 100644 www/analytics/plugins/Referrers/Reports/GetSearchEngines.php create mode 100644 www/analytics/plugins/Referrers/Reports/GetSearchEnginesFromKeywordId.php create mode 100644 www/analytics/plugins/Referrers/Reports/GetSocials.php create mode 100644 www/analytics/plugins/Referrers/Reports/GetUrlsForSocial.php create mode 100644 www/analytics/plugins/Referrers/Reports/GetUrlsFromWebsiteId.php create mode 100644 www/analytics/plugins/Referrers/Reports/GetWebsites.php create mode 100644 www/analytics/plugins/Referrers/SearchEngine.php create mode 100644 www/analytics/plugins/Referrers/Segment.php create mode 100644 www/analytics/plugins/Referrers/Social.php create mode 100644 www/analytics/plugins/Referrers/Tasks.php create mode 100644 www/analytics/plugins/Referrers/Visitor.php create mode 100644 www/analytics/plugins/Referrers/Widgets.php create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/chercherfr.aguea.com.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/extern.peoplecheck.de.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/image.search.yahoo.co.jp.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/images.search.biglobe.ne.jp.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/k9safesearch.com.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/kwzf.net.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/m.sm.cn.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/search.auone.jp.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/search.fooooo.com.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/search.genieo.com.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/search.seesaa.jp.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/search.yahoo.co.jp.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/sp-image.search.auone.jp.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/videa.seznam.cz.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/video.search.yahoo.co.jp.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/video.so-net.ne.jp.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/videosearch.nifty.com.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/www.findhurtig.dk.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/www.haosou.com.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/www.kensaq.com.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/www.lookany.com.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/www.qwant.com.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/www.sm.de.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/www.so-net.ne.jp.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/www.sputnik.ru.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/www.toppreise.ch.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/www.woopie.jp.png create mode 100644 www/analytics/plugins/Referrers/images/searchEngines/www.zxuso.com.png create mode 100644 www/analytics/plugins/Referrers/images/socials/dribbble.com.png create mode 100644 www/analytics/plugins/Referrers/lang/am.json create mode 100644 www/analytics/plugins/Referrers/lang/ar.json create mode 100644 www/analytics/plugins/Referrers/lang/be.json create mode 100644 www/analytics/plugins/Referrers/lang/bg.json create mode 100644 www/analytics/plugins/Referrers/lang/bn.json create mode 100644 www/analytics/plugins/Referrers/lang/bs.json create mode 100644 www/analytics/plugins/Referrers/lang/ca.json create mode 100644 www/analytics/plugins/Referrers/lang/cs.json create mode 100644 www/analytics/plugins/Referrers/lang/cy.json create mode 100644 www/analytics/plugins/Referrers/lang/da.json create mode 100644 www/analytics/plugins/Referrers/lang/de.json create mode 100644 www/analytics/plugins/Referrers/lang/el.json create mode 100644 www/analytics/plugins/Referrers/lang/en.json create mode 100644 www/analytics/plugins/Referrers/lang/es.json create mode 100644 www/analytics/plugins/Referrers/lang/et.json create mode 100644 www/analytics/plugins/Referrers/lang/eu.json create mode 100644 www/analytics/plugins/Referrers/lang/fa.json create mode 100644 www/analytics/plugins/Referrers/lang/fi.json create mode 100644 www/analytics/plugins/Referrers/lang/fr.json create mode 100644 www/analytics/plugins/Referrers/lang/gl.json create mode 100644 www/analytics/plugins/Referrers/lang/he.json create mode 100644 www/analytics/plugins/Referrers/lang/hi.json create mode 100644 www/analytics/plugins/Referrers/lang/hr.json create mode 100644 www/analytics/plugins/Referrers/lang/hu.json create mode 100644 www/analytics/plugins/Referrers/lang/id.json create mode 100644 www/analytics/plugins/Referrers/lang/is.json create mode 100644 www/analytics/plugins/Referrers/lang/it.json create mode 100644 www/analytics/plugins/Referrers/lang/ja.json create mode 100644 www/analytics/plugins/Referrers/lang/ka.json create mode 100644 www/analytics/plugins/Referrers/lang/ko.json create mode 100644 www/analytics/plugins/Referrers/lang/lt.json create mode 100644 www/analytics/plugins/Referrers/lang/lv.json create mode 100644 www/analytics/plugins/Referrers/lang/nb.json create mode 100644 www/analytics/plugins/Referrers/lang/nl.json create mode 100644 www/analytics/plugins/Referrers/lang/nn.json create mode 100644 www/analytics/plugins/Referrers/lang/pl.json create mode 100644 www/analytics/plugins/Referrers/lang/pt-br.json create mode 100644 www/analytics/plugins/Referrers/lang/pt.json create mode 100644 www/analytics/plugins/Referrers/lang/ro.json create mode 100644 www/analytics/plugins/Referrers/lang/ru.json create mode 100644 www/analytics/plugins/Referrers/lang/sk.json create mode 100644 www/analytics/plugins/Referrers/lang/sl.json create mode 100644 www/analytics/plugins/Referrers/lang/sq.json create mode 100644 www/analytics/plugins/Referrers/lang/sr.json create mode 100644 www/analytics/plugins/Referrers/lang/sv.json create mode 100644 www/analytics/plugins/Referrers/lang/ta.json create mode 100644 www/analytics/plugins/Referrers/lang/te.json create mode 100644 www/analytics/plugins/Referrers/lang/th.json create mode 100644 www/analytics/plugins/Referrers/lang/tl.json create mode 100644 www/analytics/plugins/Referrers/lang/tr.json create mode 100644 www/analytics/plugins/Referrers/lang/uk.json create mode 100644 www/analytics/plugins/Referrers/lang/vi.json create mode 100644 www/analytics/plugins/Referrers/lang/zh-cn.json create mode 100644 www/analytics/plugins/Referrers/lang/zh-tw.json create mode 100644 www/analytics/plugins/Referrers/templates/allReferrers.twig create mode 100644 www/analytics/plugins/Resolution/API.php create mode 100644 www/analytics/plugins/Resolution/Archiver.php create mode 100644 www/analytics/plugins/Resolution/Columns/Configuration.php create mode 100644 www/analytics/plugins/Resolution/Columns/Resolution.php create mode 100644 www/analytics/plugins/Resolution/Reports/Base.php create mode 100644 www/analytics/plugins/Resolution/Reports/GetConfiguration.php create mode 100644 www/analytics/plugins/Resolution/Reports/GetResolution.php create mode 100644 www/analytics/plugins/Resolution/Resolution.php create mode 100644 www/analytics/plugins/Resolution/Segment.php create mode 100644 www/analytics/plugins/Resolution/Visitor.php create mode 100644 www/analytics/plugins/Resolution/functions.php create mode 100644 www/analytics/plugins/Resolution/lang/am.json create mode 100644 www/analytics/plugins/Resolution/lang/ar.json create mode 100644 www/analytics/plugins/Resolution/lang/be.json create mode 100644 www/analytics/plugins/Resolution/lang/bg.json create mode 100644 www/analytics/plugins/Resolution/lang/ca.json create mode 100644 www/analytics/plugins/Resolution/lang/cs.json create mode 100644 www/analytics/plugins/Resolution/lang/da.json create mode 100644 www/analytics/plugins/Resolution/lang/de.json create mode 100644 www/analytics/plugins/Resolution/lang/el.json create mode 100644 www/analytics/plugins/Resolution/lang/en.json create mode 100644 www/analytics/plugins/Resolution/lang/es.json create mode 100644 www/analytics/plugins/Resolution/lang/et.json create mode 100644 www/analytics/plugins/Resolution/lang/eu.json create mode 100644 www/analytics/plugins/Resolution/lang/fa.json create mode 100644 www/analytics/plugins/Resolution/lang/fi.json create mode 100644 www/analytics/plugins/Resolution/lang/fr.json create mode 100644 www/analytics/plugins/Resolution/lang/gl.json create mode 100644 www/analytics/plugins/Resolution/lang/he.json create mode 100644 www/analytics/plugins/Resolution/lang/hi.json create mode 100644 www/analytics/plugins/Resolution/lang/hr.json create mode 100644 www/analytics/plugins/Resolution/lang/hu.json create mode 100644 www/analytics/plugins/Resolution/lang/id.json create mode 100644 www/analytics/plugins/Resolution/lang/is.json create mode 100644 www/analytics/plugins/Resolution/lang/it.json create mode 100644 www/analytics/plugins/Resolution/lang/ja.json create mode 100644 www/analytics/plugins/Resolution/lang/ka.json create mode 100644 www/analytics/plugins/Resolution/lang/ko.json create mode 100644 www/analytics/plugins/Resolution/lang/lt.json create mode 100644 www/analytics/plugins/Resolution/lang/lv.json create mode 100644 www/analytics/plugins/Resolution/lang/nb.json create mode 100644 www/analytics/plugins/Resolution/lang/nl.json create mode 100644 www/analytics/plugins/Resolution/lang/nn.json create mode 100644 www/analytics/plugins/Resolution/lang/pl.json create mode 100644 www/analytics/plugins/Resolution/lang/pt-br.json create mode 100644 www/analytics/plugins/Resolution/lang/pt.json create mode 100644 www/analytics/plugins/Resolution/lang/ro.json create mode 100644 www/analytics/plugins/Resolution/lang/ru.json create mode 100644 www/analytics/plugins/Resolution/lang/sk.json create mode 100644 www/analytics/plugins/Resolution/lang/sl.json create mode 100644 www/analytics/plugins/Resolution/lang/sq.json create mode 100644 www/analytics/plugins/Resolution/lang/sr.json create mode 100644 www/analytics/plugins/Resolution/lang/sv.json create mode 100644 www/analytics/plugins/Resolution/lang/te.json create mode 100644 www/analytics/plugins/Resolution/lang/th.json create mode 100644 www/analytics/plugins/Resolution/lang/tl.json create mode 100644 www/analytics/plugins/Resolution/lang/tr.json create mode 100644 www/analytics/plugins/Resolution/lang/uk.json create mode 100644 www/analytics/plugins/Resolution/lang/vi.json create mode 100644 www/analytics/plugins/Resolution/lang/zh-cn.json create mode 100644 www/analytics/plugins/Resolution/lang/zh-tw.json delete mode 100644 www/analytics/plugins/SEO/Controller.php delete mode 100644 www/analytics/plugins/SEO/MajesticClient.php create mode 100644 www/analytics/plugins/SEO/Metric/Aggregator.php create mode 100644 www/analytics/plugins/SEO/Metric/Alexa.php create mode 100644 www/analytics/plugins/SEO/Metric/Bing.php create mode 100644 www/analytics/plugins/SEO/Metric/Dmoz.php create mode 100644 www/analytics/plugins/SEO/Metric/DomainAge.php create mode 100644 www/analytics/plugins/SEO/Metric/Google.php create mode 100644 www/analytics/plugins/SEO/Metric/Metric.php create mode 100644 www/analytics/plugins/SEO/Metric/MetricsProvider.php create mode 100644 www/analytics/plugins/SEO/Metric/ProviderCache.php delete mode 100644 www/analytics/plugins/SEO/RankChecker.php create mode 100644 www/analytics/plugins/SEO/Widgets.php create mode 100644 www/analytics/plugins/SEO/lang/ar.json create mode 100644 www/analytics/plugins/SEO/lang/be.json create mode 100644 www/analytics/plugins/SEO/lang/bg.json create mode 100644 www/analytics/plugins/SEO/lang/ca.json create mode 100644 www/analytics/plugins/SEO/lang/cs.json create mode 100644 www/analytics/plugins/SEO/lang/da.json create mode 100644 www/analytics/plugins/SEO/lang/de.json create mode 100644 www/analytics/plugins/SEO/lang/el.json create mode 100644 www/analytics/plugins/SEO/lang/en.json create mode 100644 www/analytics/plugins/SEO/lang/es.json create mode 100644 www/analytics/plugins/SEO/lang/et.json create mode 100644 www/analytics/plugins/SEO/lang/fa.json create mode 100644 www/analytics/plugins/SEO/lang/fi.json create mode 100644 www/analytics/plugins/SEO/lang/fr.json create mode 100644 www/analytics/plugins/SEO/lang/he.json create mode 100644 www/analytics/plugins/SEO/lang/hi.json create mode 100644 www/analytics/plugins/SEO/lang/hu.json create mode 100644 www/analytics/plugins/SEO/lang/id.json create mode 100644 www/analytics/plugins/SEO/lang/it.json create mode 100644 www/analytics/plugins/SEO/lang/ja.json create mode 100644 www/analytics/plugins/SEO/lang/ka.json create mode 100644 www/analytics/plugins/SEO/lang/ko.json create mode 100644 www/analytics/plugins/SEO/lang/lt.json create mode 100644 www/analytics/plugins/SEO/lang/lv.json create mode 100644 www/analytics/plugins/SEO/lang/nb.json create mode 100644 www/analytics/plugins/SEO/lang/nl.json create mode 100644 www/analytics/plugins/SEO/lang/pl.json create mode 100644 www/analytics/plugins/SEO/lang/pt-br.json create mode 100644 www/analytics/plugins/SEO/lang/pt.json create mode 100644 www/analytics/plugins/SEO/lang/ro.json create mode 100644 www/analytics/plugins/SEO/lang/ru.json create mode 100644 www/analytics/plugins/SEO/lang/sk.json create mode 100644 www/analytics/plugins/SEO/lang/sl.json create mode 100644 www/analytics/plugins/SEO/lang/sq.json create mode 100644 www/analytics/plugins/SEO/lang/sr.json create mode 100644 www/analytics/plugins/SEO/lang/sv.json create mode 100644 www/analytics/plugins/SEO/lang/te.json create mode 100644 www/analytics/plugins/SEO/lang/th.json create mode 100644 www/analytics/plugins/SEO/lang/tl.json create mode 100644 www/analytics/plugins/SEO/lang/tr.json create mode 100644 www/analytics/plugins/SEO/lang/uk.json create mode 100644 www/analytics/plugins/SEO/lang/vi.json create mode 100644 www/analytics/plugins/SEO/lang/zh-cn.json create mode 100644 www/analytics/plugins/SEO/lang/zh-tw.json create mode 100644 www/analytics/plugins/ScheduledReports/Menu.php create mode 100644 www/analytics/plugins/ScheduledReports/Model.php create mode 100644 www/analytics/plugins/ScheduledReports/Tasks.php create mode 100644 www/analytics/plugins/ScheduledReports/lang/ar.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/be.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/bg.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/ca.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/cs.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/da.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/de.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/el.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/en.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/es.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/et.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/fa.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/fi.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/fr.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/he.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/hi.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/hu.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/id.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/is.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/it.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/ja.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/ka.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/ko.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/lt.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/lv.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/nb.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/nl.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/pl.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/pt-br.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/pt.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/ro.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/ru.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/sk.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/sl.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/sq.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/sr.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/sv.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/ta.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/te.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/th.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/tl.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/tr.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/uk.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/vi.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/zh-cn.json create mode 100644 www/analytics/plugins/ScheduledReports/lang/zh-tw.json create mode 100644 www/analytics/plugins/ScheduledReports/stylesheets/scheduledreports.less delete mode 100644 www/analytics/plugins/SegmentEditor/Controller.php create mode 100644 www/analytics/plugins/SegmentEditor/SegmentFormatter.php create mode 100644 www/analytics/plugins/SegmentEditor/SegmentList.php create mode 100644 www/analytics/plugins/SegmentEditor/SegmentQueryDecorator.php create mode 100644 www/analytics/plugins/SegmentEditor/Services/StoredSegmentService.php create mode 100644 www/analytics/plugins/SegmentEditor/config/config.php create mode 100644 www/analytics/plugins/SegmentEditor/images/edit_segment.png create mode 100644 www/analytics/plugins/SegmentEditor/lang/bg.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/cs.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/da.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/de.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/el.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/en.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/es.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/et.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/fa.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/fi.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/fr.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/he.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/hi.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/id.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/it.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/ja.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/lt.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/nb.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/nl.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/pl.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/pt-br.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/ro.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/ru.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/sk.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/sl.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/sr.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/sv.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/tl.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/tr.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/vi.json create mode 100644 www/analytics/plugins/SegmentEditor/lang/zh-cn.json create mode 100644 www/analytics/plugins/SitesManager/Menu.php create mode 100644 www/analytics/plugins/SitesManager/Model.php create mode 100644 www/analytics/plugins/SitesManager/SiteUrls.php create mode 100644 www/analytics/plugins/SitesManager/Tracker/SitesManagerRequestProcessor.php create mode 100644 www/analytics/plugins/SitesManager/angularjs/sites-manager/api-core.service.js create mode 100644 www/analytics/plugins/SitesManager/angularjs/sites-manager/api-helper.service.js create mode 100644 www/analytics/plugins/SitesManager/angularjs/sites-manager/api-site.service.js create mode 100644 www/analytics/plugins/SitesManager/angularjs/sites-manager/edit-trigger.directive.js create mode 100644 www/analytics/plugins/SitesManager/angularjs/sites-manager/multiline-field.directive.html create mode 100644 www/analytics/plugins/SitesManager/angularjs/sites-manager/multiline-field.directive.js create mode 100644 www/analytics/plugins/SitesManager/angularjs/sites-manager/sites-manager-admin-sites-model.js create mode 100644 www/analytics/plugins/SitesManager/angularjs/sites-manager/sites-manager-site.controller.js create mode 100644 www/analytics/plugins/SitesManager/angularjs/sites-manager/sites-manager-type-model.js create mode 100644 www/analytics/plugins/SitesManager/angularjs/sites-manager/sites-manager.controller.js delete mode 100644 www/analytics/plugins/SitesManager/javascripts/SitesManager.js create mode 100644 www/analytics/plugins/SitesManager/lang/am.json create mode 100644 www/analytics/plugins/SitesManager/lang/ar.json create mode 100644 www/analytics/plugins/SitesManager/lang/be.json create mode 100644 www/analytics/plugins/SitesManager/lang/bg.json create mode 100644 www/analytics/plugins/SitesManager/lang/bn.json create mode 100644 www/analytics/plugins/SitesManager/lang/bs.json create mode 100644 www/analytics/plugins/SitesManager/lang/ca.json create mode 100644 www/analytics/plugins/SitesManager/lang/cs.json create mode 100644 www/analytics/plugins/SitesManager/lang/cy.json create mode 100644 www/analytics/plugins/SitesManager/lang/da.json create mode 100644 www/analytics/plugins/SitesManager/lang/de.json create mode 100644 www/analytics/plugins/SitesManager/lang/el.json create mode 100644 www/analytics/plugins/SitesManager/lang/en.json create mode 100644 www/analytics/plugins/SitesManager/lang/es.json create mode 100644 www/analytics/plugins/SitesManager/lang/et.json create mode 100644 www/analytics/plugins/SitesManager/lang/eu.json create mode 100644 www/analytics/plugins/SitesManager/lang/fa.json create mode 100644 www/analytics/plugins/SitesManager/lang/fi.json create mode 100644 www/analytics/plugins/SitesManager/lang/fr.json create mode 100644 www/analytics/plugins/SitesManager/lang/gl.json create mode 100644 www/analytics/plugins/SitesManager/lang/he.json create mode 100644 www/analytics/plugins/SitesManager/lang/hi.json create mode 100644 www/analytics/plugins/SitesManager/lang/hr.json create mode 100644 www/analytics/plugins/SitesManager/lang/hu.json create mode 100644 www/analytics/plugins/SitesManager/lang/id.json create mode 100644 www/analytics/plugins/SitesManager/lang/is.json create mode 100644 www/analytics/plugins/SitesManager/lang/it.json create mode 100644 www/analytics/plugins/SitesManager/lang/ja.json create mode 100644 www/analytics/plugins/SitesManager/lang/ka.json create mode 100644 www/analytics/plugins/SitesManager/lang/ko.json create mode 100644 www/analytics/plugins/SitesManager/lang/lt.json create mode 100644 www/analytics/plugins/SitesManager/lang/lv.json create mode 100644 www/analytics/plugins/SitesManager/lang/nb.json create mode 100644 www/analytics/plugins/SitesManager/lang/nl.json create mode 100644 www/analytics/plugins/SitesManager/lang/nn.json create mode 100644 www/analytics/plugins/SitesManager/lang/pl.json create mode 100644 www/analytics/plugins/SitesManager/lang/pt-br.json create mode 100644 www/analytics/plugins/SitesManager/lang/pt.json create mode 100644 www/analytics/plugins/SitesManager/lang/ro.json create mode 100644 www/analytics/plugins/SitesManager/lang/ru.json create mode 100644 www/analytics/plugins/SitesManager/lang/sk.json create mode 100644 www/analytics/plugins/SitesManager/lang/sl.json create mode 100644 www/analytics/plugins/SitesManager/lang/sq.json create mode 100644 www/analytics/plugins/SitesManager/lang/sr.json create mode 100644 www/analytics/plugins/SitesManager/lang/sv.json create mode 100644 www/analytics/plugins/SitesManager/lang/ta.json create mode 100644 www/analytics/plugins/SitesManager/lang/te.json create mode 100644 www/analytics/plugins/SitesManager/lang/th.json create mode 100644 www/analytics/plugins/SitesManager/lang/tl.json create mode 100644 www/analytics/plugins/SitesManager/lang/tr.json create mode 100644 www/analytics/plugins/SitesManager/lang/uk.json create mode 100644 www/analytics/plugins/SitesManager/lang/vi.json create mode 100644 www/analytics/plugins/SitesManager/lang/zh-cn.json create mode 100644 www/analytics/plugins/SitesManager/lang/zh-tw.json create mode 100644 www/analytics/plugins/SitesManager/templates/dialogs/dialogs.html create mode 100644 www/analytics/plugins/SitesManager/templates/dialogs/edit-dialog.html create mode 100644 www/analytics/plugins/SitesManager/templates/dialogs/remove-dialog.html create mode 100644 www/analytics/plugins/SitesManager/templates/global-settings.html create mode 100644 www/analytics/plugins/SitesManager/templates/help/excluded-ip-help.html create mode 100644 www/analytics/plugins/SitesManager/templates/help/excluded-query-parameters-help.html create mode 100644 www/analytics/plugins/SitesManager/templates/help/excluded-user-agents-help.html create mode 100644 www/analytics/plugins/SitesManager/templates/help/timezone-help.html create mode 100644 www/analytics/plugins/SitesManager/templates/index.html create mode 100644 www/analytics/plugins/SitesManager/templates/loading.html create mode 100644 www/analytics/plugins/SitesManager/templates/measurable_type_settings.twig create mode 100644 www/analytics/plugins/SitesManager/templates/siteWithoutData.twig create mode 100644 www/analytics/plugins/SitesManager/templates/sites-list/add-entity-dialog.html create mode 100644 www/analytics/plugins/SitesManager/templates/sites-list/add-site-link.html create mode 100644 www/analytics/plugins/SitesManager/templates/sites-list/site-fields.html create mode 100644 www/analytics/plugins/SitesManager/templates/sites-list/site-search-field.html create mode 100644 www/analytics/plugins/SitesManager/templates/sites-list/sites-list.html create mode 100644 www/analytics/plugins/SitesManager/templates/sites-manager-header.html create mode 100644 www/analytics/plugins/Transitions/lang/bg.json create mode 100644 www/analytics/plugins/Transitions/lang/ca.json create mode 100644 www/analytics/plugins/Transitions/lang/cs.json create mode 100644 www/analytics/plugins/Transitions/lang/da.json create mode 100644 www/analytics/plugins/Transitions/lang/de.json create mode 100644 www/analytics/plugins/Transitions/lang/el.json create mode 100644 www/analytics/plugins/Transitions/lang/en.json create mode 100644 www/analytics/plugins/Transitions/lang/es.json create mode 100644 www/analytics/plugins/Transitions/lang/et.json create mode 100644 www/analytics/plugins/Transitions/lang/fa.json create mode 100644 www/analytics/plugins/Transitions/lang/fi.json create mode 100644 www/analytics/plugins/Transitions/lang/fr.json create mode 100644 www/analytics/plugins/Transitions/lang/hi.json create mode 100644 www/analytics/plugins/Transitions/lang/id.json create mode 100644 www/analytics/plugins/Transitions/lang/it.json create mode 100644 www/analytics/plugins/Transitions/lang/ja.json create mode 100644 www/analytics/plugins/Transitions/lang/ko.json create mode 100644 www/analytics/plugins/Transitions/lang/lt.json create mode 100644 www/analytics/plugins/Transitions/lang/nb.json create mode 100644 www/analytics/plugins/Transitions/lang/nl.json create mode 100644 www/analytics/plugins/Transitions/lang/pl.json create mode 100644 www/analytics/plugins/Transitions/lang/pt-br.json create mode 100644 www/analytics/plugins/Transitions/lang/ro.json create mode 100644 www/analytics/plugins/Transitions/lang/ru.json create mode 100644 www/analytics/plugins/Transitions/lang/sl.json create mode 100644 www/analytics/plugins/Transitions/lang/sr.json create mode 100644 www/analytics/plugins/Transitions/lang/sv.json create mode 100644 www/analytics/plugins/Transitions/lang/th.json create mode 100644 www/analytics/plugins/Transitions/lang/tl.json create mode 100644 www/analytics/plugins/Transitions/lang/tr.json create mode 100644 www/analytics/plugins/Transitions/lang/vi.json create mode 100644 www/analytics/plugins/Transitions/lang/zh-cn.json create mode 100644 www/analytics/plugins/UserCountry/Columns/Base.php create mode 100644 www/analytics/plugins/UserCountry/Columns/City.php create mode 100644 www/analytics/plugins/UserCountry/Columns/Continent.php create mode 100644 www/analytics/plugins/UserCountry/Columns/Country.php create mode 100644 www/analytics/plugins/UserCountry/Columns/Latitude.php create mode 100644 www/analytics/plugins/UserCountry/Columns/Longitude.php create mode 100644 www/analytics/plugins/UserCountry/Columns/Provider.php create mode 100644 www/analytics/plugins/UserCountry/Columns/Region.php create mode 100644 www/analytics/plugins/UserCountry/Commands/AttributeHistoricalDataWithLocations.php create mode 100644 www/analytics/plugins/UserCountry/Diagnostic/GeolocationDiagnostic.php delete mode 100755 www/analytics/plugins/UserCountry/LocationProvider/Default.php create mode 100755 www/analytics/plugins/UserCountry/LocationProvider/DefaultProvider.php create mode 100644 www/analytics/plugins/UserCountry/Menu.php create mode 100644 www/analytics/plugins/UserCountry/Reports/Base.php create mode 100644 www/analytics/plugins/UserCountry/Reports/GetCity.php create mode 100644 www/analytics/plugins/UserCountry/Reports/GetContinent.php create mode 100644 www/analytics/plugins/UserCountry/Reports/GetCountry.php create mode 100644 www/analytics/plugins/UserCountry/Reports/GetRegion.php create mode 100644 www/analytics/plugins/UserCountry/Segment.php create mode 100644 www/analytics/plugins/UserCountry/Tasks.php create mode 100644 www/analytics/plugins/UserCountry/Visitor.php create mode 100644 www/analytics/plugins/UserCountry/VisitorGeolocator.php create mode 100644 www/analytics/plugins/UserCountry/config/config.php create mode 100644 www/analytics/plugins/UserCountry/lang/am.json create mode 100644 www/analytics/plugins/UserCountry/lang/ar.json create mode 100644 www/analytics/plugins/UserCountry/lang/be.json create mode 100644 www/analytics/plugins/UserCountry/lang/bg.json create mode 100644 www/analytics/plugins/UserCountry/lang/ca.json create mode 100644 www/analytics/plugins/UserCountry/lang/cs.json create mode 100644 www/analytics/plugins/UserCountry/lang/da.json create mode 100644 www/analytics/plugins/UserCountry/lang/de.json create mode 100644 www/analytics/plugins/UserCountry/lang/el.json create mode 100644 www/analytics/plugins/UserCountry/lang/en.json create mode 100644 www/analytics/plugins/UserCountry/lang/es.json create mode 100644 www/analytics/plugins/UserCountry/lang/et.json create mode 100644 www/analytics/plugins/UserCountry/lang/eu.json create mode 100644 www/analytics/plugins/UserCountry/lang/fa.json create mode 100644 www/analytics/plugins/UserCountry/lang/fi.json create mode 100644 www/analytics/plugins/UserCountry/lang/fr.json create mode 100644 www/analytics/plugins/UserCountry/lang/gl.json create mode 100644 www/analytics/plugins/UserCountry/lang/he.json create mode 100644 www/analytics/plugins/UserCountry/lang/hi.json create mode 100644 www/analytics/plugins/UserCountry/lang/hr.json create mode 100644 www/analytics/plugins/UserCountry/lang/hu.json create mode 100644 www/analytics/plugins/UserCountry/lang/id.json create mode 100644 www/analytics/plugins/UserCountry/lang/is.json create mode 100644 www/analytics/plugins/UserCountry/lang/it.json create mode 100644 www/analytics/plugins/UserCountry/lang/ja.json create mode 100644 www/analytics/plugins/UserCountry/lang/ka.json create mode 100644 www/analytics/plugins/UserCountry/lang/ko.json create mode 100644 www/analytics/plugins/UserCountry/lang/lt.json create mode 100644 www/analytics/plugins/UserCountry/lang/lv.json create mode 100644 www/analytics/plugins/UserCountry/lang/nb.json create mode 100644 www/analytics/plugins/UserCountry/lang/nl.json create mode 100644 www/analytics/plugins/UserCountry/lang/nn.json create mode 100644 www/analytics/plugins/UserCountry/lang/pl.json create mode 100644 www/analytics/plugins/UserCountry/lang/pt-br.json create mode 100644 www/analytics/plugins/UserCountry/lang/pt.json create mode 100644 www/analytics/plugins/UserCountry/lang/ro.json create mode 100644 www/analytics/plugins/UserCountry/lang/ru.json create mode 100644 www/analytics/plugins/UserCountry/lang/sk.json create mode 100644 www/analytics/plugins/UserCountry/lang/sl.json create mode 100644 www/analytics/plugins/UserCountry/lang/sq.json create mode 100644 www/analytics/plugins/UserCountry/lang/sr.json create mode 100644 www/analytics/plugins/UserCountry/lang/sv.json create mode 100644 www/analytics/plugins/UserCountry/lang/ta.json create mode 100644 www/analytics/plugins/UserCountry/lang/te.json create mode 100644 www/analytics/plugins/UserCountry/lang/th.json create mode 100644 www/analytics/plugins/UserCountry/lang/tl.json create mode 100644 www/analytics/plugins/UserCountry/lang/tr.json create mode 100644 www/analytics/plugins/UserCountry/lang/uk.json create mode 100644 www/analytics/plugins/UserCountry/lang/vi.json create mode 100644 www/analytics/plugins/UserCountry/lang/zh-cn.json create mode 100644 www/analytics/plugins/UserCountry/lang/zh-tw.json create mode 100644 www/analytics/plugins/UserCountryMap/Menu.php delete mode 100644 www/analytics/plugins/UserCountryMap/javascripts/vendor/chroma.min.js create mode 100644 www/analytics/plugins/UserCountryMap/lang/ar.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/be.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/bg.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/ca.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/cs.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/da.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/de.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/el.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/en.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/es.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/et.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/fa.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/fi.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/fr.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/he.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/hi.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/hr.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/hu.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/id.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/it.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/ja.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/ka.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/ko.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/lt.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/lv.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/nb.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/nl.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/pl.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/pt-br.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/pt.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/ro.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/ru.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/sk.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/sl.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/sq.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/sr.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/sv.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/te.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/th.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/tl.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/tr.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/uk.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/vi.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/zh-cn.json create mode 100644 www/analytics/plugins/UserCountryMap/lang/zh-tw.json create mode 100644 www/analytics/plugins/UserLanguage/API.php create mode 100644 www/analytics/plugins/UserLanguage/Archiver.php create mode 100644 www/analytics/plugins/UserLanguage/Columns/Language.php create mode 100644 www/analytics/plugins/UserLanguage/Reports/Base.php create mode 100644 www/analytics/plugins/UserLanguage/Reports/GetLanguage.php create mode 100644 www/analytics/plugins/UserLanguage/Reports/GetLanguageCode.php create mode 100644 www/analytics/plugins/UserLanguage/UserLanguage.php create mode 100644 www/analytics/plugins/UserLanguage/Visitor.php create mode 100644 www/analytics/plugins/UserLanguage/functions.php create mode 100644 www/analytics/plugins/UserLanguage/lang/am.json create mode 100644 www/analytics/plugins/UserLanguage/lang/ar.json create mode 100644 www/analytics/plugins/UserLanguage/lang/be.json create mode 100644 www/analytics/plugins/UserLanguage/lang/bg.json create mode 100644 www/analytics/plugins/UserLanguage/lang/ca.json create mode 100644 www/analytics/plugins/UserLanguage/lang/cs.json create mode 100644 www/analytics/plugins/UserLanguage/lang/da.json create mode 100644 www/analytics/plugins/UserLanguage/lang/de.json create mode 100644 www/analytics/plugins/UserLanguage/lang/el.json create mode 100644 www/analytics/plugins/UserLanguage/lang/en.json create mode 100644 www/analytics/plugins/UserLanguage/lang/es.json create mode 100644 www/analytics/plugins/UserLanguage/lang/et.json create mode 100644 www/analytics/plugins/UserLanguage/lang/eu.json create mode 100644 www/analytics/plugins/UserLanguage/lang/fa.json create mode 100644 www/analytics/plugins/UserLanguage/lang/fi.json create mode 100644 www/analytics/plugins/UserLanguage/lang/fr.json create mode 100644 www/analytics/plugins/UserLanguage/lang/gl.json create mode 100644 www/analytics/plugins/UserLanguage/lang/he.json create mode 100644 www/analytics/plugins/UserLanguage/lang/hi.json create mode 100644 www/analytics/plugins/UserLanguage/lang/hr.json create mode 100644 www/analytics/plugins/UserLanguage/lang/hu.json create mode 100644 www/analytics/plugins/UserLanguage/lang/id.json create mode 100644 www/analytics/plugins/UserLanguage/lang/is.json create mode 100644 www/analytics/plugins/UserLanguage/lang/it.json create mode 100644 www/analytics/plugins/UserLanguage/lang/ja.json create mode 100644 www/analytics/plugins/UserLanguage/lang/ka.json create mode 100644 www/analytics/plugins/UserLanguage/lang/ko.json create mode 100644 www/analytics/plugins/UserLanguage/lang/lt.json create mode 100644 www/analytics/plugins/UserLanguage/lang/lv.json create mode 100644 www/analytics/plugins/UserLanguage/lang/nb.json create mode 100644 www/analytics/plugins/UserLanguage/lang/nl.json create mode 100644 www/analytics/plugins/UserLanguage/lang/nn.json create mode 100644 www/analytics/plugins/UserLanguage/lang/pl.json create mode 100644 www/analytics/plugins/UserLanguage/lang/pt-br.json create mode 100644 www/analytics/plugins/UserLanguage/lang/pt.json create mode 100644 www/analytics/plugins/UserLanguage/lang/ro.json create mode 100644 www/analytics/plugins/UserLanguage/lang/ru.json create mode 100644 www/analytics/plugins/UserLanguage/lang/sk.json create mode 100644 www/analytics/plugins/UserLanguage/lang/sl.json create mode 100644 www/analytics/plugins/UserLanguage/lang/sq.json create mode 100644 www/analytics/plugins/UserLanguage/lang/sr.json create mode 100644 www/analytics/plugins/UserLanguage/lang/sv.json create mode 100644 www/analytics/plugins/UserLanguage/lang/ta.json create mode 100644 www/analytics/plugins/UserLanguage/lang/te.json create mode 100644 www/analytics/plugins/UserLanguage/lang/th.json create mode 100644 www/analytics/plugins/UserLanguage/lang/tl.json create mode 100644 www/analytics/plugins/UserLanguage/lang/tr.json create mode 100644 www/analytics/plugins/UserLanguage/lang/uk.json create mode 100644 www/analytics/plugins/UserLanguage/lang/vi.json create mode 100644 www/analytics/plugins/UserLanguage/lang/zh-cn.json create mode 100644 www/analytics/plugins/UserLanguage/lang/zh-tw.json delete mode 100644 www/analytics/plugins/UserSettings/API.php delete mode 100644 www/analytics/plugins/UserSettings/Archiver.php delete mode 100644 www/analytics/plugins/UserSettings/Controller.php delete mode 100644 www/analytics/plugins/UserSettings/UserSettings.php delete mode 100644 www/analytics/plugins/UserSettings/functions.php delete mode 100644 www/analytics/plugins/UserSettings/images/os/IOS.gif delete mode 100644 www/analytics/plugins/UserSettings/images/os/W2K.gif delete mode 100644 www/analytics/plugins/UserSettings/images/os/W61.gif delete mode 100644 www/analytics/plugins/UserSettings/images/os/W65.gif delete mode 100644 www/analytics/plugins/UserSettings/images/os/W75.gif delete mode 100644 www/analytics/plugins/UserSettings/images/os/W95.gif delete mode 100644 www/analytics/plugins/UserSettings/images/os/W98.gif delete mode 100644 www/analytics/plugins/UserSettings/images/os/WI7.gif delete mode 100644 www/analytics/plugins/UserSettings/images/os/WI8.gif delete mode 100644 www/analytics/plugins/UserSettings/images/os/WME.gif delete mode 100644 www/analytics/plugins/UserSettings/images/os/WNT.gif delete mode 100644 www/analytics/plugins/UserSettings/images/os/WP7.gif delete mode 100644 www/analytics/plugins/UserSettings/images/os/WS3.gif delete mode 100644 www/analytics/plugins/UserSettings/images/os/WVI.gif delete mode 100644 www/analytics/plugins/UserSettings/images/os/WXP.gif delete mode 100644 www/analytics/plugins/UserSettings/images/screens/dual.gif delete mode 100644 www/analytics/plugins/UserSettings/images/screens/mobile.gif delete mode 100644 www/analytics/plugins/UserSettings/images/screens/normal.gif delete mode 100644 www/analytics/plugins/UserSettings/images/screens/unknown.gif delete mode 100644 www/analytics/plugins/UserSettings/images/screens/wide.gif delete mode 100644 www/analytics/plugins/UserSettings/templates/index.twig create mode 100644 www/analytics/plugins/UsersManager/Menu.php create mode 100644 www/analytics/plugins/UsersManager/Tasks.php create mode 100644 www/analytics/plugins/UsersManager/UserAccessFilter.php create mode 100644 www/analytics/plugins/UsersManager/UserPreferences.php delete mode 100644 www/analytics/plugins/UsersManager/images/add.png create mode 100644 www/analytics/plugins/UsersManager/javascripts/giveViewAccess.js create mode 100644 www/analytics/plugins/UsersManager/lang/am.json create mode 100644 www/analytics/plugins/UsersManager/lang/ar.json create mode 100644 www/analytics/plugins/UsersManager/lang/be.json create mode 100644 www/analytics/plugins/UsersManager/lang/bg.json create mode 100644 www/analytics/plugins/UsersManager/lang/bs.json create mode 100644 www/analytics/plugins/UsersManager/lang/ca.json create mode 100644 www/analytics/plugins/UsersManager/lang/cs.json create mode 100644 www/analytics/plugins/UsersManager/lang/da.json create mode 100644 www/analytics/plugins/UsersManager/lang/de.json create mode 100644 www/analytics/plugins/UsersManager/lang/el.json create mode 100644 www/analytics/plugins/UsersManager/lang/en.json create mode 100644 www/analytics/plugins/UsersManager/lang/es.json create mode 100644 www/analytics/plugins/UsersManager/lang/et.json create mode 100644 www/analytics/plugins/UsersManager/lang/eu.json create mode 100644 www/analytics/plugins/UsersManager/lang/fa.json create mode 100644 www/analytics/plugins/UsersManager/lang/fi.json create mode 100644 www/analytics/plugins/UsersManager/lang/fr.json create mode 100644 www/analytics/plugins/UsersManager/lang/gl.json create mode 100644 www/analytics/plugins/UsersManager/lang/he.json create mode 100644 www/analytics/plugins/UsersManager/lang/hi.json create mode 100644 www/analytics/plugins/UsersManager/lang/hr.json create mode 100644 www/analytics/plugins/UsersManager/lang/hu.json create mode 100644 www/analytics/plugins/UsersManager/lang/id.json create mode 100644 www/analytics/plugins/UsersManager/lang/is.json create mode 100644 www/analytics/plugins/UsersManager/lang/it.json create mode 100644 www/analytics/plugins/UsersManager/lang/ja.json create mode 100644 www/analytics/plugins/UsersManager/lang/ka.json create mode 100644 www/analytics/plugins/UsersManager/lang/ko.json create mode 100644 www/analytics/plugins/UsersManager/lang/lt.json create mode 100644 www/analytics/plugins/UsersManager/lang/lv.json create mode 100644 www/analytics/plugins/UsersManager/lang/nb.json create mode 100644 www/analytics/plugins/UsersManager/lang/nl.json create mode 100644 www/analytics/plugins/UsersManager/lang/nn.json create mode 100644 www/analytics/plugins/UsersManager/lang/pl.json create mode 100644 www/analytics/plugins/UsersManager/lang/pt-br.json create mode 100644 www/analytics/plugins/UsersManager/lang/pt.json create mode 100644 www/analytics/plugins/UsersManager/lang/ro.json create mode 100644 www/analytics/plugins/UsersManager/lang/ru.json create mode 100644 www/analytics/plugins/UsersManager/lang/sk.json create mode 100644 www/analytics/plugins/UsersManager/lang/sl.json create mode 100644 www/analytics/plugins/UsersManager/lang/sq.json create mode 100644 www/analytics/plugins/UsersManager/lang/sr.json create mode 100644 www/analytics/plugins/UsersManager/lang/sv.json create mode 100644 www/analytics/plugins/UsersManager/lang/ta.json create mode 100644 www/analytics/plugins/UsersManager/lang/te.json create mode 100644 www/analytics/plugins/UsersManager/lang/th.json create mode 100644 www/analytics/plugins/UsersManager/lang/tl.json create mode 100644 www/analytics/plugins/UsersManager/lang/tr.json create mode 100644 www/analytics/plugins/UsersManager/lang/uk.json create mode 100644 www/analytics/plugins/UsersManager/lang/vi.json create mode 100644 www/analytics/plugins/UsersManager/lang/zh-cn.json create mode 100644 www/analytics/plugins/UsersManager/lang/zh-tw.json create mode 100644 www/analytics/plugins/UsersManager/templates/anonymousSettings.twig create mode 100644 www/analytics/plugins/UsersManager/templates/noWebsiteAdminAccess.twig create mode 100644 www/analytics/plugins/VisitFrequency/Columns/Metrics/ReturningMetric.php create mode 100644 www/analytics/plugins/VisitFrequency/Menu.php create mode 100644 www/analytics/plugins/VisitFrequency/Reports/Get.php create mode 100644 www/analytics/plugins/VisitFrequency/Widgets.php create mode 100644 www/analytics/plugins/VisitFrequency/lang/am.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/ar.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/be.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/bg.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/ca.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/cs.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/da.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/de.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/el.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/en.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/es.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/et.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/eu.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/fa.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/fi.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/fr.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/gl.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/he.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/hi.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/hu.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/id.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/is.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/it.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/ja.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/ka.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/ko.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/lt.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/lv.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/nb.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/nl.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/nn.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/pl.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/pt-br.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/pt.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/ro.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/ru.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/sk.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/sl.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/sq.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/sr.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/sv.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/te.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/th.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/tl.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/uk.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/vi.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/zh-cn.json create mode 100644 www/analytics/plugins/VisitFrequency/lang/zh-tw.json create mode 100644 www/analytics/plugins/VisitTime/Columns/DayOfTheWeek.php create mode 100644 www/analytics/plugins/VisitTime/Columns/LocalTime.php create mode 100644 www/analytics/plugins/VisitTime/Columns/ServerTime.php create mode 100644 www/analytics/plugins/VisitTime/DataTable/Filter/AddSegmentByLabelInUTC.php create mode 100644 www/analytics/plugins/VisitTime/Menu.php create mode 100644 www/analytics/plugins/VisitTime/Reports/Base.php create mode 100644 www/analytics/plugins/VisitTime/Reports/GetByDayOfWeek.php create mode 100644 www/analytics/plugins/VisitTime/Reports/GetVisitInformationPerLocalTime.php create mode 100644 www/analytics/plugins/VisitTime/Reports/GetVisitInformationPerServerTime.php create mode 100644 www/analytics/plugins/VisitTime/Segment.php create mode 100644 www/analytics/plugins/VisitTime/lang/am.json create mode 100644 www/analytics/plugins/VisitTime/lang/ar.json create mode 100644 www/analytics/plugins/VisitTime/lang/be.json create mode 100644 www/analytics/plugins/VisitTime/lang/bg.json create mode 100644 www/analytics/plugins/VisitTime/lang/ca.json create mode 100644 www/analytics/plugins/VisitTime/lang/cs.json create mode 100644 www/analytics/plugins/VisitTime/lang/da.json create mode 100644 www/analytics/plugins/VisitTime/lang/de.json create mode 100644 www/analytics/plugins/VisitTime/lang/el.json create mode 100644 www/analytics/plugins/VisitTime/lang/en.json create mode 100644 www/analytics/plugins/VisitTime/lang/es.json create mode 100644 www/analytics/plugins/VisitTime/lang/et.json create mode 100644 www/analytics/plugins/VisitTime/lang/eu.json create mode 100644 www/analytics/plugins/VisitTime/lang/fa.json create mode 100644 www/analytics/plugins/VisitTime/lang/fi.json create mode 100644 www/analytics/plugins/VisitTime/lang/fr.json create mode 100644 www/analytics/plugins/VisitTime/lang/gl.json create mode 100644 www/analytics/plugins/VisitTime/lang/he.json create mode 100644 www/analytics/plugins/VisitTime/lang/hi.json create mode 100644 www/analytics/plugins/VisitTime/lang/hr.json create mode 100644 www/analytics/plugins/VisitTime/lang/hu.json create mode 100644 www/analytics/plugins/VisitTime/lang/id.json create mode 100644 www/analytics/plugins/VisitTime/lang/is.json create mode 100644 www/analytics/plugins/VisitTime/lang/it.json create mode 100644 www/analytics/plugins/VisitTime/lang/ja.json create mode 100644 www/analytics/plugins/VisitTime/lang/ka.json create mode 100644 www/analytics/plugins/VisitTime/lang/ko.json create mode 100644 www/analytics/plugins/VisitTime/lang/lt.json create mode 100644 www/analytics/plugins/VisitTime/lang/lv.json create mode 100644 www/analytics/plugins/VisitTime/lang/nb.json create mode 100644 www/analytics/plugins/VisitTime/lang/nl.json create mode 100644 www/analytics/plugins/VisitTime/lang/nn.json create mode 100644 www/analytics/plugins/VisitTime/lang/pl.json create mode 100644 www/analytics/plugins/VisitTime/lang/pt-br.json create mode 100644 www/analytics/plugins/VisitTime/lang/pt.json create mode 100644 www/analytics/plugins/VisitTime/lang/ro.json create mode 100644 www/analytics/plugins/VisitTime/lang/ru.json create mode 100644 www/analytics/plugins/VisitTime/lang/sk.json create mode 100644 www/analytics/plugins/VisitTime/lang/sl.json create mode 100644 www/analytics/plugins/VisitTime/lang/sq.json create mode 100644 www/analytics/plugins/VisitTime/lang/sr.json create mode 100644 www/analytics/plugins/VisitTime/lang/sv.json create mode 100644 www/analytics/plugins/VisitTime/lang/te.json create mode 100644 www/analytics/plugins/VisitTime/lang/th.json create mode 100644 www/analytics/plugins/VisitTime/lang/tl.json create mode 100644 www/analytics/plugins/VisitTime/lang/tr.json create mode 100644 www/analytics/plugins/VisitTime/lang/uk.json create mode 100644 www/analytics/plugins/VisitTime/lang/vi.json create mode 100644 www/analytics/plugins/VisitTime/lang/zh-cn.json create mode 100644 www/analytics/plugins/VisitTime/lang/zh-tw.json create mode 100644 www/analytics/plugins/VisitorInterest/Columns/PagesPerVisit.php create mode 100644 www/analytics/plugins/VisitorInterest/Columns/VisitDuration.php create mode 100644 www/analytics/plugins/VisitorInterest/Columns/VisitsByDaysSinceLastVisit.php create mode 100644 www/analytics/plugins/VisitorInterest/Columns/VisitsbyVisitNumber.php create mode 100644 www/analytics/plugins/VisitorInterest/Menu.php create mode 100644 www/analytics/plugins/VisitorInterest/Reports/Base.php create mode 100644 www/analytics/plugins/VisitorInterest/Reports/GetNumberOfVisitsByDaysSinceLast.php create mode 100644 www/analytics/plugins/VisitorInterest/Reports/GetNumberOfVisitsByVisitCount.php create mode 100644 www/analytics/plugins/VisitorInterest/Reports/GetNumberOfVisitsPerPage.php create mode 100644 www/analytics/plugins/VisitorInterest/Reports/GetNumberOfVisitsPerVisitDuration.php create mode 100644 www/analytics/plugins/VisitorInterest/lang/am.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/ar.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/be.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/bg.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/ca.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/cs.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/da.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/de.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/el.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/en.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/es.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/et.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/eu.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/fa.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/fi.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/fr.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/gl.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/he.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/hi.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/hu.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/id.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/is.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/it.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/ja.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/ka.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/ko.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/lt.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/lv.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/nb.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/nl.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/nn.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/pl.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/pt-br.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/pt.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/ro.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/ru.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/sk.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/sl.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/sq.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/sr.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/sv.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/te.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/th.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/tl.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/tr.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/uk.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/vi.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/zh-cn.json create mode 100644 www/analytics/plugins/VisitorInterest/lang/zh-tw.json create mode 100644 www/analytics/plugins/VisitsSummary/Menu.php create mode 100644 www/analytics/plugins/VisitsSummary/Reports/Get.php create mode 100644 www/analytics/plugins/VisitsSummary/Widgets.php create mode 100644 www/analytics/plugins/VisitsSummary/lang/am.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/ar.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/be.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/bg.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/ca.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/cs.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/da.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/de.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/el.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/en.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/es.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/et.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/eu.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/fa.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/fi.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/fr.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/gl.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/he.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/hi.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/hr.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/hu.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/id.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/is.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/it.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/ja.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/ka.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/ko.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/lt.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/lv.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/nb.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/nl.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/nn.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/pl.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/pt-br.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/pt.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/ro.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/ru.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/sk.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/sl.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/sq.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/sr.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/sv.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/te.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/th.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/tl.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/tr.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/uk.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/vi.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/zh-cn.json create mode 100644 www/analytics/plugins/VisitsSummary/lang/zh-tw.json create mode 100644 www/analytics/plugins/WebsiteMeasurable/Type.php create mode 100644 www/analytics/plugins/WebsiteMeasurable/WebsiteMeasurable.php create mode 100644 www/analytics/plugins/WebsiteMeasurable/lang/ar.json create mode 100644 www/analytics/plugins/WebsiteMeasurable/lang/cs.json create mode 100644 www/analytics/plugins/WebsiteMeasurable/lang/de.json create mode 100644 www/analytics/plugins/WebsiteMeasurable/lang/el.json create mode 100644 www/analytics/plugins/WebsiteMeasurable/lang/en.json create mode 100644 www/analytics/plugins/WebsiteMeasurable/lang/es.json create mode 100644 www/analytics/plugins/WebsiteMeasurable/lang/fr.json create mode 100644 www/analytics/plugins/WebsiteMeasurable/lang/hi.json create mode 100644 www/analytics/plugins/WebsiteMeasurable/lang/it.json create mode 100644 www/analytics/plugins/WebsiteMeasurable/lang/ja.json create mode 100644 www/analytics/plugins/WebsiteMeasurable/lang/ko.json create mode 100644 www/analytics/plugins/WebsiteMeasurable/lang/lt.json create mode 100644 www/analytics/plugins/WebsiteMeasurable/lang/nb.json create mode 100644 www/analytics/plugins/WebsiteMeasurable/lang/nl.json create mode 100644 www/analytics/plugins/WebsiteMeasurable/lang/pt-br.json create mode 100644 www/analytics/plugins/WebsiteMeasurable/lang/sk.json create mode 100644 www/analytics/plugins/WebsiteMeasurable/lang/sq.json create mode 100644 www/analytics/plugins/WebsiteMeasurable/lang/sr.json create mode 100644 www/analytics/plugins/WebsiteMeasurable/lang/sv.json create mode 100644 www/analytics/plugins/WebsiteMeasurable/lang/zh-tw.json create mode 100644 www/analytics/plugins/WebsiteMeasurable/plugin.json create mode 100644 www/analytics/plugins/Widgetize/Menu.php create mode 100644 www/analytics/plugins/Widgetize/lang/ar.json create mode 100644 www/analytics/plugins/Widgetize/lang/be.json create mode 100644 www/analytics/plugins/Widgetize/lang/bg.json create mode 100644 www/analytics/plugins/Widgetize/lang/bn.json create mode 100644 www/analytics/plugins/Widgetize/lang/bs.json create mode 100644 www/analytics/plugins/Widgetize/lang/ca.json create mode 100644 www/analytics/plugins/Widgetize/lang/cs.json create mode 100644 www/analytics/plugins/Widgetize/lang/cy.json create mode 100644 www/analytics/plugins/Widgetize/lang/da.json create mode 100644 www/analytics/plugins/Widgetize/lang/de.json create mode 100644 www/analytics/plugins/Widgetize/lang/el.json create mode 100644 www/analytics/plugins/Widgetize/lang/en.json create mode 100644 www/analytics/plugins/Widgetize/lang/es.json create mode 100644 www/analytics/plugins/Widgetize/lang/et.json create mode 100644 www/analytics/plugins/Widgetize/lang/fa.json create mode 100644 www/analytics/plugins/Widgetize/lang/fi.json create mode 100644 www/analytics/plugins/Widgetize/lang/fr.json create mode 100644 www/analytics/plugins/Widgetize/lang/gl.json create mode 100644 www/analytics/plugins/Widgetize/lang/he.json create mode 100644 www/analytics/plugins/Widgetize/lang/hi.json create mode 100644 www/analytics/plugins/Widgetize/lang/hr.json create mode 100644 www/analytics/plugins/Widgetize/lang/hu.json create mode 100644 www/analytics/plugins/Widgetize/lang/id.json create mode 100644 www/analytics/plugins/Widgetize/lang/is.json create mode 100644 www/analytics/plugins/Widgetize/lang/it.json create mode 100644 www/analytics/plugins/Widgetize/lang/ja.json create mode 100644 www/analytics/plugins/Widgetize/lang/ka.json create mode 100644 www/analytics/plugins/Widgetize/lang/ko.json create mode 100644 www/analytics/plugins/Widgetize/lang/lt.json create mode 100644 www/analytics/plugins/Widgetize/lang/lv.json create mode 100644 www/analytics/plugins/Widgetize/lang/nb.json create mode 100644 www/analytics/plugins/Widgetize/lang/nl.json create mode 100644 www/analytics/plugins/Widgetize/lang/nn.json create mode 100644 www/analytics/plugins/Widgetize/lang/pl.json create mode 100644 www/analytics/plugins/Widgetize/lang/pt-br.json create mode 100644 www/analytics/plugins/Widgetize/lang/pt.json create mode 100644 www/analytics/plugins/Widgetize/lang/ro.json create mode 100644 www/analytics/plugins/Widgetize/lang/ru.json create mode 100644 www/analytics/plugins/Widgetize/lang/sk.json create mode 100644 www/analytics/plugins/Widgetize/lang/sl.json create mode 100644 www/analytics/plugins/Widgetize/lang/sq.json create mode 100644 www/analytics/plugins/Widgetize/lang/sr.json create mode 100644 www/analytics/plugins/Widgetize/lang/sv.json create mode 100644 www/analytics/plugins/Widgetize/lang/th.json create mode 100644 www/analytics/plugins/Widgetize/lang/tl.json create mode 100644 www/analytics/plugins/Widgetize/lang/tr.json create mode 100644 www/analytics/plugins/Widgetize/lang/uk.json create mode 100644 www/analytics/plugins/Widgetize/lang/vi.json create mode 100644 www/analytics/plugins/Widgetize/lang/zh-cn.json create mode 100644 www/analytics/plugins/Widgetize/lang/zh-tw.json delete mode 100644 www/analytics/plugins/Widgetize/templates/testJsInclude1.twig delete mode 100644 www/analytics/plugins/Widgetize/templates/testJsInclude2.twig delete mode 100644 www/analytics/plugins/Zeitgeist/images/annotations.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/annotations_starred.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/chart_bar.png delete mode 100755 www/analytics/plugins/Zeitgeist/images/chart_line_edit.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/chart_pie.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/close.png delete mode 100755 www/analytics/plugins/Zeitgeist/images/configure-highlight.png delete mode 100755 www/analytics/plugins/Zeitgeist/images/configure.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/dashboard_h_bg_hover.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/datepicker_arr_l.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/datepicker_arr_r.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/export.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/goal.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/help.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/ico_delete.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/ico_edit.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/icon-calendar.gif delete mode 100644 www/analytics/plugins/Zeitgeist/images/image.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/link.gif delete mode 100644 www/analytics/plugins/Zeitgeist/images/loading-blue.gif delete mode 100644 www/analytics/plugins/Zeitgeist/images/logo-header.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/logo.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/logo.svg delete mode 100644 www/analytics/plugins/Zeitgeist/images/maximise.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/minimise.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/minus.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/plus.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/refresh.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/search_bg.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/search_ico.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/sort_subtable_desc.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/sortasc.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/sortdesc.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/table.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/table_more.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/tagcloud.png delete mode 100644 www/analytics/plugins/Zeitgeist/images/zoom-out.png delete mode 100644 www/analytics/plugins/Zeitgeist/plugin.json delete mode 100644 www/analytics/plugins/Zeitgeist/stylesheets/base.less delete mode 100644 www/analytics/plugins/Zeitgeist/stylesheets/general/_default.less delete mode 100644 www/analytics/plugins/Zeitgeist/stylesheets/general/_form.less delete mode 100644 www/analytics/plugins/Zeitgeist/stylesheets/general/_jqueryUI.less delete mode 100644 www/analytics/plugins/Zeitgeist/stylesheets/rtl.css delete mode 100644 www/analytics/plugins/Zeitgeist/stylesheets/simple_structure.css delete mode 100644 www/analytics/plugins/Zeitgeist/stylesheets/ui/_header.less delete mode 100644 www/analytics/plugins/Zeitgeist/stylesheets/ui/_headerMessage.less delete mode 100644 www/analytics/plugins/Zeitgeist/stylesheets/ui/_languageSelect.less delete mode 100644 www/analytics/plugins/Zeitgeist/stylesheets/ui/_loading.less delete mode 100644 www/analytics/plugins/Zeitgeist/stylesheets/ui/_periodSelect.less delete mode 100644 www/analytics/plugins/Zeitgeist/stylesheets/ui/_siteSelect.less delete mode 100644 www/analytics/plugins/Zeitgeist/templates/_jsCssIncludes.twig delete mode 100644 www/analytics/plugins/Zeitgeist/templates/_jsGlobalVariables.twig delete mode 100644 www/analytics/plugins/Zeitgeist/templates/_piwikTag.twig delete mode 100644 www/analytics/plugins/Zeitgeist/templates/admin.twig delete mode 100644 www/analytics/plugins/Zeitgeist/templates/ajaxMacros.twig delete mode 100644 www/analytics/plugins/Zeitgeist/templates/dashboard.twig delete mode 100644 www/analytics/plugins/Zeitgeist/templates/genericForm.twig delete mode 100644 www/analytics/plugins/Zeitgeist/templates/javascriptCode.tpl delete mode 100644 www/analytics/plugins/Zeitgeist/templates/macros.twig delete mode 100644 www/analytics/plugins/Zeitgeist/templates/simpleLayoutFooter.tpl delete mode 100644 www/analytics/plugins/Zeitgeist/templates/simpleLayoutHeader.tpl delete mode 100644 www/analytics/robots.txt create mode 100644 www/analytics/vendor/composer/LICENSE create mode 100644 www/analytics/vendor/composer/include_paths.php create mode 100644 www/analytics/vendor/container-interop/container-interop/LICENSE create mode 100644 www/analytics/vendor/container-interop/container-interop/README.md create mode 100644 www/analytics/vendor/container-interop/container-interop/composer.json create mode 100644 www/analytics/vendor/container-interop/container-interop/docs/ContainerInterface-meta.md create mode 100644 www/analytics/vendor/container-interop/container-interop/docs/ContainerInterface.md create mode 100644 www/analytics/vendor/container-interop/container-interop/docs/Delegate-lookup-meta.md create mode 100644 www/analytics/vendor/container-interop/container-interop/docs/Delegate-lookup.md create mode 100644 www/analytics/vendor/container-interop/container-interop/docs/images/interoperating_containers.png create mode 100644 www/analytics/vendor/container-interop/container-interop/docs/images/priority.png create mode 100644 www/analytics/vendor/container-interop/container-interop/docs/images/side_by_side_containers.png create mode 100644 www/analytics/vendor/container-interop/container-interop/src/Interop/Container/ContainerInterface.php create mode 100644 www/analytics/vendor/container-interop/container-interop/src/Interop/Container/Exception/ContainerException.php create mode 100644 www/analytics/vendor/container-interop/container-interop/src/Interop/Container/Exception/NotFoundException.php create mode 100644 www/analytics/vendor/doctrine/annotations/LICENSE create mode 100644 www/analytics/vendor/doctrine/annotations/README.md create mode 100644 www/analytics/vendor/doctrine/annotations/composer.json create mode 100644 www/analytics/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php create mode 100644 www/analytics/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php create mode 100644 www/analytics/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php create mode 100644 www/analytics/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php create mode 100644 www/analytics/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php create mode 100644 www/analytics/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Required.php create mode 100644 www/analytics/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php create mode 100644 www/analytics/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php create mode 100644 www/analytics/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php create mode 100644 www/analytics/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php create mode 100644 www/analytics/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php create mode 100644 www/analytics/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php create mode 100644 www/analytics/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php create mode 100644 www/analytics/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php create mode 100644 www/analytics/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php create mode 100644 www/analytics/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php create mode 100644 www/analytics/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php create mode 100644 www/analytics/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php create mode 100644 www/analytics/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php create mode 100644 www/analytics/vendor/doctrine/cache/LICENSE create mode 100644 www/analytics/vendor/doctrine/cache/README.md create mode 100644 www/analytics/vendor/doctrine/cache/UPGRADE.md create mode 100644 www/analytics/vendor/doctrine/cache/build.properties create mode 100644 www/analytics/vendor/doctrine/cache/build.xml create mode 100644 www/analytics/vendor/doctrine/cache/composer.json create mode 100644 www/analytics/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php create mode 100644 www/analytics/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php create mode 100644 www/analytics/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php create mode 100644 www/analytics/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php create mode 100644 www/analytics/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ChainCache.php create mode 100644 www/analytics/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ClearableCache.php create mode 100644 www/analytics/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php create mode 100644 www/analytics/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php create mode 100644 www/analytics/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php create mode 100644 www/analytics/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FlushableCache.php create mode 100644 www/analytics/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php create mode 100644 www/analytics/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php create mode 100644 www/analytics/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MongoDBCache.php create mode 100644 www/analytics/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiGetCache.php create mode 100644 www/analytics/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php create mode 100644 www/analytics/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PredisCache.php create mode 100644 www/analytics/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RedisCache.php create mode 100644 www/analytics/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RiakCache.php create mode 100644 www/analytics/vendor/doctrine/cache/lib/Doctrine/Common/Cache/SQLite3Cache.php create mode 100644 www/analytics/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Version.php create mode 100644 www/analytics/vendor/doctrine/cache/lib/Doctrine/Common/Cache/VoidCache.php create mode 100644 www/analytics/vendor/doctrine/cache/lib/Doctrine/Common/Cache/WinCacheCache.php create mode 100644 www/analytics/vendor/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php create mode 100644 www/analytics/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php create mode 100644 www/analytics/vendor/doctrine/cache/phpunit.xml.dist create mode 100644 www/analytics/vendor/doctrine/lexer/LICENSE create mode 100644 www/analytics/vendor/doctrine/lexer/README.md create mode 100644 www/analytics/vendor/doctrine/lexer/composer.json create mode 100644 www/analytics/vendor/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php delete mode 100644 www/analytics/vendor/leafo/lessphp/.gitignore delete mode 100644 www/analytics/vendor/leafo/lessphp/.travis.yml create mode 100644 www/analytics/vendor/mnapoli/phpdocreader/LICENSE create mode 100644 www/analytics/vendor/mnapoli/phpdocreader/README.md create mode 100644 www/analytics/vendor/mnapoli/phpdocreader/composer.json create mode 100644 www/analytics/vendor/mnapoli/phpdocreader/phpunit.xml.dist create mode 100644 www/analytics/vendor/mnapoli/phpdocreader/src/PhpDocReader/AnnotationException.php create mode 100644 www/analytics/vendor/mnapoli/phpdocreader/src/PhpDocReader/PhpDocReader.php create mode 100644 www/analytics/vendor/monolog/monolog/CHANGELOG.mdown create mode 100644 www/analytics/vendor/monolog/monolog/LICENSE create mode 100644 www/analytics/vendor/monolog/monolog/README.mdown create mode 100644 www/analytics/vendor/monolog/monolog/composer.json create mode 100644 www/analytics/vendor/monolog/monolog/doc/01-usage.md create mode 100644 www/analytics/vendor/monolog/monolog/doc/02-handlers-formatters-processors.md create mode 100644 www/analytics/vendor/monolog/monolog/doc/03-utilities.md create mode 100644 www/analytics/vendor/monolog/monolog/doc/04-extending.md create mode 100644 www/analytics/vendor/monolog/monolog/doc/sockets.md create mode 100644 www/analytics/vendor/monolog/monolog/phpunit.xml.dist create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/ErrorHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Logger.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php create mode 100644 www/analytics/vendor/monolog/monolog/src/Monolog/Registry.php create mode 100644 www/analytics/vendor/pear/archive_tar/Archive/Tar.php create mode 100644 www/analytics/vendor/pear/archive_tar/README.md create mode 100644 www/analytics/vendor/pear/archive_tar/composer.json create mode 100644 www/analytics/vendor/pear/archive_tar/docs/Archive_Tar.txt create mode 100644 www/analytics/vendor/pear/archive_tar/package.xml create mode 100755 www/analytics/vendor/pear/archive_tar/scripts/phptar.in create mode 100755 www/analytics/vendor/pear/archive_tar/sync-php4 create mode 100644 www/analytics/vendor/pear/console_getopt/Console/Getopt.php create mode 100644 www/analytics/vendor/pear/console_getopt/LICENSE create mode 100644 www/analytics/vendor/pear/console_getopt/README.rst create mode 100644 www/analytics/vendor/pear/console_getopt/composer.json create mode 100644 www/analytics/vendor/pear/console_getopt/package.xml create mode 100644 www/analytics/vendor/pear/pear-core-minimal/README.rst create mode 100644 www/analytics/vendor/pear/pear-core-minimal/composer.json create mode 100755 www/analytics/vendor/pear/pear-core-minimal/copy-from-pear-core.sh create mode 100644 www/analytics/vendor/pear/pear-core-minimal/src/OS/Guess.php rename www/analytics/{libs => vendor/pear/pear-core-minimal/src}/PEAR.php (87%) create mode 100644 www/analytics/vendor/pear/pear-core-minimal/src/PEAR/Error.php create mode 100644 www/analytics/vendor/pear/pear-core-minimal/src/PEAR/ErrorStack.php create mode 100644 www/analytics/vendor/pear/pear-core-minimal/src/System.php rename www/analytics/{libs/PEAR => vendor/pear/pear_exception}/LICENSE (100%) create mode 100644 www/analytics/vendor/pear/pear_exception/PEAR/Exception.php create mode 100644 www/analytics/vendor/pear/pear_exception/composer.json create mode 100644 www/analytics/vendor/pear/pear_exception/package.xml create mode 100644 www/analytics/vendor/php-di/invoker/CONTRIBUTING.md create mode 100644 www/analytics/vendor/php-di/invoker/LICENSE create mode 100644 www/analytics/vendor/php-di/invoker/README.md create mode 100644 www/analytics/vendor/php-di/invoker/composer.json create mode 100644 www/analytics/vendor/php-di/invoker/doc/parameter-resolvers.md create mode 100644 www/analytics/vendor/php-di/invoker/src/CallableResolver.php create mode 100644 www/analytics/vendor/php-di/invoker/src/Exception/InvocationException.php create mode 100644 www/analytics/vendor/php-di/invoker/src/Exception/NotCallableException.php create mode 100644 www/analytics/vendor/php-di/invoker/src/Exception/NotEnoughParametersException.php create mode 100644 www/analytics/vendor/php-di/invoker/src/Invoker.php create mode 100644 www/analytics/vendor/php-di/invoker/src/InvokerInterface.php create mode 100644 www/analytics/vendor/php-di/invoker/src/ParameterResolver/AssociativeArrayResolver.php create mode 100644 www/analytics/vendor/php-di/invoker/src/ParameterResolver/Container/ParameterNameContainerResolver.php create mode 100644 www/analytics/vendor/php-di/invoker/src/ParameterResolver/Container/TypeHintContainerResolver.php create mode 100644 www/analytics/vendor/php-di/invoker/src/ParameterResolver/DefaultValueResolver.php create mode 100644 www/analytics/vendor/php-di/invoker/src/ParameterResolver/NumericArrayResolver.php create mode 100644 www/analytics/vendor/php-di/invoker/src/ParameterResolver/ParameterResolver.php create mode 100644 www/analytics/vendor/php-di/invoker/src/ParameterResolver/ResolverChain.php create mode 100644 www/analytics/vendor/php-di/invoker/src/Reflection/CallableReflection.php create mode 100644 www/analytics/vendor/php-di/php-di/404.md create mode 100644 www/analytics/vendor/php-di/php-di/CONTRIBUTING.md create mode 100644 www/analytics/vendor/php-di/php-di/LICENSE create mode 100644 www/analytics/vendor/php-di/php-di/README.md create mode 100644 www/analytics/vendor/php-di/php-di/change-log.md create mode 100644 www/analytics/vendor/php-di/php-di/composer.json create mode 100644 www/analytics/vendor/php-di/php-di/couscous.yml create mode 100644 www/analytics/vendor/php-di/php-di/phpunit.xml.dist create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Annotation/Inject.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Annotation/Injectable.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Container.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/ContainerBuilder.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Debug.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/AbstractFunctionCallDefinition.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/AliasDefinition.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/ArrayDefinition.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/ArrayDefinitionExtension.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/CacheableDefinition.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/DecoratorDefinition.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Definition.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Dumper/AliasDefinitionDumper.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Dumper/ArrayDefinitionDumper.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Dumper/DecoratorDefinitionDumper.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Dumper/DefinitionDumper.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Dumper/DefinitionDumperDispatcher.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Dumper/EnvironmentVariableDefinitionDumper.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Dumper/FactoryDefinitionDumper.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Dumper/ObjectDefinitionDumper.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Dumper/StringDefinitionDumper.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Dumper/ValueDefinitionDumper.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/EntryReference.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/EnvironmentVariableDefinition.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Exception/AnnotationException.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Exception/DefinitionException.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/FactoryDefinition.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/HasSubDefinition.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Helper/ArrayDefinitionExtensionHelper.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Helper/DefinitionHelper.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Helper/EnvironmentVariableDefinitionHelper.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Helper/FactoryDefinitionHelper.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Helper/ObjectDefinitionHelper.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Helper/StringDefinitionHelper.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Helper/ValueDefinitionHelper.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/InstanceDefinition.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/ObjectDefinition.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/ObjectDefinition/MethodInjection.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/ObjectDefinition/PropertyInjection.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Resolver/AliasResolver.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Resolver/ArrayResolver.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Resolver/DecoratorResolver.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Resolver/DefinitionResolver.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Resolver/EnvironmentVariableResolver.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Resolver/FactoryResolver.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Resolver/InstanceInjector.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Resolver/ObjectCreator.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Resolver/ParameterResolver.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Resolver/ResolverDispatcher.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Resolver/StringResolver.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Resolver/ValueResolver.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Source/AnnotationReader.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Source/Autowiring.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Source/CachedDefinitionSource.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Source/DefinitionArray.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Source/DefinitionFile.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Source/DefinitionSource.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Source/MutableDefinitionSource.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/Source/SourceChain.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/StringDefinition.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Definition/ValueDefinition.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/DependencyException.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/FactoryInterface.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Invoker/DefinitionParameterResolver.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/InvokerInterface.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/NotFoundException.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Proxy/ProxyFactory.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Reflection/CallableReflectionFactory.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/Scope.php create mode 100644 www/analytics/vendor/php-di/php-di/src/DI/functions.php create mode 100644 www/analytics/vendor/piwik/cache/README.md create mode 100644 www/analytics/vendor/piwik/cache/composer.json create mode 100644 www/analytics/vendor/piwik/cache/phpunit.xml create mode 100644 www/analytics/vendor/piwik/cache/src/Backend.php create mode 100644 www/analytics/vendor/piwik/cache/src/Backend/ArrayCache.php create mode 100644 www/analytics/vendor/piwik/cache/src/Backend/Chained.php create mode 100644 www/analytics/vendor/piwik/cache/src/Backend/Factory.php create mode 100644 www/analytics/vendor/piwik/cache/src/Backend/Factory/BackendNotFoundException.php create mode 100644 www/analytics/vendor/piwik/cache/src/Backend/File.php create mode 100644 www/analytics/vendor/piwik/cache/src/Backend/NullCache.php create mode 100644 www/analytics/vendor/piwik/cache/src/Backend/Redis.php create mode 100644 www/analytics/vendor/piwik/cache/src/Cache.php create mode 100644 www/analytics/vendor/piwik/cache/src/Eager.php create mode 100644 www/analytics/vendor/piwik/cache/src/Lazy.php create mode 100644 www/analytics/vendor/piwik/cache/src/Transient.php create mode 100644 www/analytics/vendor/piwik/decompress/README.md create mode 100644 www/analytics/vendor/piwik/decompress/composer.json create mode 100755 www/analytics/vendor/piwik/decompress/libs/PclZip/gnu-lgpl.txt create mode 100644 www/analytics/vendor/piwik/decompress/libs/PclZip/pclzip.lib.php create mode 100755 www/analytics/vendor/piwik/decompress/libs/PclZip/readme.txt create mode 100644 www/analytics/vendor/piwik/decompress/libs/README.md create mode 100644 www/analytics/vendor/piwik/decompress/phpunit.xml create mode 100644 www/analytics/vendor/piwik/decompress/src/DecompressInterface.php rename www/analytics/{core/Unzip => vendor/piwik/decompress/src}/Gzip.php (86%) rename www/analytics/{core/Unzip => vendor/piwik/decompress/src}/PclZip.php (82%) create mode 100755 www/analytics/vendor/piwik/decompress/src/Tar.php create mode 100644 www/analytics/vendor/piwik/decompress/src/ZipArchive.php delete mode 100644 www/analytics/vendor/piwik/device-detector/.gitignore delete mode 100644 www/analytics/vendor/piwik/device-detector/.travis.yml create mode 100644 www/analytics/vendor/piwik/device-detector/Cache/Cache.php create mode 100644 www/analytics/vendor/piwik/device-detector/Cache/StaticCache.php create mode 100644 www/analytics/vendor/piwik/device-detector/Parser/Bot.php create mode 100644 www/analytics/vendor/piwik/device-detector/Parser/Client/Browser.php create mode 100644 www/analytics/vendor/piwik/device-detector/Parser/Client/Browser/Engine.php create mode 100644 www/analytics/vendor/piwik/device-detector/Parser/Client/ClientParserAbstract.php create mode 100644 www/analytics/vendor/piwik/device-detector/Parser/Client/FeedReader.php create mode 100644 www/analytics/vendor/piwik/device-detector/Parser/Client/Library.php create mode 100644 www/analytics/vendor/piwik/device-detector/Parser/Client/MediaPlayer.php create mode 100644 www/analytics/vendor/piwik/device-detector/Parser/Client/MobileApp.php create mode 100644 www/analytics/vendor/piwik/device-detector/Parser/Client/PIM.php create mode 100644 www/analytics/vendor/piwik/device-detector/Parser/Device/Camera.php create mode 100644 www/analytics/vendor/piwik/device-detector/Parser/Device/CarBrowser.php create mode 100644 www/analytics/vendor/piwik/device-detector/Parser/Device/Console.php create mode 100644 www/analytics/vendor/piwik/device-detector/Parser/Device/DeviceParserAbstract.php create mode 100644 www/analytics/vendor/piwik/device-detector/Parser/Device/HbbTv.php create mode 100644 www/analytics/vendor/piwik/device-detector/Parser/Device/Mobile.php create mode 100644 www/analytics/vendor/piwik/device-detector/Parser/Device/PortableMediaPlayer.php create mode 100644 www/analytics/vendor/piwik/device-detector/Parser/OperatingSystem.php create mode 100644 www/analytics/vendor/piwik/device-detector/Parser/ParserAbstract.php create mode 100644 www/analytics/vendor/piwik/device-detector/Parser/VendorFragment.php create mode 100644 www/analytics/vendor/piwik/device-detector/regexes/bots.yml delete mode 100644 www/analytics/vendor/piwik/device-detector/regexes/browsers.yml create mode 100644 www/analytics/vendor/piwik/device-detector/regexes/client/browser_engine.yml create mode 100644 www/analytics/vendor/piwik/device-detector/regexes/client/browsers.yml create mode 100644 www/analytics/vendor/piwik/device-detector/regexes/client/feed_readers.yml create mode 100644 www/analytics/vendor/piwik/device-detector/regexes/client/libraries.yml create mode 100644 www/analytics/vendor/piwik/device-detector/regexes/client/mediaplayers.yml create mode 100644 www/analytics/vendor/piwik/device-detector/regexes/client/mobile_apps.yml create mode 100644 www/analytics/vendor/piwik/device-detector/regexes/client/pim.yml create mode 100644 www/analytics/vendor/piwik/device-detector/regexes/device/cameras.yml create mode 100644 www/analytics/vendor/piwik/device-detector/regexes/device/car_browsers.yml create mode 100644 www/analytics/vendor/piwik/device-detector/regexes/device/consoles.yml create mode 100644 www/analytics/vendor/piwik/device-detector/regexes/device/mobiles.yml create mode 100644 www/analytics/vendor/piwik/device-detector/regexes/device/portable_media_player.yml create mode 100644 www/analytics/vendor/piwik/device-detector/regexes/device/televisions.yml delete mode 100644 www/analytics/vendor/piwik/device-detector/regexes/mobiles.yml delete mode 100644 www/analytics/vendor/piwik/device-detector/regexes/televisions.yml create mode 100644 www/analytics/vendor/piwik/device-detector/regexes/vendorfragments.yml create mode 100644 www/analytics/vendor/piwik/ini/README.md create mode 100644 www/analytics/vendor/piwik/ini/composer.json create mode 100644 www/analytics/vendor/piwik/ini/phpunit.xml create mode 100644 www/analytics/vendor/piwik/ini/src/IniReader.php create mode 100644 www/analytics/vendor/piwik/ini/src/IniReadingException.php create mode 100644 www/analytics/vendor/piwik/ini/src/IniWriter.php create mode 100644 www/analytics/vendor/piwik/ini/src/IniWritingException.php create mode 100644 www/analytics/vendor/piwik/network/README.md create mode 100644 www/analytics/vendor/piwik/network/composer.json create mode 100644 www/analytics/vendor/piwik/network/phpunit.xml create mode 100644 www/analytics/vendor/piwik/network/src/IP.php create mode 100644 www/analytics/vendor/piwik/network/src/IPUtils.php create mode 100644 www/analytics/vendor/piwik/network/src/IPv4.php create mode 100644 www/analytics/vendor/piwik/network/src/IPv6.php create mode 100644 www/analytics/vendor/piwik/piwik-php-tracker/LICENSE create mode 100644 www/analytics/vendor/piwik/piwik-php-tracker/PiwikTracker.php create mode 100644 www/analytics/vendor/piwik/piwik-php-tracker/README.md create mode 100644 www/analytics/vendor/piwik/piwik-php-tracker/composer.json create mode 100644 www/analytics/vendor/piwik/referrer-spam-blacklist/CONTRIBUTING.md create mode 100644 www/analytics/vendor/piwik/referrer-spam-blacklist/README.md create mode 100644 www/analytics/vendor/piwik/referrer-spam-blacklist/composer.json create mode 100644 www/analytics/vendor/piwik/referrer-spam-blacklist/spammers.txt create mode 100644 www/analytics/vendor/piwik/searchengine-and-social-list/README.md create mode 100644 www/analytics/vendor/piwik/searchengine-and-social-list/SearchEngines.yml create mode 100644 www/analytics/vendor/piwik/searchengine-and-social-list/Socials.yml create mode 100644 www/analytics/vendor/piwik/searchengine-and-social-list/composer.json create mode 100644 www/analytics/vendor/psr/log/LICENSE create mode 100644 www/analytics/vendor/psr/log/Psr/Log/AbstractLogger.php create mode 100644 www/analytics/vendor/psr/log/Psr/Log/InvalidArgumentException.php create mode 100644 www/analytics/vendor/psr/log/Psr/Log/LogLevel.php create mode 100644 www/analytics/vendor/psr/log/Psr/Log/LoggerAwareInterface.php create mode 100644 www/analytics/vendor/psr/log/Psr/Log/LoggerAwareTrait.php create mode 100644 www/analytics/vendor/psr/log/Psr/Log/LoggerInterface.php create mode 100644 www/analytics/vendor/psr/log/Psr/Log/LoggerTrait.php create mode 100644 www/analytics/vendor/psr/log/Psr/Log/NullLogger.php create mode 100644 www/analytics/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php create mode 100644 www/analytics/vendor/psr/log/README.md create mode 100644 www/analytics/vendor/psr/log/composer.json delete mode 100644 www/analytics/vendor/symfony/console/Symfony/Component/Console/.gitignore create mode 100644 www/analytics/vendor/symfony/console/Symfony/Component/Console/Helper/DebugFormatterHelper.php create mode 100644 www/analytics/vendor/symfony/console/Symfony/Component/Console/Helper/ProcessHelper.php create mode 100644 www/analytics/vendor/symfony/console/Symfony/Component/Console/Helper/ProgressBar.php create mode 100644 www/analytics/vendor/symfony/console/Symfony/Component/Console/Helper/QuestionHelper.php create mode 100644 www/analytics/vendor/symfony/console/Symfony/Component/Console/Helper/Table.php create mode 100644 www/analytics/vendor/symfony/console/Symfony/Component/Console/Helper/TableSeparator.php create mode 100644 www/analytics/vendor/symfony/console/Symfony/Component/Console/Helper/TableStyle.php create mode 100644 www/analytics/vendor/symfony/console/Symfony/Component/Console/Logger/ConsoleLogger.php create mode 100644 www/analytics/vendor/symfony/console/Symfony/Component/Console/Question/ChoiceQuestion.php create mode 100644 www/analytics/vendor/symfony/console/Symfony/Component/Console/Question/ConfirmationQuestion.php create mode 100644 www/analytics/vendor/symfony/console/Symfony/Component/Console/Question/Question.php create mode 100644 www/analytics/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/CHANGELOG.md create mode 100644 www/analytics/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/ContainerAwareEventDispatcher.php create mode 100644 www/analytics/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php create mode 100644 www/analytics/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcherInterface.php create mode 100644 www/analytics/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Debug/WrappedListener.php create mode 100644 www/analytics/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php create mode 100644 www/analytics/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Event.php create mode 100644 www/analytics/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventDispatcher.php create mode 100644 www/analytics/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventDispatcherInterface.php create mode 100644 www/analytics/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventSubscriberInterface.php create mode 100644 www/analytics/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/GenericEvent.php create mode 100644 www/analytics/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.php create mode 100644 www/analytics/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/LICENSE create mode 100644 www/analytics/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/README.md create mode 100644 www/analytics/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/composer.json create mode 100644 www/analytics/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/phpunit.xml.dist create mode 100644 www/analytics/vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/CHANGELOG.md create mode 100644 www/analytics/vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php create mode 100644 www/analytics/vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php create mode 100644 www/analytics/vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php create mode 100644 www/analytics/vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/Handler/DebugHandler.php create mode 100644 www/analytics/vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/Handler/FingersCrossed/NotFoundActivationStrategy.php create mode 100644 www/analytics/vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php create mode 100644 www/analytics/vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/Handler/SwiftMailerHandler.php create mode 100644 www/analytics/vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/LICENSE create mode 100644 www/analytics/vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/Logger.php create mode 100644 www/analytics/vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/Processor/WebProcessor.php create mode 100644 www/analytics/vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/README.md create mode 100644 www/analytics/vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/composer.json create mode 100644 www/analytics/vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/phpunit.xml.dist create mode 100644 www/analytics/vendor/tecnickcom/tcpdf/CHANGELOG.TXT rename www/analytics/{libs => vendor/tecnickcom}/tcpdf/LICENSE.TXT (100%) rename www/analytics/{libs => vendor/tecnickcom}/tcpdf/README.TXT (90%) create mode 100644 www/analytics/vendor/tecnickcom/tcpdf/composer.json rename www/analytics/{libs => vendor/tecnickcom}/tcpdf/config/tcpdf_config.php (90%) create mode 100644 www/analytics/vendor/tecnickcom/tcpdf/fonts/aealarabiya.ctg.z create mode 100644 www/analytics/vendor/tecnickcom/tcpdf/fonts/aealarabiya.php create mode 100644 www/analytics/vendor/tecnickcom/tcpdf/fonts/aealarabiya.z create mode 100644 www/analytics/vendor/tecnickcom/tcpdf/fonts/dejavusans.ctg.z create mode 100644 www/analytics/vendor/tecnickcom/tcpdf/fonts/dejavusans.php create mode 100644 www/analytics/vendor/tecnickcom/tcpdf/fonts/dejavusans.z create mode 100644 www/analytics/vendor/tecnickcom/tcpdf/fonts/freesans.ctg.z create mode 100644 www/analytics/vendor/tecnickcom/tcpdf/fonts/freesans.php create mode 100644 www/analytics/vendor/tecnickcom/tcpdf/fonts/freesans.z create mode 100644 www/analytics/vendor/tecnickcom/tcpdf/fonts/freeserif.ctg.z create mode 100644 www/analytics/vendor/tecnickcom/tcpdf/fonts/freeserif.php create mode 100644 www/analytics/vendor/tecnickcom/tcpdf/fonts/freeserif.z rename www/analytics/{libs => vendor/tecnickcom}/tcpdf/fonts/helvetica.php (100%) rename www/analytics/{libs => vendor/tecnickcom}/tcpdf/fonts/helveticab.php (100%) rename www/analytics/{libs => vendor/tecnickcom}/tcpdf/fonts/helveticabi.php (100%) rename www/analytics/{libs => vendor/tecnickcom}/tcpdf/fonts/helveticai.php (100%) rename www/analytics/{libs => vendor/tecnickcom}/tcpdf/fonts/hysmyeongjostdmedium.php (100%) rename www/analytics/{libs => vendor/tecnickcom}/tcpdf/fonts/kozgopromedium.php (100%) rename www/analytics/{libs => vendor/tecnickcom}/tcpdf/fonts/kozminproregular.php (100%) rename www/analytics/{libs => vendor/tecnickcom}/tcpdf/fonts/msungstdlight.php (100%) rename www/analytics/{libs => vendor/tecnickcom}/tcpdf/fonts/stsongstdlight.php (100%) rename www/analytics/{libs => vendor/tecnickcom}/tcpdf/fonts/symbol.php (100%) create mode 100644 www/analytics/vendor/tecnickcom/tcpdf/fonts/zapfdingbats.php create mode 100644 www/analytics/vendor/tecnickcom/tcpdf/include/barcodes/datamatrix.php rename www/analytics/{libs/tcpdf => vendor/tecnickcom/tcpdf/include/barcodes}/pdf417.php (98%) rename www/analytics/{libs/tcpdf => vendor/tecnickcom/tcpdf/include/barcodes}/qrcode.php (99%) rename www/analytics/{libs => vendor/tecnickcom}/tcpdf/include/sRGB.icc (100%) rename www/analytics/{libs => vendor/tecnickcom}/tcpdf/include/tcpdf_colors.php (98%) rename www/analytics/{libs => vendor/tecnickcom}/tcpdf/include/tcpdf_filters.php (98%) rename www/analytics/{libs => vendor/tecnickcom}/tcpdf/include/tcpdf_font_data.php (100%) rename www/analytics/{libs => vendor/tecnickcom}/tcpdf/include/tcpdf_fonts.php (98%) rename www/analytics/{libs => vendor/tecnickcom}/tcpdf/include/tcpdf_images.php (82%) create mode 100644 www/analytics/vendor/tecnickcom/tcpdf/include/tcpdf_static.php rename www/analytics/{libs => vendor/tecnickcom}/tcpdf/tcpdf.php (94%) rename www/analytics/{libs => vendor/tecnickcom}/tcpdf/tcpdf_autoconfig.php (92%) create mode 100644 www/analytics/vendor/tecnickcom/tcpdf/tcpdf_barcodes_1d.php create mode 100644 www/analytics/vendor/tecnickcom/tcpdf/tcpdf_barcodes_2d.php rename www/analytics/{libs => vendor/tecnickcom}/tcpdf/tcpdf_import.php (100%) rename www/analytics/{libs => vendor/tecnickcom}/tcpdf/tcpdf_parser.php (94%) create mode 100644 www/analytics/vendor/tecnickcom/tcpdf/tools/.htaccess create mode 100644 www/analytics/vendor/tecnickcom/tcpdf/tools/convert_fonts_examples.txt create mode 100755 www/analytics/vendor/tecnickcom/tcpdf/tools/tcpdf_addfont.php delete mode 100644 www/analytics/vendor/tedivm/jshrink/.gitignore delete mode 100644 www/analytics/vendor/tedivm/jshrink/.travis.yml create mode 100644 www/analytics/vendor/tedivm/jshrink/package.xml create mode 100644 www/analytics/vendor/tedivm/jshrink/phpunit.xml.dist delete mode 100644 www/analytics/vendor/twig/twig/.editorconfig delete mode 100644 www/analytics/vendor/twig/twig/.gitignore delete mode 100644 www/analytics/vendor/twig/twig/.travis.yml delete mode 100644 www/analytics/vendor/twig/twig/ext/twig/.gitignore delete mode 100644 www/analytics/vendor/twig/twig/ext/twig/LICENSE create mode 100644 www/analytics/vendor/twig/twig/lib/Twig/BaseNodeVisitor.php create mode 100644 www/analytics/vendor/twig/twig/lib/Twig/Cache/Filesystem.php create mode 100644 www/analytics/vendor/twig/twig/lib/Twig/Cache/Null.php create mode 100644 www/analytics/vendor/twig/twig/lib/Twig/CacheInterface.php create mode 100644 www/analytics/vendor/twig/twig/lib/Twig/Extension/Profiler.php create mode 100644 www/analytics/vendor/twig/twig/lib/Twig/FileExtensionEscapingStrategy.php create mode 100644 www/analytics/vendor/twig/twig/lib/Twig/Node/CheckSecurity.php delete mode 100644 www/analytics/vendor/twig/twig/lib/Twig/Node/SandboxedModule.php create mode 100644 www/analytics/vendor/twig/twig/lib/Twig/Profiler/Dumper/Blackfire.php create mode 100644 www/analytics/vendor/twig/twig/lib/Twig/Profiler/Dumper/Html.php create mode 100644 www/analytics/vendor/twig/twig/lib/Twig/Profiler/Dumper/Text.php create mode 100644 www/analytics/vendor/twig/twig/lib/Twig/Profiler/Node/EnterProfile.php create mode 100644 www/analytics/vendor/twig/twig/lib/Twig/Profiler/Node/LeaveProfile.php create mode 100644 www/analytics/vendor/twig/twig/lib/Twig/Profiler/NodeVisitor/Profiler.php create mode 100644 www/analytics/vendor/twig/twig/lib/Twig/Profiler/Profile.php create mode 100644 www/analytics/vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedFilterError.php create mode 100644 www/analytics/vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedFunctionError.php create mode 100644 www/analytics/vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedTagError.php create mode 100644 www/analytics/vendor/twig/twig/lib/Twig/Util/DeprecationCollector.php create mode 100644 www/analytics/vendor/twig/twig/lib/Twig/Util/TemplateDirIterator.php diff --git a/.hgignore b/.hgignore index 94d6c873..29a79b09 100644 --- a/.hgignore +++ b/.hgignore @@ -6,5 +6,5 @@ syntax: regexp ^seminarymedia/* ^seminaryuploads/* ^www/analytics/config/config.ini.php* -^www/analytics/temp/* +^www/analytics/tmp/* ^app/lib/phpqrcode/cache/* diff --git a/views/html/html.tpl b/views/html/html.tpl index 24a34e3a..d46631e0 100644 --- a/views/html/html.tpl +++ b/views/html/html.tpl @@ -128,6 +128,7 @@ + + diff --git a/www/analytics/CHANGELOG.md b/www/analytics/CHANGELOG.md new file mode 100644 index 00000000..f80c4a0b --- /dev/null +++ b/www/analytics/CHANGELOG.md @@ -0,0 +1,411 @@ +# Piwik Platform Changelog + +This is a changelog for Piwik platform developers. All changes for our HTTP API's, Plugins, Themes, etc will be listed here. + +## Piwik 2.16.0 + +### New features + * New segment `actionType` lets you segment all actions of a given type, eg. `actionType==events` or `actionType==downloads`. Action types values are: `pageviews`, `contents`, `sitesearches`, `events`, `outlinks`, `downloads` + * New segment `actionUrl` lets you segment any action that matches a given URL, whether they are Pageviews, Site searches, Contents, Downloads or Events. + * New segment `deviceBrand` lets you restrict your users to those using a particular device brand such as Apple, Samsung, LG, Google, Nokia, Sony, Lenovo, Alcatel, etc. View the [complete list of device brands.](http://developer.piwik.org/api-reference/segmentation) + * New segment operators `=^` "Starts with" and `=$` "Ends with" complement the existing segment operators: Contains, Does not contain, Equals, Not equals, Greater than or equal to, Less than or equal to. + * The JavaScript Tracker method `PiwikTracker.setDomains()` can now handle paths. This means when setting eg `_paq.push(['setDomains, '*.piwik.org/website1'])` all link that goes to the same domain `piwik.org` but to any other path than `website1/*` will be treated as outlink. + * In Administration > Websites, for each website, there is a checkbox "Only track visits and actions when the action URL starts with one of the above URLs". In Piwik 2.14.0, any action URL starting with one of the Alias URLs or starting with a subdomain of the Alias URL would be tracked. As of Piwik 2.15.0, when this checkbox is enabled, it may track less data: action URLs on an Alias URL subdomain will not be tracked anymore (you must specify each sub-domain as Alias URL). + * It is now possible to pass an option `php-cli-options` to the `core:archive` command. The given cli options will be forwarded to the actual PHP command. This allows to for example specifiy a different memory limit for the archiving process like this: `./console core:archive --php-cli-options="-d memory_limit=8G"` + * New less variable `@theme-color-menu-contrast-textSelected` that lets you specify the color of a selected menu item. + * in Administration > Diagnostics, there is a new page `Config file` which lets Super User view all config values from `global.ini.php` in the UI, and whether they were overriden in your `config/config.ini.php` + +### New commands + * New command `config:set` lets you set INI config options from the command line. This command can be used for convenience or for automation. + +### Internal changes + * `UsersManager.*` API calls: when an API request specifies a `token_auth` of a user with `admin` permission, the returned dataset will not include all usernames as previously, API will now only return usernames for users with `view` or `admin` permission to website(s) viewable by this `token_auth`. + * When generating a new plugin skeleton via `generate:plugin` command, plugin name must now contain only letters and numbers. + * JavaScript Tracker tests no longer require `SQLite`. The existing MySQL configuration for tests is used now. In order to run the tests make sure Piwik is installed and `[database_tests]` is configured in `config/config.ini.php`. + * The definitions for search engine and social network detection have been moved from bundled data files to a separate package (see [https://github.com/piwik/searchengine-and-social-list](https://github.com/piwik/searchengine-and-social-list)). + * In [UI screenshot tests](https://developer.piwik.org/guides/tests-ui), a test environment `configOverride` setting should be no longer overwritten. Instead new values should be added to the existing `configOverride` array in PHP or JavaScript. For example instead of `testEnvironment.configOverride = {group: {name: 1}}` use `testEnvironment.overrideConfig('group', 'name', '1')`. + +### New APIs + * Add your own SMS/Text provider by creating a new class in the `SMSProvider` directory of your plugin. The class has to extend `Piwik\Plugins\MobileMessaging\SMSProvider` and implement the required methods. + * Segments can now be composed by a union of multiple segments. To do this set an array of segments that shall be used for that segment `$segment->setUnionOfSegments(array('outlinkUrl', 'downloadUrl'))` instead of defining a SQL column. + +### Deprecations + * The method `DB::tableExists` was un-used and has been removed. + + +## Piwik 2.15.0 + +### New commands + * New command `diagnostics:analyze-archive-table` that analyzes archive tables + * New command `database:optimize-archive-tables` to optimize archive tables and possibly save disk space (even if on InnoDB) + * New Command `core:invalidate-report-data` to invalidate archive data (w/ period cascading) ([FAQ](https://piwik.org/faq/how-to/faq_155/)) + +### New APIs and features +* Piwik 2.15.0 is now mostly compatible with PHP7. +* The JavaScript Tracker `piwik.js` got a new method `logAllContentBlocksOnPage` to log all found content blocks within a page to the console. This is useful to debug / test content tracking. It can be triggered via `_paq.push(['logAllContentBlocksOnPage'])` +* The Class `Piwik\Plugins\Login\Controller` is now considered a public API. +* The new method `Piwik\Menu\MenuAbstract::registerMenuIcon()` can be used to define an icon for a menu category to replace the default arrow icon. +* New event `CronArchive.getIdSitesNotUsingTracker` that allows you to set a list of idSites that do not use the Tracker API to make sure we archive these sites if needed. +* New events `CronArchive.init.start` which is triggered when the CLI archiver starts and `CronArchive.end` when the archiver ended. +* Piwik tracker can now be configured with strict Content Security Policy ([CSP FAQ](https://piwik.org/faq/general/faq_20904/)). +* Super Users can choose whether to use the latest stable release or latest Long Term Support release. + +### Breaking Changes +* The method `Dimension::getId()` has been set as `final`. It is not allowed to overwrite this method. +* We fixed a bug where the API method `Sites.getPatternMatchSites` only returned a very limited number of websites by default. We now return all websites by default unless a limit is specified specifically. +* Handling of localized date, time and range formats has been changed. Patterns no longer contain placeholders like %shortDay%, but work with CLDR pattern instead. You can use one of the predefined format constants in Date class for using getLocalized(). +* As we are now using CLDR formats for all languages, some time formats were even changed in english. Attributes like prettyDate in API responses might so have been changed slightly. +* The config `enable_measure_piwik_usage_in_idsite` which is used to track the Piwik usage with Piwik was removed and replaced by a new plugin `AnonymousPiwikUsageMeasurement` + +### Deprecations +* The following HTTP API methods have been deprecated and will be removed in Piwik 3.0: + * `SitesManager.getSitesIdWithVisits` + * `API.getLastDate` +* The following events have been deprecated and will be removed in Piwik 3.0. Use [dimensions](http://developer.piwik.org/guides/dimensions) instead. + * `Tracker.existingVisitInformation` + * `Tracker.getVisitFieldsToPersist` + * `Tracker.newConversionInformation` + * `Tracker.newVisitorInformation` + * `Tracker.recordAction` + * `Tracker.recordEcommerceGoal` + * `Tracker.recordStandardGoals` +* The Platform API method `\Piwik\Plugin::getListHooksRegistered()` has been deprecated and will be removed in Piwik 3.0. Use `\Piwik\Plugin::registerEvents()` instead. + + +### Internal changes +* When logging in, the username is now case insensitive +* URLs with emojis and any other unicode character will be tracked, with special characters replaced with `�` +* A permanent warning notification is now displayed when PHP is 5.4.* or older, since it has reached End Of Life +* In `piwik.js` we replaced [JSON2](https://github.com/douglascrockford/JSON-js) with [JSON3](https://bestiejs.github.io/json3/) to implement CSP (Content Security Policy) as JSON3 does not use `eval()`. JSON3 will be used if a browser does not provide a native JSON API. We are using `JSON3` in a way that it will not conflict if your website is using `JSON3` as well. +* The option `branch` of the console command `development:sync-system-test-processed` was removed as it is no longer needed. +* All numbers in reports will now appear formatted (eg. `1,000,000` instead of `1000000`) +* Database connections now use `UTF-8` charset explicitely to force UTF-8 data handling + +## Piwik 2.14.0 + +### Breaking Changes +* The `UserSettings` API has been removed. The API was deprecated in earlier versions. Use `DevicesDetection`, `Resolution` and `DevicePlugins` API instead. +* Many translations have been moved to the new Intl plugin. Most of them will still work, but please update their usage. See https://github.com/piwik/piwik/pull/8101 for a full list + +### New features +* The JavaScript Tracker does now track outlinks and downloads if a user opens the context menu if the `enabled` parameter of the `enableLinkTracking()` method is set to `true`. To use this new feature use `tracker.enableLinkTracking(true)` or `_paq.push(['enableLinkTracking', true]);`. This is not industry standard and is vulnerable to false positives since not every user will select "Open in a new tab" when the context menu is shown. Most users will do though and it will lead to more accurate results in most cases. +* The JavaScript Tracker now contains the 'heart beat' feature which can be used to obtain more accurate visit lengths by periodically sending 'ping' requests to Piwik. To use this feature use `tracker.enableHeartBeatTimer();` or `_paq.push(['enableHeartBeatTimer']);`. By default, a ping request will be sent every 15 seconds. You can specify a custom ping delay (in seconds) by passing an argument, eg, `tracker.enableHeartBeatTimer(10);` or `_paq.push(['enableHeartBeatTimer', 10]);`. +* New custom segment `languageCode` that lets you segment visitors that are using a particular language. Example values: `de`, `fr`, `en-gb`, `zh-cn`, etc. +* Segment `userId` now supports any segment operator (previously only operator Contains `=@` was supported for this segment). + +### Commands updates +* The command `core:archive` now has two new parameter: `--force-idsegments` and `--skip-idsegments` that let you force (or skip) processing archives for one or several custom segments. +* The command `scheduled-tasks:run` now has an argument `task` that lets you force run a particular scheduled task. + +### Library updates +* Updated pChart library from 2.1.3 to 2.1.4. The files were moved from the directory `libs/pChart2.1.3` to `libs/pChart` + +### Internal change +* To execute UI tests "ImageMagick" is now required. +* The Q JavaScript promise library is now distributed with tests and can be used in the piwik.js tests. + +## Piwik 2.13.0 + +### Breaking Changes +* The API method `Live.getLastVisitsDetails` does no longer support the API parameter `filter_sort_column` to prevent possible memory issues when `filter_offset` is large. +* The Event `Site.setSite` was removed as it causes performance problems. +* `piwik.php` does now return a HTTP 400 (Bad request) if requested without any tracking parameters (GET/POST). If you still want to use `piwik.php` for checks please use `piwik.php?rec=0`. + +### Deprecations +* The method `Piwik\Archive::getBlob()` has been deprecated and will be removed from June 1st 2015. Use one of the methods `getDataTable*()` methods instead. +* The API parameter `countVisitorsToFetch` of the API method `Live.getLastVisitsDetails` has been deprecated as `filter_offset` and `filter_limit` work correctly now. + +### New commands +* There is now a `diagnostic:run` command to run the system check from the command line. +* There is now an option `--xhprof` that can be used with any command to profile that command via XHProf. + +### APIs Improvements +* Visitor details now additionally contain: `deviceTypeIcon`, `deviceBrand` and `deviceModel` +* In 2.6.0 we added the possibility to use `filter_limit` and `filter_offset` if an API returns an indexed array. This was not working in all cases and is fixed now. +* The API parameter `filter_pattern` and `filter_offset[]` can now be used if an API returns an indexed array. + +### Internal changes + +* The referrer spam filter has moved from the `referrer_urls_spam` INI option (in `global.ini.php`) to a separate package (see [https://github.com/piwik/referrer-spam-blacklist](https://github.com/piwik/referrer-spam-blacklist)). + +## Piwik 2.12.0 + +### Breaking Changes +* The deprecated method `Period::factory()` has been removed. Use `Period\Factory` instead. +* The deprecated method `Config::getConfigSuperUserForBackwardCompatibility()` has been removed. +* The deprecated methods `MenuAdmin::addEntry()` and `MenuAdmin::removeEntry()` have been removed. Use `Piwik\Plugin\Menu` instead. +* The deprecated methods `MenuTop::addEntry()` and `MenuTop::removeEntry()` have been removed. Use `Piwik\Plugin\Menu` instead. +* The deprecated method `SettingsPiwik::rewriteTmpPathWithInstanceId()` has been removed. +* The following deprecated methods from the `Piwik\IP` class have been removed, use `Piwik\Network\IP` instead: + * `sanitizeIp()` + * `sanitizeIpRange()` + * `P2N()` + * `N2P()` + * `prettyPrint()` + * `isIPv4()` + * `long2ip()` + * `isIPv6()` + * `isMappedIPv4()` + * `getIPv4FromMappedIPv6()` + * `getIpsForRange()` + * `isIpInRange()` + * `getHostByAddr()` + +### Deprecations +* `API` classes should no longer have a protected constructor. Classes with a protected constructor will generate a notice in the logs and should expose a public constructor instead. +* Update classes should not declare static `getSql()` and `update()` methods anymore. It is still supported to use those, but developers should instead override the `Updates::getMigrationQueries()` and `Updates::doUpdate()` instance methods. + +### New features +* `API` classes can now use dependency injection in their constructor to inject other instances. + +### New commands +* There is now a command `core:purge-old-archive-data` that can be used to manually purge temporary, error-ed and invalidated archives from one or more archive tables. +* There is now a command `usercountry:attribute` that can be used to re-attribute geolocated location data to existing visits and conversions. If you have visits that were tracked before setting up GeoIP, you can use this command to add location data to them. + +## Piwik 2.11.0 + +### Breaking Changes +* The event `User.getLanguage` has been removed. +* The following deprecated event has been removed: `TaskScheduler.getScheduledTasks` +* Special handling for operating system `Windows` has been removed. Like other operating systems all versions will now only be reported as `Windows` with versions like `XP`, `7`, `8`, etc. +* Reporting for operating systems has been adjusted to report information according to browser information. Visitor details now contain: `operatingSystemName`, `operatingSystemIcon`, `operatingSystemCode` and `operatingSystemVersion` + +### Deprecations +* The following methods have been deprecated in favor of the new `Piwik\Intl` component: + * `Piwik\Common::getContinentsList()`: use `RegionDataProvider::getContinentList()` instead + * `Piwik\Common::getCountriesList()`: use `RegionDataProvider::getCountryList()` instead + * `Piwik\Common::getLanguagesList()`: use `LanguageDataProvider::getLanguageList()` instead + * `Piwik\Common::getLanguageToCountryList()`: use `LanguageDataProvider::getLanguageToCountryList()` instead + * `Piwik\Metrics\Formatter::getCurrencyList()`: use `CurrencyDataProvider::getCurrencyList()` instead +* The `Piwik\Translate` class has been deprecated in favor of `Piwik\Translation\Translator`. +* The `core:plugin` console has been deprecated in favor of the new `plugin:list`, `plugin:activate` and `plugin:deactivate` commands +* The following classes have been deprecated: + * `Piwik\TaskScheduler`: use `Piwik\Scheduler\Scheduler` instead + * `Piwik\ScheduledTask`: use `Piwik\Scheduler\Task` instead +* The API method `UserSettings.getLanguage` is deprecated and will be removed from May 1st 2015. Use `UserLanguage.getLanguage` instead +* The API method `UserSettings.getLanguageCode` is deprecated and will be removed from May 1st 2015. Use `UserLanguage.getLanguageCode` instead +* The `Piwik\Registry` class has been deprecated in favor of using the container: + * `Registry::get('auth')` should be replaced with `StaticContainer::get('Piwik\Auth')` + * `Registry::set('auth', $auth)` should be replaced with `StaticContainer::getContainer()->set('Piwik\Auth', $auth)` + +### New features +* You can now generate UI / screenshot tests using the command `generate:test` +* During UI tests we do now add a CSS class to the HTML element called `uiTest`. This allows you do hide content when screenshots are captured. + +### New commands +* A new command (core:fix-duplicate-log-actions) has been added which can be used to remove duplicate actions and correct references to them in other tables. Duplicates were caused by this bug: [#6436](https://github.com/piwik/piwik/issues/6436) + +### Library updates +* Updated AngularJS from 1.2.26 to 1.2.28 +* Updated piwik/device-detector from 2.8 to 3.0 + +### Internal change +* UI specs were moved from `tests/PHPUnit/UI` to `tests/UI`. We also moved the UI specs directly into the Piwik repository meaning the [piwik-ui-tests](https://github.com/piwik/piwik-ui-tests) repository contains only the expected screenshots from now on. +* There is a new command `development:sync-system-test-processed` for core developers that allows you to copy processed test results from travis to your local dev environment. + +## Piwik 2.10.0 + +### Breaking Changes +* API responses containing visitor information will no longer contain the fields `screenType` and `screenTypeIcon` as those reports have been completely removed +* os, browser and browser plugin icons are now located in the DevicesDetection and DevicePlugins plugin. If you are not using the Reporting or Metadata API to get the icon locations please update your paths. +* The deprecated method `Piwik\SettingsPiwik::rewriteTmpPathWithHostname()` has been removed. +* The following events have been removed: + * `Log.formatFileMessage` + * `Log.formatDatabaseMessage` + * `Log.formatScreenMessage` + * These events have been removed as Piwik now uses the Monolog logging library. [Learn more.](http://developer.piwik.org/guides/logging) +* The event `Log.getAvailableWriters` has been removed: to add custom log backends, you now need to configure Monolog handlers +* The INI options `log_only_when_cli` and `log_only_when_debug_parameter` have been removed + +### Library updates +* We added the `symfony/var-dumper` library allowing you to better print any arbitrary PHP variable via `dump($var1, $var2, ...)`. +* Piwik now uses [Monolog](https://github.com/Seldaek/monolog) as a logger. +* The tracker proxy (previously in `misc/proxy-hide-piwik-url/`) has been moved to a separate repository: [https://github.com/piwik/tracker-proxy](https://github.com/piwik/tracker-proxy). + +### Deprecations +* Some duplicate reports from UserSettings plugin have been removed. Widget URLs for those reports will still work till May 1st 2015. Please update those to the new reports of DevicesDetection plugin. +* The API method `UserSettings.getBrowserVersion` is deprecated and will be removed from May 1st 2015. Use `DevicesDetection.getBrowserVersions` instead +* The API method `UserSettings.getBrowser` is deprecated and will be removed from May 1st 2015. Use `DevicesDetection.getBrowsers` instead +* The API method `UserSettings.getOSFamily` is deprecated and will be removed from May 1st 2015. Use `DevicesDetection.getOsFamilies` instead +* The API method `UserSettings.getOS` is deprecated and will be removed from May 1st 2015. Use `DevicesDetection.getOsVersions` instead +* The API method `UserSettings.getMobileVsDesktop` is deprecated and will be removed from May 1st 2015. Use `DevicesDetection.getType` instead +* The API method `UserSettings.getBrowserType` is deprecated and will be removed from May 1st 2015. Use `DevicesDetection.getBrowserEngines` instead +* The API method `UserSettings.getResolution` is deprecated and will be removed from May 1st 2015. Use `Resolution.getResolution` instead +* The API method `UserSettings.getConfiguration` is deprecated and will be removed from May 1st 2015. Use `Resolution.getConfiguration` instead +* The API method `UserSettings.getPlugin` is deprecated and will be removed from May 1st 2015. Use `DevicePlugins.getPlugin` instead +* The API method `UserSettings.getWideScreen` has been removed. Use `UserSettings.getScreenType` instead. +* `Piwik\SettingsPiwik::rewriteTmpPathWithInstanceId()` has been deprecated. Instead of hardcoding the `tmp/` path everywhere in the codebase and then calling `rewriteTmpPathWithInstanceId()`, developers should get the `path.tmp` configuration value from the DI container (e.g. `StaticContainer::getContainer()->get('path.tmp')`). +* The method `Piwik\Log::setLogLevel()` has been deprecated +* The method `Piwik\Log::getLogLevel()` has been deprecated + +## Piwik 2.9.1 + +### Breaking Changes +* The HTTP Tracker API does now respond with a HTTP 400 instead of a HTTP 500 in case an invalid `idsite` is used + +### New APIs +* New URL parameter `send_image=0` in the [HTTP Tracking API](http://developer.piwik.org/api-reference/tracking-api) to receive a HTTP 204 response code instead of a GIF image. This improves performance and can fix errors if images are not allowed to be obtained directly (eg Chrome Apps). + +### New commands +* `core:plugin list` lists all plugins currently activated in Piwik. + +## Piwik 2.9.0 + +### Breaking Changes +* Development related [console commands](http://developer.piwik.org/guides/piwik-on-the-command-line) are only available if the development mode is enabled. To enable the development mode execute `./console development:enable`. +* The command `php console core:update` does no longer have a parameter `--dry-run`. A dry run is now executed by default followed by a question whether one actually wants to execute the updates. To skip this confirmation step one can use the `--yes` option. + +### Deprecations +* Most methods of `Piwik\IP` have been deprecated in favor of the new [piwik/network](https://github.com/piwik/component-network) component. +* The file `tests/PHPUnit/phpunit.xml` is no longer needed in order to run tests and we suggest to delete it. The test configuration is now done automatically if possible. In case the tests do no longer work check out the `[tests]` section in `config/global.ini.php` + +### Library updates +* Code for manipulating IP addresses has been moved to a separate standalone component: [piwik/network](https://github.com/piwik/component-network). Backward compatibility is kept in Piwik core. + +## Piwik 2.8.2 + +### Library updates +* Updated AngularJS from 1.2.25 to 1.2.26 +* Updated jQuery from 1.11.0 to 1.11.1 + +## Piwik 2.8.0 + +### Breaking Changes +* The Auth interface has been modified, existing Auth implementations will have to be modified. Changes include: + * The initSession method has been moved. Since this behavior must be executed for every Auth implementation, it has been put into a new class: SessionInitializer. + If your Auth implementation implements its own session logic you will have to extend and override SessionInitializer. + * The following methods have been added: setPassword, setPasswordHash, getTokenAuthSecret and getLogin. + * Clarifying semantics of each method and what they must support and can support. + * **Read the documentation for the [Auth interface](http://developer.piwik.org/api-reference/Piwik/Auth) to learn more.** +* The `Piwik\Unzip\*` classes have been extracted out of the Piwik repository into a separate component named [Decompress](https://github.com/piwik/component-decompress). + * `Piwik\Unzip` has not moved, it is kept for backward compatibility. If you have been using that class, you don't need to change anything. + * The `Piwik\Unzip\*` classes (Tar, PclZip, Gzip, ZipArchive) have moved to the `Piwik\Decompress\*` namespace (inside the new repository). + * `Piwik\Unzip\UncompressInterface` has been moved and renamed to `Piwik\Decompress\DecompressInterface` (inside the new repository). + +### Deprecations +* The `Piwik::setUserHasSuperUserAccess` method is deprecated, instead use Access::doAsSuperUser. This method will ensure that super user access is properly rescinded after the callback finishes. +* The class `\IntegrationTestCase` is deprecated and will be removed from February 6th 2015. Use `\Piwik\Tests\Framework\TestCase\SystemTestCase` instead. +* The class `\DatabaseTestCase` is deprecated and will be removed from February 6th 2015. Use `\Piwik\Tests\Framework\TestCase\IntegrationTestCase` instead. +* The class `\BenchmarkTestCase` is deprecated and will be removed from February 6th 2015. Use `\Piwik\Tests\Framework\TestCase\BenchmarkTestCase` instead. +* The class `\ConsoleCommandTestCase` is deprecated and will be removed from February 6th 2015. Use `\Piwik\Tests\Framework\TestCase\ConsoleCommandTestCase` instead. +* The class `\FakeAccess` is deprecated and will be removed from February 6th 2015. Use `\Piwik\Tests\Framework\Mock\FakeAccess` instead. +* The class `\Piwik\Tests\Fixture` is deprecated and will be removed from February 6th 2015. Use `\Piwik\Tests\Framework\Fixture` instead. +* The class `\Piwik\Tests\OverrideLogin` is deprecated and will be removed from February 6ths 2015. Use `\Piwik\Framework\Framework\OverrideLogin` instead. + +### New API Features +* The pivotBy and related query parameters can be used to pivot reports by another dimension. Read more about the new query parameters [here](http://developer.piwik.org/api-reference/reporting-api#optional-api-parameters). + +### Library updates +* Updated AngularJS from 1.2.13 to 1.2.25 + +### New commands +* `generate:angular-directive` Let's you easily generate a template for a new angular directive for any plugin. + +### Internal change +* Piwik 2.8.0 now requires PHP >= 5.3.3. + * If you use an older PHP version, please upgrade now to the latest PHP so you can enjoy improvements and security fixes in Piwik. + +## Piwik 2.7.0 + +### Reporting APIs +* Several APIs will now expose a new metric `nb_users` which measures the number of unique users when a [User ID](http://piwik.org/docs/user-id/) is set. +* New APIs have been added for [Content Tracking](http://piwik.org/docs/content-tracking/) feature: Contents.getContentNames, Contents.getContentPieces + +### Deprecations +* The `Piwik\Menu\MenuAbstract::add()` method is deprecated in favor of `addItem()`. Read more about this here: [#6140](https://github.com/piwik/piwik/issues/6140). We do not plan to remove the deprecated method before Piwik 3.0. + +### New APIs +* It is now easier to generate the URL for a menu item see [#6140](https://github.com/piwik/piwik/issues/6140), [urlForDefaultAction()](http://developer.piwik.org/api-reference/Piwik/Plugin/Menu#urlfordefaultaction), [urlForAction()](http://developer.piwik.org/api-reference/Piwik/Plugin/Menu#urlforaction), [urlForModuleAction()](http://developer.piwik.org/api-reference/Piwik/Plugin/Menu#urlformoduleaction) + +### New commands +* `core:clear-caches` Lets you easily delete all caches. This command can be useful for instance after updating Piwik files manually. + + +## Piwik 2.6.0 + +### Deprecations +* The `'json'` API format is considered deprecated. We ask all new code to use the `'json2'` format. Eventually when Piwik 3.0 is released the `'json'` format will be replaced with `'json2'`. Differences in the json2 format include: + * A bug in JSON formatting was fixed so API methods that return simple associative arrays like `array('name' => 'value', 'name2' => 'value2')` will now appear correctly as `{"name":"value","name2":"value2"}` in JSON API output instead of `[{"name":"value","name2":"value2"}]`. API methods like **SitesManager.getSiteFromId** & **UsersManager.getUser** are affected. + +#### Reporting API +* If an API returns an indexed array, it is now possible to use `filter_limit` and `filter_offset`. This was before only possible if an API returned a DataTable. +* The Live API now returns only visitor information of activated plugins. So if for instance the Referrers plugin is deactivated a visitor won't contain any referrers related properties. This is a bugfix as the API was crashing before if some core plugins were deactivated. Affected methods are for instance `getLastVisitDetails` or `getVisitorProfile`. If all core plugins are enabled as by default there will be no change at all except the order of the properties within one visitor. + +### New commands +* `core:run-scheduled-tasks` Let's you run all scheduled tasks due to run at this time. Useful for instance when testing tasks. + +#### Internal change + * We removed our own autoloader that was used to load Piwik files in favor of the composer autoloader which we already have been using for some libraries. This means the file `core/Loader.php` will no longer exist. In case you are using Piwik from Git make sure to run `php composer.phar self-update && php composer.phar install` to make your Piwik work again. Also make sure to no longer include `core/Loader.php` in case it is used in any custom script. + * We do no longer store the list of plugins that are used during tracking in the config file. They are dynamically detect instead. The detection of a tracker plugin works the same as before. A plugin has to either listen to any `Tracker.*` or `Request.initAuthenticationObject` event or it has to define dimensions in order to be detected as a tracker plugin. + +## Piwik 2.5.0 + +### Breaking Changes +* Javascript Tracking API: if you are using `getCustomVariable` function to access custom variables values that were set on previous page views, you now must also call `storeCustomVariablesInCookie` before the first call to `trackPageView`. Read more about [Javascript Tracking here](http://developer.piwik.org/api-reference/tracking-javascript). +* The [settings](http://developer.piwik.org/guides/piwik-configuration) API will receive the actual entered value and will no longer convert characters like `&` to `&`. If you still want this behavior - for instance to prevent XSS - you can define a filter by setting the `transform` property like this: + `$setting->transform = function ($value) { return Common::sanitizeInputValue($value); }` +* Config setting `disable_merged_assets` moved from `Debug` section to `Development`. The updater will automatically change the section for you. +* `API.getRowEvolution` will throw an exception if a report is requested that does not have a dimension, for instance `VisitsSummary.get`. This is a fix as an invalid format was returned before see [#5951](https://github.com/piwik/piwik/issues/5951) +* `MultiSites.getAll` returns from now on always an array of websites. In the past it returned a single object and it didn't contain all properties in case only one website was found which was a bug see [#5987](https://github.com/piwik/piwik/issues/5987) + +### Deprecations +The following events are considered as deprecated and the new structure should be used in the future. We have not scheduled when those events will be removed but probably in Piwik 3.0 which is not scheduled yet and won't be soon. New features will be added only to the new classes. + +* `API.getReportMetadata`, `API.getSegmentDimensionMetadata`, `Goals.getReportsWithGoalMetrics`, `ViewDataTable.configure`, `ViewDataTable.getDefaultType`: use [Report](http://developer.piwik.org/api-reference/Piwik/Plugin/Report) class instead to define new reports. There is an updated guide as well [Part1](http://developer.piwik.org/guides/getting-started-part-1) +* `WidgetsList.addWidgets`: use [Widgets](http://developer.piwik.org/api-reference/Piwik/Plugin/Widgets) class instead to define new widgets +* `Menu.Admin.addItems`, `Menu.Reporting.addItems`, `Menu.Top.addItems`: use [Menu](http://developer.piwik.org/api-reference/Piwik/Plugin/Menu) class instead +* `TaskScheduler.getScheduledTasks`: use [Tasks](http://developer.piwik.org/api-reference/Piwik/Plugin/Tasks) class instead to define new tasks +* `Tracker.recordEcommerceGoal`, `Tracker.recordStandardGoals`, `Tracker.newConversionInformation`: use [Conversion Dimension](http://developer.piwik.org/api-reference/Piwik/Plugin/Dimension/ConversionDimension) class instead +* `Tracker.existingVisitInformation`, `Tracker.newVisitorInformation`, `Tracker.getVisitFieldsToPersist`: use [Visit Dimension](http://developer.piwik.org/api-reference/Piwik/Plugin/Dimension/VisitDimension) class instead +* `ViewDataTable.addViewDataTable`: This event is no longer needed. Visualizations are automatically discovered if they are placed within a `Visualizations` directory inside the plugin. + +### New features + +#### Translation search +As a plugin developer you might want to reuse existing translation keys. You can now find all available translations and translation keys by opening the page "Settings => Development:Translation search" in your Piwik installation. Read more about [internationalization](http://developer.piwik.org/guides/internationalization) here. + +#### Reporting API +It is now possible to use the `filter_sort_column` parameter when requesting `Live.getLastVisitDetails`. For instance `&filter_sort_column=visitCount`. + +#### @since annotation +We are using `@since` annotations in case we are introducing new API's to make it easy to see in which Piwik version a new method was added. This information is now displayed in the [Classes API-Reference](http://developer.piwik.org/api-reference/classes). + +### New APIs +* [Report](http://developer.piwik.org/api-reference/Piwik/Plugin/Report) to add a new report +* [Action Dimension](http://developer.piwik.org/api-reference/Piwik/Plugin/Dimension/ActionDimension) to add a dimension that tracks action related information +* [Visit Dimension](http://developer.piwik.org/api-reference/Piwik/Plugin/Dimension/VisitDimension) to add a dimension that tracks visit related information +* [Conversion Dimension](http://developer.piwik.org/api-reference/Piwik/Plugin/Dimension/ConversionDimension) to add a dimension that tracks conversion related information +* [Dimension](http://developer.piwik.org/api-reference/Piwik/Columns/Dimension) to add a basic non tracking dimension that can be used in `Reports` +* [Widgets](http://developer.piwik.org/api-reference/Piwik/Plugin/Widgets) to add or modfiy widgets +* These Menu classes got new methods that make it easier to add new items to a specific section + * [MenuAdmin](http://developer.piwik.org/api-reference/Piwik/Menu/MenuAdmin) to add or modify admin menu items. + * [MenuReporting](http://developer.piwik.org/api-reference/Piwik/Menu/MenuReporting) to add or modify reporting menu items + * [MenuUser](http://developer.piwik.org/api-reference/Piwik/Menu/MenuUser) to add or modify user menu items +* [Tasks](http://developer.piwik.org/api-reference/Piwik/Plugin/Tasks) to add scheduled tasks + +### New commands +* `generate:theme` Let's you easily generate a new theme and customize colors, see the [Theming guide](http://developer.piwik.org/guides/theming) +* `generate:update` Let's you generate an update file +* `generate:report` Let's you generate a report +* `generate:dimension` Let's you enhance the tracking by adding new dimensions +* `generate:menu` Let's you generate a menu class to add or modify menu items +* `generate:widgets` Let's you generate a widgets class to add or modify widgets +* `generate:tasks` Let's you generate a tasks class to add or modify tasks +* `development:enable` Let's you enable the development mode which will will disable some caching to make code changes directly visible and it will assist developers by performing additional checks to prevent for instance typos. Should not be used in production. +* `development:disable` Let's you disable the development mode + + + +Find the general Piwik Changelogs for each release at [piwik.org/changelog](http://piwik.org/changelog/) + diff --git a/www/analytics/CONTRIBUTING.md b/www/analytics/CONTRIBUTING.md new file mode 100644 index 00000000..f24e51b4 --- /dev/null +++ b/www/analytics/CONTRIBUTING.md @@ -0,0 +1,12 @@ +# How to contribute + +Great to have you here! Read the following guide on our developer zone to learn how you can help make this project better! + +http://developer.piwik.org/guides/contributing-to-piwik-core + +## How to submit a bug report or suggest a feature? +Please read the recommendations on writing a good [bug report](http://developer.piwik.org/guides/core-team-workflow#submitting-a-bug-report) or [feature request](http://developer.piwik.org/guides/core-team-workflow#submitting-a-feature-request). + +## How to suggest improvements to translations? + +You can help improve translations in Piwik, please read [contribute to translations](https://github.com/piwik/piwik/blob/master/lang/README.md). diff --git a/www/analytics/LEGALNOTICE b/www/analytics/LEGALNOTICE index e95df846..1ab89f00 100644 --- a/www/analytics/LEGALNOTICE +++ b/www/analytics/LEGALNOTICE @@ -1,10 +1,10 @@ COPYRIGHT - Piwik - Open Source Web Analytics + Piwik - free/libre analytics platform The software package is: - Copyright (C) 2013 Matthieu Aubry + Copyright (C) 2014 Matthieu Aubry Individual contributions, components, and libraries are copyright of their respective authors. @@ -40,7 +40,7 @@ CREDITS For detailed contribution history, refer to the source, tickets, patches, and Git revision history, available at - http://dev.piwik.org/trac/ + https://github.com/piwik/piwik/issues https://github.com/piwik/piwik @@ -65,9 +65,17 @@ SEPARATELY LICENSED COMPONENTS AND LIBRARIES Link: https://github.com/piwik/piwik/blob/master/libs/PiwikTracker/ License: New BSD - Name: UserAgentParser - Link: https://github.com/piwik/piwik/blob/master/libs/UserAgentParser/ - License: New BSD + Name: DeviceDetector + Link: https://github.com/piwik/device-detector + License: LGPL + + Name: Piwik/Decompress + Link: https://github.com/piwik/component-decompress + License: LGPL v3.0 + + Name: Piwik/Network + Link: https://github.com/piwik/component-network + License: LGPL v3.0 THIRD-PARTY COMPONENTS AND LIBRARIES @@ -77,40 +85,40 @@ THIRD-PARTY COMPONENTS AND LIBRARIES Name: jqPlot Link: http://www.jqplot.com/ - License: Dual-licensed: MIT or GPL v2 - + License: Dual-licensed: MIT (Expat) or GPL v2 + Name: jQuery Link: http://jquery.com/ - License: Dual-licensed: MIT or GPL + License: Dual-licensed: MIT (Expat) or GPL Notes: - GPL version not explicitly stated in source but GPL v2 is in git - - includes Sizzle.js - multi-licensed: MIT, New BSD, or GPL [v2] + - includes Sizzle.js - multi-licensed: MIT (Expat), New BSD, or GPL [v2] Name: jQuery UI Link: http://jqueryui.com/ - License: Dual-licensed: MIT or GPL + License: Dual-licensed: MIT (Expat) or GPL Notes: - GPL version not explicitly stated in source but GPL v2 is in git Name: jquery.history Link: http://tkyk.github.com/jquery-history-plugin/ - License: MIT + License: MIT (Expat) Name: jquery.scrollTo Link: http://plugins.jquery.com/project/ScrollTo - License: Dual licensed: MIT or GPL + License: Dual licensed: MIT (Expat) or GPL Name: jquery Tooltip Link: http://bassistance.de/jquery-plugins/jquery-plugin-tooltip/ - License: Dual licensed: MIT or GPL + License: Dual licensed: MIT (Expat) or GPL Name: jquery placeholder Link: http://mths.be/placeholder - License: Dual licensed: MIT or GPL + License: Dual licensed: MIT (Expat) or GPL Name: jquery smartbanner Link: https://github.com/jasny/jquery.smartbanner - License: Dual licensed: MIT + License: Dual licensed: MIT (Expat) Name: json2.js Link: http://json.org/ @@ -187,8 +195,8 @@ THIRD-PARTY COMPONENTS AND LIBRARIES Name: Zend Framework Link: http://www.zendframework.com/ License: New BSD - - Name: pChart 2.1.3 + + Name: pChart 2.1.4 Link: http://www.pchart.net License: GPL v3 @@ -206,15 +214,27 @@ THIRD-PARTY COMPONENTS AND LIBRARIES Name: Raphaël - JavaScript Vector Library Link: http://raphaeljs.com/ - License: MIT + License: MIT (Expat) Name: lessphp Link: http://leafo.net/lessphp - License: GPL3/MIT + License: GPL3, MIT (Expat) Name: Symfony Console Component Link: https://github.com/symfony/Console - License: MIT + License: MIT (Expat) + + Name: AngularJS + Link: https://github.com/angular/angular.js + License: MIT (Expat) + + Name: Mousetrap + Link: https://github.com/ccampbell/mousetrap + License: Apache 2.0 + + Name: PHP-DI + Link: http://php-di.org/ + License: MIT (Expat) THIRD-PARTY CONTENT @@ -235,10 +255,6 @@ THIRD-PARTY CONTENT Notes: - used in ImageGraph plugin - Name: plugins/CorePluginsAdmin/images/themes.png - Link: https://www.iconfinder.com/icons/17022/colors_draw_paint_icon - License: Free for commercial use - Name: plugins/Feedback/angularjs/ratefeature/thumbs-down.png Link: https://www.iconfinder.com/icons/216428/down_thumbs_icon License: Creative Commons (Attribution-Share Alike 3.0 Unported) @@ -247,10 +263,6 @@ THIRD-PARTY CONTENT Link: https://www.iconfinder.com/icons/216429/thumbs_up_icon License: Creative Commons (Attribution-Share Alike 3.0 Unported) - Name: plugins/CorePluginsAdmin/images/plugins.png - Link: http://findicons.com/icon/94051/tools_wizard?id=396912 - License: GNU/GPL - Name: plugins/Insights/images/idea.png Link: https://www.iconfinder.com/icons/6074/brainstorm_bulb_idea_jabber_light_icon License: GPL @@ -259,7 +271,6 @@ THIRD-PARTY CONTENT Notes: - the "New BSD" license refers to either the "Modified BSD" and "Simplified BSD" licenses (2- or 3-clause), which are GPL compatible. -- the "MIT" license is also referred to as the "X11" license - icons for browsers, operating systems, browser plugins, search engines, and and flags of countries are nominative use of third-party trademarks when referring to the corresponding product or entity diff --git a/www/analytics/PRIVACY.md b/www/analytics/PRIVACY.md new file mode 100644 index 00000000..ecccfe8f --- /dev/null +++ b/www/analytics/PRIVACY.md @@ -0,0 +1,60 @@ +# Privacy +This is a summary of all of the components within Piwik which may affect your privacy in some way. Please keep in mind +third party Themes, Plugins or Apps may introduce privacy concerns not listed here. + +## Privacy for users being tracked by Piwik +In this section we document how to protect the privacy of visitors who are tracked by your Piwik analytics service. + +### Anonymise visitor IP addresses +By default, Piwik stores the visitor IP address (IPv4 or IPv6 format) in the database for each new visitor. +If a visitor has a static IP address this means her browsing history can be easily identified across several days and +even across several websites tracked within the same Piwik server. You can anonymize IP addresses to ensure visitors cannot +be tracked this way: [How to anonymise IP addresses.](http://piwik.org/docs/privacy/#step-1-automatically-anonymize-visitor-ips) + +### Delete old visitors logs +By default, Piwik stores tracked data forever. To better respect the privacy of your users, it is recommended to regularly +purge old data. You can configure Piwik to automatically delete log data older than a specified number of months: +[How to delete old visitors log data.](http://piwik.org/docs/privacy/#step-2-delete-old-visitors-logs) + +### Include a tracking Opt-Out feature on your site +In your website, we recommended providing an easy way for your visitors to “opt-out” of being tracked by Piwik. +You can use the Opt-Out feature to display a link your website that sets a special browser cookie (`piwik_ignore`) when +clicked. Visitors that click that link will be ignored by Piwik in the future: +[How to include a tracking opt-out iframe.](http://piwik.org/docs/privacy/#step-3-include-a-web-analytics-opt-out-feature-on-your-site-using-an-iframe) + +### Respect DoNotTrack preference +Do Not Track is a browser-level technology and policy proposal that lets visitors opt out of tracking by websites they +do not visit. Visitors can enable this preference in their browser, and then it's up to Piwik to respect it. By default, +Piwik is configured to ignore visitors that have enabled it: +[How to check if your Piwik respects DoNotTrack.] (http://piwik.org/docs/privacy/#step-4-respect-donottrack-preference) + +### Disable tracking cookies +A cookie is a collection of information that a website stores on a visitor’s computer and accesses each time the visitor +returns. By default, Piwik uses cookies to aid in tracking visitor behavior. If someone gains access to a visitor's +computer, they could learn a few things about how the visitor visited your website. For many websites, this isn't a +problem, but for others where a strong level of privacy is required (like online banking), disabling tracking cookies may +be a good idea: [How to disable tracking cookies.](http://piwik.org/faq/general/faq_157/) + +### Keep your visitors details private +Any user that has at least `view` access (the default access level) to Piwik can view detailed information for all users +tracked in Piwik (such as their IP addresses, visitor IDs, details of all past visits and actions, etc.) through features +provided by the `Live` plugin (such as the Visitor Log and Visitor Profile). As the Piwik administrator, you may decide +that not all of your users need access to this data. You can deactivate the `Live` plugin to prevent users from viewing +visitor details in the Administration > Plugins page. + +## Privacy for Piwik admins and website owners +In this section we document how a Piwik administrator can better protect their own privacy. + +### Keep your Piwik server URL private +By default, the Piwik Javascript code on all tracked websites contains the Piwik server URL. In some cases you might +want to hide this Piwik URL completely while still tracking all websites in your Piwik instance. To hide your Piwik +server's URL, you can modify the Javascript Tracking code and point it to a proxy piwik.php script instead of your actual +Piwik server: [How to keep Piwik server URL private.](http://piwik.org/faq/how-to/faq_132/) + +### Automatic update check +From time to time, Piwik uses `api.piwik.org` to check if the current version of Piwik is the latest version of Piwik. +If an update is available, a notification is displayed allowing you to upgrade Piwik. To disable the update check, +and stop your instance from sending HTTP requests to `api.piwik.org`, deactivate the "Automatic update" feature by +setting `enable_auto_update = 0` in your configuration file `config/config.ini.php`. + +Learn more about [Privacy in Piwik](http://piwik.org/privacy/). diff --git a/www/analytics/README.md b/www/analytics/README.md index 7894effd..0b01769e 100644 --- a/www/analytics/README.md +++ b/www/analytics/README.md @@ -1,14 +1,27 @@ -# Piwik - piwik.org +# Piwik - piwik.org + +[![Latest Stable Version](https://poser.pugx.org/piwik/piwik/v/stable)](https://packagist.org/packages/piwik/piwik) +[![Latest Unstable Version](https://poser.pugx.org/piwik/piwik/v/unstable)](https://packagist.org/packages/piwik/piwik) +[![Total Downloads](https://poser.pugx.org/piwik/piwik/downloads)](https://packagist.org/packages/piwik/piwik) +[![License](https://poser.pugx.org/piwik/piwik/license)](https://packagist.org/packages/piwik/piwik) + +## Code Status + +[![Build Status](https://travis-ci.org/piwik/piwik.svg?branch=master)](https://travis-ci.org/piwik/piwik) +[![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/piwik/piwik.svg)](https://scrutinizer-ci.com/g/piwik/piwik?branch=master) +[![Code Coverage](https://scrutinizer-ci.com/g/piwik/piwik/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/piwik/piwik/?branch=master) +[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/piwik/piwik.svg)](http://isitmaintained.com/project/piwik/piwik "Average time to resolve an issue") +[![Percentage of issues still open](http://isitmaintained.com/badge/open/piwik/piwik.svg)](http://isitmaintained.com/project/piwik/piwik "Percentage of issues still open") ## Description -Piwik is the leading Free/Libre open source Web Analytics platform. +Piwik is the leading Free/Libre open analytics platform. Piwik is a full featured PHP MySQL software program that you download and install on your own webserver. At the end of the five minute installation process you will be given a JavaScript code. Simply copy and paste this tag on websites you wish to track and access your analytics reports in real time. -Piwik aims to be a Free software alternative to Google Analytics, and is already used on more than 1,000,000 websites. +Piwik aims to be a Free software alternative to Google Analytics, and is already used on more than 1,000,000 websites. Privacy is built-in! ## Mission Statement @@ -21,17 +34,27 @@ Or in short: Piwik is released under the GPL v3 (or later) license, see [misc/gpl-3.0.txt](misc/gpl-3.0.txt) +## We’re seeking a talented Software Engineer + +Are you looking for a new challenge? We are currently seeking a software engineer or software developer who is passionate about data processing, security, privacy, the open source and free/libre philosophy and usable interface design. + +[View Job Description](https://piwik.org/blog/2015/01/piwik-expanding-seeking-talented-software-engineer-new-zealand-poland/) - [Apply online](http://piwik.org/jobs/) + +This is for a full time position to work on the open source Piwik platform, either remotely or we can help the right candidate relocate to beautiful New Zealand (Wellington) or Poland (Wroclaw). + +We are grateful if you can share the job description with your friends and wider network! + ## Requirements - * PHP 5.3.2 or greater + * PHP 5.3.3 or greater * MySQL 4.1 or greater, and either MySQLi or PDO library must be enabled * Piwik is OS / server independent See http://piwik.org/docs/requirements/ -## Install +## Install - * Upload piwik to your webserver + * Upload piwik to your webserver * Point your browser to the directory * Follow the steps * Add the given javascript code to your pages @@ -43,7 +66,7 @@ If you do not have a server, consider our Piwik Hosting partner: http://piwik.or ## Changelog -For the list of all tickets closed in the current and past releases, see http://piwik.org/changelog/ +For the list of all tickets closed in the current and past releases, see http://piwik.org/changelog/. For the list of technical changes in the Piwik platform, see [http://developer.piwik.org/changelog](http://developer.piwik.org/changelog). ## Participate! @@ -64,7 +87,7 @@ About us: http://piwik.org/the-piwik-team/ What makes Piwik unique from the competition: - * Real time web analytics reports: in Piwik, reports are by default generated in real time. + * Real time web analytics reports: in Piwik, reports are by default generated in real time. For high traffic websites, you can choose the frequency for reports to be processed. * You own your web analytics data: since Piwik is installed on your server, the data is stored in your own database and you can get all the statistics @@ -74,19 +97,12 @@ What makes Piwik unique from the competition: * Modern, easy to use User Interface: you can fully customize your dashboard, drag and drop widgets and more. - * Piwik features are built inside plugins: you can add new features and remove the ones you don’t need. + * Piwik features are built inside plugins: you can add new features and remove the ones you don’t need. You can build your own web analytics plugins or hire a consultant to have your custom feature built in Piwik * Vibrant international Open community of more than 200,000 active users (tracking even more websites!) - * Advanced Web Analytics capabilities such as Ecommerce Tracking, Goal tracking, Campaign tracking, + * Advanced Web Analytics capabilities such as Ecommerce Tracking, Goal tracking, Campaign tracking, Custom Variables, Email Reports, Custom Segment Editor, Geo Location, Real time maps, and more! Documentation and more info on http://piwik.org - -## Code Status -The Piwik project uses an ever-expanding comprehensive set of thousands of unit tests and dozens of integration [tests](https://github.com/piwik/piwik/tree/master/tests), - running on the hosted distributed continuous integration platform Travis-CI. - -Build status (master branch) [![Build Status](https://travis-ci.org/piwik/piwik.png?branch=master)](https://travis-ci.org/piwik/piwik) - Screenshot tests Build [![Build Status](https://travis-ci.org/piwik/piwik-ui-tests.png?branch=master)](https://travis-ci.org/piwik/piwik-ui-tests) - diff --git a/www/analytics/SECURITY.md b/www/analytics/SECURITY.md new file mode 100644 index 00000000..7834108a --- /dev/null +++ b/www/analytics/SECURITY.md @@ -0,0 +1,21 @@ +# Reporting Security Issues + +## Security Bug Bounty Program + +The Piwik Security Bug Bounty Program is designed to encourage security research in Piwik software and to reward those who help us create the safest web analytics platform. The bounty for valid critical security bugs is a **$555** (US) cash reward. The bounty for non-critical bugs is **$242** (US), paid via Paypal. + + +## Responsible disclosure by email + +If you have found a security issue in Piwik please read [our security notes](http://piwik.org/security/) regarding responsible disclosures. + +[Email your Report Vulnerability to the Piwik Security team](mailto:security@piwik.org?subject=Reporting%20Vulnerability%20in%20Piwik) + + +## Improve your Piwik Server Security + +[Secure Piwik server](http://piwik.org/docs/how-to-secure-piwik/): follow these steps to keep your Piwik data safe. + +## Security announcements + +Please subscribe to [the Changelog](http://piwik.org/changelog/) ([rss feed](http://piwik.org/changelog/feed/)) to be notified of new releases (including security releases). diff --git a/www/analytics/bower.json b/www/analytics/bower.json new file mode 100644 index 00000000..49252d93 --- /dev/null +++ b/www/analytics/bower.json @@ -0,0 +1,41 @@ +{ + "name": "Piwik", + "main": "piwik.js", + "homepage": "http://piwik.org", + "authors": [ + "Piwik.org " + ], + "description": "the leading free/libre analytics platform", + "private": true, + "keywords": [ + "piwik", + "web", + "analytics" + ], + "dependencies": { + "jquery-ui": "1.10.4", + "jquery": "~1.11.0", + "angular": "~1.2.0", + "angular-sanitize": "~1.2.0", + "angular-animate": "~1.2.0", + "angular-cookies": "~1.2.0", + "angular-mocks": "~1.2.0", + "ngDialog": "~0.2.0", + "html5shiv": "~3.7.0", + "mousetrap": "~1.4.0", + "sprintf": "~1.0.0", + "jScrollPane": "~2.0.0", + "jquery-mousewheel": "~3.1.12", + "jquery-placeholder": "~2.0.8", + "jQuery.dotdotdot": "~1.7.2", + "jquery.scrollTo": "~1.4.13", + "chroma-js": "~0.6.0", + "visibilityjs": "~1.2.1" + }, + "license": "GPLv3 or later", + "ignore": [ + "**/.*", + "node_modules", + "tests" + ] +} diff --git a/www/analytics/composer.json b/www/analytics/composer.json index 8c3fc620..3c6521b8 100644 --- a/www/analytics/composer.json +++ b/www/analytics/composer.json @@ -1,7 +1,7 @@ { "name": "piwik/piwik", "type": "application", - "description": "Open Source Real Time Web Analytics Platform", + "description": "the leading free/libre analytics platform", "keywords": ["piwik","web","analytics"], "homepage": "http://piwik.org", "license": "GPL-3.0+", @@ -14,17 +14,98 @@ ], "support": { "forum": "http://forum.piwik.org/", - "issues": "http://dev.piwik.org/trac/roadmap", - "wiki": "http://dev.piwik.org/", + "issues": "https://github.com/piwik/piwik/issues", + "wiki": "https://github.com/piwik/piwik/wiki", "source": "https://github.com/piwik/piwik" }, + "autoload": { + "psr-4": { + "Piwik\\Plugins\\": "plugins/", + "Piwik\\": "core/" + }, + "psr-0": { + "Zend_": "libs/", + "HTML_": "libs/", + "PEAR_": "libs/", + "Archive_": "libs/" + } + }, + "autoload-dev": { + "psr-4": { + "Piwik\\Tests\\": "tests/PHPUnit/" + } + }, "require": { - "php": ">=5.3.2", - "twig/twig": "1.*", - "leafo/lessphp": "~0.3", - "symfony/console": ">=v2.3.5", - "tedivm/jshrink": "v0.5.1", - "mustangostang/spyc": "0.5.*", - "piwik/device-detector": "*" + "php": ">=5.3.3", + "twig/twig": "~1.0", + "leafo/lessphp": "~0.5.0", + "symfony/console": "~2.6.0", + "tedivm/jshrink": "~0.5.1", + "mustangostang/spyc": "~0.5.0", + "piwik/device-detector": "~3.0", + "piwik/decompress": "~1.0", + "piwik/network": "~0.1.0", + "piwik/cache": "~0.2.5", + "piwik/ini": "^1.0.6", + "php-di/php-di": "5.0.0-beta1", + "psr/log": "~1.0", + "monolog/monolog": "~1.11", + "symfony/monolog-bridge": "~2.6.0", + "symfony/event-dispatcher": "~2.6.0", + "pear/pear_exception": "~1.0.0", + "piwik/referrer-spam-blacklist": "~1.0", + "piwik/searchengine-and-social-list": "~1.0", + "tecnickcom/tcpdf": "~6.0", + "piwik/piwik-php-tracker": "^1.0" + }, + "require-dev": { + "aws/aws-sdk-php": "2.7.1", + "phpunit/phpunit": "~4.8", + "facebook/xhprof": "dev-master", + "phpseclib/phpseclib": "~0.3.8", + "symfony/var-dumper": "~2.6.0", + "symfony/yaml": "~2.6.0" + }, + "repositories": [ + { + "type": "package", + "package": { + "name": "facebook/xhprof", + "type": "library", + "description": "XHProf: A Hierarchical Profiler for PHP", + "keywords": ["profiling", "performance"], + "homepage": "http://pecl.php.net/package/xhprof", + "license": "Apache-2.0", + "version": "master", + "require": { + "php": ">=5.2.0" + }, + "autoload": { + "files": [ + "xhprof_lib/utils/xhprof_lib.php", + "xhprof_lib/utils/xhprof_runs.php" + ] + }, + "source": { + "type": "git", + "url": "https://github.com/phacility/xhprof", + "reference": "master" + } + } + } + ], + "scripts": { + "pre-update-cmd": [ + "Piwik\\Composer\\ScriptHandler::cleanXhprof" + ], + "pre-install-cmd": [ + "Piwik\\Composer\\ScriptHandler::cleanXhprof" + ], + "post-update-cmd": [ + "Piwik\\Composer\\ScriptHandler::buildXhprof" + ], + "post-install-cmd": [ + "Piwik\\Composer\\ScriptHandler::buildXhprof" + ] } } diff --git a/www/analytics/composer.lock b/www/analytics/composer.lock index c31eafe5..32d55198 100644 --- a/www/analytics/composer.lock +++ b/www/analytics/composer.lock @@ -1,28 +1,249 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" ], - "hash": "69246584e6b57bfbc8d39799cd3b9213", + "hash": "de61be52972a0fe8fe751306c271f4b8", + "content-hash": "68130b067cdceef8346b47d858b763a3", "packages": [ { - "name": "leafo/lessphp", - "version": "v0.4.0", + "name": "container-interop/container-interop", + "version": "1.1.0", "source": { "type": "git", - "url": "https://github.com/leafo/lessphp.git", - "reference": "51f3f06f0fe78a722dabfd14578444bdd078d9de" + "url": "https://github.com/container-interop/container-interop.git", + "reference": "fc08354828f8fd3245f77a66b9e23a6bca48297e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/leafo/lessphp/zipball/51f3f06f0fe78a722dabfd14578444bdd078d9de", - "reference": "51f3f06f0fe78a722dabfd14578444bdd078d9de", + "url": "https://api.github.com/repos/container-interop/container-interop/zipball/fc08354828f8fd3245f77a66b9e23a6bca48297e", + "reference": "fc08354828f8fd3245f77a66b9e23a6bca48297e", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Interop\\Container\\": "src/Interop/Container/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", + "time": "2014-12-30 15:22:37" + }, + { + "name": "doctrine/annotations", + "version": "v1.2.7", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "f25c8aab83e0c3e976fd7d19875f198ccf2f7535" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/f25c8aab83e0c3e976fd7d19875f198ccf2f7535", + "reference": "f25c8aab83e0c3e976fd7d19875f198ccf2f7535", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "php": ">=5.3.2" + }, + "require-dev": { + "doctrine/cache": "1.*", + "phpunit/phpunit": "4.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Annotations\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "time": "2015-08-31 12:32:49" + }, + { + "name": "doctrine/cache", + "version": "v1.4.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "8c434000f420ade76a07c64cbe08ca47e5c101ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/8c434000f420ade76a07c64cbe08ca47e5c101ca", + "reference": "8c434000f420ade76a07c64cbe08ca47e5c101ca", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "phpunit/phpunit": ">=3.7", + "predis/predis": "~1.0", + "satooshi/php-coveralls": "~0.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Cache\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Caching library offering an object-oriented API for many cache backends", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "cache", + "caching" + ], + "time": "2015-08-31 12:36:41" + }, + { + "name": "doctrine/lexer", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Lexer\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "lexer", + "parser" + ], + "time": "2014-09-09 13:34:57" + }, + { + "name": "leafo/lessphp", + "version": "v0.5.0", + "source": { + "type": "git", + "url": "https://github.com/leafo/lessphp.git", + "reference": "0f5a7f5545d2bcf4e9fad9a228c8ad89cc9aa283" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/leafo/lessphp/zipball/0f5a7f5545d2bcf4e9fad9a228c8ad89cc9aa283", + "reference": "0f5a7f5545d2bcf4e9fad9a228c8ad89cc9aa283", "shasum": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "0.3-dev" + "dev-master": "0.4.x-dev" } }, "autoload": { @@ -44,7 +265,119 @@ ], "description": "lessphp is a compiler for LESS written in PHP.", "homepage": "http://leafo.net/lessphp/", - "time": "2013-08-09 17:09:19" + "time": "2014-11-24 18:39:20" + }, + { + "name": "mnapoli/phpdocreader", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/mnapoli/PhpDocReader.git", + "reference": "8a6e123fd1ce54f7fcbd71747b3bf04e465da229" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mnapoli/PhpDocReader/zipball/8a6e123fd1ce54f7fcbd71747b3bf04e465da229", + "reference": "8a6e123fd1ce54f7fcbd71747b3bf04e465da229", + "shasum": "" + }, + "require": { + "doctrine/annotations": "1.*", + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "PhpDocReader": "src/", + "UnitTest": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "keywords": [ + "phpdoc", + "reflection" + ], + "time": "2014-08-21 08:20:45" + }, + { + "name": "monolog/monolog", + "version": "1.17.2", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "bee7f0dc9c3e0b69a6039697533dca1e845c8c24" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bee7f0dc9c3e0b69a6039697533dca1e845c8c24", + "reference": "bee7f0dc9c3e0b69a6039697533dca1e845c8c24", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "jakub-onderka/php-parallel-lint": "0.9", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit": "~4.5", + "phpunit/phpunit-mock-objects": "2.3.0", + "raven/raven": "^0.13", + "ruflin/elastica": ">=0.90 <3.0", + "swiftmailer/swiftmailer": "~5.3", + "videlalvaro/php-amqplib": "~2.4" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "raven/raven": "Allow sending log messages to a Sentry server", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "videlalvaro/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.16.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "time": "2015-10-14 12:51:02" }, { "name": "mustangostang/spyc", @@ -94,32 +427,347 @@ "time": "2013-02-21 10:52:01" }, { - "name": "piwik/device-detector", - "version": "1.0", + "name": "pear/archive_tar", + "version": "1.4.1", "source": { "type": "git", - "url": "https://github.com/piwik/device-detector.git", - "reference": "ea7c5d8b76def0d8345a4eba59c5f98ec0109de6" + "url": "https://github.com/pear/Archive_Tar.git", + "reference": "fc2937c0e5a2a1c62a378d16394893172f970064" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/piwik/device-detector/zipball/ea7c5d8b76def0d8345a4eba59c5f98ec0109de6", - "reference": "ea7c5d8b76def0d8345a4eba59c5f98ec0109de6", + "url": "https://api.github.com/repos/pear/Archive_Tar/zipball/fc2937c0e5a2a1c62a378d16394893172f970064", + "reference": "fc2937c0e5a2a1c62a378d16394893172f970064", "shasum": "" }, "require": { - "mustangostang/spyc": "*", - "php": ">=5.3.1" + "pear/pear-core-minimal": "^1.10.0alpha2", + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "suggest": { + "ext-bz2": "bz2 compression support.", + "ext-xz": "lzma2 compression support.", + "ext-zlib": "Gzip compression support." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-0": { + "Archive_Tar": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "./" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Vincent Blavet", + "email": "vincent@phpconcept.net" + }, + { + "name": "Greg Beaver", + "email": "greg@chiaraquartet.net" + }, + { + "name": "Michiel Rook", + "email": "mrook@php.net" + } + ], + "description": "Tar file management class", + "homepage": "https://github.com/pear/Archive_Tar", + "keywords": [ + "archive", + "tar" + ], + "time": "2015-08-05 12:31:03" + }, + { + "name": "pear/console_getopt", + "version": "v1.4.1", + "source": { + "type": "git", + "url": "https://github.com/pear/Console_Getopt.git", + "reference": "82f05cd1aa3edf34e19aa7c8ca312ce13a6a577f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pear/Console_Getopt/zipball/82f05cd1aa3edf34e19aa7c8ca312ce13a6a577f", + "reference": "82f05cd1aa3edf34e19aa7c8ca312ce13a6a577f", + "shasum": "" }, "type": "library", "autoload": { + "psr-0": { + "Console": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "./" + ], + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Greg Beaver", + "email": "cellog@php.net", + "role": "Helper" + }, + { + "name": "Andrei Zmievski", + "email": "andrei@php.net", + "role": "Lead" + }, + { + "name": "Stig Bakken", + "email": "stig@php.net", + "role": "Developer" + } + ], + "description": "More info available on: http://pear.php.net/package/Console_Getopt", + "time": "2015-07-20 20:28:12" + }, + { + "name": "pear/pear-core-minimal", + "version": "v1.10.1", + "source": { + "type": "git", + "url": "https://github.com/pear/pear-core-minimal.git", + "reference": "cae0f1ce0cb5bddb611b0a652d322905a65a5896" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pear/pear-core-minimal/zipball/cae0f1ce0cb5bddb611b0a652d322905a65a5896", + "reference": "cae0f1ce0cb5bddb611b0a652d322905a65a5896", + "shasum": "" + }, + "require": { + "pear/console_getopt": "~1.3", + "pear/pear_exception": "~1.0" + }, + "replace": { + "rsky/pear-core-min": "self.version" + }, + "type": "library", + "autoload": { + "psr-0": { + "": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "src/" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@php.net", + "role": "Lead" + } + ], + "description": "Minimal set of PEAR core files to be used as composer dependency", + "time": "2015-10-17 11:41:19" + }, + { + "name": "pear/pear_exception", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/pear/PEAR_Exception.git", + "reference": "8c18719fdae000b690e3912be401c76e406dd13b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pear/PEAR_Exception/zipball/8c18719fdae000b690e3912be401c76e406dd13b", + "reference": "8c18719fdae000b690e3912be401c76e406dd13b", + "shasum": "" + }, + "require": { + "php": ">=4.4.0" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "type": "class", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "PEAR": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "." + ], + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Helgi Thormar", + "email": "dufuz@php.net" + }, + { + "name": "Greg Beaver", + "email": "cellog@php.net" + } + ], + "description": "The PEAR Exception base class.", + "homepage": "https://github.com/pear/PEAR_Exception", + "keywords": [ + "exception" + ], + "time": "2015-02-10 20:07:52" + }, + { + "name": "php-di/invoker", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "9949fff87fcf14e8f2ccfbe36dac1e5921944c48" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/9949fff87fcf14e8f2ccfbe36dac1e5921944c48", + "reference": "9949fff87fcf14e8f2ccfbe36dac1e5921944c48", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "~1.1" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "phpunit/phpunit": "~4.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "time": "2015-10-22 19:49:23" + }, + { + "name": "php-di/php-di", + "version": "5.0.0-beta1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "2325afb15d74728f52cb9721c9e184829f8f343a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/2325afb15d74728f52cb9721c9e184829f8f343a", + "reference": "2325afb15d74728f52cb9721c9e184829f8f343a", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "~1.0", + "doctrine/annotations": "~1.2", + "doctrine/cache": "~1.0", + "mnapoli/phpdocreader": "~1.3", + "php": ">=5.3.3", + "php-di/invoker": "~1.0" + }, + "require-dev": { + "mnapoli/phpunit-easymock": "~0.1.4", + "ocramius/proxy-manager": "~0.5", + "phpunit/phpunit": "~4.5" + }, + "suggest": { + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~0.5)" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "DI\\": "src/DI/" + }, "files": [ - "DeviceDetector.php" + "src/DI/functions.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "GPL-3.0+" + "MIT" + ], + "description": "PHP-DI is a Container that makes Dependency Injection as practical as possible in PHP", + "homepage": "http://mnapoli.github.com/PHP-DI/", + "keywords": [ + "container", + "dependency injection", + "di" + ], + "time": "2015-04-25 02:05:04" + }, + { + "name": "piwik/cache", + "version": "0.2.6", + "source": { + "type": "git", + "url": "https://github.com/piwik/component-cache.git", + "reference": "b8f2d18069c77726862f67d0199896d13073a831" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/piwik/component-cache/zipball/b8f2d18069c77726862f67d0199896d13073a831", + "reference": "b8f2d18069c77726862f67d0199896d13073a831", + "shasum": "" + }, + "require": { + "doctrine/cache": "~1.4", + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Piwik\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" ], "authors": [ { @@ -128,43 +776,322 @@ "homepage": "http://piwik.org/the-piwik-team/" } ], - "description": "The Universal Device Detection library, that parses User Agents and detects devices (desktop, tablet, mobile, tv, cars, console, etc.), and detects browsers, operating systems, devices, brands and models.", + "description": "PHP caching library based on Doctrine cache", + "keywords": [ + "array", + "cache", + "file", + "redis" + ], + "time": "2015-09-29 16:50:32" + }, + { + "name": "piwik/decompress", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/piwik/component-decompress.git", + "reference": "deca40d71d29d6140aad39db007aea82676b7631" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/piwik/component-decompress/zipball/deca40d71d29d6140aad39db007aea82676b7631", + "reference": "deca40d71d29d6140aad39db007aea82676b7631", + "shasum": "" + }, + "require": { + "pear/archive_tar": "~1.3,>=1.3.15", + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Piwik\\Decompress\\": "src/" + }, + "classmap": [ + "libs/PclZip" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "time": "2015-09-22 10:58:19" + }, + { + "name": "piwik/device-detector", + "version": "3.5.1", + "source": { + "type": "git", + "url": "https://github.com/piwik/device-detector.git", + "reference": "29830f9bd67c8300e37828db0688161dd6f5f7a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/piwik/device-detector/zipball/29830f9bd67c8300e37828db0688161dd6f5f7a5", + "reference": "29830f9bd67c8300e37828db0688161dd6f5f7a5", + "shasum": "" + }, + "require": { + "mustangostang/spyc": "*", + "php": ">=5.3.2" + }, + "require-dev": { + "fabpot/php-cs-fixer": "~1.7", + "phpunit/phpunit": "4.1.*" + }, + "suggest": { + "doctrine/cache": "Can directly be used for caching purpose" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeviceDetector\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0+" + ], + "authors": [ + { + "name": "The Piwik Team", + "email": "hello@piwik.org", + "homepage": "http://piwik.org/the-piwik-team/" + } + ], + "description": "The Universal Device Detection library, that parses User Agents and detects devices (desktop, tablet, mobile, tv, cars, console, etc.), clients (browsers, media players, mobile apps, feed readers, libraries, etc), operating systems, devices, brands and models.", "homepage": "http://piwik.org", "keywords": [ "devicedetection", "parser", "useragent" ], - "time": "2014-04-03 08:59:48" + "time": "2016-01-21 22:26:37" }, { - "name": "symfony/console", - "version": "v2.4.3", - "target-dir": "Symfony/Component/Console", + "name": "piwik/ini", + "version": "1.0.6", "source": { "type": "git", - "url": "https://github.com/symfony/Console.git", - "reference": "ef20f1f58d7f693ee888353962bd2db336e3bbcb" + "url": "https://github.com/piwik/component-ini.git", + "reference": "bd2711ba4d5e20e4ca09b6829dc2831576b59dc3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Console/zipball/ef20f1f58d7f693ee888353962bd2db336e3bbcb", - "reference": "ef20f1f58d7f693ee888353962bd2db336e3bbcb", + "url": "https://api.github.com/repos/piwik/component-ini/zipball/bd2711ba4d5e20e4ca09b6829dc2831576b59dc3", + "reference": "bd2711ba4d5e20e4ca09b6829dc2831576b59dc3", "shasum": "" }, "require": { "php": ">=5.3.3" }, "require-dev": { - "symfony/event-dispatcher": "~2.1" + "athletic/athletic": "0.1.*", + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Piwik\\Ini\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "time": "2016-01-14 21:13:33" + }, + { + "name": "piwik/network", + "version": "0.1.0", + "source": { + "type": "git", + "url": "https://github.com/piwik/component-network.git", + "reference": "9037fa29509f86767e02ba58a57d4deb1d01a844" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/piwik/component-network/zipball/9037fa29509f86767e02ba58a57d4deb1d01a844", + "reference": "9037fa29509f86767e02ba58a57d4deb1d01a844", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Piwik\\Network\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "time": "2014-10-23 03:30:23" + }, + { + "name": "piwik/piwik-php-tracker", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/piwik/piwik-php-tracker.git", + "reference": "ac3e26bb3e2c8a428ccbf6ca663c2ef37fa47a5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/piwik/piwik-php-tracker/zipball/ac3e26bb3e2c8a428ccbf6ca663c2ef37fa47a5e", + "reference": "ac3e26bb3e2c8a428ccbf6ca663c2ef37fa47a5e", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "." + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "The Piwik Team", + "email": "hello@piwik.org", + "homepage": "http://piwik.org/the-piwik-team/" + } + ], + "description": "PHP Client for Piwik Analytics Tracking API", + "homepage": "http://piwik.org", + "keywords": [ + "analytics", + "piwik", + "tracker" + ], + "time": "2015-11-11 02:55:37" + }, + { + "name": "piwik/referrer-spam-blacklist", + "version": "1.0.7", + "source": { + "type": "git", + "url": "https://github.com/piwik/referrer-spam-blacklist.git", + "reference": "85db74cfc7249cb34ff59eba22edeb6704fd69b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/piwik/referrer-spam-blacklist/zipball/85db74cfc7249cb34ff59eba22edeb6704fd69b8", + "reference": "85db74cfc7249cb34ff59eba22edeb6704fd69b8", + "shasum": "" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Public Domain" + ], + "description": "Community-contributed list of referrer spammers", + "time": "2016-01-05 17:31:58" + }, + { + "name": "piwik/searchengine-and-social-list", + "version": "1.1", + "source": { + "type": "git", + "url": "https://github.com/piwik/searchengine-and-social-list.git", + "reference": "5b6763e77dadf24e579f03a7a0e79f1827b5db8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/piwik/searchengine-and-social-list/zipball/5b6763e77dadf24e579f03a7a0e79f1827b5db8a", + "reference": "5b6763e77dadf24e579f03a7a0e79f1827b5db8a", + "shasum": "" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Public Domain" + ], + "description": "Search engine and social network definitions used by Piwik", + "time": "2015-11-16 22:24:23" + }, + { + "name": "psr/log", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", + "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-0": { + "Psr\\Log\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2012-12-21 11:40:51" + }, + { + "name": "symfony/console", + "version": "v2.6.11", + "target-dir": "Symfony/Component/Console", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "0e5e18ae09d3f5c06367759be940e9ed3f568359" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/0e5e18ae09d3f5c06367759be940e9ed3f568359", + "reference": "0e5e18ae09d3f5c06367759be940e9ed3f568359", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.1", + "symfony/phpunit-bridge": "~2.7", + "symfony/process": "~2.1" }, "suggest": { - "symfony/event-dispatcher": "" + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/process": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.4-dev" + "dev-master": "2.6-dev" } }, "autoload": { @@ -179,31 +1106,210 @@ "authors": [ { "name": "Fabien Potencier", - "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org", - "role": "Lead Developer" + "email": "fabien@symfony.com" }, { "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" + "homepage": "https://symfony.com/contributors" } ], "description": "Symfony Console Component", - "homepage": "http://symfony.com", - "time": "2014-03-01 17:35:04" + "homepage": "https://symfony.com", + "time": "2015-07-26 09:08:40" }, { - "name": "tedivm/jshrink", - "version": "v0.5.1", + "name": "symfony/event-dispatcher", + "version": "v2.6.11", + "target-dir": "Symfony/Component/EventDispatcher", "source": { "type": "git", - "url": "https://github.com/tedivm/JShrink.git", - "reference": "2d3f1a7d336ad54bdf2180732b806c768a791cbf" + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "672593bc4b0043a0acf91903bb75a1c82d8f2e02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tedivm/JShrink/zipball/2d3f1a7d336ad54bdf2180732b806c768a791cbf", - "reference": "2d3f1a7d336ad54bdf2180732b806c768a791cbf", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/672593bc4b0043a0acf91903bb75a1c82d8f2e02", + "reference": "672593bc4b0043a0acf91903bb75a1c82d8f2e02", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.0,>=2.0.5", + "symfony/dependency-injection": "~2.6", + "symfony/expression-language": "~2.6", + "symfony/phpunit-bridge": "~2.7", + "symfony/stopwatch": "~2.3" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2015-05-02 15:18:45" + }, + { + "name": "symfony/monolog-bridge", + "version": "v2.6.11", + "target-dir": "Symfony/Bridge/Monolog", + "source": { + "type": "git", + "url": "https://github.com/symfony/monolog-bridge.git", + "reference": "ba66eeabaa004e3ab70764cab59b056b182aa535" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/ba66eeabaa004e3ab70764cab59b056b182aa535", + "reference": "ba66eeabaa004e3ab70764cab59b056b182aa535", + "shasum": "" + }, + "require": { + "monolog/monolog": "~1.11", + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/console": "~2.4", + "symfony/event-dispatcher": "~2.2", + "symfony/http-kernel": "~2.4", + "symfony/phpunit-bridge": "~2.7" + }, + "suggest": { + "symfony/console": "For the possibility to show log messages in console commands depending on verbosity settings. You need version ~2.3 of the console for it.", + "symfony/event-dispatcher": "Needed when using log messages in console commands", + "symfony/http-kernel": "For using the debugging handlers together with the response life cycle of the HTTP kernel." + }, + "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Bridge\\Monolog\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Monolog Bridge", + "homepage": "https://symfony.com", + "time": "2015-06-25 11:21:15" + }, + { + "name": "tecnickcom/tcpdf", + "version": "6.2.12", + "source": { + "type": "git", + "url": "https://github.com/tecnickcom/TCPDF.git", + "reference": "2f732eaa91b5665274689b1d40b285a7bacdc37f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/2f732eaa91b5665274689b1d40b285a7bacdc37f", + "reference": "2f732eaa91b5665274689b1d40b285a7bacdc37f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "fonts", + "config", + "include", + "tcpdf.php", + "tcpdf_parser.php", + "tcpdf_import.php", + "tcpdf_barcodes_1d.php", + "tcpdf_barcodes_2d.php", + "include/tcpdf_colors.php", + "include/tcpdf_filters.php", + "include/tcpdf_font_data.php", + "include/tcpdf_fonts.php", + "include/tcpdf_images.php", + "include/tcpdf_static.php", + "include/barcodes/datamatrix.php", + "include/barcodes/pdf417.php", + "include/barcodes/qrcode.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPLv3" + ], + "authors": [ + { + "name": "Nicola Asuni", + "email": "info@tecnick.com", + "homepage": "http://nicolaasuni.tecnick.com" + } + ], + "description": "TCPDF is a PHP class for generating PDF documents and barcodes.", + "homepage": "http://www.tcpdf.org/", + "keywords": [ + "PDFD32000-2008", + "TCPDF", + "barcodes", + "datamatrix", + "pdf", + "pdf417", + "qrcode" + ], + "time": "2015-09-12 10:08:34" + }, + { + "name": "tedivm/jshrink", + "version": "v0.5.2", + "source": { + "type": "git", + "url": "https://github.com/tedious/JShrink.git", + "reference": "4b48e3d051cf0ab145db9df20d3292d91485bb60" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tedious/JShrink/zipball/4b48e3d051cf0ab145db9df20d3292d91485bb60", + "reference": "4b48e3d051cf0ab145db9df20d3292d91485bb60", "shasum": "" }, "require": { @@ -231,29 +1337,33 @@ "javascript", "minifier" ], - "time": "2012-11-26 04:48:55" + "time": "2014-01-14 22:23:53" }, { "name": "twig/twig", - "version": "v1.15.1", + "version": "v1.22.3", "source": { "type": "git", - "url": "https://github.com/fabpot/Twig.git", - "reference": "1fb5784662f438d7d96a541e305e28b812e2eeed" + "url": "https://github.com/twigphp/Twig.git", + "reference": "ebfc36b7e77b0c1175afe30459cf943010245540" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fabpot/Twig/zipball/1fb5784662f438d7d96a541e305e28b812e2eeed", - "reference": "1fb5784662f438d7d96a541e305e28b812e2eeed", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/ebfc36b7e77b0c1175afe30459cf943010245540", + "reference": "ebfc36b7e77b0c1175afe30459cf943010245540", "shasum": "" }, "require": { - "php": ">=5.2.4" + "php": ">=5.2.7" + }, + "require-dev": { + "symfony/debug": "~2.7", + "symfony/phpunit-bridge": "~2.7" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.22-dev" } }, "autoload": { @@ -279,7 +1389,7 @@ }, { "name": "Twig Team", - "homepage": "https://github.com/fabpot/Twig/graphs/contributors", + "homepage": "http://twig.sensiolabs.org/contributors", "role": "Contributors" } ], @@ -288,23 +1398,1322 @@ "keywords": [ "templating" ], - "time": "2014-02-13 10:19:29" + "time": "2015-10-13 07:07:02" } ], "packages-dev": [ - - ], - "aliases": [ - + { + "name": "aws/aws-sdk-php", + "version": "2.7.1", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "937a39ca3cee98d31a7410a17db24e0496c41494" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/937a39ca3cee98d31a7410a17db24e0496c41494", + "reference": "937a39ca3cee98d31a7410a17db24e0496c41494", + "shasum": "" + }, + "require": { + "guzzle/guzzle": "~3.7", + "php": ">=5.3.3" + }, + "require-dev": { + "doctrine/cache": "~1.0", + "ext-openssl": "*", + "monolog/monolog": "~1.4", + "phpunit/phpunit": "~4.0", + "symfony/yaml": "~2.1" + }, + "suggest": { + "doctrine/cache": "Adds support for caching of credentials and responses", + "ext-apc": "Allows service description opcode caching, request and response caching, and credentials caching", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "monolog/monolog": "Adds support for logging HTTP requests and responses", + "symfony/yaml": "Eases the ability to write manifests for creating jobs in AWS Import/Export" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-0": { + "Aws": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "time": "2014-10-16 21:37:55" + }, + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "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/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14 21:17:01" + }, + { + "name": "facebook/xhprof", + "version": "master", + "source": { + "type": "git", + "url": "https://github.com/phacility/xhprof", + "reference": "master" + }, + "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" + ], + "time": "2015-02-26 14:37:51" + }, + { + "name": "guzzle/guzzle", + "version": "v3.9.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle3.git", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": "~2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "~1.3", + "monolog/monolog": "~1.0", + "phpunit/phpunit": "3.7.*", + "psr/log": "~1.0", + "symfony/class-loader": "~2.1", + "zendframework/zend-cache": "2.*,<2.3", + "zendframework/zend-log": "2.*,<2.3" + }, + "suggest": { + "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.9-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2015-03-18 18:23:50" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", + "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "phpDocumentor": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "time": "2015-02-03 12:10:50" + }, + { + "name": "phpseclib/phpseclib", + "version": "0.3.10", + "source": { + "type": "git", + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "d15bba1edcc7c89e09cc74c5d961317a8b947bf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/d15bba1edcc7c89e09cc74c5d961317a8b947bf4", + "reference": "d15bba1edcc7c89e09cc74c5d961317a8b947bf4", + "shasum": "" + }, + "require": { + "php": ">=5.0.0" + }, + "require-dev": { + "phing/phing": "~2.7", + "phpunit/phpunit": "~4.0", + "sami/sami": "~2.0", + "squizlabs/php_codesniffer": "~1.5" + }, + "suggest": { + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-mcrypt": "Install the Mcrypt extension in order to speed up a wide variety of cryptographic operations.", + "pear-pear/PHP_Compat": "Install PHP_Compat to get phpseclib working on PHP < 4.3.3." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.3-dev" + } + }, + "autoload": { + "psr-0": { + "Crypt": "phpseclib/", + "File": "phpseclib/", + "Math": "phpseclib/", + "Net": "phpseclib/", + "System": "phpseclib/" + }, + "files": [ + "phpseclib/Crypt/Random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "phpseclib/" + ], + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "role": "Lead Developer" + }, + { + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" + }, + { + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" + }, + { + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" + } + ], + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "http://phpseclib.sourceforge.net", + "keywords": [ + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" + ], + "time": "2015-01-28 21:50:33" + }, + { + "name": "phpspec/prophecy", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4745ded9307786b730d7a60df5cb5a6c43cf95f7", + "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "phpdocumentor/reflection-docblock": "~2.0", + "sebastian/comparator": "~1.1" + }, + "require-dev": { + "phpspec/phpspec": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2015-08-13 10:07:40" + }, + { + "name": "phpunit/php-code-coverage", + "version": "2.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "~1.3", + "sebastian/environment": "^1.3.2", + "sebastian/version": "~1.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "~4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.2.1", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2015-10-06 15:47:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2015-06-21 13:08:43" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21 13:50:34" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b", + "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2015-06-21 08:01:12" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2015-09-15 10:49:45" + }, + { + "name": "phpunit/phpunit", + "version": "4.8.16", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "625f8c345606ed0f3a141dfb88f4116f0e22978e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/625f8c345606ed0f3a141dfb88f4116f0e22978e", + "reference": "625f8c345606ed0f3a141dfb88f4116f0e22978e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpspec/prophecy": "^1.3.1", + "phpunit/php-code-coverage": "~2.1", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": ">=1.0.6", + "phpunit/phpunit-mock-objects": "~2.3", + "sebastian/comparator": "~1.1", + "sebastian/diff": "~1.2", + "sebastian/environment": "~1.3", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/version": "~1.0", + "symfony/yaml": "~2.1|~3.0" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.8.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2015-10-23 06:48:33" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "2.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": ">=5.3.3", + "phpunit/php-text-template": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2015-10-02 06:51:40" + }, + { + "name": "sebastian/comparator", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2015-07-26 15:48:44" + }, + { + "name": "sebastian/diff", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "863df9687835c62aa423a22412d26fa2ebde3fd3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/863df9687835c62aa423a22412d26fa2ebde3fd3", + "reference": "863df9687835c62aa423a22412d26fa2ebde3fd3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "http://www.github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2015-02-22 15:13:53" + }, + { + "name": "sebastian/environment", + "version": "1.3.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "6324c907ce7a52478eeeaede764f48733ef5ae44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6324c907ce7a52478eeeaede764f48733ef5ae44", + "reference": "6324c907ce7a52478eeeaede764f48733ef5ae44", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2015-08-03 06:14:51" + }, + { + "name": "sebastian/exporter", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "7ae5513327cb536431847bcc0c10edba2701064e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e", + "reference": "7ae5513327cb536431847bcc0c10edba2701064e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2015-06-21 07:55:53" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12 03:26:01" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "994d4a811bafe801fb06dccbee797863ba2792ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/994d4a811bafe801fb06dccbee797863ba2792ba", + "reference": "994d4a811bafe801fb06dccbee797863ba2792ba", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2015-06-21 08:04:50" + }, + { + "name": "sebastian/version", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2015-06-21 13:59:46" + }, + { + "name": "symfony/var-dumper", + "version": "v2.6.11", + "target-dir": "Symfony/Component/VarDumper", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "5fba957a30161d8724aade093593cd22f815bea2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/5fba957a30161d8724aade093593cd22f815bea2", + "reference": "5fba957a30161d8724aade093593cd22f815bea2", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, + "suggest": { + "ext-symfony_debug": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + }, + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-0": { + "Symfony\\Component\\VarDumper\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony mechanism for exploring and dumping PHP variables", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "time": "2015-07-01 10:03:42" + }, + { + "name": "symfony/yaml", + "version": "v2.6.11", + "target-dir": "Symfony/Component/Yaml", + "source": { + "type": "git", + "url": "https://github.com/symfony/Yaml.git", + "reference": "c044d1744b8e91aaaa0d9bac683ab87ec7cbf359" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Yaml/zipball/c044d1744b8e91aaaa0d9bac683ab87ec7cbf359", + "reference": "c044d1744b8e91aaaa0d9bac683ab87ec7cbf359", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Yaml\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2015-07-26 08:59:42" + } ], + "aliases": [], "minimum-stability": "stable", - "stability-flags": [ - - ], - "platform": { - "php": ">=5.3.2" + "stability-flags": { + "php-di/php-di": 10, + "facebook/xhprof": 20 }, - "platform-dev": [ - - ] + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.3.3" + }, + "platform-dev": [] } diff --git a/www/analytics/config/.htaccess b/www/analytics/config/.htaccess index 6cd2e134..f4e970ee 100644 --- a/www/analytics/config/.htaccess +++ b/www/analytics/config/.htaccess @@ -1,13 +1,8 @@ - -Deny from all - - - -Deny from all - - - -Deny from all - + + Deny from all + + = 2.4> + Require all denied + diff --git a/www/analytics/config/environment/dev.php b/www/analytics/config/environment/dev.php new file mode 100644 index 00000000..a8bedecc --- /dev/null +++ b/www/analytics/config/environment/dev.php @@ -0,0 +1,12 @@ + DI\object('Piwik\Cache\Backend\ArrayCache'), + + 'Piwik\Translation\Loader\LoaderInterface' => DI\object('Piwik\Translation\Loader\LoaderCache') + ->constructor(DI\get('Piwik\Translation\Loader\DevelopmentLoader')), + 'Piwik\Translation\Loader\DevelopmentLoader' => DI\object() + ->constructor(DI\get('Piwik\Translation\Loader\JsonFileLoader')), + +); diff --git a/www/analytics/config/environment/test.php b/www/analytics/config/environment/test.php new file mode 100644 index 00000000..702bebae --- /dev/null +++ b/www/analytics/config/environment/test.php @@ -0,0 +1,97 @@ + DI\object('Psr\Log\NullLogger'), + + 'Piwik\Cache\Backend' => function () { + return \Piwik\Cache::buildBackend('file'); + }, + 'cache.eager.cache_id' => 'eagercache-test-', + + // Disable loading core translations + 'Piwik\Translation\Translator' => DI\decorate(function ($previous, ContainerInterface $c) { + $loadRealTranslations = $c->get('test.vars.loadRealTranslations'); + if (!$loadRealTranslations) { + return new \Piwik\Translation\Translator($c->get('Piwik\Translation\Loader\LoaderInterface'), $directories = array()); + } else { + return $previous; + } + }), + + 'Piwik\Config' => DI\decorate(function ($previous, ContainerInterface $c) { + $testingEnvironment = $c->get('Piwik\Tests\Framework\TestingEnvironmentVariables'); + + $dontUseTestConfig = $c->get('test.vars.dontUseTestConfig'); + if (!$dontUseTestConfig) { + $settingsProvider = $c->get('Piwik\Application\Kernel\GlobalSettingsProvider'); + return new TestConfig($settingsProvider, $testingEnvironment, $allowSave = false, $doSetTestEnvironment = true); + } else { + return $previous; + } + }), + + 'Piwik\Access' => DI\decorate(function ($previous, ContainerInterface $c) { + $testUseMockAuth = $c->get('test.vars.testUseMockAuth'); + if ($testUseMockAuth) { + $idSitesAdmin = $c->get('test.vars.idSitesAdminAccess'); + $access = new FakeAccess(); + if (!empty($idSitesAdmin)) { + FakeAccess::$superUser = false; + FakeAccess::$idSitesAdmin = $idSitesAdmin; + FakeAccess::$identity = 'adminUserLogin'; + } else { + FakeAccess::$superUser = true; + FakeAccess::$superUserLogin = 'superUserLogin'; + } + return $access; + } else { + return $previous; + } + }), + + 'observers.global' => DI\add(array( + + array('AssetManager.getStylesheetFiles', function (&$stylesheets) { + $useOverrideCss = \Piwik\Container\StaticContainer::get('test.vars.useOverrideCss'); + if ($useOverrideCss) { + $stylesheets[] = 'tests/resources/screenshot-override/override.css'; + } + }), + + array('AssetManager.getJavaScriptFiles', function (&$jsFiles) { + $useOverrideJs = \Piwik\Container\StaticContainer::get('test.vars.useOverrideJs'); + if ($useOverrideJs) { + $jsFiles[] = 'tests/resources/screenshot-override/override.js'; + } + }), + + array('Updater.checkForUpdates', function () { + try { + @\Piwik\Filesystem::deleteAllCacheOnUpdate(); + } catch (Exception $ex) { + // pass + } + }), + + array('Test.Mail.send', function (\Zend_Mail $mail) { + $outputFile = PIWIK_INCLUDE_PATH . '/tmp/' . Common::getRequestVar('module', '') . '.' . Common::getRequestVar('action', '') . '.mail.json'; + $outputContent = str_replace("=\n", "", $mail->getBodyText($textOnly = true)); + $outputContent = str_replace("=0A", "\n", $outputContent); + $outputContent = str_replace("=3D", "=", $outputContent); + $outputContents = array( + 'from' => $mail->getFrom(), + 'to' => $mail->getRecipients(), + 'subject' => $mail->getSubject(), + 'contents' => $outputContent + ); + file_put_contents($outputFile, json_encode($outputContents)); + }), + )), +); diff --git a/www/analytics/config/environment/ui-test.php b/www/analytics/config/environment/ui-test.php new file mode 100644 index 00000000..b5ef0fed --- /dev/null +++ b/www/analytics/config/environment/ui-test.php @@ -0,0 +1,62 @@ + array(), + 'tests.ui.url_normalizer_blacklist.controller' => array(), + + 'Piwik\Config' => \DI\decorate(function (\Piwik\Config $config) { + $config->General['cors_domains'][] = '*'; + $config->General['trusted_hosts'][] = $config->tests['http_host']; + $config->General['trusted_hosts'][] = $config->tests['http_host'] . ':' . $config->tests['port']; + return $config; + }), + + 'observers.global' => \DI\add(array( + + // removes port from all URLs to the test Piwik server so UI tests will pass no matter + // what port is used + array('Request.dispatch.end', function (&$result) { + $request = $_GET + $_POST; + + $apiblacklist = StaticContainer::get('tests.ui.url_normalizer_blacklist.api'); + if (!empty($request['method']) + && in_array($request['method'], $apiblacklist) + ) { + return; + } + + $controllerActionblacklist = StaticContainer::get('tests.ui.url_normalizer_blacklist.controller'); + if (!empty($request['module']) + && !empty($request['action']) + ) { + $controllerAction = $request['module'] . '.' . $request['action']; + if (in_array($controllerAction, $controllerActionblacklist)) { + return; + } + } + + $config = \Piwik\Config::getInstance(); + $host = $config->tests['http_host']; + $port = $config->tests['port']; + + if (!empty($port)) { + // remove the port from URLs if any so UI tests won't fail if the port isn't 80 + $result = str_replace($host . ':' . $port, $host, $result); + } + + // remove PIWIK_INCLUDE_PATH from result so tests don't change based on the machine used + $result = str_replace(realpath(PIWIK_INCLUDE_PATH), '', $result); + }), + + array('Controller.ExampleRssWidget.rssPiwik.end', function (&$result, $parameters) { + $result = ""; + }), + )), + +); diff --git a/www/analytics/config/global.ini.php b/www/analytics/config/global.ini.php index 9d76c4be..6ac4feac 100644 --- a/www/analytics/config/global.ini.php +++ b/www/analytics/config/global.ini.php @@ -18,25 +18,46 @@ password = dbname = tables_prefix = port = 3306 -adapter = PDO_MYSQL +adapter = PDO\MYSQL type = InnoDB schema = Mysql - ; if charset is set to utf8, Piwik will ensure that it is storing its data using UTF8 charset. ; it will add a sql query SET at each page view. -; Piwik should work correctly without this setting. -;charset = utf8 +; Piwik should work correctly without this setting but we recommend to have a charset set. +charset = utf8 [database_tests] host = localhost -username = root +username = "@USERNAME@" password = dbname = piwik_tests tables_prefix = piwiktests_ port = 3306 -adapter = PDO_MYSQL +adapter = PDO\MYSQL type = InnoDB schema = Mysql +charset = utf8 + +[tests] +; needed in order to run tests. +; if Piwik is available at http://localhost/dev/piwik/ replace @REQUEST_URI@ with /dev/piwik/ +; note: the REQUEST_URI should not contain "plugins" or "tests" in the PATH +http_host = localhost +remote_addr = "127.0.0.1" +request_uri = "@REQUEST_URI@" +port = + +; access key and secret as listed in AWS -> IAM -> Users +aws_accesskey = "" +aws_secret = "" +; key pair name as listed in AWS -> EC2 -> Key Pairs. Key name should be different per user. +aws_keyname = "" +; PEM file can be downloaded after creating a new key pair in AWS -> EC2 -> Key Pairs +aws_pem_file = "" +aws_securitygroups[] = "default" +aws_region = "us-east-1" +aws_ami = "ami-ac24bac4" +aws_instance_type = "c3.large" [log] ; possible values for log: screen, database, file @@ -44,20 +65,36 @@ log_writers[] = screen ; log level, everything logged w/ this level or one of greater severity ; will be logged. everything else will be ignored. possible values are: -; NONE, ERROR, WARN, INFO, DEBUG, VERBOSE +; ERROR, WARN, INFO, DEBUG log_level = WARN -; if set to 1, only requests done in CLI mode (eg. the archive.php cron run) will be logged -; NOTE: log_only_when_debug_parameter will also be checked for -log_only_when_cli = 0 - -; if set to 1, only requests with "&debug" parameter will be logged -; NOTE: log_only_when_cli will also be checked for -log_only_when_debug_parameter = 0 - ; if configured to log in a file, log entries will be made to this file logger_file_path = tmp/logs/piwik.log +[Cache] +; available backends are 'file', 'array', 'null', 'redis', 'chained' +; 'array' will cache data only during one request +; 'null' will not cache anything at all +; 'file' will cache on the filesystem +; 'redis' will cache on a Redis server, use this if you are running Piwik with multiple servers. Further configuration in [RedisCache] is needed +; 'chained' will chain multiple cache backends. Further configuration in [ChainedCache] is needed +backend = chained + +[ChainedCache] +; The chained cache will always try to read from the fastest backend first (the first listed one) to avoid requesting +; the same cache entry from the slowest backend multiple times in one request. +backends[] = array +backends[] = file + +[RedisCache] +; Redis server configuration. +host = "127.0.0.1" +port = 6379 +timeout = 0.0 +password = "" +database = 14 +; In case you are using queued tracking: Make sure to configure a different database! Otherwise queued requests might +; be flushed [Debug] ; if set to 1, the archiving process will always be triggered, even if the archive has already been computed @@ -72,40 +109,68 @@ always_archive_data_range = 0; ; NOTE: you must also set [log] log_writers[] = "screen" to enable the profiler to print on screen enable_sql_profiler = 0 -; if set to 1, a Piwik tracking code will be included in the Piwik UI footer and will track visits, pages, etc. to idsite = 1 -; this is useful for Piwik developers as an easy way to create data in their local Piwik -track_visits_inside_piwik_ui = 0 - -; if set to 1, javascript files will be included individually and neither merged nor minified. -; this option must be set to 1 when adding, removing or modifying javascript files -disable_merged_assets = 0 - ; If set to 1, all requests to piwik.php will be forced to be 'new visitors' tracker_always_new_visitor = 0 -; Allow automatic upgrades to Beta or RC releases -allow_upgrades_to_beta = 0 +; if set to 1, all SQL queries will be logged using the DEBUG log level +log_sql_queries = 0 [DebugTests] -; Set to 1 by default. If you set to 0, the standalone plugins (with their own git repositories) -; will not be loaded when executing tests. -enable_load_standalone_plugins_during_tests = 1 +; When set to 1, standalone plugins (those with their own git repositories) +; will be loaded when executing tests. +enable_load_standalone_plugins_during_tests = 0 + +[Development] +; Enables the development mode where we avoid most caching to make sure code changes will be directly applied as +; some caches are only invalidated after an update otherwise. When enabled it'll also performs some validation checks. +; For instance if you register a method in a widget we will verify whether the method actually exists and is public. +; If not, we will show you a helpful warning to make it easy to find simple typos etc. +enabled = 0 + +; if set to 1, javascript files will be included individually and neither merged nor minified. +; this option must be set to 1 when adding, removing or modifying javascript files +; Note that for quick debugging, instead of using below setting, you can add `&disable_merged_assets=1` to the Piwik URL +disable_merged_assets = 0 [General] -; the following settings control whether Unique Visitors will be processed for different period types. + +; the following settings control whether Unique Visitors `nb_uniq_visitors` and Unique users `nb_users` will be processed for different period types. ; year and range periods are disabled by default, to ensure optimal performance for high traffic Piwik instances ; if you set it to 1 and want the Unique Visitors to be re-processed for reports in the past, drop all piwik_archive_* tables -; it is recommended to always enable Unique Visitors processing for 'day' periods +; it is recommended to always enable Unique Visitors and Unique Users processing for 'day' periods enable_processing_unique_visitors_day = 1 enable_processing_unique_visitors_week = 1 enable_processing_unique_visitors_month = 1 enable_processing_unique_visitors_year = 0 enable_processing_unique_visitors_range = 0 +; controls whether Unique Visitors will be processed for groups of websites. these metrics describe the number +; of unique visitors across the entire set of websites, so if a visitor visited two websites in the group, she +; would still only be counted as one. only relevant when using plugins that group sites together +enable_processing_unique_visitors_multiple_sites = 0 + +; The list of periods that are available in the Piwik calendar +; Example use case: custom date range requests are processed in real time, +; so they may take a few minutes on very high traffic website: you may remove "range" below to disable this period +enabled_periods_UI = "day,week,month,year,range" +enabled_periods_API = "day,week,month,year,range" + +; whether to enable subquery cache for Custom Segment archiving queries +enable_segments_subquery_cache = 0 +; Any segment subquery that matches more than segments_subquery_cache_limit IDs will not be cached, +; and the original subquery executed instead. +segments_subquery_cache_limit = 100000 +; TTL: Time to live for cache files, in seconds. Default to 60 minutes +segments_subquery_cache_ttl = 3600 + ; when set to 1, all requests to Piwik will return a maintenance message without connecting to the DB ; this is useful when upgrading using the shell command, to prevent other users from accessing the UI while Upgrade is in progress maintenance_mode = 0 +; Defines the release channel that shall be used. Currently available values are: +; "latest_stable", "latest_beta", "latest_2x_stable", "latest_2x_beta" +release_channel = "latest_stable" + ; character used to automatically create categories in the Actions > Pages, Outlinks and Downloads reports ; for example a URL like "example.com/blog/development/first-post" will create ; the page first-post in the subcategory development which belongs to the blog category @@ -143,10 +208,36 @@ browser_archiving_disabled_enforce = 0 ; By default, users can create Segments which are to be processed in Real-time. ; Setting this to 0 will force all newly created Custom Segments to be "Pre-processed (faster, requires archive.php cron)" ; This can be useful if you want to prevent users from adding much load on the server. -; Note: any existing Segment set to "processed in Real time", will still be set to Real-time. -; this will only affect custom segments added or modified after this setting is changed. +; Notes: +; * any existing Segment set to "processed in Real time", will still be set to Real-time. +; this will only affect custom segments added or modified after this setting is changed. +; * when set to 0 then any user with at least 'view' access will be able to create pre-processed segments. enable_create_realtime_segments = 1 +; Whether to enable the "Suggest values for segment" in the Segment Editor panel. +; Set this to 0 in case your Piwik database is very big, and suggested values may not appear in time +enable_segment_suggested_values = 1 + +; By default, any user with a "view" access for a website can create segment assigned to this website. +; Set this to "admin" or "superuser" to require that users should have at least this access to create new segments. +; Note: anonymous user (even if it has view access) is not allowed to create or edit segment. +; Possible values are "view", "admin", "superuser" +adding_segment_requires_access = "view" + +; Whether it is allowed for users to add segments that affect all websites or not. If there are many websites +; this admin option can be used to prevent users from performing an action that will have a major impact +; on Piwik performance. +allow_adding_segments_for_all_websites = 1 + +; When archiving segments for the first time, this determines the oldest date that will be archived. +; This option can be used to avoid archiving (for isntance) the lastN years for every new segment. +; Valid option values include: "beginning_of_time" (start date of archiving will not be changed) +; "segment_last_edit_time" (start date of archiving will be the earliest last edit date found, +; if none is found, the created date is used) +; "segment_creation_time" (start date of archiving will be the creation date of the segment) +; lastN where N is an integer (eg "last10" to archive for 10 days before the segment creation date) +process_new_segments_from = "beginning_of_time" + ; this action name is used when the URL ends with a slash / ; it is useful to have an actual string to write in the UI action_default_name = index @@ -158,6 +249,11 @@ default_language = en ; default number of elements in the datatable datatable_default_limit = 10 +; Each datatable report has a Row Limit selector at the bottom right. +; By default you can select from 5 to 500 rows. You may customise the values below +; -1 will be displayed as 'all' and it will export all rows (filter_limit=-1) +datatable_row_limits = "5,10,25,50,100,250,500,-1" + ; default number of rows returned in API responses ; this value is overwritten by the '# Rows to display' selector. ; if set to -1, a click on 'Export as' will export all rows independently of the current '# Rows to display'. @@ -175,21 +271,32 @@ default_day = yesterday default_period = day ; Time in seconds after which an archive will be computed again. This setting is used only for today's statistics. -; Defaults to 10 seconds so that by default, Piwik provides real time reporting. ; This setting is overriden in the UI, under "General Settings". ; This setting is only used if it hasn't been overriden via the UI yet, or if enable_general_settings_admin=0 -time_before_today_archive_considered_outdated = 10 +time_before_today_archive_considered_outdated = 150 ; This setting is overriden in the UI, under "General Settings". ; The default value is to allow browsers to trigger the Piwik archiving process. ; This setting is only used if it hasn't been overriden via the UI yet, or if enable_general_settings_admin=0 enable_browser_archiving_triggering = 1 +; By default, Piwik will force archiving of range periods from browser requests, even if enable_browser_archiving_triggering +; is set to 0. This can sometimes create too much of a demand on system resources. Setting this option to 0 and setting +; enable_browser_archiving_triggering to 0 will make sure ranges are not archived on browser request. Since the cron +; archiver does not archive ranges, you must either disable ranges or make sure the ranges users' want to see will be +; processed somehow. +archiving_range_force_on_browser_request = 1 + ; By default Piwik runs OPTIMIZE TABLE SQL queries to free spaces after deleting some data. ; If your Piwik tracks millions of pages, the OPTIMIZE TABLE queries might run for hours (seen in "SHOW FULL PROCESSLIST \g") ; so you can disable these special queries here: enable_sql_optimize_queries = 1 +; By default Piwik is purging complete date range archives to free spaces after deleting some data. +; If you are pre-processing custom ranges using CLI task to make them easily available in UI, +; you can prevent this action from happening by setting this parameter to value bigger than 1 +purge_date_range_archives_after_X_days = 1 + ; MySQL minimum required version ; note: timezone support added in 4.1.3 minimum_mysql_version = 4.1 @@ -200,7 +307,7 @@ minimum_pgsql_version = 8.3 ; Minimum adviced memory limit in php.ini file (see memory_limit value) minimum_memory_limit = 128 -; Minimum memory limit enforced when archived via misc/cron/archive.php +; Minimum memory limit enforced when archived via ./console core:archive minimum_memory_limit_when_archiving = 768 ; Piwik will check that usernames and password have a minimum length, and will check that characters are "allowed" @@ -217,14 +324,15 @@ session_save_handler = files ; If set to 1, Piwik will automatically redirect all http:// requests to https:// ; If SSL / https is not correctly configured on the server, this will break Piwik -; If you set this to 1, and your SSL configuration breaks later on, you can always edit this back to 0 +; If you set this to 1, and your SSL configuration breaks later on, you can always edit this back to 0 ; it is recommended for security reasons to always use Piwik over https force_ssl = 0 ; login cookie name login_cookie_name = piwik_auth -; login cookie expiration (14 days) +; By default, the auth cookie is set only for the duration of session. +; if "Remember me" is checked, the auth cookie will be valid for 14 days by default login_cookie_expire = 1209600 ; The path on the server in which the cookie will be available on. @@ -237,6 +345,12 @@ login_password_recovery_email_address = "password-recovery@{DOMAIN}" ; name that appears as a Sender in the password recovery email login_password_recovery_email_name = Piwik +; email address that appears as a Repy-to in the password recovery email +; if specified, {DOMAIN} will be replaced by the current Piwik domain +login_password_recovery_replyto_email_address = "no-reply@{DOMAIN}" +; name that appears as a Reply-to in the password recovery email +login_password_recovery_replyto_email_name = "No-reply" + ; By default when user logs out he is redirected to Piwik "homepage" usually the Login form. ; Uncomment the next line to set a URL to redirect the user to after he logs out of Piwik. ; login_logout_url = http://... @@ -255,10 +369,21 @@ language_cookie_name = piwik_lang ; standard email address displayed when sending emails noreply_email_address = "noreply@{DOMAIN}" +; standard email name displayed when sending emails. If not set, a default name will be used. +noreply_email_name = "" + ; feedback email address; ; when testing, use your own email address or "nobody" feedback_email_address = "feedback@piwik.org" +; using to set reply_to in reports e-mail to login of report creator +scheduled_reports_replyto_is_user_email_and_alias = 0 + +; scheduled reports truncate limit +; the report will be rendered with the first 23 rows and will aggregate other rows in a summary row +; 23 rows table fits in one portrait page +scheduled_reports_truncate = 23 + ; during archiving, Piwik will limit the number of results recorded, for performance reasons ; maximum number of rows for any of the Referrers tables (keywords, search engines, campaigns, etc.) datatable_archiving_maximum_rows_referrers = 1000 @@ -347,9 +472,20 @@ enable_trusted_host_check = 1 ;trusted_hosts[] = example.com ;trusted_hosts[] = stats.example.com -; The release server is an essential part of the Piwik infrastructure/ecosystem -; to provide the latest software version. -latest_version_url = http://builds.piwik.org/latest.zip +; List of Cross-origin resource sharing domains (eg domain or subdomain names) when generating absolute URLs. +; Described here: http://en.wikipedia.org/wiki/Cross-origin_resource_sharing +; +; Examples: +;cors_domains[] = http://example.com +;cors_domains[] = http://stats.example.com +; +; Or you may allow cross domain requests for all domains with: +;cors_domains[] = * + +; If you use this Piwik instance over multiple hostnames, Piwik will need to know +; a unique instance_id for this instance, so that Piwik can serve the right custom logo and tmp/* assets, +; independently of the hostname Piwik is currently running under. +; instance_id = stats.example.com ; The API server is an essential part of the Piwik infrastructure/ecosystem to ; provide services to Piwik installations, e.g., getLatestVersion and @@ -362,6 +498,10 @@ api_service_url = http://api.piwik.org ; eg. $period=range&date=previous10 becomes $period=day&date=previous10. Use this setting to override the $period value. graphs_default_period_to_plot_when_period_range = day +; When the ImageGraph plugin is activated, enabling this option causes the image graphs to show the evolution +; within the selected period instead of the evolution across the last n periods. +graphs_show_evolution_within_selected_period = 0 + ; The Overlay plugin shows the Top X following pages, Top X downloads and Top X outlinks which followed ; a view of the current page. The value X can be set here. overlay_following_pages_limit = 300 @@ -411,7 +551,29 @@ enable_auto_update = 1 ; If set to 0 it also disables the "sent plugin update emails" feature in general and the related setting in the UI. enable_update_communication = 1 +; This controls whether the pivotBy query parameter can be used with any dimension or just subtable +; dimensions. If set to 1, it will fetch a report with a segment for each row of the table being pivoted. +; At present, this is very inefficient, so it is disabled by default. +pivot_by_filter_enable_fetch_by_segment = 0 + +; This controls the default maximum number of columns to display in a pivot table. Since a pivot table displays +; a table's rows as columns, the number of columns can become very large, which will affect webpage layouts. +; Set to -1 to specify no limit. Note: The pivotByColumnLimit query parameter can be used to override this default +; on a per-request basis; +pivot_by_filter_default_column_limit = 10 + +; If set to 0 it will disable Piwik Pro advertisements in some places. For example in the installation screen, the +; Piwik Pro Ad widget will be removed etc. +piwik_pro_ads_enabled = 1 + [Tracker] + +; Piwik uses "Privacy by default" model. When one of your users visit multiple of your websites tracked in this Piwik, +; Piwik will create for this user a fingerprint that will be different across the multiple websites. +; If you want to track unique users across websites (for example when using the InterSites plugin) you may set this setting to 1. +; Note: setting this to 0 increases your users' privacy. +enable_fingerprinting_across_websites = 0 + ; Piwik uses first party cookies by default. If set to 1, ; the visit ID cookie will be set on the Piwik server domain as well ; this is useful when you want to do cross websites analysis @@ -421,14 +583,14 @@ use_third_party_id_cookie = 0 ; Once enabled (set to 1) messages will be logged to all loggers defined in "[log] log_writers" config. debug = 0 -; There is a feature in the Tracking API that lets you create new visit at any given time, for example if you know that a different user/customer is using -; the app then you would want to tell Piwik to create a new visit (even though both users are using the same browser/computer). -; To prevent abuse and easy creation of fake visits, this feature requires admin token_auth by default -; If you wish to use this feature using the Javascript tracker, you can set the setting new_visit_api_requires_admin=0, and in Javascript write: -; _paq.push(['appendToTrackingUrl', 'new_visit=1']); -new_visit_api_requires_admin = 1 +; This option is an alternative to the debug option above. When set to 1, you can debug tracker request by adding +; a debug=1 query paramater in the URL. All other HTTP requests will not have debug enabled. For security reasons this +; option should be only enabled if really needed and only for a short time frame. Otherwise anyone can set debug=1 and +; see the log output as well. +debug_on_demand = 0 -; This setting should only be set to 1 in an intranet setting, where most users have the same configuration (browsers, OS) +; This setting is described in this FAQ: http://piwik.org/faq/how-to/faq_175/ +; Note: generally this should only be set to 1 in an intranet setting, where most users have the same configuration (browsers, OS) ; and the same IP. If left to 0 in this setting, all visitors will be counted as one single visitor. trust_visitors_cookies = 0 @@ -436,9 +598,9 @@ trust_visitors_cookies = 0 ; This is used only if use_third_party_id_cookie = 1 cookie_name = _pk_uid -; by default, the Piwik tracking cookie expires in 2 years +; by default, the Piwik tracking cookie expires in 13 months (365 + 28 days) ; This is used only if use_third_party_id_cookie = 1 -cookie_expire = 63072000 +cookie_expire = 33955200; ; The path on the server in which the cookie will be available on. ; Defaults to empty. See spec in http://curl.haxx.se/rfc/cookie_spec.html @@ -466,7 +628,7 @@ default_time_one_page_visit = 0 ; The mapping is defined in core/DataFiles/LanguageToCountry.php, enable_language_to_country_guess = 1 -; When the misc/cron/archive.php cron hasn't been setup, we still need to regularly run some maintenance tasks. +; When the `./console core:archive` cron hasn't been setup, we still need to regularly run some maintenance tasks. ; Visits to the Tracker will try to trigger Scheduled Tasks (eg. scheduled PDF/HTML reports by email). ; Scheduled tasks will only run if 'Enable Piwik Archiving from Browser' is enabled in the General Settings. ; Tasks run once every hour maximum, they might not run every hour if traffic is low. @@ -479,13 +641,27 @@ ignore_visits_cookie_name = piwik_ignore ; Comma separated list of variable names that will be read to define a Campaign name, for example CPC campaign ; Example: If a visitor first visits 'index.php?piwik_campaign=Adwords-CPC' then it will be counted as a campaign referrer named 'Adwords-CPC' ; Includes by default the GA style campaign parameters -campaign_var_name = "pk_campaign,piwik_campaign,utm_campaign,utm_source,utm_medium" +campaign_var_name = "pk_cpn,pk_campaign,piwik_campaign,utm_campaign,utm_source,utm_medium" ; Comma separated list of variable names that will be read to track a Campaign Keyword ; Example: If a visitor first visits 'index.php?piwik_campaign=Adwords-CPC&piwik_kwd=My killer keyword' ; ; then it will be counted as a campaign referrer named 'Adwords-CPC' with the keyword 'My killer keyword' ; Includes by default the GA style campaign keyword parameter utm_term -campaign_keyword_var_name = "pk_kwd,piwik_kwd,pk_keyword,utm_term" +campaign_keyword_var_name = "pk_kwd,pk_keyword,piwik_kwd,utm_term" + +; if set to 1, actions that contain different campaign information from the visitor's ongoing visit will +; be treated as the start of a new visit. This will include situations when campaign information was absent before, +; but is present now. +create_new_visit_when_campaign_changes = 1 + +; if set to 1, actions that contain different website referrer information from the visitor's ongoing visit +; will be treated as the start of a new visit. This will include situations when website referrer information was +; absent before, but is present now. +create_new_visit_when_website_referrer_changes = 0 + +; ONLY CHANGE THIS VALUE WHEN YOU DO NOT USE PIWIK ARCHIVING, SINCE THIS COULD CAUSE PARTIALLY MISSING ARCHIVE DATA +; Whether to force a new visit at midnight for every visitor. Default 1. +create_new_visit_after_midnight = 1 ; maximum length of a Page Title or a Page URL recorded in the log_action.name table page_maximum_length = 1024; @@ -494,9 +670,16 @@ page_maximum_length = 1024; ; TTL: Time to live for cache files, in seconds. Default to 5 minutes. tracker_cache_file_ttl = 300 +; Whether Bulk tracking requests to the Tracking API requires the token_auth to be set. +bulk_requests_require_authentication = 0 + +; Whether Bulk tracking requests will be wrapped within a DB Transaction. +; This greatly increases performance of Log Analytics and in general any Bulk Tracking API requests. +bulk_requests_use_transaction = 1 + ; DO NOT USE THIS SETTING ON PUBLICLY AVAILABLE PIWIK SERVER ; !!! Security risk: if set to 0, it would allow anyone to push data to Piwik with custom dates in the past/future and even with fake IPs! -; When using the Tracking API, to override either the datetime and/or the visitor IP, +; When using the Tracking API, to override either the datetime and/or the visitor IP, ; token_auth with an "admin" access is required. If you set this setting to 0, the token_auth will not be required anymore. ; DO NOT USE THIS SETTING ON PUBLIC PIWIK SERVERS tracking_requests_require_authentication = 1 @@ -509,7 +692,7 @@ tracking_requests_require_authentication = 1 ; for which all reports should be Archived during the cron execution ; All segment values MUST be URL encoded. ;Segments[]="visitorType==new" -;Segments[]="visitorType==returning" +;Segments[]="visitorType==returning,visitorType==returningCustomer" ; If you define Custom Variables for your visitor, for example set the visit type ;Segments[]="customVariableName1==VisitType;customVariableValue1==Customer" @@ -554,9 +737,12 @@ username = ; Proxy username: optional; if specified, password is mandatory password = ; Proxy password: optional; if specified, username is mandatory [Plugins] +; list of plugins (in order they will be loaded) that are activated by default in the Piwik platform Plugins[] = CorePluginsAdmin Plugins[] = CoreAdminHome Plugins[] = CoreHome +Plugins[] = WebsiteMeasurable +Plugins[] = Diagnostics Plugins[] = CoreVisualizations Plugins[] = Proxy Plugins[] = API @@ -568,8 +754,10 @@ Plugins[] = Actions Plugins[] = Dashboard Plugins[] = MultiSites Plugins[] = Referrers -Plugins[] = UserSettings +Plugins[] = UserLanguage +Plugins[] = DevicesDetection Plugins[] = Goals +Plugins[] = Ecommerce Plugins[] = SEO Plugins[] = Events Plugins[] = UserCountry @@ -579,8 +767,8 @@ Plugins[] = VisitTime Plugins[] = VisitorInterest Plugins[] = ExampleAPI Plugins[] = ExampleRssWidget -Plugins[] = Provider Plugins[] = Feedback +Plugins[] = Monolog Plugins[] = Login Plugins[] = UsersManager @@ -599,29 +787,31 @@ Plugins[] = MobileMessaging Plugins[] = Overlay Plugins[] = SegmentEditor Plugins[] = Insights - Plugins[] = Morpheus +Plugins[] = Contents +Plugins[] = BulkTracking +Plugins[] = Resolution +Plugins[] = DevicePlugins +Plugins[] = Heartbeat +Plugins[] = Intl +Plugins[] = PiwikPro [PluginsInstalled] +PluginsInstalled[] = Diagnostics PluginsInstalled[] = Login PluginsInstalled[] = CoreAdminHome PluginsInstalled[] = UsersManager PluginsInstalled[] = SitesManager PluginsInstalled[] = Installation - -[Plugins_Tracker] -Plugins_Tracker[] = Provider -Plugins_Tracker[] = Goals -Plugins_Tracker[] = PrivacyManager -Plugins_Tracker[] = UserCountry -Plugins_Tracker[] = Login +PluginsInstalled[] = Monolog +PluginsInstalled[] = Intl [APISettings] ; Any key/value pair can be added in this section, they will be available via the REST call -; index.php?module=API&method=API.getSettings +; index.php?module=API&method=API.getSettings ; This can be used to expose values from Piwik, to control for example a Mobile app tracking SDK_batch_size = 10 SDK_interval_value = 30 ; NOTE: do not directly edit this file! See notice at the top - + diff --git a/www/analytics/config/global.php b/www/analytics/config/global.php new file mode 100644 index 00000000..36e04970 --- /dev/null +++ b/www/analytics/config/global.php @@ -0,0 +1,85 @@ + PIWIK_USER_PATH, + + 'path.tmp' => function (ContainerInterface $c) { + $root = $c->get('path.root'); + + // TODO remove that special case and instead have plugins override 'path.tmp' to add the instance id + if ($c->has('ini.General.instance_id')) { + $instanceId = $c->get('ini.General.instance_id'); + $instanceId = $instanceId ? '/' . $instanceId : ''; + } else { + $instanceId = ''; + } + + return $root . '/tmp' . $instanceId; + }, + + 'path.cache' => DI\string('{path.tmp}/cache/tracker/'), + + 'Piwik\Cache\Eager' => function (ContainerInterface $c) { + $backend = $c->get('Piwik\Cache\Backend'); + $cacheId = $c->get('cache.eager.cache_id'); + + if (SettingsServer::isTrackerApiRequest()) { + $eventToPersist = 'Tracker.end'; + $cacheId .= 'tracker'; + } else { + $eventToPersist = 'Request.dispatch.end'; + $cacheId .= 'ui'; + } + + $cache = new Eager($backend, $cacheId); + \Piwik\Piwik::addAction($eventToPersist, function () use ($cache) { + $cache->persistCacheIfNeeded(43200); + }); + + return $cache; + }, + 'Piwik\Cache\Backend' => function (ContainerInterface $c) { + try { + $backend = $c->get('ini.Cache.backend'); + } catch (NotFoundException $ex) { + $backend = 'chained'; // happens if global.ini.php is not available + } + + return \Piwik\Cache::buildBackend($backend); + }, + 'cache.eager.cache_id' => function () { + return 'eagercache-' . str_replace(array('.', '-'), '', \Piwik\Version::VERSION) . '-'; + }, + + 'Psr\Log\LoggerInterface' => DI\object('Psr\Log\NullLogger'), + + 'Piwik\Translation\Loader\LoaderInterface' => DI\object('Piwik\Translation\Loader\LoaderCache') + ->constructor(DI\get('Piwik\Translation\Loader\JsonFileLoader')), + + 'observers.global' => array(), + + 'Piwik\EventDispatcher' => DI\object()->constructorParameter('observers', DI\get('observers.global')), + + 'Zend_Validate_EmailAddress' => function () { + return new \Zend_Validate_EmailAddress(array( + 'hostname' => new \Zend_Validate_Hostname(array( + 'tld' => false, + )))); + }, + + 'Piwik\Tracker\VisitorRecognizer' => DI\object() + ->constructorParameter('trustCookiesOnly', DI\get('ini.Tracker.trust_visitors_cookies')) + ->constructorParameter('visitStandardLength', DI\get('ini.Tracker.visit_standard_length')) + ->constructorParameter('lookbackNSecondsCustom', DI\get('ini.Tracker.window_look_back_for_visitor')) + ->constructorParameter('trackerAlwaysNewVisitor', DI\get('ini.Debug.tracker_always_new_visitor')), + + 'Piwik\Tracker\Settings' => DI\object() + ->constructorParameter('isSameFingerprintsAcrossWebsites', DI\get('ini.Tracker.enable_fingerprinting_across_websites')), + +); diff --git a/www/analytics/config/manifest.inc.php b/www/analytics/config/manifest.inc.php index 2a4eada2..f1013575 100644 --- a/www/analytics/config/manifest.inc.php +++ b/www/analytics/config/manifest.inc.php @@ -1,423 +1,1070 @@ array("890", "69246584e6b57bfbc8d39799cd3b9213"), - "composer.lock" => array("10488", "8a98b2fd7cda2b841fe15aa1e72402a5"), - "config/global.ini.php" => array("28859", "5d3e559652bf2ebd390c2a4c0054b02b"), - "console" => array("924", "a4877c66060ee26f1edc476a5ee72776"), - "core/Access.php" => array("12704", "c9839d3aa44e8edc82acfbf1c2e9a4aa"), - "core/API/DataTableGenericFilter.php" => array("5060", "ab1da3c6e3e965a56f5d1274b4703a69"), - "core/API/DataTableManipulator/Flattener.php" => array("4096", "1ee3893fee29909ea33f6e4b767c99d5"), - "core/API/DataTableManipulator/LabelFilter.php" => array("5127", "92fffb40df331cd852a222cc482ea273"), - "core/API/DataTableManipulator.php" => array("6316", "45d5aabe63f1d50766fb036dfd9e5aa1"), - "core/API/DataTableManipulator/ReportTotalsCalculator.php" => array("7459", "dc3674946b59f691b4da26775ab575a9"), - "core/API/DocumentationGenerator.php" => array("10290", "2a0b1f027ca702048e7af16e238f89fa"), - "core/API/Proxy.php" => array("20309", "b447ae17730f347efd4c8407e2172c85"), - "core/API/Request.php" => array("14637", "3373eb0d7cf984a910a18e0335c1576a"), - "core/API/ResponseBuilder.php" => array("17712", "5b666de76bebe3f4163d106da1ef1610"), - "core/Archive/DataCollection.php" => array("11407", "64bcbd03c02f25cc0548ec9960459830"), - "core/Archive/DataTableFactory.php" => array("13364", "3fc26be6b543d69fcf5a5e24b071c89c"), - "core/Archive/Parameters.php" => array("1333", "4d8b5cee96ed8db3b9accb37e3be80b8"), - "core/Archive.php" => array("33032", "9102dc388920e1ce6962103d3afc5e01"), - "core/ArchiveProcessor/Loader.php" => array("6875", "d6dea12a41b609b1fb1a2cc52dc8ae8a"), - "core/ArchiveProcessor/Parameters.php" => array("4312", "fe51534b7e53509e371481a13295cbcd"), - "core/ArchiveProcessor.php" => array("18191", "e1266497acaac1c9f772965a123d832d"), - "core/ArchiveProcessor/PluginsArchiver.php" => array("5983", "aa8b4c422def253126cec71a6a7f4e26"), - "core/ArchiveProcessor/Rules.php" => array("11465", "6133c6cccad15b54b92e22b46ede06c9"), - "core/AssetManager.php" => array("11423", "97e469ef84fdd26bd050169ab869e8b3"), - "core/AssetManager/UIAssetCacheBuster.php" => array("1508", "4d376f8e099aaea9a2341528bf1db023"), - "core/AssetManager/UIAssetCatalog.php" => array("1340", "a225abb9e87ad2ee661f81f855d44872"), - "core/AssetManager/UIAssetCatalogSorter.php" => array("1512", "c6989c08fb3124904ced569774468488"), - "core/AssetManager/UIAssetFetcher/JScriptUIAssetFetcher.php" => array("2772", "8aa332089d60c0362bfabe121020448c"), - "core/AssetManager/UIAssetFetcher.php" => array("2247", "fb0cd6454f394c71adcbb3c19d897dfd"), - "core/AssetManager/UIAssetFetcher/StaticUIAssetFetcher.php" => array("729", "736a364eb1c3f2bef8d420364a17c6f4"), - "core/AssetManager/UIAssetFetcher/StylesheetUIAssetFetcher.php" => array("1915", "5476bd9d96ec0effeed0bd4696dadae6"), - "core/AssetManager/UIAsset/InMemoryUIAsset.php" => array("1090", "f26f85ec2046ec9d767f7d69434247a8"), - "core/AssetManager/UIAssetMerger/JScriptUIAssetMerger.php" => array("2466", "9098ce95776b28e6d48fab8dab09ea74"), - "core/AssetManager/UIAssetMerger.php" => array("4549", "9ac6a5d8b409076548b0dcc277985e29"), - "core/AssetManager/UIAssetMerger/StylesheetUIAssetMerger.php" => array("4254", "d85777f9b6a2fffefbe3d329e450cc79"), - "core/AssetManager/UIAssetMinifier.php" => array("1727", "28d43428fd6ae8b1460c3e444e269b29"), - "core/AssetManager/UIAsset/OnDiskUIAsset.php" => array("2540", "f70f89fd35d15983aae8681e1facea6b"), - "core/AssetManager/UIAsset.php" => array("1198", "073b3a89bd5737ca6176b597ef16eea5"), - "core/Auth.php" => array("2915", "aeb757345dec1077cd0ba75774356962"), - "core/CacheFile.php" => array("5665", "985342d2fdae584019a7f4c1e955976a"), - "core/CliMulti/Output.php" => array("1063", "d00891b635aaf4d1dc2120ee39b5c5a6"), - "core/CliMulti.php" => array("8349", "265403a19c082fc4b0b4c74959a9208b"), - "core/CliMulti/Process.php" => array("4681", "3a6faa96f191b8d405f6a4972abd13b3"), - "core/CliMulti/RequestCommand.php" => array("2249", "c224d55ef5d6a3a55295cf35259feb96"), - "core/Common.php" => array("35753", "a8c976da84bc9ff1c54918992725eadc"), - "core/Config.php" => array("22997", "147ab7aa770b1fa5423e46871bc0278a"), - "core/Console.php" => array("4872", "c594ab40478f30fa294ea96484389439"), - "core/Cookie.php" => array("11300", "9f160dcf724d3f0f30ac6c50556ca88d"), - "core/CronArchive/FixedSiteIds.php" => array("1305", "7b0307e871cc50d74bbd00e8e26cccbd"), - "core/CronArchive.php" => array("47570", "18a2222278c77c7f4fba542005fa3f17"), - "core/CronArchive/SharedSiteIds.php" => array("4596", "0bc0bd027d24d9cb11f8cc99f73023bc"), - "core/DataAccess/ArchiveSelector.php" => array("14351", "98eaf72ed205b6261ca663d93a6693fe"), - "core/DataAccess/ArchiveTableCreator.php" => array("3320", "455700705f6f6c784f97dc2c84be1f7e"), - "core/DataAccess/ArchiveWriter.php" => array("9384", "3e3235f3a37fdd1e6a78e5624048dbf3"), - "core/DataAccess/LogAggregator.php" => array("41234", "7f733c60c036d166c31cea37567791e9"), - "core/DataArray.php" => array("16587", "ca482fd7e31e0f3479387c80ae746e88"), - "core/DataFiles/Countries.php" => array("7867", "32e4468ba434ffd83bc07d70ad7f4969"), - "core/DataFiles/Currencies.php" => array("8648", "4edf0cb07ef189624b4d14d682183d7a"), - "core/DataFiles/Languages.php" => array("7317", "7cbf38c6feae3f1dea58bc3d74a6e6a4"), - "core/DataFiles/LanguageToCountry.php" => array("2520", "d2de6f0a9b23560bc52c7b1726f1e5c7"), - "core/DataFiles/Providers.php" => array("1792", "bbb4dec4a616cd73ae6566feadc24154"), - "core/DataFiles/SearchEngines.php" => array("45747", "c4f37ecde5286617e51a445300502034"), - "core/DataFiles/Socials.php" => array("5467", "ff231864e2c740a1cac4c70311c09756"), - "core/DataTable/BaseFilter.php" => array("2042", "fce60bb7fa6dc0fe4f9ac0cc5cb0de69"), - "core/DataTable/Bridges.php" => array("562", "d0d252681214102d0f10a277bbddad57"), - "core/DataTable/DataTableInterface.php" => array("857", "19babd1eb37f5541bdd436b2eadbca5e"), - "core/DataTable/Filter/AddColumnsProcessedMetricsGoal.php" => array("10060", "19245f7364829705ed645129187a26be"), - "core/DataTable/Filter/AddColumnsProcessedMetrics.php" => array("5444", "6cab57109eb9db0400a5d747bf88539e"), - "core/DataTable/Filter/AddSummaryRow.php" => array("1380", "072952c5aebbc41e28a032f3766b3e71"), - "core/DataTable/Filter/BeautifyRangeLabels.php" => array("6074", "ea93fc021a0a3f19c9a83111e63279d3"), - "core/DataTable/Filter/BeautifyTimeRangeLabels.php" => array("4548", "bcc614caf8c17085b9ac4b12e15701cd"), - "core/DataTable/Filter/CalculateEvolutionFilter.php" => array("5891", "fe1b274e742a054dbc701d905a5087f1"), - "core/DataTable/Filter/ColumnCallbackAddColumnPercentage.php" => array("1012", "2132297b4a7073faf44f38eec883eb58"), - "core/DataTable/Filter/ColumnCallbackAddColumn.php" => array("2845", "ceb1d9be8b76d9869585dfe107c4a45a"), - "core/DataTable/Filter/ColumnCallbackAddColumnQuotient.php" => array("5056", "6e5056fd84b5745733e65c628609e184"), - "core/DataTable/Filter/ColumnCallbackAddMetadata.php" => array("3021", "29eea90b9dbb6b5c3ce730bd5eb8ef29"), - "core/DataTable/Filter/ColumnCallbackDeleteRow.php" => array("2460", "8bf70a091da4f4e014ae393fbc999bc2"), - "core/DataTable/Filter/ColumnCallbackReplace.php" => array("4249", "fec351b71143e9298255c6e86a12ca6c"), - "core/DataTable/Filter/ColumnDelete.php" => array("5062", "dc7f5cd71a488bbc996fd38f1bf0bb30"), - "core/DataTable/Filter/ExcludeLowPopulation.php" => array("3379", "9847cc4a48fc0549546b51427f223457"), - "core/DataTable/Filter/GroupBy.php" => array("3472", "551e742f8d0c5e64e89b0660969fa6f5"), - "core/DataTable/Filter/Limit.php" => array("1892", "276f4352a6918be4c549d008d406c7c0"), - "core/DataTable/Filter/MetadataCallbackAddMetadata.php" => array("2651", "e0576cdfa84a99765f6e88960bb8e422"), - "core/DataTable/Filter/MetadataCallbackReplace.php" => array("2346", "be71bc6d2a6aca70a04404b42a1085f0"), - "core/DataTable/Filter/Pattern.php" => array("2933", "08e09a9da40a39a12d262fbc51d2753d"), - "core/DataTable/Filter/PatternRecursive.php" => array("2743", "12abb36c7bde6d4cf981f9d9a0684c9c"), - "core/DataTable/Filter/RangeCheck.php" => array("1607", "091a7675c8217cb03448b129b8daca5e"), - "core/DataTable/Filter/ReplaceColumnNames.php" => array("5749", "ff15fca13dcd7b5ed2f669f583233dc9"), - "core/DataTable/Filter/ReplaceSummaryRowLabel.php" => array("2089", "ada7b250bd2ea1079d3b10387763627f"), - "core/DataTable/Filter/SafeDecodeLabel.php" => array("1832", "4e46025ff3ae6b1fb1b8fae2db421785"), - "core/DataTable/Filter/Sort.php" => array("6432", "424750b4a5d7f94f4896c4705837cc53"), - "core/DataTable/Filter/Truncate.php" => array("4175", "7ea93e0bd0abd847c0e919207d1a1320"), - "core/DataTable/Manager.php" => array("4400", "c95164db72086a7c6479257aeef0d173"), - "core/DataTable/Map.php" => array("13011", "8dfdbc7dfe0f7992a83977005f0e2324"), - "core/DataTable.php" => array("57572", "d509d1932005f8dfb00c3a9f03ea78c9"), - "core/DataTable/Renderer/Console.php" => array("5028", "4b1b777fec6b5ae5ccd9c9ec2d523a0a"), - "core/DataTable/Renderer/Csv.php" => array("12195", "d127b1ac63e24895389b17cb43072752"), - "core/DataTable/Renderer/Html.php" => array("5839", "559f0fc629844984a9adf5edeb0b1507"), - "core/DataTable/Renderer/Json.php" => array("3671", "e4b3138a405fc1e129185150cee5b8ce"), - "core/DataTable/Renderer.php" => array("13396", "cfad13e54038358b5776d4cda01e98ee"), - "core/DataTable/Renderer/Php.php" => array("7732", "c603669a6b9945e711ba68b59c8b737d"), - "core/DataTable/Renderer/Rss.php" => array("6068", "fbb296b297c6d3ad4705196c587f76db"), - "core/DataTable/Renderer/Tsv.php" => array("701", "772cf9af832f601154357535a62a9d32"), - "core/DataTable/Renderer/Xml.php" => array("15715", "37c3a6d8f2b5965c720e2b6abca0c0b0"), - "core/DataTable/Row/DataTableSummaryRow.php" => array("1903", "8a25ba9516a43929261f157023402b49"), - "core/DataTable/Row.php" => array("21822", "91d9896e5123c829035a1011c524dd3a"), - "core/DataTable/Simple.php" => array("967", "5e662c2478307b1a9ca6b69294b99c0d"), - "core/DataTable/TableNotFoundException.php" => array("234", "74c2ecb43569238eb30d3f91a21941d8"), - "core/Date.php" => array("22031", "9a8a5696c6595e2ea04d5e5b2f17a61d"), - "core/Db/AdapterInterface.php" => array("1372", "e61f5273b376e6698f3a0a83e15896a6"), - "core/Db/Adapter/Mysqli.php" => array("4455", "8aef57991aae0aef6210a958569c2415"), - "core/Db/Adapter/Pdo/Mssql.php" => array("7582", "f5bbeeb60469cd98048933056286bcec"), - "core/Db/Adapter/Pdo/Mysql.php" => array("6172", "a1c2f38bf52784583abd6dbc7af639fe"), - "core/Db/Adapter/Pdo/Pgsql.php" => array("4769", "decde138f1566ea834a159b8694433f1"), - "core/Db/Adapter.php" => array("2807", "87a49f2522ddbec917fb1d7ec1456708"), - "core/Db/BatchInsert.php" => array("9296", "33039a060eaff7d7abaff65eaaa1c93b"), - "core/DbHelper.php" => array("3822", "a2b18b7df87431bcc7d9740557150412"), - "core/Db.php" => array("24578", "9f2e812e7a76db407a59a9f7495d1e14"), - "core/Db/SchemaInterface.php" => array("2112", "f55d75b05af2cb7de8133942faef36ca"), - "core/Db/Schema/Mysql.php" => array("19642", "9fe1d3d20a5c3a824fa13d7ae7e8da7c"), - "core/Db/Schema.php" => array("5616", "622f40c2f6a7a49e333f7309bb7e7111"), - "core/dispatch.php" => array("955", "6a9a4c5f99af08ed0048f8b2de1886ec"), - "core/Error.php" => array("7210", "5153b7ae689b464cce8b721ddb361d28"), - "core/EventDispatcher.php" => array("6188", "787ae06fb148af48dacf6fe2ab768586"), - "core/ExceptionHandler.php" => array("2053", "86c9c27bd7868af403d53c8a0b143fff"), - "core/Filechecks.php" => array("8786", "576db7c700047fbe3de33abd3a3e6dd3"), - "core/Filesystem.php" => array("10596", "fefaf74a3736cb39ee144e22a77a30ac"), - "core/FrontController.php" => array("20372", "3acd41cc80fdf61723cf0ec8f94c60b0"), - "core/Http.php" => array("29559", "4319b7bb5789c60dfb67b91f2c09520e"), - "core/IP.php" => array("14032", "c418038633d69c9c6acb2f512b08ae14"), - "core/Loader.php" => array("3251", "8a3541845131b8a209b60057b1a97568"), - "core/Log.php" => array("22487", "ae607b359689ee4539d1acade5dfe0bd"), - "core/Mail.php" => array("3051", "dd77f65726ccb1fb35125bc446fc7e9e"), - "core/Menu/MenuAbstract.php" => array("8516", "2380174f7029dad633a52319be1af913"), - "core/Menu/MenuAdmin.php" => array("3752", "d52a4c7296cb935b74122b42a802d8ab"), - "core/Menu/MenuMain.php" => array("2644", "d74adf1afca5dc8fc72e80b93b99a5c4"), - "core/Menu/MenuTop.php" => array("4237", "62983c9f66e4caf4812af80c9407b348"), - "core/MetricsFormatter.php" => array("8530", "aa60c76868df55960a3235d7e2f4b37f"), - "core/Metrics.php" => array("14842", "b1dbee19d075b83a3080a99909b0b380"), - "core/Nonce.php" => array("5260", "6908ea4c50150171fd6eed821a916a14"), - "core/Notification/Manager.php" => array("3903", "a23c790d484dc3ac76e19947efb6fd54"), - "core/Notification.php" => array("5732", "1cbe56fe6665c7206ca88739aad8832f"), - "core/Option.php" => array("6684", "3b110408402a646d6c7861dfd3eaed4e"), - "core/Period/Day.php" => array("2058", "dd8cc0376ce9235221bbc25c8730bc41"), - "core/Period/Month.php" => array("1630", "974b6b2604e1f9ef2a928ab4eaab58b7"), - "core/Period.php" => array("9748", "def7194a46591d12a1b66eb93a97a9cb"), - "core/Period/Range.php" => array("15936", "30c69c5eacb62cc6c173e58b95ca96dc"), - "core/Period/Week.php" => array("2544", "663151eb94b86e7a30404e49146088c2"), - "core/Period/Year.php" => array("1869", "da04fd59cdd94516ecb842c8ea00e5f2"), - "core/Piwik.php" => array("26583", "e8e87eb48bd0df5b155c9649e55cd457"), - "core/Plugin/API.php" => array("1108", "ee9a5d269cf33cde142c8c438322ace9"), - "core/Plugin/Archiver.php" => array("4319", "bd0c156de5ef58c75c3bb68cc79bc6f5"), - "core/Plugin/ConsoleCommand.php" => array("1805", "f1850b6f0d989d47b369afe18fb261f2"), - "core/Plugin/ControllerAdmin.php" => array("7991", "fe4081f2d6d58c5587b1b462afe20b0a"), - "core/Plugin/Controller.php" => array("38680", "7fcbba7a289fb5b1908046e7ef1a35ff"), - "core/Plugin/Dependency.php" => array("2890", "aaa67fe7929ebd4ca30a19a9635a94b8"), - "core/Plugin/Manager.php" => array("37639", "21ba8ef6db04c3800ea9074dafd4c7fe"), - "core/Plugin/MetadataLoader.php" => array("2553", "22ce1fad1069035d6692129b65cec244"), - "core/Plugin.php" => array("10373", "715f226ecb91745815cac730585df8d5"), - "core/Plugin/Settings.php" => array("12013", "ddb5052a732359804337c4eadee99bb4"), - "core/Plugin/ViewDataTable.php" => array("15746", "d6e134e0ae998383ea20d981106400a8"), - "core/Plugin/Visualization.php" => array("22404", "e0313135a50bbf1cd236a4de20606f81"), - "core/Profiler.php" => array("9578", "fb3a2b8d4abe4e848976ea842560f064"), - "core/ProxyHeaders.php" => array("2329", "2c2040497ebd44a257a7813b873ce8ae"), - "core/ProxyHttp.php" => array("9293", "a66d7477c1715daa43bfaf03848bc2d1"), - "core/QuickForm2.php" => array("3991", "38181ddffae0ddb03dded14f44ae8233"), - "core/RankingQuery.php" => array("12775", "86664cae23f6c9669606d1189d33d4af"), - "core/Registry.php" => array("1104", "5f93fbe06640eef9980ada45677c6ebe"), - "core/ReportRenderer/Csv.php" => array("3977", "5ec7fe33c5a71d6a0044446d2dc823bf"), - "core/ReportRenderer/Html.php" => array("5797", "b46530e59c537058a1cd5bebed7d5d9c"), - "core/ReportRenderer/Pdf.php" => array("20769", "1ace48a18c949c67516c3124db35cad7"), - "core/ReportRenderer.php" => array("7959", "bf5443a70c2f0c83ee4649139b3d25a2"), - "core/ScheduledTask.php" => array("5696", "83c2633ccf1c6d970827bb43bbe59578"), - "core/ScheduledTaskTimetable.php" => array("3398", "166f58a5c50c282c7c29d6f644ccc58a"), - "core/ScheduledTime/Daily.php" => array("1233", "d022648a5088a797a26348dc450fd774"), - "core/ScheduledTime/Hourly.php" => array("1300", "05f17149dbc4a4fbe3763097abc2526a"), - "core/ScheduledTime/Monthly.php" => array("4052", "8badd753f6c06f07ecac69f5e91bf16c"), - "core/ScheduledTime.php" => array("7625", "ab30399dcfca1ba1f3aa2d89c67e3cc8"), - "core/ScheduledTime/Weekly.php" => array("2032", "8de6f993989ee66ea786ad1ddc76baab"), - "core/SegmentExpression.php" => array("12147", "1c989bf36472614b2be51beef8f59eea"), - "core/Segment.php" => array("16496", "4a4795937a2c7726150e5d5d797a1f4c"), - "core/Session.php" => array("5408", "9c0b50a50516a2078ee3e043e5c5cdf8"), - "core/Session/SaveHandler/DbTable.php" => array("3400", "231cf14b800bbfbe688ced3fba0c743c"), - "core/Session/SessionNamespace.php" => array("656", "43fd5d53fdb1a60b41e93be8c8f80b44"), - "core/Settings/Manager.php" => array("4028", "42535abca90e8eb964a46c7289dde797"), - "core/SettingsPiwik.php" => array("12885", "a07fe55e36ea565d9bdb38c384345ce5"), - "core/SettingsServer.php" => array("6313", "2785b33d06035d586e892f2868ff57d0"), - "core/Settings/Setting.php" => array("6833", "82707639f441064423be496ecb229ede"), - "core/Settings/StorageInterface.php" => array("1615", "eacd97cea047d06e137d569e15af1179"), - "core/Settings/SystemSetting.php" => array("984", "821339fb538bc6fe35f6cc2ede502e8b"), - "core/Settings/UserSetting.php" => array("3449", "79cc353b6019a245fef3f0bacc6ec7e6"), - "core/Singleton.php" => array("1353", "ee0398b6ffb82242e498de630f73c091"), - "core/Site.php" => array("15122", "9ce12ed8d49fb31fbabf3380052cd199"), - "core/TaskScheduler.php" => array("6626", "4850a933550e4570430b73512cceab7a"), - "core/TCPDF.php" => array("1971", "a94bb257665eefecceadf7f753d7fc77"), - "core/testMinimumPhpVersion.php" => array("7398", "deada667cb4971e1296074cdc780d07f"), - "core/Theme.php" => array("4541", "d4de1d79c1cd622603ca70e5f0af91ae"), - "core/Timer.php" => array("1808", "39acf463b724b25bcce2583d7292d87f"), - "core/Tracker/ActionClickUrl.php" => array("1742", "61c02b31e21b6f9a401eb657fee4364e"), - "core/Tracker/ActionEvent.php" => array("2265", "336607e4d024a5f08c3be764f33c2460"), - "core/Tracker/ActionPageview.php" => array("2013", "366739709f90aa8d232511834f11414b"), - "core/Tracker/Action.php" => array("9811", "2f4aa70d96316e21d62aa56ce1cac5fb"), - "core/Tracker/ActionSiteSearch.php" => array("9254", "1b0f81d05b783b2bca998cd896e1ab8f"), - "core/Tracker/Cache.php" => array("6289", "94a28a7608f12b3e8acffe67c7acc211"), - "core/Tracker/Db/DbException.php" => array("271", "e705dd83b4a7498aa4f37ba24f7a82ca"), - "core/Tracker/Db/Mysqli.php" => array("7571", "63642f63c2364d44188c65e543d44a34"), - "core/Tracker/Db/Pdo/Mysql.php" => array("6810", "32af933dd0219d7f17234d61b6222f75"), - "core/Tracker/Db/Pdo/Pgsql.php" => array("3204", "d76f5782916317fa5d560baddf554877"), - "core/Tracker/Db.php" => array("6120", "f2fda7c7dfc83c28c5ab585ed47e26f0"), - "core/Tracker/GoalManager.php" => array("35981", "be9605a31afa46221ad36eb0a1dcb249"), - "core/Tracker/IgnoreCookie.php" => array("1762", "9c438161c21ee28ca5fceeb67d0884f8"), - "core/Tracker/PageUrl.php" => array("11076", "3c36bc8fdc8fcaf564f25a97ad1c766f"), - "core/Tracker.php" => array("28846", "9edc0aec5accc5d1f6c3a1bdea7fced1"), - "core/Tracker/Referrer.php" => array("11012", "0697bfd2c20a646fc6de922a7ec6e153"), - "core/Tracker/Request.php" => array("18612", "8796d52a20094467b8f3cb46b24ee4c6"), - "core/Tracker/TableLogAction.php" => array("9173", "0688a6b89a8eef0943bbb051dd5464b8"), - "core/Tracker/VisitExcluded.php" => array("8453", "d9179d7244a794b2a9fc471e53e0a96b"), - "core/Tracker/VisitInterface.php" => array("591", "44e61d7caeb0701b0a0cbd4351b8a4d5"), - "core/Tracker/VisitorNotFoundInDb.php" => array("237", "bb64791a33dfee4d64aff1f8495302ec"), - "core/Tracker/Visit.php" => array("42168", "193887df85ce090bcfb9f50bcf326422"), - "core/Translate/Filter/ByBaseTranslations.php" => array("1711", "0592c1a63f43e15c49b6f9579e80b0e2"), - "core/Translate/Filter/ByParameterCount.php" => array("2399", "1b054c3111be6abbbb393efcd80cdecf"), - "core/Translate/Filter/EmptyTranslations.php" => array("1140", "21a8ee941ba93b652f22043424ef0ca6"), - "core/Translate/Filter/EncodedEntities.php" => array("1028", "0fcf7b3c769742621beef97887b6f968"), - "core/Translate/Filter/FilterAbstract.php" => array("651", "385a5f448f1f38e14570a3e462c477b9"), - "core/Translate/Filter/UnnecassaryWhitespaces.php" => array("2408", "502ac2955cf7be7f655ddd5bc0c867b1"), - "core/Translate.php" => array("6857", "b18b68afe4900f9a9b42df364e4370ae"), - "core/Translate/Validate/CoreTranslations.php" => array("3269", "42f30ed563dbac7855e1558194bb28f6"), - "core/Translate/Validate/NoScripts.php" => array("1023", "d1db00f141ac004f0cb195a44d327468"), - "core/Translate/Validate/ValidateAbstract.php" => array("685", "5af8be0c67032bb6405c3dc6dd36a88f"), - "core/Translate/Writer.php" => array("9383", "2ffc837feb2e9e3c3b27f03a0d0cb931"), - "core/Twig.php" => array("10465", "f2cdbdb455ea6537b221905d9823d7bb"), - "core/Unzip/Gzip.php" => array("1588", "c37fe684918ef45cf5fceba0d90f2db8"), - "core/Unzip/PclZip.php" => array("2203", "a9b66ff795a00b9f9489231c40f00f38"), - "core/Unzip.php" => array("1246", "962c3818d46540dba5d0bdb1ff99d747"), - "core/Unzip/Tar.php" => array("1991", "aca63f78845e3a94b2c7c4afc65e0fa1"), - "core/Unzip/UncompressInterface.php" => array("789", "914626f0c5ff3750f2db9b618146a9fa"), - "core/Unzip/ZipArchive.php" => array("4446", "aa086fbaa27dc9653c49019022e912dc"), - "core/UpdateCheck.php" => array("3554", "78229bc91a7ab4d029e2c1a8f1285f11"), - "core/Updater.php" => array("11899", "1f59e2ee92b7cec575344d2c51fa4eae"), - "core/Updates/0.2.10.php" => array("2651", "876e9fe07b71a4fe9d76fd8fd174f0a6"), - "core/Updates/0.2.12.php" => array("932", "59f8221c661ccf08e94b5bf923d67c51"), - "core/Updates/0.2.13.php" => array("786", "349c538337653cb7c1b61603001ead79"), - "core/Updates/0.2.24.php" => array("978", "db0f1757f204103520d52a369d305406"), - "core/Updates/0.2.27.php" => array("2791", "4947fca28656acd2e1343514c7203ec3"), - "core/Updates/0.2.32.php" => array("1092", "0962b3c0781f2621e15825facc5f5ed8"), - "core/Updates/0.2.33.php" => array("1185", "a9d96ddf9036a5dd819da808ed875d68"), - "core/Updates/0.2.34.php" => array("592", "98ae3b028633eba1791d9b92c794d760"), - "core/Updates/0.2.35.php" => array("587", "a5c358cd52da528bcf9becb54172df8c"), - "core/Updates/0.2.37.php" => array("642", "ec8400f457c7a945713fe252947a9436"), - "core/Updates/0.4.1.php" => array("806", "e223d3c70ea7e3c1d804489a91853c7b"), - "core/Updates/0.4.2.php" => array("1156", "d37d6ddf7068db07377601fa6595ffb7"), - "core/Updates/0.4.4.php" => array("660", "c72894136443d613abb674baa7c11338"), - "core/Updates/0.4.php" => array("1167", "98af006ec25c0c0140e7f5ad9135794d"), - "core/Updates/0.5.4.php" => array("1958", "779d5f3a0d4d326f9e2f74d1c8cd128e"), - "core/Updates/0.5.5.php" => array("1293", "7f0033047ccc97f6d66d30390402070a"), - "core/Updates/0.5.php" => array("2096", "959cd8b36d92f8ef20ebc1ad78b1b28f"), - "core/Updates/0.6.2.php" => array("1126", "ac49a76d42ed5be509d3c7a40276ea6d"), - "core/Updates/0.6.3.php" => array("1279", "d2fb0180705d57144e3652f69ec3b166"), - "core/Updates/0.6-rc1.php" => array("4507", "9634cef986b658716b06e3a62c066adb"), - "core/Updates/0.7.php" => array("594", "6210a8eeb517c83d6d839e6b1864c13f"), - "core/Updates/0.9.1.php" => array("1454", "1c5185e07ca0bb2f08c93a86c65ed4b4"), - "core/Updates/1.10.1.php" => array("515", "9c1f18c6bed150d9088e4ffbd6ae85e0"), - "core/Updates/1.10.2-b1.php" => array("660", "6b8e21cc1f8082b01858940dee50fb92"), - "core/Updates/1.10.2-b2.php" => array("674", "64b57788df79cbc9ea1180b2ca2f4ced"), - "core/Updates/1.10-b4.php" => array("524", "da5f285f2e7de1a17ef8d39df8778fbf"), - "core/Updates/1.11-b1.php" => array("523", "e230d3911722fd98b6cbf349d34c664d"), - "core/Updates/1.12-b15.php" => array("452", "31134ebb6971b1459ca2f876d4892f22"), - "core/Updates/1.12-b16.php" => array("650", "7e4186377a518ddb166b1d55b8d25bac"), - "core/Updates/1.12-b1.php" => array("668", "bb6566428c9bf4382ff58cba3bc90091"), - "core/Updates/1.1.php" => array("971", "270bf13926c63efc04e6157d1e351ef8"), - "core/Updates/1.2.3.php" => array("1077", "f667e11902edc92fd98b335c236e2afe"), - "core/Updates/1.2.5-rc1.php" => array("864", "a36b86d356ce093a63177efcc56a8d3c"), - "core/Updates/1.2.5-rc7.php" => array("604", "8db19752e89143fe991cd9529d8be11d"), - "core/Updates/1.2-rc1.php" => array("6822", "346d520a049c2426fb2442e7f29347c3"), - "core/Updates/1.2-rc2.php" => array("434", "c92ff71c25dfb7eccec537abf745e1e1"), - "core/Updates/1.4-rc1.php" => array("805", "0691f7982446f93ddcc1047291c86001"), - "core/Updates/1.4-rc2.php" => array("1639", "c9f5619ad1c8a92545c73d548e44b58a"), - "core/Updates/1.5-b1.php" => array("2262", "ba785cbad196b247571781cadada94b6"), - "core/Updates/1.5-b2.php" => array("1087", "5f5e77f985c277d714b7281d7f965069"), - "core/Updates/1.5-b3.php" => array("2805", "3b4e6761ecdf28343cb3184223525f75"), - "core/Updates/1.5-b4.php" => array("571", "ad302b8cb925c0c10a417456fb502b07"), - "core/Updates/1.5-b5.php" => array("700", "96c669e08b45e60525fe1bb1fb943c9d"), - "core/Updates/1.5-rc6.php" => array("433", "db7f6f03adbb92928cb439c53d9d9ef9"), - "core/Updates/1.6-b1.php" => array("3151", "e5544d116d44ab8538b92eeeca4f3c58"), - "core/Updates/1.6-rc1.php" => array("429", "abc0632476540e4e8ef7e8a0c5d311f9"), - "core/Updates/1.7.2-rc5.php" => array("675", "c204aae2d7f7689d37f4091c37524268"), - "core/Updates/1.7.2-rc7.php" => array("1310", "111c0d0cb4f19766ff8e7635c387eb48"), - "core/Updates/1.7-b1.php" => array("801", "7d0bf59a66243ae5adb41e01b584a30a"), - "core/Updates/1.8.3-b1.php" => array("4214", "d2dab6e537e059c3f42be3b3fd00c1d2"), - "core/Updates/1.8.4-b1.php" => array("5602", "f6e09c6abb389ebb423530214fcb3732"), - "core/Updates/1.9.1-b2.php" => array("756", "926f80df9c4e1874aefe77d59a6ec56e"), - "core/Updates/1.9.3-b10.php" => array("522", "4b239e1966e6183b52fe8a56887e0480"), - "core/Updates/1.9.3-b3.php" => array("687", "effe8f40e5733e5a0244421735944f63"), - "core/Updates/1.9.3-b8.php" => array("730", "34ccb3d0357dbcd9a0d9df4030fdbf36"), - "core/Updates/1.9-b16.php" => array("1493", "07e70dd30d645aada68bfd71e65e9e49"), - "core/Updates/1.9-b19.php" => array("976", "a1f50808b7f7339bec7bb31c7423b120"), - "core/Updates/1.9-b9.php" => array("1440", "67363fcdc79574bda86993f3cea59405"), - "core/Updates/2.0.3-b7.php" => array("1816", "80902356a3532433940567151aaa5a82"), - "core/Updates/2.0.4-b5.php" => array("2589", "95f1d30f619a50ad99ad21d52713c949"), - "core/Updates/2.0.4-b7.php" => array("1773", "1bb19c5e4277994b1f414647def74cd6"), - "core/Updates/2.0.4-b8.php" => array("2124", "91e5b1108076279e5a3c8b0764887573"), - "core/Updates/2.0-a12.php" => array("1221", "c96c6665f2f05c26527968bb9391b01e"), - "core/Updates/2.0-a13.php" => array("2361", "bcc1c2d24b5e72bace075ac8075918fb"), - "core/Updates/2.0-a17.php" => array("1015", "c5d6321d2bbb8fd436c733a4213e3513"), - "core/Updates/2.0-a7.php" => array("872", "72f65ce2241de0e53e02e8f9a6911709"), - "core/Updates/2.0-b10.php" => array("404", "c10a63eb97efeb774f78b5efdbf8a866"), - "core/Updates/2.0-b13.php" => array("986", "897baf57c6104b512bb81ec186ed173a"), - "core/Updates/2.0-b3.php" => array("1122", "5f4d8995e425682b8e2feb9dc162e35a"), - "core/Updates/2.0-b9.php" => array("653", "8d3bd1b10367fac6d9ac351addd2e4da"), - "core/Updates/2.0-rc1.php" => array("431", "172c8341bd784da35b3b83b2c2f5b1f6"), - "core/Updates/2.1.1-b11.php" => array("5745", "acce7f7e6ff35875809478861353dbcb"), - "core/Updates/2.2.0-b15.php" => array("639", "c89858a73296cda3e66b2ded05ba6f4d"), - "core/Updates.php" => array("2803", "2deac460daaceeee0fa1fe9e479185bb"), - "core/UrlHelper.php" => array("18349", "dbd9d9cd951cfafa14e29bcc382502c5"), - "core/Url.php" => array("17731", "554508c27948cb1b29da65844b73b6aa"), - "core/Version.php" => array("345", "636e0ebdf450757751fa98b6ccca24c4"), - "core/ViewDataTable/Config.php" => array("20852", "41d12a2246ca76006b223105edcf5e4d"), - "core/ViewDataTable/Factory.php" => array("7098", "36920d013e8245b49cbf92ba2c0b500d"), - "core/ViewDataTable/Manager.php" => array("8846", "51e33664ee6ae517ed42648eddd31964"), - "core/ViewDataTable/RequestConfig.php" => array("8907", "343b0baae408f730c167129bf1ba2571"), - "core/ViewDataTable/Request.php" => array("4031", "d7b2c817853f519a8b2fda06e6211038"), - "core/View/OneClickDone.php" => array("2077", "bf64751dc4c8fa73f7bbc49b319308c6"), - "core/View.php" => array("12640", "47020ad4b4284ce91a83b1149a1190e5"), - "core/View/RenderTokenParser.php" => array("2051", "a8b5b4566507c29e1fdd09bfaeedd99e"), - "core/View/ReportsByDimension.php" => array("4192", "d72f400326ec14706f7f08f5386828c3"), - "core/View/UIControl.php" => array("4410", "d16a751a2063cac0b834ab63b448fdef"), - "core/View/ViewInterface.php" => array("403", "4a9732dd334255ba0ead94222d74f87f"), - "core/Visualization/Sparkline.php" => array("4576", "3533412a25076c86b0b2a2474a024e74"), - "core/WidgetsList.php" => array("7060", "dc525a4a4e618d56ae0563d22549bb08"), - "index.php" => array("1546", "15d01f358d90f66a19dcab6946679095"), - "js/index.php" => array("749", "a80b88022a2748952a62401871d025bd"), - "js/LICENSE.txt" => array("1549", "c1d16895887980c4494a95a7f6ee3671"), - "js/piwik.js" => array("125559", "9f58141514a1bb0001636373cbb8e068"), - "js/README.md" => array("2142", "a59319062bafe9657baac94e9fdb7995"), - "lang/am.json" => array("43298", "2e05b5fda32bdb397888910de8165463"), - "lang/ar.json" => array("116774", "108b69246990df2762bfddbe22696501"), - "lang/be.json" => array("133845", "d4791872ccafc77296fdc7f01c18935a"), - "lang/bg.json" => array("236400", "c618daca1fc95e32e57eefdcefcd6642"), - "lang/bn.json" => array("30389", "de30a94afc6c0cfd5e39805821dbcb8a"), - "lang/bs.json" => array("47846", "5f02521870cfe3146efee207e59bca4b"), - "lang/ca.json" => array("148886", "6219033d88bf58c96e5c09029708e38b"), - "lang/cs.json" => array("96631", "95e50796f835a36f0dd4a2f5b45ba362"), - "lang/cy.json" => array("33669", "3ae7125bad892632b4dbadbcde73e0eb"), - "lang/da.json" => array("175497", "3770805fc026e5100fd9ae140849514e"), - "lang/de.json" => array("192559", "021bc6cbdbce3eaae44017ef834d2f09"), - "lang/el.json" => array("293119", "f3a72418fd95b71ca8ec2ba7f833e0f2"), - "lang/en.json" => array("176161", "9c820d429d7a4cc0db23376a3166d9e2"), - "lang/es.json" => array("187985", "003cf7629a086625ba4277b9d1bdbe50"), - "lang/et.json" => array("89740", "7c339aa2dca5c02fcddd31ea37566a98"), - "lang/eu.json" => array("46504", "220e4c85c784d98f04b7915a07070dc0"), - "lang/fa.json" => array("185290", "3b5a3430bd600b2980f13418a2457884"), - "lang/fi.json" => array("171923", "92fabf05e4bc46c407dba69407077b53"), - "lang/fr.json" => array("191935", "169d8019b419009aaeee37c7f5ab2682"), - "lang/gl.json" => array("31239", "6e8e2ee3b00d9edb52d909ddf3765d0f"), - "lang/he.json" => array("66841", "5b3bded179f179a250499886a4dcb141"), - "lang/hi.json" => array("267566", "1243af856ce0f2de652a47777c01bdeb"), - "lang/hr.json" => array("44181", "d4d1f8a8210b743dfc74b2909c93e29d"), - "lang/hu.json" => array("88631", "5a393be54f20621ec373aae7952119b0"), - "lang/id.json" => array("160275", "8fe3288e6ab07886257964d2755af0d0"), - "lang/is.json" => array("44565", "d3e6d051c1bd28ec5677c50582e2249b"), - "lang/it.json" => array("188242", "f7547dcef63cce6d91627d6c001d22c1"), - "lang/ja.json" => array("146362", "f47bd751197cfc3d072f806d8deaaef2"), - "lang/ka.json" => array("133972", "d919209c916cd66daad1e233b97c24a6"), - "lang/ko.json" => array("159126", "7e91ce1f784680466763eb7b5a664c87"), - "lang/lt.json" => array("72812", "bacac0086478ec8d07f63229cd109c04"), - "lang/lv.json" => array("68729", "4073b4637d26b430110508ff59a800e8"), - "lang/nb.json" => array("79362", "b0da604e507b5c621b7e697ea1c6bf84"), - "lang/nl.json" => array("159749", "25663b03d177a31f8a8986dd8edcbde6"), - "lang/nn.json" => array("72987", "61ece0f6d31afbd53f42e840643613a8"), - "lang/pl.json" => array("105283", "96ea726f5a9c5ebf8b85f531069760eb"), - "lang/pt-br.json" => array("178705", "955707f471cbde34c6597fc06199469b"), - "lang/pt.json" => array("103399", "c8db5c35133612b7badeca45967a8cf8"), - "lang/README.md" => array("439", "7f5249109f73ae292bcd01eb23853738"), - "lang/ro.json" => array("107388", "4e16d637081c5f7d3bf887debbadee3d"), - "lang/ru.json" => array("236266", "3601f2fbf822f4e3eade4a99f25f0abb"), - "lang/sk.json" => array("72678", "82b11fbdab21b8bcb55665b48794ff42"), - "lang/sl.json" => array("71473", "8e2836b6c48a39847f029a08cc864768"), - "lang/sq.json" => array("108350", "abde20a3257f155aa6b6e88dd65830c9"), - "lang/sr.json" => array("171239", "b348d6ce2bb4f75a190b0e468e0b1584"), - "lang/sv.json" => array("179173", "ce59c0b2c6dab1f8180c6f87daffc90d"), - "lang/ta.json" => array("89885", "f861e7329aac8c31dc1f634d7bab8625"), - "lang/te.json" => array("45655", "41136b9370c8df0f8264d098e576a984"), - "lang/th.json" => array("161630", "ee834a50d83b3a356b0ee7069b80d6f2"), - "lang/tr.json" => array("67775", "d3059314807802e6e9c308c02c7a2b1f"), - "lang/uk.json" => array("101355", "305b6faaf47f8f52ff96070300759047"), - "lang/vi.json" => array("201534", "7a21f65bd54d7b500a4fbf1e6d4de09c"), - "lang/zh-cn.json" => array("149382", "3a6056925b838c9c17ec72ce97705958"), - "lang/zh-tw.json" => array("69883", "1b3dc84c6da058b41f3faf6f33052302"), - "LEGALNOTICE" => array("7322", "cec47f121512f46d75f04edc3821e0e9"), - "libs/angularjs/angular-animate.js" => array("73299", "f57eec6cfc24a9d7de89e7815c54b9cb"), - "libs/angularjs/angular-animate.min.js" => array("10506", "5927bc7044bf9f88c7fcef982cfc84e0"), - "libs/angularjs/angular-cookies.js" => array("5712", "8f359b0b2ccce92d6d0f27a275178646"), - "libs/angularjs/angular-cookies.min.js" => array("850", "f70eb186d69b8c34b9fc083fd95ad46e"), - "libs/angularjs/angular-csp.css" => array("346", "34f147527516ef16935f3499d13fdadf"), - "libs/angularjs/angular.js" => array("737843", "823656f04f0d28049733ea5472eaf885"), - "libs/angularjs/angular-loader.js" => array("14464", "dbd936b897ff5995f868309b1682973e"), - "libs/angularjs/angular-loader.min.js" => array("1505", "540937ba021f2ba2d65679de81cfc225"), - "libs/angularjs/angular.min.js" => array("101076", "f13ec1ca50778530d05f5e7d55964873"), - "libs/angularjs/angular-mocks.js" => array("67709", "b64cf0b49047bda8058ffebcb4b969ba"), - "libs/angularjs/angular-resource.js" => array("23494", "e567b1825d04f7d3c8c4932bafe45c92"), - "libs/angularjs/angular-resource.min.js" => array("3297", "52f14aa58396ba6f06c873048ac757ad"), - "libs/angularjs/angular-route.js" => array("32505", "8eaf3177d26ff41eadaa0bb325cc84cc"), - "libs/angularjs/angular-route.min.js" => array("3885", "39e7751707e2fb3108f6148c957bb726"), - "libs/angularjs/angular-sanitize.js" => array("20736", "bccd4bf0adf6318da2e84e87eea451ef"), - "libs/angularjs/angular-sanitize.min.js" => array("4246", "57046863351fc02e0058321e20691254"), - "libs/angularjs/angular-scenario.js" => array("1080031", "b4202d55bb0ef488e710d0ec0b718f7b"), - "libs/angularjs/angular-touch.js" => array("20706", "f1c23f96e05f1cf0991be9f0ac5f44d8"), - "libs/angularjs/angular-touch.min.js" => array("3205", "27edc5cdcdb0cc9da0a8e1bc6510cb9b"), - "libs/angularjs/errors.json" => array("5385", "4de0bf0d0e45b08c96275179d1e45445"), - "libs/angularjs/LICENSE" => array("1098", "0547d38c79782d84bb4df3c036a0bcc0"), - "libs/angularjs/version.json" => array("102", "2ca74bf29fbf307a7f626d922bb20696"), - "libs/angularjs/version.txt" => array("6", "88979175ec60695fcd6e3c97243cdecd"), - "libs/Archive_Tar/Tar.php" => array("66956", "501bf628481dce9ded472e4ae24d1c7a"), - "libs/html5shiv/html5shiv.js" => array("2427", "11af8654413ddf8b0fe00c3395787e77"), + "bower.json" => array("932", "a53acf32175be6e1097f43d7caaf19f9"), + "CHANGELOG.md" => array("36412", "0eeec5e48ef475da856d158d85ecb17e"), + "composer.json" => array("3483", "de61be52972a0fe8fe751306c271f4b8"), + "composer.lock" => array("96582", "d31ce9595493a228c06d04744acbbd04"), + "config/environment/dev.php" => array("416", "27239ab7df09b6997d1503a58fd5f0bf"), + "config/environment/test.php" => array("3826", "0574d36a20fdc5d2921b9fdb56801e94"), + "config/environment/ui-test.php" => array("2371", "5558f0df8a360aea4ac2ef7a26507e91"), + "config/global.ini.php" => array("39258", "8b6e0857ded5b3b9409b8360587b16c6"), + "config/global.php" => array("3102", "a9f65997997900a0c2816d5e78c03117"), + "console" => array("689", "b89a89288416184c6fc76e029d746211"), + "CONTRIBUTING.md" => array("700", "4a0126deecb900a4b0c3aae95aa9e515"), + "core/Access.php" => array("14255", "baaba9f6c9e66db2e0d1899cd6bff63a"), + "core/API/ApiRenderer.php" => array("3544", "d95b52974730120b83517a7cfe757ee3"), + "core/API/CORSHandler.php" => array("936", "04ca601b499bc60e0526fb4f6b9683cc"), + "core/API/DataTableGenericFilter.php" => array("6658", "7d2ae6fbf9f9898a3806bec9ea645a2a"), + "core/API/DataTableManipulator/Flattener.php" => array("4615", "596866ee834f78d1f4149966ac2e1e78"), + "core/API/DataTableManipulator/LabelFilter.php" => array("6048", "7a5554ef49ffee709082231f0fccef50"), + "core/API/DataTableManipulator.php" => array("6733", "79f3699d86502c88502574a392f2b973"), + "core/API/DataTableManipulator/ReportTotalsCalculator.php" => array("7422", "c782df8d1b48b3c876566d62d8a76420"), + "core/API/DataTablePostProcessor.php" => array("15122", "a0393bedf70ce89758bb318abee0da36"), + "core/API/DocumentationGenerator.php" => array("16028", "8bf07e2b005ec485dd33d8d58ffbc2ad"), + "core/API/Inconsistencies.php" => array("1267", "2609882ac7aced8574bcf948c9a01ad0"), + "core/API/Proxy.php" => array("21438", "09612a4c058f0dd4b97d7334e1de6d86"), + "core/API/Request.php" => array("19528", "c476d6be682149a836f3c7aabc0570d8"), + "core/API/ResponseBuilder.php" => array("9446", "870270905e45498fdcf302172bfaad99"), + "core/Application/EnvironmentManipulator.php" => array("1649", "6dbdaba74fcc168504ba8ae0ee48e263"), + "core/Application/Environment.php" => array("7282", "6ffb92b5b91a929b13c5e1349cdd6000"), + "core/Application/Kernel/EnvironmentValidator.php" => array("2403", "528525e3a3e7a2b404b193ac54a64cce"), + "core/Application/Kernel/GlobalSettingsProvider.php" => array("2841", "5906966232aa0b97b2d95515e4781ecd"), + "core/Application/Kernel/PluginList.php" => array("3373", "048f4fd44165938e2bb232835eb0bb05"), + "core/Archive/ArchiveInvalidator/InvalidationResult.php" => array("1485", "8eebf5cac5846e865d8ea9b52e6ddabe"), + "core/Archive/ArchiveInvalidator.php" => array("10512", "ed335369b9aad39d660d9a2180be8ef7"), + "core/Archive/ArchivePurger.php" => array("9620", "038dc88337663e38f0d529a85f8371d2"), + "core/Archive/Chunk.php" => array("4497", "0ba051ecf2539b61de253264fcabf5fa"), + "core/Archive/DataCollection.php" => array("12559", "80010277e191ff708368a0cd45fd1ed8"), + "core/Archive/DataTableFactory.php" => array("17860", "567181b7c3d5b779b60938b7b76d9510"), + "core/Archive/Parameters.php" => array("1029", "35b01b1524b487de5898963f0495407b"), + "core/Archive.php" => array("36235", "a86fad6e05d609ece1bfc475ca6078c2"), + "core/ArchiveProcessor/Loader.php" => array("7668", "bb908afd463e2348c5840903ce70842b"), + "core/ArchiveProcessor/Parameters.php" => array("4090", "9acca7a0758dcbd304050df21fbb19fa"), + "core/ArchiveProcessor.php" => array("22393", "6f127ce147f1348e5d1fc47accdfe279"), + "core/ArchiveProcessor/PluginsArchiver.php" => array("7811", "1436f0b97ecc07ab6b407edeedbccc62"), + "core/ArchiveProcessor/Rules.php" => array("9925", "c8495433ac322e836170460e5385a7a9"), + "core/Archiver/Request.php" => array("741", "a959db4ff137ec0ceb694ae0b3b9459f"), + "core/AssetManager.php" => array("11565", "da7731fd80f2eb2426d7c7f42871dd36"), + "core/AssetManager/UIAssetCacheBuster.php" => array("1512", "d3665922dab19d06e1ebf46b7f34f9f9"), + "core/AssetManager/UIAssetCatalog.php" => array("1449", "e78abd58f417d1a5a181147ad732ecb1"), + "core/AssetManager/UIAssetCatalogSorter.php" => array("1523", "ba51945bc4c0f65ab299bd96eda78bd5"), + "core/AssetManager/UIAssetFetcher/JScriptUIAssetFetcher.php" => array("2995", "e4ca818eaca60c892d8a740323ab79bf"), + "core/AssetManager/UIAssetFetcher.php" => array("2270", "92412ef0ca70c078c69567bebf36fadf"), + "core/AssetManager/UIAssetFetcher/StaticUIAssetFetcher.php" => array("739", "758f8dcefd0ddb6957fa2020c7d926cc"), + "core/AssetManager/UIAssetFetcher/StylesheetUIAssetFetcher.php" => array("2354", "3abc9973624bbe4762b86819954046ed"), + "core/AssetManager/UIAsset/InMemoryUIAsset.php" => array("1093", "09d2f65d9226e7a05d975678d34831bb"), + "core/AssetManager/UIAssetMerger/JScriptUIAssetMerger.php" => array("2406", "6f117aec5229fd3200c68fcda8ae3f2f"), + "core/AssetManager/UIAssetMerger.php" => array("4176", "8f66c5adab442805c45ab2252801cbd8"), + "core/AssetManager/UIAssetMerger/StylesheetUIAssetMerger.php" => array("4994", "4fdf12e5eb24db90936b4e79dfc28f3d"), + "core/AssetManager/UIAssetMinifier.php" => array("1735", "52e1fc18128304f091525209bbf2ddba"), + "core/AssetManager/UIAsset/OnDiskUIAsset.php" => array("2709", "1dc6a4a6e6956366250adbb05b9c1124"), + "core/AssetManager/UIAsset.php" => array("1202", "1aa6935c82b4bf06c7973898adc18311"), + "core/Auth.php" => array("6113", "5f01585abf520f55f1e8c0b76c98ef39"), + "core/BaseFactory.php" => array("1703", "eb8862177d58c70cae4610fcefaf08d3"), + "core/bootstrap.php" => array("1825", "d4f57207e954d0ba5db1d39cbd973b97"), + "core/CacheId.php" => array("678", "c545036c692908982b1c7bb4fce0ba8f"), + "core/Cache.php" => array("3572", "9bd1eff45172d8115f765550c040c8ff"), + "core/CliMulti/CliPhp.php" => array("2415", "28100e5127c4270a433af26cf2588f88"), + "core/CliMulti/Output.php" => array("1365", "3d6f4f6607f87c0d13f316d14665eb87"), + "core/CliMulti.php" => array("10887", "8a53fef217addb2e2b20e1f3b3222380"), + "core/CliMulti/Process.php" => array("6275", "168693d4591a5536fb41f9d47643523d"), + "core/CliMulti/RequestCommand.php" => array("3628", "04cacb08a25b1ded6ad8787db7345aa4"), + "core/Columns/Dimension.php" => array("6686", "f84d24342b22776fc85fe1e862ebe772"), + "core/Columns/Updater.php" => array("15383", "7ec773b6c0ef1dc5d189e78d450f161b"), + "core/Common.php" => array("41168", "1da5e1a95d13639bddd440ca1fcec32d"), + "core/Composer/ScriptHandler.php" => array("1069", "3c232a235313a3952f3be1c8e7adf9a8"), + "core/Concurrency/DistributedList.php" => array("4259", "5b26014730d1e9f6cb2c84eaf7d38b0c"), + "core/Config/ConfigNotFoundException.php" => array("296", "3de0a882212918def5458e2ba8406deb"), + "core/Config/IniFileChain.php" => array("15780", "56a2093783f6e4d7e0771a600291c1db"), + "core/Config.php" => array("11059", "d6ea70b90e058f13ae1abfc33ff04aa0"), + "core/Console.php" => array("6868", "ea38f4aff3b96b3a70230efe84ee6ce8"), + "core/Container/ContainerDoesNotExistException.php" => array("360", "4015d88d190271af36698fc83168e3da"), + "core/Container/ContainerFactory.php" => array("4224", "18e45926c0eaceac552a5088243a0921"), + "core/Container/IniConfigDefinitionSource.php" => array("2206", "c598467a334cb61abb2f1e86a46cabe0"), + "core/Container/StaticContainer.php" => array("1968", "2e00378ebf7530a10dfb4cc1ca93116d"), + "core/Cookie.php" => array("11461", "8c5d82bf265801980d9f6713a63834f7"), + "core/CronArchive/FixedSiteIds.php" => array("1284", "aa4a399d9eb5bf26b082de952052292f"), + "core/CronArchive.php" => array("62627", "25ffa4b168cb92d8bbdfe1df80fc992d"), + "core/CronArchive/SegmentArchivingRequestUrlProvider.php" => array("7977", "7e997c70128bbb29606f2d647dd8c516"), + "core/CronArchive/SharedSiteIds.php" => array("4833", "8e99eb0b10dcd809a4a3d3d39f830d55"), + "core/CronArchive/SitesToReprocessDistributedList.php" => array("1015", "ed72a0aecc63d1a7fadbf689da9eff34"), + "core/DataAccess/Actions.php" => array("730", "6a573f3c38217812f0fa321bd58a033b"), + "core/DataAccess/ArchiveSelector.php" => array("13920", "09043083b1b13d81f27e3099d8af2343"), + "core/DataAccess/ArchiveTableCreator.php" => array("3244", "c4709d1b7084f1e4bb0bd46c81c4f55e"), + "core/DataAccess/ArchiveTableDao.php" => array("3267", "fecb7478d77333675fe9a55b3e2f8c00"), + "core/DataAccess/ArchiveWriter.php" => array("7664", "5a933065334cdcec3b679f30358a8284"), + "core/DataAccess/LogAggregator.php" => array("42646", "cad25cd093d16e6068dc1934a2dad2c7"), + "core/DataAccess/LogQueryBuilder.php" => array("15474", "a3f53f45f774cf29505effddb3b2b286"), + "core/DataAccess/Model.php" => array("12717", "cd4ff4bf5a7a75224ff98812f800254c"), + "core/DataAccess/RawLogDao.php" => array("13481", "4d9b0d3cf667bab473073bedef297565"), + "core/DataAccess/TableMetadata.php" => array("1429", "06530dcea7f12e755507852f68b64e92"), + "core/DataArray.php" => array("18573", "d704495631d966d3a849aca4380bd6a6"), + "core/DataFiles/cacert.pem" => array("258069", "31e74d3b981e5fd89e10f2dd92b7a0bf"), + "core/DataFiles/Providers.php" => array("1796", "b3981a0e46d14a6b6733eb73582824f4"), + "core/DataTable/BaseFilter.php" => array("1962", "8d0895efe663067a088b13d0de1db36b"), + "core/DataTable/Bridges.php" => array("864", "690b9444bef7cb957ccb9aeeab797add"), + "core/DataTable/DataTableInterface.php" => array("861", "7dc36f12bc9ebd52eaf1431e8b1ea8a8"), + "core/DataTable/Filter/AddColumnsProcessedMetricsGoal.php" => array("6839", "c8f30dd8113b3cae3d4788573933af8f"), + "core/DataTable/Filter/AddColumnsProcessedMetrics.php" => array("3108", "ccbf38f0a632728ac48220f91b9bf08b"), + "core/DataTable/Filter/AddSegmentByLabelMapping.php" => array("1573", "66a7f7411ea11b367bcfe67bd8242a03"), + "core/DataTable/Filter/AddSegmentByLabel.php" => array("3250", "4ada3fb332c275a0534b4028cec0a147"), + "core/DataTable/Filter/AddSegmentBySegmentValue.php" => array("1862", "b009b774281803d8c4c6ccfb53b2c431"), + "core/DataTable/Filter/AddSegmentValue.php" => array("815", "c53508cba03306ba825bbf0aff372345"), + "core/DataTable/Filter/AddSummaryRow.php" => array("1381", "2454d091551cc5f41858b16566ebd0d0"), + "core/DataTable/Filter/BeautifyRangeLabels.php" => array("6077", "e58a217369ec08d2cf1f236072e23f33"), + "core/DataTable/Filter/BeautifyTimeRangeLabels.php" => array("4548", "c61c80d570d6db0492b38bf6d4905358"), + "core/DataTable/Filter/CalculateEvolutionFilter.php" => array("6163", "41bdc80b0944bce9e172dd3360e1f385"), + "core/DataTable/Filter/ColumnCallbackAddColumnPercentage.php" => array("1014", "b192f6689787c5db2d424733bab6fd45"), + "core/DataTable/Filter/ColumnCallbackAddColumn.php" => array("3569", "9930e9bcb718422a44d6e52494f37a73"), + "core/DataTable/Filter/ColumnCallbackAddColumnQuotient.php" => array("5048", "cf21c49fd168a36040c95e0fdbbfe9dc"), + "core/DataTable/Filter/ColumnCallbackAddMetadata.php" => array("3063", "c82351905c5bf7d485e5dad69a991c98"), + "core/DataTable/Filter/ColumnCallbackDeleteMetadata.php" => array("1318", "5b7ddf859f130800b70d3f618f41607d"), + "core/DataTable/Filter/ColumnCallbackDeleteRow.php" => array("2432", "e4b9e87361a427ae4a312a9678418ab4"), + "core/DataTable/Filter/ColumnCallbackReplace.php" => array("4421", "e69b3320f7b0cd3033672a7651f6b1e9"), + "core/DataTable/Filter/ColumnDelete.php" => array("5100", "a9dfc6fffafea29454fa24421a79f78d"), + "core/DataTable/Filter/ExcludeLowPopulation.php" => array("4333", "de340d2d8dd98d770103da55698a2f37"), + "core/DataTable/Filter/GroupBy.php" => array("3716", "1a0c35d9c4b567869f4c25f9adba17a3"), + "core/DataTable/Filter/Limit.php" => array("1895", "673c5777380e28a62ff617e45477d2af"), + "core/DataTable/Filter/MetadataCallbackAddMetadata.php" => array("2680", "bc588d2fe74248048e9eee6171a4f7bf"), + "core/DataTable/Filter/MetadataCallbackReplace.php" => array("2347", "0b94bc6a1177a3e3b2faf6552529d864"), + "core/DataTable/Filter/Pattern.php" => array("3608", "741c748630ac92f6869e9e26c86ba89a"), + "core/DataTable/Filter/PatternRecursive.php" => array("2548", "5e4776abe6360abc10fb4399172936b2"), + "core/DataTable/Filter/PivotByDimension.php" => array("20576", "e835242ddf0520612b1ac2f5cefa1643"), + "core/DataTable/Filter/PrependSegment.php" => array("916", "7330235ff32797105ccf9901a815abeb"), + "core/DataTable/Filter/PrependValueToMetadata.php" => array("1839", "4207f49e1b877f9e8c0011b30a9f19b8"), + "core/DataTable/Filter/RangeCheck.php" => array("2176", "688424acb31f69de9550418536b76244"), + "core/DataTable/Filter/ReplaceColumnNames.php" => array("5631", "c059555f586e099a496412899304c964"), + "core/DataTable/Filter/ReplaceSummaryRowLabel.php" => array("2042", "11f3399ce9ab64de1cb117424af459b5"), + "core/DataTable/Filter/SafeDecodeLabel.php" => array("1835", "120603c3cb24fb2d0c2d13629f3b0425"), + "core/DataTable/Filter/Sort.php" => array("7947", "95c443d2f43efb7955cf873dc1611718"), + "core/DataTable/Filter/Truncate.php" => array("4360", "72f09d072570b1c1c20268865b38e0ee"), + "core/DataTable/Manager.php" => array("4307", "c3b5b199fc58a0c4ff39a2b44349b105"), + "core/DataTable/Map.php" => array("14715", "b65cd2c33732e73aaa111623eda04934"), + "core/DataTable.php" => array("65275", "288ef225eab72eb0cd5e3788751c6873"), + "core/DataTable/Renderer/Console.php" => array("4668", "0d9fcfb6611e770e1aab513b2623e9ba"), + "core/DataTable/Renderer/Csv.php" => array("13569", "80150167b3092887d6ed0f9082945a47"), + "core/DataTable/Renderer/Html.php" => array("5425", "782e5917fc41cc233466bddfafd043aa"), + "core/DataTable/Renderer/Json.php" => array("2559", "a6c09118d5df967e770ab0fad8d4a38e"), + "core/DataTable/Renderer.php" => array("12030", "dfa2c0ddeb6a2cf6cf600967704cee49"), + "core/DataTable/Renderer/Php.php" => array("7211", "cce3a60218d09ef600c0eca694fc030d"), + "core/DataTable/Renderer/Rss.php" => array("5527", "20b70c785e000a90dac603a6341238bd"), + "core/DataTable/Renderer/Tsv.php" => array("718", "40d08a020e0dfd1abc7e2e29f5b9e4f9"), + "core/DataTable/Renderer/Xml.php" => array("16599", "ebbf351d287288c8b90c9b757cd6bfb0"), + "core/DataTable/Row/DataTableSummaryRow.php" => array("2035", "029fee4adfad884e57f2af08b34f4def"), + "core/DataTable/Row.php" => array("23215", "2380e7ebb6382d7b982656f056ab55f1"), + "core/DataTable/Simple.php" => array("949", "eed3e8bae119305c45245561dcc26490"), + "core/DataTable/TableNotFoundException.php" => array("236", "d20b1cd593341976965ff239e1d20f07"), + "core/Date.php" => array("30919", "a9fa3cabe6862ccce70f14891fc49566"), + "core/Db/AdapterInterface.php" => array("1376", "b76e32c114c3be1b72da40ac5fdeaa5b"), + "core/Db/Adapter/Mysqli.php" => array("4703", "858b2d9e885c293be5208320e4766387"), + "core/Db/Adapter/Pdo/Mssql.php" => array("7537", "6dbd56dc5ca816e5ed253b2cb217691b"), + "core/Db/Adapter/Pdo/Mysql.php" => array("6807", "f95b8e1659af5284c672e2b15c949bbe"), + "core/Db/Adapter/Pdo/Pgsql.php" => array("4717", "afae9926296e42cb6602f22476b93985"), + "core/Db/Adapter.php" => array("3134", "6659a8c19d0467687fa1fde81e9587d9"), + "core/Db/BatchInsert.php" => array("9659", "933d756325925458c38ae2f5947f9c00"), + "core/DbHelper.php" => array("4595", "df048bbcec43bcedb718799a0900de0d"), + "core/Db.php" => array("27004", "d3bdfc2e22ae67965e72a965fcb7e96b"), + "core/Db/SchemaInterface.php" => array("2201", "1ecdac30e16334d5b777fac696b03d8a"), + "core/Db/Schema/Mysql.php" => array("20904", "d658b2ece43b1bbed49d69418078cbc6"), + "core/Db/Schema.php" => array("4457", "e57fd807adc994ad227b6009ef3a64de"), + "core/Db/Settings.php" => array("803", "2d50ee3e267f89e10f8fe014cdfa8102"), + "core/Development.php" => array("6648", "73aea90822fb126781d480cae708e415"), + "core/DeviceDetectorCache.php" => array("2151", "941f8c7afe0e918ae1abb3c64bc7bd25"), + "core/DeviceDetectorFactory.php" => array("983", "0106a3ebdef29c5515f57cd1a2c7d007"), + "core/dispatch.php" => array("946", "70af16b3b0712420a29e57fee56f1116"), + "core/ErrorHandler.php" => array("4557", "56c7357e740f7045c5a0c071370b2ba5"), + "core/EventDispatcher.php" => array("6693", "7e43dda0304ddcac2119922bbcaf2188"), + "core/Exception/AuthenticationFailedException.php" => array("242", "03569d50c99c2826e38eeb363aaca2be"), + "core/Exception/DatabaseSchemaIsNewerThanCodebaseException.php" => array("255", "1adc44615b8e4aa1aa9338b2a361383a"), + "core/Exception/ErrorException.php" => array("380", "7466dc25931bc56fa2dfcd21d8711057"), + "core/Exception/Exception.php" => array("639", "ddbbfcc7d725528119aaad170dab9275"), + "core/ExceptionHandler.php" => array("3686", "a1821d0fcf963a325c0ec1b3789db208"), + "core/Exception/InvalidRequestParameterException.php" => array("245", "e36bf68c3289ea85a0630d6deaec8f9e"), + "core/Exception/MissingFilePermissionException.php" => array("243", "f0dec73dc0b363cf77c6d78bbb7984fb"), + "core/Exception/NoPrivilegesException.php" => array("234", "fd3f1cd06ac88fbca9f9b98cfe0f664a"), + "core/Exception/NoWebsiteFoundException.php" => array("236", "986b23877e39de9d514257620d86abd4"), + "core/Exception/UnexpectedWebsiteFoundException.php" => array("244", "79ce36dd4c78bb28ad2ddf4951ebc245"), + "core/Filechecks.php" => array("9250", "107dfed3b846efd353a488bf3287caaa"), + "core/Filesystem.php" => array("16057", "a3f5cf6972f6236c1249f6d70c70b866"), + "core/FrontController.php" => array("20149", "4a7e168f6fe0b729a14cbaf083e3f962"), + "core/Http/ControllerResolver.php" => array("3756", "7ecca7ba5206dd979a068a9851684ef3"), + "core/Http.php" => array("34909", "f55dd5741e1aae8e16b50348820f1734"), + "core/Http/Router.php" => array("906", "d5c83a5ef470e70763e7fa1a7783827e"), + "core/Intl/Data/Provider/CurrencyDataProvider.php" => array("777", "551a10021fe039419af3d570587d9023"), + "core/Intl/Data/Provider/DateTimeFormatProvider.php" => array("2133", "b1df0224a07886edca7f157eab32c7f8"), + "core/Intl/Data/Provider/LanguageDataProvider.php" => array("1316", "0e0fb5afee1e58232f19a059f8b6a511"), + "core/Intl/Data/Provider/RegionDataProvider.php" => array("1480", "cd7883f3e703d99fbde35929106c1416"), + "core/Intl/Data/Resources/continents.php" => array("462", "4b09814881cec1ed513058566e3c003c"), + "core/Intl/Data/Resources/countries-extra.php" => array("1377", "923b0a830ecabd680a3ca8ac83a369ec"), + "core/Intl/Data/Resources/countries.php" => array("5468", "0fa69a37b7801fcfe287009f95ecbdd9"), + "core/Intl/Data/Resources/currencies.php" => array("7919", "fb256d28473ef1e6911693e2c7e4a649"), + "core/Intl/Data/Resources/languages.php" => array("6500", "5010596e39fbec3975afe6240395c69a"), + "core/Intl/Data/Resources/languages-to-countries.php" => array("2269", "01890c31b02f8bd55bbd10c41a9bd648"), + "core/Intl/Locale.php" => array("1092", "d69a28471ab6cc188f5b68e15d13587a"), + "core/IP.php" => array("4263", "87869784c745552835a9608a5b844fee"), + "core/LogDeleter.php" => array("3769", "4c307c949b50159db7192ddd69e3f1e7"), + "core/Log.php" => array("7772", "5640acd6c1345cc951501822ad7a2d3e"), + "core/Mail.php" => array("4992", "4f79fb8e7f1fd4452fa6e62130fb632c"), + "core/Measurable/Measurable.php" => array("720", "4165c06a37f452ceda09447d599664a8"), + "core/Measurable/MeasurableSetting.php" => array("1807", "9b4c4e04e1ca69b1e83cc5b22b9e2f09"), + "core/Measurable/MeasurableSettings.php" => array("3095", "53bb4dbafe9c98e9ffb23d409e79ee3c"), + "core/Measurable/Settings/Storage.php" => array("2650", "c523559ef17c6a40e4d150bcc18827d2"), + "core/Measurable/Type.php" => array("1409", "d39d87deb72a7e665d5c058bd1e8c8b9"), + "core/Measurable/Type/TypeManager.php" => array("764", "dc16f3ac253c9717a9cb7c83fdf171ca"), + "core/Menu/Group.php" => array("531", "1d168fb585879fe2067f42948658700b"), + "core/Menu/MenuAbstract.php" => array("12037", "5c4e6caeaeeedb22eba95a2c7db742ff"), + "core/Menu/MenuAdmin.php" => array("3663", "6be47baaac3221ccbaafb17f05c3e5c2"), + "core/Menu/MenuMain.php" => array("329", "0681af8c36bc5518ad0b4e678d990f6e"), + "core/Menu/MenuReporting.php" => array("3945", "c79532032efc43bf3f4ec4f5c1cdaf00"), + "core/Menu/MenuTop.php" => array("2244", "abc2f1bfd3d7aa3dad1883fe4d8f1a8c"), + "core/Menu/MenuUser.php" => array("2604", "bbc9c9f5d07155134121694cf549af34"), + "core/Metrics/Formatter/Html.php" => array("1320", "e24b8efb23561b391dbfea737180ab69"), + "core/Metrics/Formatter.php" => array("10172", "dd36cd68936019827eddebc51e1505ec"), + "core/MetricsFormatter.php" => array("2044", "544d2045542a6400218ab413332d7ee0"), + "core/Metrics.php" => array("17776", "16876a085528f1809dd9ef4ad593a8f3"), + "core/Nonce.php" => array("5278", "8629d0efe09f512e156e9c2fa8129ec0"), + "core/Notification/Manager.php" => array("4639", "4aaa21a3cf855bbe005fcd8e5060c4be"), + "core/Notification.php" => array("5711", "6b640f97f0108bc407fa07cfc5cf0d84"), + "core/NumberFormatter.php" => array("11211", "dbb836c9db476f29b9d4a19b1db8d68c"), + "core/Option.php" => array("7006", "ca958d52506f8437c2990c876cb06e20"), + "core/Period/Day.php" => array("2194", "ad3c9ebff44b941b4d0ee9e9a3b319d0"), + "core/Period/Factory.php" => array("4101", "b1bc16d65ab0676e1c4c9eb87ffdf1df"), + "core/Period/Month.php" => array("2989", "130dae73ac24f8f3ca9683f860d00c25"), + "core/Period/PeriodValidator.php" => array("1105", "4ecff97d7f6011041109751a1abdbd9d"), + "core/Period.php" => array("11738", "5ffcbc06fa02f336039fe1d0c8a16750"), + "core/Period/Range.php" => array("17339", "70dede775fcf2c4351cfcbeecc733c6d"), + "core/Period/Week.php" => array("2001", "dbbf6ed1e0b769d36f3d9275fd326478"), + "core/Period/Year.php" => array("2084", "cab9627d3a1401c40bac4faaff1e22e7"), + "core/Piwik.php" => array("21796", "028eeffec0e7c3dc6162154b09c7517e"), + "core/PiwikPro/Advertising.php" => array("3885", "bf081b4a20a9a2fa0b2c4b36fbdd6124"), + "core/Plugin/AggregatedMetric.php" => array("609", "6c6904e8f148dbf93e4ed973f108b61b"), + "core/Plugin/API.php" => array("2726", "743de5e429f5b95b3987400e360b6068"), + "core/Plugin/Archiver.php" => array("4274", "9211abae3264fcbed831c6f9dec3f8b6"), + "core/Plugin/ComponentFactory.php" => array("5057", "5666dc9a7a366684967fe778e38ca9ca"), + "core/Plugin/ConsoleCommand.php" => array("1450", "0fcee729503052cfd4dda3faefe64dd3"), + "core/Plugin/ControllerAdmin.php" => array("10701", "7bf1d8d48e5568e47abb0f299ba01da3"), + "core/Plugin/Controller.php" => array("41653", "77870887696277ce4ff397396058a9a4"), + "core/PluginDeactivatedException.php" => array("503", "e588dcac865d328aa7e80a56287b73ce"), + "core/Plugin/Dependency.php" => array("2910", "8cf99f6f904a90adb57bd983b0c5dc69"), + "core/Plugin/Dimension/ActionDimension.php" => array("9343", "6d879b12704d7bced2da4b9eb2913ea2"), + "core/Plugin/Dimension/ConversionDimension.php" => array("7999", "2e0dfb4aff81e444c45c4697dc8288c3"), + "core/Plugin/Dimension/DimensionMetadataProvider.php" => array("3375", "33c6b3c8da4ac7440f78cbf279aaf0fb"), + "core/Plugin/Dimension/VisitDimension.php" => array("14094", "204632db2ff50deea3678c541aebf356"), + "core/Plugin/Manager.php" => array("40494", "89a3c7cf068a8a906f78cce4fa649422"), + "core/Plugin/Menu.php" => array("10587", "ca9317c58d44817e5dc59a2be0465cb0"), + "core/Plugin/MetadataLoader.php" => array("2773", "ea985b736f03f57d84935aa35da6ef5e"), + "core/Plugin/Metric.php" => array("6286", "43a030e4f61dab13526c21c3329ce0c7"), + "core/Plugin.php" => array("17392", "d8287cf6729e89560e40b9d9ac484708"), + "core/Plugin/PluginException.php" => array("1135", "62c32d13fab8e734c958ede2f6088bfd"), + "core/Plugin/ProcessedMetric.php" => array("2055", "29f9c4e9b2af9498dc4e1ad7613cd14b"), + "core/Plugin/ReleaseChannels.php" => array("2578", "3e005c5e6c0f33ca8239465c4a8dbaa0"), + "core/Plugin/Report.php" => array("31950", "0977a62ebc4abcc1edf6ed4e874f4e66"), + "core/Plugin/RequestProcessors.php" => array("627", "82b34eaf6f8ba947d3fd8496c7a022a3"), + "core/Plugin/Segment.php" => array("10325", "6a75c70d54e9958666a1dbc24e828c7d"), + "core/Plugin/Settings.php" => array("9275", "b3448ca05c1e42e4628f91f17f80380e"), + "core/Plugin/Tasks.php" => array("5494", "2b7819ed6ef29a8b9ca975d6682acba9"), + "core/Plugin/ViewDataTable.php" => array("19359", "b0257aa89e9d00c83cbe7ccb95070fc5"), + "core/Plugin/Visualization.php" => array("28130", "d05aba629c7f23c85ba4c6324f775587"), + "core/Plugin/Widgets.php" => array("6080", "e40f161da230b167af08bc151389362b"), + "core/Profiler.php" => array("11605", "0f7eda0e1fb580514e8b3202850a618d"), + "core/ProxyHeaders.php" => array("2209", "3b4911643f8b324a9306478fabf9c7d1"), + "core/ProxyHttp.php" => array("10512", "a7f9e74ff5d118f0c56766aad9e437ee"), + "core/QuickForm2.php" => array("4030", "67a7f96d3361e949f9af570cde78b443"), + "core/RankingQuery.php" => array("12792", "3010f308493eabd03163a0daca433d83"), + "core/Registry.php" => array("1246", "90d8f95d29909388b0bc4dcc9448a242"), + "core/ReportRenderer/Csv.php" => array("4444", "da1dd74f9fe897038033cb4171edcc02"), + "core/ReportRenderer/Html.php" => array("7754", "8bd7ff2f39a98aae21c74a222738e000"), + "core/ReportRenderer/Pdf.php" => array("21790", "87708551539360cb617ff8a2acbe2dcb"), + "core/ReportRenderer.php" => array("8337", "fd7a298df1aedc425c81b60799b11be2"), + "core/ScheduledTask.php" => array("508", "6959610b9389a000e67fbc01b86cfcc4"), + "core/Scheduler/Schedule/Daily.php" => array("1220", "d289f15335f21201d604654a0c2ea67f"), + "core/Scheduler/Schedule/Hourly.php" => array("1283", "c032998f3c1eba2712466bfe81f06f74"), + "core/Scheduler/Schedule/Monthly.php" => array("4013", "c0377aac646fe4e1d0895073ff1d30c6"), + "core/Scheduler/Scheduler.php" => array("7023", "8bf77c2773f9e161355c1cbcaa8b620a"), + "core/Scheduler/Schedule/Schedule.php" => array("7548", "7daca08fa20f94788697670c65547639"), + "core/Scheduler/Schedule/Weekly.php" => array("2015", "ef8bf8b0fb5d7995b1004cac5b7f2b81"), + "core/Scheduler/TaskLoader.php" => array("790", "4c2ae49c4512f6d6e0e44f3fd6af4e67"), + "core/Scheduler/Task.php" => array("5624", "0296de70d32ac414e447a393b3c24c9f"), + "core/Scheduler/Timetable.php" => array("3518", "4350f80ceaf697d0ef3c0c3cb08e416f"), + "core/Segment.php" => array("11081", "7fc74f06701c5a9d3b62fe696a6a22eb"), + "core/Segment/SegmentExpression.php" => array("15314", "729347d96ce1b8655c850e0766b2b7bd"), + "core/Sequence.php" => array("3182", "f19d6cd7bde41febdc85db3a77b24b73"), + "core/Session.php" => array("5529", "279c40a43a729cedfa97fac01b15e787"), + "core/Session/SaveHandler/DbTable.php" => array("3411", "5ecddc4b18a768d3d676fda9b76dc97f"), + "core/Session/SessionNamespace.php" => array("706", "97c6aecfc5fef6ab7e8719806806f538"), + "core/Settings/Manager.php" => array("4723", "58a0b18b16d3e673b8a54be2a2d6b30c"), + "core/SettingsPiwik.php" => array("15551", "d47fc3b07e467e460071ae5d8601aa5c"), + "core/SettingsServer.php" => array("6751", "4e766c0eb2d25cfe612c96ffea2d47b9"), + "core/Settings/Setting.php" => array("9482", "486c9f13b94a5779ea07cdc4adf03928"), + "core/Settings/Storage/Factory.php" => array("569", "e455c550b211c666450188966825eba7"), + "core/Settings/StorageInterface.php" => array("1819", "e87f20aa72227ba837a3bace502f805f"), + "core/Settings/Storage.php" => array("3862", "22d8763e894638a53eae37c4e54ae1d0"), + "core/Settings/Storage/StaticStorage.php" => array("653", "04369019ba7b72e99e957070ab3febec"), + "core/Settings/SystemSetting.php" => array("2800", "68e4e4cf01b7c420c8382e5ee99152b0"), + "core/Settings/UserSetting.php" => array("4185", "31fe1183624232c9ed0094d83fe97788"), + "core/Singleton.php" => array("1491", "b8e1d3cee216a7b15027291d8fc2045c"), + "core/Site.php" => array("17275", "2ee1d8187207e92eda075f86a127b2fc"), + "core/TaskScheduler.php" => array("3635", "65a96edfb794c8e516912e845e3db2fe"), + "core/TCPDF.php" => array("1913", "f57e46513a5e5d330ceb9b16e573e975"), + "core/testMinimumPhpVersion.php" => array("9467", "431105b69f4d9b3c37c8632b13ffa8fe"), + "core/Theme.php" => array("4551", "a7d4831a5fb3146850e1926912f43148"), + "core/Timer.php" => array("1911", "2859f53d6bb63e55ff351df2a321bae5"), + "core/Tracker/ActionPageview.php" => array("2515", "e8d2c459f51e484583ef4b65db40663b"), + "core/Tracker/Action.php" => array("12588", "8f4db3db70a4db9a08964759fd8e870c"), + "core/Tracker/Cache.php" => array("6406", "4bafd6e9e123f3eb5138fff18dac19f5"), + "core/Tracker/Db/DbException.php" => array("275", "bc34a06d73b8485a34dba83a0995047e"), + "core/Tracker/Db/Mysqli.php" => array("9484", "b431e382369d8117144e2478ccf42f48"), + "core/Tracker/Db/Pdo/Mysql.php" => array("8626", "aa0a526866232979826f199289ba6f7a"), + "core/Tracker/Db/Pdo/Pgsql.php" => array("3208", "d33fa635427c3ef2b8b62b10af735c8c"), + "core/Tracker/Db.php" => array("8682", "26fba1411555e123f4f1b1c954f0d6d2"), + "core/Tracker/GoalManager.php" => array("32798", "e32aa7e80004392ee76a0ac95968605a"), + "core/Tracker/Handler/Factory.php" => array("1340", "0bed98e2f1edebadedf6f11e8c83aaac"), + "core/Tracker/Handler.php" => array("2767", "98cf921a6b3f3cb7d17816a10473785b"), + "core/Tracker/IgnoreCookie.php" => array("1766", "1b1d10bc1e94d6bbbc0ad04879d64174"), + "core/Tracker/Model.php" => array("14979", "01264120211ed47d1b0d77a9401d01dd"), + "core/Tracker/PageUrl.php" => array("12410", "48e37aafe11da255d1c3b65f39b5f5c3"), + "core/Tracker.php" => array("10524", "37f63bf6a8edb927646cbadf0937bf91"), + "core/Tracker/Request.php" => array("25498", "38d49a827293e920faa60db2af1aef24"), + "core/Tracker/RequestProcessor.php" => array("7384", "bc43cdc2d74865c3e366f9fd974fad7d"), + "core/Tracker/RequestSet.php" => array("6189", "ef3406d0add4c63cf84489441164d42f"), + "core/Tracker/Response.php" => array("5676", "f1b1720bab602f60bad4f95b9511fbf1"), + "core/Tracker/ScheduledTasksRunner.php" => array("2925", "9b51330fce9644c85e6e71b2d8b602b1"), + "core/Tracker/Settings.php" => array("4326", "d89ea776e9c610345592ac0a49414ffd"), + "core/Tracker/SettingsStorage.php" => array("1212", "1eb1f2939f515a0ec38e8bd733be7078"), + "core/Tracker/TableLogAction/Cache.php" => array("4075", "2a08dee9d8d8843c0103bf47298b3004"), + "core/Tracker/TableLogAction.php" => array("10251", "695dd0965e9514fae2b5e71d171e3ae6"), + "core/Tracker/TrackerCodeGenerator.php" => array("8432", "83707b2ebb9ece6a4b306e0d094e6dde"), + "core/Tracker/TrackerConfig.php" => array("791", "60712345d7cc78af362d3f114ced2bfa"), + "core/Tracker/VisitExcluded.php" => array("10355", "e69da7c1a7a35955cdcb299e26293832"), + "core/Tracker/Visit/Factory.php" => array("1441", "d26bc067d88ecf846cc6c159d3a14cb7"), + "core/Tracker/VisitInterface.php" => array("592", "b171ec552c3f469bccd33ab6362737ea"), + "core/Tracker/VisitorNotFoundInDb.php" => array("240", "3f1ce6ea862903ea111db7bc74068ba2"), + "core/Tracker/Visitor.php" => array("1465", "0fdadbff85b57b51a81624ad801a825c"), + "core/Tracker/VisitorRecognizer.php" => array("9807", "d42f48ecd9cac2095c2b03469765fa69"), + "core/Tracker/Visit.php" => array("20816", "9ddc3a0178637bec447b9257fc7d5d02"), + "core/Tracker/Visit/ReferrerSpamFilter.php" => array("2239", "c68ffcdf46aef2567b3408fa6fd14206"), + "core/Tracker/Visit/VisitProperties.php" => array("1584", "ff6899b2d0678289cb183b5c6b9ad16a"), + "core/Translate.php" => array("3125", "c4190ed894e9afb6952365264bebbebc"), + "core/Translation/Loader/DevelopmentLoader.php" => array("1885", "9eac2043ffac54cfa6d7cc03f740b0b6"), + "core/Translation/Loader/JsonFileLoader.php" => array("1430", "3fce4290ef7b3c1c06e70e17e91766bc"), + "core/Translation/Loader/LoaderCache.php" => array("1444", "3b1fed6f5683d016ee045b06b8f5551c"), + "core/Translation/Loader/LoaderInterface.php" => array("532", "0dd2b5f00463fa4d3dc9e8dff3cffc93"), + "core/Translation/Transifex/API.php" => array("4383", "3500e306909cbe7d7035869ca29c5c01"), + "core/Translation/Translator.php" => array("7633", "e64106500cd9dbb11f4ee9725020e2f4"), + "core/Twig.php" => array("17006", "9514b91e817a9ada67084961e8ef2eb3"), + "core/Unzip.php" => array("1299", "978c4f95e999916f9961c5006cae1f54"), + "core/UpdateCheck.php" => array("3501", "2c03ef6b19921b7347642e7d26c1065e"), + "core/UpdateCheck/ReleaseChannel.php" => array("2521", "30d7ecfe5b45862cad2bd73ec937925a"), + "core/Updater.php" => array("24501", "46c9be4ce7893b0924c030212a625ad9"), + "core/Updater/UpdateObserver.php" => array("4101", "f6bbc3083ffbe3a77f5e5fd387df8042"), + "core/Updates/0.2.10.php" => array("2477", "3a305a654a6f387ceb3e62a71249643c"), + "core/Updates/0.2.12.php" => array("1077", "3fc4ee2d96cedc042b16b05325f5688f"), + "core/Updates/0.2.13.php" => array("868", "7cc89eb32960b96182bb62b894295349"), + "core/Updates/0.2.24.php" => array("1069", "6e01fa473bfc708a6f7d6f82c5d3f8b3"), + "core/Updates/0.2.27.php" => array("2771", "c18926a783b1e60f701b897526dda8d0"), + "core/Updates/0.2.32.php" => array("1175", "3b16a01a4ea5fde2055bbd202f0f46be"), + "core/Updates/0.2.33.php" => array("1268", "aaa1c83b60503edc36190dca810fa895"), + "core/Updates/0.2.35.php" => array("670", "2bbaaaba65dea301219bc89bf9d5fe49"), + "core/Updates/0.2.37.php" => array("725", "c3e1353a09e4b88d0c9c4a191e929a7d"), + "core/Updates/0.4.1.php" => array("889", "262f1f2e698eba2b038687013b2de2be"), + "core/Updates/0.4.2.php" => array("1169", "f4f98e5c8c5fb169263e725935593692"), + "core/Updates/0.4.4.php" => array("701", "29d6e74c9a2899d657dffd85f855dd8e"), + "core/Updates/0.4.php" => array("1221", "60542535c4374ba4f0853b07d27282d8"), + "core/Updates/0.5.4.php" => array("2041", "ea6f0b3156fb67e17ceef9315bc51a09"), + "core/Updates/0.5.5.php" => array("1379", "f30d016e2d17fe730832cc5f275fa6b0"), + "core/Updates/0.5.php" => array("2161", "d4156e3a54951246f10c60ffc84ee61b"), + "core/Updates/0.6.3.php" => array("1364", "21c6aeb624762cd1e822964645950b8f"), + "core/Updates/0.6-rc1.php" => array("4626", "a4118c4c24113c62e428d07ba5ea8c28"), + "core/Updates/0.7.php" => array("677", "7b00ed002817e4bc98a86c2380fbbfab"), + "core/Updates/0.9.1.php" => array("1534", "51b98c724f70887bc32745522330d6f5"), + "core/Updates/1.10.1.php" => array("563", "9dc4c3b8fa7f81452578f87d5a1eed45"), + "core/Updates/1.10.2-b1.php" => array("743", "d00be0f12a51d95f5a4b662708f06e5c"), + "core/Updates/1.10.2-b2.php" => array("757", "efe0ab2d44c51fce419f965c3c1a10d5"), + "core/Updates/1.10-b4.php" => array("572", "a6025f37edf0d8abd2d743f494562bad"), + "core/Updates/1.11-b1.php" => array("571", "0973849010e0b884602008365d099947"), + "core/Updates/1.12-b15.php" => array("493", "f06549e234a7db7e9fb552a3a457d8f6"), + "core/Updates/1.12-b16.php" => array("733", "2d9b656afde21317adf03956072ca6bb"), + "core/Updates/1.12-b1.php" => array("757", "28335b9d3acece7f584d7d89c305dfb4"), + "core/Updates/1.1.php" => array("1012", "81dafb5e782e20fe496217e4b0565a89"), + "core/Updates/1.2.3.php" => array("1132", "985a4b05f43a651e3bc94d1fe0147ae9"), + "core/Updates/1.2.5-rc1.php" => array("894", "f86d83f7d4ec917329a1db2503fe618d"), + "core/Updates/1.2.5-rc7.php" => array("684", "fd27b3e076f452e86b0f561d03c1f793"), + "core/Updates/1.2-rc1.php" => array("7092", "85c37f26ecbfff9e17ecb925725a4650"), + "core/Updates/1.2-rc2.php" => array("474", "47b9069ce3ee5a6f303e4686cdeb9f54"), + "core/Updates/1.4-rc1.php" => array("829", "2342e59e3717d805e0c63ddd32adfb92"), + "core/Updates/1.4-rc2.php" => array("1662", "67d3b8144c608882e1d0e0ce1ccb5b6f"), + "core/Updates/1.5-b1.php" => array("2430", "c3852518456f2e944887a2b509c22eaa"), + "core/Updates/1.5-b2.php" => array("1170", "261ec2c25bdb8ccceece65b21ad838ca"), + "core/Updates/1.5-b3.php" => array("2888", "10fdbaeb429a338d9df454b7e05def7e"), + "core/Updates/1.5-b4.php" => array("653", "80a7424a6fecdfd3bd0c9ff6001adf6f"), + "core/Updates/1.5-b5.php" => array("782", "866864d5ce6e68fd9e7005a8542a1f77"), + "core/Updates/1.5-rc6.php" => array("473", "1cd674d67f706537ef9cefe3af1f2f6d"), + "core/Updates/1.6-b1.php" => array("3156", "0b65aef2b19f4005afd0fe1dd45a33ca"), + "core/Updates/1.6-rc1.php" => array("469", "0b41483c93eb2ca5d664d24adff91412"), + "core/Updates/1.7.2-rc5.php" => array("758", "ceda0cff1323e822a7ec3d776dc56ef7"), + "core/Updates/1.7.2-rc7.php" => array("1392", "57026c93418ffec3e3f2d519d7164633"), + "core/Updates/1.7-b1.php" => array("883", "1ecc9fe3bf66b68bd3f62e664a423dff"), + "core/Updates/1.8.3-b1.php" => array("4279", "449699e4cbdb20695790dd4c4f26f686"), + "core/Updates/1.8.4-b1.php" => array("5691", "e5ed273cf444a4f9854b97ae9bb14778"), + "core/Updates/1.9.1-b2.php" => array("839", "5fca436482aacea98f4880e72c84f58f"), + "core/Updates/1.9.3-b10.php" => array("570", "e5fd5fe6c86d46f0928a2bdd95193bf1"), + "core/Updates/1.9.3-b3.php" => array("728", "87370c33291bf38823f9969cce874544"), + "core/Updates/1.9.3-b8.php" => array("813", "ed75af638541a1a8ae066e9370c0c358"), + "core/Updates/1.9-b16.php" => array("1581", "0fa923796f1328ea2613e641b5b22797"), + "core/Updates/1.9-b19.php" => array("1057", "49be2a481119b28fbe48e06a998ea14d"), + "core/Updates/1.9-b9.php" => array("1677", "6e72fb7dbb17aa86ce0bc3d3f69f711d"), + "core/Updates/2.0.3-b7.php" => array("1843", "b9ddbc0d38ad0459c7dadbf36a34d5bf"), + "core/Updates/2.0.4-b5.php" => array("2736", "b573443eea5ff7538b6fb923e5d196e6"), + "core/Updates/2.0.4-b7.php" => array("1758", "6cec5e6645177efcd6b67732905e97e1"), + "core/Updates/2.0.4-b8.php" => array("2179", "150d877dd6c1da69e91cff16f217c9a1"), + "core/Updates/2.0-a12.php" => array("1290", "a955080340d2d8a00a7d869950e779af"), + "core/Updates/2.0-a13.php" => array("2415", "c26723a290ecd3134298b186bc8bdfb8"), + "core/Updates/2.0-a17.php" => array("1036", "c462e1a6bd3e18a3bb2eb662e4565ee0"), + "core/Updates/2.0-a7.php" => array("955", "1f3938b8666f253b4c4ca117455779ef"), + "core/Updates/2.0-b10.php" => array("445", "5e17edc9cc8d1c6eb20a390d1c7b34cd"), + "core/Updates/2.0-b13.php" => array("1021", "e9de9e9533321a8fbfb0978ea1343b72"), + "core/Updates/2.0-b3.php" => array("1212", "c7ae0aba92a6986b9ee54108ea2097e9"), + "core/Updates/2.0-b9.php" => array("736", "a2ca1e88c9cf57bab28c182fafdc5a70"), + "core/Updates/2.0-rc1.php" => array("466", "057dcb8965b5d1175e80fae9602ca91d"), + "core/Updates/2.10.0-b10.php" => array("1215", "fff11dca4ecd3c1d78311518652bf98f"), + "core/Updates/2.10.0-b4.php" => array("549", "7385e456b6646ac81985582153e6cd33"), + "core/Updates/2.10.0-b5.php" => array("10242", "54fc7622b884789d0e9c9840424ae22f"), + "core/Updates/2.10.0-b7.php" => array("1167", "414dc989b0f8424e813b4415f9b65300"), + "core/Updates/2.10.0-b8.php" => array("505", "72ce572b0b9d6163b45bca1dd587ce18"), + "core/Updates/2.11.0-b2.php" => array("2185", "596d232300284495c87442f2e752f81c"), + "core/Updates/2.11.0-b4.php" => array("1216", "dcd526c41c7551ee75a660a0240f6774"), + "core/Updates/2.11.0-b5.php" => array("469", "c68872040f9cd3fb8f720e06c8c0813b"), + "core/Updates/2.11.1-b4.php" => array("897", "f0131c7824022c39876cad6b4944c0e6"), + "core/Updates/2.1.1-b11.php" => array("5727", "ba49bdbacbe8902de01d05e798e7d31c"), + "core/Updates/2.13.0-b3.php" => array("506", "851f79ff947535ed4e41f6e5f56b283a"), + "core/Updates/2.13.1.php" => array("1138", "63d681aea66e936bb2b4eeeb8eea149e"), + "core/Updates/2.14.0-b1.php" => array("1036", "cf2e76416fb319011e4d4f3ec3ca79e6"), + "core/Updates/2.14.0-b2.php" => array("1120", "053dfa2b35a8a8500d6af640d11a0d63"), + "core/Updates/2.14.2.php" => array("4205", "fd64bd2ebeeb7fd7c051bdca1b382cd9"), + "core/Updates/2.15.0-b12.php" => array("1127", "9b85c6fd9582b421b02a1ac776733c9f"), + "core/Updates/2.15.0-b16.php" => array("1077", "775cb8dd98ed67001479c29a7e209520"), + "core/Updates/2.15.0-b17.php" => array("1059", "d952d26e2a3f1552b5321e2d7e4ef27c"), + "core/Updates/2.15.0-b20.php" => array("1013", "882b41ac5b5de2ab15f9232a52da3df7"), + "core/Updates/2.15.0-b3.php" => array("739", "e6db95ffae19dacb73d75b48221afd10"), + "core/Updates/2.15.0-b4.php" => array("551", "c7675e9e5ee17f43ef6761448fad804c"), + "core/Updates/2.15.0.php" => array("497", "cd36a0901f4c47742992e9eea69b172d"), + "core/Updates/2.16.0-rc2.php" => array("620", "baec837efa66ab7ef5a6078fb71fdc8a"), + "core/Updates/2.2.0-b15.php" => array("644", "fac6fbd6084542970778c1e9fabf6c2b"), + "core/Updates/2.2.3-b6.php" => array("466", "36ca6560082c5b599d82366c08388aae"), + "core/Updates/2.3.0-rc2.php" => array("492", "2858fd746e1940b5f4f2a778419708d6"), + "core/Updates/2.4.0-b1.php" => array("658", "0c7e7bc601300a8207ce17bc3d624a1f"), + "core/Updates/2.4.0-b2.php" => array("599", "1195001194309c1b1f29ce3aabc22533"), + "core/Updates/2.4.0-b3.php" => array("736", "5e2db22ad86414c8f57af710bedb2bc3"), + "core/Updates/2.4.0-b4.php" => array("767", "3cc1e08e2bae9e4ce01715ed2e2fc169"), + "core/Updates/2.4.0-b6.php" => array("509", "101711f41bb4543ed776314d301b1c53"), + "core/Updates/2.4.0-b8.php" => array("667", "e3b27e5255b57bf4b2d66f5b41226e76"), + "core/Updates/2.5.0-b1.php" => array("891", "c4d92f5cde5a74ceb338c729c11de28e"), + "core/Updates/2.5.0-rc2.php" => array("2027", "758eab9dd20862594ee9ee85786e163a"), + "core/Updates/2.5.0-rc4.php" => array("489", "6f23e25ac90ff1fc173d4d4e7db1d8b5"), + "core/Updates/2.6.0-b1.php" => array("687", "f5a9023a8753803876a6e87a490cbeeb"), + "core/Updates/2.7.0-b2.php" => array("510", "8d712b7f4f29a8ce1b0f036a7d26ac5f"), + "core/Updates/2.7.0-b4.php" => array("587", "0209139d7bde416cf5b31b9fba6b6f2f"), + "core/Updates/2.9.0-b1.php" => array("2890", "1d4fdf521b28b4c087729cf5eab93a3c"), + "core/Updates/2.9.0-b7.php" => array("2487", "205527c848fa8df742cd1ed48e59bc59"), + "core/Updates.php" => array("4875", "0968bbf770aea2cee040155397bb19e7"), + "core/UrlHelper.php" => array("10108", "09e30ea3a4e5a288fd58e7aa112746ea"), + "core/Url.php" => array("22690", "7b2f6e9c8569c71ccde556a00edd212b"), + "core/Version.php" => array("770", "a12dc206410108f611ecb05f1d31802c"), + "core/ViewDataTable/Config.php" => array("24035", "672de85be723731ad2e4805eaca35be8"), + "core/ViewDataTable/Factory.php" => array("9731", "bba600c7fa55cc8a28b9e9dd1bd0327e"), + "core/ViewDataTable/Manager.php" => array("13927", "f66c1256f3ca149516695788cb3bd1fb"), + "core/ViewDataTable/RequestConfig.php" => array("9711", "5399c3a73f461202fc90a9ff33762d46"), + "core/ViewDataTable/Request.php" => array("4255", "0da3ccc7845bb59fb65364f47048a571"), + "core/View/OneClickDone.php" => array("2344", "5ada6f6f8cdddbaba7abbc859dda7688"), + "core/View.php" => array("14062", "b100985df09dcc63fcad6de350ebfb33"), + "core/View/RenderTokenParser.php" => array("2029", "b5501cae199ae4434b9b304cdf2bcb04"), + "core/View/ReportsByDimension.php" => array("4198", "8bb5dd7ad80b63057c6586169e851eaa"), + "core/View/UIControl.php" => array("4545", "74127c5ca7208bd83810226c785ab825"), + "core/View/ViewInterface.php" => array("414", "27f1fe14f80dcdd4f525a1c383e90472"), + "core/Visualization/Sparkline.php" => array("4644", "547d13219346c9ccbfbb27ad74754613"), + "core/WidgetsList.php" => array("9079", "b0e3b6414e230b8a06d6928e8251a136"), + "index.php" => array("730", "a401ca678920558c7b9a3a9530d765b6"), + "js/index.php" => array("236", "43ac89baca7c8fbfa63c476653222cff"), + "js/LICENSE.txt" => array("1526", "e8eb3ba0e95713ae6468edbae20c1aa9"), + "js/piwik.js" => array("286459", "25de8861a9fd063cc515ac99cac8e5fb"), + "js/README.md" => array("2348", "4d29433c3712818b797a4ff2793d0c63"), + "js/tracker.php" => array("1324", "2f2614547b3d7c806c9f853dc7355c2b"), + "lang/am.json" => array("3645", "063c791f2acfa861cbccc593db6897ed"), + "lang/ar.json" => array("18975", "1d33ca158bee52a3cd2f823479a39208"), + "lang/be.json" => array("25876", "caf0de80b777b6cef0922091471a0dc2"), + "lang/bg.json" => array("43386", "23567ff51a7807a13d157d561d40e9ba"), + "lang/bn.json" => array("2937", "c6de3aef15ba262e1d73908e24407461"), + "lang/bs.json" => array("19000", "4ae4d2c9a62e1a503f37b10681672fe7"), + "lang/ca.json" => array("26384", "3f02eca1e9e49993b3e76788d45d6220"), + "lang/cs.json" => array("35074", "609d4eea43f227fc353d5119e0528253"), + "lang/cy.json" => array("16511", "06373470eee9f849886394e01abfdfab"), + "lang/da.json" => array("31775", "a1e2d857099e30076d49bcdf381b098e"), + "lang/de.json" => array("36861", "a5fb479ae2a40ab4cc630e9f366ce4bf"), + "lang/dev.json" => array("78", "dc26f1ebd9eefbd146e1506c2ba0affa"), + "lang/el.json" => array("54689", "fec64da3a4cfd062d1edd4dbc8bb7b3a"), + "lang/en.json" => array("33208", "0f873bd2698e69052eb95b51255daed9"), + "lang/es.json" => array("35362", "2b4a5aff766002ebfb486a8b0e0eb30a"), + "lang/et.json" => array("16106", "78bbe57f853117bbf126a9f50a1c534c"), + "lang/eu.json" => array("6935", "c0040785ea654a0138f747a167d2846b"), + "lang/fa.json" => array("36353", "4f332125a8fe19c0deca956550a64685"), + "lang/fi.json" => array("31769", "d7d76299cdf4f1f0c359a45c33595b81"), + "lang/fr.json" => array("37224", "eabfd0792dce3f7400e1208f00018e09"), + "lang/gl.json" => array("5938", "41fb2172ccdbcda419ee33651304c1a1"), + "lang/he.json" => array("16691", "566ef7dc0a8749e158f74f1d2acec209"), + "lang/hi.json" => array("51348", "4a233e823a835097cc65cabe4b947aea"), + "lang/hr.json" => array("20505", "e6b1844136eca09f5c3cee3279df0f70"), + "lang/hu.json" => array("21822", "d2d0488b7bcf67004523605f50a53500"), + "lang/id.json" => array("30629", "48e873feff9395d24ee75649de9c0444"), + "lang/is.json" => array("8843", "0ecf1e3aac19c9adf010dfc30a215fa7"), + "lang/it.json" => array("35589", "67a2fb97cc6046792b4d069056edb8f9"), + "lang/ja.json" => array("40566", "7ef5cd2517a42393b27a811faa14e174"), + "lang/ka.json" => array("21654", "0532a87e1710f73429d0916b89272a99"), + "lang/ko.json" => array("31547", "cf5058c706d2b74250726ed2b2692d57"), + "lang/lt.json" => array("16145", "aa54dd2466aa978ba78d11251878a427"), + "lang/lv.json" => array("16100", "c0f8391417393fafa24433c84b7b2d71"), + "lang/nb.json" => array("34210", "658f42c7d2f928833651b5d22165d352"), + "lang/nl.json" => array("34457", "e6f1444ab0d1feb0862063da08b7d16c"), + "lang/nn.json" => array("16602", "78a2d48580ec85488c5f315a39456edd"), + "lang/pl.json" => array("28339", "76c4b409468f034607efc7e418af3338"), + "lang/pt-br.json" => array("36066", "7e61e00a3bfc7bafd827d67fb0d012ea"), + "lang/pt.json" => array("20578", "e7868b16d89e8e0f9f7c95ee37540555"), + "lang/README.md" => array("456", "ec21de9a7f2c2afd61edac08a28a3907"), + "lang/ro.json" => array("30511", "8b370e1e1a6fd3c099e81a3ba93d8d6d"), + "lang/ru.json" => array("49181", "58c2857da8c1a4fa2e3b84ebd0a2051b"), + "lang/sk.json" => array("35495", "fd91537bbeac9382c71cefad7c6261be"), + "lang/sl.json" => array("32010", "e0b507eb93208f616785d7b5f2e33dac"), + "lang/sq.json" => array("36133", "83ef99905444c61fdec64dd5767a250d"), + "lang/sr.json" => array("33553", "3e1e1a0a7385c98d29b2973bcb52815c"), + "lang/sv.json" => array("34635", "e022d9c203ef4e97575d93b35614bfb4"), + "lang/ta.json" => array("16576", "97e1b5e6ca86235e6db944157c0ffc2f"), + "lang/te.json" => array("6518", "d147a8a90089ab890341b82b2afcbf4c"), + "lang/th.json" => array("34896", "a2421bef979fc96ca5af30c493f8f6b5"), + "lang/tl.json" => array("28375", "35a321ffc37880cdc58be6c4c988f162"), + "lang/tr.json" => array("18066", "2975614f7a4e2a54b29247a4e07ffb32"), + "lang/uk.json" => array("15983", "f9fed854365adf35fd88e17957aeb30d"), + "lang/vi.json" => array("34464", "bf499a6e773b736a47daa03c1572b06a"), + "lang/zh-cn.json" => array("25792", "c4a0849f2734a3e8a4e05fbda2f466cf"), + "lang/zh-tw.json" => array("12671", "378662dfef7d6ea15a79760a3703c4dc"), + "LEGALNOTICE" => array("7486", "c23b7e200217d393650f6149356e0b07"), + "libs/bower_components/angular/angular-csp.css" => array("535", "68c905585cc349c10329b000eacc02ba"), + "libs/bower_components/angular/angular.js" => array("789267", "4e41f4d21f26771e9454f51f5d36e8ed"), + "libs/bower_components/angular/angular.min.js" => array("108028", "c5d22c0a6f50fd66ac9ee980a2b7ac61"), + "libs/bower_components/angular/angular.min.js.gzip" => array("40123", "f15fa37289b1c56fae579c6f09446776"), + "libs/bower_components/angular/angular.min.js.map" => array("290859", "c4277d7fb13c26304b6b0f73679bcacf"), + "libs/bower_components/angular-animate/angular-animate.js" => array("77784", "d27a63f0f64cb90682403cb8b982e51e"), + "libs/bower_components/angular-animate/angular-animate.min.js" => array("11295", "869301b9f7f1828f27b18acc262d166a"), + "libs/bower_components/angular-animate/angular-animate.min.js.map" => array("32715", "26f1bd8d743cea56fa3f4f78b1a01d54"), + "libs/bower_components/angular-animate/bower.json" => array("154", "467d2cba00f80d92f104ea621ae7a952"), + "libs/bower_components/angular-animate/package.json" => array("613", "a51d16e36926e8e99c4b3caca32084b8"), + "libs/bower_components/angular-animate/README.md" => array("2270", "a2c8288dde71cde3c94e2e4e3576b622"), + "libs/bower_components/angular/bower.json" => array("114", "a9e0387f4fdfe0ddd7c59a81c7db3f18"), + "libs/bower_components/angular-cookies/angular-cookies.js" => array("5824", "80acc564cbad7d3a0d1586b588ea004a"), + "libs/bower_components/angular-cookies/angular-cookies.min.js" => array("825", "13a33148e7124f6c200e05ac83c505ac"), + "libs/bower_components/angular-cookies/angular-cookies.min.js.map" => array("2253", "4cd4cef713599958917fec5cf0c983f7"), + "libs/bower_components/angular-cookies/bower.json" => array("154", "a0cb0981bc1b084d8416b6f2b1908419"), + "libs/bower_components/angular-cookies/package.json" => array("608", "92763f49ceefac127fa420d2674c46f6"), + "libs/bower_components/angular-cookies/README.md" => array("2265", "b142b6f8c430bdd4abd3349d04c9cc2a"), + "libs/bower_components/angular-mocks/angular-mocks.js" => array("68823", "a2b7d8ffbfdddc8b526298faaaf589de"), + "libs/bower_components/angular-mocks/bower.json" => array("150", "d29ede7cea19ba5df25cdc4985f69883"), + "libs/bower_components/angular-mocks/package.json" => array("616", "847d5d874fa6ad8ab52c99a02ce7cbcb"), + "libs/bower_components/angular-mocks/README.md" => array("1946", "44e1feae097f7459e6a769e9a20a3124"), + "libs/bower_components/angular/package.json" => array("575", "b4ed7afa49335c7d4f2556151501f36a"), + "libs/bower_components/angular/README.md" => array("2144", "a4bc333832e474821a91ee06c22bced8"), + "libs/bower_components/angular-sanitize/angular-sanitize.js" => array("21815", "9979030c3ed2a947f3fdef2287a253ba"), + "libs/bower_components/angular-sanitize/angular-sanitize.min.js" => array("4566", "e352a441354986f151172a16b0a80a5a"), + "libs/bower_components/angular-sanitize/angular-sanitize.min.js.map" => array("10670", "0b3eceabfc5f7ee474d2f91b4c0b48f1"), + "libs/bower_components/angular-sanitize/bower.json" => array("156", "64bc4f9113d812c96b357a9f390cd85e"), + "libs/bower_components/angular-sanitize/package.json" => array("615", "992bf854fb7cd008ecd4de313ce3c234"), + "libs/bower_components/angular-sanitize/README.md" => array("2279", "b6dca0580c4087849e406a7eebe6a428"), + "libs/bower_components/chroma-js/bower.json" => array("536", "dc8295dbf13eaafe6421b98bbb097c12"), + "libs/bower_components/chroma-js/chroma.js" => array("56192", "0c55632f0d43674b4041c0d053abe79a"), + "libs/bower_components/chroma-js/chroma.min.js" => array("32010", "79d1574b5c9585f7c8e2d0595cf1750f"), + "libs/bower_components/chroma-js/LICENSE" => array("1497", "3cd2b215a2f79da827342dbbf3a1733f"), + "libs/bower_components/chroma-js/LICENSE-colors" => array("806", "a483ff87f333ccabc3ce541ae8c169bc"), + "libs/bower_components/chroma-js/Makefile" => array("623", "9db21bf228f259284a68e923e13992cf"), + "libs/bower_components/chroma-js/package.json" => array("833", "03558c034a09100407e7362e09e167fe"), + "libs/bower_components/chroma-js/readme.md" => array("2038", "90a323713ba39a95abd553084fbb4311"), + "libs/bower_components/html5shiv/bower.json" => array("193", "271d2b708f1e2fdf3943fd4af13d957f"), + "libs/bower_components/html5shiv/dist/html5shiv.js" => array("10189", "ee68da404bd6cbdab3adb3bf9219c207"), + "libs/bower_components/html5shiv/dist/html5shiv.min.js" => array("2636", "3044234175ac91f49b03ff999c592b85"), + "libs/bower_components/html5shiv/dist/html5shiv-printshiv.js" => array("16143", "d0d9a764f9d376be88401200ad930100"), + "libs/bower_components/html5shiv/dist/html5shiv-printshiv.min.js" => array("4272", "8c3c50c95caa7cef54d2c8720de4db37"), + "libs/bower_components/html5shiv/Gruntfile.js" => array("1177", "a6418754499f873e31aff73c8d2dc8e3"), + "libs/bower_components/html5shiv/package.json" => array("340", "681176cfbf9782e1da3a8ce7f4460df8"), + "libs/bower_components/html5shiv/readme.md" => array("8579", "c6be7162b534841b39e49fcee33e257d"), + "libs/bower_components/jquery/bower.json" => array("425", "216eec46a750884fc581801b695b7ac1"), + "libs/bower_components/jquery/dist/jquery.js" => array("282766", "3d93b072d14f2bd1ede58f4847f537fd"), + "libs/bower_components/jquery/dist/jquery.min.js" => array("95821", "d4a20d75db01a33e2d65e303ce5c34f3"), + "libs/bower_components/jquery/dist/jquery.min.map" => array("141666", "d5771a2316a6a8fa000ad94aaae84d60"), + "libs/bower_components/jQuery.dotdotdot/bower.json" => array("582", "17c14fbdc64e32ced55f1f53e7cace83"), + "libs/bower_components/jQuery.dotdotdot/src/js/jquery.dotdotdot.js" => array("12463", "ac3e09c806432a6db99bc8b089c0374c"), + "libs/bower_components/jQuery.dotdotdot/src/js/jquery.dotdotdot.min.js" => array("6117", "a2fe486543d8a20e2bd5056b1126c3c8"), + "libs/bower_components/jquery/MIT-LICENSE.txt" => array("1099", "14f9e86644baad8332932dc6fe2af261"), + "libs/bower_components/jquery-mousewheel/bower.json" => array("267", "ef2a804f62fcf20dcf1694f80609f699"), + "libs/bower_components/jquery-mousewheel/ChangeLog.md" => array("3328", "2c62f63033999287a6d2db448515eeab"), + "libs/bower_components/jquery-mousewheel/jquery.mousewheel.js" => array("8279", "426ff44fdde60c9e548a11806e5e9681"), + "libs/bower_components/jquery-mousewheel/jquery.mousewheel.min.js" => array("2777", "639d1c35a685d111aa4a509a2dbf660c"), + "libs/bower_components/jquery-mousewheel/LICENSE.txt" => array("1084", "3029474551341261688c4e3433afadd7"), + "libs/bower_components/jquery-mousewheel/README.md" => array("2906", "708cee278dd1a949b7152115452c50c9"), + "libs/bower_components/jquery-placeholder/bower.json" => array("91", "537585672ca2d90f3317ef26607427cc"), + "libs/bower_components/jquery-placeholder/demo.html" => array("3232", "8145dd812172b8310b1ab0c509eacee4"), + "libs/bower_components/jquery-placeholder/jquery.placeholder.js" => array("5297", "d7098f9b5df7c2fdf5119c7428a19441"), + "libs/bower_components/jquery-placeholder/LICENSE-MIT.txt" => array("1075", "0146cb31436f780624be47ce08eec616"), + "libs/bower_components/jquery-placeholder/README.md" => array("3918", "21040d5d644d4661824c5c8aa8fec6af"), + "libs/bower_components/jquery.scrollTo/bower.json" => array("557", "3cb24b035d81c24ba8b6cb024f0cd4b7"), + "libs/bower_components/jquery.scrollTo/jquery.scrollTo.js" => array("5606", "206af780ad37f3fdc994527beeabaa3a"), + "libs/bower_components/jquery.scrollTo/jquery.scrollTo.min.js" => array("2712", "a2079fc42e9afdfcceb62088df47d8a0"), + "libs/bower_components/jquery.scrollTo/LICENSE" => array("1101", "f5f6a434ca35e83565e47923006fb721"), + "libs/bower_components/jquery.scrollTo/package.json" => array("564", "bf772f566cd1e23c4e25e4e596b49256"), + "libs/bower_components/jquery.scrollTo/README.md" => array("3185", "953dc68cc4aa0f61ef869efb9f3796ac"), + "libs/bower_components/jquery.scrollTo/scrollTo.jquery.json" => array("972", "5d9d521674f53ed5b53a1b5aea220e9f"), + "libs/bower_components/jquery/src/ajax.js" => array("21812", "adcd45ad7a0a90cf7416c6b3c40c9272"), + "libs/bower_components/jquery/src/ajax/jsonp.js" => array("2516", "c09be897ccc55b18fd553df2e63b0ace"), + "libs/bower_components/jquery/src/ajax/load.js" => array("1681", "370b63e7003b80224c02116059d4adf9"), + "libs/bower_components/jquery/src/ajax/parseJSON.js" => array("1476", "6a6188a3844858d60c3e2c62db3a1bc6"), + "libs/bower_components/jquery/src/ajax/parseXML.js" => array("650", "7d6ed14794f48610ae5418e3194b85f7"), + "libs/bower_components/jquery/src/ajax/script.js" => array("1964", "0e450053763ce6335cc2d4b853c88496"), + "libs/bower_components/jquery/src/ajax/var/nonce.js" => array("73", "c0fee61b182d6c03a455585f74b5e1bd"), + "libs/bower_components/jquery/src/ajax/var/rquery.js" => array("40", "86cc4813fe7ee092d0f25de3403c1811"), + "libs/bower_components/jquery/src/ajax/xhr.js" => array("5786", "6640ee85cecf8fddfb2cf74998f8acb1"), + "libs/bower_components/jquery/src/attributes/attr.js" => array("7263", "7e197ba681b707843e59db99ca8e8ebb"), + "libs/bower_components/jquery/src/attributes/classes.js" => array("4116", "30f07e5fa34ea0661516ccb75476fb7a"), + "libs/bower_components/jquery/src/attributes.js" => array("200", "29b8b42452bec1b7049873dd2c0ba91d"), + "libs/bower_components/jquery/src/attributes/prop.js" => array("3138", "1d7d0d70f78dfc5158c22b5d40043720"), + "libs/bower_components/jquery/src/attributes/support.js" => array("1994", "5075ca7ab8b92f86a75193ab59ede719"), + "libs/bower_components/jquery/src/attributes/val.js" => array("4303", "d89462be24c01fc8c724e63f99ac668e"), + "libs/bower_components/jquery/src/callbacks.js" => array("5506", "de1c5e8d7bc16f7d104b37b13a99681f"), + "libs/bower_components/jquery/src/core/access.js" => array("1219", "5634def0482ba0d43ed3b43bfe8eba7b"), + "libs/bower_components/jquery/src/core/init.js" => array("3716", "d958cd58bcb263413dacdcb8091ccb5f"), + "libs/bower_components/jquery/src/core.js" => array("12500", "444edfd62c1d0cb77af387d1e3aadddf"), + "libs/bower_components/jquery/src/core/parseHTML.js" => array("938", "431b6e91175f25ab58dedad61f1bf849"), + "libs/bower_components/jquery/src/core/ready.js" => array("3896", "a108ade83202e0e549385c12669d5f18"), + "libs/bower_components/jquery/src/core/var/rsingleTag.js" => array("91", "0f07e690c168f77a90f4e0886c76e74b"), + "libs/bower_components/jquery/src/css/addGetHookIf.js" => array("785", "072c24e016cdc8d3f50acd34e66f24e0"), + "libs/bower_components/jquery/src/css/curCSS.js" => array("3307", "8eb595b2d35a5ae0d66154060759d875"), + "libs/bower_components/jquery/src/css/defaultDisplay.js" => array("1907", "a903e2da9c918dacabc40908ee01db2a"), + "libs/bower_components/jquery/src/css/hiddenVisibleSelectors.js" => array("542", "b605ad8f6c7ee57370a5095a940a090c"), + "libs/bower_components/jquery/src/css.js" => array("14612", "3284292cf73000b5107953986b30ddfb"), + "libs/bower_components/jquery/src/css/support.js" => array("4823", "26b9246b5b1b0f94bb9318542a077734"), + "libs/bower_components/jquery/src/css/swap.js" => array("555", "aef668d22cdb39ba3e691caa32294123"), + "libs/bower_components/jquery/src/css/var/cssExpand.js" => array("70", "2d8ac6725d0ff4faaa415eb25708a9ed"), + "libs/bower_components/jquery/src/css/var/isHidden.js" => array("355", "13a6f243bea6c82a6311ff011d3693fa"), + "libs/bower_components/jquery/src/css/var/rmargin.js" => array("45", "028581c37c9dda64dc81ce7295ea5f34"), + "libs/bower_components/jquery/src/css/var/rnumnonpx.js" => array("113", "2a34d3f4def5ae34a4cfc90766f92085"), + "libs/bower_components/jquery/src/data/accepts.js" => array("567", "0e7dfc3a20102946046bf6a6de96e7fb"), + "libs/bower_components/jquery/src/data.js" => array("8509", "515aaba78a521c12763e4ea1829ec013"), + "libs/bower_components/jquery/src/data/support.js" => array("438", "4949c78014e8b038652a8d8096e5fb80"), + "libs/bower_components/jquery/src/deferred.js" => array("4413", "36d5f0378ff200400844238ee0a6ee28"), + "libs/bower_components/jquery/src/deprecated.js" => array("223", "55ef50aae575deea2e12776b6e95ea19"), + "libs/bower_components/jquery/src/dimensions.js" => array("1881", "5966f07a9d63cd9b4058887c5f9024aa"), + "libs/bower_components/jquery/src/effects/animatedSelector.js" => array("225", "293275acc2e12d1a64ed0f9214f85388"), + "libs/bower_components/jquery/src/effects.js" => array("17243", "e899c616eab61a7ee38e9c01bb20c9d0"), + "libs/bower_components/jquery/src/effects/support.js" => array("1459", "54a9fa0f41a0a0c06a4a22e9922566e6"), + "libs/bower_components/jquery/src/effects/Tween.js" => array("3034", "fc737cbaa07e2c89aa92b452a37c34ff"), + "libs/bower_components/jquery/src/event/alias.js" => array("1094", "ba4e3a937d720dccbc0eb99a9e8f72ef"), + "libs/bower_components/jquery/src/event.js" => array("30072", "8d112219ff954b82406a0906b8e1266d"), + "libs/bower_components/jquery/src/event/support.js" => array("641", "57578123c95312b20806ee463a53fcf0"), + "libs/bower_components/jquery/src/exports/amd.js" => array("1006", "0e2411cca15d802f6a8da3aed34d9369"), + "libs/bower_components/jquery/src/exports/global.js" => array("641", "686c97ddbf5a04083351686d06ba6fde"), + "libs/bower_components/jquery/src/intro.js" => array("1405", "501013e055d7a4b09a2457e911bbfe1a"), + "libs/bower_components/jquery/src/jquery.js" => array("571", "97dda5c52a639f4dc0256db2ed7047eb"), + "libs/bower_components/jquery/src/manipulation/_evalUrl.js" => array("240", "d3bbdfa4e6d906378d987a733b87e420"), + "libs/bower_components/jquery/src/manipulation.js" => array("20616", "14e30c7a3cc163b3117bcc7e740e952e"), + "libs/bower_components/jquery/src/manipulation/support.js" => array("2476", "1be243801041a3bda86c0547f49dc622"), + "libs/bower_components/jquery/src/manipulation/var/rcheckableType.js" => array("59", "19f6af061c62c4a89d82c0972a992c61"), + "libs/bower_components/jquery/src/offset.js" => array("5856", "4a5d618f2c76f6f75a8735a920c67c86"), + "libs/bower_components/jquery/src/outro.js" => array("5", "0b9c0e7d4b72a5f95b3ce20f4508a84d"), + "libs/bower_components/jquery/src/queue/delay.js" => array("561", "6e52fac4cd26e9e74694d8c3f9e85294"), + "libs/bower_components/jquery/src/queue.js" => array("3071", "762403c095fa4b03f6f1b57915f211ed"), + "libs/bower_components/jquery/src/selector.js" => array("33", "cdff25b189c9501fbe0b0c540d19074c"), + "libs/bower_components/jquery/src/selector-sizzle.js" => array("294", "08ef80e78fb184932eae6a1d541d2de4"), + "libs/bower_components/jquery/src/serialize.js" => array("3211", "7b20f1d4fa67f16749efb0f81215b02f"), + "libs/bower_components/jquery/src/sizzle/dist/sizzle.js" => array("58579", "8c27c9f5aa4024663effbc88d441e6cf"), + "libs/bower_components/jquery/src/sizzle/dist/sizzle.min.js" => array("18574", "1f6c920ee2c6d21942d507dd24b4c7e5"), + "libs/bower_components/jquery/src/sizzle/dist/sizzle.min.map" => array("28986", "176774d8e1902a2b32f3b98cdbf716e6"), + "libs/bower_components/jquery/src/support.js" => array("1697", "d9283e88af6c6e2813267f2e5d03afbb"), + "libs/bower_components/jquery/src/traversing/findFilter.js" => array("2466", "6af2a4ec1d78ab4d03f58048ebc34f07"), + "libs/bower_components/jquery/src/traversing.js" => array("4575", "813d071c13be81cecc2339d046dce7d0"), + "libs/bower_components/jquery/src/traversing/var/rneedsContext.js" => array("110", "b5676c00977e2e54de53af1228e39020"), + "libs/bower_components/jquery/src/var/class2type.js" => array("64", "8beeb098fb5eca5a728bd7bf2de1ceed"), + "libs/bower_components/jquery/src/var/concat.js" => array("84", "246dde37b4e3635d98eea3e6b32b7493"), + "libs/bower_components/jquery/src/var/deletedIds.js" => array("36", "73fe8cc31324cb3022fce1b1be3c9e92"), + "libs/bower_components/jquery/src/var/hasOwn.js" => array("92", "068dbad1531e9fac5d842e57914a122a"), + "libs/bower_components/jquery/src/var/indexOf.js" => array("85", "62250190fef0835c88effcc7c575f7ad"), + "libs/bower_components/jquery/src/var/pnum.js" => array("80", "04912d16c7442b5c4c7c946235174395"), + "libs/bower_components/jquery/src/var/push.js" => array("82", "6d7ea35e1edf414310d2a3d529edb854"), + "libs/bower_components/jquery/src/var/rnotwhite.js" => array("42", "b1e91d1278805eff8bf9e85c34a41c26"), + "libs/bower_components/jquery/src/var/slice.js" => array("83", "6c0b9cd2ebca15a8ecf9dbe6b44e27ed"), + "libs/bower_components/jquery/src/var/strundefined.js" => array("50", "9e452cb4a55337a4fe5c8351f1d4c54b"), + "libs/bower_components/jquery/src/var/support.js" => array("99", "135b80391f6ec64fa10d1d702a8bee17"), + "libs/bower_components/jquery/src/var/toString.js" => array("86", "6fc5af5803e4128c9225f8606d9f3c35"), + "libs/bower_components/jquery/src/wrap.js" => array("1456", "399c541599cddb95d2e7155277053da8"), + "libs/bower_components/jquery-ui/AUTHORS.txt" => array("9844", "245c8b0f9f894ccb76d70826de495688"), + "libs/bower_components/jquery-ui/bower.json" => array("135", "ed3d29afab772544be4fe7fa5ef47a45"), + "libs/bower_components/jquery-ui/component.json" => array("227", "262877636ab79d8c250382953d701a07"), + "libs/bower_components/jquery-ui/composer.json" => array("1898", "a0088cd9b6910a217b999babe42447bd"), + "libs/bower_components/jquery-ui/MIT-LICENSE.txt" => array("1336", "54ab1578b1fe12a7dcae71b4c14ee4a8"), + "libs/bower_components/jquery-ui/package.json" => array("1575", "69680490bdf978290e2331239ecdb334"), + "libs/bower_components/jquery-ui/README.md" => array("758", "bb910c57516d6e86bcf3d176ac9c1f7d"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-af.js" => array("896", "3f6dc7167ebfdab2e4c06ca1f7ecbf55"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-ar-DZ.js" => array("1207", "f9c86467366d98200c97ac9c8b843fbb"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-ar.js" => array("1297", "9924612cef93d8722863287157768180"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-az.js" => array("921", "f9f7fb74b273da0307a8bd4ec7acb6b5"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-be.js" => array("1146", "7b1c87006e19cac0f3590845efd008fb"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-bg.js" => array("1124", "d965b3639b678aaf5819db10189d3472"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-bs.js" => array("844", "eeccd3d7df38ab2c37ae290e46b3cd93"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-ca.js" => array("874", "a857021fa5601842d94a41f20a5cab9d"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-cs.js" => array("923", "54791b35c9819515ce0cad20b7537277"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-cy-GB.js" => array("904", "fb40b70ba78ef9f4251a86355c5f65f7"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-da.js" => array("890", "656d173c027d5a08186e39c156ba5597"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-de.js" => array("881", "bf12bcbfcb995b003e6cb3c257904be6"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-el.js" => array("1199", "c3150764daf20b6fa2142581180be1ad"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-en-AU.js" => array("897", "4a38655904f6c55da227cea464b55a2b"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-en-GB.js" => array("874", "24a226a281a11799c495abc21f696c23"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-en-NZ.js" => array("899", "af985e8d034123f14696aa116027760d"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-eo.js" => array("893", "e1f5d8ed4599ca392aeb284fe637df33"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-es.js" => array("888", "f576dede2a5e0e2dc999057b1b2bd3b0"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-et.js" => array("951", "1a7c15ddc89179a0e309d9e7d2b97ad4"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-eu.js" => array("913", "24751dd4dcabb58b82ee0817fea84fd3"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-fa.js" => array("1164", "9687cad817acecd88a808d7ef8c58fcf"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-fi.js" => array("945", "60c06554626335687497e570d49f2e5e"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-fo.js" => array("918", "85444da9fc4c900eba95f8ff4704688f"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-fr-CA.js" => array("913", "da166bae7a0b6dfd7d9e2f6bab4576dc"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-fr-CH.js" => array("957", "b23437da5946bba5bf560ec26a0c6d83"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-fr.js" => array("1042", "1957028ad184b40a7cca3448dbb647b9"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-gl.js" => array("890", "0f4dee4528f5f8fb8eb20a14496b7e37"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-he.js" => array("1016", "b2ad344bf1df226aa1a760f1d3653da7"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-hi.js" => array("1313", "1e2602a3c232f31242c47a9cadccf9dd"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-hr.js" => array("869", "7582ea79c7fd35b2b7758ff103b11b4b"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-hu.js" => array("948", "dee235f99823541ec88be57dec431230"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-hy.js" => array("1174", "2f3828a4c02a475b1b8966609721b9c3"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-id.js" => array("882", "fb0ad98a3ad212b1986fcac5015b0435"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-is.js" => array("946", "0cc09cc96c4d279ebaba585507cc6abd"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-it.js" => array("912", "7e651d93d0219066bf596faa06db4a81"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-ja.js" => array("902", "bf1cf98e79f2d6792c7c7a193b4c7497"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-ka.js" => array("1414", "fd0b08bdc63b1d969fa2df907083062a"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-kk.js" => array("1117", "57a792b4c55dc23b2095cc190180c440"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-km.js" => array("1330", "f5c6ed9f64ff97adfd29cb149176021f"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-ko.js" => array("911", "5fb849693b65beed7146624ba498b517"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-ky.js" => array("1109", "a5db310345d66c395b592fd2f6136bf0"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-lb.js" => array("934", "642aa75625a4ab2c324fb5df74063509"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-lt.js" => array("961", "da32786e14f5d1bd858951d9d8fd2796"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-lv.js" => array("942", "ea864f875aa32ef7b5ada2bf3d44876b"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-mk.js" => array("1057", "cdfdd4b3a2e181c9ed297fa55c739d5e"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-ml.js" => array("1436", "17fe3b0548bf5a2c9f4e0b081efaeb04"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-ms.js" => array("891", "51efc50e21ae012a17f4f3cd0f2ac93d"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-nb.js" => array("896", "693af0abc258aaf903c4d4b23a882676"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-nl-BE.js" => array("918", "60b63d90f6eb6ea3334ec75d6a0831ec"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-nl.js" => array("922", "8c765466b1bb2709f8c9db056029ec89"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-nn.js" => array("893", "690553270244b0de96ade29a9e04b02a"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-no.js" => array("888", "5f531f078d367d5f10c287479533b0c8"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-pl.js" => array("917", "2d7dd09c586d4275b402d627778123ca"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-pt-BR.js" => array("945", "2d3c1dc7191cf5081b4f982c8cf78c98"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-pt.js" => array("868", "3b4e1fe50589bc5fadc80a5380ec37c4"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-rm.js" => array("905", "0601228208954434efea2ccf265f5b94"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-ro.js" => array("989", "3e888ad522a6581f99b47ad987292c20"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-ru.js" => array("1117", "813acc83f4f77a0d874426207da0208a"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-sk.js" => array("905", "377b3c5fa2285a8fa665206957c95ceb"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-sl.js" => array("943", "7b87e98ac2241fffb8f3e5bb6415ec07"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-sq.js" => array("887", "47ea965b616f6afeab8d860d75787847"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-sr.js" => array("1037", "7083f39fcb737210e0a13c6196f3feb4"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-sr-SR.js" => array("848", "3d23308dfb3943acdf90bdd46b25f9e2"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-sv.js" => array("890", "88fbc9581e8abeac0fe083d572428c45"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-ta.js" => array("1496", "da7607dd5df15b0bcd4da344c33447a3"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-th.js" => array("1274", "0f1be4ae65e24fc7d6a37dce828a9cee"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-tj.js" => array("1074", "f868a410d5438feee15a20e24e4caf5b"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-tr.js" => array("883", "6d11aae285bdd88294e66353feb284da"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-uk.js" => array("1183", "e0b56bc48d64fa8ffef2b8c39f1db725"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-vi.js" => array("1094", "7d54cb0edfbc31232d4ac12f94cec562"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-zh-CN.js" => array("983", "46cc885a69ff490c660e99173dc05ea3"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-zh-HK.js" => array("981", "ab64f179cc7f62ba45d7708e1dee8cae"), + "libs/bower_components/jquery-ui/ui/i18n/jquery.ui.datepicker-zh-TW.js" => array("977", "411a70a31fe6420be6e5990ea5122e18"), + "libs/bower_components/jquery-ui/ui/i18n/jquery-ui-i18n.js" => array("70069", "5e42ed24fa979312d8711f52eb3a49a0"), + "libs/bower_components/jquery-ui/ui/jquery.ui.accordion.js" => array("14937", "cec7b2afde9634119a7b30c711480c64"), + "libs/bower_components/jquery-ui/ui/jquery.ui.autocomplete.js" => array("15896", "b9924a6e3b305145f156fd027c25d902"), + "libs/bower_components/jquery-ui/ui/jquery.ui.button.js" => array("10972", "e50be67b4e07e1cd61df0806b74566bc"), + "libs/bower_components/jquery-ui/ui/jquery.ui.core.js" => array("8198", "495fc824644e9f56c716f4f19f797394"), + "libs/bower_components/jquery-ui/ui/jquery-ui.custom.js" => array("436715", "2f89b9b0bdd026d1c6c3845fd358183f"), + "libs/bower_components/jquery-ui/ui/jquery.ui.datepicker.js" => array("76324", "fc1180f7327a8534805e8035020da251"), + "libs/bower_components/jquery-ui/ui/jquery.ui.dialog.js" => array("20476", "310f7977156e1caeae4d6c60a1aa9794"), + "libs/bower_components/jquery-ui/ui/jquery.ui.draggable.js" => array("31352", "bea5eccf905d1a97cb97c13d590f1053"), + "libs/bower_components/jquery-ui/ui/jquery.ui.droppable.js" => array("11056", "dbf93a484952fb7d4ef5722b9ca6598d"), + "libs/bower_components/jquery-ui/ui/jquery.ui.effect-blind.js" => array("1928", "a514127e65fdf55c30cb669174b2c788"), + "libs/bower_components/jquery-ui/ui/jquery.ui.effect-bounce.js" => array("2789", "33bbcc23d7d93ccc6b6c1ae57a41a831"), + "libs/bower_components/jquery-ui/ui/jquery.ui.effect-clip.js" => array("1468", "79b4c36bc5dedad582f1db1d39d86e87"), + "libs/bower_components/jquery-ui/ui/jquery.ui.effect-drop.js" => array("1473", "5836525420c6b441c5ccb74d80c1877b"), + "libs/bower_components/jquery-ui/ui/jquery.ui.effect-explode.js" => array("2402", "cc0752b84f66ea9ec6fdd80479d4a98b"), + "libs/bower_components/jquery-ui/ui/jquery.ui.effect-fade.js" => array("558", "db69be03386c29822fbe39f3f0152cc2"), + "libs/bower_components/jquery-ui/ui/jquery.ui.effect-fold.js" => array("1698", "7f51467d5c2d39df62090acf5bf49802"), + "libs/bower_components/jquery-ui/ui/jquery.ui.effect-highlight.js" => array("1002", "384ba1f7dac1e5fb14d83b5c548084d2"), + "libs/bower_components/jquery-ui/ui/jquery.ui.effect.js" => array("31918", "09855fe0bc8af60aa9db954e6124ddc6"), + "libs/bower_components/jquery-ui/ui/jquery.ui.effect-pulsate.js" => array("1374", "b4c0f9e9a0bd8360c744b0e221af99d7"), + "libs/bower_components/jquery-ui/ui/jquery.ui.effect-scale.js" => array("8274", "bbb2b31cf99419c96ea93e6137d8657c"), + "libs/bower_components/jquery-ui/ui/jquery.ui.effect-shake.js" => array("1948", "b70a671758326cdbc1439e9f22645278"), + "libs/bower_components/jquery-ui/ui/jquery.ui.effect-slide.js" => array("1472", "261491a676a7b1f25042e4e421ac0aa1"), + "libs/bower_components/jquery-ui/ui/jquery.ui.effect-transfer.js" => array("1237", "206b754fca95f138508708f7498ed0d7"), + "libs/bower_components/jquery-ui/ui/jquery-ui.js" => array("436715", "27c35e4ff8c3538f6d74aad67c494113"), + "libs/bower_components/jquery-ui/ui/jquery.ui.menu.js" => array("16834", "7ed11ba837b32589f672e2b8b464b042"), + "libs/bower_components/jquery-ui/ui/jquery.ui.mouse.js" => array("4561", "1088fc344eb2a5f2e8ed23d396873332"), + "libs/bower_components/jquery-ui/ui/jquery.ui.position.js" => array("15845", "781f11b2ff2f551a965346c64dd2c752"), + "libs/bower_components/jquery-ui/ui/jquery.ui.progressbar.js" => array("3368", "8ab2415c18a1a87855ebf42c0bf05428"), + "libs/bower_components/jquery-ui/ui/jquery.ui.resizable.js" => array("27692", "5c5cbeb41c39f661fa7cc7e279cdfc33"), + "libs/bower_components/jquery-ui/ui/jquery.ui.selectable.js" => array("6967", "3f00c36bd6c3a57f2af9e132fe6cad09"), + "libs/bower_components/jquery-ui/ui/jquery.ui.slider.js" => array("18177", "90eadaecb851adf9f493d81208b48042"), + "libs/bower_components/jquery-ui/ui/jquery.ui.sortable.js" => array("42714", "288c75389c0f45ecd974b7e05b6cfcde"), + "libs/bower_components/jquery-ui/ui/jquery.ui.spinner.js" => array("12529", "1926c0d5fa6d453cbddd2adace7a0afd"), + "libs/bower_components/jquery-ui/ui/jquery.ui.tabs.js" => array("22081", "8253514521d5ccd4195e29efcdb006a4"), + "libs/bower_components/jquery-ui/ui/jquery.ui.tooltip.js" => array("10822", "719ee6c85bc2a220fb129f570807dc8a"), + "libs/bower_components/jquery-ui/ui/jquery.ui.widget.js" => array("15085", "d1d42e7b1fa13faeeb3f1d9487107135"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-af.min.js" => array("818", "95dd1e3ff70e29ac714e88a7fee23f2d"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-ar-DZ.min.js" => array("1066", "7f388a3fe5d539ccb8e743f017046dcb"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-ar.min.js" => array("1016", "0abf55fb78e61291703d21513e182ad1"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-az.min.js" => array("835", "b209479bc518183890a63b8f4461c939"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-be.min.js" => array("1067", "7d875d926506d1c48327c267d435de3d"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-bg.min.js" => array("1048", "dc9168f71eeddc07c473cebf9debfef9"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-bs.min.js" => array("805", "5310e3fb7f2795c71bf3704797e3ab87"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-ca.min.js" => array("804", "7bfaf9c32510cb57e4857f4011cf3853"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-cs.min.js" => array("838", "caaa7f653067c29313d662a2f4c34954"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-cy-GB.min.js" => array("832", "fe39c5be2d1b2795a57c28b4ca2573da"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-da.min.js" => array("813", "985e2a264f3df994d46f1ac2c3dde06a"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-de.min.js" => array("814", "3a1d2248e816af1535ad907934886402"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-el.min.js" => array("1118", "ddd39f9f9c3bfe65ba92d0667b30bddb"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-en-AU.min.js" => array("811", "7c1bcd90c08cc22bacc93bbc72d5e06e"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-en-GB.min.js" => array("811", "95c8dd4c74fc551730deca102d3b7c5d"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-en-NZ.min.js" => array("811", "36fa506b7bb291b212afdfc2db1f6cb2"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-eo.min.js" => array("818", "60f7812b48343991fc2419dcd43ba3e2"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-es.min.js" => array("805", "81680e3e6cc802b3b2357702c533ecf6"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-et.min.js" => array("850", "3b810dc582cfd63e26bce8155f4f2c34"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-eu.min.js" => array("839", "729ab24b14fca434b2cfd5d9a5b4779c"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-fa.min.js" => array("873", "cfc9031c8b1f60bcffb37a1273d1a59d"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-fi.min.js" => array("870", "9474a1fd76430f607a1edc3c7af81429"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-fo.min.js" => array("845", "d7334e577a2ee2bc20361921096919f6"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-fr-CA.min.js" => array("847", "bb39bf8d07f8017af829d9fb52e85173"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-fr-CH.min.js" => array("844", "10c54f9640b4e909d23bae7cb89d09dd"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-fr.min.js" => array("835", "c40aa3d1ff446ee489bff0326ec1371d"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-gl.min.js" => array("811", "fd82165aa3a825c9273e14f9f68e98b9"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-he.min.js" => array("935", "ca91c5ce4f69dc19dc996777cd1bf978"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-hi.min.js" => array("1235", "0319ff15589798907b1c288dcaf09071"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-hr.min.js" => array("825", "38ad296e324f4b1677846deead50ff1e"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-hu.min.js" => array("834", "b048ce59a1cb6bca2c1f78c441682b77"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-hy.min.js" => array("1089", "68613abd5cc8ad7f9769a59ba3fce29c"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-id.min.js" => array("805", "b13647a4ad6222080aca4cf6cc71a43a"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-is.min.js" => array("869", "cd878178ca371d75ad0135d788067a92"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-it.min.js" => array("825", "e71d3804ddb5874aae49584dea4c008c"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-ja.min.js" => array("831", "3d42e25ebe6eb800845a2104134324ca"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-ka.min.js" => array("1334", "b82289725d5f81c57592bfe01f993c5f"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-kk.min.js" => array("1027", "5831fe16fe18cb8e0afb65173e1f1a48"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-km.min.js" => array("1239", "1c736cd489e4661f925439bf3e387283"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-ko.min.js" => array("827", "e9e327ea651b28c8d96a22eddff8c268"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-ky.min.js" => array("1013", "b0d670eed1ee7d22021c5e5305b398fc"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-lb.min.js" => array("824", "a821f60a3e99d934b2c32b368f7f7d0d"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-lt.min.js" => array("879", "2aea79541c11a3d4e6a3887af1525d85"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-lv.min.js" => array("848", "286e505793926fa29b9f128db252f052"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-mk.min.js" => array("1009", "762b19cf6aba4d89c4a52b29f77803c2"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-ml.min.js" => array("1330", "dccc7bf4963ba028c2ab77bee461bee7"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-ms.min.js" => array("803", "2ca7efa65446b5b94ba15edf083be79f"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-nb.min.js" => array("813", "ac119f93089e005957570bc0a061f8ae"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-nl-BE.min.js" => array("818", "ab1393b432d36410b44042ae96ac2e8c"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-nl.min.js" => array("806", "51bbdcba389c2bba9901d856dc3d2f4f"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-nn.min.js" => array("810", "4fc51fff289245c001aa527b3eb10ca9"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-no.min.js" => array("813", "af1547c827feeb2dbe17949e3ad83f29"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-pl.min.js" => array("841", "2ac0374b67e30860d9cb8291119e4b77"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-pt-BR.min.js" => array("869", "e4505ef936512fdc3d15dd2ef7dd5031"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-pt.min.js" => array("846", "98be5be90fcd05600f7496e6b1f43c86"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-rm.min.js" => array("833", "54479a6b44a450ebb767bfd590a9d8ac"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-ro.min.js" => array("851", "9a94b4552a43189d325492c5235493a0"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-ru.min.js" => array("1035", "8c88c42627db5a3c11476e5efbe16d18"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-sk.min.js" => array("840", "d82f4ab3ab4568aa24f001baed6af597"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-sl.min.js" => array("826", "827adcd038af02f7c97cf5faca6c10c3"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-sq.min.js" => array("814", "f6629a414e8c4611924dd104a83c9e9b"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-sr.min.js" => array("997", "521b0e16d8e878da601f7e48975c8abd"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-sr-SR.min.js" => array("814", "13f0ad99e0b646e5e698e83450b0de4f"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-sv.min.js" => array("818", "1c36624e7d3ffd465c719342aa0df1c9"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-ta.min.js" => array("1417", "20e2ca602a5f72f48ce222686ea2dc3b"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-th.min.js" => array("1216", "7d95bf362917429854c0f3fd813f51de"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-tj.min.js" => array("991", "95d40ad3c35f157524536bb238d6f57f"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-tr.min.js" => array("806", "0b9646228e7eabee7854012a8ebbaafe"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-uk.min.js" => array("1040", "4b33acff7e9ef7de642984987a1ba9ec"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-vi.min.js" => array("973", "e0b50d9e810097c4cdffa6edae9a255d"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-zh-CN.min.js" => array("921", "751c0c3690cca6e18e0bf1d273ac66a0"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-zh-HK.min.js" => array("921", "75d33aa6b761bcd9b8613530b12abb79"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery.ui.datepicker-zh-TW.min.js" => array("921", "37c6107e860ca8d2d85992e4204b0790"), + "libs/bower_components/jquery-ui/ui/minified/i18n/jquery-ui-i18n.min.js" => array("58950", "4619143190adccef201ab6dc7bcf28ec"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.accordion.min.js" => array("8367", "8bde422d37ea79520da13b9e8e3c9dea"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.autocomplete.min.js" => array("7787", "01cc67ccc5744bbb131707e131906bc8"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.button.min.js" => array("6874", "5d05441442f31a37834bf7aeb3389f48"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.core.min.js" => array("4290", "940f7fb38a963e3ecef6a011ab78ad11"), + "libs/bower_components/jquery-ui/ui/minified/jquery-ui.custom.min.js" => array("228539", "0e3eecf578b68a7ba6b8008b03a6f9be"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.datepicker.min.js" => array("35807", "9c2c6c9d53ed3df9d074d16a14632e3d"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.dialog.min.js" => array("11263", "17de9be551fa5a26b1853fb7895c36b0"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.draggable.min.js" => array("18560", "8670376fb0ef0356ad4d28dd4540a05a"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.droppable.min.js" => array("5985", "0d6ea5daaacde6a8fceedd4c7fa3d99b"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-blind.min.js" => array("956", "a380f75e908a8d1f5b49431821e65fae"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-bounce.min.js" => array("1058", "9b7767b75ace68d88ff55b0ea1c5247a"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-clip.min.js" => array("733", "7b32f3f4013f500ebd32d552e2ba148d"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-drop.min.js" => array("812", "1b475a12106b3fac915d6cfde4b864e1"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-explode.min.js" => array("985", "0c3ab3a5bc731c78077386aa512270ae"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-fade.min.js" => array("330", "5698d995935764f500450ab94c9e58b4"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-fold.min.js" => array("845", "a9fb1583821e1db2e7bda45e9b0aceff"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-highlight.min.js" => array("594", "b90ce04b7d12b17acca1963ec743bf49"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect.min.js" => array("12973", "b45e8e071bebe3a23b8841ed2b394673"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-pulsate.min.js" => array("609", "4b491d7c9d9a578c94f99837d7c1950a"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-scale.min.js" => array("4356", "abf1870462ffc6ad3a7bf64178caefd5"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-shake.min.js" => array("914", "9ad5301b69c5792620e04df6f77982ec"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-slide.min.js" => array("775", "d7f15e22c25ef798f02d02755ed8701e"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.effect-transfer.min.js" => array("664", "3a0a3c44fce6272241ddd6df463968f0"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.menu.min.js" => array("9584", "393ffb17e43535f7d8387b16907ef693"), + "libs/bower_components/jquery-ui/ui/minified/jquery-ui.min.js" => array("228539", "fb4770e78488812ef9f99b7c7484688d"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.mouse.min.js" => array("2842", "8609e8e9044082f7f9c8826d11a3ee1f"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.position.min.js" => array("6361", "b9c5a4f9ba4b40d8cfb67a7f1766029a"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.progressbar.min.js" => array("2179", "4955d934978b2770fd6d8dd42c8d8efa"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.resizable.min.js" => array("17410", "e92766a5a1af65fc51fc30e71ec2c6bd"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.selectable.min.js" => array("4066", "dc2491c9c1f6d7c679a104f6b4c048ec"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.slider.min.js" => array("10245", "ff4d6dc74d90401632f0e7c5968efa0d"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.sortable.min.js" => array("24111", "8f18c8fbafb73d566a399b978f9169fa"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.spinner.min.js" => array("6890", "271c0eebb1c42fb201e681327a26f5e3"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.tabs.min.js" => array("11624", "4ac127fe79fe4068c60649e2089c766e"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.tooltip.min.js" => array("4782", "0b7c86ef7b1c66719c3cecac0001b48f"), + "libs/bower_components/jquery-ui/ui/minified/jquery.ui.widget.min.js" => array("6522", "63ddc580856fe7821bf537f6e214de07"), + "libs/bower_components/jScrollPane/ajax_content.html" => array("5592", "102fd3f0de3dd102cc44c131a28cd3a9"), + "libs/bower_components/jScrollPane/ajax.html" => array("7570", "63e97faba95b27c09d35951fbe983068"), + "libs/bower_components/jScrollPane/anchors.html" => array("7784", "f48e17a50dda08d773f50087da2644c0"), + "libs/bower_components/jScrollPane/api.html" => array("9105", "742a5b74d67594a6194062b48d39c0db"), + "libs/bower_components/jScrollPane/arrow_hover.html" => array("12410", "cb56370ed3b94f10c280ff5af62cd4c6"), + "libs/bower_components/jScrollPane/arrow_positions.html" => array("43068", "69d58be759b22da688b11989b3963544"), + "libs/bower_components/jScrollPane/arrows.html" => array("12321", "a1310c80e04db681de3fe164cd95743d"), + "libs/bower_components/jScrollPane/auto_reinitialise.html" => array("3566", "84515480d57ad33e8264c5e3e9958320"), + "libs/bower_components/jScrollPane/basic.html" => array("12344", "cde118345bbe03363d02103498b1a227"), + "libs/bower_components/jScrollPane/caps.html" => array("14214", "02f55ac7582d162e1f04d67af136f8c1"), + "libs/bower_components/jScrollPane/changelog.html" => array("4401", "34d5c1d74457686e913fd880ecd6b025"), + "libs/bower_components/jScrollPane/destroy.html" => array("12912", "5545f0b0899e9a2674c92efd180379b4"), + "libs/bower_components/jScrollPane/drag_size.html" => array("12663", "0f230c14887df09460f9721f682ab808"), + "libs/bower_components/jScrollPane/dynamic_content.html" => array("3365", "6ac6060980497c9dfe71fdccc73da2ab"), + "libs/bower_components/jScrollPane/dynamic_height.html" => array("10699", "fb5dbd84c24cfcbf126205bdf91697be"), + "libs/bower_components/jScrollPane/dynamic_width.html" => array("23060", "f61abbaebe7d9e40904cfcd0cb117868"), + "libs/bower_components/jScrollPane/events.html" => array("14242", "da2a2a5cfd4dde4723cd4c94f8775569"), + "libs/bower_components/jScrollPane/faqs.html" => array("2446", "ac72e3d8f138e336ee81e35dd39df7f7"), + "libs/bower_components/jScrollPane/fixed_width.html" => array("13020", "1ee207ce45b9224803d13daf15bf03ef"), + "libs/bower_components/jScrollPane/focus.html" => array("12254", "b12e6df7262a1eb553335368cd1f17c6"), + "libs/bower_components/jScrollPane/fullpage_scroll.html" => array("13791", "0aceebf5176d06e8bea327cd136b0565"), + "libs/bower_components/jScrollPane/GPL-LICENSE.txt" => array("15099", "2c1778696d3ba68569a0352e709ae6b7"), + "libs/bower_components/jScrollPane/iframe2.html" => array("4113", "82f5fb0621df570c6e67b076797608f9"), + "libs/bower_components/jScrollPane/iframe_content1.html" => array("6702", "df5484eec27a4e2d59bb997cb68bf157"), + "libs/bower_components/jScrollPane/iframe_content2.html" => array("6735", "c285b02ebd24dec1b1616ece083f39fb"), + "libs/bower_components/jScrollPane/iframe_content3.html" => array("4626", "1802c5f3d7ab8a1dd00523c6164d948d"), + "libs/bower_components/jScrollPane/iframe_content4.html" => array("4647", "445cc6d09fcc217b8d0877002a3642ad"), + "libs/bower_components/jScrollPane/iframe.html" => array("3195", "e095b1982e2897da61740fdc59c020e2"), + "libs/bower_components/jScrollPane/image2.html" => array("3411", "1f2f8b074ba184256bd660ff5cb8cfc7"), + "libs/bower_components/jScrollPane/image.html" => array("3613", "b1683a7b0449e70a210c5e9b0bb0ea5a"), + "libs/bower_components/jScrollPane/image/logo.png" => array("1838", "3359cf416860883290483674e17eb5bf"), + "libs/bower_components/jScrollPane/index.html" => array("15038", "00441568b7b288b69a8a3fac9512dd13"), + "libs/bower_components/jScrollPane/invisibles.html" => array("33431", "5268959b3e104a509b0b66c2c589e500"), + "libs/bower_components/jScrollPane/issues/11/after.html" => array("2160", "817c9bc671c3ffc1946738c48eeacdff"), + "libs/bower_components/jScrollPane/issues/11/before.html" => array("2116", "a461a3342e08e7911b28ddfaaf950f80"), + "libs/bower_components/jScrollPane/issues/11/brs_main.css" => array("5093", "f063a1ceaa272c398ce79eabb90d7039"), + "libs/bower_components/jScrollPane/issues/11/index.html" => array("2061", "6903d939c464a27bbc6607753ed780e0"), + "libs/bower_components/jScrollPane/issues/11/jquery.mousewheel.js" => array("2200", "b9f3122f01f7b9d0b9ea8d6bb09edf31"), + "libs/bower_components/jScrollPane/issues/11/jscrollpane-2b3.css" => array("1864", "51c400fc8b2d2a23ff5a06f117266c46"), + "libs/bower_components/jScrollPane/issues/11/jscrollpane-2b3.js" => array("33568", "68a64c6549b6317008cba70a74380685"), + "libs/bower_components/jScrollPane/issues/11/native.html" => array("2107", "23fa665cee7ea18ae72fdf948e2f1ec1"), + "libs/bower_components/jScrollPane/issues/12/after.html" => array("14454", "eef1f42bc797ad609a638b26eaa5d6c5"), + "libs/bower_components/jScrollPane/issues/12/after_reinit.html" => array("14362", "a877214ba9cd2b84e09d13ce4101dd12"), + "libs/bower_components/jScrollPane/issues/12/before.html" => array("14292", "0470876a7fd4219f265b9a15940ce505"), + "libs/bower_components/jScrollPane/issues/12/before_reinit.html" => array("14318", "c37b7404817ad9a97ce7e6480423da0c"), + "libs/bower_components/jScrollPane/issues/12/brs_main.css" => array("5093", "f063a1ceaa272c398ce79eabb90d7039"), + "libs/bower_components/jScrollPane/issues/12/index.html" => array("2061", "58769a0ebff71ccf2c10d9dd42a2ea74"), + "libs/bower_components/jScrollPane/issues/12/jquery.mousewheel.js" => array("2200", "b9f3122f01f7b9d0b9ea8d6bb09edf31"), + "libs/bower_components/jScrollPane/issues/12/jscrollpane-2b3.css" => array("1864", "51c400fc8b2d2a23ff5a06f117266c46"), + "libs/bower_components/jScrollPane/issues/12/jscrollpane-2b3.js" => array("33568", "68a64c6549b6317008cba70a74380685"), + "libs/bower_components/jScrollPane/issues/12/native.html" => array("14283", "4529d89c5cfc578bb67343e4b0d764a8"), + "libs/bower_components/jScrollPane/issues/12/wrapped.html" => array("14430", "a8adf06da0a1b5db7dde64a0efb846ea"), + "libs/bower_components/jScrollPane/issues/7/after.html" => array("4910", "d544b806ed495bc104c320d3d2e188a5"), + "libs/bower_components/jScrollPane/issues/7/before.html" => array("4881", "8dbd05c085081ba46885e32d0ed67174"), + "libs/bower_components/jScrollPane/issues/7/index.html" => array("2003", "a054b31dd7e0f8be5615e41bb87e85a2"), + "libs/bower_components/jScrollPane/issues/7/jscrollpane-2b1.css" => array("1423", "65b3d741ebfbc939998a3bd8c905d8e0"), + "libs/bower_components/jScrollPane/issues/7/jscrollpane-2b2.js" => array("30109", "4a38c9839d7c67a4b94274700003bd17"), + "libs/bower_components/jScrollPane/issues/7/native.html" => array("4466", "ad344a030dcd000bf122d00d7aad1a40"), + "libs/bower_components/jScrollPane/known_issues.html" => array("3140", "b2cf2458244a914393e7daf3b829fb52"), + "libs/bower_components/jScrollPane/less_basic.html" => array("9468", "82c854318de452f35a9a56b59d7edc9f"), + "libs/bower_components/jScrollPane/MIT-LICENSE.txt" => array("1069", "adafb28f6f446ffd3d347599818c5b55"), + "libs/bower_components/jScrollPane/mwheel_intent.html" => array("12540", "82421440aaddae1bde635b8b187942d8"), + "libs/bower_components/jScrollPane/override_animate.html" => array("9130", "3d58564cca9c460fd39c13b2492b8c0a"), + "libs/bower_components/jScrollPane/README.md" => array("552", "66c2c0bd6e8f2a6bc919d2374921f261"), + "libs/bower_components/jScrollPane/runeimp2.html" => array("20735", "d0427278e02f7723a671aafe34215d7e"), + "libs/bower_components/jScrollPane/runeimp.html" => array("20550", "373a2545ee6fe87211a9f72871d835c4"), + "libs/bower_components/jScrollPane/script/demo.js" => array("1619", "ce61d8ee3ee746c708ab42e0f025243b"), + "libs/bower_components/jScrollPane/script/jquery.jscrollpane.js" => array("46340", "b44fef9c7881542e1f28e057d0de9d3a"), + "libs/bower_components/jScrollPane/script/jquery.jscrollpane.min.js" => array("15114", "390d645ad63cfabaa347286a1023a957"), + "libs/bower_components/jScrollPane/script/jquery.mousewheel.js" => array("3846", "f77bd9ca0396c7a8672f536884b1e1aa"), + "libs/bower_components/jScrollPane/script/mwheelIntent.js" => array("1748", "3d22ec7b158eb1a1518a11f4124f5ff4"), + "libs/bower_components/jScrollPane/scroll_on_left.html" => array("6894", "ca1ed4de9866a6b109e4fe4ef2606676"), + "libs/bower_components/jScrollPane/scroll_to_animate.html" => array("9044", "305f3e06ef2c56f4c144ae7bc13f2832"), + "libs/bower_components/jScrollPane/scroll_to.html" => array("8404", "bb5dc6a9ecd533d401507310e0a56424"), + "libs/bower_components/jScrollPane/settings.html" => array("10422", "2acc1e1b2923c8065ceeffaac05fbb6c"), + "libs/bower_components/jScrollPane/short.html" => array("3289", "9be69211f6b3448c54730d8ab6699dc9"), + "libs/bower_components/jScrollPane/style/demo.css" => array("2578", "322bdee8d36094459294f97af449d5cc"), + "libs/bower_components/jScrollPane/style/jquery.jscrollpane.css" => array("1423", "65b3d741ebfbc939998a3bd8c905d8e0"), + "libs/bower_components/jScrollPane/themes/lozenge/image/ui-icons_222222_256x240.png" => array("4369", "ebe6b6902a408fbf9cac6379a1477525"), + "libs/bower_components/jScrollPane/themes/lozenge/image/ui-icons_888888_256x240.png" => array("4369", "9c46d7cab43e22a14bad26d2d4806d80"), + "libs/bower_components/jScrollPane/themes/lozenge/image/ui-icons_cd0a0a_256x240.png" => array("4369", "3e450c2a2c66328d9498e7001ad7197c"), + "libs/bower_components/jScrollPane/themes/lozenge/index.html" => array("22285", "b295c70a2366356ab3836dff06b13d78"), + "libs/bower_components/jScrollPane/themes/lozenge/style/jquery.jscrollpane.lozenge.css" => array("1105", "70c1450ad9476106f7ef415aad683ea8"), + "libs/bower_components/jScrollPane/v1.html" => array("2122", "8c514af4e1f780b7ccd43cbd0bfd66da"), + "libs/bower_components/mousetrap/Gruntfile.js" => array("1061", "d00d437139b0c76968d13ba0abca0c81"), + "libs/bower_components/mousetrap/mousetrap.js" => array("29576", "0faa7fc59989c1db78acfa7d62c64de4"), + "libs/bower_components/mousetrap/mousetrap.min.js" => array("3850", "5543a5480413b59a5f50a8ec189c5214"), + "libs/bower_components/mousetrap/package.json" => array("654", "c674554370b801aa019fb9c13bcbbbae"), + "libs/bower_components/mousetrap/plugins/bind-dictionary/mousetrap-bind-dictionary.js" => array("956", "45d1531622eac12812a7e88f84519d06"), + "libs/bower_components/mousetrap/plugins/bind-dictionary/mousetrap-bind-dictionary.min.js" => array("222", "73ea6cfb494e9969674b55d6f59eee9e"), + "libs/bower_components/mousetrap/plugins/bind-dictionary/README.md" => array("431", "df9f4621884e550fcf9a86fdb6d071b2"), + "libs/bower_components/mousetrap/plugins/global-bind/mousetrap-global-bind.js" => array("987", "8602f9fa8f531430d3d9866b6d6063a2"), + "libs/bower_components/mousetrap/plugins/global-bind/mousetrap-global-bind.min.js" => array("239", "f0a67ff6d71bcafb29c0cc40749e595d"), + "libs/bower_components/mousetrap/plugins/global-bind/README.md" => array("552", "615d515d6c01717270a4d597363e3ff6"), + "libs/bower_components/mousetrap/plugins/pause/mousetrap-pause.js" => array("678", "7b693460cbe9dbb34db5c5eab1434129"), + "libs/bower_components/mousetrap/plugins/pause/mousetrap-pause.min.js" => array("175", "0494f81db4e6bea30cbebec43705a3af"), + "libs/bower_components/mousetrap/plugins/pause/README.md" => array("291", "40c5516e147bc30e16823ece328a13e0"), + "libs/bower_components/mousetrap/plugins/README.md" => array("563", "8501ed3c4f3fcac11d06a3ab942c8bc4"), + "libs/bower_components/mousetrap/plugins/record/mousetrap-record.js" => array("5146", "8d9ea4fe7b076ad376124bde92b33308"), + "libs/bower_components/mousetrap/plugins/record/mousetrap-record.min.js" => array("630", "16c15797fa7af825ab161da1c978b08c"), + "libs/bower_components/mousetrap/plugins/record/README.md" => array("391", "5415944b891db38e56dc31d4baaab211"), + "libs/bower_components/mousetrap/README.md" => array("3121", "9b6c10a7afb8e136f459aa095314112e"), + "libs/bower_components/ngDialog/bower.json" => array("691", "d81f9ae975b126960b0cb07c73faa8d7"), + "libs/bower_components/ngDialog/css/ngDialog.css" => array("1579", "db5cb6ed0da790a4dc59305729b41797"), + "libs/bower_components/ngDialog/css/ngDialog.min.css" => array("1320", "24cf82c389768be5a05a60ca5872f6eb"), + "libs/bower_components/ngDialog/css/ngDialog-theme-default.css" => array("4471", "09dfaa02adaa2d2083d7d15ab9fb4c03"), + "libs/bower_components/ngDialog/css/ngDialog-theme-default.min.css" => array("3852", "92346177a86da9200d8debf5dd52914b"), + "libs/bower_components/ngDialog/css/ngDialog-theme-plain.css" => array("3451", "5cc4d9ad1b2c21dba85c9609a70372d8"), + "libs/bower_components/ngDialog/css/ngDialog-theme-plain.min.css" => array("3117", "9b3aca326ddb24049f0342c2c065330b"), + "libs/bower_components/ngDialog/js/ngDialog.js" => array("10788", "ce9d17ce99239bfa8a7bfb9dfebd9acd"), + "libs/bower_components/ngDialog/js/ngDialog.min.js" => array("4722", "6e43eca51d89f4bc0f75305ec4d89e17"), + "libs/bower_components/ngDialog/package.json" => array("995", "7eb269f40ac2098bdba2a18faca9c241"), + "libs/bower_components/ngDialog/README.md" => array("11280", "e075efd034400283ddbd792963ce22c4"), + "libs/bower_components/sprintf/bower.json" => array("439", "49b6a39e763755263ab6d758690b1742"), + "libs/bower_components/sprintf/demo/angular.html" => array("690", "61276ccc42eb16f69df6f9dc82527ff2"), + "libs/bower_components/sprintf/dist/angular-sprintf.min.js" => array("449", "80b1dd478d4cf875a1118c1c2ad8c3c1"), + "libs/bower_components/sprintf/dist/angular-sprintf.min.map" => array("429", "88110c6656f210b1a33dfbacaca20bc8"), + "libs/bower_components/sprintf/dist/sprintf.min.js" => array("2955", "9d4d5248855a0a744e4354e516ace711"), + "libs/bower_components/sprintf/dist/sprintf.min.map" => array("4160", "124b5979ca1eeb818c6668f5cf9e0e06"), + "libs/bower_components/sprintf/gruntfile.js" => array("970", "4758263aff4cfbc3c1680d34c4d763e5"), + "libs/bower_components/sprintf/LICENSE" => array("1518", "1168e69d403e6f51cbbb01752ae99667"), + "libs/bower_components/sprintf/package.json" => array("598", "f5001fa43a0a710f86595a0397e6d262"), + "libs/bower_components/sprintf/README.md" => array("4405", "cb1413160aa7132ea34f625c98e4e3b2"), + "libs/bower_components/sprintf/src/angular-sprintf.js" => array("490", "7955cc90728c050c63177dd9c53f6b5e"), + "libs/bower_components/sprintf/src/sprintf.js" => array("7536", "d7c902d0301e4a3aad06bc3d581b4f4c"), + "libs/bower_components/sprintf/test/test.js" => array("3212", "4014279da9e8b364ab8f510a622fa3cd"), + "libs/bower_components/visibilityjs/bower.json" => array("418", "a7c00e98663476c18f30f7cef20cf834"), + "libs/bower_components/visibilityjs/ChangeLog.md" => array("2260", "197d392d11975152af573b9223dc84fb"), + "libs/bower_components/visibilityjs/index.js" => array("55", "8d0169177fdb0221daf60c6ec19e2a84"), + "libs/bower_components/visibilityjs/lib/visibility.core.js" => array("6458", "0f3d3058659304f2cdacbc12c0b8719b"), + "libs/bower_components/visibilityjs/lib/visibility.fallback.js" => array("1743", "1d9595749bb960ce6b15a896f3d93f6e"), + "libs/bower_components/visibilityjs/lib/visibility.js" => array("93", "a95bfd47da3a0a8e041a2be58e3d2db4"), + "libs/bower_components/visibilityjs/lib/visibility.timers.js" => array("5244", "8ac6ab268bfbe3855e2526c6c0516fe5"), + "libs/bower_components/visibilityjs/LICENSE" => array("1095", "4a9937d1d46c5d507f091ca83314620c"), + "libs/bower_components/visibilityjs/logo.svg" => array("3068", "9b5651c6cda2be8ac1152fcc65a3c471"), + "libs/bower_components/visibilityjs/README.md" => array("9574", "f5eeb1e85219c50191ee987b5f863d5c"), "libs/HTML/Common2.php" => array("15005", "a9c9b708cd3f2691192171bb78177db3"), "libs/HTML/QuickForm2/Container/Fieldset.php" => array("3009", "c01d23505028494f202da1e7c092c285"), "libs/HTML/QuickForm2/Container/Group.php" => array("10690", "af6598a96ab2505936156cf3c82eb231"), @@ -482,15 +1129,14 @@ class Manifest { "libs/HTML/QuickForm2/Rule/Regex.php" => array("5158", "842b30f0687b7d65c2c940bbfc44008e"), "libs/HTML/QuickForm2/Rule/Required.php" => array("3555", "4c440ff4e187faf10e307b04e774011b"), "libs/javascript/json2.js" => array("3377", "ba3293970e13b03a2ea92f5b6b5bf544"), - "libs/javascript/sprintf.js" => array("3795", "f8659e7549fb9e482d4f8145399f421c"), - "libs/jqplot/build_minified_script.sh" => array("1434", "b5fe6cddbb5e8bf8f440a380f3e7e5a9"), + "libs/jqplot/build_minified_script.sh" => array("1434", "9847924971c2ac2efb45b990fddb1867"), "libs/jqplot/excanvas.min.js" => array("19351", "5e2fefd5c782233c12383cca3b19e935"), "libs/jqplot/gpl-2.0.txt" => array("15112", "8ef64b86db8e0e63606284cf36b643be"), "libs/jqplot/jqplot.axisLabelRenderer.js" => array("3275", "61248e4baf0752ee2e89cf303c52a95e"), "libs/jqplot/jqplot.axisTickRenderer.js" => array("6570", "f7bdc4053619d39dd6ad9564de13b779"), "libs/jqplot/jqplot.canvasGridRenderer.js" => array("19588", "5d6c6588a60d6a298747d095fa11c662"), "libs/jqplot/jqplot.core.js" => array("175127", "57d2753e6e1a1aedb78e4c1ed7e2f48b"), - "libs/jqplot/jqplot-custom.min.js" => array("191785", "ad2a8daa551040c7c6061ade311f91b0"), + "libs/jqplot/jqplot-custom.min.js" => array("191818", "bb742f360c7af1d5f3b656a2bd56888d"), "libs/jqplot/jqplot.divTitleRenderer.js" => array("4174", "1ae09b12907cc772b2137ce60364b8a0"), "libs/jqplot/jqplot.linearAxisRenderer.js" => array("45487", "187b69d695443da0b2135a5e46d7b292"), "libs/jqplot/jqplot.linePattern.js" => array("4772", "1091be3885aaa2a2d9ed8171b85aed80"), @@ -506,7 +1152,7 @@ class Manifest { "libs/jqplot/plugins/jqplot.canvasAxisTickRenderer.js" => array("9843", "3f8a0bbc13e1793f6fc9a123c9bf3b98"), "libs/jqplot/plugins/jqplot.canvasTextRenderer.js" => array("24372", "58a963d919b36061685c46a75f6a4a50"), "libs/jqplot/plugins/jqplot.categoryAxisRenderer.js" => array("28571", "77d0a561f20e0b889e88758449ba7180"), - "libs/jqplot/plugins/jqplot.pieRenderer.js" => array("35551", "f78c034a0bae5eb0310392b5632907ec"), + "libs/jqplot/plugins/jqplot.pieRenderer.js" => array("35584", "bd7f7899e4a7414ab30c4034ac23ebe1"), "libs/jquery/gpl-2.0.txt" => array("15099", "2c1778696d3ba68569a0352e709ae6b7"), "libs/jquery/gpl-3.0.txt" => array("35147", "d32239bcb673463ab874e80d47fae504"), "libs/jquery/images/down_arrow.png" => array("2898", "6a5d9f1bd953608817ebf4904d4591bc"), @@ -514,25 +1160,14 @@ class Manifest { "libs/jquery/images/slide.png" => array("2831", "ef737b4f1d6b594656bca9e99b7f5968"), "libs/jquery/images/up_arrow.png" => array("2881", "bf4bbd999b2027d836e00fde27a46b39"), "libs/jquery/jquery.browser.js" => array("619", "236a3b854b2eeae01c8640ad82bb4178"), - "libs/jquery/jquery.history.js" => array("5592", "acd0667f090e0bf6e759bb86db1d674f"), - "libs/jquery/jquery.js" => array("96380", "52d16e147b5346147d0f3269cd4d0f80"), - "libs/jquery/jquery.jscrollpane.js" => array("63508", "08450c17ce2a83ab43176f80253d6f99"), - "libs/jquery/jquery.mousewheel.js" => array("3846", "f77bd9ca0396c7a8672f536884b1e1aa"), - "libs/jquery/jquery.placeholder.js" => array("4515", "6e5b889042b348bdee267ceecbe4159d"), - "libs/jquery/jquery.scrollTo.js" => array("2434", "c4dff68594e0fdb05b48aac9a90c0a19"), - "libs/jquery/jquery.smartbanner.js" => array("9921", "4e24cb04b88e2e0a2c013ab589ef5e2f"), + "libs/jquery/jquery.smartbanner.js" => array("14851", "82cfe5a1bb8e42ae3d168b04e482621e"), "libs/jquery/jquery.truncate.js" => array("2452", "cc3fcfc30d8a4496e891469ee20fbd77"), - "libs/jquery/jquery-ui.js" => array("228546", "ea464a34403c8772f3bc6ff2f5355f4b"), "libs/jquery/LICENSE-sizzle.txt" => array("17635", "418d0239a1435dae6b5c2e919a75f8c9"), "libs/jquery/MIT-LICENSE-history.txt" => array("1109", "242685841d67aa0bbc837735b177709f"), - "libs/jquery/MIT-LICENSE-jquery.txt" => array("1074", "13a84fe33922678518c49596de032d92"), - "libs/jquery/MIT-LICENSE-jqueryui.txt" => array("1311", "bac9338b4387621f0cea7720ace0450c"), - "libs/jquery/MIT-LICENSE-placeholder.txt" => array("1075", "0146cb31436f780624be47ce08eec616"), - "libs/jquery/MIT-LICENSE-scrollto.txt" => array("1120", "4a5d0d8578e331f5172fe5d0d1470fc4"), "libs/jquery/MIT-LICENSE-smartbanner.txt" => array("1071", "a23932b5f367ad0272456270829b52a7"), "libs/jquery/mwheelIntent.js" => array("2249", "71fe6a97b5e149ae08829a586fe7e7ad"), "libs/jquery/stylesheets/jquery.jscrollpane.css" => array("1400", "ead9005a6e67449768db45b42e3f9b89"), - "libs/jquery/stylesheets/jquery.smartbanner.css" => array("3901", "94fb09ced6636405d47b5ec6c1ed23c1"), + "libs/jquery/stylesheets/jquery.smartbanner.css" => array("4004", "d9e2cba33edaf7e2d21076924184b805"), "libs/jquery/stylesheets/scroll.less" => array("2066", "ab33e88a6d4360052d87685173dbf07a"), "libs/jquery/themes/base/images/ui-anim_basic_16x16.gif" => array("1553", "03ce3dcc84af110e9da8699a841e5200"), "libs/jquery/themes/base/images/ui-bg_flat_0_aaaaaa_40x100.png" => array("180", "2a44fbdb7360c60122bcf6dcef0387d8"), @@ -548,90 +1183,30 @@ class Manifest { "libs/jquery/themes/base/images/ui-icons_454545_256x240.png" => array("4369", "771099482bdc1571ece41073b1752596"), "libs/jquery/themes/base/images/ui-icons_888888_256x240.png" => array("4369", "faf6f5dc44e713178784c1fb053990aa"), "libs/jquery/themes/base/images/ui-icons_cd0a0a_256x240.png" => array("4369", "5d8808d43cefca6f6781a5316d176632"), - "libs/jquery/themes/base/jquery-ui.css" => array("25699", "76b945b2f5246c4cbe31857afd481ceb"), - "libs/MaxMindGeoIP/geoipcity.inc" => array("6973", "d191ea9f911a9cf3bc98a07faadf511d"), - "libs/MaxMindGeoIP/geoip.inc" => array("31447", "26f4d94b6c394dcbb6abf57df5acbccf"), - "libs/MaxMindGeoIP/geoipregionvars.php" => array("95763", "1635d7024c6a8b2502ed6f60659c9a1f"), - "libs/pChart2.1.3/change.log" => array("11838", "47d20227c6b22d5425e0bc60475084ad"), - "libs/pChart2.1.3/class/pData.class.php" => array("30575", "3e35fc351f143b70e8959fb65f6bb522"), - "libs/pChart2.1.3/class/pDraw.class.php" => array("319989", "f27d42bc9fa34966f4483546254c312c"), - "libs/pChart2.1.3/class/pImage.class.php" => array("19960", "abbaf39d5fb456ccec682c0f0814d8a9"), - "libs/pChart2.1.3/class/pPie.class.php" => array("65564", "4f2ed8e8c89bd3646c4289e5f2547b7b"), - "libs/pChart2.1.3/GPLv3.txt" => array("35148", "8f0e2cd40e05189ec81232da84bd6e1a"), - "libs/pChart2.1.3/readme.txt" => array("5971", "8e04cbe332d419f3177b2d9300b15e1a"), - "libs/PclZip/lgpl-2.1.txt" => array("26530", "4fbd65380cdd255951079008b364516c"), - "libs/PclZip/pclzip.lib.php" => array("196363", "968cb96854866df0370e6fd5523fa05a"), - "libs/PEAR5.php" => array("1087", "1a8f67d58009372a6cbcddd638b128cf"), - "libs/PEAR/Exception.php" => array("14006", "424a61a67dbd5f9f3ed5fc3be2b9ac54"), - "libs/PEAR/FixPHP5PEARWarnings.php" => array("152", "ff5f4e5d365b916ea63225840bc0b71a"), - "libs/PEAR/LICENSE" => array("1477", "45b44486d8090de17b2a8b4211fab247"), - "libs/PEAR.php" => array("33932", "f9f83fb6efef354ec16765ffe17d2ae4"), - "libs/PiwikTracker/LICENSE.txt" => array("1505", "7bbcab51f5db7fee7e6864a639ff56ab"), - "libs/PiwikTracker/PiwikTracker.php" => array("56782", "2a84b2de3492460089a52baad3b1fcd6"), - "libs/README.md" => array("1713", "fad15836001bfea82cdc4f362febe4fb"), + "libs/jquery/themes/base/jquery-ui.min.css" => array("25699", "76b945b2f5246c4cbe31857afd481ceb"), + "libs/MaxMindGeoIP/geoipcity.inc" => array("7446", "c5ac78777d5c430ed3236e466040a67f"), + "libs/MaxMindGeoIP/geoip.inc" => array("44492", "17d480b5501dc59dffa4d4ee6dc2448a"), + "libs/MaxMindGeoIP/geoipregionvars.php" => array("125067", "3999586d260df20e06129cf10c187301"), + "libs/pChart/change.log" => array("25310", "c90748e01e5617278081a5a0b4ac845b"), + "libs/pChart/class/pData.class.php" => array("30667", "f45c4fba3489b5b1a940acaa7a59feef"), + "libs/pChart/class/pDraw.class.php" => array("327671", "963177a8bfd387c7a6048a3681a11241"), + "libs/pChart/class/pImage.class.php" => array("20001", "26fcbe95d274656a7a11ba6583f668d4"), + "libs/pChart/class/pPie.class.php" => array("65577", "30b65724de538a03aca427be199ad58a"), + "libs/pChart/GPLv3.txt" => array("35823", "664aa96239b59b044722945d56f70200"), + "libs/pChart/readme.txt" => array("12528", "2417b0df8a049d780f8866db6a6f946f"), + "libs/PiwikTracker/PiwikTracker.php" => array("631", "f4d49c84c2212987a26ce7c0b822eba9"), + "libs/README.md" => array("1712", "04e9ad7e47b792e5c4d0ebdc3b6cfe8f"), "libs/sparkline/CHANGES" => array("648", "b6d213a7ad5d1f2c6e3eac38d48c1f8a"), "libs/sparkline/DESIGN" => array("648", "a9e2a29ce386fb408ab2eaa02a86c2ed"), "libs/sparkline/gpl-2.0.txt" => array("18092", "b234ee4d69f5fce4486a80fdaf4a4263"), - "libs/sparkline/lib/Object.php" => array("3951", "095eae57154bed92a78e3d951e0135ef"), - "libs/sparkline/lib/Sparkline_Bar.php" => array("6834", "c5aa452cf5698ca7baca128aa02ab2f8"), - "libs/sparkline/lib/Sparkline_Line.php" => array("11026", "9cc756ac498e10ae7291d0a252ef3bf6"), - "libs/sparkline/lib/Sparkline.php" => array("16623", "31777f846d96077e1b32cba2f1874138"), + "libs/sparkline/lib/Object.php" => array("3956", "422308842217470ec8ee823019460cd1"), + "libs/sparkline/lib/Sparkline_Bar.php" => array("6834", "43d43b34cd21761fee83094ef402d9df"), + "libs/sparkline/lib/Sparkline_Line.php" => array("11025", "8b7ad1aadf105b470112ebfd4869eb50"), + "libs/sparkline/lib/Sparkline.php" => array("16630", "7ea0fc0be7625a2418e4f4b635856e49"), "libs/sparkline/LICENSE-BSD.txt" => array("1505", "51639a73ddb4999a16fc9249eb445acc"), "libs/sparkline/README" => array("1043", "aa954952640a7c645151f63905e9692a"), - "libs/tcpdf/2dbarcodes.php" => array("7785", "ab7d01f44bbea0b839c6b2c2f362f4cb"), - "libs/tcpdf/barcodes.php" => array("59791", "9f0f79a92e025acfd5aef0c09b0ea736"), - "libs/tcpdf/composer.json" => array("933", "8681cdc1c3eea06625bf0c827f4a353e"), - "libs/tcpdf/config/lang/eng.php" => array("1200", "cc64ed556dacc69a43777204d08c952b"), - "libs/tcpdf/config/tcpdf_config_alt.php" => array("5320", "43c5e852ab49252b52871512ef6bd3db"), - "libs/tcpdf/config/tcpdf_config.php" => array("5202", "f2ca50b70b473fa4f0fd002e34970761"), - "libs/tcpdf/fonts/almohanad.ctg.z" => array("2780", "f2a06979cd7c5262b773f1c5257f93f8"), - "libs/tcpdf/fonts/almohanad.php" => array("14074", "3cf9d431468593f9d4eba1795631943b"), - "libs/tcpdf/fonts/almohanad.z" => array("121292", "80a4fbacf654e77c487bbcefad390dfb"), - "libs/tcpdf/fonts/dejavusans.ctg.z" => array("10120", "b693c24b2880b5d2e5f7c7ecd31213dc"), - "libs/tcpdf/fonts/dejavusans.php" => array("52352", "f38c6f5c629707b6b1197c60a68a77ed"), - "libs/tcpdf/fonts/dejavusans.z" => array("361229", "df214f9763ae5fcab34b85aab01d49d9"), - "libs/tcpdf/fonts/helveticabi.php" => array("2589", "c22fdc8941f2956e0930b20105870468"), - "libs/tcpdf/fonts/helveticab.php" => array("2580", "3daad3713df02c15beebd09ceecacacd"), - "libs/tcpdf/fonts/helveticai.php" => array("2584", "e0a7f23376f50de631db93814aff2e35"), - "libs/tcpdf/fonts/helvetica.php" => array("2575", "2a315fa2593161154c319788f0ef2127"), - "libs/tcpdf/fonts/hysmyeongjostdmedium.php" => array("1829", "51f6fe162641de3714866950d5eff4e8"), - "libs/tcpdf/fonts/kozgopromedium.php" => array("3577", "2c5e8a67d1a805aae9842bbad59a873f"), - "libs/tcpdf/fonts/kozminproregular.php" => array("3454", "78fdf805f1cea6cd01912192821ec734"), - "libs/tcpdf/fonts/msungstdlight.php" => array("1550", "c940b153fb6c5b3498efa181881b5b6c"), - "libs/tcpdf/fonts/stsongstdlight.php" => array("1627", "eb85dc872664c0769e9fab1b7540b4d5"), - "libs/tcpdf/fonts/symbol.php" => array("2555", "20e28c8b386ddbb38ead777f717d7c44"), - "libs/tcpdf/fonts/timesbi.php" => array("2580", "a5f3fbbef1831fe0bcd060edb6e5010b"), - "libs/tcpdf/fonts/timesb.php" => array("2577", "ad485022027867116de0bf6c25b1854a"), - "libs/tcpdf/fonts/timesi.php" => array("2575", "8fd8e9a11cca513a4da0f25ff1a24149"), - "libs/tcpdf/fonts/times.php" => array("2572", "a75033315ee90464410b47cc27ce9ff0"), - "libs/tcpdf/gpl.txt" => array("35147", "d32239bcb673463ab874e80d47fae504"), - "libs/tcpdf/htmlcolors.php" => array("5499", "fe132ea6a41cd787b4c2ff18716625b5"), - "libs/tcpdf/include/sRGB.icc" => array("3048", "060e79448f1454582be37b3de490da2f"), - "libs/tcpdf/include/tcpdf_colors.php" => array("14661", "f782f4cb468e8a5e956ca0759530e132"), - "libs/tcpdf/include/tcpdf_filters.php" => array("14677", "e175abe0e5c8661e08834014abead451"), - "libs/tcpdf/include/tcpdf_font_data.php" => array("313432", "8f83bbc144d70505672f82679546c72d"), - "libs/tcpdf/include/tcpdf_fonts.php" => array("95129", "70425e7872fbabb524b934ec474b0880"), - "libs/tcpdf/include/tcpdf_images.php" => array("11170", "b1c6baba7c286f48d813df899350c8db"), - "libs/tcpdf/include/tcpdf_static.php" => array("106691", "fab9356ece2e9d51e831dd00da64324b"), - "libs/tcpdf/lgpl-3.0.txt" => array("7651", "e6a600fd5e1d9cbde2d983680233ad02"), - "libs/tcpdf/LICENSE.TXT" => array("43636", "5c87b66a5358ebcc495b03e0afcd342c"), - "libs/tcpdf/pdf417.php" => array("53738", "130560d35265c9666440dbf5086f7846"), - "libs/tcpdf/qrcode.php" => array("80058", "59adfba524c4445f850a92380eb2a17f"), - "libs/tcpdf/README.TXT" => array("5396", "46bcf50a79dc0d5f82b4c5c3df95f504"), - "libs/tcpdf/spotcolors.php" => array("2153", "4b3c09efafad1e9c28021fca16f52147"), - "libs/tcpdf/tcpdf_autoconfig.php" => array("6918", "1df4c5b1b14c31160da910ed8e390e91"), - "libs/tcpdf/tcpdf.crt" => array("2290", "c137aab97f7e06a6038589968e10d976"), - "libs/tcpdf/tcpdf.fdf" => array("1286", "96f873c30a6f6a0f884d713d185d5bd8"), - "libs/tcpdf/tcpdf_import.php" => array("3327", "6bb88a8a3d69511d1bf9e7af12ab5f47"), - "libs/tcpdf/tcpdf.p12" => array("1749", "7e078148a1ab66ca05041915f7013204"), - "libs/tcpdf/tcpdf_parser.php" => array("26986", "d7fcc07fbea917f9ae4e07aad9b86fd3"), - "libs/tcpdf/tcpdf.php" => array("883754", "5c02ba6d7a3dba39c140ca7aa25d22f0"), - "libs/tcpdf/unicode_data.php" => array("227828", "2ec28c23ec6ad9f71f8b822a0ce0a1bf"), "libs/upgradephp/README" => array("373", "f0a0f26fd38b1e01be19766c11654564"), - "libs/upgradephp/upgrade.php" => array("16164", "4cc4006f9ae5e23b73e69c51b64cd99c"), - "libs/UserAgentParser/README.md" => array("972", "34ff27b5748bcd32dd255595cb386ece"), - "libs/UserAgentParser/UserAgentParser.php" => array("28445", "974d5ff712e0331175d543d797e821fd"), - "libs/UserAgentParser/UserAgentParser.test.php" => array("4690", "7ec960da6d502b46706f6fa3602a72f4"), + "libs/upgradephp/upgrade.php" => array("16857", "8017764ef397aedbd9c4e96753aa5142"), "libs/Zend/Cache/Backend/Apc.php" => array("11108", "db51f9156bd866fa2ffcb7ee3f8bfd77"), "libs/Zend/Cache/Backend/BlackHole.php" => array("7461", "0058fa2c88b65f6c3aa022534aa1f86e"), "libs/Zend/Cache/Backend/ExtendedInterface.php" => array("4035", "ac5c1f66c2eb9a542dd144a9c1ea8b53"), @@ -770,9 +1345,9 @@ class Manifest { "libs/Zend/Mime.php" => array("12927", "0059c7eef064f1225af33d1ddc0e8cef"), "libs/Zend/Registry.php" => array("6155", "cebfa4d6d7658af3167d521ffdc09153"), "libs/Zend/Session/Abstract.php" => array("6051", "f46f1047a6f5b37b2180cfe18f9f37a2"), - "libs/Zend/Session/Exception.php" => array("2313", "5c87f32e2d6c61f0937481ff6da38d05"), + "libs/Zend/Session/Exception.php" => array("2273", "fb3218b5361f28fa86cfcef6e1eef62f"), "libs/Zend/Session/Namespace.php" => array("16860", "1351d7294b06b262d8fa63f88fed5503"), - "libs/Zend/Session.php" => array("27322", "02d23cef6fbabae08268d017defdb0bc"), + "libs/Zend/Session.php" => array("27324", "cf609c7d0e9b3d327f1436920c9e2ab7"), "libs/Zend/Session/SaveHandler/DbTable.php" => array("17926", "97ed5c81d24270470450211991de3c12"), "libs/Zend/Session/SaveHandler/Exception.php" => array("1174", "b49e92bc8cd9c7e29afb7a5182da8325"), "libs/Zend/Session/SaveHandler/Interface.php" => array("2056", "f27f2b6f050869a7f7aef5a5385a8e05"), @@ -815,7 +1390,7 @@ class Manifest { "libs/Zend/Validate/Hostname/Cn.php" => array("168071", "55fa16bdeecbebf10df472ede8681606"), "libs/Zend/Validate/Hostname/Com.php" => array("13661", "1b25658d4741fdcb5702e0fdc64f7e0e"), "libs/Zend/Validate/Hostname/Jp.php" => array("55551", "0fe384871528b21e2a981bbf1d0de767"), - "libs/Zend/Validate/Hostname.php" => array("35735", "cb6fff7c0769efb452f7e04ba185e54a"), + "libs/Zend/Validate/Hostname.php" => array("45931", "2eafdf34459c8abfa4a9174b08d0f5a8"), "libs/Zend/Validate/Iban.php" => array("7303", "2725d29f25df052fe2a0b5b28464960b"), "libs/Zend/Validate/Identical.php" => array("4094", "3c325081db3380872533a48ffb4e54f3"), "libs/Zend/Validate/InArray.php" => array("5291", "2a1f8aab4b1d7e39da319972c0fcbba5"), @@ -828,368 +1403,1149 @@ class Manifest { "libs/Zend/Validate.php" => array("8509", "88ef476e71d54440da1123a10f59ceea"), "libs/Zend/Validate/PostCode.php" => array("6018", "1cffbba8fc10e1b3780bb4c6e5550956"), "libs/Zend/Validate/Regex.php" => array("4021", "eb2c6b2377b924a3ebcd1c708802caf7"), - "libs/Zend/Validate/StringLength.php" => array("6633", "9c0f8e241452e13eb5a19c3994e74d11"), + "libs/Zend/Validate/StringLength.php" => array("7010", "5855c9a142984f5c8c1a1e82f5521954"), "libs/Zend/Version.php" => array("2558", "5d840e09ef8ece6d15e8bfb786af0faf"), - "misc/cron/archive.php" => array("1427", "17d3d028e0063eddbd2171a971937e96"), - "misc/cron/archive.sh" => array("3411", "310b24292e5968a2b27fc81cc2d0133b"), - "misc/cron/updatetoken.php" => array("1227", "40be39dd7be96bf3cf07a25321646a53"), + "misc/composer/build-xhprof.sh" => array("1487", "21e0b5dceefc2c7b080a6b1a5bfaae4d"), + "misc/composer/clean-xhprof.sh" => array("325", "139953c79fab328c3de2e2c2fee20e5c"), + "misc/cron/archive.php" => array("2630", "cffebf416f25bb2f125dffc61c50b092"), + "misc/cron/archive.sh" => array("1228", "e86b8120a36fe555907de4bd617d6d49"), + "misc/cron/.htaccess" => array("103", "9430d4c88b1d229030349569fefc7b35"), + "misc/cron/updatetoken.php" => array("1833", "40b6df17908ed1ee013fdf971769e1bf"), "misc/gpl-3.0.txt" => array("35147", "d32239bcb673463ab874e80d47fae504"), - "misc/How to install Piwik.html" => array("281", "f4a48dcdad08996699fa52876fe2e59f"), - "misc/log-analytics/import_logs.py" => array("63576", "d27a2bb0e1aeb6c8ef28ee2140eac2f6"), - "misc/log-analytics/README.md" => array("12850", "81e24593a103725475033d73a7911308"), - "misc/others/api_internal_call.php" => array("925", "f0cadfd1d505c9190c5b799116329c27"), - "misc/others/api_rest_call.php" => array("926", "ee4a436279d3588350998d23a4457331"), - "misc/others/cli-script-bootstrap.php" => array("1197", "c9f9cc36be2fa58324405d675f6cdb21"), - "misc/others/download-count.txt" => array("563", "fa8ababaa1c23873c85b576807012997"), - "misc/others/ExamplePiwikTracker.php" => array("650", "fef8fb6167d19edfe9050da691d3e80d"), - "misc/others/geoipUpdateRows.php" => array("8375", "f35ef4770cebf00f9cf6b733aef3b14a"), + "misc/How to install Piwik.html" => array("336", "9e59e7b4b64998114c992b77d2eee716"), + "misc/internal-docs/content-tracking.md" => array("38548", "fc130e30f124b054b646b3641e0b73bb"), + "misc/log-analytics/CONTRIBUTING.md" => array("347", "154beae0480501ee765deb75564e8e7b"), + "misc/log-analytics/import_logs.py" => array("91919", "89b90a5b31b0c5057ade2f451615695e"), + "misc/log-analytics/README.md" => array("16859", "cf215092ac50f23407611bbde12ead0d"), + "misc/others/api_internal_call.php" => array("845", "377eb304bdc41f91da3dc6abf19829ea"), + "misc/others/api_rest_call.php" => array("929", "9dcb1bf366ba9b34ad00bc827058b3de"), + "misc/others/download-count.txt" => array("561", "4ce0d7021c01111dee281cc7d777ac66"), + "misc/others/ExamplePiwikTracker.php" => array("648", "24972c98a5c46a311b77dd17333ab1b4"), + "misc/others/geoipUpdateRows.php" => array("304", "a614a5b93499a7a5e21c2e0a5aa56655"), "misc/others/iframeWidget.htm" => array("613", "3c0f965d8cbc05e01e431f2a060fa3e5"), "misc/others/iframeWidget_localhost.php" => array("2156", "d0ab5ceb17e0010599d67442a6e113f7"), - "misc/others/phpstorm-codestyles/Piwik_codestyle.xml" => array("3585", "18b7d31e7283b1db8e778fdf7ab18721"), - "misc/others/phpstorm-codestyles/README.md" => array("984", "948c9dfd9918312ffe9dab44eb454054"), "misc/others/stress.sh" => array("293", "d1964246f8080b3e2101610fad483c8c"), - "misc/others/test_cookies_GenerateHundredsWebsitesAndVisits.php" => array("1055", "0dee3fddccfe0ab8aa440f1a9f4d7827"), - "misc/others/test_generateLotsVisitsWebsites.php" => array("10290", "2e61928d6e310ddf6ab7a61964850fdb"), - "misc/others/tracker_simpleImageTracker.php" => array("1060", "a9436b017e7ad30d59cff5b87e715308"), - "misc/others/uninstall-delete-piwik-directory.php" => array("1140", "8bdcdad35ced4e49b82cb9b809eebabe"), + "misc/others/tracker_simpleImageTracker.php" => array("992", "079b19e20d6813c3ce394c3091c50e81"), + "misc/others/uninstall-delete-piwik-directory.php" => array("1230", "e2b52ad258ad2d6934e85f360bbb2ece"), "misc/others/widget_example_lastvisits.html" => array("456", "faacc1f323fede8966eabd56cbacc362"), - "misc/proxy-hide-piwik-url/piwik.php" => array("2763", "742ba5f11208ea5e08dc90d8055bc6c8"), - "misc/proxy-hide-piwik-url/README.md" => array("3234", "dd49db51608aa49d3b490053a98f0d68"), - "misc/user/.gitkeep" => array("0", "d41d8cd98f00b204e9800998ecf8427e"), + "misc/proxy-hide-piwik-url/README.md" => array("122", "41d686494ba5be91c02b6a1e9fad5135"), "misc/user/index.html" => array("167", "bcef3676ee6eefa6968695ef954db78f"), - "piwik.js" => array("23183", "6e7bb550f0b87029593c66404f477088"), - "piwik.php" => array("5150", "30817ddf6963d6ef026ec587b8c262c7"), - "plugins/Actions/Actions.php" => array("47067", "8fd3c026d5bda12e6e35d4582fece05e"), - "plugins/Actions/API.php" => array("25131", "eb9695dccffefdb8b0be655caf4c9c7e"), - "plugins/Actions/Archiver.php" => array("22986", "f340a0c2888b7ad69baaa017ae85978f"), - "plugins/Actions/ArchivingHelper.php" => array("23888", "e2a415e828d32a1ba8f07137df8cf60d"), - "plugins/Actions/Controller.php" => array("3508", "d2b0b84b804e880aee1d80a68e8bdd03"), - "plugins/Actions/javascripts/actionsDataTable.js" => array("13340", "00a944a54e01638152a462776094fecf"), + "piwik.js" => array("54851", "7d6acc696e3b30383747e1c03b7ed6e0"), + "piwik.php" => array("2534", "2970c9592a256a7a4230162653780af7"), + "plugins/Actions/Actions/ActionClickUrl.php" => array("1919", "ed36b2d616772e8e1e00a84b4892ded8"), + "plugins/Actions/Actions/ActionDownloadUrl.php" => array("998", "ddecea552c6a2f856336c2d3c00472d1"), + "plugins/Actions/Actions/ActionSiteSearch.php" => array("9777", "7a1982d52f3cec7e8d347ed622851912"), + "plugins/Actions/Actions.php" => array("6214", "5f36a595e02531638efd84aaa89d749b"), + "plugins/Actions/API.php" => array("21178", "01a43ab96e677bab6bb9d3574d7a49ad"), + "plugins/Actions/Archiver.php" => array("21662", "97fc4bfe36d3e9a644b816a28a687019"), + "plugins/Actions/ArchivingHelper.php" => array("24517", "227b79d3691f1b3a728af23f914cd38e"), + "plugins/Actions/Columns/ActionType.php" => array("2250", "f5891d6e4408bedd4d65ad29775a4f81"), + "plugins/Actions/Columns/ActionUrl.php" => array("752", "7e74832ac3d7fddc91e3beb8ca4cc0c1"), + "plugins/Actions/Columns/ClickedUrl.php" => array("741", "569435ad07d5cfc1cc2485964edf14c5"), + "plugins/Actions/Columns/DestinationPage.php" => array("396", "959a4732f630255e15445dced7c8d2db"), + "plugins/Actions/Columns/DownloadUrl.php" => array("745", "4f32fffc5aeec53f04d3f0a7271ac777"), + "plugins/Actions/Columns/EntryPageTitle.php" => array("1288", "d3fdbc2926514818968910c2677c4caf"), + "plugins/Actions/Columns/EntryPageUrl.php" => array("1276", "0555350d3b6ebb11ad5d52d9e1586e55"), + "plugins/Actions/Columns/ExitPageTitle.php" => array("1644", "470abd224b7a526443f8e5ab28893e8a"), + "plugins/Actions/Columns/ExitPageUrl.php" => array("1718", "214186edcb73b85b733172d5efb40038"), + "plugins/Actions/Columns/Keyword.php" => array("380", "3548f9cd71eb77758d5cfa046106902a"), + "plugins/Actions/Columns/KeywordwithNoSearchResult.php" => array("406", "2978f63d8afd8c75c53d7988f8d91fde"), + "plugins/Actions/Columns/Metrics/AveragePageGenerationTime.php" => array("3492", "aeeefc4ee852c8cbee57b24af5e02b3e"), + "plugins/Actions/Columns/Metrics/AverageTimeOnPage.php" => array("1249", "e6667ff89bc97c518e3eaaeba3d6be3b"), + "plugins/Actions/Columns/Metrics/BounceRate.php" => array("1339", "1a6089b086fe2d0053bf8fee849625bf"), + "plugins/Actions/Columns/Metrics/ExitRate.php" => array("1206", "6d50ee5e65c7264f8552415c66d51aaa"), + "plugins/Actions/Columns/PageTitle.php" => array("762", "d7fdc5ca389b02815be1cb14354f98fb"), + "plugins/Actions/Columns/PageUrl.php" => array("911", "8bac626402b01576fbf083f65f4f1875"), + "plugins/Actions/Columns/SearchCategory.php" => array("394", "248bb839017d9d6027961f9019c28cb6"), + "plugins/Actions/Columns/SearchDestinationPage.php" => array("403", "a739fdc7c70e9011018c159a6773a4cd"), + "plugins/Actions/Columns/SearchKeyword.php" => array("749", "c25e2141b475cc2aedab1390344efafa"), + "plugins/Actions/Columns/SearchNoResultKeyword.php" => array("403", "4a565399ecb9870c50a70276f19928a6"), + "plugins/Actions/Columns/TimeSpentRefAction.php" => array("755", "e7d53820c1f0ae2e4c7b698f46d6178f"), + "plugins/Actions/Columns/VisitTotalActions.php" => array("2233", "750e89aa8ddc40d40a746d9475e8fe19"), + "plugins/Actions/Columns/VisitTotalSearches.php" => array("1816", "72e932064fa35eca9e62423a8ee77ff4"), + "plugins/Actions/Controller.php" => array("1215", "5b0d5fa8ef2a35f4404231b0cc6478ad"), + "plugins/Actions/DataTable/Filter/Actions.php" => array("1292", "16c377eccd19f5f0c60b945c0784773d"), + "plugins/Actions/javascripts/actionsDataTable.js" => array("14108", "138b49a1b364dd5b48df26be95762aa4"), + "plugins/Actions/javascripts/rowactions.js" => array("2171", "26baccf5d1b317f3c9fc5213f0662910"), + "plugins/Actions/lang/am.json" => array("376", "5b399d577eb14f72c95fceeaa6c9f137"), + "plugins/Actions/lang/ar.json" => array("8416", "4b728ccea2e3a52b9caa3c8a54f996fb"), + "plugins/Actions/lang/be.json" => array("4066", "93336770aff542bfe5ced0602cbc08fd"), + "plugins/Actions/lang/bg.json" => array("10180", "47b3faef2f99ca3b2438e97c5d4f0ed0"), + "plugins/Actions/lang/bs.json" => array("6320", "158df7218928ea4e808e982cc2a8cac9"), + "plugins/Actions/lang/ca.json" => array("6959", "41e4791112655de05c95d371ab34395a"), + "plugins/Actions/lang/cs.json" => array("6839", "36fc736467b641ae56c9ff7379778801"), + "plugins/Actions/lang/da.json" => array("6240", "f6f417f68a23b23eb92527fa3a0c70fa"), + "plugins/Actions/lang/de.json" => array("6698", "9a749fddf817c5e5def5ee5785a217fd"), + "plugins/Actions/lang/el.json" => array("11578", "1063a46e1d9cad68b71543fe67ceefe3"), + "plugins/Actions/lang/en.json" => array("6307", "833e5c76268d937af82fec8690a36384"), + "plugins/Actions/lang/es.json" => array("7250", "6a7f83856ec803f798b71c995e4ebef3"), + "plugins/Actions/lang/et.json" => array("2418", "793fefd5089a115234cd311d972ee95b"), + "plugins/Actions/lang/eu.json" => array("401", "2e0323e1b050700bbcd649f0d8a6b7c6"), + "plugins/Actions/lang/fa.json" => array("7289", "3694c15bfb66430aaeb4def59561a05b"), + "plugins/Actions/lang/fi.json" => array("5622", "b95bce037cf4a09fc90a64178cd3c28a"), + "plugins/Actions/lang/fr.json" => array("7258", "66c24d7f702e4c525fcad9878c8a362b"), + "plugins/Actions/lang/gl.json" => array("149", "dc6987977eb292a80851170884b71913"), + "plugins/Actions/lang/he.json" => array("6130", "762a07f46348ce2b1dde657293ca48c4"), + "plugins/Actions/lang/hi.json" => array("12782", "a9579f1e31d0525a5950378c7963199c"), + "plugins/Actions/lang/hr.json" => array("4949", "669880aca1d799000da83b7c6820490b"), + "plugins/Actions/lang/hu.json" => array("3774", "52bbfaa80a13f8f0d4b751b0b9ae4696"), + "plugins/Actions/lang/id.json" => array("6429", "9ecee682e447ebda32ca1e597e1d6f70"), + "plugins/Actions/lang/is.json" => array("501", "2eaee8b58c0b9118d736d71a152e65c6"), + "plugins/Actions/lang/it.json" => array("6987", "2b53adaff18d681a2ffae448e9244460"), + "plugins/Actions/lang/ja.json" => array("7429", "b95c7e07037e3b3fc12fb9e1a0065ad8"), + "plugins/Actions/lang/ka.json" => array("820", "054c1b8cf89e6119dc4c7102ae122d79"), + "plugins/Actions/lang/ko.json" => array("6782", "d2c31eb267be8fd20e107299d9fe763e"), + "plugins/Actions/lang/lt.json" => array("620", "1032b329dd62f82fa747d9bda1fc6395"), + "plugins/Actions/lang/lv.json" => array("842", "70862e89e64cad312ec4e2ba795d0e43"), + "plugins/Actions/lang/nb.json" => array("6298", "c44bd08fc530812e22b632b0d63bb7f8"), + "plugins/Actions/lang/nl.json" => array("6534", "761e68260263d4692d120cd9b89cb915"), + "plugins/Actions/lang/nn.json" => array("2852", "8d05622f8bfd4cd16b4e9d8d0358ec78"), + "plugins/Actions/lang/pl.json" => array("6996", "db24a7965fc5554153f55dda79a0f5e0"), + "plugins/Actions/lang/pt-br.json" => array("7121", "f3a16b2d8806f1b32b91a46255a27947"), + "plugins/Actions/lang/pt.json" => array("6086", "79ba7f1f4702b43043c2fbc6a11fb064"), + "plugins/Actions/lang/ro.json" => array("6877", "cc95ca84b8726b30d5787a2631651585"), + "plugins/Actions/lang/ru.json" => array("9889", "09bbed254583204b0bde56b537bc696a"), + "plugins/Actions/lang/sk.json" => array("2376", "f8f75b6767cd43347244bf3677e6b2ee"), + "plugins/Actions/lang/sl.json" => array("3971", "2ec0bf792f37dcc11502c9d9e19a2465"), + "plugins/Actions/lang/sq.json" => array("6907", "c267471d5e400c00a1e4c74d3fda4b59"), + "plugins/Actions/lang/sr.json" => array("6266", "5affec1d5694c6adaff40df76651a6bb"), + "plugins/Actions/lang/sv.json" => array("6381", "b43489b930405c04c3807a027bb605f4"), + "plugins/Actions/lang/ta.json" => array("11774", "aadbc78702afa25b13e1d5fef259c251"), + "plugins/Actions/lang/te.json" => array("1954", "2e66c315d884a6bea6109c0510b3616e"), + "plugins/Actions/lang/th.json" => array("9206", "c2ac213e4689019d18f176b8ec7e2eb9"), + "plugins/Actions/lang/tl.json" => array("7072", "21f36f73c17289033915ad69a1158ceb"), + "plugins/Actions/lang/tr.json" => array("6103", "c36ab78df5e6c8d51fd73450b26fd083"), + "plugins/Actions/lang/uk.json" => array("642", "830df2c8659b0f4cb6f6505b2876f6b0"), + "plugins/Actions/lang/vi.json" => array("7612", "57a05efb7df6bbc7eb79c6a2916dda5b"), + "plugins/Actions/lang/zh-cn.json" => array("5373", "878edf069c32806c2f589341068c178a"), + "plugins/Actions/lang/zh-tw.json" => array("2092", "597e0da9cc0a9e0d7b896ffb772824ea"), + "plugins/Actions/Menu.php" => array("752", "996bcc9fb134543c4982433a1443ae35"), + "plugins/Actions/Metrics.php" => array("3328", "28ac7894cbaec2bd700fccacaedb0b7d"), + "plugins/Actions/Reports/Base.php" => array("4089", "4bc81513de2464ad8a3dc449bc167a53"), + "plugins/Actions/Reports/GetDownloads.php" => array("1698", "1d892a40cf14f77e8a800caec320cfd8"), + "plugins/Actions/Reports/GetEntryPageTitles.php" => array("2815", "4399bdaa9562fe3574b005345bd5df0b"), + "plugins/Actions/Reports/GetEntryPageUrls.php" => array("2784", "c4bdc1218fe29131c63e1a049acdae1c"), + "plugins/Actions/Reports/GetExitPageTitles.php" => array("2834", "da0ad5ca820bf419c8fc6ba006bdf6a9"), + "plugins/Actions/Reports/GetExitPageUrls.php" => array("3163", "f41e1131304cfee4d03470e0063de1a9"), + "plugins/Actions/Reports/GetOutlinks.php" => array("1872", "9e21d00cdeb564e305a7db9a4eb0bd0f"), + "plugins/Actions/Reports/GetPageTitlesFollowingSiteSearch.php" => array("2982", "a4419a52d67cfdbd352837c1c8b64e26"), + "plugins/Actions/Reports/GetPageTitles.php" => array("2817", "e61d82dfb4560e29e683109d5b516bfc"), + "plugins/Actions/Reports/GetPageUrlsFollowingSiteSearch.php" => array("1214", "c90ab0765c4e113efd88929bccde379a"), + "plugins/Actions/Reports/GetPageUrls.php" => array("2341", "87930ff88c6790c33dfc758a63a9a314"), + "plugins/Actions/Reports/Get.php" => array("940", "7cf5da1d4ce4c7e1ae81bbffa36a8dae"), + "plugins/Actions/Reports/GetSiteSearchCategories.php" => array("2368", "ccc6eabe8fd4988cde78a3f366c2b359"), + "plugins/Actions/Reports/GetSiteSearchKeywords.php" => array("2591", "4a31acd8bb77263bd0ab6d41d4f5d84f"), + "plugins/Actions/Reports/GetSiteSearchNoResultKeywords.php" => array("2191", "254b1d8ff89bc88a7bddc516fa975868"), + "plugins/Actions/Reports/SiteSearchBase.php" => array("1840", "4c3af06e5c25d20c05066ed25027695d"), + "plugins/Actions/Segment.php" => array("456", "dd99845b7be42024b0d7696ce125e1e1"), "plugins/Actions/stylesheets/dataTableActions.less" => array("80", "6ea9a28abb3fdf5d59b75af48dbde129"), - "plugins/Actions/templates/indexSiteSearch.twig" => array("598", "f8495db5767bbb21576a5690e9b92435"), - "plugins/Annotations/AnnotationList.php" => array("15708", "0507b807d118f763ff4012e34ffca9ae"), - "plugins/Annotations/Annotations.php" => array("1134", "fa376e6beeda62967ac35bf96f42486f"), - "plugins/Annotations/API.php" => array("14544", "83c95cab8ef9e4862d31cab0dfa21c55"), - "plugins/Annotations/Controller.php" => array("8319", "42bb469975f6bc567f0bc78f3362b08d"), - "plugins/Annotations/javascripts/annotations.js" => array("22584", "b14eadbeac0cfacb80e9858140da762f"), - "plugins/Annotations/stylesheets/annotations.less" => array("3505", "f311fd592023253c5a386753b70eec9f"), - "plugins/Annotations/templates/_annotationList.twig" => array("1281", "6305cb8e31ab433b40e2fde4ae22ecf2"), - "plugins/Annotations/templates/_annotation.twig" => array("2543", "2e4cd965a46c6c3ed241be7ef6253006"), - "plugins/Annotations/templates/getAnnotationManager.twig" => array("1133", "b330fa07ac6c11095ed0c165f3a51446"), - "plugins/Annotations/templates/getEvolutionIcons.twig" => array("859", "fb82d9e0fe92f4e31701a902173f17a1"), + "plugins/Actions/templates/indexSiteSearch.twig" => array("682", "c3b8fe2c3ca8fcdffbb608d21d5c15fe"), + "plugins/Actions/Tracker/ActionsRequestProcessor.php" => array("3574", "3e3572fffed135f5bf2f5a9ffd464fda"), + "plugins/Annotations/AnnotationList.php" => array("15797", "094f4ad14ccb2de71545d6aa32e969cf"), + "plugins/Annotations/Annotations.php" => array("1334", "da2c5837d9a2feea7b450d5a56c3dff8"), + "plugins/Annotations/API.php" => array("14553", "ca5ada6bbf907207518163f98c4c0cad"), + "plugins/Annotations/Controller.php" => array("8501", "45f0d233bd8662fd2b018203ccdd36c6"), + "plugins/Annotations/javascripts/annotations.js" => array("22500", "04bcf977ccfe42c2e3115c9d45da62f8"), + "plugins/Annotations/lang/ar.json" => array("2056", "cfa18772cf457356d04b02e410fde6de"), + "plugins/Annotations/lang/bg.json" => array("2716", "a3b2b49029e20d00e6ef12178c1aa2df"), + "plugins/Annotations/lang/bs.json" => array("1218", "8cabf9ad341afc327135154a707cd821"), + "plugins/Annotations/lang/ca.json" => array("1561", "fa9a9c54b2259e133fa919e3c4a0349f"), + "plugins/Annotations/lang/cs.json" => array("1621", "2be63dce1fa2cb34ce802c3a0752f0f5"), + "plugins/Annotations/lang/da.json" => array("1495", "c6f4ec36258851bf11b79a02c1314752"), + "plugins/Annotations/lang/de.json" => array("1957", "3eb607cc1226ef490c6c647affb4dcbb"), + "plugins/Annotations/lang/el.json" => array("3005", "b6bd308550536f56e6131fd196900aff"), + "plugins/Annotations/lang/en.json" => array("1590", "c4fc006f6a1828222e1ea54a80cde879"), + "plugins/Annotations/lang/es.json" => array("1788", "475baa68587995994b2208a7044a1066"), + "plugins/Annotations/lang/et.json" => array("913", "12f7521cc9cb3e4aa2f2a079b5ac3b83"), + "plugins/Annotations/lang/fa.json" => array("2465", "a7a17289a14a910248aa28f3ace21526"), + "plugins/Annotations/lang/fi.json" => array("1699", "32c6ac22f5222f5273b8ededfd421acb"), + "plugins/Annotations/lang/fr.json" => array("1795", "fef2c8c4ce756918d3427c2371a68360"), + "plugins/Annotations/lang/gl.json" => array("460", "14b5980a4972b6f4bf9399609d69014d"), + "plugins/Annotations/lang/he.json" => array("855", "88977dd5a4d8f6c53a9c0d362c9d9fee"), + "plugins/Annotations/lang/hi.json" => array("3552", "c3e35852bd91c59eed37dd42ab1d94c5"), + "plugins/Annotations/lang/hr.json" => array("444", "bd243877449c1fbf7e51f2e3501b4bd7"), + "plugins/Annotations/lang/id.json" => array("1811", "1aacbbb66973d061e7b44254ee5d161a"), + "plugins/Annotations/lang/it.json" => array("1644", "42d15eee8406b4e8328cbbc8d1abed30"), + "plugins/Annotations/lang/ja.json" => array("1785", "91dea5ad7c0ae86a455520a9e2cfcb3c"), + "plugins/Annotations/lang/ko.json" => array("1855", "1f172e1721e51a7f61ebb517593105e7"), + "plugins/Annotations/lang/nb.json" => array("1649", "ce4e4f4d1479979a34e548ccb64e3ed0"), + "plugins/Annotations/lang/nl.json" => array("1537", "302f2d75bc3fabf3229b2c96a53ced53"), + "plugins/Annotations/lang/pl.json" => array("1752", "cb9d6c799521e0b1c09c89cb0779efc2"), + "plugins/Annotations/lang/pt-br.json" => array("1791", "f4de7db8bb8928f5803ce4f75449cf87"), + "plugins/Annotations/lang/pt.json" => array("1837", "06b7d81bfe3e8e920581841e5fd73072"), + "plugins/Annotations/lang/ro.json" => array("1719", "dd94940c61707cf6f14ef60e0f408003"), + "plugins/Annotations/lang/ru.json" => array("2116", "a490bdb79fe4a473d3bbe8ca529f30a1"), + "plugins/Annotations/lang/sk.json" => array("828", "ec382b9169046d4079f35b0786be030a"), + "plugins/Annotations/lang/sl.json" => array("649", "e294e1b0a44cb8d6aae7d9eb83803ff2"), + "plugins/Annotations/lang/sq.json" => array("1885", "8de93c10dde6ad83f109fd57cd7e3fbe"), + "plugins/Annotations/lang/sr.json" => array("1526", "767a9280bb2310a7cfe3e5e9906765e7"), + "plugins/Annotations/lang/sv.json" => array("1845", "ced9701371e48aee083c257088c00f44"), + "plugins/Annotations/lang/ta.json" => array("2324", "f78b55455e2946df25d39a2ca0b08afe"), + "plugins/Annotations/lang/te.json" => array("114", "b26ba6170dd198efc4e527f6d43ccb3a"), + "plugins/Annotations/lang/th.json" => array("326", "77c4edcc8ed8f20ea5aecb8322dd0193"), + "plugins/Annotations/lang/tl.json" => array("1951", "a1e8694f8dc5407c604e501ee7ff2a8b"), + "plugins/Annotations/lang/tr.json" => array("1303", "9d7c46589c696eace498f5cd6fb24a60"), + "plugins/Annotations/lang/vi.json" => array("2074", "e788d111502408522c9a3251e71a210f"), + "plugins/Annotations/lang/zh-cn.json" => array("1396", "4ce3f41f7622cd97b7adb76e69d04389"), + "plugins/Annotations/lang/zh-tw.json" => array("159", "71019bb0e809bbcfb2090fafd089d6fe"), + "plugins/Annotations/stylesheets/annotations.less" => array("3822", "a906fde0b213d473fdeefc3f8ef259c8"), + "plugins/Annotations/templates/_annotationList.twig" => array("1287", "c96f7400a3e5abfd6ae40c81bdf5dc5c"), + "plugins/Annotations/templates/_annotation.twig" => array("2541", "cf7804d231481ff349031f193a9f88bf"), + "plugins/Annotations/templates/getAnnotationManager.twig" => array("1132", "073ed16924f708080261ea79e45b6c52"), + "plugins/Annotations/templates/getEvolutionIcons.twig" => array("858", "0a28f3417b1a0dbcd17e760ea4606b66"), "plugins/Annotations/templates/saveAnnotation.twig" => array("45", "5ad07d0a082d1cb3b199874b80285ea0"), - "plugins/API/API.php" => array("29238", "2b460cf97b6ab918db29b21d2effae1b"), - "plugins/API/Controller.php" => array("4818", "4d7a6fc1ec88da6888efa79a784387af"), - "plugins/API/ProcessedReport.php" => array("30939", "247cb79b399231721079044292d3686e"), - "plugins/API/RowEvolution.php" => array("19547", "2f282c72293c1b915683ba7e88033bec"), + "plugins/API/API.php" => array("28353", "0a2c33d8193a6735ec06fb1b88f8171b"), + "plugins/API/Controller.php" => array("5463", "f1aabd2a8f2cf22ddc42067b48fd4039"), + "plugins/API/DataTable/MergeDataTables.php" => array("1665", "172b3fd991ad31633409e94ff592515a"), + "plugins/API/Glossary.php" => array("3465", "33542c38e2ac56c19315bd7d0d6de7e6"), + "plugins/API/lang/am.json" => array("100", "c86d0311151a4e58e89a5776455a2d74"), + "plugins/API/lang/ar.json" => array("1721", "7c16f433e9c35aaf62089b1bfefaf9e3"), + "plugins/API/lang/be.json" => array("1394", "1e6cc204b5d32b459a2f569349a5c71a"), + "plugins/API/lang/bg.json" => array("1576", "bce928cf56607ab072dd729405cba3be"), + "plugins/API/lang/bs.json" => array("494", "9b58bc89b4ec2d038b228e4c2cc6715c"), + "plugins/API/lang/ca.json" => array("1061", "46b4a652ff7c6c0e3ca15db6d21e611c"), + "plugins/API/lang/cs.json" => array("1451", "72b85b6c1d2e8d6ddf6f6b0461f7e883"), + "plugins/API/lang/da.json" => array("1113", "894ee84163eb05968e03dbc32c3644d6"), + "plugins/API/lang/de.json" => array("1474", "e6e28462a8cc29062ff0610c12cc8ffc"), + "plugins/API/lang/el.json" => array("2437", "b57a1e8492a19ceaaade3e3c422910b5"), + "plugins/API/lang/en.json" => array("1367", "e36359789ba4f8070f2cbff002b572a2"), + "plugins/API/lang/es.json" => array("1404", "3e9f71d1f86f577467c46863fe845a6c"), + "plugins/API/lang/et.json" => array("257", "15cfccb5af4fd0b6e2a545f149f74897"), + "plugins/API/lang/eu.json" => array("71", "2fd1750a93e5c97728db7600da7defa4"), + "plugins/API/lang/fa.json" => array("554", "34356696095a68ed17b3efe930be7514"), + "plugins/API/lang/fi.json" => array("930", "63498e762db127a59f4b3d20605f7163"), + "plugins/API/lang/fr.json" => array("1562", "4c04029adfca6e6b81cb02f929dda01b"), + "plugins/API/lang/gl.json" => array("138", "46605622a5c30783616b7bfa13dcaa83"), + "plugins/API/lang/he.json" => array("1024", "47ff7e97140ac74ca4312658a2f8aec5"), + "plugins/API/lang/hi.json" => array("2003", "492df06f0a815fe150592da5f75163df"), + "plugins/API/lang/hr.json" => array("286", "45e31ae5f866db5275b07b8cb5e0dd7f"), + "plugins/API/lang/hu.json" => array("1072", "cdd4a2bb0b35a750668eb487b4dcd79c"), + "plugins/API/lang/id.json" => array("988", "47814287f8737c3ac6cca8ec73c2c9f0"), + "plugins/API/lang/is.json" => array("915", "8946fde78eaeb97a80622275a31008bd"), + "plugins/API/lang/it.json" => array("1452", "ce90b7adbb6297aacd40465a3a3779ec"), + "plugins/API/lang/ja.json" => array("1697", "d53598f33eb890b45d58610197bcd023"), + "plugins/API/lang/ka.json" => array("2153", "bb807e4dbc9aa0aeb1948db7c1febcc2"), + "plugins/API/lang/ko.json" => array("1530", "9d908f1e2b14cb59748c8afcc2d9dd8b"), + "plugins/API/lang/lt.json" => array("900", "6c14f4cba95b9ebadb3a2646be2a69d1"), + "plugins/API/lang/lv.json" => array("82", "9f1d4ea8de504f0ae17d893e79800a40"), + "plugins/API/lang/nb.json" => array("1320", "c2082b4d97d52dfef6357e6268930cbf"), + "plugins/API/lang/nl.json" => array("1213", "bfeb54111733c2729bc695f372c093b9"), + "plugins/API/lang/nn.json" => array("70", "1a90a41ba8788bf25b90efe637bf0132"), + "plugins/API/lang/pl.json" => array("1411", "ad6729378d06a4b0e9d9e047c07feeb5"), + "plugins/API/lang/pt-br.json" => array("1496", "69000fc194c00fbce4ba1cda53440551"), + "plugins/API/lang/pt.json" => array("994", "c7277a423b2c94a63bc8e4f8e768b09f"), + "plugins/API/lang/ro.json" => array("958", "665f7d12ff9a578762ff5180b07df148"), + "plugins/API/lang/ru.json" => array("1881", "0efde6aedb0732dc398701531497c7b9"), + "plugins/API/lang/sk.json" => array("130", "7264e4b894b642f053d25627d685f9f3"), + "plugins/API/lang/sl.json" => array("793", "200e842e87fde80256b230a14bcd58ef"), + "plugins/API/lang/sq.json" => array("1080", "bf5d099a851668d25bfbbff03b1add64"), + "plugins/API/lang/sr.json" => array("1189", "5e0f6aa5e32f86b80d1f3042974b2d9c"), + "plugins/API/lang/sv.json" => array("1236", "26b33896d483d409c14ec92977a6bbe4"), + "plugins/API/lang/ta.json" => array("760", "c387cb7e812326eeabfbe53f74ba115a"), + "plugins/API/lang/te.json" => array("95", "1f9dbcd0b8ea2b35694f97b7e55129de"), + "plugins/API/lang/th.json" => array("1653", "74465d1c8584323cd99b078882dac145"), + "plugins/API/lang/tl.json" => array("1071", "47b5c19bc3d3525ca5e0493d81dfbed2"), + "plugins/API/lang/tr.json" => array("952", "635b11c1b9b549322cda07ec054f5898"), + "plugins/API/lang/uk.json" => array("1285", "42c99e62a34c6820da9f51386be28abc"), + "plugins/API/lang/vi.json" => array("1422", "81058d06cb6976056f3f5d1a3611a103"), + "plugins/API/lang/zh-cn.json" => array("876", "6619e2357533180aa4838726a0f3eadf"), + "plugins/API/lang/zh-tw.json" => array("1125", "b53d7875393bbb7e6a39003cca12fb89"), + "plugins/API/Menu.php" => array("1971", "b71f56539d6a41dd0de8654bd4e63bed"), + "plugins/API/ProcessedReport.php" => array("36816", "e95044cbaf12f49223ae08baea1d7448"), + "plugins/API/Renderer/Console.php" => array("596", "af07f2df2f6082b2900f7cc2d900213e"), + "plugins/API/Renderer/Csv.php" => array("1789", "d22ecb074585d10040bab66cbc42ca79"), + "plugins/API/Renderer/Html.php" => array("1395", "d82c4210bc60acda0dc5006144ba4d2c"), + "plugins/API/Renderer/Json2.php" => array("802", "e6ff837766637b8d911831090812306a"), + "plugins/API/Renderer/Json.php" => array("2727", "d7ac763de150d27cdccbef7d1b0e6923"), + "plugins/API/Renderer/Original.php" => array("1590", "ddbcb5ecfe21a9c03ce4d6d536858b1d"), + "plugins/API/Renderer/Php.php" => array("2179", "39e6a05ec9b343193a38890a9cba7335"), + "plugins/API/Renderer/Rss.php" => array("1311", "60eec0ba72d275af0205a2ddd3d4e1b8"), + "plugins/API/Renderer/Tsv.php" => array("485", "3a86bf8a13fab4982e5be0dc93d01093"), + "plugins/API/Renderer/Xml.php" => array("954", "ecf61ddb304c0020164c050d758c5c0a"), + "plugins/API/Reports/Get.php" => array("2763", "fa106ce358229b889cc92302eeb0b484"), + "plugins/API/RowEvolution.php" => array("20719", "226ec756a82192b7e6c2a19a5bd42ec2"), "plugins/API/stylesheets/listAllAPI.less" => array("724", "c18396c6a93e74893483fbdcd7ff3fb6"), - "plugins/API/templates/listAllAPI.twig" => array("1049", "4adf1392c2bd87a6aac4b578dbcd65b6"), - "plugins/CoreAdminHome/API.php" => array("8164", "96c8e23e9390b352a77e4242f79665df"), - "plugins/CoreAdminHome/Controller.php" => array("12138", "5ff36d4263102d2c0e2e576b56354dfe"), - "plugins/CoreAdminHome/CoreAdminHome.php" => array("4902", "36802b98ee443d60935a58497c19306d"), - "plugins/CoreAdminHome/CustomLogo.php" => array("6329", "c8d6e7f8a505489bb4eb6a2e31c2ca05"), - "plugins/CoreAdminHome/javascripts/generalSettings.js" => array("4863", "b219e00ad3f5544e7b5e37b0effb3463"), - "plugins/CoreAdminHome/javascripts/jsTrackingGenerator.js" => array("12282", "291cc9b609d1b3f40ff1b4084043b3a8"), - "plugins/CoreAdminHome/javascripts/pluginSettings.js" => array("2358", "4a5e5984b547f409e8bd3a73542e961c"), - "plugins/CoreAdminHome/stylesheets/generalSettings.less" => array("2793", "fc401910d4ff9fa8b7be809f0468f717"), - "plugins/CoreAdminHome/stylesheets/jsTrackingGenerator.css" => array("1405", "9265daa10ff275ccf75dcfcd2926b9a9"), - "plugins/CoreAdminHome/stylesheets/menu.less" => array("2149", "4f32e2ba236cf2ea0798676b670050d3"), - "plugins/CoreAdminHome/stylesheets/pluginSettings.less" => array("520", "4e1b32c3e7108e45055051cfbdede938"), - "plugins/CoreAdminHome/templates/generalSettings.twig" => array("17731", "16a31e5ccac7ff372d952e492d037d90"), - "plugins/CoreAdminHome/templates/_menu.twig" => array("840", "5ee52c14bfe1e41fa07db26bd0f49e90"), - "plugins/CoreAdminHome/templates/optOut.twig" => array("1122", "4b2985cac29da2d123a4c780d368fa50"), - "plugins/CoreAdminHome/templates/pluginSettings.twig" => array("7608", "52ae5d20cd1ad28f616fe08ecf3047ad"), - "plugins/CoreAdminHome/templates/trackingCodeGenerator.twig" => array("13443", "34381ec263e148ee5991913c51a1bd01"), - "plugins/CoreConsole/Commands/CodeCoverage.php" => array("3188", "675e82de2141b1caadedbf2f91a59529"), - "plugins/CoreConsole/Commands/CoreArchiver.php" => array("4245", "37f3acf3ee8df8d4973f192a56227cb5"), - "plugins/CoreConsole/Commands/GenerateApi.php" => array("1863", "0998c1fcfd9fe56c82bc22812a45bb60"), - "plugins/CoreConsole/Commands/GenerateCommand.php" => array("2898", "d4180aa10d123f70c33ea2385d760cb0"), - "plugins/CoreConsole/Commands/GenerateController.php" => array("1958", "93b4d27330e51e13c497052ca3f5854a"), - "plugins/CoreConsole/Commands/GeneratePluginBase.php" => array("4542", "6f359b866d9df97303139fbf955bd30d"), - "plugins/CoreConsole/Commands/GeneratePlugin.php" => array("7264", "786dcfdc680750902ddea34ee0325aa8"), - "plugins/CoreConsole/Commands/GenerateSettings.php" => array("1935", "d760e64d6b9f41d48431d7cfff2247da"), - "plugins/CoreConsole/Commands/GenerateTest.php" => array("6086", "dab2b156fe2548658562090e686c9b88"), - "plugins/CoreConsole/Commands/GenerateVisualizationPlugin.php" => array("3556", "653ebdfc17dccde2d643134d7c708f1c"), - "plugins/CoreConsole/Commands/GitCommit.php" => array("4456", "dc6ef207850ca10950f694d8cd9b4cc0"), - "plugins/CoreConsole/Commands/GitPull.php" => array("1592", "69f2c84488535f2944a8b3c61619da35"), - "plugins/CoreConsole/Commands/GitPush.php" => array("1186", "774591997e01ba6d6a898e447048b6a7"), - "plugins/CoreConsole/Commands/ManagePlugin.php" => array("2372", "d2926cda27fee604b517b113b2ad83ad"), - "plugins/CoreConsole/Commands/ManageTestFiles.php" => array("1812", "a86ae0b8137c618dcf1af27fa18ba68b"), - "plugins/CoreConsole/Commands/RunTests.php" => array("3057", "b3a65e6e74b23b67d682edbc9e6679b8"), - "plugins/CoreConsole/Commands/RunUITests.php" => array("2647", "538924f987e82a8c4ae48851404ad03f"), - "plugins/CoreConsole/Commands/SetupFixture.php" => array("6080", "2b1aff33dd7ffda5737cb83f9998ac8d"), - "plugins/CoreConsole/Commands/SyncUITestScreenshots.php" => array("3016", "595086d2646fe3e3b15d2e7e904cb948"), - "plugins/CoreConsole/Commands/WatchLog.php" => array("855", "092903712bd6a244a1199eeab7458b50"), - "plugins/CoreHome/angularjs/anchorLinkFix.js" => array("2634", "96f55c02ee23e9e4b2c8f88e9c05e2b8"), - "plugins/CoreHome/angularjs/common/directives/autocomplete-matched.js" => array("1355", "9e42e502d560b4a4d140db85b9ac485e"), - "plugins/CoreHome/angularjs/common/directives/autocomplete-matched_spec.js" => array("1515", "ac1cb173a0b49e64d18f8f7bd2e49c34"), - "plugins/CoreHome/angularjs/common/directives/dialog.js" => array("1197", "343ca648f67762baada217cb848d0564"), - "plugins/CoreHome/angularjs/common/directives/directive.js" => array("174", "35c24d7f8602201a1a86fccd1c2ced63"), - "plugins/CoreHome/angularjs/common/directives/focus-anywhere-but-here.js" => array("1257", "a54b382326b1724ecfe08b75d9bda271"), - "plugins/CoreHome/angularjs/common/directives/focusif.js" => array("727", "3e050dd7229a73fc4c850acba33f215d"), - "plugins/CoreHome/angularjs/common/directives/ignore-click.js" => array("606", "7c44f9411918577878d2786d29149709"), - "plugins/CoreHome/angularjs/common/directives/onenter.js" => array("737", "c4da43d5d72695e6accaa72635e0b619"), - "plugins/CoreHome/angularjs/common/filters/evolution.js" => array("1067", "f03049c52419158e37b3b7485ad9ec97"), - "plugins/CoreHome/angularjs/common/filters/filter.js" => array("170", "e3cb8ba6c47023ea0eb11b00da89d752"), - "plugins/CoreHome/angularjs/common/filters/startfrom.js" => array("319", "b0d892fbbba5a5c59c0ebfe9cccc2ff4"), - "plugins/CoreHome/angularjs/common/filters/startfrom_spec.js" => array("997", "28e65c932ef636402a5436ce9036747b"), - "plugins/CoreHome/angularjs/common/filters/translate.js" => array("518", "4b3608c3d3925c2de1c812ca4c7095b7"), - "plugins/CoreHome/angularjs/common/services/piwik-api.js" => array("4931", "1f26832cd0e4667a9550f140d9b63f2d"), - "plugins/CoreHome/angularjs/common/services/piwik.js" => array("289", "7f8e1209a339cbb3df5c8b1c67608ff5"), - "plugins/CoreHome/angularjs/common/services/piwik_spec.js" => array("958", "918d81d86edb8ac04ea9fd4b2b3233d2"), - "plugins/CoreHome/angularjs/common/services/service.js" => array("172", "7df34c228b662caedc891189287d26bc"), - "plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline-directive.js" => array("2117", "f53eb6919e53ee6085b20af35e75a071"), - "plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.html" => array("968", "0a3bb46fea31257f0c3c254e5686a2d4"), - "plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.less" => array("763", "74046d60c1f671857b7583fad14cb2b5"), + "plugins/API/templates/glossary.twig" => array("1107", "231bd30ea68e0837b6219fa716bed02d"), + "plugins/API/templates/listAllAPI.twig" => array("1019", "b6feac2fa9c1a31a4740fa19eeb0b12f"), + "plugins/BulkTracking/BulkTracking.php" => array("1959", "f3346a4202e6f06a479fa9a7fcaa7fa2"), + "plugins/BulkTracking/plugin.json" => array("169", "dfcaeb509de55a67d1490dd7a4b0be3b"), + "plugins/BulkTracking/Tracker/Handler.php" => array("3542", "9570c4f7d5e5d638733373a7190d5dab"), + "plugins/BulkTracking/Tracker/Requests.php" => array("3126", "38c5f0972eb9108a338b9757f64ab9e9"), + "plugins/BulkTracking/Tracker/Response.php" => array("2671", "c24d58752fb6b246879e4199b9b017b2"), + "plugins/Contents/Actions/ActionContent.php" => array("1292", "5f371a333943da6ada48c99f74c8129d"), + "plugins/Contents/API.php" => array("2391", "7ede8a66ff4812eb6c1dfbb1a930f16e"), + "plugins/Contents/Archiver.php" => array("11347", "eabeb434fe17d34fb7bb2f12f810f362"), + "plugins/Contents/Columns/ContentInteraction.php" => array("1423", "6b7e2409b51efcddb1559e5ec0de41ff"), + "plugins/Contents/Columns/ContentName.php" => array("1371", "ea0febf2219494cd8b159736d3249dfb"), + "plugins/Contents/Columns/ContentPiece.php" => array("1389", "8e6dc65714611eac346e42c299aa1faf"), + "plugins/Contents/Columns/ContentTarget.php" => array("1341", "266c87c61a6668d20788959dce02c703"), + "plugins/Contents/Columns/Metrics/InteractionRate.php" => array("1379", "53df551f5d5a80ed6dae0b2270c950f9"), + "plugins/Contents/Contents.php" => array("1547", "fd7f5fe7e87ba4e6d21a032214dca199"), + "plugins/Contents/Controller.php" => array("1382", "f2c2cc1dbfeb5ef089a66fded111e79d"), + "plugins/Contents/DataArray.php" => array("2673", "2416874c5d01e01537996ea90c525b12"), + "plugins/Contents/Dimensions.php" => array("807", "7b4779b739acd14bc219088579b137e8"), + "plugins/Contents/javascripts/contentsDataTable.js" => array("1674", "7b8b8f01e0b2c46c93744ad21e803471"), + "plugins/Contents/lang/ar.json" => array("265", "97d25e52f815e46dc2b8e4fd2c82366c"), + "plugins/Contents/lang/bg.json" => array("428", "1f65f86b45d02103a0b20fd66b10138d"), + "plugins/Contents/lang/ca.json" => array("104", "c52c0aeeb30bb973edc8bcc52bd0a4b2"), + "plugins/Contents/lang/cs.json" => array("864", "4a6c8837a2c18a169f0e4ba00c364fd0"), + "plugins/Contents/lang/da.json" => array("343", "c3f7d7170556f64f9323e19591ab87e4"), + "plugins/Contents/lang/de.json" => array("1000", "c328efae8e09bef2a56d385b438cc56b"), + "plugins/Contents/lang/el.json" => array("1477", "bf0b0ab34b1c135b6ca2eb7e281a854c"), + "plugins/Contents/lang/en.json" => array("896", "07fc464e4b5b63d9e6c1ce2be7d34e47"), + "plugins/Contents/lang/es.json" => array("607", "2327eca3d4f31a145dd08092194991f1"), + "plugins/Contents/lang/et.json" => array("212", "0a1896e43ad9e640cf1c413058e855ff"), + "plugins/Contents/lang/fi.json" => array("355", "c08377ca1b6e3d63007a58f74e304cf8"), + "plugins/Contents/lang/fr.json" => array("991", "91190a312d20676518d1c4010f3a4ea7"), + "plugins/Contents/lang/gl.json" => array("217", "070db1ff75c5f1d9cb4dba6f015f7347"), + "plugins/Contents/lang/hi.json" => array("962", "96c58dcd8cc6845ec5d3f9572d55c99f"), + "plugins/Contents/lang/it.json" => array("1006", "89a5fd657de985b26f23cb23c7f23caf"), + "plugins/Contents/lang/ja.json" => array("1115", "c909aa4b9cd7cd42ceda01a367114930"), + "plugins/Contents/lang/ko.json" => array("326", "1c8f4dad616ab29d6ccaf38cea06113b"), + "plugins/Contents/lang/nb.json" => array("888", "7c27c8e0edcacfd3de93719dba69bd04"), + "plugins/Contents/lang/nl.json" => array("520", "f9b2efd73f7218763c25dee49a62c827"), + "plugins/Contents/lang/pl.json" => array("135", "b24d70c50f2af3a16bebe37fd6d27421"), + "plugins/Contents/lang/pt-br.json" => array("1003", "d8c9944eca5d07260bf8637572741956"), + "plugins/Contents/lang/pt.json" => array("225", "6bb927d09db08e05c79c392c9b350d05"), + "plugins/Contents/lang/ro.json" => array("145", "671fe8ff8d2a3bae4c20c0ef6068602a"), + "plugins/Contents/lang/ru.json" => array("477", "819bea4113f523fde5de4659517ec628"), + "plugins/Contents/lang/sl.json" => array("334", "a738a1d0c556b6e6179795014ea39806"), + "plugins/Contents/lang/sq.json" => array("556", "11f2a10e47d24541db415f24683887cd"), + "plugins/Contents/lang/sr.json" => array("522", "d14a1819817a1c3753b32dce649c2f41"), + "plugins/Contents/lang/sv.json" => array("552", "781dc39d5b3d281007778dd1e9527558"), + "plugins/Contents/lang/ta.json" => array("549", "b7874735f029cc248fcbe1fb56edfbdb"), + "plugins/Contents/lang/tl.json" => array("395", "54758fe5b605f93904a786bededcc967"), + "plugins/Contents/lang/tr.json" => array("357", "9e1987c5811621477a08b9d3ff1a9f19"), + "plugins/Contents/lang/vi.json" => array("593", "439ea2fa8d26cb0e1d1118644b71ae0b"), + "plugins/Contents/Menu.php" => array("743", "6fb2f16bf77a0640c43db44b8177d72f"), + "plugins/Contents/README.md" => array("202", "bdd0c2ddd6acb0a71a9c19c5e79a5c53"), + "plugins/Contents/Reports/Base.php" => array("1797", "7ff68b5d681245a8a533d54d566159c5"), + "plugins/Contents/Reports/GetContentNames.php" => array("1139", "81278332cb0c4c0b76b564b34daaa2a3"), + "plugins/Contents/Reports/GetContentPieces.php" => array("1146", "d235cc6133a0c54c4be6bba1a867d106"), + "plugins/Contents/stylesheets/datatable.less" => array("80", "65d853fbb3cc86d4f83e658b124a99dc"), + "plugins/CoreAdminHome/API.php" => array("5334", "36c1f1da052ffe687dbc95529416cda8"), + "plugins/CoreAdminHome/Commands/DeleteLogsData.php" => array("6759", "2e0dfb015c7e9d36e54d53ee6f5d4b2f"), + "plugins/CoreAdminHome/Commands/FixDuplicateLogActions.php" => array("8041", "69f41e5a31d7ec0e1138f6274b1e4730"), + "plugins/CoreAdminHome/Commands/InvalidateReportData.php" => array("8239", "40ddcdb8b1c5a17730f65972ce5bee09"), + "plugins/CoreAdminHome/Commands/OptimizeArchiveTables.php" => array("4459", "10c12d00a793604486d7b8ace00cb5ed"), + "plugins/CoreAdminHome/Commands/PurgeOldArchiveData.php" => array("8319", "ef82ab26547dbc169dd74e4825673f11"), + "plugins/CoreAdminHome/Commands/RunScheduledTasks.php" => array("2563", "66e9eb6b65454aa1fbe9abf9eda3916f"), + "plugins/CoreAdminHome/Commands/SetConfig/ConfigSettingManipulation.php" => array("4282", "3d14b24d6e4c4426e3774fadef5d2396"), + "plugins/CoreAdminHome/Commands/SetConfig.php" => array("3997", "0d766b8eedfc6dd8d2bc12f0507a09b6"), + "plugins/CoreAdminHome/Controller.php" => array("15193", "b4af385cad369e5456fb37adf634f494"), + "plugins/CoreAdminHome/CoreAdminHome.php" => array("2974", "6cf35367b3dc9d07bbeb2fd95a991806"), + "plugins/CoreAdminHome/CustomLogo.php" => array("6762", "7e9d13b98b1018d913c53cade226759f"), + "plugins/CoreAdminHome/javascripts/generalSettings.js" => array("5441", "4e8ac1080aed868e9a85d3ccf6bde5f2"), + "plugins/CoreAdminHome/javascripts/jsTrackingGenerator.js" => array("12837", "eec10a7366576fcd58a4b70e3c8b10e1"), + "plugins/CoreAdminHome/javascripts/pluginSettings.js" => array("2522", "8a514af58b165d85600300a6f58becbb"), + "plugins/CoreAdminHome/javascripts/protocolCheck.js" => array("1220", "6080df499f95067f1ab049aa7e853135"), + "plugins/CoreAdminHome/lang/ar.json" => array("6447", "30616af68ea7a23b43a9bfd06838f4c8"), + "plugins/CoreAdminHome/lang/be.json" => array("3655", "660940426d406fe06818e95f978c96c0"), + "plugins/CoreAdminHome/lang/bg.json" => array("12221", "cf9784df0edf49b77bae49e45db374fc"), + "plugins/CoreAdminHome/lang/bs.json" => array("259", "7aaac6ef093fe237d25f3ffe01c62768"), + "plugins/CoreAdminHome/lang/ca.json" => array("3743", "d2ae75e8f1e094ea4cd2993cb5cce301"), + "plugins/CoreAdminHome/lang/cs.json" => array("11039", "1c1b3f0d60648033801d4b00367841da"), + "plugins/CoreAdminHome/lang/da.json" => array("9717", "7c4a6c30ba94b06bfc9775a38e454557"), + "plugins/CoreAdminHome/lang/de.json" => array("11661", "80aecf13615ec145f190905346ae8392"), + "plugins/CoreAdminHome/lang/el.json" => array("19513", "4875a4730ee84e295377bd09d00adfff"), + "plugins/CoreAdminHome/lang/en.json" => array("10274", "9d153406739e01e8afc64c9919ade998"), + "plugins/CoreAdminHome/lang/es.json" => array("10883", "0b1cd137adae08cbfebad54e293b8b7a"), + "plugins/CoreAdminHome/lang/et.json" => array("1637", "5d3a3be1703728519da6dea35b28edf3"), + "plugins/CoreAdminHome/lang/fa.json" => array("7720", "06d0907461f5a294cdc1b86f6b3edc08"), + "plugins/CoreAdminHome/lang/fi.json" => array("9000", "6c64802d534579c234c1dbd661def9f1"), + "plugins/CoreAdminHome/lang/fr.json" => array("11831", "049a71e3bcc43a0d0640d8ff77674da1"), + "plugins/CoreAdminHome/lang/he.json" => array("1343", "2e0a7c73007a6b0098b9b62f49b66d6d"), + "plugins/CoreAdminHome/lang/hi.json" => array("15466", "2659cbb14fcda0c2d6b1dba1acceca94"), + "plugins/CoreAdminHome/lang/hr.json" => array("273", "fbd1bb3f0cdc3ac576367a463d981ab8"), + "plugins/CoreAdminHome/lang/hu.json" => array("10709", "1b6344f935b85c729e9f9946ff0113e9"), + "plugins/CoreAdminHome/lang/id.json" => array("8037", "c4d710e7a1dfc143813dfb318fae4c0a"), + "plugins/CoreAdminHome/lang/is.json" => array("806", "cb0dac3505efb91694e324284c7559e2"), + "plugins/CoreAdminHome/lang/it.json" => array("11460", "397c6b7d3175b7b86e9dac863274c2ca"), + "plugins/CoreAdminHome/lang/ja.json" => array("12391", "0e8c6433ff777acae453e42d21a70875"), + "plugins/CoreAdminHome/lang/ka.json" => array("3612", "96fc91c05ee64b1532e970d392d613a5"), + "plugins/CoreAdminHome/lang/ko.json" => array("5932", "7c46ddc03ba9900c03e43a8924fbde17"), + "plugins/CoreAdminHome/lang/lt.json" => array("440", "adbdcce3376f61283deccbac26dfd015"), + "plugins/CoreAdminHome/lang/lv.json" => array("657", "f9b6b26655894da5391f6d2f606a0217"), + "plugins/CoreAdminHome/lang/nb.json" => array("10847", "add2cc083af22d9fa1e6c5dce60e53fb"), + "plugins/CoreAdminHome/lang/nl.json" => array("9974", "f859d0600604141a727c5e9f0268a24b"), + "plugins/CoreAdminHome/lang/nn.json" => array("1857", "7e320976f8a714bc1104016339ecbef1"), + "plugins/CoreAdminHome/lang/pl.json" => array("9526", "b6ec486defc4001ea05cdc4fd5a4a796"), + "plugins/CoreAdminHome/lang/pt-br.json" => array("11699", "11135826f189f961e18ef3ed14566ae3"), + "plugins/CoreAdminHome/lang/pt.json" => array("4988", "c3192f8d8c5a178472f1afa1ba0447c4"), + "plugins/CoreAdminHome/lang/ro.json" => array("9172", "915ef6b04b17529ab7c61503cf27d22d"), + "plugins/CoreAdminHome/lang/ru.json" => array("15081", "617d558db1f983f6f3f331e80f12562d"), + "plugins/CoreAdminHome/lang/sk.json" => array("875", "118598bcb74ee441c95ee2ac72c02406"), + "plugins/CoreAdminHome/lang/sl.json" => array("3115", "2c93e918a108a5b025760f65cc461342"), + "plugins/CoreAdminHome/lang/sq.json" => array("2839", "abf14f13806f6b66bbceaff74bea4b07"), + "plugins/CoreAdminHome/lang/sr.json" => array("9859", "1ca6cbd9eabc589045f6a840f440a644"), + "plugins/CoreAdminHome/lang/sv.json" => array("10556", "102278e9dda7232e1eb8de91cee50e35"), + "plugins/CoreAdminHome/lang/ta.json" => array("6172", "a94a4884b43b16cf182648fd6bec1b37"), + "plugins/CoreAdminHome/lang/te.json" => array("157", "20cf58c9a9f87ed376dd51006742bc12"), + "plugins/CoreAdminHome/lang/th.json" => array("3743", "f51cf365abe5d52c62a3da68a24a6a1a"), + "plugins/CoreAdminHome/lang/tl.json" => array("9925", "bbb2b5a4d5e0e33d2dc2816d20f443a9"), + "plugins/CoreAdminHome/lang/tr.json" => array("7007", "26a6f05a901c65f8cee0554ba77a73b3"), + "plugins/CoreAdminHome/lang/uk.json" => array("2669", "266ed8275eb3d374374218d0773a9295"), + "plugins/CoreAdminHome/lang/vi.json" => array("10030", "cf1b5353891f90af031b111f7e0b1ba5"), + "plugins/CoreAdminHome/lang/zh-cn.json" => array("7148", "19d0f7a0d789dccaa7039153b75204b8"), + "plugins/CoreAdminHome/lang/zh-tw.json" => array("289", "c3333d99acc8ab7186933df7610045fd"), + "plugins/CoreAdminHome/Menu.php" => array("2424", "ea75163e628b76dba829ad7d15874a3a"), + "plugins/CoreAdminHome/Model/DuplicateActionRemover.php" => array("6400", "6af515924590d21369ebbe345de9e311"), + "plugins/CoreAdminHome/OptOutManager.php" => array("5849", "b3d861030f091df9beb3584572aea687"), + "plugins/CoreAdminHome/stylesheets/generalSettings.less" => array("3014", "84865c44cc27ddfc3508ba5594c1e74c"), + "plugins/CoreAdminHome/stylesheets/jsTrackingGenerator.css" => array("435", "a6c7d1871b2ffeafedae152f5d3810fd"), + "plugins/CoreAdminHome/Tasks/ArchivesToPurgeDistributedList.php" => array("2732", "a8d74db1d4856bfcb513ede0e9b485a0"), + "plugins/CoreAdminHome/Tasks.php" => array("5152", "6191b6a3612c6c79b6c27a8c515f35c5"), + "plugins/CoreAdminHome/templates/generalSettings.twig" => array("15119", "35472195341d12136f9e0c6b7341e9ce"), + "plugins/CoreAdminHome/templates/optOut.twig" => array("3380", "e89cf4c609f8c1aec060b8ca8dc5e3cc"), + "plugins/CoreAdminHome/templates/pluginSettings.twig" => array("1636", "e0f77da90b7cde67fd4f58bb08cf5c5d"), + "plugins/CoreAdminHome/templates/trackingCodeGenerator.twig" => array("10816", "ddfa6093d24517c19f8268e5f5752ee3"), + "plugins/CoreConsole/Commands/ClearCaches.php" => array("1116", "b43bf6af0ab5f862e824a9989d389584"), + "plugins/CoreConsole/Commands/CoreArchiver.php" => array("7770", "c74bdd8c7d10604853199a4551e89374"), + "plugins/CoreConsole/Commands/DevelopmentEnable.php" => array("1619", "407b0ca4826cbf533e1b72f8602cf608"), + "plugins/CoreConsole/Commands/DevelopmentManageTestFiles.php" => array("1926", "309b5e9eb08f6de4330f78aa0cda563f"), + "plugins/CoreConsole/Commands/DevelopmentSyncProcessedSystemTests.php" => array("2820", "b26d9b78457853ea2517707eef9b50e9"), + "plugins/CoreConsole/Commands/GenerateAngularDirective.php" => array("5019", "0af6919a4c0c7f4b442738202c5ce9fb"), + "plugins/CoreConsole/Commands/GenerateApi.php" => array("1940", "151c9774dc7ae7893ad00b0de2ab497a"), + "plugins/CoreConsole/Commands/GenerateArchiver.php" => array("2035", "9cfa1f351c7cd479184c4896be84a092"), + "plugins/CoreConsole/Commands/GenerateCommand.php" => array("3624", "16b1663264302d4822bcdd6a2828e017"), + "plugins/CoreConsole/Commands/GenerateController.php" => array("2035", "bcbbc41915ab291d180f454f9db97140"), + "plugins/CoreConsole/Commands/GenerateDimension.php" => array("10430", "b7ce4c30a2ea8df79b30482cc0cd3d15"), + "plugins/CoreConsole/Commands/GenerateMenu.php" => array("1981", "c50510951a7cc7a70b7046e1c63e810a"), + "plugins/CoreConsole/Commands/GeneratePluginBase.php" => array("12095", "da36cb47f5d995c49542eafc947a1a75"), + "plugins/CoreConsole/Commands/GeneratePlugin.php" => array("7165", "a90cce6d5463a496994ccd041b608cf5"), + "plugins/CoreConsole/Commands/GenerateReport.php" => array("10918", "788c12d3ba33aff02c16920a7813e87a"), + "plugins/CoreConsole/Commands/GenerateScheduledTask.php" => array("2052", "60b37f5dc1bc893a87dd37dcdc11447a"), + "plugins/CoreConsole/Commands/GenerateSettings.php" => array("2012", "66c80af3eed723813deb0a0c5870c3e5"), + "plugins/CoreConsole/Commands/GenerateTest.php" => array("6586", "40f41f7d45145c2611be478db1711881"), + "plugins/CoreConsole/Commands/GenerateUpdate.php" => array("3902", "969409dea2ff88f8051934ac352fcc6c"), + "plugins/CoreConsole/Commands/GenerateVisualizationPlugin.php" => array("3672", "52c1bd5425bba2bae10183cf65abe30a"), + "plugins/CoreConsole/Commands/GenerateWidget.php" => array("4100", "ea4193d16cf85c020d2eaa57b337fe30"), + "plugins/CoreConsole/Commands/GitCommit.php" => array("4579", "bcb252fd88f9090b98b9628a629a24d3"), + "plugins/CoreConsole/Commands/GitPull.php" => array("1615", "3efe18272aec58b81c9b14682730767d"), + "plugins/CoreConsole/Commands/GitPush.php" => array("1260", "3245e20d1f7faad56762b75f286292b4"), + "plugins/CoreConsole/Commands/ManagePlugin.php" => array("3827", "9dda766c81f3523550fdc4f9c4bef1a3"), + "plugins/CoreConsole/Commands/WatchLog.php" => array("933", "731015b3f289194da71225ce6435668b"), + "plugins/CoreHome/angularjs/ajax-form/ajax-form.controller.js" => array("2659", "abca56433b192e40e2cfb56ed05e6b74"), + "plugins/CoreHome/angularjs/ajax-form/ajax-form.directive.js" => array("5949", "8ec7bb671f9bb4b01bfbd11c3f8227a4"), + "plugins/CoreHome/angularjs/anchorLinkFix.js" => array("2866", "8e56fdd25f8c036761319f784d35fdca"), + "plugins/CoreHome/angularjs/common/directives/autocomplete-matched.js" => array("2002", "ef1110fa971179ef53181c760ff23473"), + "plugins/CoreHome/angularjs/common/directives/dialog.js" => array("1576", "f3d75742bb632c1f1b9ac71f259aeeab"), + "plugins/CoreHome/angularjs/common/directives/directive.module.js" => array("214", "ac6b77adc1c87808df6f9a251f9cd806"), + "plugins/CoreHome/angularjs/common/directives/focus-anywhere-but-here.js" => array("1499", "69ae4035ad7c899e18723a40024a756f"), + "plugins/CoreHome/angularjs/common/directives/focusif.js" => array("893", "9101239dbcaefeeb8666639bbe2db49d"), + "plugins/CoreHome/angularjs/common/directives/ignore-click.js" => array("710", "f3856cd967f7430a4a7d35c7355e1080"), + "plugins/CoreHome/angularjs/common/directives/onenter.js" => array("853", "fb2635e3675ed8f8f4b94dbb5ef3c9d4"), + "plugins/CoreHome/angularjs/common/directives/translate.js" => array("1226", "25631e79c43116b4bc3d09b0cd1b1888"), + "plugins/CoreHome/angularjs/common/filters/evolution.js" => array("1280", "2d73aaa3bec3eb49a147be5ac1d97a97"), + "plugins/CoreHome/angularjs/common/filters/filter.module.js" => array("211", "437a6034fdb37539d3aed523cfad6313"), + "plugins/CoreHome/angularjs/common/filters/htmldecode.js" => array("686", "ba2d989b0f42815503c418246136ac47"), + "plugins/CoreHome/angularjs/common/filters/length.js" => array("455", "044553b96551318b3631ee0ace4db473"), + "plugins/CoreHome/angularjs/common/filters/pretty-url.js" => array("356", "325e19c11fc5685926d7c0aebd3db9e7"), + "plugins/CoreHome/angularjs/common/filters/startfrom.js" => array("404", "61c73f5f48fe0e59cbd53a8b736de38b"), + "plugins/CoreHome/angularjs/common/filters/startfrom.spec.js" => array("1121", "c349283452bd9333dc923849d796e82d"), + "plugins/CoreHome/angularjs/common/filters/translate.js" => array("623", "506dba3d11f2533d3033f28fbbdaf971"), + "plugins/CoreHome/angularjs/common/filters/trim.js" => array("415", "fa0dcf521dccf4f5e9c3c5fab48a7556"), + "plugins/CoreHome/angularjs/common/filters/ucfirst.js" => array("500", "e6c65f6dde4ccac134a462de0475a594"), + "plugins/CoreHome/angularjs/common/services/piwik-api.js" => array("9853", "aaf46455d03b233014330879ee4dcb3e"), + "plugins/CoreHome/angularjs/common/services/piwik-api.spec.js" => array("9340", "5ad12f7790c88fb3da58af213b3683ea"), + "plugins/CoreHome/angularjs/common/services/piwik.js" => array("374", "ba910785547f2c3596b0eadb22467ec0"), + "plugins/CoreHome/angularjs/common/services/piwik.spec.js" => array("1086", "7bcd31b934b7f60cf85ae84e6ffffb79"), + "plugins/CoreHome/angularjs/common/services/service.module.js" => array("211", "b556271a166215692f022125fa3f5b23"), + "plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler.controller.js" => array("3253", "561ac89dd5a1c3a5f96b8fe2f709d9b4"), + "plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler.directive.js" => array("710", "fc58d980d245e100f8ed2afd2799f58f"), + "plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler-urllistener.service.js" => array("3589", "ddc827dcbb2a6299d775bed94b79e68e"), + "plugins/CoreHome/angularjs/dialogtoggler/ngdialog.less" => array("1363", "c9d1b2a219dde5ccf2e22c2fdbfbe4f7"), + "plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.directive.html" => array("1285", "29ec6b3f32288a79a69e3488fb0889ab"), + "plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.directive.js" => array("2679", "08d2be8497acd8780ea8ddf64b44d866"), + "plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.directive.less" => array("1076", "857cd7a07e8b207bf980a366fa8c0528"), "plugins/CoreHome/angularjs/enrichedheadline/help.png" => array("350", "a2442fd403f530897728f540aa374c70"), - "plugins/CoreHome/angularjs/piwikAppConfig.js" => array("335", "286c144553a0f1389f804a4f2931c27a"), - "plugins/CoreHome/angularjs/piwikApp.js" => array("321", "8b891e5568091f5bef5566fe2a8440f3"), - "plugins/CoreHome/angularjs/siteselector/siteselector-controller.js" => array("1826", "a297e0adb8c651e9c49b975f72290e17"), - "plugins/CoreHome/angularjs/siteselector/siteselector-directive.js" => array("2676", "3c6cb7efaefd524922328842205f15ed"), - "plugins/CoreHome/angularjs/siteselector/siteselector.html" => array("3030", "2a1df7bc671183fb140a04132a0a6a6e"), - "plugins/CoreHome/angularjs/siteselector/siteselector.less" => array("3527", "7741715b7b9ccbdfd9c44f92e9080eff"), - "plugins/CoreHome/angularjs/siteselector/siteselector-model.js" => array("1909", "9ef295ef4cec80096d9c24705411618b"), - "plugins/CoreHome/Controller.php" => array("8189", "5cc9b5304f98cd5dd4fe96c28665a7eb"), - "plugins/CoreHome/CoreHome.php" => array("10068", "d32fe2afae1943efe49db6276a33c164"), - "plugins/CoreHome/DataTableRowAction/MultiRowEvolution.php" => array("2134", "09919d9abed888e1a1ad0149cf241ea9"), - "plugins/CoreHome/DataTableRowAction/RowEvolution.php" => array("11730", "6dbc8fcb21eb457148022097a445d778"), + "plugins/CoreHome/angularjs/history/history.service.js" => array("4428", "f591fbd512bc1774d8b5efcaecdd1c1b"), + "plugins/CoreHome/angularjs/http404check.js" => array("1818", "6fa9ab54fef3905880fde779accc2a19"), + "plugins/CoreHome/angularjs/menudropdown/menudropdown.directive.html" => array("1153", "2df9597c7e1fcd3234a46682d0dd2de8"), + "plugins/CoreHome/angularjs/menudropdown/menudropdown.directive.js" => array("2457", "3183c14e0e9a16899c2f236b0cadf8b5"), + "plugins/CoreHome/angularjs/menudropdown/menudropdown.directive.less" => array("2017", "0a21fa9157b48b56bf06613acba944dd"), + "plugins/CoreHome/angularjs/notification/notification.controller.js" => array("979", "844362df3c2828ea518d4840a2702ff6"), + "plugins/CoreHome/angularjs/notification/notification.directive.html" => array("340", "eaa588a40c0b019ce90daa2b9873a978"), + "plugins/CoreHome/angularjs/notification/notification.directive.js" => array("3534", "ae7c18b571e9bf71706c087f4b5f8f36"), + "plugins/CoreHome/angularjs/notification/notification.directive.less" => array("684", "5a741c01664f038087947bafe89e0f13"), + "plugins/CoreHome/angularjs/piwikApp.config.js" => array("414", "7ff195eda2782e7199367dcc380d3eae"), + "plugins/CoreHome/angularjs/piwikApp.js" => array("434", "db286d351874552917926fa15857c22f"), + "plugins/CoreHome/angularjs/quick-access/quick-access.controller.js" => array("3190", "eda03e846729315b019ee807a76be561"), + "plugins/CoreHome/angularjs/quick-access/quick-access.directive.html" => array("2365", "fc70418bab12871a4c5ff196033fd2e8"), + "plugins/CoreHome/angularjs/quick-access/quick-access.directive.js" => array("10477", "365fd3c6f420cb0472845d74272dc39b"), + "plugins/CoreHome/angularjs/quick-access/quick-access.directive.less" => array("934", "aaee54e0eff2c7d8d47da8031b427754"), + "plugins/CoreHome/angularjs/selector/selector.directive.js" => array("2671", "f390e2b976c77de399f356f4125b3208"), + "plugins/CoreHome/angularjs/selector/selector.directive.less" => array("1006", "226d6c918276cab7bb7264067315ea25"), + "plugins/CoreHome/angularjs/siteselector/siteselector.controller.js" => array("1651", "2f89a00282998b27b94aa74bd1f7f67b"), + "plugins/CoreHome/angularjs/siteselector/siteselector.directive.html" => array("3530", "2aeed0ccddecd57d455bb0c8ba9e827b"), + "plugins/CoreHome/angularjs/siteselector/siteselector.directive.js" => array("3515", "a032c188da7448182a7e012fd4671d51"), + "plugins/CoreHome/angularjs/siteselector/siteselector.directive.less" => array("2746", "7ec054af1c6843acaca5682f54aa0200"), + "plugins/CoreHome/angularjs/siteselector/siteselector-model.service.js" => array("4061", "642a97067d416f851af26584cc2bcc4c"), + "plugins/CoreHome/Columns/IdSite.php" => array("1492", "12ccec9447a69c7bc83f2abc1977f279"), + "plugins/CoreHome/Columns/Metrics/ActionsPerVisit.php" => array("1034", "e537ce58bfe370e7130dcdcfa923ae04"), + "plugins/CoreHome/Columns/Metrics/AverageTimeOnSite.php" => array("1261", "416dc71e93fd63ca0ab3f581bb50ccc3"), + "plugins/CoreHome/Columns/Metrics/BounceRate.php" => array("1231", "04fea9fa33e9a811026575295d776ec4"), + "plugins/CoreHome/Columns/Metrics/CallableProcessedMetric.php" => array("993", "94a21d0e06aee534298764e276d071f0"), + "plugins/CoreHome/Columns/Metrics/ConversionRate.php" => array("1269", "21013305c172cc0af00fcd7ecdada152"), + "plugins/CoreHome/Columns/Metrics/EvolutionMetric.php" => array("3290", "cd1e0addef5107ebfd43d9906837b178"), + "plugins/CoreHome/Columns/Metrics/VisitsPercent.php" => array("1858", "8cd6934fd89b3688100356859126f35d"), + "plugins/CoreHome/Columns/ServerTime.php" => array("935", "c26cec838ae8e40d32625e1975a2a1f6"), + "plugins/CoreHome/Columns/UserId.php" => array("3664", "e7a71268e1a1204f28881c6d72244dfd"), + "plugins/CoreHome/Columns/VisitFirstActionTime.php" => array("817", "7043d4efb0ae2979c9bb0f95d6af5489"), + "plugins/CoreHome/Columns/VisitGoalBuyer.php" => array("4101", "282a73b3f5b8db0b50fd7786b8d88598"), + "plugins/CoreHome/Columns/VisitGoalConverted.php" => array("1276", "27710a0547f2d959c597e6c0e902471d"), + "plugins/CoreHome/Columns/VisitId.php" => array("1030", "53d28c6fad9a785aa5024b346ada98f9"), + "plugins/CoreHome/Columns/VisitIp.php" => array("1190", "48094c2e89ecbace94f2ddcf52b638d0"), + "plugins/CoreHome/Columns/VisitLastActionTime.php" => array("2057", "5d75a82d5081d8e9c3ea3e3bc591e1e0"), + "plugins/CoreHome/Columns/VisitorDaysSinceFirst.php" => array("1383", "e61320b30edcccb4d28b341117f038d2"), + "plugins/CoreHome/Columns/VisitorDaysSinceOrder.php" => array("1544", "99c10b8ed51c65e7f4eb5523725d1483"), + "plugins/CoreHome/Columns/VisitorId.php" => array("1205", "bc54d6c0de8a6c240ac236cd6a30d77a"), + "plugins/CoreHome/Columns/VisitorReturning.php" => array("2296", "0b57df644e458be6d81013de36ad0a98"), + "plugins/CoreHome/Columns/VisitsCount.php" => array("1346", "b3626d13420083b653804d9fe6b40fad"), + "plugins/CoreHome/Columns/VisitTotalTime.php" => array("2970", "0acabfb0da9b3fd8cd765f0f42759f11"), + "plugins/CoreHome/config/config.php" => array("199", "56a0d120c06028ec881f6e32c67a9ef9"), + "plugins/CoreHome/Controller.php" => array("8998", "c986e29cbf4a0ef0146000c6f8f82b48"), + "plugins/CoreHome/CoreHome.php" => array("16077", "7611742a969aec4579fe41468790e33f"), + "plugins/CoreHome/DataTableRowAction/MultiRowEvolution.php" => array("2138", "cc9fc3ed224716fd952d18dce93cd138"), + "plugins/CoreHome/DataTableRowAction/RowEvolution.php" => array("12727", "767f02e506f6a0a6bdf9ceccf48bc478"), "plugins/CoreHome/images/bg_header.jpg" => array("9097", "0af504cfba0e9a1df7024e5e8d37ace9"), "plugins/CoreHome/images/bullet1.gif" => array("52", "4ea97a49fbc122f369c0509a1e312d36"), "plugins/CoreHome/images/bullet2.gif" => array("52", "e105364ac53547afc6453aac69c4e43a"), "plugins/CoreHome/images/favicon.ico" => array("17947", "9f21326e0c543b50bbe7022b44d97615"), + "plugins/CoreHome/images/favicon.png" => array("18085", "b5a8b909b721a7d4e91e5724ce43df1c"), "plugins/CoreHome/images/googleplay.png" => array("16550", "94203701766674b6ea54cce20b5e4a7e"), "plugins/CoreHome/images/more_date.gif" => array("56", "0f97ae90441e7bf074981d7854f7bdb7"), "plugins/CoreHome/images/more_period.gif" => array("53", "0741dc6f4a2e767ac60c619e85142555"), "plugins/CoreHome/images/more.png" => array("1045", "91d6a597dc70d86071f6aa333cce20aa"), + "plugins/CoreHome/images/navigation_collapse.png" => array("484", "71180c660cc8b94acc16c91c100766f6"), + "plugins/CoreHome/images/navigation_expand.png" => array("502", "13d521bb20871a1053d1401ffededf94"), "plugins/CoreHome/images/promo_splash.png" => array("12070", "98b50af9fcfe08214b630aa5c598848e"), "plugins/CoreHome/images/reset_search.png" => array("1021", "7e761f3444bf4edd4cd1779801c963bd"), "plugins/CoreHome/images/search.png" => array("136", "2fae5ccecd05ad37bd228e9490875ad4"), - "plugins/CoreHome/javascripts/broadcast.js" => array("22763", "4c3cd06055cafa714fd2e2efd4ff82ba"), - "plugins/CoreHome/javascripts/calendar.js" => array("22538", "b87e51fbf1cad8c856337656bca22a8b"), - "plugins/CoreHome/javascripts/color_manager.js" => array("10985", "c22a21e71febe410b3e62de68ce31359"), - "plugins/CoreHome/javascripts/corehome.js" => array("5684", "30d487fa1f07c5b3907886359d4b168a"), - "plugins/CoreHome/javascripts/dataTable.js" => array("67710", "03047b604c67c04845a34b7a1ff3074c"), - "plugins/CoreHome/javascripts/dataTable_rowactions.js" => array("12810", "c98799009416f594610fb887bd7414f9"), - "plugins/CoreHome/javascripts/donate.js" => array("5832", "b1519aaa05ab20ff55e6515f73f22840"), - "plugins/CoreHome/javascripts/menu_init.js" => array("445", "d11ca5e76d64fcb588095037bb3a9e4f"), - "plugins/CoreHome/javascripts/menu.js" => array("3808", "0c2b7400db1cd4144b9820d8a71d6dbf"), - "plugins/CoreHome/javascripts/notification.js" => array("6438", "409154616a282cc31d394f15b1207ee9"), - "plugins/CoreHome/javascripts/notification_parser.js" => array("824", "4860da380ee3224ae5628967055c7285"), - "plugins/CoreHome/javascripts/popover.js" => array("8640", "805e980248f8043a1df32b011d677c13"), - "plugins/CoreHome/javascripts/promo.js" => array("541", "d55cfc743448fc782c3a084ca5205619"), - "plugins/CoreHome/javascripts/require.js" => array("1297", "7f170c5276e38599d334b6e4c167ceb7"), - "plugins/CoreHome/javascripts/sparkline.js" => array("3304", "47ab94274d11f6bdbf840aad41129f0e"), - "plugins/CoreHome/javascripts/top_controls.js" => array("704", "5a27c5eeaa6e8e0c0682c170128be44d"), - "plugins/CoreHome/javascripts/uiControl.js" => array("3663", "b0d3706d547d7f102d39c17b000ea894"), - "plugins/CoreHome/stylesheets/cloud.less" => array("874", "7f28097a511022a86beb4735cb3eb877"), + "plugins/CoreHome/javascripts/broadcast.js" => array("25257", "306d383d96a336516550b0cfa48c0762"), + "plugins/CoreHome/javascripts/calendar.js" => array("22845", "3877fd4d6477d2d6ccf9e8c2ccf8c3d3"), + "plugins/CoreHome/javascripts/color_manager.js" => array("10967", "b353d58b5b4e15661aee335885ee410b"), + "plugins/CoreHome/javascripts/corehome.js" => array("6948", "d20870e6505fb3c11a483504825bd03c"), + "plugins/CoreHome/javascripts/dataTable.js" => array("75119", "813db2a07782ba83ebbfce390ef9e721"), + "plugins/CoreHome/javascripts/dataTable_rowactions.js" => array("13810", "1509d2f6fc2118c27abea1e7c96cc315"), + "plugins/CoreHome/javascripts/donate.js" => array("5845", "68b91fa072dceb7f26d298136e063557"), + "plugins/CoreHome/javascripts/menu_init.js" => array("1108", "d3495850c0c21bea0fbb518349867375"), + "plugins/CoreHome/javascripts/menu.js" => array("5223", "6fa924e1bdb0e49439777d15e40b36f9"), + "plugins/CoreHome/javascripts/notification.js" => array("4640", "0f3c5c133a46014bb58b8052d0fd8903"), + "plugins/CoreHome/javascripts/notification_parser.js" => array("840", "0355511f49d021a3b3c0ad7671c3f556"), + "plugins/CoreHome/javascripts/numberFormatter.js" => array("4905", "6b3346bc8bcd2049d8318b45ad8abc29"), + "plugins/CoreHome/javascripts/popover.js" => array("9146", "344269ceac5c593298aef1a0b5c37f8c"), + "plugins/CoreHome/javascripts/require.js" => array("1311", "51935def5f73c3a156fc3c2a9711c37c"), + "plugins/CoreHome/javascripts/sparkline.js" => array("3645", "ff2d22b59ec4931d51a37db8a6822ec4"), + "plugins/CoreHome/javascripts/top_controls.js" => array("3274", "44f4eaeacc3e1849b401bbf7288ba85e"), + "plugins/CoreHome/javascripts/uiControl.js" => array("3667", "60260138e086658c1c11c48b1b9a2182"), + "plugins/CoreHome/lang/am.json" => array("293", "3f589e67838d75a6a85867bced743b73"), + "plugins/CoreHome/lang/ar.json" => array("5957", "1e6488690d6684189e2d67ff283957a5"), + "plugins/CoreHome/lang/be.json" => array("1420", "74dd7c9a19335d242227a1ba5125ec0a"), + "plugins/CoreHome/lang/bg.json" => array("6249", "6daffdb7e37de2c3ae2df6c6d682033e"), + "plugins/CoreHome/lang/bs.json" => array("191", "dcd4f69b4db7ba742ec6a208c843f1b5"), + "plugins/CoreHome/lang/ca.json" => array("3020", "756505ce395c21b74add9399c1bf3993"), + "plugins/CoreHome/lang/cs.json" => array("5214", "49306f1d64959f7d6cf59cb709c8ac8b"), + "plugins/CoreHome/lang/da.json" => array("4732", "9f500ec0ce459ae1271257d936ea9d54"), + "plugins/CoreHome/lang/de.json" => array("5865", "488193e51830e35a33eaee21948fe3bf"), + "plugins/CoreHome/lang/el.json" => array("9207", "4daac3c46140eeca1f355ddeb5478f7e"), + "plugins/CoreHome/lang/en.json" => array("5077", "7dd99ec0da369fffdb606e669350910a"), + "plugins/CoreHome/lang/es.json" => array("5181", "245f6592c1e10782cf73709fad9a7241"), + "plugins/CoreHome/lang/et.json" => array("1771", "1a673b3b63e0255658dad9fb1600dddc"), + "plugins/CoreHome/lang/eu.json" => array("572", "5953a2984961166b0a2a64800f74ad4c"), + "plugins/CoreHome/lang/fa.json" => array("4390", "5f802e7ea85acebab2837d6d8653f595"), + "plugins/CoreHome/lang/fi.json" => array("4796", "a7e15e4375e604f9946fc35291662e6d"), + "plugins/CoreHome/lang/fr.json" => array("5676", "33af17617c9b4a1cbf4426ab2edc0d96"), + "plugins/CoreHome/lang/gl.json" => array("221", "41bd1519566cce99bfb3e515b522a173"), + "plugins/CoreHome/lang/he.json" => array("1496", "da109c3438e9293a76a670722be7223b"), + "plugins/CoreHome/lang/hi.json" => array("8530", "f60490868dc8f480e021ef9c25a3ca7b"), + "plugins/CoreHome/lang/hr.json" => array("77", "13403fada02c2532bb998ad2908dae6f"), + "plugins/CoreHome/lang/hu.json" => array("1051", "c68f876f0f31babb919b65af0214f574"), + "plugins/CoreHome/lang/id.json" => array("4417", "6b775dc045f8bfabdf259dfdf23adf39"), + "plugins/CoreHome/lang/is.json" => array("470", "13475a07031c4827df7589e999cd616c"), + "plugins/CoreHome/lang/it.json" => array("5657", "b187d975fa3cc18fd3729c65f3621447"), + "plugins/CoreHome/lang/ja.json" => array("6255", "eb8f82e4715425efb4fb7e4d0e12736f"), + "plugins/CoreHome/lang/ka.json" => array("1702", "d4bf8b0b44f9c2fee1a91ff1251d852b"), + "plugins/CoreHome/lang/ko.json" => array("5753", "d0524976ca4b0f9540d5a1475a0c72c6"), + "plugins/CoreHome/lang/lt.json" => array("943", "aba801491b521fd3cd8c1ca948912085"), + "plugins/CoreHome/lang/lv.json" => array("984", "bb98adcf3b3bc291ba2b759ab8da50d2"), + "plugins/CoreHome/lang/nb.json" => array("5220", "09dc0d12ea5440472ad78b16d2d1b680"), + "plugins/CoreHome/lang/nl.json" => array("4956", "9765a994044cab8b8cccc8757e54ee53"), + "plugins/CoreHome/lang/nn.json" => array("1685", "f18b8771fe07aba94b1dac9c30624506"), + "plugins/CoreHome/lang/pl.json" => array("4770", "9003828ba5f4a99922045eb85ac065f1"), + "plugins/CoreHome/lang/pt-br.json" => array("5678", "416fe9be52b13c71ba9b4244e92b3fa3"), + "plugins/CoreHome/lang/pt.json" => array("1320", "c9dcc227cb00378fb3917d5e8fcbd962"), + "plugins/CoreHome/lang/ro.json" => array("4835", "34849359279f194e821a88a9384dc0a5"), + "plugins/CoreHome/lang/ru.json" => array("7241", "771cc56580779142c0d22f1346a0ce23"), + "plugins/CoreHome/lang/sk.json" => array("5147", "63a77dda587e34285686aad8a48197d2"), + "plugins/CoreHome/lang/sl.json" => array("773", "0f99c25bfc9b8a8b38bc2650514ce2a4"), + "plugins/CoreHome/lang/sq.json" => array("1204", "a9304618931870ec83194b2a7769e3a5"), + "plugins/CoreHome/lang/sr.json" => array("4827", "a13be300970735f469212cc8165a0d47"), + "plugins/CoreHome/lang/sv.json" => array("5039", "a36238a658e29075469ee82605fdc1d0"), + "plugins/CoreHome/lang/ta.json" => array("5082", "570180a121f912fb39ab1831bef1e3e9"), + "plugins/CoreHome/lang/te.json" => array("119", "7e9c6ec6465a602b12c4a73bafe260c0"), + "plugins/CoreHome/lang/th.json" => array("1804", "d477d76043a7a60f33033511bb8af17d"), + "plugins/CoreHome/lang/tl.json" => array("5352", "76876f1dfad73bef25ea9fc11c6b7542"), + "plugins/CoreHome/lang/tr.json" => array("3020", "0c8171405b07db58acd3f4bc65ee1717"), + "plugins/CoreHome/lang/uk.json" => array("1009", "1882ad1616f47c7beb774837ad9c7f61"), + "plugins/CoreHome/lang/vi.json" => array("5383", "782ccd38a3f50d02e6582531e72d4e6f"), + "plugins/CoreHome/lang/zh-cn.json" => array("4212", "de2edff85024110f5047c0292f5bead0"), + "plugins/CoreHome/lang/zh-tw.json" => array("662", "18e17d93964d0a413301c728ea4cc378"), + "plugins/CoreHome/Menu.php" => array("2616", "5b7abf74077706521e5a6c08ec2d3215"), + "plugins/CoreHome/Segment.php" => array("367", "eb86574f0cbe1d33fc9f449bb3ca60b8"), + "plugins/CoreHome/stylesheets/cloud.less" => array("1138", "69db3085ff95dbeb0e3d6a01bc456e2e"), "plugins/CoreHome/stylesheets/color_manager.css" => array("42", "40709b59fb63c5e7fcdcab9230c1dbaa"), - "plugins/CoreHome/stylesheets/coreHome.less" => array("4274", "966a251617979d9103c999cb5a3c56e8"), - "plugins/CoreHome/stylesheets/dataTable/_dataTable.less" => array("10157", "7fe43c52bc99e776a9e2b7de3db419f4"), + "plugins/CoreHome/stylesheets/coreHome.less" => array("3735", "f2b04ba74b9f89c94a2c881cc5a285f9"), + "plugins/CoreHome/stylesheets/dataTable/_dataTable.less" => array("11088", "ec7e511e7c080893d250c76f46cd3f78"), "plugins/CoreHome/stylesheets/dataTable.less" => array("366", "b5a4df46b9d7f8e1193f54d3aad665c8"), - "plugins/CoreHome/stylesheets/dataTable/_limitSelection.less" => array("1306", "68d63775a50badf3b9ac1992997d1c9b"), - "plugins/CoreHome/stylesheets/dataTable/_reportDocumentation.less" => array("1182", "2423f8ebb34d4a3fd818d580b176ca45"), - "plugins/CoreHome/stylesheets/dataTable/_rowActions.less" => array("656", "afe520a206b30502fa9e4c3812efa605"), - "plugins/CoreHome/stylesheets/dataTable/_subDataTable.less" => array("860", "e5b7fb01c2a7682310d5e9121960d6fa"), - "plugins/CoreHome/stylesheets/dataTable/_tableConfiguration.less" => array("1499", "5c63c6d3ff0fc046514ee80f572158a4"), - "plugins/CoreHome/stylesheets/_donate.less" => array("2251", "8a37ac490210fd01f9439e821d2f5aeb"), - "plugins/CoreHome/stylesheets/jqplotColors.less" => array("2449", "cee067a8b15cf5a393fdde65c03e0d03"), - "plugins/CoreHome/stylesheets/jquery.ui.autocomplete.css" => array("1200", "518cf68de85e435653447e9102316661"), - "plugins/CoreHome/stylesheets/menu.less" => array("2935", "de31a81958b769f798adaea6673f6fe8"), - "plugins/CoreHome/stylesheets/notification.less" => array("1612", "a5400eef0a05d0159e5675726ef8e2f3"), - "plugins/CoreHome/stylesheets/promo.less" => array("1222", "10e9dd09f69cd77f2d2b35ae86a99b7b"), - "plugins/CoreHome/stylesheets/sparklineColors.less" => array("522", "a8fe19271fefc2f1829b9e271838467a"), + "plugins/CoreHome/stylesheets/dataTable/_limitSelection.less" => array("1338", "c01e49c501ff406bb8357e1c7ee43f26"), + "plugins/CoreHome/stylesheets/dataTable/_reportDocumentation.less" => array("834", "148c2212218a0d82f9a918bda774d07d"), + "plugins/CoreHome/stylesheets/dataTable/_rowActions.less" => array("695", "b9d3af9358b6057e37fb7e4f5cad92de"), + "plugins/CoreHome/stylesheets/dataTable/_subDataTable.less" => array("816", "3cffe0b93ee08fa806da2332247509b3"), + "plugins/CoreHome/stylesheets/dataTable/_tableConfiguration.less" => array("1541", "b1495a2d90bbc42114eee327a6a158c2"), + "plugins/CoreHome/stylesheets/_donate.less" => array("2321", "4acd667f756ddf920caa98bf75bd8a03"), + "plugins/CoreHome/stylesheets/jqplotColors.less" => array("2512", "657cae47275e71b93413ca816c4bbf40"), + "plugins/CoreHome/stylesheets/jquery.ui.autocomplete.css" => array("1198", "e41c776588399505e932173bc556ef41"), + "plugins/CoreHome/stylesheets/layout.less" => array("7925", "5fbefe86cb79ae42b6a2c998305c3248"), + "plugins/CoreHome/stylesheets/notification.less" => array("87", "f82d13a39e0a90c2096db92ee62360e7"), + "plugins/CoreHome/stylesheets/promo.less" => array("1268", "13b273cc4a64ddcd1e83c24c2043adb7"), + "plugins/CoreHome/stylesheets/sparklineColors.less" => array("362", "143089281239350f78d6c71554b27e19"), + "plugins/CoreHome/stylesheets/zen-mode.less" => array("1881", "da662bcd2fe479f1a49156ec4eae234b"), + "plugins/CoreHome/templates/_adblockDetect.twig" => array("1492", "97a0b6464c0125f845e7448e1cacc52d"), "plugins/CoreHome/templates/checkForUpdates.twig" => array("45", "15faa60f49f1c4c281c76bc2a7e00ce7"), - "plugins/CoreHome/templates/_dataTableCell.twig" => array("2957", "d6b282e985fee6e9f1b9524c3d5b8346"), - "plugins/CoreHome/templates/_dataTableFooter.twig" => array("8068", "cbe13cdda27009f397de576cbab1e37d"), - "plugins/CoreHome/templates/_dataTableHead.twig" => array("821", "f39052d9ca22525d016796aa27662900"), + "plugins/CoreHome/templates/_dataTableCell.twig" => array("3108", "3e260fcbeaf3d972ceea3bc31314bfea"), + "plugins/CoreHome/templates/_dataTableFooter.twig" => array("9531", "15dc15199d866cfa78238a9d525c0c1c"), + "plugins/CoreHome/templates/_dataTableHead.twig" => array("916", "0d42c2d5a9fef9cee1565b9ad51e378a"), "plugins/CoreHome/templates/_dataTableJS.twig" => array("159", "7233ccf9c682cdb1744b43ccb9ca84d7"), - "plugins/CoreHome/templates/_dataTable.twig" => array("2016", "0e4018ea821c7e713d8f62c7e1bd31f1"), - "plugins/CoreHome/templates/_donate.twig" => array("2986", "2dc12be6edbf01dcb96bca7caf65a9d2"), - "plugins/CoreHome/templates/getDefaultIndexView.twig" => array("291", "a63dbf0283c9695118f5403b9cea9c9e"), + "plugins/CoreHome/templates/_dataTable.twig" => array("2097", "217737499d8db855ad31c8a76e8ea6b1"), + "plugins/CoreHome/templates/_donate.twig" => array("2994", "4a54eea81fc0cf58387698ef4725233f"), + "plugins/CoreHome/templates/_favicon.twig" => array("225", "b594a67cb6fbe1d2c11ba6e50b29907f"), + "plugins/CoreHome/templates/getDefaultIndexView.twig" => array("595", "5b5b3d422da2380eb75c8ae20d88a077"), "plugins/CoreHome/templates/getDonateForm.twig" => array("38", "ceda9c8c6ba889dbac8b55ca7e61e030"), - "plugins/CoreHome/templates/getMultiRowEvolutionPopover.twig" => array("1907", "2777f9e84b4d06496a779b8581566038"), - "plugins/CoreHome/templates/getPromoVideo.twig" => array("1379", "f40ed16a053efaa6df2c682c3e620b41"), - "plugins/CoreHome/templates/getRowEvolutionPopover.twig" => array("1685", "d9be8e51169aef72e1e6acf3d93b1d82"), - "plugins/CoreHome/templates/_headerMessage.twig" => array("2915", "83a77be74cbcedac5eff74e80e33be49"), - "plugins/CoreHome/templates/_indexContent.twig" => array("567", "936cb35de43d98985a05715734b634d6"), + "plugins/CoreHome/templates/getMultiRowEvolutionPopover.twig" => array("1962", "3f60ff1e39b96e9d43e157c4379f8fe5"), + "plugins/CoreHome/templates/getPromoVideo.twig" => array("2095", "5ac46827a8e56dc2c7f3bec7456963f6"), + "plugins/CoreHome/templates/getRowEvolutionPopover.twig" => array("1720", "f6205c1f580886de1530ff8f7c82fc7c"), + "plugins/CoreHome/templates/_headerMessage.twig" => array("3072", "10e030efafea8422b09d16766406aebb"), "plugins/CoreHome/templates/_javaScriptDisabled.twig" => array("134", "9f7e7b75b3dd7928b0d7db27802cec9e"), - "plugins/CoreHome/templates/_logo.twig" => array("607", "d14a3ee156c537331868b5cfe52034b9"), - "plugins/CoreHome/templates/_menu.twig" => array("952", "499786b48fe52cdc248d4ecb45a7d7dd"), + "plugins/CoreHome/templates/_logo.twig" => array("635", "bccae163fc358e71bacf6a6e3a9e4111"), + "plugins/CoreHome/templates/_menu.twig" => array("3252", "2d7afd94246daa4134694d6050e87028"), "plugins/CoreHome/templates/_notifications.twig" => array("333", "974e20fdb4837324b61cbfb052d3b911"), - "plugins/CoreHome/templates/_periodSelect.twig" => array("1794", "71ebf4a07f218ccd3a6b8e71bd9ad9a4"), - "plugins/CoreHome/templates/ReportRenderer/_htmlReportBody.twig" => array("3773", "5e44bacceb55616bd659c7c6b7883fe6"), + "plugins/CoreHome/templates/_periodSelect.twig" => array("1891", "c7277ccbbd71f75f5117b96dd4ab1692"), + "plugins/CoreHome/templates/ReportRenderer/_htmlReportBody.twig" => array("4080", "427c3830fa306ff2ea73f33e03485cf6"), "plugins/CoreHome/templates/ReportRenderer/_htmlReportFooter.twig" => array("15", "4d214dd44eaca17a5b8c100a0cee7ebc"), - "plugins/CoreHome/templates/ReportRenderer/_htmlReportHeader.twig" => array("1165", "f16ab5bc3e57cb8df3a4d5c101f506b2"), - "plugins/CoreHome/templates/ReportsByDimension/_reportsByDimension.twig" => array("1112", "7c22d58e39b426bd649d88d1199aa749"), + "plugins/CoreHome/templates/ReportRenderer/_htmlReportHeader.twig" => array("1329", "ef0db4e6f6105bcf672247b554494cb1"), + "plugins/CoreHome/templates/ReportsByDimension/_reportsByDimension.twig" => array("1111", "11776a7aec0652095a3fec01f3bc8017"), "plugins/CoreHome/templates/_singleReport.twig" => array("61", "3ed4c965676469386ba1e6142f55f049"), - "plugins/CoreHome/templates/_siteSelectHeader.twig" => array("234", "9b4fa86af9fec5ea3755b4dde733efa6"), - "plugins/CoreHome/templates/_topBarHelloMenu.twig" => array("1027", "f7dfa6faaa6a6ccc28f5f00e16874a0b"), - "plugins/CoreHome/templates/_topBarTopMenu.twig" => array("763", "ab004040fabe91dff6bc8752ddbc8ef0"), - "plugins/CoreHome/templates/_topBar.twig" => array("200", "9987986288e7ce9072205fb329f9fc87"), - "plugins/CoreHome/templates/_topScreen.twig" => array("109", "6fae551319f6efff81bf460ebad78b66"), - "plugins/CoreHome/templates/_uiControl.twig" => array("327", "35b5eef8d6bb93196947469ab721d0d6"), - "plugins/CoreHome/templates/_warningInvalidHost.twig" => array("915", "ef59e3a2d2828b269f2e9dd41238602d"), - "plugins/CorePluginsAdmin/Controller.php" => array("17319", "a0cecaa6e6c468d9a598d2e5ded186b8"), - "plugins/CorePluginsAdmin/CorePluginsAdmin.php" => array("4817", "4b53a6ef27725c8d61723351162f3156"), + "plugins/CoreHome/templates/_siteSelectHeader.twig" => array("147", "7f9e97bdeaf71f23550395e9d392c14b"), + "plugins/CoreHome/templates/_topBar.twig" => array("1400", "90abae55f024bc017d1ea877fa970c34"), + "plugins/CoreHome/templates/_topScreen.twig" => array("628", "e82984958b46890ae74c03f92c194a7a"), + "plugins/CoreHome/templates/_uiControl.twig" => array("429", "52df099a1a443d1c7ddb64334090f177"), + "plugins/CoreHome/templates/_warningInvalidHost.twig" => array("741", "60be9da447924dd2cf2df0adbe4c79f1"), + "plugins/CoreHome/Tracker/VisitRequestProcessor.php" => array("7417", "e49a02d7c36f5f975c6c9394da8092e1"), + "plugins/CoreHome/Visitor.php" => array("2864", "b83a0a8b1cc891868f5df7ef7074db1d"), + "plugins/CoreHome/Widgets.php" => array("1616", "a8bd48e2fe3d532f50b7d6d578460be0"), + "plugins/CorePluginsAdmin/Commands/ActivatePlugin.php" => array("1224", "09778e7ef87200a365894206a953f35e"), + "plugins/CorePluginsAdmin/Commands/DeactivatePlugin.php" => array("1239", "7dd77a5a7e15147f6c2d1d84a6b42710"), + "plugins/CorePluginsAdmin/Commands/ListPlugins.php" => array("1558", "1065d522c50f6143b730a9634bb05e72"), + "plugins/CorePluginsAdmin/Controller.php" => array("18767", "45840909c1571a6d0f20550ad64c084a"), + "plugins/CorePluginsAdmin/CorePluginsAdmin.php" => array("1918", "7aa8205c3b97c67f63665a3799949ef4"), + "plugins/CorePluginsAdmin/images/flattr.png" => array("1639", "fb7338392a7e06ed64c534f69f0c01f5"), + "plugins/CorePluginsAdmin/images/paypal_donate.jpg" => array("3665", "b5c0a835ae76a566b81ddacf370cf1e6"), "plugins/CorePluginsAdmin/images/plugins.png" => array("14076", "44b89bd30206dd317c032a8595e1ddeb"), "plugins/CorePluginsAdmin/images/rating_important.png" => array("673", "0e63393e13ce89a4920684b4b06f68ee"), "plugins/CorePluginsAdmin/images/themes.png" => array("79517", "67eb6beb264c9181c79246fc6f255c04"), - "plugins/CorePluginsAdmin/javascripts/pluginDetail.js" => array("2270", "5122b5212d9bd47b215b8899fcc448a2"), - "plugins/CorePluginsAdmin/javascripts/pluginExtend.js" => array("756", "4177e6ed911cce4e0c2fdf257f0b538f"), - "plugins/CorePluginsAdmin/javascripts/pluginOverview.js" => array("900", "a7221dc1b624beb54936fce19faf8fcf"), - "plugins/CorePluginsAdmin/javascripts/plugins.js" => array("2974", "e9b57f6e7a1fe31bf46672c7af2001a7"), - "plugins/CorePluginsAdmin/MarketplaceApiClient.php" => array("5380", "32b4ee5cc03f93a5cb0b437023025628"), - "plugins/CorePluginsAdmin/MarketplaceApiException.php" => array("258", "504b17a7c04736eac83d9d532c4666d9"), - "plugins/CorePluginsAdmin/Marketplace.php" => array("5717", "9ee5d691a3e9b28404b7caddfafe6fb3"), - "plugins/CorePluginsAdmin/PluginInstallerException.php" => array("286", "6f252dd00be63fc7c60035c042bfa17e"), - "plugins/CorePluginsAdmin/PluginInstaller.php" => array("9509", "6b462a3b03902bcfeeebab6491e6cfca"), - "plugins/CorePluginsAdmin/stylesheets/marketplace.less" => array("5941", "0d9e782e0476bed3714d79572289e3fb"), - "plugins/CorePluginsAdmin/stylesheets/plugins_admin.less" => array("910", "c3f90c42b657a52f81e4de1f774acfad"), - "plugins/CorePluginsAdmin/templates/browsePluginsActions.twig" => array("787", "ec50c06b24b52ac8a77e0473f35ecdf8"), - "plugins/CorePluginsAdmin/templates/browsePlugins.twig" => array("1569", "807f2a4521e8c2fa60ecb096c7733aae"), - "plugins/CorePluginsAdmin/templates/browseThemes.twig" => array("1355", "ace812045eef7acf7f999493badb3154"), - "plugins/CorePluginsAdmin/templates/extend.twig" => array("3852", "d29aebabcbd868d40e6e13703436d65e"), + "plugins/CorePluginsAdmin/javascripts/marketplace.js" => array("1404", "f418feb6ec58c63ada7c39f6dca0040c"), + "plugins/CorePluginsAdmin/javascripts/pluginExtend.js" => array("756", "c49fe7ecac64d68d909e7e9041ce598d"), + "plugins/CorePluginsAdmin/javascripts/pluginOverview.js" => array("1120", "23045bfed482808699ea17e472f97da7"), + "plugins/CorePluginsAdmin/javascripts/plugins.js" => array("2990", "f2a5b12bfda2a36b9d6ddfae16f09855"), + "plugins/CorePluginsAdmin/lang/am.json" => array("499", "12a0c8a8165a869f7111a60db060f75b"), + "plugins/CorePluginsAdmin/lang/ar.json" => array("2658", "6e0b9c11e816ef4350404b7914f49249"), + "plugins/CorePluginsAdmin/lang/be.json" => array("1005", "fc5908655de0f7c4c6c84f20ff4df850"), + "plugins/CorePluginsAdmin/lang/bg.json" => array("9517", "0fa07c938f75d1270dc3e0c704272ab8"), + "plugins/CorePluginsAdmin/lang/bn.json" => array("476", "620aef2bb387f8c701bb6d7868122f73"), + "plugins/CorePluginsAdmin/lang/bs.json" => array("443", "bee3f0b817f2aad1931acc6bfb2c85e5"), + "plugins/CorePluginsAdmin/lang/ca.json" => array("836", "945e4cb44f518e406f41b89beabdd540"), + "plugins/CorePluginsAdmin/lang/cs.json" => array("7688", "e7122f70d031104ee40a7288866247f4"), + "plugins/CorePluginsAdmin/lang/da.json" => array("7092", "cdb3281ee13587df15e694eee320886a"), + "plugins/CorePluginsAdmin/lang/de.json" => array("8000", "1728e735fbf5604f40b74ac7d4a3c401"), + "plugins/CorePluginsAdmin/lang/el.json" => array("11648", "7d458aed281cdec3f75ca658bf800b0d"), + "plugins/CorePluginsAdmin/lang/en.json" => array("7180", "8b9c4d55a411972a1803c44be1d99b3a"), + "plugins/CorePluginsAdmin/lang/es.json" => array("7573", "acf0eac9735cf05f1c725ad1522bbd8c"), + "plugins/CorePluginsAdmin/lang/et.json" => array("3216", "fbc78a4a5a8b9c9a4595583a47bbda8b"), + "plugins/CorePluginsAdmin/lang/eu.json" => array("648", "8436cfb268e81f029a5289275e526a1d"), + "plugins/CorePluginsAdmin/lang/fa.json" => array("7243", "fcdf7a5a392068f1181391d5a29e961a"), + "plugins/CorePluginsAdmin/lang/fi.json" => array("6443", "bf14ea64b8f32f53c4cef907d6d57f3c"), + "plugins/CorePluginsAdmin/lang/fr.json" => array("8222", "c50941d9f081b57bf2fd2233ba5d7612"), + "plugins/CorePluginsAdmin/lang/gl.json" => array("208", "6587713e133353087ffd81f1cf93f2ab"), + "plugins/CorePluginsAdmin/lang/he.json" => array("1136", "a4539f91699eb37baf33cc4fb6598c5b"), + "plugins/CorePluginsAdmin/lang/hi.json" => array("3538", "54a0746fffe74e6c95ab1ab65b91086d"), + "plugins/CorePluginsAdmin/lang/hr.json" => array("73", "63950bcb79c3f6dfad126e41c3cf50c2"), + "plugins/CorePluginsAdmin/lang/hu.json" => array("748", "95706052d5e33ff0422610f77fde7e52"), + "plugins/CorePluginsAdmin/lang/id.json" => array("766", "5bb5f712a14835a5c12b7e589f34cdbe"), + "plugins/CorePluginsAdmin/lang/is.json" => array("751", "6dfa32bd9af98bf4ef25b24a68011702"), + "plugins/CorePluginsAdmin/lang/it.json" => array("7492", "3bf1093c0ff91caac94b31c1f6d216a5"), + "plugins/CorePluginsAdmin/lang/ja.json" => array("9400", "8f4c1c9e20367926501f857981835f43"), + "plugins/CorePluginsAdmin/lang/ka.json" => array("1317", "04089b7cd323407bbeff1d38f312904c"), + "plugins/CorePluginsAdmin/lang/ko.json" => array("3409", "e94371c779d770bb427b961e31462984"), + "plugins/CorePluginsAdmin/lang/lt.json" => array("1619", "e4ca6e641d91026d44757231c3eae185"), + "plugins/CorePluginsAdmin/lang/lv.json" => array("740", "56cb390c2a35aa5b6add7a31eeb6bcb0"), + "plugins/CorePluginsAdmin/lang/nb.json" => array("7329", "931c24c1178c12fc8ddad4408d68f4d6"), + "plugins/CorePluginsAdmin/lang/nl.json" => array("7507", "d56e3f5cda1f15da10bbed78503d0269"), + "plugins/CorePluginsAdmin/lang/nn.json" => array("725", "e64e34604eb01e2941c33d15322fbdc6"), + "plugins/CorePluginsAdmin/lang/pl.json" => array("6434", "53c69b88156b56bc4e1be49a5ce1ab2d"), + "plugins/CorePluginsAdmin/lang/pt-br.json" => array("7673", "81da108103a4408c5f647c3c26e6a6f3"), + "plugins/CorePluginsAdmin/lang/pt.json" => array("1378", "e1c6555c2761c380062cb7301648da13"), + "plugins/CorePluginsAdmin/lang/ro.json" => array("6594", "e3453af55b4794f6c0f5ec091ce8a592"), + "plugins/CorePluginsAdmin/lang/ru.json" => array("8850", "c274ece9186441776b351da0213a34fe"), + "plugins/CorePluginsAdmin/lang/sk.json" => array("7371", "9c6ed20972cd46293cd59ef80a7bb1cc"), + "plugins/CorePluginsAdmin/lang/sl.json" => array("876", "52dc63465419116f03f75a410d968840"), + "plugins/CorePluginsAdmin/lang/sq.json" => array("871", "c3454a7733b6793cf7fa76000ead438f"), + "plugins/CorePluginsAdmin/lang/sr.json" => array("7420", "8b36cf2c9a9d70de7840320e6bcd2632"), + "plugins/CorePluginsAdmin/lang/sv.json" => array("6942", "09a6a211f1fbe535be0257b43a7fba10"), + "plugins/CorePluginsAdmin/lang/ta.json" => array("1687", "ef761f8935fcb0fb1ca7b954dbf2745b"), + "plugins/CorePluginsAdmin/lang/te.json" => array("534", "d305e9eebafc2b667beeae8d5dfcb826"), + "plugins/CorePluginsAdmin/lang/th.json" => array("1267", "acba15bde3aa64c43d9db07a1ce6bf27"), + "plugins/CorePluginsAdmin/lang/tl.json" => array("7010", "ab9ba75f1c287556c0fe4fdc8a679f81"), + "plugins/CorePluginsAdmin/lang/tr.json" => array("3600", "2cac28e90dec341442f8a4175b380c87"), + "plugins/CorePluginsAdmin/lang/uk.json" => array("996", "a45577adf7df3d1f32c3c119cf150113"), + "plugins/CorePluginsAdmin/lang/vi.json" => array("4373", "4fed138df3afafd869dc30f92c71727f"), + "plugins/CorePluginsAdmin/lang/zh-cn.json" => array("3574", "79f84724e55fe172d02da5462f024c29"), + "plugins/CorePluginsAdmin/lang/zh-tw.json" => array("614", "f168f8aff2b1d30e8e7af5b3a108aa3a"), + "plugins/CorePluginsAdmin/MarketplaceApiClient.php" => array("5024", "8720371679b50aed3504f932e98f70c3"), + "plugins/CorePluginsAdmin/MarketplaceApiException.php" => array("262", "e13982ce6c99a1cf4bb894c4b10dfe5a"), + "plugins/CorePluginsAdmin/Marketplace.php" => array("5842", "fcb81c755318ef8c58db988ffe37a108"), + "plugins/CorePluginsAdmin/Menu.php" => array("2401", "cee29267b22c5aef7211d7c570753995"), + "plugins/CorePluginsAdmin/PluginInstallerException.php" => array("290", "a4cea1cae2ebced279c90ff6f22a09f5"), + "plugins/CorePluginsAdmin/PluginInstaller.php" => array("9847", "30ef7acb085deaeb16b9ca32331f696e"), + "plugins/CorePluginsAdmin/stylesheets/marketplace.less" => array("2037", "1ae8d80fa80df6d1eeba8ada1a770666"), + "plugins/CorePluginsAdmin/stylesheets/plugin-details.less" => array("1838", "224bf6cbd8edcce54e9427d1f9c88f9a"), + "plugins/CorePluginsAdmin/stylesheets/plugins_admin.less" => array("2671", "7a1408a5a50f1d166d1a2e0539626f43"), + "plugins/CorePluginsAdmin/Tasks.php" => array("933", "a2b562d71118f42cd1f53849318d216a"), "plugins/CorePluginsAdmin/templates/installPlugin.twig" => array("1593", "678e6e40b503dfce996bd1638140dae2"), - "plugins/CorePluginsAdmin/templates/macros.twig" => array("13807", "c53678062e9729c889b4f0c964e3eed6"), - "plugins/CorePluginsAdmin/templates/pluginDetails.twig" => array("10614", "425ca997711e3a742a8c05b9a7547a40"), - "plugins/CorePluginsAdmin/templates/pluginMetadata.twig" => array("579", "573e9a85cc47e6581d4ea2c27e21f271"), - "plugins/CorePluginsAdmin/templates/pluginOverview.twig" => array("1373", "f63e81873d0894f1fe510860e0bb72ce"), - "plugins/CorePluginsAdmin/templates/plugins.twig" => array("985", "614227399bb790846563585476ab7293"), - "plugins/CorePluginsAdmin/templates/safemode.twig" => array("4851", "b7bc45d170dd3d6906251f8f66c24fd4"), - "plugins/CorePluginsAdmin/templates/themeOverview.twig" => array("1432", "7a7fe7a864cacf63b83afcb9ad1ae51a"), - "plugins/CorePluginsAdmin/templates/themes.twig" => array("1165", "ab2cb33c411d1b72aae3b2810982e603"), - "plugins/CorePluginsAdmin/templates/updatePlugin.twig" => array("1431", "509baa9e82b722e0518e31f45c85678c"), - "plugins/CorePluginsAdmin/templates/uploadPlugin.twig" => array("1640", "f61cd131dda39221cf364b82f03d727e"), - "plugins/CorePluginsAdmin/UpdateCommunication.php" => array("6203", "cd40906be1bc9082e7ae01ebe55b4434"), - "plugins/CoreUpdater/Commands/Update.php" => array("2038", "f713523751a5850ea31df6068d813a4f"), - "plugins/CoreUpdater/Controller.php" => array("15315", "baa77e239df7fe6679394684ef0db52f"), - "plugins/CoreUpdater/CoreUpdater.php" => array("5380", "7e2b3bc913e03712d30aef6eecf7af00"), - "plugins/CoreUpdater/javascripts/updateLayout.js" => array("246", "97c81a1a8c66bb5ddb6567591c623387"), - "plugins/CoreUpdater/NoUpdatesFoundException.php" => array("243", "4c4002fd3bdb636949915467b95d9607"), - "plugins/CoreUpdater/stylesheets/updateLayout.css" => array("564", "9fd4b49281584e2af9d964fd9e8a754a"), - "plugins/CoreUpdater/templates/layout.twig" => array("1774", "4764b278f8e7d90fdab9523feef448a1"), - "plugins/CoreUpdater/templates/newVersionAvailable.twig" => array("1667", "dc764e7c76232bca2fac9626e7127b6c"), - "plugins/CoreUpdater/templates/oneClickResults.twig" => array("768", "fd13410beecb2907de3e0121379fc0a2"), - "plugins/CoreUpdater/templates/runUpdaterAndExit_done_cli.twig" => array("1632", "34eaa2c5de8a88c4b320e5f8d4c0df54"), - "plugins/CoreUpdater/templates/runUpdaterAndExit_done.twig" => array("3017", "e28b48da5e46a69f714e03d6e0ad845b"), - "plugins/CoreUpdater/templates/runUpdaterAndExit_welcome_cli.twig" => array("1329", "b84d36f1cac2070b4f82efc39955e8da"), - "plugins/CoreUpdater/templates/runUpdaterAndExit_welcome.twig" => array("4394", "ec2fafcabb5b9ff34f63873f710dc259"), - "plugins/CoreUpdater/UpdateCommunication.php" => array("4260", "44ea4f46e1a34360f2b8d2b1779d1bf0"), - "plugins/CoreVisualizations/CoreVisualizations.php" => array("3098", "59fc60592977b80fecb9a22cb789a339"), - "plugins/CoreVisualizations/javascripts/jqplotBarGraph.js" => array("2624", "8663e49a42d16fcd7faaf628b88c2cbc"), - "plugins/CoreVisualizations/javascripts/jqplotEvolutionGraph.js" => array("4990", "0555513c693cdcc529d45b82044646c0"), - "plugins/CoreVisualizations/javascripts/jqplot.js" => array("37901", "e06b89f2c320761abc576e24f0d73057"), - "plugins/CoreVisualizations/javascripts/jqplotPieGraph.js" => array("2675", "f51532d8a5c7ff32da50b032c31c5584"), - "plugins/CoreVisualizations/javascripts/seriesPicker.js" => array("13503", "834304040b3260bb6f708cc5f43d7547"), - "plugins/CoreVisualizations/JqplotDataGenerator/Chart.php" => array("3191", "b7b441685a7b019bb9c29b8478be0538"), - "plugins/CoreVisualizations/JqplotDataGenerator/Evolution.php" => array("7403", "be2979d456835753937f5c10890270cc"), - "plugins/CoreVisualizations/JqplotDataGenerator.php" => array("4865", "b53f8d32bfe0d79da637d502863170ce"), + "plugins/CorePluginsAdmin/templates/macros.twig" => array("16619", "c131ee6f87823578c10fd800f51788b1"), + "plugins/CorePluginsAdmin/templates/marketplace/plugin-list.twig" => array("5556", "28e08f5105c42017b91b177ace5ddd7d"), + "plugins/CorePluginsAdmin/templates/marketplace.twig" => array("3349", "232afba27c1990540b32c36dec5577f2"), + "plugins/CorePluginsAdmin/templates/pluginDetails.twig" => array("10671", "d206f0cef3431f31431574c21fd018ad"), + "plugins/CorePluginsAdmin/templates/plugins.twig" => array("1308", "0ae428eefaca56d4a1b788e54a54b9c3"), + "plugins/CorePluginsAdmin/templates/safemode.twig" => array("5622", "91508f42dc492fde60ab2ac7e0ec7ff3"), + "plugins/CorePluginsAdmin/templates/themes.twig" => array("886", "57a10742139790b8236c6930480f996b"), + "plugins/CorePluginsAdmin/templates/updatePlugin.twig" => array("1436", "8dd906fac836c0eaa02be2441c835c2c"), + "plugins/CorePluginsAdmin/templates/uploadPlugin.twig" => array("1650", "2896e819b4881ee1a3eb619e7ca41a40"), + "plugins/CorePluginsAdmin/UpdateCommunication.php" => array("6473", "9dc7e7b5276fc01905540ad2ea4b6b1d"), + "plugins/CoreUpdater/ArchiveDownloadException.php" => array("432", "e058e85ee92b93c785f3dd694802ee33"), + "plugins/CoreUpdater/Commands/Update/CliUpdateObserver.php" => array("1407", "4c98536cf0fc75964450a2813961ac6b"), + "plugins/CoreUpdater/Commands/Update.php" => array("11085", "8a96b9f6b0637e94137c0a8f543f1d35"), + "plugins/CoreUpdater/config/config.php" => array("268", "61cba55ae65f82e007089ab59a9eca4d"), + "plugins/CoreUpdater/Controller.php" => array("9586", "9da445672bdbc93c34ca9a863f73af9a"), + "plugins/CoreUpdater/CoreUpdater.php" => array("2939", "dbeb0b227eb3575ac1c3d711fd9d9f34"), + "plugins/CoreUpdater/Diagnostic/HttpsUpdateCheck.php" => array("1160", "8b3a8ece0aa3c1de6ed4937a792d9425"), + "plugins/CoreUpdater/javascripts/updateLayout.js" => array("353", "560f94921854694ec2b4815d633fab91"), + "plugins/CoreUpdater/lang/am.json" => array("3461", "54998106a099fb3400ac41c8f46d7a43"), + "plugins/CoreUpdater/lang/ar.json" => array("6517", "748097937cba1ece478e32c5ddc66353"), + "plugins/CoreUpdater/lang/be.json" => array("7369", "5506557524a8a78e1b2764328002ff18"), + "plugins/CoreUpdater/lang/bg.json" => array("8817", "b78358b2e6f41eaa29b35c5d1ce7e65f"), + "plugins/CoreUpdater/lang/bs.json" => array("183", "acb5d6399859005cef98196a000341e1"), + "plugins/CoreUpdater/lang/ca.json" => array("5682", "64d08d26f478d2089b9ac7ae8ba817ba"), + "plugins/CoreUpdater/lang/cs.json" => array("8303", "c6cfd3cb43b331fc6397c1561f8e6e66"), + "plugins/CoreUpdater/lang/da.json" => array("6367", "5db0570e452e040c13b231c1465de2ee"), + "plugins/CoreUpdater/lang/de.json" => array("9090", "dc25269aa1675e84d8c18dfd185c419b"), + "plugins/CoreUpdater/lang/el.json" => array("13209", "944c7211de34785a16ddd8216aba1ddf"), + "plugins/CoreUpdater/lang/en.json" => array("7890", "fad3ac9d3363b3272d7a2b0a42712bc5"), + "plugins/CoreUpdater/lang/es.json" => array("8510", "786957e3854c5d55112e63369085d422"), + "plugins/CoreUpdater/lang/et.json" => array("3582", "37dc8785e22c74da692ebfea2cdd8063"), + "plugins/CoreUpdater/lang/eu.json" => array("4164", "5e3f2e1c443a953661e5a1daa99c891e"), + "plugins/CoreUpdater/lang/fa.json" => array("6911", "cf7a786a6d3f310d1df16e3044b9388f"), + "plugins/CoreUpdater/lang/fi.json" => array("6042", "d81e088f4e22d950c2bc2c603df30001"), + "plugins/CoreUpdater/lang/fr.json" => array("9143", "4e4fcb98feb108a399651a3e237ce9c7"), + "plugins/CoreUpdater/lang/gl.json" => array("1212", "ec59865095074e3e1df5b9adecaf5d8a"), + "plugins/CoreUpdater/lang/he.json" => array("5984", "0724d54f12ba1296c39ebbf3bdfbd3ea"), + "plugins/CoreUpdater/lang/hi.json" => array("9965", "1abba4b57b119d097bdc40871a6598ca"), + "plugins/CoreUpdater/lang/hu.json" => array("5792", "e9904bfa957290c49e5cf77874b4056c"), + "plugins/CoreUpdater/lang/id.json" => array("5225", "4dadf172be01b18c59d82673fdc74285"), + "plugins/CoreUpdater/lang/it.json" => array("8529", "960870cb791a4859adea8c2afd86f6c7"), + "plugins/CoreUpdater/lang/ja.json" => array("10464", "2b246aebf290ff7828fd5f6b40813183"), + "plugins/CoreUpdater/lang/ka.json" => array("10935", "f8fd3135fa081b9b743079d49573db05"), + "plugins/CoreUpdater/lang/ko.json" => array("6325", "5c3b3060ade6be5f1b129fe9bbf325c2"), + "plugins/CoreUpdater/lang/lt.json" => array("5641", "66e3b776b66e513c30a7da3db2c3984e"), + "plugins/CoreUpdater/lang/lv.json" => array("2202", "154ae076e24d47bd76d277534467c39c"), + "plugins/CoreUpdater/lang/nb.json" => array("8048", "5f54f105a8f78abcb6111c5e0c732bde"), + "plugins/CoreUpdater/lang/nl.json" => array("8026", "d094ce6d4b6c67d362045de03dcebd99"), + "plugins/CoreUpdater/lang/nn.json" => array("4522", "ce3fb0b72866dc7a3905ce905df31405"), + "plugins/CoreUpdater/lang/pl.json" => array("7290", "15d058a653705e2bfd943b9224afe6a9"), + "plugins/CoreUpdater/lang/pt-br.json" => array("8560", "0f39f16a99a1f4cf89f2ca9faf7e7614"), + "plugins/CoreUpdater/lang/pt.json" => array("5279", "21d63381328d231410cb635379e9501a"), + "plugins/CoreUpdater/lang/ro.json" => array("6434", "3ef43832f4711d1fe2b01f2f945b22e9"), + "plugins/CoreUpdater/lang/ru.json" => array("10699", "e670bf2b25c8c0ea17a6439df242109a"), + "plugins/CoreUpdater/lang/sk.json" => array("8249", "28eaafb4f6f3115fead6d3fee6cda4ec"), + "plugins/CoreUpdater/lang/sl.json" => array("2869", "ec94d35365fcce2b3a59b6be568619d2"), + "plugins/CoreUpdater/lang/sq.json" => array("5723", "8a81437c6736f2016077c301c0f4f4f3"), + "plugins/CoreUpdater/lang/sr.json" => array("7798", "f346b121536858df01201468e83fd251"), + "plugins/CoreUpdater/lang/sv.json" => array("7671", "cb2c1fe0baa814e39530881741d1918c"), + "plugins/CoreUpdater/lang/ta.json" => array("6546", "2d73edde54a31c4cb2df3d933d22e7b9"), + "plugins/CoreUpdater/lang/te.json" => array("397", "8b250115eb5dedd9f177dee41ec1a2b2"), + "plugins/CoreUpdater/lang/th.json" => array("9961", "e484397f272990b4b5fc384b382bf7a7"), + "plugins/CoreUpdater/lang/tl.json" => array("6729", "92c4326d5e05635e8589625a4d0e6af4"), + "plugins/CoreUpdater/lang/tr.json" => array("3627", "906f6c753b09bc87442875f484705354"), + "plugins/CoreUpdater/lang/uk.json" => array("7676", "d39bcdaa2691e464b928e0a8ddfcf8df"), + "plugins/CoreUpdater/lang/vi.json" => array("6364", "f1d916d30a6e4545b9b402ff603e553b"), + "plugins/CoreUpdater/lang/zh-cn.json" => array("4978", "60f51b7f9b1fd81057bbc2888c5cfd18"), + "plugins/CoreUpdater/lang/zh-tw.json" => array("7116", "b3d7e4561a7b7ae2d902ac715a3c3051"), + "plugins/CoreUpdater/Model.php" => array("942", "b0d3eadaa1ff0462a038931aeee813f3"), + "plugins/CoreUpdater/NoUpdatesFoundException.php" => array("247", "0d4c19405628a88e41b16beb3df81e24"), + "plugins/CoreUpdater/ReleaseChannel/Latest2XBeta.php" => array("681", "0e0a1615981a95636a15f970f3771c76"), + "plugins/CoreUpdater/ReleaseChannel/Latest2XStable.php" => array("687", "cb5de6ba03ceac467bcfd339be147060"), + "plugins/CoreUpdater/ReleaseChannel/LatestBeta.php" => array("558", "2225f760a6bae4d7e7a1a7789f6f82ac"), + "plugins/CoreUpdater/ReleaseChannel/LatestStable.php" => array("789", "367c4cc9f0e7a5b5a573d447c6d996ff"), + "plugins/CoreUpdater/ReleaseChannel.php" => array("1252", "0c03f5bc18c7680b61ff1d251554ad05"), + "plugins/CoreUpdater/stylesheets/updateLayout.css" => array("452", "b82835729641caf404972ae9df52d2ff"), + "plugins/CoreUpdater/Tasks.php" => array("637", "efb8c30e6fd18f92575e09e1d1faf290"), + "plugins/CoreUpdater/templates/layout.twig" => array("3158", "f979c43becd2eb3049977a70571a211d"), + "plugins/CoreUpdater/templates/newVersionAvailable.twig" => array("1995", "42d4faa7b7d229a8420ad13152cb3844"), + "plugins/CoreUpdater/templates/runUpdaterAndExit_done.twig" => array("3229", "c4d6bafdec81c459fe0b26411e3c8b36"), + "plugins/CoreUpdater/templates/runUpdaterAndExit_welcome.twig" => array("4960", "2678c737bd4c4846b5a1b18520f8e244"), + "plugins/CoreUpdater/templates/updateHttpError.twig" => array("819", "93603a7c8a11a82915748f099712e709"), + "plugins/CoreUpdater/templates/updateHttpsError.twig" => array("1652", "8d51be10531c815d0e10423aa0c3b86d"), + "plugins/CoreUpdater/templates/updateSuccess.twig" => array("1189", "aa7173080f7104b14cc8bd6cc353fc9e"), + "plugins/CoreUpdater/Test/Fixtures/DbUpdaterTestFixture.php" => array("591", "5c88368b42db58530a6123b76c27a3b8"), + "plugins/CoreUpdater/Test/Fixtures/FailUpdateHttpsFixture.php" => array("610", "3194abee5fc46adb951b414883b141f5"), + "plugins/CoreUpdater/Test/Integration/Commands/UpdateTest.php" => array("4203", "ef727902af4e09046e794b17a9f797b6"), + "plugins/CoreUpdater/Test/Integration/ReleaseChannelTest.php" => array("1666", "239e01132daeb2851ba0276398b954dc"), + "plugins/CoreUpdater/Test/Integration/UpdateCommunicationTest.php" => array("4756", "aa7267288f869f704627e3c8ab9fe817"), + "plugins/CoreUpdater/Test/Mock/UpdaterMock.php" => array("1711", "0991a9def66d21bd9e38b0e0f2f129f4"), + "plugins/CoreUpdater/Test/Unit/ModelTest.php" => array("1726", "8c9ad2c04e8d53edd59c9d620a3a0e75"), + "plugins/CoreUpdater/UpdateCommunication.php" => array("4852", "8f1b6fb1bd7648d288769fb5ed604f95"), + "plugins/CoreUpdater/UpdaterException.php" => array("746", "67b0f028ed004af1d6b8a59933324d6b"), + "plugins/CoreUpdater/Updater.php" => array("8898", "4db3e790463cec29dfdbca8d2a9b41f3"), + "plugins/CoreVisualizations/CoreVisualizations.php" => array("2950", "ad077d12621c27a2f734f87f500dd367"), + "plugins/CoreVisualizations/javascripts/jqplotBarGraph.js" => array("2670", "a5e8cf96fca8dff30a52eccaa50349c9"), + "plugins/CoreVisualizations/javascripts/jqplotEvolutionGraph.js" => array("6646", "e3c3dd7d26c3077bf01fe9e84683cad6"), + "plugins/CoreVisualizations/javascripts/jqplot.js" => array("42238", "b95bd13c9dcc691f2d705f8bcbdf0dd4"), + "plugins/CoreVisualizations/javascripts/jqplotPieGraph.js" => array("2721", "2f141f5a36b394da5b73e558364aeda3"), + "plugins/CoreVisualizations/javascripts/seriesPicker.js" => array("13719", "41e8456559b6426c34f55b0ead63d1eb"), + "plugins/CoreVisualizations/JqplotDataGenerator/Chart.php" => array("3239", "e7ea8f4f1a78f92ab2be703aa56d2898"), + "plugins/CoreVisualizations/JqplotDataGenerator/Evolution.php" => array("7390", "6cf8c16449a503df36d46475d1782871"), + "plugins/CoreVisualizations/JqplotDataGenerator.php" => array("4937", "8182bd4878f524f8d70e9c82136ab6a0"), + "plugins/CoreVisualizations/Metrics/Formatter/Numeric.php" => array("1247", "541b2d4cb515a5a0ded54171d6bc88c5"), "plugins/CoreVisualizations/stylesheets/dataTableVisualizations.less" => array("537", "d54bd4990eb168a209dea17adfccc1df"), - "plugins/CoreVisualizations/stylesheets/jqplot.css" => array("4847", "03cf5f3e336655a1b5e43b7d7d35bc3a"), - "plugins/CoreVisualizations/templates/_dataTableViz_htmlTable.twig" => array("2641", "e28eee7fdc78defe23ba01c368f153b4"), - "plugins/CoreVisualizations/templates/_dataTableViz_jqplotGraph.twig" => array("149", "ee20fc44c0c435c5dc4b876a42b5632d"), - "plugins/CoreVisualizations/templates/_dataTableViz_tagCloud.twig" => array("839", "b01f096bf415c6b90b405e5be27938dd"), - "plugins/CoreVisualizations/Visualizations/Cloud/Config.php" => array("848", "096f5191662d35f16818ec8262976315"), - "plugins/CoreVisualizations/Visualizations/Cloud.php" => array("5071", "c7e1b761725380954a056fd1b523a0aa"), - "plugins/CoreVisualizations/Visualizations/Graph/Config.php" => array("3359", "44e5bfb03fdc0a72f58f8a50e34f6e15"), - "plugins/CoreVisualizations/Visualizations/Graph.php" => array("5311", "f72e91bb0e8c6a7dd7870a330f51b028"), - "plugins/CoreVisualizations/Visualizations/HtmlTable/AllColumns.php" => array("2206", "891d8f79c7fa4509dbe51fd21d87759d"), - "plugins/CoreVisualizations/Visualizations/HtmlTable/Config.php" => array("3363", "22c395e75a04d580abd824cba212d43d"), - "plugins/CoreVisualizations/Visualizations/HtmlTable.php" => array("2170", "4281101774e7592f03d22abb332e760d"), - "plugins/CoreVisualizations/Visualizations/HtmlTable/RequestConfig.php" => array("1559", "1fa011dffcbb16625833555774fec70e"), - "plugins/CoreVisualizations/Visualizations/JqplotGraph/Bar.php" => array("1054", "d94682d59ff312d8cb3632b5f1bb34a0"), - "plugins/CoreVisualizations/Visualizations/JqplotGraph/Config.php" => array("1808", "c070eb1e52624df57e477ef395a1d8b8"), - "plugins/CoreVisualizations/Visualizations/JqplotGraph/Evolution/Config.php" => array("1146", "6d32a05c97c3ed61fcf07a4934772099"), - "plugins/CoreVisualizations/Visualizations/JqplotGraph/Evolution.php" => array("6285", "273e819c224b072f98acbd38db00dcd2"), - "plugins/CoreVisualizations/Visualizations/JqplotGraph.php" => array("1415", "802e838dd17eb03d047bf80cdf75507a"), - "plugins/CoreVisualizations/Visualizations/JqplotGraph/Pie.php" => array("1526", "a63ef246318b7af4a3f930b8ba9e1f95"), - "plugins/CoreVisualizations/Visualizations/Sparkline.php" => array("3542", "4901c3281e7997d351f27bcd868aabc9"), - "plugins/CustomVariables/API.php" => array("3729", "a14f0b41fa2c9d429f4bcb1b79604a89"), - "plugins/CustomVariables/Archiver.php" => array("8023", "67584cad7e95fcea09249f9a98d79dae"), - "plugins/CustomVariables/Commands/Info.php" => array("2337", "7ad2373927c02fc97c9dd0b718918b43"), - "plugins/CustomVariables/Commands/SetNumberOfCustomVariables.php" => array("7048", "7988044609e3aec8acb12b5adb9add94"), - "plugins/CustomVariables/Controller.php" => array("734", "4660b5e7cda48db92afab520d689c7bc"), - "plugins/CustomVariables/CustomVariables.php" => array("8766", "c0dfd6ad3cef8186ca6512524c71a604"), - "plugins/CustomVariables/Model.php" => array("4890", "ab27c1ca66ad3015d05f5fcda3ef660f"), - "plugins/Dashboard/API.php" => array("3932", "d87fdaf7673503e6014957c961d16189"), - "plugins/Dashboard/Controller.php" => array("10134", "f867dfa6d4e7c74293bfb9e44abf247b"), - "plugins/Dashboard/DashboardManagerControl.php" => array("1610", "fb1b28942d7990b8bbe90406cf800830"), - "plugins/Dashboard/Dashboard.php" => array("9928", "ffdb68de39c57e26b77d7ac9ecaf08c8"), - "plugins/Dashboard/DashboardSettingsControlBase.php" => array("671", "4979cb16799468c2e48306fc69b51a9d"), - "plugins/Dashboard/javascripts/dashboard.js" => array("10758", "b790e5f955248dec1129ef302cd268e3"), - "plugins/Dashboard/javascripts/dashboardObject.js" => array("18451", "dd99d1afae535b93176693343d7496b0"), - "plugins/Dashboard/javascripts/dashboardWidget.js" => array("11715", "60dc1080ad458f829e2ecca912fd2a34"), - "plugins/Dashboard/javascripts/widgetMenu.js" => array("16065", "45348093e269dbbfaa759ad98965ecc9"), - "plugins/Dashboard/stylesheets/dashboard.less" => array("8508", "65b1b29e4898fb49936a9c02bbcfc1fc"), - "plugins/Dashboard/stylesheets/standalone.css" => array("1163", "c05b2508df30267e2272e8998fd441d8"), - "plugins/Dashboard/templates/_dashboardSettings.twig" => array("800", "2c5218bdfc8b5500d438590af1babacd"), - "plugins/Dashboard/templates/embeddedIndex.twig" => array("4843", "6ee72f07ff096808072bd4667298bf90"), - "plugins/Dashboard/templates/_header.twig" => array("581", "5d695bcc4f1f82c41acb586eeac3d191"), - "plugins/Dashboard/templates/index.twig" => array("775", "f9cfac7175ab189a36954a9a6021796c"), - "plugins/Dashboard/templates/_widgetFactoryTemplate.twig" => array("963", "a2a1af34eef792aaa743d97e6d0f15e4"), - "plugins/DBStats/API.php" => array("9746", "7fc743a372e25e41bf222d6aaab68162"), - "plugins/DBStats/Controller.php" => array("5044", "d4efb7c5a12a4c87209c848e36766d75"), - "plugins/DBStats/DBStats.php" => array("15822", "98f174296aea7b597ff7b508944e3377"), - "plugins/DBStats/.gitignore" => array("33", "478d4f2ee44c0d87d241ed2565482e1c"), + "plugins/CoreVisualizations/stylesheets/jqplot.css" => array("4708", "2094e4bb4c802bb048745f57ead1f905"), + "plugins/CoreVisualizations/templates/_dataTableViz_htmlTable.twig" => array("3369", "cc943cba18faa5e721d6749c3e344cb7"), + "plugins/CoreVisualizations/templates/_dataTableViz_jqplotGraph.twig" => array("194", "aafbb43ba063732fc2ba0056b8bc3d72"), + "plugins/CoreVisualizations/templates/_dataTableViz_tagCloud.twig" => array("886", "1387074d19e97ca8edd681fbf1447716"), + "plugins/CoreVisualizations/Visualizations/Cloud/Config.php" => array("852", "ccdfb0f61d3c12fb3a985489a286878f"), + "plugins/CoreVisualizations/Visualizations/Cloud.php" => array("5115", "1c3df12698f3d0a275edba9938c0c257"), + "plugins/CoreVisualizations/Visualizations/Graph/Config.php" => array("3363", "f784c301f7dcd33050fa4b2bf369e7cb"), + "plugins/CoreVisualizations/Visualizations/Graph.php" => array("5433", "dee47a3fbaff7a638e66b277866dea52"), + "plugins/CoreVisualizations/Visualizations/HtmlTable/AllColumns.php" => array("2138", "1e8235a59a2864111dc44c2777af56e3"), + "plugins/CoreVisualizations/Visualizations/HtmlTable/Config.php" => array("3367", "53f1b7bf01c9f25f9db63ddd0914c841"), + "plugins/CoreVisualizations/Visualizations/HtmlTable.php" => array("2209", "b6f4cc3b069bfb17920296d2e0071030"), + "plugins/CoreVisualizations/Visualizations/HtmlTable/PivotBy.php" => array("896", "8a8d0efc43ab88363f8c72cdbe3bbbfd"), + "plugins/CoreVisualizations/Visualizations/HtmlTable/RequestConfig.php" => array("1753", "146fb38093372ec0a800d6a18d9c6455"), + "plugins/CoreVisualizations/Visualizations/JqplotGraph/Bar.php" => array("1057", "f2bc5bb8216a4664269b1efcd701509f"), + "plugins/CoreVisualizations/Visualizations/JqplotGraph/Config.php" => array("1812", "5a0fc2669a91697fb527dfcb5708b1d9"), + "plugins/CoreVisualizations/Visualizations/JqplotGraph/Evolution/Config.php" => array("1150", "db0ebdddf050a307c357394f08260706"), + "plugins/CoreVisualizations/Visualizations/JqplotGraph/Evolution.php" => array("6289", "0cb8db0af48f46242882f46dfb9a42d7"), + "plugins/CoreVisualizations/Visualizations/JqplotGraph.php" => array("1418", "ff0d8a2d8fb7158cc8da87fe6f3ac5f4"), + "plugins/CoreVisualizations/Visualizations/JqplotGraph/Pie.php" => array("1529", "de48cbbfd5628f146310abc50e31afba"), + "plugins/CoreVisualizations/Visualizations/Sparkline.php" => array("3546", "87ec3e738f458d08473c3bb2bfbe9d51"), + "plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.controller.js" => array("769", "a44ee2a662480b326b0cd60f573f8bb2"), + "plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.directive.html" => array("2659", "91f42d6ae61d9f2aa5b69f6e98a2e97d"), + "plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.directive.js" => array("724", "1a9ef7fd796b3d189dd65cfad95f5248"), + "plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.directive.less" => array("240", "1761e56f3ff1380b315eacd286b9de97"), + "plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.model.js" => array("1990", "4ec33a3e893e31b527b252e3d5cd2816"), + "plugins/CustomVariables/API.php" => array("5841", "812b54b593b6e552c6f148bd9416826b"), + "plugins/CustomVariables/Archiver.php" => array("10163", "24a4bf97c3a890b067697cac6628323a"), + "plugins/CustomVariables/Columns/Base.php" => array("1757", "a028ef2e07f53aa79912e5ed7e358019"), + "plugins/CustomVariables/Columns/CustomVariableName.php" => array("485", "89aae4a9208e8e4b446772f659822f17"), + "plugins/CustomVariables/Columns/CustomVariableValue.php" => array("488", "cd558d65cb5daf36301f625dd045d5a6"), + "plugins/CustomVariables/Commands/Info.php" => array("2288", "39ba3750ffb13363806f287159213a3e"), + "plugins/CustomVariables/Commands/SetNumberOfCustomVariables.php" => array("7017", "f46b53f0b7c7f0312321077d877a1f38"), + "plugins/CustomVariables/config/config.php" => array("138", "d4bbf8117bbe2cdca32140476243e2fb"), + "plugins/CustomVariables/config/test.php" => array("66", "f2e99120104729896c430fb1fa99d3c7"), + "plugins/CustomVariables/Controller.php" => array("511", "f643e01847f0ceb47026a23edb839349"), + "plugins/CustomVariables/CustomVariables.php" => array("6769", "ed3eda0f155e66ba55f2add079d13a43"), + "plugins/CustomVariables/DataTable/Filter/CustomVariablesValuesFromNameId.php" => array("1079", "6ba2bbf837323e95c1b7e3d7db456fc1"), + "plugins/CustomVariables/lang/ar.json" => array("248", "083b5e9f45f5d68ccbfb59ce0797bd61"), + "plugins/CustomVariables/lang/be.json" => array("763", "115dc5bdb0497ecfa18b6929707e6394"), + "plugins/CustomVariables/lang/bg.json" => array("924", "d9461c32e98815084aaf2efb9a5163cb"), + "plugins/CustomVariables/lang/ca.json" => array("663", "a28089ee5bfcd91c00a1dabcc1ca10d0"), + "plugins/CustomVariables/lang/cs.json" => array("2314", "044fff1b545aec5fada24429c9ac09ef"), + "plugins/CustomVariables/lang/da.json" => array("609", "422ed0e0426b5c538c84b2b2ec84f668"), + "plugins/CustomVariables/lang/de.json" => array("2501", "66b70f1da8e8f321509294174fbcbb3e"), + "plugins/CustomVariables/lang/el.json" => array("4025", "eb5f7f8f4a4ec5dc6a54b2501b248b04"), + "plugins/CustomVariables/lang/en.json" => array("2417", "086f7accde4bf5df7a72676387180e35"), + "plugins/CustomVariables/lang/es.json" => array("1229", "8a1e2e86d93df0e5278aea68dfed37c5"), + "plugins/CustomVariables/lang/et.json" => array("298", "102bc5c068cd3e56a92cff65eea94826"), + "plugins/CustomVariables/lang/fa.json" => array("329", "b757781232213982a275f8739cd10edd"), + "plugins/CustomVariables/lang/fi.json" => array("597", "3ecf11ea1b9ab515d90bbee9728ca028"), + "plugins/CustomVariables/lang/fr.json" => array("2663", "6211974c94fa2e9a9f50bec79f991a23"), + "plugins/CustomVariables/lang/he.json" => array("219", "fd0b095c9513eddcc75351d29230af40"), + "plugins/CustomVariables/lang/hi.json" => array("950", "c68ea25cfd021222f6445b2bcb21e3ac"), + "plugins/CustomVariables/lang/id.json" => array("558", "e64bc0ac45bc7386586ba6e50643af44"), + "plugins/CustomVariables/lang/it.json" => array("2407", "b8a4d0d57cb283dd24360cf20e314711"), + "plugins/CustomVariables/lang/ja.json" => array("657", "334746f9e4843cf4fd31842d36ae7a8b"), + "plugins/CustomVariables/lang/ko.json" => array("581", "7c708a5ac1aadbd31a3baff32e8631b1"), + "plugins/CustomVariables/lang/nb.json" => array("115", "402ba9cb397a5ab654c60edd136eace6"), + "plugins/CustomVariables/lang/nl.json" => array("1127", "09830b160b14a6fa7e97a02afd471a98"), + "plugins/CustomVariables/lang/pl.json" => array("620", "05fa0fd8ae23aceee494370d88770659"), + "plugins/CustomVariables/lang/pt-br.json" => array("2545", "21d71f5758d89e948321092f41a02e47"), + "plugins/CustomVariables/lang/pt.json" => array("694", "13524977179c0535d700d1b8ae86381e"), + "plugins/CustomVariables/lang/ro.json" => array("649", "6289e20a80a76e1c6e1fd9832233b280"), + "plugins/CustomVariables/lang/ru.json" => array("939", "b63342f0c7519035e46d90538bd9605f"), + "plugins/CustomVariables/lang/sk.json" => array("304", "2edbab7acbc510eeff67ce71c1406fc5"), + "plugins/CustomVariables/lang/sl.json" => array("81", "1dca80911cb675863506b6c9d48b5574"), + "plugins/CustomVariables/lang/sq.json" => array("1291", "151092af8e2fb170662bd287e43f455c"), + "plugins/CustomVariables/lang/sr.json" => array("983", "1878c99cd8e704bc6dc7b96cc08b8bc5"), + "plugins/CustomVariables/lang/sv.json" => array("693", "deaa27952aeb22e69ab71327ac8b1809"), + "plugins/CustomVariables/lang/ta.json" => array("425", "a6bccede35fc81eca5b1983ab9cdde0e"), + "plugins/CustomVariables/lang/th.json" => array("319", "ca4d00958e1f5ab315e40e552a2d6e3f"), + "plugins/CustomVariables/lang/tl.json" => array("670", "d2781738edfbeecb8fe801110ec2ad05"), + "plugins/CustomVariables/lang/tr.json" => array("286", "f5fa2c807b7446fb1639c3a3aa3dc354"), + "plugins/CustomVariables/lang/vi.json" => array("684", "82a09e0873030a76a9dc38e942ae2c13"), + "plugins/CustomVariables/lang/zh-cn.json" => array("495", "e499693ef31e7d901e9ac20d65a3f15d"), + "plugins/CustomVariables/Menu.php" => array("1038", "5fbaf10416d8a96428e61fadc6b9b33d"), + "plugins/CustomVariables/Model.php" => array("4942", "b6cbae67399cf1d69f1b8d5dfbd34487"), + "plugins/CustomVariables/Reports/Base.php" => array("398", "e9e74c61fbc7e506f95fb9ff9cda5b70"), + "plugins/CustomVariables/Reports/GetCustomVariables.php" => array("2813", "7b04fc82d41cf48872f3b5814dee87a9"), + "plugins/CustomVariables/Reports/GetCustomVariablesValuesFromNameId.php" => array("1418", "4a4e46d9a2549d18b6a72feedab5ef0f"), + "plugins/CustomVariables/templates/manage.twig" => array("296", "09c8229003b02a62cb9c14c41c824564"), + "plugins/CustomVariables/Tracker/CustomVariablesRequestProcessor.php" => array("3057", "b621698adf6732b195fba546ff4af3fd"), + "plugins/Dashboard/API.php" => array("3973", "587b704878e8973f5632beb7db171ae1"), + "plugins/Dashboard/Controller.php" => array("8130", "6dbf5481dded9237abf715bb0dbab790"), + "plugins/Dashboard/DashboardManagerControl.php" => array("1593", "fbb6289fb26fb97b30b3a9895aece757"), + "plugins/Dashboard/Dashboard.php" => array("9189", "80632159de539ed55d8677b6656ba727"), + "plugins/Dashboard/DashboardSettingsControlBase.php" => array("753", "343e3c684af7a911dc4bc83816daac2e"), + "plugins/Dashboard/javascripts/dashboard.js" => array("11253", "10786e31e1bcf0ed9c5087f1bb4a989c"), + "plugins/Dashboard/javascripts/dashboardObject.js" => array("20599", "18d4b9e2ae6c923e36152f03681f0735"), + "plugins/Dashboard/javascripts/dashboardWidget.js" => array("12743", "5f1101764f67bd1d5cb9f6438fe9ddf0"), + "plugins/Dashboard/javascripts/widgetMenu.js" => array("16628", "427ca716bb2a57ea68aa3098a24b804e"), + "plugins/Dashboard/lang/am.json" => array("569", "8fda94ca89590d74ce3fe3c53b475ce9"), + "plugins/Dashboard/lang/ar.json" => array("3653", "b47bf1563ed93ee1b962ba008cf8e9df"), + "plugins/Dashboard/lang/be.json" => array("773", "f70c775ab0902fd3d99db0036e48a143"), + "plugins/Dashboard/lang/bg.json" => array("3893", "dd4ee59c48a85cbad424a9a0c13cc25c"), + "plugins/Dashboard/lang/bn.json" => array("453", "52fabf97d2deae7f4cbcf88357a3a413"), + "plugins/Dashboard/lang/bs.json" => array("139", "d4095b9eb9e5ccc3673cafa8a39914be"), + "plugins/Dashboard/lang/ca.json" => array("2590", "7710c56dc9b1c8ad3552f91bf826805f"), + "plugins/Dashboard/lang/cs.json" => array("2571", "14a1fa96e2df6585f0e9304c74db2429"), + "plugins/Dashboard/lang/cy.json" => array("63", "590dac2bf4d5091e6d47949c7a3f1996"), + "plugins/Dashboard/lang/da.json" => array("2463", "a118aaf7370086eb5be775c6c33af381"), + "plugins/Dashboard/lang/de.json" => array("2786", "677a74004fa8947053bc1bc89898585c"), + "plugins/Dashboard/lang/el.json" => array("4790", "8aa424cb6115776fde2c837434767d57"), + "plugins/Dashboard/lang/en.json" => array("2568", "1c39519f4579b367c5a7d564c9613a62"), + "plugins/Dashboard/lang/es.json" => array("2757", "8e6fdf4a3c71785af5cc9220aaf67c12"), + "plugins/Dashboard/lang/et.json" => array("1368", "597108e065f10ca4f12b24ca1df1e10a"), + "plugins/Dashboard/lang/eu.json" => array("449", "16e72f790d19cf14874da74a7b921a40"), + "plugins/Dashboard/lang/fa.json" => array("2978", "fb5bd6881faf1d05244ac0949a615bc2"), + "plugins/Dashboard/lang/fi.json" => array("2159", "9a59c537746c503a7a5231466fa596de"), + "plugins/Dashboard/lang/fr.json" => array("3026", "d19e5200134c978edd9cdcb46c952aaa"), + "plugins/Dashboard/lang/gl.json" => array("530", "ca5f821c0b219814a9016cfff2a53755"), + "plugins/Dashboard/lang/he.json" => array("859", "417101b6e24b4db8041b241129fa20b0"), + "plugins/Dashboard/lang/hi.json" => array("4564", "c56e63d9f7c093b69c0e71170048acaf"), + "plugins/Dashboard/lang/hr.json" => array("696", "d33351c130cb6c1b01b7498893d52d4e"), + "plugins/Dashboard/lang/hu.json" => array("590", "0f2fadeda3d88c9e881e3bb2b7464d2d"), + "plugins/Dashboard/lang/id.json" => array("2382", "2e4a72dcd7ae06c097de2539ee3fcaeb"), + "plugins/Dashboard/lang/is.json" => array("531", "e04d5e1ca3ea6626335acc595fdea4bf"), + "plugins/Dashboard/lang/it.json" => array("2772", "05154604c3433fd518620f998865b4b6"), + "plugins/Dashboard/lang/ja.json" => array("3557", "4b4d050f138562eb2396aab65893caf5"), + "plugins/Dashboard/lang/ka.json" => array("980", "bd12b4d68fefde166adac26c59001f26"), + "plugins/Dashboard/lang/ko.json" => array("2863", "b79f37bf16fe47809fed19497535fccb"), + "plugins/Dashboard/lang/lt.json" => array("2636", "b382d040c2150565505aa45f47c09dd1"), + "plugins/Dashboard/lang/lv.json" => array("551", "9cbc3cd52cd5068990fe2f4cf623bafc"), + "plugins/Dashboard/lang/nb.json" => array("2612", "b1d8eb6e219e7d8c44b768c533501229"), + "plugins/Dashboard/lang/nl.json" => array("2622", "978c380a70eb0188e6c834daf3ac7fb0"), + "plugins/Dashboard/lang/nn.json" => array("2164", "1ca6c97912d271768dd8b10a9109c857"), + "plugins/Dashboard/lang/pl.json" => array("2676", "670f5f3beafbc6b427daf94229de8f97"), + "plugins/Dashboard/lang/pt-br.json" => array("2703", "32e3bee76402358a50d1471db021f69b"), + "plugins/Dashboard/lang/pt.json" => array("595", "86bc066076bf5de219c0b80b394c1f83"), + "plugins/Dashboard/lang/ro.json" => array("2712", "cc973c7880fc8053860d19d66c72e0a9"), + "plugins/Dashboard/lang/ru.json" => array("3799", "7cbd47bd8dff24137f6e85cf4c5389bd"), + "plugins/Dashboard/lang/sk.json" => array("2918", "cdf9cb016f339f9d09554ee54400bf0d"), + "plugins/Dashboard/lang/sl.json" => array("1077", "f2253a972e9badda762fe2767d64a6b7"), + "plugins/Dashboard/lang/sq.json" => array("934", "7309588d8513d0d6d853cc1c4f53a146"), + "plugins/Dashboard/lang/sr.json" => array("2449", "7c3955b50ef3ab3296cd197cd44a5642"), + "plugins/Dashboard/lang/sv.json" => array("2674", "11252d8c74a1eb3d42bb70bd022e44a8"), + "plugins/Dashboard/lang/ta.json" => array("4493", "408f09655ece3e36f391d74a895758c5"), + "plugins/Dashboard/lang/te.json" => array("304", "61a18d83af188af0f83f1056b154ce83"), + "plugins/Dashboard/lang/th.json" => array("2742", "4570b40f07aeec70ebd57fed1304c2a4"), + "plugins/Dashboard/lang/tl.json" => array("2551", "c0bf1de6c15a86585c2a09e8b54604a1"), + "plugins/Dashboard/lang/tr.json" => array("1732", "8c673f389d0d88973e55beeed1b4110b"), + "plugins/Dashboard/lang/uk.json" => array("688", "b068f8b9e5c1c108b20617eb5776b94e"), + "plugins/Dashboard/lang/vi.json" => array("3274", "82fd5f22cb38a3fa97c206aff6017529"), + "plugins/Dashboard/lang/zh-cn.json" => array("2210", "38e0418b0a1d226632c67e39707dca9d"), + "plugins/Dashboard/lang/zh-tw.json" => array("505", "5f3ede3ed060e4e8d05aa8549d5671f7"), + "plugins/Dashboard/Menu.php" => array("1879", "2408aae44c211dcb574bf369ebfa1a30"), + "plugins/Dashboard/Model.php" => array("7447", "b854ea335cbee3ace0dde1abe86c9a8e"), + "plugins/Dashboard/stylesheets/dashboard.less" => array("7443", "3f71ac78a0d7b429a304bc9cbcacdd9b"), + "plugins/Dashboard/stylesheets/standalone.css" => array("1012", "84e41d5c9b1a53e55b5b3a0a738a9c9b"), + "plugins/Dashboard/stylesheets/widget.less" => array("3191", "510062901125747e8406db34811ec17f"), + "plugins/Dashboard/templates/_dashboardSettings.twig" => array("918", "727fa6d1e866020e0e16283464845f27"), + "plugins/Dashboard/templates/embeddedIndex.twig" => array("5004", "43973e12ed01827e2720e20e448a4311"), + "plugins/Dashboard/templates/_header.twig" => array("741", "5528f135dd6df001c90b6a9c1580888d"), + "plugins/Dashboard/templates/index.twig" => array("842", "de7557260adbe304856f496800cd9035"), + "plugins/Dashboard/templates/_widgetFactoryTemplate.twig" => array("1251", "191e4b8ce42fd40f7b4ddba26bae5336"), + "plugins/DBStats/API.php" => array("9633", "8c80c4a6dfe0e153325bbcb5f779ed7d"), + "plugins/DBStats/config/test.php" => array("144", "7826aff77c3bde2340fa441f7ee285e1"), + "plugins/DBStats/Controller.php" => array("1675", "5563e410ae421343cf2f55367aa60b5b"), + "plugins/DBStats/DBStats.php" => array("1128", "28d02390b72286f0232141871c5d58b4"), "plugins/DBStats/lang/am.json" => array("544", "7baa1ff89068b48d9d30244ee87c00cd"), - "plugins/DBStats/lang/ar.json" => array("1131", "56ba42e94f87e55fe5350d50eb1be05f"), - "plugins/DBStats/lang/be.json" => array("1093", "d46871abcfc087252143e630b0658a21"), - "plugins/DBStats/lang/bg.json" => array("1470", "4529c23a8802c448433bee85ca847bbe"), + "plugins/DBStats/lang/ar.json" => array("965", "737b502e264481af4efca7e905dd64f5"), + "plugins/DBStats/lang/be.json" => array("939", "9cfcf1f97e230552f3920d099c7a466d"), + "plugins/DBStats/lang/bg.json" => array("1297", "2c67d3fe9f7efb063a72d15a6c343b90"), "plugins/DBStats/lang/bn.json" => array("61", "82e91890b454800e8d058c5f53f2e5d0"), "plugins/DBStats/lang/bs.json" => array("178", "f35b90a6e1ec759920d7b1e1eb536308"), - "plugins/DBStats/lang/ca.json" => array("1129", "25c425a101f8093240cbc5499c9343e5"), - "plugins/DBStats/lang/cs.json" => array("1008", "cffa24240285345e52e5ef6e9d1204bc"), - "plugins/DBStats/lang/da.json" => array("1025", "12d7024ec084a9be18e42dfbeade0690"), - "plugins/DBStats/lang/de.json" => array("1063", "db7842b901098a4f140678bdedebf630"), - "plugins/DBStats/lang/el.json" => array("1672", "dc03df11ce42ed0f466fccd2cf512cbe"), - "plugins/DBStats/lang/en.json" => array("971", "19ece5a2cac5a31c426fd8bb21435e89"), - "plugins/DBStats/lang/es.json" => array("1120", "68b351e3419418a1da6084016a5a05ac"), - "plugins/DBStats/lang/et.json" => array("1043", "f0cfc6c767c2659465ec0c0d909c2502"), + "plugins/DBStats/lang/ca.json" => array("1015", "82674b5b3223233a6b35e797f0f68707"), + "plugins/DBStats/lang/cs.json" => array("1091", "5a55cac31b3376e498314b1ffbb5f982"), + "plugins/DBStats/lang/cy.json" => array("50", "bba7e064e9daadf6065f4d35fac64b28"), + "plugins/DBStats/lang/da.json" => array("1058", "6b7b9614082a5daa5ef28d663c3303ea"), + "plugins/DBStats/lang/de.json" => array("1087", "3d9dff5fb6e1dd7df3cbad186ee5b659"), + "plugins/DBStats/lang/el.json" => array("1714", "d3425568a72c4b508dc1785c4e38f6f1"), + "plugins/DBStats/lang/en.json" => array("1003", "6086fed43fb11e3694a85e0e16ff0a9e"), + "plugins/DBStats/lang/es.json" => array("1194", "71640d9ec3cfea32d88f16174f1ef696"), + "plugins/DBStats/lang/et.json" => array("937", "c55b92ffa8d5ce46a01ae23494caa0b6"), "plugins/DBStats/lang/eu.json" => array("607", "ba6f32b3c0e57fb6ccaedc4ba30b64e2"), - "plugins/DBStats/lang/fa.json" => array("1512", "c2dae8fe3dfb7b109afa25ccdfedd69e"), - "plugins/DBStats/lang/fi.json" => array("971", "9fdf9bb616f21541583c6d7cbe2b98c6"), - "plugins/DBStats/lang/fr.json" => array("1183", "9cbacf1e08fdc1f5addf27e3ed1b723f"), - "plugins/DBStats/lang/he.json" => array("867", "dfa4fc8df95dd71cc99bc14ad034d229"), - "plugins/DBStats/lang/hi.json" => array("1844", "46f80ba8f44e7333f082f2f35e595b40"), - "plugins/DBStats/lang/hu.json" => array("780", "fcbd2ce72e8155f459fe5cb090f09a31"), - "plugins/DBStats/lang/id.json" => array("1034", "b9e8adfb0c460cd383755fb9f5a81987"), - "plugins/DBStats/lang/is.json" => array("480", "9610b7003f73148183d44ac3f6cc7ded"), - "plugins/DBStats/lang/it.json" => array("1080", "3b7b0b7c2eb09c2c57d748f11c2a78bc"), - "plugins/DBStats/lang/ja.json" => array("1206", "6a36a174f6cca7cc6d16e95f99f88814"), - "plugins/DBStats/lang/ka.json" => array("1486", "17d027bb3b07c7b4f6e84b138f32a668"), - "plugins/DBStats/lang/ko.json" => array("1082", "259dbf55c70e0f2dac918896e2a104fc"), - "plugins/DBStats/lang/lt.json" => array("683", "f2dd194a13234292af5c78d4f918e948"), - "plugins/DBStats/lang/lv.json" => array("667", "e22bc702ad6799f314090447d3890c4d"), - "plugins/DBStats/lang/nb.json" => array("770", "6e9614e188669135143894149cf6afb8"), - "plugins/DBStats/lang/nl.json" => array("1034", "b75930ea35d558b9b31239dabb98615b"), - "plugins/DBStats/lang/nn.json" => array("1010", "bc65370bea4018a4c4a89f471170316c"), - "plugins/DBStats/lang/pl.json" => array("741", "351e6de4ab5a458b65790e7f61374607"), - "plugins/DBStats/lang/pt-br.json" => array("1098", "35a2292924438fdabc003199c9ce0927"), - "plugins/DBStats/lang/pt.json" => array("707", "b9f87b78d97a52b9815fa071ec0f1fdd"), - "plugins/DBStats/lang/ro.json" => array("1071", "58ff2ea72c28ee743b598ff434ca1135"), - "plugins/DBStats/lang/ru.json" => array("1521", "56c5e96f586510b1c3d29fcd89f80cfb"), + "plugins/DBStats/lang/fa.json" => array("1348", "83cb60891f23e818fcfe71e6db93b29f"), + "plugins/DBStats/lang/fi.json" => array("887", "b13a0070435d0ea24c361e06dbf18c2d"), + "plugins/DBStats/lang/fr.json" => array("1222", "5877af515593d6d1d668eeea87124792"), + "plugins/DBStats/lang/gl.json" => array("52", "ae9b8e099bdaa500b4051109ab72d79d"), + "plugins/DBStats/lang/he.json" => array("733", "9d94b2dae5734eb530ee05bde0f10a70"), + "plugins/DBStats/lang/hi.json" => array("1667", "1ed4bdaace4b85b5eb6f09ba808495fa"), + "plugins/DBStats/lang/hr.json" => array("53", "6b70af91412e28cba7558a6243c6f4d5"), + "plugins/DBStats/lang/hu.json" => array("670", "8c48ebfabb03bc23e389d003f867f32f"), + "plugins/DBStats/lang/id.json" => array("935", "2c7ef06ad32b228d3cb7ff48ce2d7439"), + "plugins/DBStats/lang/is.json" => array("385", "e1c8fd764b7045f94812ad3014ef6af8"), + "plugins/DBStats/lang/it.json" => array("1125", "d5e85f4a203e7c03f771d7c654991694"), + "plugins/DBStats/lang/ja.json" => array("1270", "a8e12097fe570cf12affdae65af0e441"), + "plugins/DBStats/lang/ka.json" => array("1243", "e3c40a4b8ccc8b1be321151f6bfd1f87"), + "plugins/DBStats/lang/ko.json" => array("1148", "a03c74c6a9f4787f38f8b57ffb553acd"), + "plugins/DBStats/lang/lt.json" => array("590", "d19c84bea16e895ceae5aabb3010ed94"), + "plugins/DBStats/lang/lv.json" => array("562", "b60a85fe884b65fd929678ca9797908d"), + "plugins/DBStats/lang/nb.json" => array("1054", "33e73a392aa21354fe76dad80f77a732"), + "plugins/DBStats/lang/nl.json" => array("1079", "c96777f7e239ce09f2014a8309d181bb"), + "plugins/DBStats/lang/nn.json" => array("912", "e5a5d71fb0e94f2fdd9d1776b058692b"), + "plugins/DBStats/lang/pl.json" => array("805", "183190003eb3eb3a63ff768c5cc97698"), + "plugins/DBStats/lang/pt-br.json" => array("1143", "9741e57fa0247c08db3f621b8aeb6c63"), + "plugins/DBStats/lang/pt.json" => array("606", "ce2afc3cae44d03890fe27fc25384225"), + "plugins/DBStats/lang/ro.json" => array("971", "9a732c310cacb666b2c93708999f42c0"), + "plugins/DBStats/lang/ru.json" => array("1617", "d8d48d55ab1fae869484f2cdbb588eb4"), "plugins/DBStats/lang/sk.json" => array("486", "1df64facbdd26e49116c87e6629c71e6"), - "plugins/DBStats/lang/sl.json" => array("576", "e1e0d54c1bf14659683595826509e709"), - "plugins/DBStats/lang/sq.json" => array("796", "3d358248703abc04f0609902883fdbcb"), - "plugins/DBStats/lang/sr.json" => array("1013", "abb86e964d3e0fa91e301d6d2ea6d9fc"), - "plugins/DBStats/lang/sv.json" => array("1037", "b8bda69de93baab3d956a23d3e8a03bc"), - "plugins/DBStats/lang/ta.json" => array("993", "e1d3c2a837fa6be11f1d5668dc4d812a"), + "plugins/DBStats/lang/sl.json" => array("481", "5aa215465d66ce077bd65f5ef7ca58bb"), + "plugins/DBStats/lang/sq.json" => array("674", "11f76962822c4d2313a2c5468f81c8b4"), + "plugins/DBStats/lang/sr.json" => array("1057", "7854699be9c12f7a331c8b2302bfd4e8"), + "plugins/DBStats/lang/sv.json" => array("929", "8309dd3144ffc68f86b7f4bbbb451be1"), + "plugins/DBStats/lang/ta.json" => array("782", "7dd6f257f51b264bcd12114e6b10e28a"), "plugins/DBStats/lang/te.json" => array("125", "10cb7b33f9cab70f0cd8f12b752063df"), - "plugins/DBStats/lang/th.json" => array("1383", "efaa040a75b0b20ef860d27218d7a784"), - "plugins/DBStats/lang/tr.json" => array("651", "848ac6471a4e74f96d213e3488dae3dc"), - "plugins/DBStats/lang/uk.json" => array("978", "56d1978d76916aea7480b4414003ea41"), - "plugins/DBStats/lang/vi.json" => array("1245", "336115958d203a6e6b72beebc7278b4a"), - "plugins/DBStats/lang/zh-cn.json" => array("943", "ccfe32f44e5d3cdd8febf164e3a239f5"), - "plugins/DBStats/lang/zh-tw.json" => array("673", "760492ac770163bc1cf019e96ae1d464"), - "plugins/DBStats/MySQLMetadataDataAccess.php" => array("2279", "d6b533086e17fb9fe1eb988c4e030744"), - "plugins/DBStats/MySQLMetadataProvider.php" => array("12336", "7d8c570b43a07269a54627220ba473fe"), - "plugins/DBStats/stylesheets/dbStatsTable.less" => array("233", "4324953bbb513a4e0aa2d932d2447e5a"), - "plugins/DBStats/templates/index.twig" => array("4282", "3790e00ea5964ec59aa5b014b7a9d957"), - "plugins/DevicesDetection/API.php" => array("5930", "1d311bf37c78c7ffd3d6a55523f53e57"), - "plugins/DevicesDetection/Archiver.php" => array("2780", "9f07edb22c5f4ce1240d8d742193895a"), - "plugins/DevicesDetection/Controller.php" => array("5541", "ded6665d42e88f384cd5e45574600a52"), - "plugins/DevicesDetection/DevicesDetection.php" => array("14416", "3065fdd010d298d6ad65ef38625335ae"), - "plugins/DevicesDetection/functions.php" => array("6788", "f239b1d1cb96dfc6e4ff2ed2ef676d25"), + "plugins/DBStats/lang/th.json" => array("1208", "1e52679eb394bf99a84284f1682f0751"), + "plugins/DBStats/lang/tl.json" => array("987", "d6ef114efdbe7f7f0be28f6b4cf1b6fe"), + "plugins/DBStats/lang/tr.json" => array("942", "86cb2ca878337afe4fc40b797036bfdf"), + "plugins/DBStats/lang/uk.json" => array("828", "55846b40c9cb4b33c5da502a444ea7a2"), + "plugins/DBStats/lang/vi.json" => array("1125", "8037fc9f0c439929cb2e16ef177ffe57"), + "plugins/DBStats/lang/zh-cn.json" => array("971", "b07ef6f6b4f9466111ae0820a93f6166"), + "plugins/DBStats/lang/zh-tw.json" => array("582", "f57727910eda2738779d8cfa13a9858a"), + "plugins/DBStats/Menu.php" => array("588", "67c4359ea2cc7faa9db8540c2a483bee"), + "plugins/DBStats/MySQLMetadataDataAccess.php" => array("2282", "80e83f859973078b19e3166e65a255b7"), + "plugins/DBStats/MySQLMetadataProvider.php" => array("12173", "f3687a846082318498fa3b1cc5d47361"), + "plugins/DBStats/Reports/Base.php" => array("6496", "4325a630a1a81f17a06d067c87aa5b1b"), + "plugins/DBStats/Reports/GetAdminDataSummary.php" => array("946", "d2777cd8e5d18e15dec4fb0a78368ae9"), + "plugins/DBStats/Reports/GetDatabaseUsageSummary.php" => array("1800", "0556cb2074b07a5f6e8ff4934b273485"), + "plugins/DBStats/Reports/GetIndividualMetricsSummary.php" => array("1124", "384de4be5a012dc28fe4b076dffe0118"), + "plugins/DBStats/Reports/GetIndividualReportsSummary.php" => array("1343", "4089c68bbf6f10fae430a22e85b46e44"), + "plugins/DBStats/Reports/GetMetricDataSummaryByYear.php" => array("1064", "00d03f910c6a184da85f718b46726c5b"), + "plugins/DBStats/Reports/GetMetricDataSummary.php" => array("944", "3a95a92dd75f13cb51a26424cb6e2203"), + "plugins/DBStats/Reports/GetReportDataSummaryByYear.php" => array("1058", "30b3eecb987728f9220a34ebbcfcff8c"), + "plugins/DBStats/Reports/GetReportDataSummary.php" => array("940", "0956b5b81fda06202d1d663dd5aceeab"), + "plugins/DBStats/Reports/GetTrackerDataSummary.php" => array("793", "c955160d621c6ab4bb6c0259350bf1fc"), + "plugins/DBStats/stylesheets/dbStatsTable.less" => array("331", "3080ab1ed9301c09f0b054e44110ed22"), + "plugins/DBStats/Tasks.php" => array("894", "e592446eb19c1d799a6375db30aa228a"), + "plugins/DBStats/templates/index.twig" => array("4428", "d5baa4de9e77388c4210eb6756e3255f"), + "plugins/DevicePlugins/API.php" => array("3839", "7348c3db665ec3f5d5bc435e86eda7ef"), + "plugins/DevicePlugins/Archiver.php" => array("2916", "ecb9e7cd2e5195c5f86033de10a3852c"), + "plugins/DevicePlugins/Columns/PluginCookie.php" => array("790", "480751e55d0860e5a9cc775fbb0196d5"), + "plugins/DevicePlugins/Columns/PluginDirector.php" => array("791", "a14397e759f5582b7ccce50196d0f98e"), + "plugins/DevicePlugins/Columns/PluginFlash.php" => array("785", "6ffe1f748a2ff1c0e744b110b56ec5c4"), + "plugins/DevicePlugins/Columns/PluginGears.php" => array("787", "adeb752489b4a46a3f52e42ea253427b"), + "plugins/DevicePlugins/Columns/PluginJava.php" => array("784", "74a5c6dd03401b95140c167efb5adbc5"), + "plugins/DevicePlugins/Columns/PluginPdf.php" => array("781", "3dab1136c16f433e9687ee98cd6fd1ae"), + "plugins/DevicePlugins/Columns/Plugin.php" => array("378", "6e3528bce1d165e7c275601e98254dee"), + "plugins/DevicePlugins/Columns/PluginQuickTime.php" => array("792", "a824e47f3e23054dc21e7885d2c89ca5"), + "plugins/DevicePlugins/Columns/PluginRealPlayer.php" => array("797", "046bdf870b8feb4c8d7c68489ae836f9"), + "plugins/DevicePlugins/Columns/PluginSilverlight.php" => array("796", "4f2295a66b3e431af0e3f01611f40115"), + "plugins/DevicePlugins/Columns/PluginWindowsMedia.php" => array("799", "2cbf902a7e537f524a3eeaf5e3d4895b"), + "plugins/DevicePlugins/DevicePlugins.php" => array("1507", "75af59f89916addaddcda3901740dd8d"), + "plugins/DevicePlugins/functions.php" => array("405", "18554155487b074fb0612fdadd0b0425"), + "plugins/DevicePlugins/images/plugins/cookie.gif" => array("211", "9e564884defc036134b19ab38c192a6b"), + "plugins/DevicePlugins/images/plugins/director.gif" => array("198", "952c4a0e083ed089ca5c8ab2804c35ab"), + "plugins/DevicePlugins/images/plugins/flash.gif" => array("1018", "f315906f425f089dd4664ee530c691bd"), + "plugins/DevicePlugins/images/plugins/gears.gif" => array("558", "d4ec944ef01420637a709619f067953e"), + "plugins/DevicePlugins/images/plugins/java.gif" => array("565", "b4d24f76a64e2df762292e892e9215c4"), + "plugins/DevicePlugins/images/plugins/pdf.gif" => array("1021", "79b3d68c112942cefbb24dda9b421464"), + "plugins/DevicePlugins/images/plugins/quicktime.gif" => array("1003", "0feda8dc4ddec39e35b6f4925603c8bd"), + "plugins/DevicePlugins/images/plugins/realplayer.gif" => array("1025", "95739f527d29cab050a3d4eff35e93c7"), + "plugins/DevicePlugins/images/plugins/silverlight.gif" => array("1012", "8449c3a43f42c44bc5b82948d179b4b4"), + "plugins/DevicePlugins/images/plugins/windowsmedia.gif" => array("1026", "a374aec2d5488c5dc8d4eaf21ec59728"), + "plugins/DevicePlugins/lang/am.json" => array("91", "13c73a82d49097a743559c35c88757a8"), + "plugins/DevicePlugins/lang/ar.json" => array("87", "90af3c8900bac7aedeee737bed4754f2"), + "plugins/DevicePlugins/lang/be.json" => array("464", "f423a2c57eb19927d5140d8f7d531c83"), + "plugins/DevicePlugins/lang/bg.json" => array("610", "574fdf6d02381fc3dd726190312778fe"), + "plugins/DevicePlugins/lang/ca.json" => array("553", "6848044db393c04122a1095c5bc6968e"), + "plugins/DevicePlugins/lang/cs.json" => array("829", "b1f2e8fda4b16004cf947a2b20a5c94a"), + "plugins/DevicePlugins/lang/da.json" => array("553", "ea4be865c78556de1f8c8a7ef555fbc6"), + "plugins/DevicePlugins/lang/de.json" => array("794", "e82f68e8f829941b7f435d6fb0498f3a"), + "plugins/DevicePlugins/lang/el.json" => array("1253", "b13fe3329251d95d3748ffe905413774"), + "plugins/DevicePlugins/lang/en.json" => array("699", "ec1b88bd9006ad00cf54c7188a94abb7"), + "plugins/DevicePlugins/lang/es.json" => array("599", "0b23d7bed815c48569ff760f6a7b912e"), + "plugins/DevicePlugins/lang/et.json" => array("81", "50dac8fff45205d1f921c5ea84807eba"), + "plugins/DevicePlugins/lang/eu.json" => array("77", "f3d6446f8533008ee49e8f864ae80388"), + "plugins/DevicePlugins/lang/fa.json" => array("483", "c1319eebb2ae9b3108c0e9a279b2a259"), + "plugins/DevicePlugins/lang/fi.json" => array("480", "d4bd42192a2a19b15e341ff80e367fa8"), + "plugins/DevicePlugins/lang/fr.json" => array("542", "e81123754c8400e69eaae02a954480dc"), + "plugins/DevicePlugins/lang/gl.json" => array("76", "c1f13a2631996f19206bbdffddde400e"), + "plugins/DevicePlugins/lang/hi.json" => array("538", "aa619b15ec59dce6fea8a5f846b741ff"), + "plugins/DevicePlugins/lang/hu.json" => array("83", "c3d15c36328bcf679a6583924ed3b564"), + "plugins/DevicePlugins/lang/id.json" => array("447", "2d6d7af2d47ca23522e1d4f5b4ab8daa"), + "plugins/DevicePlugins/lang/is.json" => array("253", "8bb253981586aa8563a510cefb92db38"), + "plugins/DevicePlugins/lang/it.json" => array("548", "2622b9cc46f4dce43f01b72e37f23302"), + "plugins/DevicePlugins/lang/ja.json" => array("619", "217641502eb65ea24aee42123ac85140"), + "plugins/DevicePlugins/lang/ka.json" => array("433", "d4c4e327e6ad6ca1dce3e60d7f8f83a8"), + "plugins/DevicePlugins/lang/ko.json" => array("296", "c91dcbd4d09972db3df63881539d6220"), + "plugins/DevicePlugins/lang/lt.json" => array("396", "b385c28013b8f81aaadf140138cde505"), + "plugins/DevicePlugins/lang/lv.json" => array("317", "8048ee845aebca69775a772f3a2ea8ce"), + "plugins/DevicePlugins/lang/nb.json" => array("750", "7b8228cdbb42558a3759b4a14882052b"), + "plugins/DevicePlugins/lang/nl.json" => array("568", "745f9692b97161f5c224a914d7bc5f02"), + "plugins/DevicePlugins/lang/nn.json" => array("79", "81dd7db34c645570d9369c7f1b3601fe"), + "plugins/DevicePlugins/lang/pl.json" => array("73", "2398ee68f1b7d7d16bd78e56190c3a86"), + "plugins/DevicePlugins/lang/pt-br.json" => array("762", "364e6646903e6b51493b423c5798035e"), + "plugins/DevicePlugins/lang/pt.json" => array("303", "6d48a674821b2f28745188efdde38cfb"), + "plugins/DevicePlugins/lang/ro.json" => array("430", "d132279ab303ba95a56cb8605fa9e69e"), + "plugins/DevicePlugins/lang/ru.json" => array("709", "41472af929b11fabb3df0e3aa2ae93e9"), + "plugins/DevicePlugins/lang/sk.json" => array("74", "22b0b24c96de66872ada8899b1ae20d4"), + "plugins/DevicePlugins/lang/sl.json" => array("227", "44e0644a2e7b16952e0e4a2242f18ad9"), + "plugins/DevicePlugins/lang/sq.json" => array("324", "d28ae0652df5b532a9be89d56ac06c0e"), + "plugins/DevicePlugins/lang/sr.json" => array("552", "2b3fdc046d6eaa27dbebe5074ec248c7"), + "plugins/DevicePlugins/lang/sv.json" => array("541", "3888cf446ec5c2bf367a73fc3a7d6f14"), + "plugins/DevicePlugins/lang/th.json" => array("102", "d30a83ace0cb49601d12faf8f49f661b"), + "plugins/DevicePlugins/lang/tl.json" => array("463", "5e7b3a76410d1ae1a521e6b221f5c9fd"), + "plugins/DevicePlugins/lang/tr.json" => array("75", "4fd28baa526123ccbac87870b8188773"), + "plugins/DevicePlugins/lang/uk.json" => array("89", "dfd614e1374291d15810dbfa0421cad4"), + "plugins/DevicePlugins/lang/vi.json" => array("527", "c5a7b2ca50e35c3938d5cb5ba22eed54"), + "plugins/DevicePlugins/lang/zh-cn.json" => array("347", "c30cc7f1915e9c1a19ad273e8a641f26"), + "plugins/DevicePlugins/lang/zh-tw.json" => array("81", "dd0f98015cdf0344850d9fa0767b14c6"), + "plugins/DevicePlugins/Reports/Base.php" => array("806", "d479d4459d45aec928716b24696741da"), + "plugins/DevicePlugins/Reports/GetPlugin.php" => array("1939", "8f2b5eb8c77d6462f0e941a76c8e8f4b"), + "plugins/DevicePlugins/Visitor.php" => array("1656", "dfb44c94e04757634a6079c972453e52"), + "plugins/DevicesDetection/API.php" => array("11812", "46a024dd920f1226fdb8aefbb98049b5"), + "plugins/DevicesDetection/Archiver.php" => array("3405", "189bff83fcc7391aa27cab39cca85cfe"), + "plugins/DevicesDetection/Columns/Base.php" => array("453", "282dac10b6d93a507f7772e8fc54ca73"), + "plugins/DevicesDetection/Columns/BrowserEngine.php" => array("1527", "d08c8ba55395037df8b2f4be36764d89"), + "plugins/DevicesDetection/Columns/BrowserName.php" => array("1398", "27f30db80f33553f44cc409a79ccf131"), + "plugins/DevicesDetection/Columns/BrowserVersion.php" => array("1390", "82b6b9bba8f317b3764e6bce6d46b986"), + "plugins/DevicesDetection/Columns/DeviceBrand.php" => array("1874", "994f830bc9ae55104e61ef5e9d7c6131"), + "plugins/DevicesDetection/Columns/DeviceModel.php" => array("965", "f3c4d68f8edb0baed0f713709b6bbf8e"), + "plugins/DevicesDetection/Columns/DeviceType.php" => array("1861", "65e7342923ed7957b4983541aafbf8c7"), + "plugins/DevicesDetection/Columns/Os.php" => array("1476", "6303b811b267798c175cb4c5fa5a9dfa"), + "plugins/DevicesDetection/Columns/OsVersion.php" => array("1343", "0b2851ccf888d98cb88484c0dcad357b"), + "plugins/DevicesDetection/Controller.php" => array("6035", "87f22243ea808fbf20b4231dfa0e4fc8"), + "plugins/DevicesDetection/DevicesDetection.php" => array("2688", "a53c76dd95e66f43d76d8e53254b9299"), + "plugins/DevicesDetection/functions.php" => array("10700", "552e40994e88c324ee825bcf0eecff0c"), + "plugins/DevicesDetection/images/brand/3Q.ico" => array("577", "4e6805d57df1ebf020adb31c6ea11453"), "plugins/DevicesDetection/images/brand/Acer.ico" => array("673", "5453e4cc0e9fddd4aac446d0b10d4e36"), "plugins/DevicesDetection/images/brand/Alcatel.ico" => array("577", "df5ccd4326721199d02870aab0e225c3"), "plugins/DevicesDetection/images/brand/Apple.ico" => array("1179", "3b58ada0634f0a1687a44c15e4e1936f"), @@ -1198,22 +2554,39 @@ class Manifest { "plugins/DevicesDetection/images/brand/Audiovox.ico" => array("807", "5e684babc5c21bf7a8255a0ad0f9545a"), "plugins/DevicesDetection/images/brand/Avvio.ico" => array("964", "c4a27544c3af4093265a0cbb45cd5b31"), "plugins/DevicesDetection/images/brand/BangOlufsen.ico" => array("3692", "e062a24b4ed0af1fe513053eb2e70750"), + "plugins/DevicesDetection/images/brand/Barnes_Noble.ico" => array("799", "5cbde4b9ca8d45608511ef0a902e10bd"), + "plugins/DevicesDetection/images/brand/BBK.ico" => array("263", "a69543b93cb366344658a8ba1ba86309"), "plugins/DevicesDetection/images/brand/Becker.ico" => array("519", "6c5ed512b9de01a0d60d4efcc78b1ea0"), "plugins/DevicesDetection/images/brand/Beetel.ico" => array("1645", "034453672fc6b71827ea0126248843b8"), "plugins/DevicesDetection/images/brand/BenQ.ico" => array("846", "70f0361f99a39dcdde7e1731364e19c9"), + "plugins/DevicesDetection/images/brand/bq.ico" => array("497", "1c556a4caeeb525cd7cedfb1ef6836c4"), "plugins/DevicesDetection/images/brand/Cat.ico" => array("809", "2b5844049936b7e33220ba80accfabfb"), + "plugins/DevicesDetection/images/brand/Celkon.ico" => array("332", "57c1d3ba5d44d33fec13e0a070ecaaa5"), + "plugins/DevicesDetection/images/brand/Cherry_Mobile.ico" => array("808", "6970cd108eaae433cc3ba28efd420fb5"), "plugins/DevicesDetection/images/brand/CnM.ico" => array("421", "6a3a2122bcab660df2d010087914b7b7"), "plugins/DevicesDetection/images/brand/Compal.ico" => array("432", "a7190dfd9e3b833a17bd9576b2fc47a0"), + "plugins/DevicesDetection/images/brand/Compaq.ico" => array("453", "f1ef4d435d8255a9304a2685fd61afb1"), + "plugins/DevicesDetection/images/brand/ConCorde.ico" => array("602", "a5875a21f0de5cd257b63dec7cd891fa"), + "plugins/DevicesDetection/images/brand/Coolpad.ico" => array("485", "8820e42152f8d2e2d2dfda8343bb6944"), "plugins/DevicesDetection/images/brand/CreNova.ico" => array("3142", "b34cd9a39800b0e0109ab291368e3177"), "plugins/DevicesDetection/images/brand/Cricket.ico" => array("1483", "6b3f14fc16f8b9e61cbf4eaf4af9efbf"), + "plugins/DevicesDetection/images/brand/Crius_Mea.ico" => array("566", "105e726178c68022b7787b56be36cdd9"), + "plugins/DevicesDetection/images/brand/Crosscall.ico" => array("3236", "1bf22b8590c00df8c6c7587126b5079b"), + "plugins/DevicesDetection/images/brand/Danew.ico" => array("3221", "45a67aca345dff2b0fb805b19646f780"), "plugins/DevicesDetection/images/brand/Dell.ico" => array("886", "10a5c1a530dc6a0758dec93fea8a08ff"), "plugins/DevicesDetection/images/brand/Denver.ico" => array("552", "6efa83cf0f661e769272515970bcbc97"), "plugins/DevicesDetection/images/brand/DMM.ico" => array("3623", "3a154f04b274d2e67ac9298ca2df08fd"), "plugins/DevicesDetection/images/brand/DoCoMo.ico" => array("636", "28df0a65fbcb99b0e2ca93c6cdcc8e38"), + "plugins/DevicesDetection/images/brand/Easypix.ico" => array("881", "e206cd5a0003d2a5317f30820090098c"), "plugins/DevicesDetection/images/brand/Ericsson.ico" => array("684", "de92f235c5d09fa7b1140538058c8bcf"), "plugins/DevicesDetection/images/brand/eTouch.ico" => array("889", "779c7c6654749cffea8363ed1c6ee971"), + "plugins/DevicesDetection/images/brand/Evertek.ico" => array("571", "afd0b26b99880536bf2f2b4a108a4053"), "plugins/DevicesDetection/images/brand/Fly.ico" => array("572", "d608aa4b0b9a861c77501c744aa4c275"), + "plugins/DevicesDetection/images/brand/Fujitsu.ico" => array("298", "3f19cc02a2c34b3907bb6b05be210809"), "plugins/DevicesDetection/images/brand/Gemini.ico" => array("323", "07def536ff813416b91b196660b1f4a2"), + "plugins/DevicesDetection/images/brand/Gigabyte.ico" => array("343", "35554b850966ef76fcac3e4c51f67a0c"), + "plugins/DevicesDetection/images/brand/Gigaset.ico" => array("354", "5bbcaaa121cc02d14454374ea84f2bbf"), + "plugins/DevicesDetection/images/brand/Gionee.ico" => array("3018", "9ccf5a406b6343afaa198bdda3694437"), "plugins/DevicesDetection/images/brand/Google.ico" => array("863", "ad67a48c2dc917325f9cb38a88f8a8a3"), "plugins/DevicesDetection/images/brand/Gradiente.ico" => array("1012", "25ed337d67bf8d6fdb5012b331aadd05"), "plugins/DevicesDetection/images/brand/Grundig.ico" => array("3029", "afae28b8f05bae1e8c476fc3cdb6b5ab"), @@ -1222,6 +2595,8 @@ class Manifest { "plugins/DevicesDetection/images/brand/HTC.ico" => array("1161", "b0b6419c96392cbaa646129a9f609f51"), "plugins/DevicesDetection/images/brand/Huawei.ico" => array("1022", "cf2cca917f1b7655f69ebf439d464823"), "plugins/DevicesDetection/images/brand/Humax.ico" => array("3010", "cb8aee651c6434c0ff6630e609808db9"), + "plugins/DevicesDetection/images/brand/Hyundai.ico" => array("407", "4e16da1cebbc4ad229b3a6f564040021"), + "plugins/DevicesDetection/images/brand/iBerry.ico" => array("3613", "edf0a22ec58c58a6f55adbfc19c84139"), "plugins/DevicesDetection/images/brand/Ikea.ico" => array("3291", "56fc079b565f603be4bea0faa90dfb71"), "plugins/DevicesDetection/images/brand/i-mobile.ico" => array("615", "15a5cf09f8e2ad6d60956ae07086b50c"), "plugins/DevicesDetection/images/brand/INQ.ico" => array("1059", "7ece6e5474c9ab6fdcda278c7e341b81"), @@ -1234,6 +2609,7 @@ class Manifest { "plugins/DevicesDetection/images/brand/Kyocera.ico" => array("639", "ca13b59af47f69c5ac6917cef9512add"), "plugins/DevicesDetection/images/brand/Lanix.ico" => array("437", "fb1e54c478ce9d908a08e4a0b1005c5c"), "plugins/DevicesDetection/images/brand/Lenovo.ico" => array("237", "42abac25970f9750ec395cefffa30b9d"), + "plugins/DevicesDetection/images/brand/Le_Pan.ico" => array("408", "f1b045262e5c504b3f5ecda27c748304"), "plugins/DevicesDetection/images/brand/LG.ico" => array("1510", "79a2c6d36a1ba70423162b4c1fe46ec2"), "plugins/DevicesDetection/images/brand/LGUPlus.ico" => array("1081", "337b3f1e7368a44b7c40dd8e53940445"), "plugins/DevicesDetection/images/brand/Loewe.ico" => array("2938", "e5ec4483d155267a2944dadad9d9235e"), @@ -1246,14 +2622,17 @@ class Manifest { "plugins/DevicesDetection/images/brand/Mio.ico" => array("753", "9f53b557757e285f1de1fe7b67434406"), "plugins/DevicesDetection/images/brand/Mitsubishi.ico" => array("342", "a0d050b3c27c242d11a1a58f30e58ff9"), "plugins/DevicesDetection/images/brand/Motorola.ico" => array("465", "0561bdea0b5842d6c9bc0301bde19b0f"), + "plugins/DevicesDetection/images/brand/MSI.ico" => array("377", "4e086d7fec17fc4ff0bd721744d109b1"), "plugins/DevicesDetection/images/brand/MyPhone.ico" => array("933", "84cd21bc7363dda6be3a1181be22de6b"), "plugins/DevicesDetection/images/brand/NEC.ico" => array("450", "a3ae0709656decaaf958595aed950f4d"), "plugins/DevicesDetection/images/brand/Nexian.ico" => array("2041", "4001957b806ce17dcaca59b9b852be8a"), "plugins/DevicesDetection/images/brand/NGM.ico" => array("1298", "3cbe7ef2372c4074ee2734aa3a58b328"), + "plugins/DevicesDetection/images/brand/Nikon.ico" => array("607", "546e5c33c7901edd71435d4ff8d14662"), "plugins/DevicesDetection/images/brand/Nintendo.ico" => array("740", "7bdf9ff565d9cc45a08824a6bade6f47"), "plugins/DevicesDetection/images/brand/Nokia.ico" => array("1283", "ae41cc8ec06c6e81d9f5afadf6097f5b"), "plugins/DevicesDetection/images/brand/O2.ico" => array("768", "36b1b15cc750a45b5c2b6edc80594bbd"), "plugins/DevicesDetection/images/brand/Onda.ico" => array("732", "2a8e1c31d12c2cf27c1931a13fd178df"), + "plugins/DevicesDetection/images/brand/OnePlus.ico" => array("181", "307796de7745773249cad3962d6ce6ed"), "plugins/DevicesDetection/images/brand/OPPO.ico" => array("870", "4128f2c75414aa765adc5866bb627635"), "plugins/DevicesDetection/images/brand/Orange.ico" => array("461", "21a6df281c10a240cbc134f79ea0d6eb"), "plugins/DevicesDetection/images/brand/Panasonic.ico" => array("3649", "71da2a84a1fdf04abd130562699a9ab4"), @@ -1262,14 +2641,18 @@ class Manifest { "plugins/DevicesDetection/images/brand/Philips.ico" => array("3749", "2213ce1a4b99c4af0a53dff146aca457"), "plugins/DevicesDetection/images/brand/Polaroid.ico" => array("737", "c6703ba6a2d008aa2197df33370e124c"), "plugins/DevicesDetection/images/brand/PolyPad.ico" => array("1381", "35f641a4463636392646a476a4ffbfbd"), + "plugins/DevicesDetection/images/brand/Quechua.ico" => array("296", "f20d9d68e393691e8974cd3c6b9cf78d"), "plugins/DevicesDetection/images/brand/RIM.ico" => array("705", "e5660baaa9484cddcd779e16672d48ce"), "plugins/DevicesDetection/images/brand/Sagem.ico" => array("694", "6d0664111d655a222cf7ec86cebd7750"), "plugins/DevicesDetection/images/brand/Samsung.ico" => array("3095", "988bb1039ac4b49d767dcfbdff86172a"), "plugins/DevicesDetection/images/brand/Sanyo.ico" => array("639", "ca13b59af47f69c5ac6917cef9512add"), "plugins/DevicesDetection/images/brand/Sega.ico" => array("706", "d73c2098167d02eb81c7b83cfe5787a2"), "plugins/DevicesDetection/images/brand/Selevision.ico" => array("3497", "c920706882f4d553619e275f0d336423"), + "plugins/DevicesDetection/images/brand/Sencor.ico" => array("885", "f57169b5881e9b166c4acc761da2f503"), + "plugins/DevicesDetection/images/brand/SFR.ico" => array("686", "7de3efe1dee578907ffc1b653653be3f"), "plugins/DevicesDetection/images/brand/Sharp.ico" => array("403", "ce78aee244f948bba92bfbf91397448b"), "plugins/DevicesDetection/images/brand/Siemens.ico" => array("395", "a82f9c7c51665f04cad1a2a4a69bc590"), + "plugins/DevicesDetection/images/brand/Smartfren.ico" => array("691", "6df4c52756c13a8408062e6198f37d81"), "plugins/DevicesDetection/images/brand/Smart.ico" => array("3419", "04bc938b9b436ac140922f399767fd9f"), "plugins/DevicesDetection/images/brand/Softbank.ico" => array("381", "8c42b583b4e1b05900b06698a65678c5"), "plugins/DevicesDetection/images/brand/Sony_Ericsson.ico" => array("628", "7c8230f2d8dc34c6c5761aa9db204588"), @@ -1278,23 +2661,213 @@ class Manifest { "plugins/DevicesDetection/images/brand/TCL.ico" => array("2927", "207121f24b1b59c9e3e2ae43ad420f97"), "plugins/DevicesDetection/images/brand/TechniSat.ico" => array("3347", "08762844360b5220aeb01e23dd6ea410"), "plugins/DevicesDetection/images/brand/TechnoTrend.ico" => array("3500", "65748765610b09c759b739f1a97dc8ca"), + "plugins/DevicesDetection/images/brand/Tecno_Mobile.ico" => array("437", "7f512b7ce5211a9961df2360ef8078b1"), "plugins/DevicesDetection/images/brand/Telefunken.ico" => array("3651", "677bdb0ce07503bf11c437896148332d"), "plugins/DevicesDetection/images/brand/Telit.ico" => array("527", "419e9ae7ac193023e1b0082fd96bc5df"), + "plugins/DevicesDetection/images/brand/teXet.ico" => array("643", "cdaebbdf23e79a6f985d9778c6d7fa85"), "plugins/DevicesDetection/images/brand/Thomson.ico" => array("2974", "bff62a739264a6a81f8447a1aaf1d955"), "plugins/DevicesDetection/images/brand/TiPhone.ico" => array("1179", "d5e6eeafbbde1409d878c6b17a6bba9e"), "plugins/DevicesDetection/images/brand/T-Mobile.ico" => array("499", "d7869b7ce833ed1d9db2f85dfb0140b5"), + "plugins/DevicesDetection/images/brand/Tolino.ico" => array("321", "6b571e7487c4324191d96b7c00135612"), "plugins/DevicesDetection/images/brand/Toshiba.ico" => array("248", "f96539816d5b1d433bd5e8c4f1222970"), - "plugins/DevicesDetection/images/brand/unknown.ico" => array("1077", "32104fb21cf3f82b68d30bee64972ea7"), + "plugins/DevicesDetection/images/brand/Tunisie_Telecom.ico" => array("3463", "a4d1987b111a99c5f2099135c65ecfc4"), + "plugins/DevicesDetection/images/brand/Unknown.ico" => array("1077", "32104fb21cf3f82b68d30bee64972ea7"), "plugins/DevicesDetection/images/brand/Vertu.ico" => array("387", "f87d83b0a636bb325aefddcf436d50c4"), "plugins/DevicesDetection/images/brand/Vestel.ico" => array("3096", "33dcd35a24a21eb0f660b26fcc41bca1"), "plugins/DevicesDetection/images/brand/Videocon.ico" => array("617", "c9948a26325d7e73f4eb287e1475cb82"), "plugins/DevicesDetection/images/brand/Videoweb.ico" => array("3129", "ca8e2df12c63f4799f69f74e141319e2"), "plugins/DevicesDetection/images/brand/ViewSonic.ico" => array("605", "537cb66bf7d13d8be07efefc178af164"), "plugins/DevicesDetection/images/brand/Voxtel.ico" => array("222", "93267b0d0b38e7b47f2dccb7bd791171"), + "plugins/DevicesDetection/images/brand/Wiko.ico" => array("1558", "f62fced5dd30dca4f465dbea1e15e93c"), + "plugins/DevicesDetection/images/brand/Wolder.ico" => array("513", "2d12bf1e01f70fe84b18390490c05eef"), + "plugins/DevicesDetection/images/brand/Woxter.ico" => array("775", "2e2c29c9842568b24f30d770af122897"), "plugins/DevicesDetection/images/brand/Xiaomi.ico" => array("492", "fe0ef8c5a55aeb5e33067b051ca87fd7"), + "plugins/DevicesDetection/images/brand/Yarvik.ico" => array("439", "8976fb2d78300f08607decd40ec70d87"), "plugins/DevicesDetection/images/brand/Yuandao.ico" => array("639", "866dddeae2afafd1a86a28b3621962f3"), "plugins/DevicesDetection/images/brand/Zonda.ico" => array("371", "43bfb6a3cfca4edd0bf27bbf21e5baed"), + "plugins/DevicesDetection/images/brand/Zopo.ico" => array("397", "73f2422b25150e81c3de33740061f250"), "plugins/DevicesDetection/images/brand/ZTE.ico" => array("555", "1f124412f72d709497dd3f4ad50b0761"), + "plugins/DevicesDetection/images/browsers/36.gif" => array("1036", "2f34abe9fe98903bc202d3e8aeca8578"), + "plugins/DevicesDetection/images/browsers/AA.gif" => array("1092", "dc62eff78dac919b00e60c5fb3e6266d"), + "plugins/DevicesDetection/images/browsers/AB.gif" => array("1064", "1123da862a558b1ccd8f9008c5c4fdcb"), + "plugins/DevicesDetection/images/browsers/AG.gif" => array("351", "6e793ad6ad5c69abc499422d6a43d836"), + "plugins/DevicesDetection/images/browsers/AM.gif" => array("198", "18c54fc3197f6e1533c06b0923db3bd0"), + "plugins/DevicesDetection/images/browsers/AN.gif" => array("144", "a1264f47256ff5b0d4feeeac833e7a96"), + "plugins/DevicesDetection/images/browsers/AR.gif" => array("1057", "377249d199156ee602e669c0afc02945"), + "plugins/DevicesDetection/images/browsers/AV.gif" => array("151", "f459c84d6ab90fdf0f35d20f9f82626d"), + "plugins/DevicesDetection/images/browsers/AW.gif" => array("574", "e4e4fa2ef432f2a86086ec58f4b27ab1"), + "plugins/DevicesDetection/images/browsers/B2.gif" => array("1070", "e1b9f6b47cffbdf05b4159d6a31b21ae"), + "plugins/DevicesDetection/images/browsers/BB.gif" => array("576", "d40bd99ba1dd881b164907341ec2079d"), + "plugins/DevicesDetection/images/browsers/BD.gif" => array("1051", "4a8ebfcd7aad4c1004b7f82b954b0a69"), + "plugins/DevicesDetection/images/browsers/BE.gif" => array("1042", "103994d17de92aa261c8034a8e35a84f"), + "plugins/DevicesDetection/images/browsers/BJ.gif" => array("949", "4ddb1a5385c2954489f2cbc7dc73c8a2"), + "plugins/DevicesDetection/images/browsers/BP.gif" => array("1070", "e1b9f6b47cffbdf05b4159d6a31b21ae"), + "plugins/DevicesDetection/images/browsers/BS.gif" => array("980", "36f1831f70f7c8efc44d534c6adb63a5"), + "plugins/DevicesDetection/images/browsers/BX.gif" => array("522", "7302ad862f4007c23efb73acbd41f5c0"), + "plugins/DevicesDetection/images/browsers/CA.gif" => array("573", "739fca054b61f68657b0bd349c958a86"), + "plugins/DevicesDetection/images/browsers/CC.gif" => array("435", "adfa238c50cfb4f8c1c64b6affbf238e"), + "plugins/DevicesDetection/images/browsers/CD.gif" => array("1045", "b5484f5fc254abd52cdc94f378be977a"), + "plugins/DevicesDetection/images/browsers/CF.gif" => array("1074", "e349a7dbdadf2fca33cca13287b0eba8"), + "plugins/DevicesDetection/images/browsers/CH.gif" => array("1074", "e349a7dbdadf2fca33cca13287b0eba8"), + "plugins/DevicesDetection/images/browsers/CK.gif" => array("1024", "b6d4ebb0394c48dfcb5f21475de5c79b"), + "plugins/DevicesDetection/images/browsers/CM.gif" => array("1074", "e349a7dbdadf2fca33cca13287b0eba8"), + "plugins/DevicesDetection/images/browsers/CN.gif" => array("998", "cd878afb8cc56e7c8c6caef6d5fb2ba4"), + "plugins/DevicesDetection/images/browsers/CO.gif" => array("1042", "195487c9db2e19ffae3fd443b9266e62"), + "plugins/DevicesDetection/images/browsers/CP.gif" => array("998", "af47e47253591c272a458c4645fbaf5c"), + "plugins/DevicesDetection/images/browsers/CR.gif" => array("1007", "dd242a922d2430d211a75a1a2973e2bb"), + "plugins/DevicesDetection/images/browsers/CS.gif" => array("549", "eb5151f2f46fc09d687ce27becefb831"), + "plugins/DevicesDetection/images/browsers/CX.gif" => array("1067", "a5f9bb2739b6b66ea01568a4002f6f45"), + "plugins/DevicesDetection/images/browsers/DE.gif" => array("1073", "ee3f8b86d881b9ef130e08d90e6bcdfd"), + "plugins/DevicesDetection/images/browsers/DF.gif" => array("545", "f4b65ebcf304f1675088d029ed613d28"), + "plugins/DevicesDetection/images/browsers/DI.gif" => array("1068", "4eceaf5fd7808c422b67026c9329e16f"), + "plugins/DevicesDetection/images/browsers/EL.gif" => array("90", "b515db820d883f921d05d15a34dda7f9"), + "plugins/DevicesDetection/images/browsers/EP.gif" => array("316", "660436cc97429ef52365a01084b40ee0"), + "plugins/DevicesDetection/images/browsers/ES.gif" => array("1013", "92ada780fce5e3ffc318de634eba4d21"), + "plugins/DevicesDetection/images/browsers/FB.gif" => array("254", "24663f1949bae4fb7ecd0504c9c872fd"), + "plugins/DevicesDetection/images/browsers/FD.gif" => array("1050", "a569dc9dbe1b95ce2763bee8dbdc5850"), + "plugins/DevicesDetection/images/browsers/FE.gif" => array("550", "a016a52d476c4be7943e68ccf2056db1"), + "plugins/DevicesDetection/images/browsers/FF.gif" => array("197", "6b4eecf673d5461aebdd432bca086e8c"), + "plugins/DevicesDetection/images/browsers/FL.gif" => array("1034", "0ce889dc81db377eeb00f76f72942274"), + "plugins/DevicesDetection/images/browsers/FN.gif" => array("1033", "e87473b14680939e58a9a94e9dcd39e3"), + "plugins/DevicesDetection/images/browsers/GA.gif" => array("159", "576c4646cf6938dd2079516293d3a3f9"), + "plugins/DevicesDetection/images/browsers/GE.gif" => array("997", "a3d96e8576f273ecc4a864d24620fbf2"), + "plugins/DevicesDetection/images/browsers/HA.gif" => array("1009", "fccae707e311bd009be90a7b38000082"), + "plugins/DevicesDetection/images/browsers/HJ.gif" => array("1022", "8c3019d1e0867e8455d0abf1ad3eb531"), + "plugins/DevicesDetection/images/browsers/IA.gif" => array("391", "1836a7db0bc6a4a4d8b3d6063346e3f5"), + "plugins/DevicesDetection/images/browsers/IB.gif" => array("168", "b091c3e8ce2789017d581089028fa1cc"), + "plugins/DevicesDetection/images/browsers/IC.gif" => array("131", "26a6ff98d316092214c9dc9e7be45224"), + "plugins/DevicesDetection/images/browsers/ID.gif" => array("1057", "6f1f33dc4bd104e0a60000ce49e719a9"), + "plugins/DevicesDetection/images/browsers/IE.gif" => array("999", "5e002ee72167a3a78e2252766fde1046"), + "plugins/DevicesDetection/images/browsers/IM.gif" => array("999", "5e002ee72167a3a78e2252766fde1046"), + "plugins/DevicesDetection/images/browsers/IR.gif" => array("610", "565b1e3acd514c1c9b88d6c2b0ec0c4d"), + "plugins/DevicesDetection/images/browsers/IW.gif" => array("1066", "8d3376b6699ccd4533191b38d7f6b8c4"), + "plugins/DevicesDetection/images/browsers/KI.gif" => array("1050", "497b4bc9b58c113ae72763559321071b"), + "plugins/DevicesDetection/images/browsers/KM.gif" => array("180", "3daa5fa7553d448cd280612491e8a6ea"), + "plugins/DevicesDetection/images/browsers/KO.gif" => array("986", "0d15a2d4a73582d1df109e0ff3463fd3"), + "plugins/DevicesDetection/images/browsers/KP.gif" => array("1037", "f29fb41537f7df91d827c3f6eea076a0"), + "plugins/DevicesDetection/images/browsers/KZ.gif" => array("1061", "cdc654ad5f3f5fdda6ac69ea7d7dbe31"), + "plugins/DevicesDetection/images/browsers/LB.gif" => array("991", "ff5c4fad260f28ae39bdcac81c2a3309"), + "plugins/DevicesDetection/images/browsers/LG.gif" => array("1015", "6b282f2f040962adb07446579435f777"), + "plugins/DevicesDetection/images/browsers/LI.gif" => array("104", "a3b302b3fffc9ed032add149d4ace210"), + "plugins/DevicesDetection/images/browsers/LS.gif" => array("1086", "c61646736ea4872a7e58bcd29716d778"), + "plugins/DevicesDetection/images/browsers/LX.gif" => array("104", "a3b302b3fffc9ed032add149d4ace210"), + "plugins/DevicesDetection/images/browsers/MC.gif" => array("1023", "0e17db9ed1e06feb1d23d134ce34693d"), + "plugins/DevicesDetection/images/browsers/MF.gif" => array("190", "589361249f74319b57ea98d6408bc4b3"), + "plugins/DevicesDetection/images/browsers/MI.gif" => array("1025", "5e63fceac90a88f1db14b4e8ee44201d"), + "plugins/DevicesDetection/images/browsers/MO.gif" => array("192", "67b5dac21e8f2243a955f1d9df7ef67e"), + "plugins/DevicesDetection/images/browsers/MS.gif" => array("1094", "265861a05c27b23013cb6ae3c428dff0"), + "plugins/DevicesDetection/images/browsers/MU.gif" => array("1031", "a32232145133bfeb871f8075dc45192a"), + "plugins/DevicesDetection/images/browsers/MX.gif" => array("985", "4f6f87c42bf5c6bfc2b63925da5e40c1"), + "plugins/DevicesDetection/images/browsers/NB.gif" => array("977", "d2fac7549889df9f1c0863b424543c6f"), + "plugins/DevicesDetection/images/browsers/NF.gif" => array("612", "7cb0d2713e9faf25b766ca0d13cf456b"), + "plugins/DevicesDetection/images/browsers/NL.gif" => array("1081", "f66412328676120ba3cc0eb987c16158"), + "plugins/DevicesDetection/images/browsers/NP.gif" => array("1020", "ba7d68a0f9c11647abba2f8454a7c34c"), + "plugins/DevicesDetection/images/browsers/NS.gif" => array("98", "cd8d53ec12b64294d16769dfeeaf07c7"), + "plugins/DevicesDetection/images/browsers/OB.gif" => array("1010", "67b1d28deccc92200bdc3ce4a86612c3"), + "plugins/DevicesDetection/images/browsers/OE.gif" => array("562", "2afc1c7fbbf76cce942dd7a48f3ac63f"), + "plugins/DevicesDetection/images/browsers/OF.gif" => array("861", "289d8052246f66e09e8094de1d8a798f"), + "plugins/DevicesDetection/images/browsers/OI.gif" => array("911", "c574ee579047bd1a1b7f95f03d7be6b3"), + "plugins/DevicesDetection/images/browsers/ON.gif" => array("635", "f7d7eb7f8cec24f0c192e30fe29ea320"), + "plugins/DevicesDetection/images/browsers/OP.gif" => array("987", "ac0432440ad48154a6434675b1d9c27a"), + "plugins/DevicesDetection/images/browsers/OR.gif" => array("1024", "30e874c346325cd40cf58f98944ea603"), + "plugins/DevicesDetection/images/browsers/OV.gif" => array("978", "7e98fecee01438d561b791339281bd47"), + "plugins/DevicesDetection/images/browsers/OW.gif" => array("197", "b66e88cbb941f9ac326f43e0d993e572"), + "plugins/DevicesDetection/images/browsers/PL.gif" => array("1058", "759fa0100429b3b3a4dc88894454fd8a"), + "plugins/DevicesDetection/images/browsers/PM.gif" => array("1082", "271bd4b89d06a9f9a9553b5da9053bd3"), + "plugins/DevicesDetection/images/browsers/PO.gif" => array("1065", "ac773ea28693335de8e8ac62f7d20a0d"), + "plugins/DevicesDetection/images/browsers/PU.gif" => array("1094", "1ea4a15e1b326c28158b137e4e8d07af"), + "plugins/DevicesDetection/images/browsers/PW.gif" => array("1082", "ac66922861d77e9949a72f86622e0c2f"), + "plugins/DevicesDetection/images/browsers/PX.gif" => array("170", "0bd86aa95e1ae0d5975cdc2d93210e30"), + "plugins/DevicesDetection/images/browsers/QQ.gif" => array("1080", "72bb5a454a57da4ca306c9e3a6b41ac0"), + "plugins/DevicesDetection/images/browsers/RK.gif" => array("1035", "6b87220087449e134062214fbf72f5a8"), + "plugins/DevicesDetection/images/browsers/SA.gif" => array("1008", "921467088fc56c5c9cdd0bb6e58250bf"), + "plugins/DevicesDetection/images/browsers/SE.gif" => array("996", "c7a508db72e42174b83fb93cb85038d3"), + "plugins/DevicesDetection/images/browsers/SF.gif" => array("190", "589361249f74319b57ea98d6408bc4b3"), + "plugins/DevicesDetection/images/browsers/SH.gif" => array("1001", "ed645727ac6373acd25a66789a3533ee"), + "plugins/DevicesDetection/images/browsers/SL.gif" => array("900", "1210e399e978978390cfdfd9d79159e6"), + "plugins/DevicesDetection/images/browsers/SM.gif" => array("391", "1836a7db0bc6a4a4d8b3d6063346e3f5"), + "plugins/DevicesDetection/images/browsers/SR.gif" => array("1013", "bbc604df7eda9029c3ce279c6b804600"), + "plugins/DevicesDetection/images/browsers/TB.gif" => array("1014", "79bf7ed3ad92d3da09737d2fcd3913aa"), + "plugins/DevicesDetection/images/browsers/TI.gif" => array("595", "2c09db5f54b47472971d863e183159a8"), + "plugins/DevicesDetection/images/browsers/TZ.gif" => array("973", "5858a8b149e45749424bdf2da7ebefa3"), + "plugins/DevicesDetection/images/browsers/UC.gif" => array("994", "d9622ea01cb9093592858da53443c200"), + "plugins/DevicesDetection/images/browsers/UN.gif" => array("80", "c4a7f0e333a6079fdb0b6595e11bca74"), + "plugins/DevicesDetection/images/browsers/UNK.gif" => array("80", "c4a7f0e333a6079fdb0b6595e11bca74"), + "plugins/DevicesDetection/images/browsers/VI.gif" => array("2124", "b03cddacfc436367fd83f409249a8a75"), + "plugins/DevicesDetection/images/browsers/WE.gif" => array("1012", "cce9216ee7bd3ef52a46003d249ab540"), + "plugins/DevicesDetection/images/browsers/WO.gif" => array("1065", "ed1504717c9af523e30c33908126c4ad"), + "plugins/DevicesDetection/images/browsers/WP.gif" => array("982", "5bba1edfb42ce1b96551f81af0be08a1"), + "plugins/DevicesDetection/images/browsers/YA.gif" => array("1048", "8d94386ab4796664de7b897dd2106c9c"), + "plugins/DevicesDetection/images/os/3DS.gif" => array("1085", "262b44579aadcf90973653ff3e759cc7"), + "plugins/DevicesDetection/images/os/AIX.gif" => array("176", "58a60503a8e92493153694d1d97d2f6d"), + "plugins/DevicesDetection/images/os/AMG.gif" => array("1001", "5d67b7bb52ed746480573c1600d9a34c"), + "plugins/DevicesDetection/images/os/AMI.gif" => array("1055", "ef341c4cc2ec3bbf860c7d3bfe685326"), + "plugins/DevicesDetection/images/os/AND.gif" => array("144", "a1264f47256ff5b0d4feeeac833e7a96"), + "plugins/DevicesDetection/images/os/ARL.gif" => array("947", "913d273e01b9031f5113fb82ce63a591"), + "plugins/DevicesDetection/images/os/BBX.gif" => array("590", "e5cff6836abf100d9d8310d9dbb9f5d4"), + "plugins/DevicesDetection/images/os/BEO.gif" => array("1035", "ae4420933ac47a072d0f47759ef830c2"), + "plugins/DevicesDetection/images/os/BLB.gif" => array("576", "d40bd99ba1dd881b164907341ec2079d"), + "plugins/DevicesDetection/images/os/BMP.gif" => array("1001", "b9fa97bf32b038698bffe57974979e85"), + "plugins/DevicesDetection/images/os/BSD.gif" => array("1016", "1dc9b76bb3fc8f5529e9abe9ac8841fa"), + "plugins/DevicesDetection/images/os/BTR.gif" => array("946", "cbf9b74ee2db7714ed6d6432eff3f7c8"), + "plugins/DevicesDetection/images/os/CES.gif" => array("1011", "0cdd142972c3cc89b2ceb3b7b97e1321"), + "plugins/DevicesDetection/images/os/COS.gif" => array("1074", "e349a7dbdadf2fca33cca13287b0eba8"), + "plugins/DevicesDetection/images/os/DFB.gif" => array("326", "d61f11a900d520ef515eaa139176a5f1"), + "plugins/DevicesDetection/images/os/DSI.gif" => array("1076", "5c475f3ba76f4ec3b626e720574bcb37"), + "plugins/DevicesDetection/images/os/FED.gif" => array("1022", "e86e6f5aec6c32de7eca913a9f91c3ab"), + "plugins/DevicesDetection/images/os/FOS.gif" => array("197", "6b4eecf673d5461aebdd432bca086e8c"), + "plugins/DevicesDetection/images/os/GNT.gif" => array("1075", "4196a85df43e6a5593941dcf8262416f"), + "plugins/DevicesDetection/images/os/GTV.gif" => array("1614", "a032dd001e1a5755201a6263a669ca49"), + "plugins/DevicesDetection/images/os/HPX.gif" => array("191", "999717c37d76ca099a06cf77a781bdbf"), + "plugins/DevicesDetection/images/os/IOS.gif" => array("594", "5c21bb970373a93c8876c50d43f6f231"), + "plugins/DevicesDetection/images/os/IPA.gif" => array("587", "9f247437bc140cc6e70ec59f34bcddb1"), + "plugins/DevicesDetection/images/os/IPD.gif" => array("351", "a215ada2aefcbca876055fe7a2f7c039"), + "plugins/DevicesDetection/images/os/IPH.gif" => array("577", "49805f402375692d40635ada7cc0f472"), + "plugins/DevicesDetection/images/os/IRI.gif" => array("152", "5e631b5adc35a05ae0d85815829c6f48"), + "plugins/DevicesDetection/images/os/KBT.gif" => array("998", "6ecd8b978a51fb4fd6ec94f9b820ae1d"), + "plugins/DevicesDetection/images/os/KNO.gif" => array("985", "b4595a673edf60051636fedf418eda30"), + "plugins/DevicesDetection/images/os/LBT.gif" => array("951", "ed14ac9707a0e01f4557ac9e8508dea3"), + "plugins/DevicesDetection/images/os/LIN.gif" => array("170", "19039ee87d8fccdba6a391ada5656de7"), + "plugins/DevicesDetection/images/os/MAC.gif" => array("171", "03548481597f28751368e26be49aea99"), + "plugins/DevicesDetection/images/os/MAE.gif" => array("137", "84600277bad6751b68b15586bca50aef"), + "plugins/DevicesDetection/images/os/MDR.gif" => array("918", "62f5e501d28e25fff6c3a75631c7c208"), + "plugins/DevicesDetection/images/os/MIN.gif" => array("1009", "153385eff242c4e10ea3835a17a65f0e"), + "plugins/DevicesDetection/images/os/NBS.gif" => array("168", "fc0d4fcb57c98f3a4dd6eab8e71ebd6a"), + "plugins/DevicesDetection/images/os/NDS.gif" => array("1061", "16bc6e0960747b402441c40e419a7d53"), + "plugins/DevicesDetection/images/os/OBS.gif" => array("571", "fcdb547b7ab768e131ba592e8733c75c"), + "plugins/DevicesDetection/images/os/OS2.gif" => array("162", "ee37bab155ad46f2530e7586f9971656"), + "plugins/DevicesDetection/images/os/POS.gif" => array("1060", "96b06842dc1cc80a8bb283ee8f4be320"), + "plugins/DevicesDetection/images/os/PPY.gif" => array("1037", "1bc770c1bd83e6cfdc5087b00b44cb9d"), + "plugins/DevicesDetection/images/os/PS3.gif" => array("628", "7aca5b93e7cc8142e2ab578e7aae7dc8"), + "plugins/DevicesDetection/images/os/PSP.gif" => array("592", "f92da90c6c6ea808422184c314215465"), + "plugins/DevicesDetection/images/os/PSV.gif" => array("200", "d82e64a4f0aeec6e4931a41988680af3"), + "plugins/DevicesDetection/images/os/QNX.gif" => array("241", "51ceb87cd6268837d830b225cb6c8120"), + "plugins/DevicesDetection/images/os/RHT.gif" => array("952", "72c775bb0f2b8ad388ee513577abc8ec"), + "plugins/DevicesDetection/images/os/ROS.gif" => array("956", "9a294e5c701171c8c777ce563d88d924"), + "plugins/DevicesDetection/images/os/SAF.gif" => array("242", "600259fe739536945b6f5cd5c9d3489b"), + "plugins/DevicesDetection/images/os/SBA.gif" => array("990", "be3cfde24b6517884d73270a20e9a06f"), + "plugins/DevicesDetection/images/os/SLW.gif" => array("883", "37ca3a7bdb0eeea402252c80dca89369"), + "plugins/DevicesDetection/images/os/SOS.gif" => array("1036", "686261ea170398b2eb078b67a24f4276"), + "plugins/DevicesDetection/images/os/SSE.gif" => array("1066", "8a6b48a38ee8ecca0fd14092872dba12"), + "plugins/DevicesDetection/images/os/SYL.gif" => array("1017", "5c73fe766f50d4a697dbdeac254db9e2"), + "plugins/DevicesDetection/images/os/SYM.gif" => array("1042", "a8d404e43206c52a7e5f3e6e7e469eea"), + "plugins/DevicesDetection/images/os/T64.gif" => array("220", "8e42601e52784216ed71b8e8de17f82e"), + "plugins/DevicesDetection/images/os/TDX.gif" => array("1001", "a54b0161478c26d04b304c7849ef90df"), + "plugins/DevicesDetection/images/os/TIZ.gif" => array("958", "dbc584b7603e8865c9477b18a6fbbb7d"), + "plugins/DevicesDetection/images/os/UBT.gif" => array("986", "56d67af2d61927c290b0e5af8e8ef6fc"), + "plugins/DevicesDetection/images/os/UNK.gif" => array("80", "c4a7f0e333a6079fdb0b6595e11bca74"), + "plugins/DevicesDetection/images/os/VMS.gif" => array("572", "da6881ce3b86fdbea70ae1b405f9c40a"), + "plugins/DevicesDetection/images/os/WCE.gif" => array("185", "5e7141d138d3f7afc0a113c21a8b2d9b"), + "plugins/DevicesDetection/images/os/WII.gif" => array("617", "d8f2cae9e8e7723241c6c862f12c7511"), + "plugins/DevicesDetection/images/os/WIN.gif" => array("191", "53d14246a8e5a46e927a87648111a0d3"), + "plugins/DevicesDetection/images/os/WIU.gif" => array("310", "394c491524ac263e1c4fcedb1480d281"), + "plugins/DevicesDetection/images/os/WMO.gif" => array("1060", "721b79d12ac825df25f356f5a7714f74"), + "plugins/DevicesDetection/images/os/WOS.gif" => array("70", "31e5c59d2fc5b195c5ea4f1afd878e04"), + "plugins/DevicesDetection/images/os/WPH.gif" => array("1089", "8d69225f3ebb1fe2d2b6d4e2e1687b80"), + "plugins/DevicesDetection/images/os/WRT.gif" => array("925", "d9f78bbd9009c721cf5d64b056679a19"), + "plugins/DevicesDetection/images/os/XBT.gif" => array("968", "c0f572e03ffaf7d38c78fcbb7fba84cf"), + "plugins/DevicesDetection/images/os/XBX.gif" => array("1043", "e3e0eaa5daa2903bab1e0e9ea3ef1d46"), + "plugins/DevicesDetection/images/os/YNS.gif" => array("913", "f4d8502e11b209c209fcee3312a33139"), "plugins/DevicesDetection/images/screens/camera.png" => array("644", "f948997fff235b4bbdece324d9054969"), "plugins/DevicesDetection/images/screens/carbrowser.png" => array("3218", "fcbb6fa6a2c617418df8b17299d3d7fb"), "plugins/DevicesDetection/images/screens/computer.png" => array("550", "305f4f50137fe4e909ad4aa4f61d52a8"), @@ -1307,84 +2880,273 @@ class Manifest { "plugins/DevicesDetection/images/screens/tv.png" => array("644", "415f9d8ae19f77c0203fd82de985ab2a"), "plugins/DevicesDetection/images/screens/unknown.gif" => array("80", "c4a7f0e333a6079fdb0b6595e11bca74"), "plugins/DevicesDetection/images/screens/wide.gif" => array("1025", "c0104958e6fb23668d0406fd4d89095e"), - "plugins/DevicesDetection/lang/bg.json" => array("1918", "4efec5ddc313eff04a301805586e13f2"), - "plugins/DevicesDetection/lang/cs.json" => array("340", "9cbf609028387879af0c6a54c9fd67f1"), - "plugins/DevicesDetection/lang/da.json" => array("1309", "09ba8d9edf2c0bf40a4b78c2df0d9174"), - "plugins/DevicesDetection/lang/de.json" => array("1334", "14ab1307ce18bb680bc954764bdd8e2f"), - "plugins/DevicesDetection/lang/el.json" => array("2121", "71ba9e2ac13f5592daae2e6f8085361d"), - "plugins/DevicesDetection/lang/en.json" => array("1305", "e1cc97069255c3ed10eb9c96ab1faab9"), - "plugins/DevicesDetection/lang/es.json" => array("1487", "38413c85a6b8d00fb099c450bc215493"), - "plugins/DevicesDetection/lang/et.json" => array("1326", "925cd20b8c5560958c1c5af28fdabf03"), - "plugins/DevicesDetection/lang/fa.json" => array("1191", "f9021aacbeef48009222e6157bed9fb2"), - "plugins/DevicesDetection/lang/fi.json" => array("1360", "d35fa82abe7de2b9d6ff3b6ecb40aa90"), - "plugins/DevicesDetection/lang/fr.json" => array("1616", "304b6674bbda463695f47ef601130345"), - "plugins/DevicesDetection/lang/it.json" => array("1405", "ea2a06c896ed564b2d8fb3b3b128abf3"), - "plugins/DevicesDetection/lang/ja.json" => array("1757", "b8c584f9b316aa5be5694ea2a9452baf"), - "plugins/DevicesDetection/lang/nb.json" => array("359", "bd7d02fa1b1c53285f948b31d7aae4a6"), - "plugins/DevicesDetection/lang/nl.json" => array("1251", "b0ae612fa5e5c16ae19201e610934f68"), - "plugins/DevicesDetection/lang/pt-br.json" => array("937", "a3eb60325ac25d48584eecfb771b89e6"), - "plugins/DevicesDetection/lang/ro.json" => array("1003", "676591f9dc497e4117264d56690a5647"), - "plugins/DevicesDetection/lang/ru.json" => array("1510", "ea3b2ebb6e5c0cf0ea1944c56116a539"), - "plugins/DevicesDetection/lang/sr.json" => array("550", "b31ae00fd98c9b050823db27790b2628"), - "plugins/DevicesDetection/lang/sv.json" => array("1325", "9c96fbcb18791021e36d2ca4868830ba"), - "plugins/DevicesDetection/templates/detection.twig" => array("3599", "12494f5f5bff7b5f031e040c0f44f3df"), - "plugins/DevicesDetection/templates/index.twig" => array("632", "31c98fab8308315ac38c8c0d7800e105"), + "plugins/DevicesDetection/lang/am.json" => array("267", "c6f29a6e5f2005fdbb8929f1b826b788"), + "plugins/DevicesDetection/lang/ar.json" => array("490", "5157d46b7df32c6c5d1677aeecd16f56"), + "plugins/DevicesDetection/lang/be.json" => array("1407", "9b1c87abe1a0bb5ec4aee719d5d6bcb8"), + "plugins/DevicesDetection/lang/bg.json" => array("2740", "0f88e52372535431e77300f90c58109f"), + "plugins/DevicesDetection/lang/ca.json" => array("1067", "b140a7ecbe46951d059e9a7efe46e1c7"), + "plugins/DevicesDetection/lang/cs.json" => array("2538", "8b039d787c18b86b9f82f2837f0a53cf"), + "plugins/DevicesDetection/lang/da.json" => array("2231", "071760605527e39ea8243894e6cca42a"), + "plugins/DevicesDetection/lang/de.json" => array("2342", "f1d5f5285d1876196c18b62cbf790ef3"), + "plugins/DevicesDetection/lang/el.json" => array("3987", "c212270775e5449063327c8831c44ab4"), + "plugins/DevicesDetection/lang/en.json" => array("2309", "f80b64d60bf82e38813713aa6a954a05"), + "plugins/DevicesDetection/lang/es.json" => array("2680", "1d69d82a136acd5cebaff513270fc91a"), + "plugins/DevicesDetection/lang/et.json" => array("1453", "660af0cd6ccc885e641402c3c906ce0a"), + "plugins/DevicesDetection/lang/eu.json" => array("362", "da04391b50cceca6b2a61c11262cb2d7"), + "plugins/DevicesDetection/lang/fa.json" => array("1856", "4322e44b444a14187cbea66c99d3b839"), + "plugins/DevicesDetection/lang/fi.json" => array("1810", "ca7061f2bab7c7f4215f8473db3657d1"), + "plugins/DevicesDetection/lang/fr.json" => array("2698", "a1f4bb78b16f003f3f8ab0cfdcdc9879"), + "plugins/DevicesDetection/lang/gl.json" => array("176", "4fb65463e4d86f67be69009f9835245d"), + "plugins/DevicesDetection/lang/he.json" => array("245", "dd6ff9d4c57122f825e7c53e5bbb0b8b"), + "plugins/DevicesDetection/lang/hi.json" => array("1486", "7c0ab1450dc215b4cdb5718bbc36734f"), + "plugins/DevicesDetection/lang/hr.json" => array("274", "7f2d07c77dc4e80737b1893587e0b8bd"), + "plugins/DevicesDetection/lang/hu.json" => array("403", "4fa8c112e88fc8c63d7e8016753b6261"), + "plugins/DevicesDetection/lang/id.json" => array("988", "bf971c7c2c6233f8d57d023435527e82"), + "plugins/DevicesDetection/lang/is.json" => array("351", "ea0dbd14452b2d5eb86fcdd7d06420c0"), + "plugins/DevicesDetection/lang/it.json" => array("2457", "6061508bbebabb079ed528d755d758de"), + "plugins/DevicesDetection/lang/ja.json" => array("2956", "99fb6f6e87ba4bce22013cfff544a79b"), + "plugins/DevicesDetection/lang/ka.json" => array("577", "0c2e14e1c9b658b08501f631eab54296"), + "plugins/DevicesDetection/lang/ko.json" => array("2542", "6e90b087b531bcf0d4322890c4cd9823"), + "plugins/DevicesDetection/lang/lt.json" => array("1300", "cee9ad0250b4c3ed2d665e238569402c"), + "plugins/DevicesDetection/lang/lv.json" => array("925", "2a65b9b1af7d51e0f8c63baf2be2653b"), + "plugins/DevicesDetection/lang/nb.json" => array("2294", "d89b11c6c6e432be349fd8a516d3439f"), + "plugins/DevicesDetection/lang/nl.json" => array("2366", "643872354a13c2de690069b0d992641d"), + "plugins/DevicesDetection/lang/nn.json" => array("373", "73cd32395232882396a5637964bf18ff"), + "plugins/DevicesDetection/lang/pl.json" => array("1556", "2583e17e352be30c2f74fb5b1aea3b74"), + "plugins/DevicesDetection/lang/pt-br.json" => array("2558", "a33b16d2b4226c113d29b08069ebc5f1"), + "plugins/DevicesDetection/lang/pt.json" => array("1397", "ad2eb484247ebd5f9ac8d423d0dc7097"), + "plugins/DevicesDetection/lang/ro.json" => array("2022", "f02d6280104cdb241ac89ca4677d99d1"), + "plugins/DevicesDetection/lang/ru.json" => array("3493", "fd811853c4a3059b01816bfd90d0e37a"), + "plugins/DevicesDetection/lang/sk.json" => array("399", "02aaf8ae9077162acf99369e4a1b2687"), + "plugins/DevicesDetection/lang/sl.json" => array("755", "ad6f6062be535782650b4bf99c63983f"), + "plugins/DevicesDetection/lang/sq.json" => array("992", "b117d467f06ba8a9a8bcb3ec84510ae9"), + "plugins/DevicesDetection/lang/sr.json" => array("2319", "877a2ae85573097818f0dde16f493864"), + "plugins/DevicesDetection/lang/sv.json" => array("2265", "e9c68961a32c9a63dc61e8485156e189"), + "plugins/DevicesDetection/lang/ta.json" => array("78", "1838fa179f96685669f090fed2cc57af"), + "plugins/DevicesDetection/lang/te.json" => array("743", "666be60660daa83ec7196fa4cca3b36d"), + "plugins/DevicesDetection/lang/th.json" => array("708", "5403fabfa08b193e34244465350c1df2"), + "plugins/DevicesDetection/lang/tl.json" => array("2025", "685d9b9e3751106bd30eeb2cc1ae6915"), + "plugins/DevicesDetection/lang/tr.json" => array("1503", "6986c1169bcf8483ae70c69fba2f7715"), + "plugins/DevicesDetection/lang/uk.json" => array("504", "43d205e3ccef78b39847dee14882f197"), + "plugins/DevicesDetection/lang/vi.json" => array("1249", "c6a4236f550c1c0a7ee032f8356a8a71"), + "plugins/DevicesDetection/lang/zh-cn.json" => array("862", "955c7eb84721690aa4a3bdae033a580b"), + "plugins/DevicesDetection/lang/zh-tw.json" => array("431", "666c390b937a8509b5d6c5e524e12168"), + "plugins/DevicesDetection/Menu.php" => array("908", "da60e9e42408692728088e6f0d840f05"), + "plugins/DevicesDetection/plugin.json" => array("99", "d1b05c1f8861aba29255009dd7495ce0"), + "plugins/DevicesDetection/Reports/Base.php" => array("385", "8781b2f359f818e4e3a893fcbcfe2828"), + "plugins/DevicesDetection/Reports/GetBrand.php" => array("952", "d5ce29bc3935cc7182dc7e9ac5ba3564"), + "plugins/DevicesDetection/Reports/GetBrowserEngines.php" => array("1160", "0a1c7f66a28e452959feb54b32cb6c31"), + "plugins/DevicesDetection/Reports/GetBrowsers.php" => array("1193", "3637115b150b0c0675909472ae17dafd"), + "plugins/DevicesDetection/Reports/GetBrowserVersions.php" => array("1091", "7b8c0373a6898295646c2a3af6531428"), + "plugins/DevicesDetection/Reports/GetModel.php" => array("952", "05098f68086fafd23e5ce8d57eb538aa"), + "plugins/DevicesDetection/Reports/GetOsFamilies.php" => array("1128", "f7fdb5501cfb0088183aa34f16e9121c"), + "plugins/DevicesDetection/Reports/GetOsVersions.php" => array("1177", "778a3ef034985e4d9521b8cba286a96c"), + "plugins/DevicesDetection/Reports/GetType.php" => array("1166", "6f0e181aec6c170ec87a4b3f355d4a5e"), + "plugins/DevicesDetection/Segment.php" => array("382", "19e667070282e65ce237797aa9df4225"), + "plugins/DevicesDetection/templates/detection.twig" => array("3815", "49f669086b28527b435dc3db473e0cdd"), + "plugins/DevicesDetection/templates/devices.twig" => array("655", "7bd09aac269621a5a59f98b9675faba4"), "plugins/DevicesDetection/templates/list.twig" => array("174", "bfda35ec5629ff523fea8502339daa58"), - "plugins/DevicesDetection/Updates/1.14.php" => array("780", "e405cf2a34f8299ab31ff7f27e7cda9f"), - "plugins/Events/API.php" => array("7610", "1f1edc6d625743c1301eb05f28b94534"), - "plugins/Events/Archiver.php" => array("9467", "1058da454ca46bdcbf2751605eb434ef"), - "plugins/Events/Controller.php" => array("2819", "2744d5a2eaa6532b02fdae411518d6cc"), - "plugins/Events/Events.php" => array("11572", "eb5998643543849b6a86625296dc6f37"), - "plugins/Events/lang/bg.json" => array("342", "b6ca024b4dc8d44227d9b09687522b20"), - "plugins/Events/lang/da.json" => array("257", "53898474762bee31b2bce9efa4218033"), - "plugins/Events/lang/de.json" => array("846", "e7ffc157518edd6f563a036a33ceea6b"), - "plugins/Events/lang/el.json" => array("332", "f8fa308217f7fea4bad1b5cd1fda8dae"), - "plugins/Events/lang/en.json" => array("1388", "ccfc90445d2f2d1b2e9a03276c96e076"), - "plugins/Events/lang/es.json" => array("852", "02df421dd69675296c0f084dae5c3d0a"), + "plugins/DevicesDetection/templates/software.twig" => array("863", "58ce1b71283fe675a74186f6757de60b"), + "plugins/DevicesDetection/Updates/1.14.php" => array("863", "5352047205e0c439f0be8a154b4c3e78"), + "plugins/DevicesDetection/Visitor.php" => array("2457", "0949ef0225412b6e29d88d7f9f15e63c"), + "plugins/Diagnostics/Commands/AnalyzeArchiveTable.php" => array("3373", "a4f0e69a75e019b57a60680e39c75fff"), + "plugins/Diagnostics/Commands/Run.php" => array("2950", "c085a8cab37b95046535be2a6164f4c7"), + "plugins/Diagnostics/config/config.php" => array("2170", "2d363402eade49ca3aa82efda940e3f5"), + "plugins/Diagnostics/ConfigReader.php" => array("6026", "152445fb5c88cfc27b2030e67da10982"), + "plugins/Diagnostics/Controller.php" => array("1936", "168e3dae860cc48ddf582f0e5578d2a9"), + "plugins/Diagnostics/Diagnostic/CronArchivingCheck.php" => array("1260", "cd8c2c3c3e5057f80e99065991a32a70"), + "plugins/Diagnostics/Diagnostic/DbAdapterCheck.php" => array("3293", "ce936823d801acfdc20de4092467c48f"), + "plugins/Diagnostics/Diagnostic/Diagnostic.php" => array("1136", "3410ece2566296f015ab5bb72b2ea224"), + "plugins/Diagnostics/Diagnostic/DiagnosticResultItem.php" => array("776", "99f18f55d2eafab1132e2bd19a80f035"), + "plugins/Diagnostics/Diagnostic/DiagnosticResult.php" => array("2355", "01a51938f182851b9db32309d76a816a"), + "plugins/Diagnostics/Diagnostic/FileIntegrityCheck.php" => array("1607", "8965bcbe33b69326d9b569cebe1e3dd1"), + "plugins/Diagnostics/Diagnostic/GdExtensionCheck.php" => array("1197", "018b7e2530415a04468fd02544530b76"), + "plugins/Diagnostics/Diagnostic/HttpClientCheck.php" => array("1299", "728dbf2d95ffcc991dec527ea06fb721"), + "plugins/Diagnostics/Diagnostic/LoadDataInfileCheck.php" => array("2791", "c50a7ea77493a7dbbaec61335cfe2529"), + "plugins/Diagnostics/Diagnostic/MemoryLimitCheck.php" => array("1510", "457f917088abb92b7ff5a7b1737148cb"), + "plugins/Diagnostics/Diagnostic/NfsDiskCheck.php" => array("1596", "51b07702ba57ea16c269ac10ccaa33e5"), + "plugins/Diagnostics/Diagnostic/PageSpeedCheck.php" => array("2221", "13681d4819d90b2fea83bb990998a32b"), + "plugins/Diagnostics/Diagnostic/PhpExtensionsCheck.php" => array("2389", "239979571f61465280d56e2292ab01eb"), + "plugins/Diagnostics/Diagnostic/PhpFunctionsCheck.php" => array("3302", "7831d45780a0e0490dee339d6deb49a9"), + "plugins/Diagnostics/Diagnostic/PhpSettingsCheck.php" => array("2385", "f53e45e9c030c58e046543c2134afd89"), + "plugins/Diagnostics/Diagnostic/PhpVersionCheck.php" => array("1497", "32c688cba5a95c9e14e39d74bafa883b"), + "plugins/Diagnostics/Diagnostic/RecommendedExtensionsCheck.php" => array("2128", "675a2b30989dcce52f091a44d95a5718"), + "plugins/Diagnostics/Diagnostic/RecommendedFunctionsCheck.php" => array("2118", "e59ffd0ef41223a9b01d5ea0583c57d9"), + "plugins/Diagnostics/DiagnosticReport.php" => array("2550", "2b8dadc91401848db9e196d6aa5499c3"), + "plugins/Diagnostics/DiagnosticService.php" => array("1935", "a2c4a454a1d754c304fda17bfb80bd41"), + "plugins/Diagnostics/Diagnostics.php" => array("602", "c817efee9bcd93f4968f1bb12a925219"), + "plugins/Diagnostics/Diagnostic/TimezoneCheck.php" => array("1238", "2718b405625c4845c485e6bca6a80b52"), + "plugins/Diagnostics/Diagnostic/TrackerCheck.php" => array("1290", "0088909d1f908c6675a25cd52d7fe058"), + "plugins/Diagnostics/Diagnostic/WriteAccessCheck.php" => array("2725", "31ed361cf368e67ff9a5051b0a160a6c"), + "plugins/Diagnostics/lang/cs.json" => array("583", "344992d4635a380edaf62602e5f5e7b3"), + "plugins/Diagnostics/lang/de.json" => array("626", "1fe6161a280f5967bf81e714c5633951"), + "plugins/Diagnostics/lang/el.json" => array("945", "302e2654a5a855b3fb118b21fb6cff1f"), + "plugins/Diagnostics/lang/en.json" => array("540", "3dee2204f1a442b9bc4de8ffda216a08"), + "plugins/Diagnostics/lang/it.json" => array("113", "87baad045dc39b9e49ecf89f268a78cb"), + "plugins/Diagnostics/lang/pt-br.json" => array("626", "5e49f30ef9471ef834a258281f31f5ab"), + "plugins/Diagnostics/lang/sv.json" => array("77", "00fe9017ded864cb4e3e40874393963d"), + "plugins/Diagnostics/Menu.php" => array("807", "cb19847a3dd93d255bf357f43c31d313"), + "plugins/Diagnostics/plugin.json" => array("98", "2cbf507e2c94b90856270a1bc5efed34"), + "plugins/Diagnostics/stylesheets/configfile.less" => array("323", "1c829b3f59681747544e5c0d69512954"), + "plugins/Diagnostics/templates/configfile.twig" => array("2210", "f1056f197763b98601a79e43ca9001b8"), + "plugins/Diagnostics/Test/Integration/Commands/AnalyzeArchiveTableTest.php" => array("7213", "512bebd5df4760f7be595eb3fca64463"), + "plugins/Diagnostics/Test/Integration/ConfigReaderTest.php" => array("9934", "2c3405c2e37c56285415bba211e29ddd"), + "plugins/Diagnostics/Test/Mock/DiagnosticWithError.php" => array("538", "1d7a7a0b73e46849aebbe593846884d6"), + "plugins/Diagnostics/Test/Mock/DiagnosticWithSuccess.php" => array("539", "4ecb50422bda00df5dd8bc80972e7123"), + "plugins/Diagnostics/Test/Mock/DiagnosticWithWarning.php" => array("544", "d0bbf0be19be9c899786f87efe6ec87c"), + "plugins/Diagnostics/Test/Unit/Diagnostic/DiagnosticResultTest.php" => array("1370", "2fefafbb819a8360b1bab02375504550"), + "plugins/Diagnostics/Test/Unit/DiagnosticReportTest.php" => array("1726", "aba4d15208fb3c55bcd62dee82824bdb"), + "plugins/Diagnostics/Test/Unit/DiagnosticServiceTest.php" => array("1525", "f75442d5559cf68c2946687fd0cc957b"), + "plugins/Ecommerce/Columns/BaseConversion.php" => array("818", "6682a92d1025a293137fab76ace06e45"), + "plugins/Ecommerce/Columns/ProductCategory.php" => array("390", "ec022bbf48c6038d5118d048a3356db9"), + "plugins/Ecommerce/Columns/ProductName.php" => array("382", "52d07d84596b5f4fd0856a15f76f6b20"), + "plugins/Ecommerce/Columns/ProductSku.php" => array("380", "ee57c86d075ab91416d14cc10a5dfdf5"), + "plugins/Ecommerce/Columns/RevenueDiscount.php" => array("847", "b63214d54b691a28fa002b79924da15f"), + "plugins/Ecommerce/Columns/Revenue.php" => array("1813", "73dd4d8a404ee1090ea905233fdd90e5"), + "plugins/Ecommerce/Columns/RevenueShipping.php" => array("847", "2da1c621c34ba0efe51b4a0c160700ec"), + "plugins/Ecommerce/Columns/RevenueSubtotal.php" => array("847", "fcc5d8111362e99bd80efef36da6e4bc"), + "plugins/Ecommerce/Columns/RevenueTax.php" => array("837", "54375d5033ecbbd063ad294fd8122962"), + "plugins/Ecommerce/Controller.php" => array("3897", "20f99c23be354cb6f3141c5e470bba28"), + "plugins/Ecommerce/lang/bg.json" => array("64", "fe44cef66664b19de16d08d9effa401c"), + "plugins/Ecommerce/lang/cs.json" => array("377", "68fa995ddae26e383b461781600db59b"), + "plugins/Ecommerce/lang/da.json" => array("52", "c72674207df6477f4379d8b3a9f66d6e"), + "plugins/Ecommerce/lang/de.json" => array("410", "4c1534aa5d64655eddbe4a4ccac59992"), + "plugins/Ecommerce/lang/el.json" => array("672", "7dce788a36107ed6bd5175eeb9a40bc7"), + "plugins/Ecommerce/lang/en.json" => array("329", "77352d68e7c0b5394f6821da17a3b0c0"), + "plugins/Ecommerce/lang/es.json" => array("400", "fb9ab03cb2d49e61b9e39f4ca3c82b88"), + "plugins/Ecommerce/lang/fr.json" => array("413", "62aae699a87f65553e95c43449c0281d"), + "plugins/Ecommerce/lang/hi.json" => array("782", "97cd8e3621d893cf8bb94490079f508f"), + "plugins/Ecommerce/lang/it.json" => array("398", "e9242f3a8705bff4a145ae683e8ac400"), + "plugins/Ecommerce/lang/ja.json" => array("433", "b452e18de68a4e47df2f26d160886431"), + "plugins/Ecommerce/lang/nb.json" => array("362", "6e1c0b2bec992190772c38c856b9ac34"), + "plugins/Ecommerce/lang/nl.json" => array("414", "b6e54f65b51b491e09781f626ae538e7"), + "plugins/Ecommerce/lang/pt-br.json" => array("423", "5a786711f1757fd5086b8a9ea4587888"), + "plugins/Ecommerce/lang/pt.json" => array("54", "7801c86d09df902fbc51bf9ac2f67fff"), + "plugins/Ecommerce/lang/ru.json" => array("62", "e0f3952c8486e220e0937a362ab92452"), + "plugins/Ecommerce/lang/sk.json" => array("55", "786407454a35ec232533b2b6ca02b8bf"), + "plugins/Ecommerce/lang/sr.json" => array("344", "7a8050ba8f8d16ea90f95a83d98791bc"), + "plugins/Ecommerce/lang/sv.json" => array("152", "d8e4c5d1d5eca39b375268406ecb4ed3"), + "plugins/Ecommerce/lang/ta.json" => array("244", "94af4b0843825862a1259d48d1d72b3b"), + "plugins/Ecommerce/Menu.php" => array("1206", "ff5d0bdc10b2b593a1b56ad634a0e9ee"), + "plugins/Ecommerce/plugin.json" => array("27", "0a7a8fec04ae0ac3488ce3abf68e5a8e"), + "plugins/Ecommerce/Reports/BaseItem.php" => array("5695", "3424eaa93ab85e18795c415a43b8438c"), + "plugins/Ecommerce/Reports/Base.php" => array("1669", "aa62adcb2b1530469fe711a5cbb0cfa9"), + "plugins/Ecommerce/Reports/GetDaysToConversionAbandonedCart.php" => array("870", "51acdf9c0f04cc816e202f8960816021"), + "plugins/Ecommerce/Reports/GetDaysToConversionEcommerceOrder.php" => array("872", "badebebdc7e378b16479982c2c177b2a"), + "plugins/Ecommerce/Reports/GetEcommerceAbandonedCart.php" => array("1116", "97ec61bc29f7a47e842fdb5939373bb0"), + "plugins/Ecommerce/Reports/GetEcommerceOrder.php" => array("1165", "1700882ff54d5cfb1633672bc7e1a927"), + "plugins/Ecommerce/Reports/GetItemsCategory.php" => array("587", "b6a61ce96dcc1b279955b6846d74ab01"), + "plugins/Ecommerce/Reports/GetItemsName.php" => array("567", "4ba0c30c963bde7deb8f2ec68db7b3b0"), + "plugins/Ecommerce/Reports/GetItemsSku.php" => array("564", "875e55319269556be310ade71f5f29d2"), + "plugins/Ecommerce/Reports/GetVisitsUntilConversionAbandonedCart.php" => array("895", "a5c46d8ce131f80f07bb891bb06f820b"), + "plugins/Ecommerce/Reports/GetVisitsUntilConversionEcommerceOrder.php" => array("900", "b0ef45a20220f4bd9518ba2afce06b7a"), + "plugins/Ecommerce/templates/ecommerceLog.twig" => array("94", "bcc795f826bd46dde1a1d31a5530ae3e"), + "plugins/Ecommerce/templates/products.twig" => array("97", "64bf43a5297772ea21abe965d7ac1c62"), + "plugins/Ecommerce/templates/sales.twig" => array("101", "93b7233ddf89897205ab4bd5b742c027"), + "plugins/Ecommerce/Tracker/EcommerceRequestProcessor.php" => array("2807", "2b204a2894e19c49b396a01cd2622e13"), + "plugins/Ecommerce/Widgets.php" => array("816", "ea96879813a753bb5bdf3378b0768a9f"), + "plugins/Events/Actions/ActionEvent.php" => array("2342", "2ca5d3658116145b3e5a5193bdad7eb7"), + "plugins/Events/API.php" => array("9158", "071b04ae4182601ce4b5139034680796"), + "plugins/Events/Archiver.php" => array("9710", "57cf90028e7db313819f7094f3caf4b6"), + "plugins/Events/Columns/EventAction.php" => array("1331", "43d8a1afd25f50352a737ebb1db42f32"), + "plugins/Events/Columns/EventCategory.php" => array("1353", "98bca9429c42ca7202a24b4d7751120c"), + "plugins/Events/Columns/EventName.php" => array("1238", "124150dff9d044341e412cfe34e23356"), + "plugins/Events/Columns/Metrics/AverageEventValue.php" => array("1129", "5710361e2c4f37045033d67477f037cd"), + "plugins/Events/Columns/TotalEvents.php" => array("1905", "9a65f7ddaf0a321075f48a69a3f051b8"), + "plugins/Events/Controller.php" => array("2621", "751344eba7e46431f66ecaf35c8cb5af"), + "plugins/Events/DataTable/Filter/ReplaceEventNameNotSet.php" => array("894", "cb5fbecae1c9b87e0712dae1b0958393"), + "plugins/Events/Events.php" => array("9461", "c03e4510d53245102c4784c81e9e8c38"), + "plugins/Events/lang/bg.json" => array("1284", "fcf5f8a12776cfae018a88fcb02413e1"), + "plugins/Events/lang/ca.json" => array("652", "9e0a1d477b1253ab673727ebed6a0b7b"), + "plugins/Events/lang/cs.json" => array("1616", "6711683d42b0439a12cec82714cc05e7"), + "plugins/Events/lang/da.json" => array("1499", "88cfeb7c5e5a503e1a15d5feb4a727e2"), + "plugins/Events/lang/de.json" => array("1649", "c4a07655a75dc7561fd9ad7c5479bfc5"), + "plugins/Events/lang/el.json" => array("2227", "282e63d4456c58e4b23f84e2ea4b45d0"), + "plugins/Events/lang/en.json" => array("1484", "9a3a9590f0a34a1281539fa776577303"), + "plugins/Events/lang/es.json" => array("1641", "f0697ee63d0e351d512b01e07ee258fc"), "plugins/Events/lang/et.json" => array("262", "b6f500811defac532922653d562467c6"), - "plugins/Events/lang/fa.json" => array("291", "b11cba57d7f4654e63e864509c018bcf"), - "plugins/Events/lang/fi.json" => array("262", "d00802eb689dac69d2609cc6289017f2"), - "plugins/Events/lang/fr.json" => array("279", "39724ce65da5324898709bd365ceddee"), - "plugins/Events/lang/it.json" => array("818", "6a9e72466790e1e84a20281cf188a543"), - "plugins/Events/lang/ja.json" => array("300", "8b531ac5ac179f64c711cbfa3ca7b692"), - "plugins/Events/lang/nl.json" => array("271", "6a01d8a33c0234eee5d06c2da015e23e"), - "plugins/Events/lang/pt-br.json" => array("208", "292c740643d6491e0fe066ac5198345d"), - "plugins/Events/lang/ro.json" => array("921", "59e6c3b7b2527ec51a61fdc08b8d4754"), - "plugins/Events/lang/sr.json" => array("529", "993200724d25f114e3e500eab2459df8"), - "plugins/Events/lang/sv.json" => array("258", "c3d44c14aa30eebcad1133d6ae1a7b2c"), + "plugins/Events/lang/fa.json" => array("403", "c9178a3daec627c2049e5d1248399a17"), + "plugins/Events/lang/fi.json" => array("1256", "00ea1ba3743f4829027129fa95ff75d8"), + "plugins/Events/lang/fr.json" => array("1730", "bf032ff1586871585b9cc85e655bc9c4"), + "plugins/Events/lang/hi.json" => array("1574", "46c7db6113039244363f9ad7ba14734d"), + "plugins/Events/lang/it.json" => array("1545", "96a8be1eb696bfa9c0fbe27c5446a0de"), + "plugins/Events/lang/ja.json" => array("1705", "9190a96d5b9720c4d086194a3cb913fd"), + "plugins/Events/lang/lt.json" => array("291", "e853be98e8328103bb10a9fa8e2841cd"), + "plugins/Events/lang/nb.json" => array("1450", "4449b8d92877485d267c5af5c8e2b7c7"), + "plugins/Events/lang/nl.json" => array("1675", "be15c3662d4162fad886d47ec568624f"), + "plugins/Events/lang/pl.json" => array("1278", "794b57c6d5ebf31221ea4d2d241c3aa2"), + "plugins/Events/lang/pt-br.json" => array("1581", "71d6f7e242643599aa8c5b8e9e1aece0"), + "plugins/Events/lang/pt.json" => array("87", "b1ec6406b4c41cbec689e0b7a52d7091"), + "plugins/Events/lang/ro.json" => array("1571", "5c9485d0770e21513c0448d6f47099a8"), + "plugins/Events/lang/ru.json" => array("1988", "a3723c49edfec5e839a09b4db1a2aa8a"), + "plugins/Events/lang/sk.json" => array("675", "e961d0dec1730e7800bbcea4e5cd27c0"), + "plugins/Events/lang/sl.json" => array("104", "3129420f171aefb2dfa3554b84e9ac69"), + "plugins/Events/lang/sr.json" => array("1613", "16add36202c26aa8acb7f324da6c6ecf"), + "plugins/Events/lang/sv.json" => array("1628", "2b0fa50c85c4660a393162b160b9efd0"), "plugins/Events/lang/ta.json" => array("355", "b0e9b20f277a78aee6dc90005ec0c3f3"), + "plugins/Events/lang/tl.json" => array("1538", "18e9914e4b810ac515d4a0ebb0d9fe47"), + "plugins/Events/lang/tr.json" => array("1340", "22bf68dccbd258c1316584105677558e"), "plugins/Events/lang/vi.json" => array("254", "63d3e8849dd421c075b182f3db5b3d3e"), "plugins/Events/lang/zh-cn.json" => array("204", "f15db4c7236481d992551e7964b1cc79"), - "plugins/Events/plugin.json" => array("139", "b5998447ccf2ee34f37ebe94be0aa845"), + "plugins/Events/Menu.php" => array("428", "6fc85e124ec4ba0996ee1e420db34e0d"), + "plugins/Events/Reports/Base.php" => array("664", "cefdcc1ea7db80e881ac6cf2da011073"), + "plugins/Events/Reports/GetActionFromCategoryId.php" => array("679", "a4b0d4e07e2bf6a2a1e9165b24569e1e"), + "plugins/Events/Reports/GetActionFromNameId.php" => array("671", "2cb674da0b726451373b5d43d1e49023"), + "plugins/Events/Reports/GetAction.php" => array("1000", "ccee723cd9f76ff1bc77dbd0aeb946e8"), + "plugins/Events/Reports/GetCategoryFromActionId.php" => array("686", "cbfeb76c3a8b2e93b0d465bf35c50c6f"), + "plugins/Events/Reports/GetCategoryFromNameId.php" => array("682", "b1dc12ec52f227f0c291f60797cb65f1"), + "plugins/Events/Reports/GetCategory.php" => array("1012", "4c8bc313dd87d7928bf03edc58df4cf5"), + "plugins/Events/Reports/GetNameFromActionId.php" => array("660", "7647e512711a991fb838e36fbf6b218e"), + "plugins/Events/Reports/GetNameFromCategoryId.php" => array("669", "5189d864496e098e06aa3f766af03849"), + "plugins/Events/Reports/GetName.php" => array("990", "4b7fcc935a5bd3adb2b0a37082fb6aa1"), + "plugins/Events/Segment.php" => array("452", "16a91de51555740a50be10535e20375f"), + "plugins/Events/stylesheets/datatable.less" => array("227", "049a705c135cb3b8f9c5d22b81659a62"), "plugins/Events/templates/index.twig" => array("27", "a1080e4dc13f29008f29ac2d2e5dc8a8"), - "plugins/ExampleAPI/API.php" => array("4049", "2bda550f0e37806d7d8910789eee8199"), - "plugins/ExampleAPI/ExampleAPI.php" => array("325", "cef3615976cd2876afa2d2fc77acc5c1"), - "plugins/ExampleAPI/plugin.json" => array("448", "1a13a70c7ea7281490ed4af6a7221064"), - "plugins/ExampleCommand/Commands/HelloWorld.php" => array("1074", "70069208b271812bbf7e8faca036e7af"), - "plugins/ExampleCommand/plugin.json" => array("115", "cb68e3b4adc3511c16067f2cf02c5922"), - "plugins/ExamplePlugin/API.php" => array("815", "8bfc9244633eabb0c4f7f32e9b4a3fcf"), - "plugins/ExamplePlugin/Controller.php" => array("484", "f998699c283e1b20006c9afa8092f4ad"), - "plugins/ExamplePlugin/ExamplePlugin.php" => array("587", "7fedf67b0a08fb7fe1f8d71372ce6e91"), - "plugins/ExamplePlugin/.gitignore" => array("20", "4a9a2f0c455651c6b04479aafba04568"), - "plugins/ExamplePlugin/javascripts/plugin.js" => array("480", "eb32c805acb3e4af9370afd515a6dd1d"), - "plugins/ExamplePlugin/plugin.json" => array("208", "89a9672ed7f49b867ed88133a69120f4"), - "plugins/ExamplePlugin/README.md" => array("207", "48e2fb45e071d5d991d2b6243d9e8ff4"), - "plugins/ExamplePlugin/screenshots/.gitkeep" => array("0", "d41d8cd98f00b204e9800998ecf8427e"), - "plugins/ExamplePlugin/templates/index.twig" => array("77", "16efbdb92b36269be3801782f230f7d6"), - "plugins/ExamplePlugin/.travis.yml" => array("1056", "3a37a0ec586442058d923e1d7d640f43"), - "plugins/ExampleRssWidget/Controller.php" => array("1192", "090efd2ade281d8b58713d703b2beeda"), - "plugins/ExampleRssWidget/ExampleRssWidget.php" => array("949", "75f2273eb309a058b35e8c04cf3bfc45"), - "plugins/ExampleRssWidget/plugin.json" => array("449", "5ff4fed5b604abbae9e887aa0b45cb6a"), - "plugins/ExampleRssWidget/RssRenderer.php" => array("2078", "e2101bb2155cf6cc36ec064b2ca93b9e"), + "plugins/ExampleAPI/API.php" => array("4069", "22846d08aa52f3d321fad0abc20b7432"), + "plugins/ExampleAPI/ExampleAPI.php" => array("329", "5d5864d2f56b4c5c23fe7c462798c473"), + "plugins/ExampleAPI/plugin.json" => array("444", "9eefcb7da521f3a3ff34b4a9e3f297a1"), + "plugins/ExampleCommand/Commands/HelloWorld.php" => array("2229", "2327a4f5ffca68c0eceb196aebd41389"), + "plugins/ExampleCommand/plugin.json" => array("144", "52e2aa5cc3b8bc45e126234e123afc06"), + "plugins/ExamplePlugin/angularjs/directive-component/component.controller.js" => array("577", "02553ed47e181970123f9c39f878e144"), + "plugins/ExamplePlugin/angularjs/directive-component/component.directive.html" => array("68", "5fb2d542dc5a8b056334c084e15111a2"), + "plugins/ExamplePlugin/angularjs/directive-component/component.directive.js" => array("1169", "dd804d503806630903a5e4c8c41c8d88"), + "plugins/ExamplePlugin/angularjs/directive-component/component.directive.less" => array("28", "e40be237cbcfb81a2b615c91f2616f45"), + "plugins/ExamplePlugin/API.php" => array("1393", "e2050d2bb00803bd7d33513ce23c5d8f"), + "plugins/ExamplePlugin/Archiver.php" => array("2204", "7137f9e96fcb6b84079217af0020f845"), + "plugins/ExamplePlugin/Controller.php" => array("883", "5042138a91ea35322e18f63fa771f833"), + "plugins/ExamplePlugin/ExamplePlugin.php" => array("240", "03233993ed896d69769491b37d5f9ad0"), + "plugins/ExamplePlugin/javascripts/plugin.js" => array("502", "57410d0c6c961ab4a88ec15f58936023"), + "plugins/ExamplePlugin/Menu.php" => array("2401", "d0f7c2cfa7c7fe73ecd428819c55daf2"), + "plugins/ExamplePlugin/plugin.json" => array("427", "6b505b4c444b1dd29babd7f7a11391a6"), + "plugins/ExamplePlugin/README.md" => array("209", "57579e836071567bf0d5a0e0a5cbd723"), + "plugins/ExamplePlugin/Tasks.php" => array("1013", "5edc10bb7d49460b60ad63f5210d6a92"), + "plugins/ExamplePlugin/templates/index.twig" => array("156", "ff26515cddba80f1202e95e30e67dce3"), + "plugins/ExamplePlugin/Updates/0.0.2.php" => array("2021", "185e4d94abee2bd9a598e7bb3cfcf692"), + "plugins/ExamplePlugin/Widgets.php" => array("2538", "2b693e4b338f028a67c7932d7251c8dc"), + "plugins/ExampleReport/API.php" => array("842", "b74ba7a868aac484f4218b466908fd62"), + "plugins/ExampleReport/ExampleReport.php" => array("242", "de40b1bfca7e49e7badd59a0e3ec73bf"), + "plugins/ExampleReport/plugin.json" => array("255", "7fb7d47827068f60747737ab60716c4e"), + "plugins/ExampleReport/Reports/Base.php" => array("356", "b85d189f1a87bdce2085b0655db5005b"), + "plugins/ExampleReport/Reports/GetExampleReport.php" => array("4178", "77a65379b5a325f431b4ff5ddf5d105d"), + "plugins/ExampleRssWidget/ExampleRssWidget.php" => array("609", "2c42c7a4d9161068e5db42588d7aa594"), + "plugins/ExampleRssWidget/plugin.json" => array("436", "cf496833d1176d66d799331c29d3f171"), + "plugins/ExampleRssWidget/RssRenderer.php" => array("2083", "3895b7a232d2394c9ff270591aed9e45"), "plugins/ExampleRssWidget/stylesheets/rss.less" => array("468", "9890a5a97e8e0be796c1b993b6991218"), - "plugins/ExampleSettingsPlugin/plugin.json" => array("163", "2672083085a6a297afd769beef9107a2"), - "plugins/ExampleSettingsPlugin/Settings.php" => array("5641", "a65a623faf6b325eb9109bb480eff723"), - "plugins/ExampleTheme/plugin.json" => array("158", "7bd2577897bda6846b7c457e059766f4"), - "plugins/ExampleTheme/README.md" => array("208", "1e90083ee059ed260d3cc73e3bbff133"), - "plugins/ExampleTheme/stylesheets/theme.less" => array("0", "d41d8cd98f00b204e9800998ecf8427e"), - "plugins/ExampleUI/API.php" => array("3032", "c4622a9b0fa70d56d9eef72dbf22b546"), - "plugins/ExampleUI/Controller.php" => array("7325", "4255650a64a9cf4f3b41f98fd101dff5"), - "plugins/ExampleUI/ExampleUI.php" => array("1707", "f5ce7ad0cc8af387cc373ce590105a00"), + "plugins/ExampleRssWidget/Widgets.php" => array("1428", "046e141e37bf52bca08d7094a86e957d"), + "plugins/ExampleSettingsPlugin/plugin.json" => array("167", "7b8e8ae50f1052330dea3064f983ce3d"), + "plugins/ExampleSettingsPlugin/Settings.php" => array("5860", "e48e0254a38f3efffecf0bf62efedf69"), + "plugins/ExampleTheme/plugin.json" => array("391", "dc27f8bc5b98b1d36eefb01ae0edca06"), + "plugins/ExampleTheme/README.md" => array("206", "9fb5e612c84cae82a58977292467ca87"), + "plugins/ExampleTheme/stylesheets/theme.less" => array("1309", "6989a6a97eb005da0addf43a07bdd2e4"), + "plugins/ExampleTracker/Columns/ExampleActionDimension.php" => array("5102", "392b854f0518dfa410f3669c0b430271"), + "plugins/ExampleTracker/Columns/ExampleConversionDimension.php" => array("5389", "7f5d0330c3dc3e647c6eaefdc7e7d2a0"), + "plugins/ExampleTracker/Columns/ExampleDimension.php" => array("793", "37cfbe5258f8ed11f56e19decde257a2"), + "plugins/ExampleTracker/Columns/ExampleVisitDimension.php" => array("6669", "5db930cfa58ed8193db147514ba9f78c"), + "plugins/ExampleTracker/ExampleTracker.php" => array("244", "b2c931d7aed979a85b23328d18d47886"), + "plugins/ExampleTracker/lang/en.json" => array("78", "3d865e530ac4a60c0e46fd991ea4a812"), + "plugins/ExampleTracker/plugin.json" => array("288", "e9e95f73d4731957fe7c06a58844fa2c"), + "plugins/ExampleUI/API.php" => array("3036", "628f4bee7affd6adc0942f2917edff41"), + "plugins/ExampleUI/Controller.php" => array("7651", "6239d0c0d7b972bac4437be759d6ee48"), "plugins/ExampleUI/images/icons-planet/earth.png" => array("11823", "82a4f58618b5194ba7d50600115d36da"), "plugins/ExampleUI/images/icons-planet/jupiter.png" => array("10686", "08c6c466b8068c18e0c1859115515a5d"), "plugins/ExampleUI/images/icons-planet/LICENSE" => array("112", "5275944112cc13e4e74b2de749497988"), @@ -1394,130 +3156,502 @@ class Manifest { "plugins/ExampleUI/images/icons-planet/saturn.png" => array("10561", "9514535a2fde794c6dac3e3382fcf517"), "plugins/ExampleUI/images/icons-planet/uranus.png" => array("10358", "9ff6281138f4ebb8ca5b8b3b54b9d9b0"), "plugins/ExampleUI/images/icons-planet/venus.png" => array("10557", "1aa0425158220a53f36537f83d266231"), - "plugins/ExampleUI/plugin.json" => array("502", "fdd7feb05198028a198c8cedae0ce6b8"), + "plugins/ExampleUI/Menu.php" => array("1380", "816f0010379fb435489d743d1e486535"), + "plugins/ExampleUI/plugin.json" => array("451", "f2419252e8ab778f0a7b08003c80e869"), "plugins/ExampleUI/templates/evolutiongraph.twig" => array("90", "541ef7126cfe01ac071d053d720d2ccd"), - "plugins/ExampleUI/templates/notifications.twig" => array("447", "9525341678a5441f789cd446cbbdba56"), + "plugins/ExampleUI/templates/notifications.twig" => array("521", "f1f1b35cb6ef19616265ce278b3f5b79"), "plugins/ExampleUI/templates/sparklines.twig" => array("237", "31e796d35929d2839fe62b5d2f3460f6"), - "plugins/ExampleVisualization/ExampleVisualization.php" => array("631", "44f36c70c57d48b86a5342b7ee2e3010"), - "plugins/ExampleVisualization/images/table.png" => array("151", "327ee0e75605ab865796053f2c0aebf1"), - "plugins/ExampleVisualization/plugin.json" => array("410", "6879c05e6a2b969cfac1eb24b53e5e79"), + "plugins/ExampleVisualization/ExampleVisualization.php" => array("264", "d67ce85e32ce072ea428b891354018c2"), + "plugins/ExampleVisualization/images/table.png" => array("1056", "375eae11704a2d2e749cdefcb26f2a39"), + "plugins/ExampleVisualization/plugin.json" => array("436", "006172c6e563e91ff4ac0eb3bd0ad793"), "plugins/ExampleVisualization/README.md" => array("238", "1353685fc485ed3691c8ce507d530411"), - "plugins/ExampleVisualization/SimpleTable.php" => array("2515", "c5dcc6edbdb74ed20ffe9d36f6846c22"), "plugins/ExampleVisualization/templates/simpleTable.twig" => array("829", "abccf18a3c8bfb921a53f65cb339282a"), + "plugins/ExampleVisualization/Visualizations/SimpleTable.php" => array("2534", "3a8793f8c8487a23bfbbdaf0d2b7e318"), "plugins/Feedback/angularjs/ratefeature/icon_license" => array("268", "0a278d7bd9ac7a0022c8adfdf07418cd"), - "plugins/Feedback/angularjs/ratefeature/ratefeature-controller.js" => array("633", "4dd44a1936c30a3cfc157ed7c3fd2653"), - "plugins/Feedback/angularjs/ratefeature/ratefeature-directive.js" => array("542", "4376ff3f0cf1d5c78a92e8900fa14161"), - "plugins/Feedback/angularjs/ratefeature/ratefeature.html" => array("1554", "f61eee9f73fada95471dec5c5980b6b4"), - "plugins/Feedback/angularjs/ratefeature/ratefeature.less" => array("358", "51dbbd1fab358ef455a06d53697bffc7"), - "plugins/Feedback/angularjs/ratefeature/ratefeature-model.js" => array("543", "3807890aedaff7895eb3f3c9f1f00015"), + "plugins/Feedback/angularjs/ratefeature/ratefeature.controller.js" => array("886", "5bedc947e371824649ce0edcbc59fa98"), + "plugins/Feedback/angularjs/ratefeature/ratefeature.directive.html" => array("1650", "2b44e7af1072aec9ec08feba108ac43a"), + "plugins/Feedback/angularjs/ratefeature/ratefeature.directive.js" => array("733", "951a9d0f7d77f3daff4aa8dc87cd370f"), + "plugins/Feedback/angularjs/ratefeature/ratefeature.directive.less" => array("358", "51dbbd1fab358ef455a06d53697bffc7"), + "plugins/Feedback/angularjs/ratefeature/ratefeature-model.service.js" => array("744", "e494c05f594f9f0ef0ca9ec052c56e25"), "plugins/Feedback/angularjs/ratefeature/thumbs-down.png" => array("2188", "38c37611f821aa2671d60dc843345d12"), "plugins/Feedback/angularjs/ratefeature/thumbs-up.png" => array("2202", "9befe01114b4a5801808e8899b5010a7"), - "plugins/Feedback/API.php" => array("3265", "09a9d79dc7bbc8010239f81887d0de38"), - "plugins/Feedback/Controller.php" => array("623", "89de65e12271361e2e2aee5c59641288"), - "plugins/Feedback/Feedback.php" => array("2185", "73bd60a41e1b6221493799866a47291b"), + "plugins/Feedback/API.php" => array("2841", "ef6334e02c9e12ffadee8d0fc71d0b46"), + "plugins/Feedback/Controller.php" => array("497", "40b3096479efd2dc900fd83f671ac93a"), + "plugins/Feedback/Feedback.php" => array("1790", "6ef35f0bb2ab54926c2a33789a87a1d2"), "plugins/Feedback/images/facebook.png" => array("302", "9ae558673b40a7f3eeaad17b281ba4f8"), "plugins/Feedback/images/github.png" => array("361", "9417fe2b0664c8d8f23b1765728c46c1"), "plugins/Feedback/images/linkedin.png" => array("336", "26bebcbbef85af866892f6aa7c49e079"), "plugins/Feedback/images/newsletter.png" => array("444", "740cc523cc992a4e9c089d7dc589b3eb"), "plugins/Feedback/images/twitter.png" => array("376", "fcac98bcbf056e136c7b06557d7bcb98"), - "plugins/Feedback/stylesheets/feedback.less" => array("1539", "c0c45446610709fef33f18bcf4f6ef14"), - "plugins/Feedback/templates/index.twig" => array("5746", "e963b3fcd9eb4f753c2d944c174def7b"), - "plugins/Goals/API.php" => array("24370", "539367dfc9fbf1b43bf84839ee86eb58"), - "plugins/Goals/Archiver.php" => array("15665", "d6178a9fdfbaf1c96ae1fed57ba6a683"), - "plugins/Goals/Controller.php" => array("21788", "377dae6591eab934b51afaa0fab43aa9"), - "plugins/Goals/Goals.php" => array("31816", "0ea5f9311ee7c1d5ef051dd14f81e357"), - "plugins/Goals/javascripts/goalsForm.js" => array("6201", "acbd9a7ee2de6fd6e5a2f2b6698ece5a"), - "plugins/Goals/stylesheets/goals.css" => array("460", "6b076df7c73aa99431820279517ea9cd"), - "plugins/Goals/templates/_addEditGoal.twig" => array("4456", "980da0a6237f27c853fd9c57a3db1380"), - "plugins/Goals/templates/addNewGoal.twig" => array("432", "530107fc4f4a5fb8ea5744592e3c3646"), - "plugins/Goals/templates/_formAddGoal.twig" => array("5011", "af7bdec1ae036d7dc363cbb0977fc850"), - "plugins/Goals/templates/getGoalReportView.twig" => array("3030", "395e8c171b9268be5e4e7ccf24e7c6be"), - "plugins/Goals/templates/getOverviewView.twig" => array("1863", "afb8bced90b1de94b24546bd5ae21604"), - "plugins/Goals/templates/_listGoalEdit.twig" => array("2736", "6c0d563edf0c1971097e0c41d5516a9c"), - "plugins/Goals/templates/_listTopDimension.twig" => array("615", "86df6f4ecfa56b02c72075f7beace1d5"), - "plugins/Goals/templates/_titleAndEvolutionGraph.twig" => array("3785", "ae6e728ff64176a617f69d426834e6ba"), - "plugins/Goals/Visualizations/Goals.php" => array("11535", "13ea4bfab83f1f683db27a8f62d453f8"), - "plugins/ImageGraph/API.php" => array("22505", "b5f4b78e2b6b1e4eb667cd5601124edf"), - "plugins/ImageGraph/Controller.php" => array("2596", "028bc0f95cddb8fd42a72007ee8677d4"), + "plugins/Feedback/lang/ar.json" => array("636", "c9f21b2dc28529b6817f724306ba927e"), + "plugins/Feedback/lang/be.json" => array("675", "19e5e4c5267091de81319d29fffb06c0"), + "plugins/Feedback/lang/bg.json" => array("980", "6870551b7b03accbe3a7ed6d68f9fd47"), + "plugins/Feedback/lang/bn.json" => array("69", "267c5b50e912af191bb3d14c6add0a85"), + "plugins/Feedback/lang/ca.json" => array("596", "a85f9feb7102a4b8152ba040b5263183"), + "plugins/Feedback/lang/cs.json" => array("2820", "cf2b53a3a59c0f06ae3618a984964da2"), + "plugins/Feedback/lang/da.json" => array("1877", "e967fb8eaaa8ffaa7f8ce58a7411dfe4"), + "plugins/Feedback/lang/de.json" => array("3093", "3a5236b8a733db550f226b133322027b"), + "plugins/Feedback/lang/el.json" => array("4659", "7e33efd431f6da4e1ea2c3089fbf79f4"), + "plugins/Feedback/lang/en.json" => array("2724", "3cd89597c37ee39d30931da96fd4643e"), + "plugins/Feedback/lang/es.json" => array("2989", "2ad74c798c7e4aaaa45d6fd999ee4fac"), + "plugins/Feedback/lang/et.json" => array("103", "345251cae1fbd324cb500e55fac7f7bc"), + "plugins/Feedback/lang/fa.json" => array("857", "ce24f1c8dcd904763fdeeb8358553d30"), + "plugins/Feedback/lang/fi.json" => array("1047", "288655d918587937183c4a09b4c610ba"), + "plugins/Feedback/lang/fr.json" => array("3149", "42940a9ed24e576d8284dbb8adb548b4"), + "plugins/Feedback/lang/gl.json" => array("194", "137162a0dc3609abdd85c8e3f9340c1d"), + "plugins/Feedback/lang/hi.json" => array("1808", "0763dcaa19ef9b6fd469d61883420a5c"), + "plugins/Feedback/lang/hu.json" => array("573", "078bb1154afb6e195356cf1b08950dc0"), + "plugins/Feedback/lang/id.json" => array("592", "0d51055aca26ba2a2c30562cd2a762ec"), + "plugins/Feedback/lang/it.json" => array("2893", "0579ace5da9fa1095957cd76af08b5c0"), + "plugins/Feedback/lang/ja.json" => array("3727", "6e248438bb4888411c19e3638ff5a27e"), + "plugins/Feedback/lang/ka.json" => array("949", "23104be4b3c8498f7fec62cb775d0f18"), + "plugins/Feedback/lang/ko.json" => array("3142", "34735136a98a4dffec630e2722197e43"), + "plugins/Feedback/lang/lt.json" => array("1314", "60f2f27809368debbeef03aff0e0d7ab"), + "plugins/Feedback/lang/lv.json" => array("460", "d31689ab2cee8e16f076a687921b4bc7"), + "plugins/Feedback/lang/nb.json" => array("1170", "98dd1f6f81467247b3b141683c8feed9"), + "plugins/Feedback/lang/nl.json" => array("2855", "d4f70f0fd1f74ffc6f20ddbe174f9232"), + "plugins/Feedback/lang/nn.json" => array("477", "9ac4a7303b7d7ec5ed70c53a83b9af06"), + "plugins/Feedback/lang/pl.json" => array("1475", "a1a2186fd7bb1bcd87948332a35df6fa"), + "plugins/Feedback/lang/pt-br.json" => array("2891", "7c43ef7e7c3369e8f422b844c82baa70"), + "plugins/Feedback/lang/pt.json" => array("500", "f798545eb805343d864138d35aa1f928"), + "plugins/Feedback/lang/ro.json" => array("1702", "8330630751d0d7c3a038d4f546b86661"), + "plugins/Feedback/lang/ru.json" => array("3658", "87b5d237495988958e1c728702ff80e4"), + "plugins/Feedback/lang/sk.json" => array("107", "07afc9955d05d35a72428b2ccfc59ea0"), + "plugins/Feedback/lang/sl.json" => array("496", "896f696e7f7a045e37046cb3e167fbdb"), + "plugins/Feedback/lang/sq.json" => array("3096", "c64c513c3eb23d613e110842a0259c60"), + "plugins/Feedback/lang/sr.json" => array("2862", "1ecb863e3d83a3d823ae4f852f48b515"), + "plugins/Feedback/lang/sv.json" => array("2145", "51b6964bca358d02b973d1c6cfe57086"), + "plugins/Feedback/lang/ta.json" => array("780", "c76f589a520d322782c433c86e10c1d3"), + "plugins/Feedback/lang/th.json" => array("901", "fe2b62fa1a7a7a8bf9aebe680c9e9e15"), + "plugins/Feedback/lang/tl.json" => array("2026", "d12a2906a213f21a69c8cff449ce2053"), + "plugins/Feedback/lang/tr.json" => array("935", "d53d290f73793846b424bfc9ed36b2dd"), + "plugins/Feedback/lang/uk.json" => array("730", "4255f040455d4366dfe60591b6d61b1b"), + "plugins/Feedback/lang/vi.json" => array("690", "7e56808f8dba90fae84a6b82d952dfa3"), + "plugins/Feedback/lang/zh-cn.json" => array("524", "0ff1aea432da48bb3302fdd178363b59"), + "plugins/Feedback/lang/zh-tw.json" => array("541", "3e4af952f5a40ebdb1d4a559d6550ffe"), + "plugins/Feedback/Menu.php" => array("574", "f7bfdea0c96ff23b54d1e5593e4800b9"), + "plugins/Feedback/stylesheets/feedback.less" => array("1453", "20c642919047d5d1b4cf218a6da27d13"), + "plugins/Feedback/templates/index.twig" => array("6799", "e5f094c4c05b86cfea6b22c5577faac3"), + "plugins/Goals/API.php" => array("27293", "414b3d8d2d3ec916e7157164978dde65"), + "plugins/Goals/Archiver.php" => array("15402", "792fc1d66c67003bcb81171444869e03"), + "plugins/Goals/Columns/DaysToConversion.php" => array("382", "ed82de1a43d076d39b1f13ee65263603"), + "plugins/Goals/Columns/IdGoal.php" => array("820", "4948e53266d528003ab613c7fcbd5f45"), + "plugins/Goals/Columns/Metrics/AverageOrderRevenue.php" => array("1537", "fdd6c8454325c1e45fca23e279c82096"), + "plugins/Goals/Columns/Metrics/AveragePrice.php" => array("1705", "fb284dc40ff336d3abbca7a0ac926b90"), + "plugins/Goals/Columns/Metrics/AverageQuantity.php" => array("1194", "38cc026632b56dae8e2a2f01ad061231"), + "plugins/Goals/Columns/Metrics/GoalSpecific/AverageOrderRevenue.php" => array("1973", "3435e637d9d2b4435d6f24d669eac2a0"), + "plugins/Goals/Columns/Metrics/GoalSpecific/ConversionRate.php" => array("1741", "6a5dfa070629dc3e4e7da80f19d4cf48"), + "plugins/Goals/Columns/Metrics/GoalSpecific/Conversions.php" => array("1134", "87d24ac78898babe9b1fa65bc0227a1f"), + "plugins/Goals/Columns/Metrics/GoalSpecific/ItemsCount.php" => array("1241", "46702e9b3ca8fa511cd62681ed19ff5b"), + "plugins/Goals/Columns/Metrics/GoalSpecificProcessedMetric.php" => array("3072", "73fb7953a3f6c88226c6a8ba3ea5ac40"), + "plugins/Goals/Columns/Metrics/GoalSpecific/RevenuePerVisit.php" => array("2458", "41da5e857cd8e8ee1fe2a7eb0dee3f96"), + "plugins/Goals/Columns/Metrics/GoalSpecific/Revenue.php" => array("1670", "6bed1adb40da07865cf157dd7560392a"), + "plugins/Goals/Columns/Metrics/ProductConversionRate.php" => array("1391", "9e42694565dd94e4b487b3626dad2bc5"), + "plugins/Goals/Columns/Metrics/RevenuePerVisit.php" => array("2678", "753939d3cf5d575344813515a996c428"), + "plugins/Goals/Columns/VisitsUntilConversion.php" => array("392", "61cf0df1b4bece1b546e2d78134719d0"), + "plugins/Goals/Controller.php" => array("21420", "31e2ca4e3e5ab8e779bd0d4371178a92"), + "plugins/Goals/DataTable/Filter/AppendNameToColumnNames.php" => array("1544", "6a990a585c5e30871517a07c87dcd2f9"), + "plugins/Goals/Goals.php" => array("10191", "31e124f47f7d01e90b979c00356b9637"), + "plugins/Goals/javascripts/goalsForm.js" => array("7856", "c4c1e97de8fcc292a45759872ed9d5ac"), + "plugins/Goals/lang/am.json" => array("71", "febcb197ee67c57bae6fc0d67fb17ebf"), + "plugins/Goals/lang/ar.json" => array("4405", "a3cf10a7ac44514bae9a87a71d66f07a"), + "plugins/Goals/lang/be.json" => array("8907", "e1ba1d48df7fd9e1d4d127ff22888907"), + "plugins/Goals/lang/bg.json" => array("9810", "d78a188d4e7d1a1121e817589539650e"), + "plugins/Goals/lang/bs.json" => array("389", "448905e1a6a1d0348c6a9482013781aa"), + "plugins/Goals/lang/ca.json" => array("7266", "4634c3bc309fdcb8f991485f348ccfa9"), + "plugins/Goals/lang/cs.json" => array("8525", "31500eb011d62e5a45b478d4daa73b02"), + "plugins/Goals/lang/da.json" => array("7673", "b875ce9ffc1aa7a75aa36fb5364a3f42"), + "plugins/Goals/lang/de.json" => array("8835", "0967a3a87eb98acb6a5bbf1ec922d42a"), + "plugins/Goals/lang/el.json" => array("13952", "9dbd52783c46970f83022811b3914b1b"), + "plugins/Goals/lang/en.json" => array("8206", "f36e4dffee5c0e9b294259acdc3191d5"), + "plugins/Goals/lang/es.json" => array("8884", "dfa564a84ffaeb01fa06593413c0e7fd"), + "plugins/Goals/lang/et.json" => array("3399", "e35fdce62c10e714ea427da4d5688eba"), + "plugins/Goals/lang/eu.json" => array("949", "a96fba8b3f8aa6069232dacbb2a6a878"), + "plugins/Goals/lang/fa.json" => array("6481", "e351bd942f1f12270331ca2187d86372"), + "plugins/Goals/lang/fi.json" => array("6972", "52f957d6f49438c9b0567b1fbdad7435"), + "plugins/Goals/lang/fr.json" => array("9276", "5d34cd21ca39638f7e4eee6d10323e7a"), + "plugins/Goals/lang/gl.json" => array("398", "7bac6341a22ba0fb4675957946933aba"), + "plugins/Goals/lang/he.json" => array("66", "79417c11b0507e442f677340823dba52"), + "plugins/Goals/lang/hi.json" => array("13398", "3d0407b774732eb7ddf412751748134e"), + "plugins/Goals/lang/hu.json" => array("3234", "c8ced14246b09012157b18c7ce8c17fb"), + "plugins/Goals/lang/id.json" => array("6956", "46d8d3d02cfe7f523b158eb056606649"), + "plugins/Goals/lang/is.json" => array("1812", "557094c205c28f30844f4bf5cb0618ca"), + "plugins/Goals/lang/it.json" => array("8895", "fb6ea7c3be4a4e6cc2a832255f14cbb2"), + "plugins/Goals/lang/ja.json" => array("10002", "4da76c2e97a99fa2564932d47452a58f"), + "plugins/Goals/lang/ka.json" => array("5148", "2a14fd7fc2f7a4d24c9bed73b040e196"), + "plugins/Goals/lang/ko.json" => array("8587", "2b0c29916f451c83383be9088d83a434"), + "plugins/Goals/lang/lt.json" => array("2678", "207edfc35845fce83988d6d4410d899b"), + "plugins/Goals/lang/lv.json" => array("2367", "7ea4e57fba20cc5f27eb3dcc7766c742"), + "plugins/Goals/lang/nb.json" => array("2689", "2bbec6a2c3fbd3bf4805547972fe52f0"), + "plugins/Goals/lang/nl.json" => array("8620", "aa6cb3e5bd5f78b30b827aae07dbc562"), + "plugins/Goals/lang/nn.json" => array("2001", "61dc1905279e6e2908cb69d5d30d0abf"), + "plugins/Goals/lang/pl.json" => array("5191", "c935d9688b48908e4dafddfa9fcf168f"), + "plugins/Goals/lang/pt-br.json" => array("8928", "8f163eda7455265f39b2acfe37be81c3"), + "plugins/Goals/lang/pt.json" => array("7542", "352fc7c4f5220aa90fba0887de3ec1f8"), + "plugins/Goals/lang/ro.json" => array("7487", "604f604afff5245ea3ee1082c0d950de"), + "plugins/Goals/lang/ru.json" => array("12021", "7cd0803a6ef8c8091895f2f32d4e829d"), + "plugins/Goals/lang/sk.json" => array("3001", "efe3f5982af559c9f4d12aab957db8e6"), + "plugins/Goals/lang/sl.json" => array("1648", "3f79dfee0f48c91f7ae6d819254efe02"), + "plugins/Goals/lang/sq.json" => array("9417", "1409c9c0c0a0ba474a213a2670cf3320"), + "plugins/Goals/lang/sr.json" => array("8368", "3bedb1d42032916c522125c816563fcb"), + "plugins/Goals/lang/sv.json" => array("8063", "99ef13d1d7a8197e20d087517f963521"), + "plugins/Goals/lang/ta.json" => array("1685", "91df9cb5f6ffeaaf2d412390125692ef"), + "plugins/Goals/lang/te.json" => array("242", "4c5d0b0576b482bb8391abb7294a7fd3"), + "plugins/Goals/lang/th.json" => array("5593", "05ceea397a30cf3794a5b7edd654d4f5"), + "plugins/Goals/lang/tr.json" => array("2995", "60ebf10f0958f7fb44977ea02c260059"), + "plugins/Goals/lang/uk.json" => array("3843", "f7f91bdc53d58ee86994a6d8a5397b69"), + "plugins/Goals/lang/vi.json" => array("8632", "3bfa8b64f8168863e718266f06aa0986"), + "plugins/Goals/lang/zh-cn.json" => array("6029", "d4cc9115bc6f0fb26bd716380b893319"), + "plugins/Goals/lang/zh-tw.json" => array("2461", "edff7ebbde963ca0b0701683d5c65e13"), + "plugins/Goals/Menu.php" => array("2515", "5b4c7819ace7773690393bb046b0a9a1"), + "plugins/Goals/Model.php" => array("2380", "db71a051ec46ed8419a56db41e1d4ca1"), + "plugins/Goals/Reports/Base.php" => array("1487", "e45fa34e7c9f8ce552e5946484aa14bd"), + "plugins/Goals/Reports/GetDaysToConversion.php" => array("2076", "3211996a2c56b01efe0917497f7bb5d2"), + "plugins/Goals/Reports/GetMetrics.php" => array("796", "50aff6f80706b2dd4c4bd48af38c7e10"), + "plugins/Goals/Reports/Get.php" => array("1030", "e5ea6d8513113d5b8bbe48a7c8f40f2e"), + "plugins/Goals/Reports/GetVisitsUntilConversion.php" => array("2098", "af64afa4ba3abc017c73a3d5440cb3cd"), + "plugins/Goals/stylesheets/goals.css" => array("571", "b6a1300d4a90467c8379574ae8be92a3"), + "plugins/Goals/templates/_addEditGoal.twig" => array("3251", "f94098eef1e5713b458847fefcc89cd1"), + "plugins/Goals/templates/addNewGoal.twig" => array("890", "a885958baadf8603ac42f53fd384a7bd"), + "plugins/Goals/templates/editGoals.twig" => array("331", "17fa0c95106ad899870dadf6848121c1"), + "plugins/Goals/templates/_formAddGoal.twig" => array("5806", "1d279a3c42f3ed22c7800bb7203bb4cd"), + "plugins/Goals/templates/getGoalReportView.twig" => array("3269", "a0f5f57660aed077bdff5d64c2fc019f"), + "plugins/Goals/templates/getOverviewView.twig" => array("2021", "2fd51699b44cec934e6396dc9b4170bc"), + "plugins/Goals/templates/_listGoalEdit.twig" => array("3894", "14ee1105421bdceac6c4560d581ca150"), + "plugins/Goals/templates/_listTopDimension.twig" => array("629", "6ccbc50d4b3b501d5baa85cc35bbc94a"), + "plugins/Goals/templates/manageGoals.twig" => array("1526", "1d3a7c03ad50e87045b59217cc1b2978"), + "plugins/Goals/templates/_titleAndEvolutionGraph.twig" => array("4288", "366be17d9da15bd3cc7474ba7ab532e0"), + "plugins/Goals/Tracker/GoalsRequestProcessor.php" => array("5354", "f6ba71dbcb8846c0c2855bea776859db"), + "plugins/Goals/TranslationHelper.php" => array("3480", "063a70a7bf495a3515195cf208ccacc4"), + "plugins/Goals/Visualizations/Goals.php" => array("7273", "3ca5d0087dd32e672ac5e1ccd3f9ddf2"), + "plugins/Goals/Widgets.php" => array("915", "8e7acdd780424350fd1b8490ac1b688c"), + "plugins/Heartbeat/Heartbeat.php" => array("315", "1bcd69e82e3cbf1e7cb0b846e4b04892"), + "plugins/Heartbeat/plugin.json" => array("53", "934e6931668e548a080d085fecdd7949"), + "plugins/Heartbeat/Tracker/PingRequestProcessor.php" => array("1570", "2fb3fecbb11aeea7c4eac10893f64fc9"), + "plugins/ImageGraph/API.php" => array("23508", "4e6f9bf746766650f98cdf0c4143217a"), + "plugins/ImageGraph/Controller.php" => array("2651", "012cbc318556f2b95f32590ec090802f"), "plugins/ImageGraph/fonts/tahoma.ttf" => array("94740", "fabde5f388432a4a2bd40bc07ac53971"), - "plugins/ImageGraph/ImageGraph.php" => array("6041", "e604bd6a45f1e5c165319f0108826fbf"), - "plugins/ImageGraph/StaticGraph/Evolution.php" => array("576", "75d6bc92ee26a9153a19479fc26e81d6"), - "plugins/ImageGraph/StaticGraph/Exception.php" => array("1513", "b2e05ae76bb47496c259bbbb23632b1c"), - "plugins/ImageGraph/StaticGraph/GridGraph.php" => array("19969", "3beb0b57cb2837fac11c5f53be6605d3"), - "plugins/ImageGraph/StaticGraph/HorizontalBar.php" => array("7097", "a0b09ce105f15b874f7f867a71a80888"), - "plugins/ImageGraph/StaticGraph.php" => array("9417", "9d2e8b5b979bed66c7db0bbd64c87152"), - "plugins/ImageGraph/StaticGraph/Pie3D.php" => array("464", "f432e2a17d9c16fb86909bc596bacf44"), - "plugins/ImageGraph/StaticGraph/PieGraph.php" => array("4227", "d61366790d5b83979b81633631479e50"), - "plugins/ImageGraph/StaticGraph/Pie.php" => array("463", "4ffc2f6fbc909875f01df6159472d6c5"), - "plugins/ImageGraph/StaticGraph/VerticalBar.php" => array("700", "fa2f2c41aa965694711d69b94269f0c2"), + "plugins/ImageGraph/ImageGraph.php" => array("6796", "001fe9f452e292140e116100e8764fc9"), + "plugins/ImageGraph/lang/bg.json" => array("164", "40b9a40e87ad6fcac13eff40ad4ae240"), + "plugins/ImageGraph/lang/ca.json" => array("150", "1fe6f37114be95681fa4627db68115ed"), + "plugins/ImageGraph/lang/cs.json" => array("232", "157b2c1033672b7ea95ed133be65e528"), + "plugins/ImageGraph/lang/da.json" => array("230", "ebb795d9321b1736d7b3bf0776b5f9b0"), + "plugins/ImageGraph/lang/de.json" => array("270", "9067c75797b6d6f6d13e064680029a26"), + "plugins/ImageGraph/lang/el.json" => array("380", "da0817f582b365b51c40f62c29a2d085"), + "plugins/ImageGraph/lang/en.json" => array("232", "b20badf10740f0e664d9d922c3bdfe5e"), + "plugins/ImageGraph/lang/es.json" => array("268", "cededde68a7332b8528b1bcbdfe64b78"), + "plugins/ImageGraph/lang/fa.json" => array("152", "2fe660c925b5a0a59a1b72e77fda671b"), + "plugins/ImageGraph/lang/fi.json" => array("140", "e402c7b3bbf817e30a1ac61849355cb9"), + "plugins/ImageGraph/lang/fr.json" => array("295", "738816d87eab06cfe9648662e1e716fa"), + "plugins/ImageGraph/lang/hi.json" => array("436", "dc982400b054102df811996f6687cd05"), + "plugins/ImageGraph/lang/id.json" => array("138", "97e9847c5627323ea35092dcd51aa2a9"), + "plugins/ImageGraph/lang/it.json" => array("263", "6b4b2e936c5f296e2113fbd87908bd88"), + "plugins/ImageGraph/lang/ja.json" => array("318", "026c0794cfbd384569475037873145b8"), + "plugins/ImageGraph/lang/ko.json" => array("143", "27df6acce9882e66851c58796cbecec2"), + "plugins/ImageGraph/lang/lv.json" => array("141", "01e4f605267fbf444537f9f98754c68d"), + "plugins/ImageGraph/lang/nb.json" => array("222", "00cb76b9d51ba84c127a99f4597d21c9"), + "plugins/ImageGraph/lang/nl.json" => array("250", "22dd956078d8fca91917accc1101692f"), + "plugins/ImageGraph/lang/nn.json" => array("128", "3bca6be383e1a3902a20370ae0011514"), + "plugins/ImageGraph/lang/pt-br.json" => array("245", "0c8e0d99b0eaba1345a44b2e4bdfc69f"), + "plugins/ImageGraph/lang/pt.json" => array("133", "5c482101c5c91c3687142242da39ec29"), + "plugins/ImageGraph/lang/ro.json" => array("142", "817812eeec015e376a94b3d28312ba03"), + "plugins/ImageGraph/lang/ru.json" => array("196", "fb386cf4f486c1e11f225cbc6e55d5fc"), + "plugins/ImageGraph/lang/sl.json" => array("124", "3924f93696402c518704bdf2e3c2931a"), + "plugins/ImageGraph/lang/sq.json" => array("147", "20d1de2f6808af4cd47aa0cbc737a432"), + "plugins/ImageGraph/lang/sr.json" => array("249", "9a368f7a451029168b89ab06776e840d"), + "plugins/ImageGraph/lang/sv.json" => array("230", "0e66ea7f07ad8bd73ca8f733816443ae"), + "plugins/ImageGraph/lang/vi.json" => array("146", "4282dc22e53a05b930b0a7d82a8ecdb9"), + "plugins/ImageGraph/lang/zh-cn.json" => array("108", "95a367dc7f16634127f32910c7c13ca2"), + "plugins/ImageGraph/StaticGraph/Evolution.php" => array("579", "c94186d1c04aa6068588076e167f8a16"), + "plugins/ImageGraph/StaticGraph/Exception.php" => array("1517", "4c6a133b0727e027b1cd0d907ad5eb4a"), + "plugins/ImageGraph/StaticGraph/GridGraph.php" => array("19982", "6466972ede0946317dc9442c4171a58b"), + "plugins/ImageGraph/StaticGraph/HorizontalBar.php" => array("7100", "01f8036220239c5f89e5c966bccee565"), + "plugins/ImageGraph/StaticGraph.php" => array("9352", "263f0666d0ac79042dd7bade08662de8"), + "plugins/ImageGraph/StaticGraph/Pie3D.php" => array("468", "7e9300222c549c8c34b1c46643864dd9"), + "plugins/ImageGraph/StaticGraph/PieGraph.php" => array("4226", "4abbf922b246f16285aa9c077a6e79c4"), + "plugins/ImageGraph/StaticGraph/Pie.php" => array("466", "9e1ebb3cb89aff2fcd58e0c62cdd1f60"), + "plugins/ImageGraph/StaticGraph/VerticalBar.php" => array("703", "1074ef95a75b9d28778d3ae08f426596"), "plugins/ImageGraph/templates/index.twig" => array("148", "29658e5544cd076021ea898993b8506d"), - "plugins/ImageGraph/templates/testAllSizes.twig" => array("2557", "2eca39df92b75a1c67933d928d8815e9"), - "plugins/Insights/API.php" => array("13847", "788dd0b6a9e6d3ead74f319a706b94db"), - "plugins/Insights/Controller.php" => array("2184", "2f3d6d7f84ecddfb5b75a1ad780ace40"), - "plugins/Insights/DataTable/Filter/ExcludeLowValue.php" => array("1607", "bde83b71854b84f55894e754cc36b43f"), - "plugins/Insights/DataTable/Filter/Insight.php" => array("3876", "d5b9908e494619123f6c61d1e2a9aeb0"), - "plugins/Insights/DataTable/Filter/Limit.php" => array("1427", "896f5aaa0f1203918f09bcd96152c713"), - "plugins/Insights/DataTable/Filter/MinGrowth.php" => array("1489", "f385e31013c7e27a8b178ce5cf193aed"), - "plugins/Insights/DataTable/Filter/OrderBy.php" => array("1984", "fa2aa2e46620f2d92fa2aac30960efa1"), + "plugins/ImageGraph/templates/testAllSizes.twig" => array("2538", "e0d7b01414a8126ec7d3cbdda5f09111"), + "plugins/Insights/API.php" => array("13712", "86d3a6e694290b13facd3de37247a1c1"), + "plugins/Insights/Controller.php" => array("2280", "7895de22b6b085f935f0323560ff40f9"), + "plugins/Insights/DataTable/Filter/ExcludeLowValue.php" => array("1611", "46455071eab3b0b0fc07b81a316930f4"), + "plugins/Insights/DataTable/Filter/Insight.php" => array("3880", "0b70287dbb10551d12324c2f7f8f03e7"), + "plugins/Insights/DataTable/Filter/Limit.php" => array("1431", "6eb80c876549f33ea66827dbed49ff63"), + "plugins/Insights/DataTable/Filter/MinGrowth.php" => array("1493", "33bb8452f7037a36ff121e0530647be8"), + "plugins/Insights/DataTable/Filter/OrderBy.php" => array("1988", "6c920e99b0eca4866d9502499b44687e"), "plugins/Insights/images/idea.png" => array("364", "bc677415eb4fb941a85c568b6aa656ba"), - "plugins/Insights/InsightReport.php" => array("11233", "e74af746ab91d381bb04cb1cf5c9506a"), - "plugins/Insights/Insights.php" => array("1412", "00a6ff865dc2ed2728dd8dd58438784b"), - "plugins/Insights/javascripts/insightsDataTable.js" => array("3882", "559f938d2e4a3e48ceb0e2827c2a33c3"), - "plugins/Insights/Model.php" => array("3366", "ca38127d95af08cb9f7e7aa1744f396c"), - "plugins/Insights/plugin.json" => array("284", "15013f4f566ee639b3d4566468b46fe8"), - "plugins/Insights/stylesheets/insightVisualization.less" => array("619", "5cb33ec3db0c5d33d3d7714ec80be638"), + "plugins/Insights/InsightReport.php" => array("11237", "ae427f0dbf5e4712ada47bc1e592b76c"), + "plugins/Insights/Insights.php" => array("792", "060b03198eff6d1f17e19565c9dba157"), + "plugins/Insights/javascripts/insightsDataTable.js" => array("3897", "b55359d1e089adcc00be9fa4f975ac9c"), + "plugins/Insights/lang/bg.json" => array("885", "31cff85af21f3608d428bb58dc77aef3"), + "plugins/Insights/lang/cs.json" => array("2788", "3a56d36d182f3989e1bc5a140ae8a255"), + "plugins/Insights/lang/da.json" => array("2460", "d30c9e56afce12659754775ce0f218ee"), + "plugins/Insights/lang/de.json" => array("2941", "fdb67878c5763c4ac550e105393beb7b"), + "plugins/Insights/lang/el.json" => array("4097", "ef758cb7d3e5cdaf48465092ba9a78ff"), + "plugins/Insights/lang/en.json" => array("2636", "a0b8eb4465e901d8efd7f73486cad81d"), + "plugins/Insights/lang/es.json" => array("2875", "a23a0429b5c51847e61959c7d1351c4b"), + "plugins/Insights/lang/et.json" => array("354", "07fe868c73ef66b2668000cc0a2d3e75"), + "plugins/Insights/lang/fa.json" => array("581", "133cd20f6513dcbbfe7368d023c3f1b8"), + "plugins/Insights/lang/fi.json" => array("1240", "9216bd8baa2966f9226d6c19fd50a23d"), + "plugins/Insights/lang/fr.json" => array("2969", "e1b935b5497205c534103e1ca3fdfea7"), + "plugins/Insights/lang/gl.json" => array("404", "b88d99175e88efd14a4568b36be4a81b"), + "plugins/Insights/lang/hi.json" => array("2417", "aa9ab09aae2fa67e9eafd20f54f089ee"), + "plugins/Insights/lang/it.json" => array("2819", "0bd93bf32acf0536ce0fda71690feebe"), + "plugins/Insights/lang/ja.json" => array("2947", "b47c2502fb0de5872f5ba9e712b5aab5"), + "plugins/Insights/lang/nb.json" => array("685", "e0bb01b2e20508cb68b86677f898c151"), + "plugins/Insights/lang/nl.json" => array("2775", "74419977d60273df39b868a87c39e555"), + "plugins/Insights/lang/pl.json" => array("929", "5daca4856a87db83fe54d3fcdba6f969"), + "plugins/Insights/lang/pt-br.json" => array("2761", "851f1f50e14c121ad0f36b375ea55c89"), + "plugins/Insights/lang/pt.json" => array("396", "31f76fa16b16254d498abca339c4629e"), + "plugins/Insights/lang/ro.json" => array("2662", "36b751f266c058a6db57295307b724d4"), + "plugins/Insights/lang/ru.json" => array("1120", "efd3bca887495d9a6fe0e363656ade97"), + "plugins/Insights/lang/sq.json" => array("473", "83bd2547e8f79e6a63b28fe8f21cce98"), + "plugins/Insights/lang/sr.json" => array("2688", "28fded4f7c9852f3d5659de4510f2a71"), + "plugins/Insights/lang/sv.json" => array("2526", "a72ae467459cbf8df35d2fac1fabe613"), + "plugins/Insights/lang/ta.json" => array("546", "2fd564494b0a1c7745cef6a8c47c5f4b"), + "plugins/Insights/lang/tl.json" => array("1392", "a9ecaaab526b11a7bfdaf3176fb23155"), + "plugins/Insights/lang/tr.json" => array("526", "9e08fce590e817743aa7150c1e37727b"), + "plugins/Insights/lang/vi.json" => array("85", "ecfe4c8dcd57490f4a178800db7eafda"), + "plugins/Insights/lang/zh-cn.json" => array("396", "92b8c60c095d74c1c268f88c2d1036f8"), + "plugins/Insights/Model.php" => array("3367", "4b45524fc79cc679c0d64f6dda643b32"), + "plugins/Insights/stylesheets/insightVisualization.less" => array("665", "6b387ea5d5407decffb9a2d0951d9c44"), "plugins/Insights/templates/cannotDisplayReport.twig" => array("103", "02d257f7747a1f7734235a56faa900ba"), - "plugins/Insights/templates/insightControls.twig" => array("3190", "a1b43257232d5e18e746f3839b8e17be"), - "plugins/Insights/templates/insightsOverviewWidget.twig" => array("314", "19c200a37664de6dda1a858e2e133df4"), + "plugins/Insights/templates/insightControls.twig" => array("3193", "d776fef3d93201b4c80642cbbaaed112"), + "plugins/Insights/templates/insightsOverviewWidget.twig" => array("448", "f3eee60b34c0493abd1d6bf6184f76e6"), "plugins/Insights/templates/insightVisualization.twig" => array("1360", "7036f0548b2901935552b9929bf4bd6d"), - "plugins/Insights/templates/moversAndShakersOverviewWidget.twig" => array("672", "26093e2b2575a0b1b058d8b15e30f02e"), + "plugins/Insights/templates/moversAndShakersOverviewWidget.twig" => array("842", "310ee58e56eb9093d17d77681c7f02af"), "plugins/Insights/templates/overviewWidget.twig" => array("1270", "b420d52e333ea4ca167b897de920a029"), - "plugins/Insights/templates/table_header.twig" => array("440", "6f5ad76f20c6fa8536612ffe632a8d99"), + "plugins/Insights/templates/table_header.twig" => array("451", "247e1df4a5bcca625611397a34cb2d76"), "plugins/Insights/templates/table_row.twig" => array("1497", "955a553498046e53131a3320fab79a6f"), - "plugins/Insights/Visualizations/Insight.php" => array("3714", "028a760f1f2c2fd291f0609881aac743"), - "plugins/Insights/Visualizations/Insight/RequestConfig.php" => array("1254", "e62e3a81b32acf3e8f834ff688d9e94f"), - "plugins/Installation/Controller.php" => array("37029", "f9b9cc3440e4b8ebf9a4ba4f5eb5ebe0"), - "plugins/Installation/FormDatabaseSetup.php" => array("11591", "fb9e70d02e5a8201d1455ea6b9cedd13"), - "plugins/Installation/FormFirstWebsiteSetup.php" => array("3369", "e3e7a92773c26cedb6d7b3f9801cf7a3"), - "plugins/Installation/FormGeneralSetup.php" => array("4342", "74f162e30c4601c9bc2d06fa0a26590f"), - "plugins/Installation/Installation.php" => array("3249", "3ad87571d8d4abdc72779452f3b2bb9e"), - "plugins/Installation/javascripts/installation.js" => array("307", "4c37587c9e1d490726acb45f723ca7b9"), - "plugins/Installation/ServerFilesGenerator.php" => array("4673", "64c8e3186155de098d2b7f47cf2f6fee"), - "plugins/Installation/stylesheets/installation.css" => array("3791", "821b94028ac1602a34fce93061d3a7ca"), - "plugins/Installation/stylesheets/systemCheckPage.less" => array("778", "cae7f2f24da86ea2b691ebfed87b8e4c"), - "plugins/Installation/templates/_allSteps.twig" => array("392", "82b5478f80593011dbcf3ecf0af61302"), - "plugins/Installation/templates/databaseCheck.twig" => array("1462", "0ec6aa932fdbbb9fc9a21c498f9d38c4"), - "plugins/Installation/templates/databaseSetup.twig" => array("453", "126314587ffced4a2251edfdf9805169"), - "plugins/Installation/templates/finished.twig" => array("828", "98ccc1a91a49856f891a514a434df74c"), - "plugins/Installation/templates/firstWebsiteSetup.twig" => array("803", "c069ebc5ad0dcd2f032de51e12718437"), - "plugins/Installation/templates/generalSetup.twig" => array("450", "bfbb3e56dfd595a6085034ece031ce5c"), - "plugins/Installation/templates/_integrityDetails.twig" => array("1157", "26f9f8e6a60611f972182b9188cb40ce"), - "plugins/Installation/templates/layout.twig" => array("2369", "171a8bd7aa74aa4e0993c68c6ce69937"), - "plugins/Installation/templates/reuseTables.twig" => array("2884", "11a9acbfc09adb2bdeb2b37c9c810fe0"), - "plugins/Installation/templates/_systemCheckLegend.twig" => array("783", "868726c336470173b390efd117db3eb8"), - "plugins/Installation/templates/systemCheckPage.twig" => array("959", "2e4074ddeff70cf07b68a34500647a42"), - "plugins/Installation/templates/_systemCheckSection.twig" => array("13651", "3ae35c6efa7d38f24dc0fc0206988e91"), - "plugins/Installation/templates/systemCheck.twig" => array("654", "4ca83235ac1786803d690125beb943d5"), - "plugins/Installation/templates/tablesCreation.twig" => array("2445", "0713ca27f416427d8dde18759284e5fa"), - "plugins/Installation/templates/trackingCode.twig" => array("678", "e13755a135a80b77df5960ca92b59174"), - "plugins/Installation/templates/welcome.twig" => array("1341", "c04fe82942a7b099e7d35c32f2752b33"), - "plugins/Installation/View.php" => array("1591", "7433b9692f499c2c3f8804cd8095c788"), - "plugins/LanguagesManager/API.php" => array("9715", "bf712c41d7a1171976490570ab3dac04"), - "plugins/LanguagesManager/Commands/CreatePull.php" => array("8019", "da7be9f183aed2f38461d989a9841ac8"), - "plugins/LanguagesManager/Commands/FetchFromOTrance.php" => array("6166", "19f36b874c56ad9767ce0a3378d9351d"), - "plugins/LanguagesManager/Commands/LanguageCodes.php" => array("1063", "283814548ee804e448d6f1e0e5b90739"), - "plugins/LanguagesManager/Commands/LanguageNames.php" => array("1071", "c7df597dd84037238cba1369489ad396"), - "plugins/LanguagesManager/Commands/PluginsWithTranslations.php" => array("1192", "27e732d20ad5343ad54c2671c7f469bc"), - "plugins/LanguagesManager/Commands/SetTranslations.php" => array("3990", "0197fbf7ed6a56ec8e20edd236487cd0"), - "plugins/LanguagesManager/Commands/Update.php" => array("5748", "e71235b082bad8b9c1f67b941d4ece99"), - "plugins/LanguagesManager/Controller.php" => array("813", "ccfc729a22e4398f1caffd2b4ee32d4b"), - "plugins/LanguagesManager/javascripts/languageSelector.js" => array("2545", "8cad615cf92b73f4c3401419957c2abf"), - "plugins/LanguagesManager/LanguagesManager.php" => array("6527", "522f063e16bec7dd87d380fca5095f18"), - "plugins/LanguagesManager/templates/getLanguagesSelector.twig" => array("1015", "61337194fa9393f34a99726587c6939e"), - "plugins/LeftMenu/plugin.json" => array("128", "af274a9b50394d8054b01dd4508cfcf1"), - "plugins/LeftMenu/stylesheets/theme.less" => array("3139", "3e1b68f368095012788e07bce8e111bd"), - "plugins/Live/API.php" => array("29944", "4aa60c7abc780bb6dfa2ecb0385e6e66"), - "plugins/Live/Controller.php" => array("10331", "d0abb0c4882fd59af19da0968dc063b5"), + "plugins/Insights/Visualizations/Insight.php" => array("4103", "98867a6965e89d66bce76ff6415d8ba5"), + "plugins/Insights/Visualizations/Insight/RequestConfig.php" => array("1211", "13a703baef884bc8596a940efa4d79cc"), + "plugins/Insights/Widgets.php" => array("509", "bc27a29a2817b16007d50d520c7482d8"), + "plugins/Installation/Controller.php" => array("22430", "4a353d17a3831f9ff2e3a4d01c561e23"), + "plugins/Installation/Exception/DatabaseConnectionFailedException.php" => array("298", "ad427b8c36db4be3d838b10abf4c0589"), + "plugins/Installation/FormDatabaseSetup.php" => array("11785", "beac154eed9bd7983953809a14595efb"), + "plugins/Installation/FormDefaultSettings.php" => array("508", "d51b3027dc005df92ee2a47faced2c83"), + "plugins/Installation/FormFirstWebsiteSetup.php" => array("3488", "c7bd30e64957c47615384670024f63fe"), + "plugins/Installation/FormSuperUser.php" => array("4808", "9738112343287d2f4b6d1fcb30aea637"), + "plugins/Installation/Installation.php" => array("3516", "f330802ccc26faa2ce6fe034c3e3ae59"), + "plugins/Installation/javascripts/installation.js" => array("218", "f7fc61747c2f75144384a3ce57e28a0b"), + "plugins/Installation/lang/am.json" => array("4878", "ebd4923a3a361e0644d1ab2d62d8fe44"), + "plugins/Installation/lang/ar.json" => array("17262", "825ed2687b495379dcedcffcbec4ceac"), + "plugins/Installation/lang/be.json" => array("11390", "394c140c924212003709d912392880fa"), + "plugins/Installation/lang/bg.json" => array("15108", "f45d272a9c901cd52592b69fd4874d45"), + "plugins/Installation/lang/bs.json" => array("117", "15c746f8b9a53fa730205ebdc3bbbc31"), + "plugins/Installation/lang/ca.json" => array("10628", "37d730b28f60a6e612877b2238e6dec1"), + "plugins/Installation/lang/cs.json" => array("14378", "25adc3f1b47d5c48c034ebd9baded294"), + "plugins/Installation/lang/da.json" => array("12557", "0fd5efbb9c720cf08c9a64644e98380e"), + "plugins/Installation/lang/de.json" => array("15604", "8e2db325e658988200c231e2b1ccf902"), + "plugins/Installation/lang/el.json" => array("23702", "cdbecbccdf5f5e3d0e6ccd55d0dd7753"), + "plugins/Installation/lang/en.json" => array("13957", "354f99e8f0c7213ea403ab7bcf9721c3"), + "plugins/Installation/lang/es.json" => array("15219", "820bf1e19bb60565d598ab9619d164ab"), + "plugins/Installation/lang/et.json" => array("7428", "35c24aac9416a2f8d888c4eb19a1c2b2"), + "plugins/Installation/lang/eu.json" => array("5338", "3d24149159a692b45a5ff56a143fe4ff"), + "plugins/Installation/lang/fa.json" => array("13424", "0bbf3bef5b209accd87e98beaeb9c650"), + "plugins/Installation/lang/fi.json" => array("11498", "3f8ff8bbdec29c980afb153193e51c0a"), + "plugins/Installation/lang/fr.json" => array("15430", "71e45e1115546d0ad5756c6779bb58d6"), + "plugins/Installation/lang/gl.json" => array("2859", "bf1912d9c35ee95054922db902006dd9"), + "plugins/Installation/lang/he.json" => array("882", "5124ec440e6e94cd327e3515967475cd"), + "plugins/Installation/lang/hi.json" => array("22999", "5e36c0db5f169a6d758e8ddda850b4ba"), + "plugins/Installation/lang/hu.json" => array("8768", "5493bf2db8f70e3321fdf8ca9aa91ecb"), + "plugins/Installation/lang/id.json" => array("10684", "ca40fda28e607dc4c7fd443390280af3"), + "plugins/Installation/lang/is.json" => array("83", "50e992af7138f3c35079e850e592865d"), + "plugins/Installation/lang/it.json" => array("14808", "b327e0c2aa1ead1071b3aebe29375df1"), + "plugins/Installation/lang/ja.json" => array("18301", "771d8a69c2a629c606d7e7b5ddc49f1a"), + "plugins/Installation/lang/ka.json" => array("15842", "fc71f3330731b70f4cdfecc8c53d95c5"), + "plugins/Installation/lang/ko.json" => array("15717", "cd344b0b42d50aff1a2cf66b3f7c9d62"), + "plugins/Installation/lang/lt.json" => array("8884", "271c41b01d58775c4cb66b5d45cbefb1"), + "plugins/Installation/lang/lv.json" => array("3893", "78346c52194a82702e71b4db3157a0ab"), + "plugins/Installation/lang/nb.json" => array("13910", "3661b6e16d31142b8230bfb064422bf4"), + "plugins/Installation/lang/nl.json" => array("14265", "20e0d8ce21dba8871d65d96f611fbcc3"), + "plugins/Installation/lang/nn.json" => array("6296", "689dbf9ce6293f73c0db1f49a22f9a39"), + "plugins/Installation/lang/pl.json" => array("10652", "ac06ca063057b8de4d0dbdef25947664"), + "plugins/Installation/lang/pt-br.json" => array("14887", "294e22675dc5cf90f2e5083985bf5b61"), + "plugins/Installation/lang/pt.json" => array("8128", "f0954d223b9cab04a56ab23cb19b4c86"), + "plugins/Installation/lang/ro.json" => array("12819", "0042a1dcd3e624ed5bd0d23b1af902a6"), + "plugins/Installation/lang/ru.json" => array("20383", "0d8e165cc9621e348a69af43c5dd7fc6"), + "plugins/Installation/lang/sk.json" => array("5477", "a29ac07089dcb2d53855fd07f5635099"), + "plugins/Installation/lang/sl.json" => array("1427", "ff0311c82246af47fa3fc069e3d4b912"), + "plugins/Installation/lang/sq.json" => array("8479", "85f17f5c0a496b562c9f0e6ca273adcc"), + "plugins/Installation/lang/sr.json" => array("14232", "cde224bf1f576c5e42262ab4331c3b8a"), + "plugins/Installation/lang/sv.json" => array("13886", "ba81de0e86e77278afd905069815f172"), + "plugins/Installation/lang/ta.json" => array("6322", "59bd0a6af4f6bb39a03edb3adeefdde0"), + "plugins/Installation/lang/te.json" => array("634", "3c2a74c59e80cf5f41bb46478533727e"), + "plugins/Installation/lang/th.json" => array("14474", "8ef1f23088d0b5bcc150957f84fefce9"), + "plugins/Installation/lang/tl.json" => array("13140", "15a9a93b7659b36ae796ef21737aaa9a"), + "plugins/Installation/lang/tr.json" => array("3836", "6914d786a5a525fd40cee51ed54d5bd4"), + "plugins/Installation/lang/uk.json" => array("11093", "dfa752604f2f82f24b9b1252c5217219"), + "plugins/Installation/lang/vi.json" => array("12837", "b051e80853e390877b89511d815f9beb"), + "plugins/Installation/lang/zh-cn.json" => array("12711", "9c394cfd87c06e5e6d67c24bfa092950"), + "plugins/Installation/lang/zh-tw.json" => array("6086", "2623795a3784f6df933fb40bbd5baeb2"), + "plugins/Installation/Menu.php" => array("594", "9041123f60ee642e71622c992842b7e7"), + "plugins/Installation/ServerFilesGenerator.php" => array("9054", "b792c7ad43197f880ee2a8e844895cec"), + "plugins/Installation/stylesheets/installation.css" => array("1789", "e4fef5af446b6793668e66669308d603"), + "plugins/Installation/stylesheets/systemCheckPage.less" => array("499", "ede138b18bc9f13d5e8e7632ce12d19b"), + "plugins/Installation/templates/cannotConnectToDb.twig" => array("243", "61a077ac42009bc96b54efdf39588a02"), + "plugins/Installation/templates/databaseSetup.twig" => array("440", "bfd02cb2898320b3ce4f5a9ad69cd878"), + "plugins/Installation/templates/finished.twig" => array("1773", "67a5f71a6838d28f87c0000056197d99"), + "plugins/Installation/templates/firstWebsiteSetup.twig" => array("803", "a25759307147ba384e21ab08abf238c2"), + "plugins/Installation/templates/_integrityDetails.twig" => array("1046", "a4469bea7975373a3d7e301cd06d2015"), + "plugins/Installation/templates/layout.twig" => array("4444", "5376036fa64490fbcf2a519654a710c7"), + "plugins/Installation/templates/reuseTables.twig" => array("3096", "15edbfd05d7434b42531cc53fc0ac589"), + "plugins/Installation/templates/setupSuperUser.twig" => array("442", "ad5735327a9faa341f0a52d299effc08"), + "plugins/Installation/templates/_systemCheckLegend.twig" => array("587", "8d9765ec34df69af628bf1bbc320b4b5"), + "plugins/Installation/templates/systemCheckPage.twig" => array("951", "41b103d606d6809b18e8452c87d4efdb"), + "plugins/Installation/templates/_systemCheckSection.twig" => array("1759", "1ba0eef3e2f542337a5f75975c9aba0f"), + "plugins/Installation/templates/systemCheck.twig" => array("1046", "0d955f5974abc2b89aa7cefbeffd13c6"), + "plugins/Installation/templates/tablesCreation.twig" => array("1949", "da23e832feec7b2d9ca9aea9c6abaefb"), + "plugins/Installation/templates/trackingCode.twig" => array("740", "e4d7784d71ee4e0a3222a0899b1d8d4b"), + "plugins/Installation/templates/welcome.twig" => array("1000", "bd5e4f10d1213a96f92a93cf0d3cee64"), + "plugins/Installation/View.php" => array("1594", "fec00004367c77db0bec62d25d2d46bb"), + "plugins/Intl/Commands/GenerateIntl.php" => array("21836", "fd831ce25fc3d97f4a1b39d6fbc95af6"), + "plugins/Intl/config/config.php" => array("135", "a9d64043bb991c45ef9b4b16d18ff639"), + "plugins/Intl/DateTimeFormatProvider.php" => array("3691", "2b16c4a5fd54d1215e12456e178d2bf6"), + "plugins/Intl/Intl.php" => array("225", "40df032029a8e759da56ddbe5196c5d6"), + "plugins/Intl/lang/am.json" => array("24045", "590fedd43b0d89faeadba060de76e290"), + "plugins/Intl/lang/ar.json" => array("25705", "1a3ff85778970270973639fb8eb35b09"), + "plugins/Intl/lang/be.json" => array("24344", "aa6f7e344447b8c110c8aa848629bc97"), + "plugins/Intl/lang/bg.json" => array("25417", "79ce028da911b65e6896b20aec10f80c"), + "plugins/Intl/lang/bn.json" => array("30776", "ee2d5ae15ab436b01ff5307f714cad47"), + "plugins/Intl/lang/bs.json" => array("21215", "279f142a55e5ed9c9e1a8b82b26dc373"), + "plugins/Intl/lang/ca.json" => array("21100", "65ec00eeac00c2da68c4b5f4550bfc0a"), + "plugins/Intl/lang/cs.json" => array("21897", "e7635480b4e937d982cac42b7a7ade86"), + "plugins/Intl/lang/cy.json" => array("20933", "973eb4cc61967ae863dd7e9a7dff823f"), + "plugins/Intl/lang/da.json" => array("20957", "90f7676d4364c9878bfebfa36d3898f7"), + "plugins/Intl/lang/de.json" => array("21429", "5366dc9b518eb82b573f9014b93b96f6"), + "plugins/Intl/lang/dev.json" => array("104", "2cb82b1b19a40b37ecf0ecbb22fc5fc0"), + "plugins/Intl/lang/el.json" => array("26095", "0564f92f44d355c4cb0ed7eb804507fb"), + "plugins/Intl/lang/en.json" => array("20840", "6524428d14c4e905ef073ce7ff1cbb84"), + "plugins/Intl/lang/es.json" => array("21145", "6c366cb25521d9834bee9d42a30a6628"), + "plugins/Intl/lang/et.json" => array("20875", "f66696184f4f279ed318b4abfc14a44b"), + "plugins/Intl/lang/eu.json" => array("20439", "2067fd1637591579ee0afaace3b367bf"), + "plugins/Intl/lang/fa.json" => array("24533", "2473fef2157235da833ccb219e12e427"), + "plugins/Intl/lang/fi.json" => array("21081", "2f6b5c22d86c23eaa0419f1bf445f934"), + "plugins/Intl/lang/fr.json" => array("21246", "59bcf97e69be0c6af577b67da94ee2f9"), + "plugins/Intl/lang/gl.json" => array("19899", "dea93859a3dfc291416eb6319edba479"), + "plugins/Intl/lang/he.json" => array("24287", "712abf9af17f92a5479d5f817df968d1"), + "plugins/Intl/lang/hi.json" => array("29364", "51d4da364f210f91a6078f6979477710"), + "plugins/Intl/lang/hr.json" => array("21251", "a5a7d3c2d748dcbb3e16262adce5401b"), + "plugins/Intl/lang/hu.json" => array("21260", "c7318f7b64ada249be4fa4b493eae415"), + "plugins/Intl/lang/id.json" => array("20763", "83bc4d7f0fa5043e61ea98192508b7f3"), + "plugins/Intl/lang/is.json" => array("21615", "a362e3573d6b3079fce1711af4e00168"), + "plugins/Intl/lang/it.json" => array("20994", "9216570419deb065f29f30b93ba9cbeb"), + "plugins/Intl/lang/ja.json" => array("24221", "0175c86045848e159abc1054ffd491b7"), + "plugins/Intl/lang/ka.json" => array("29659", "81175b8a13cf72f49f173cb549b49dc2"), + "plugins/Intl/lang/ko.json" => array("22888", "7c92fbece129607021c7c14fd4976801"), + "plugins/Intl/lang/lt.json" => array("21686", "de036770d0f088ba39bba9accc162fd3"), + "plugins/Intl/lang/lv.json" => array("21509", "6a198d5a1713d506c1997df4c5657a68"), + "plugins/Intl/lang/nb.json" => array("20907", "43a43b70d779da76015ae4f728334fbf"), + "plugins/Intl/lang/nl.json" => array("21096", "3e4e804da517c1863794a4948a7dddc2"), + "plugins/Intl/lang/nn.json" => array("20746", "c2dea67688b157bb98d49d27d63a87ba"), + "plugins/Intl/lang/pl.json" => array("21338", "013c59397d949c9a82ed2a2117789e5c"), + "plugins/Intl/lang/pt-br.json" => array("21342", "9aa5e60c0c21eca7c5836705aa077153"), + "plugins/Intl/lang/pt.json" => array("21331", "c07578c1e43eccd45af9b681a7e05b22"), + "plugins/Intl/lang/ro.json" => array("21283", "f1c0900d7f126f5545b59bb117e4ab28"), + "plugins/Intl/lang/ru.json" => array("25669", "e1a41f7597c0b5e21e8f861f9f4e7be5"), + "plugins/Intl/lang/sk.json" => array("21703", "a91d95af1afe150953b2adadbc8e2c78"), + "plugins/Intl/lang/sl.json" => array("21935", "30ccfebc3cc90e34994cfe7e37515471"), + "plugins/Intl/lang/sq.json" => array("19736", "a4015aa9a61f593915c7c00e330a69c7"), + "plugins/Intl/lang/sr.json" => array("25383", "503baea030beba25cfb3cdb35c8c1f0d"), + "plugins/Intl/lang/sv.json" => array("21048", "c682caa54de6bd93dea7caed9ff29dc9"), + "plugins/Intl/lang/ta.json" => array("30749", "67b62e8afd7bb5c8c1f1a6234d9def89"), + "plugins/Intl/lang/te.json" => array("30394", "8e7372b7084f6809d9c2daf460bede10"), + "plugins/Intl/lang/th.json" => array("29696", "2bb6a1d46c84305bfe7698d068132396"), + "plugins/Intl/lang/tl.json" => array("19916", "735942eca7e0b0f537ac8c42a67f0f88"), + "plugins/Intl/lang/tr.json" => array("21136", "af91ac5e643b6bf62098a57c943042db"), + "plugins/Intl/lang/uk.json" => array("25849", "aa69153b79f00558e8acb9468ded1e69"), + "plugins/Intl/lang/vi.json" => array("22853", "dac554241b03aa259220f05e41e7b691"), + "plugins/Intl/lang/zh-cn.json" => array("22357", "2ebd754b86c69fdd3233eca5b9c7c99d"), + "plugins/Intl/lang/zh-tw.json" => array("22248", "dde8e5e16f4b588dad50c07c53983a91"), + "plugins/LanguagesManager/angularjs/languageselector/languageselector.directive.js" => array("962", "7f7472fe71b389a95f098b48ec420444"), + "plugins/LanguagesManager/angularjs/translationsearch/translationsearch.controller.js" => array("862", "74e304627a4f86efd4b1e97a8a318408"), + "plugins/LanguagesManager/angularjs/translationsearch/translationsearch.directive.html" => array("1237", "8b1be14386abe55912df2ca3517fcc9c"), + "plugins/LanguagesManager/angularjs/translationsearch/translationsearch.directive.js" => array("885", "b71bf8fbf51de16b778bf0aeac27ae08"), + "plugins/LanguagesManager/API.php" => array("11816", "02e370df43dd47be89d599c0559bf5c8"), + "plugins/LanguagesManager/Commands/CreatePull.php" => array("8074", "18087b6f9a8ef2b25afc81264c7279d2"), + "plugins/LanguagesManager/Commands/FetchTranslations.php" => array("4641", "103268769c159f8136984d6b1e4f303f"), + "plugins/LanguagesManager/Commands/LanguageCodes.php" => array("1035", "9e57c16c0f05523a6b14bbb30aadbad5"), + "plugins/LanguagesManager/Commands/LanguageNames.php" => array("1043", "d2869bebc1206c6877bd8e695004a97f"), + "plugins/LanguagesManager/Commands/PluginsWithTranslations.php" => array("1064", "62d56f90d37fbb088151c86f10533f56"), + "plugins/LanguagesManager/Commands/SetTranslations.php" => array("4310", "d9c97dee65cfe60bba79523af64e7f2e"), + "plugins/LanguagesManager/Commands/TranslationBase.php" => array("625", "aafe53000c9c88067feb7c2a9c4d8703"), + "plugins/LanguagesManager/Commands/Update.php" => array("7333", "abf27d7bda27545df17f75350fc4343c"), + "plugins/LanguagesManager/Controller.php" => array("997", "1eaf6c276ecb4dae91a9b9f5ea58a56d"), + "plugins/LanguagesManager/lang/ar.json" => array("95", "d451c8e3cfdc18161543fa0ad27a1a0a"), + "plugins/LanguagesManager/lang/be.json" => array("103", "a75e345e2ccace4ab91dbde41f1593dc"), + "plugins/LanguagesManager/lang/bg.json" => array("101", "c62d43add3454b943f71e8a9a0aabe2f"), + "plugins/LanguagesManager/lang/ca.json" => array("155", "b1576e347f86852710716b06c31f5b5c"), + "plugins/LanguagesManager/lang/cs.json" => array("153", "fd57d6b9f02fe27abb44e8404955a373"), + "plugins/LanguagesManager/lang/da.json" => array("150", "57c28ba890e7d9a073c18413a3b002b9"), + "plugins/LanguagesManager/lang/de.json" => array("149", "eb4fc57c297b0f054299d371bc7a8ba8"), + "plugins/LanguagesManager/lang/el.json" => array("166", "04b3f99d77e66834716da678f23fb7cc"), + "plugins/LanguagesManager/lang/en.json" => array("147", "d30a36d32452bb7bc4ce2b664e65ac9f"), + "plugins/LanguagesManager/lang/es.json" => array("158", "f7cf544127a5edcfe456a691e644d177"), + "plugins/LanguagesManager/lang/et.json" => array("104", "8eeb061c5c2366e8fcc24495aceeb7a9"), + "plugins/LanguagesManager/lang/fa.json" => array("169", "531580350b22ec8f64cc1742a0262dd3"), + "plugins/LanguagesManager/lang/fi.json" => array("155", "23ce7e6f8fa07fe1416903bc978aaf5d"), + "plugins/LanguagesManager/lang/fr.json" => array("162", "0d70bb674e7faaa63534f2ab8579da06"), + "plugins/LanguagesManager/lang/hi.json" => array("193", "1a9b6009fcc08fc40d367a61e20d0502"), + "plugins/LanguagesManager/lang/hu.json" => array("96", "9148652673e8306ebcb6c79385e9e4d3"), + "plugins/LanguagesManager/lang/id.json" => array("98", "c9355c9899ab7cdb78dceb19a9fca475"), + "plugins/LanguagesManager/lang/is.json" => array("92", "c986412e7392641911476323cd371ca3"), + "plugins/LanguagesManager/lang/it.json" => array("156", "158145cd7ef8450ad8fc020a45cb27b3"), + "plugins/LanguagesManager/lang/ja.json" => array("147", "b6e8e5e8fea0606eefd66472c0de649b"), + "plugins/LanguagesManager/lang/ka.json" => array("133", "98f154322e14ddf4b61fe9e43295d3f6"), + "plugins/LanguagesManager/lang/ko.json" => array("94", "1dee0151e488d568c80f5ee257d2f81f"), + "plugins/LanguagesManager/lang/lt.json" => array("91", "c6104a589e5cd028845a1ab204827755"), + "plugins/LanguagesManager/lang/lv.json" => array("93", "73451316fbde065ce13f2a5b612c46a4"), + "plugins/LanguagesManager/lang/nb.json" => array("144", "3b03197f5c013e2693afa85e6a8ff334"), + "plugins/LanguagesManager/lang/nl.json" => array("146", "22f867c4a91520aca448a952dff41ae1"), + "plugins/LanguagesManager/lang/nn.json" => array("93", "8d9ce397e087a5795ab36c76848b76bd"), + "plugins/LanguagesManager/lang/pl.json" => array("94", "37ee6e37fc9479efa0cc38b68780d06d"), + "plugins/LanguagesManager/lang/pt-br.json" => array("154", "3508c4b41fcd678f29ada9315e81501c"), + "plugins/LanguagesManager/lang/pt.json" => array("100", "c208f754263035bdd24c7b8bbdb4dc0f"), + "plugins/LanguagesManager/lang/ro.json" => array("94", "e697dc8d31676be8c0d4d3faeb663c74"), + "plugins/LanguagesManager/lang/ru.json" => array("159", "237b6bc2cff743d4d10d561a59c19daf"), + "plugins/LanguagesManager/lang/sk.json" => array("90", "a02d9ab7913a0e6e018d846fbba65bdf"), + "plugins/LanguagesManager/lang/sl.json" => array("90", "a6a219e8fca7e962cd4d72a6e4943cfe"), + "plugins/LanguagesManager/lang/sq.json" => array("98", "bb1efe9a1e8fc6b06f55d3ccd088dda1"), + "plugins/LanguagesManager/lang/sr.json" => array("144", "a54eb6a168d27601d73952d30391d5cc"), + "plugins/LanguagesManager/lang/sv.json" => array("151", "3c55c97be39377040b1a5fedc3af1fff"), + "plugins/LanguagesManager/lang/te.json" => array("137", "b0f4b5f2deb4978febecf24aa35390c2"), + "plugins/LanguagesManager/lang/th.json" => array("123", "7ed1edec4e9e0662835022d7f450353c"), + "plugins/LanguagesManager/lang/tl.json" => array("160", "912f25b804e5ee258713868519a69ef0"), + "plugins/LanguagesManager/lang/tr.json" => array("99", "20423eb6e47c733f5a5c51cb5851a21e"), + "plugins/LanguagesManager/LanguagesManager.php" => array("7598", "dfb0713f8349e68c022a6dba75260802"), + "plugins/LanguagesManager/lang/uk.json" => array("103", "c75236d212bc8809eaa7d89bd00052d9"), + "plugins/LanguagesManager/lang/vi.json" => array("89", "2d44d4a128615e9c70a2983f7949e020"), + "plugins/LanguagesManager/lang/zh-cn.json" => array("136", "eec86386290e9e9c803d7082f42e3409"), + "plugins/LanguagesManager/lang/zh-tw.json" => array("91", "873fdc5f907dfb9100011d12980d8e0d"), + "plugins/LanguagesManager/Menu.php" => array("987", "61ddcf8de6077c8725e75e4671f8bf76"), + "plugins/LanguagesManager/Model.php" => array("2691", "8c1dfd904f6d14142cde117182739516"), + "plugins/LanguagesManager/templates/getLanguagesSelector.twig" => array("1016", "b312c51dd9f3152595bdf71c207a38b6"), + "plugins/LanguagesManager/templates/searchTranslation.twig" => array("237", "f4b3f349b460251933c9d8637167abd0"), + "plugins/LanguagesManager/Test/Integration/LanguagesManagerTest.php" => array("6669", "a9926ae4b53b6314690a7558b609294f"), + "plugins/LanguagesManager/Test/Integration/ModelTest.php" => array("3607", "aa6bd57ee646745bbe29669a336d3946"), + "plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/ByBaseTranslationsTest.php" => array("4581", "d2d5d26fe60f3879944f68ddd2caf1ee"), + "plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/ByParameterCountTest.php" => array("3526", "6a477f568286d4c0e259b69ef987faa2"), + "plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/EmptyTranslationsTest.php" => array("2683", "23f10cea3196c7d65bf231938c63261e"), + "plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/EncodedEntitiesTest.php" => array("3164", "3cf764ed8f0b6258df72a0a9fb234611"), + "plugins/LanguagesManager/Test/Unit/TranslationWriter/Filter/UnnecassaryWhitespacesTest.php" => array("4634", "f7fc3b6ae6e4850c9617dda02a038bcd"), + "plugins/LanguagesManager/Test/Unit/TranslationWriter/Validate/CoreTranslationsTest.php" => array("3092", "3339a3e50305dbb54c99aea7e55adec6"), + "plugins/LanguagesManager/Test/Unit/TranslationWriter/Validate/NoScriptsTest.php" => array("2895", "0c507a2f50543e5ea3bb60f132c45301"), + "plugins/LanguagesManager/Test/Unit/TranslationWriter/WriterTest.php" => array("8150", "374b3122cd48b6efe846fc8ea1febb7e"), + "plugins/LanguagesManager/TranslationWriter/Filter/ByBaseTranslations.php" => array("1739", "0b905abf6386440f5b1de2c0d7617577"), + "plugins/LanguagesManager/TranslationWriter/Filter/ByParameterCount.php" => array("2478", "3923cea8c0afabc65d1d555201b9387d"), + "plugins/LanguagesManager/TranslationWriter/Filter/EmptyTranslations.php" => array("1168", "772a04a5fa008bfa09c74b580187cdf5"), + "plugins/LanguagesManager/TranslationWriter/Filter/EncodedEntities.php" => array("1056", "61a26c5797201da70a1bfdeeaaa3a534"), + "plugins/LanguagesManager/TranslationWriter/Filter/FilterAbstract.php" => array("680", "7545c15270395e6b623fcac9d98927c4"), + "plugins/LanguagesManager/TranslationWriter/Filter/UnnecassaryWhitespaces.php" => array("2436", "545a37d98e91cdcd06e296f2347f9b53"), + "plugins/LanguagesManager/TranslationWriter/Validate/CoreTranslations.php" => array("2837", "ba9b6f9df2b8ec8ebcaafc6dac0a88ed"), + "plugins/LanguagesManager/TranslationWriter/Validate/NoScripts.php" => array("1054", "d8ec39c2d4c28dfeacb467d2fcc41323"), + "plugins/LanguagesManager/TranslationWriter/Validate/ValidateAbstract.php" => array("714", "9929224b721d01e578e9bfac137aa72e"), + "plugins/LanguagesManager/TranslationWriter/Writer.php" => array("9648", "5b8b237c2a98c1be73d490b91ad72032"), + "plugins/LanguagesManager/Updates/2.15.1-b1.php" => array("771", "deafe44968c481ad4b921efb725bf3c8"), + "plugins/Live/API.php" => array("17166", "8505d2068aa82b39f08cd91c505a82e3"), + "plugins/Live/Controller.php" => array("8867", "c615d75d7d42a34187365f6d4d381802"), "plugins/Live/images/avatar_frame.png" => array("5375", "b9a974286bc6de507e88ffd8ad40a8dd"), "plugins/Live/images/file0.png" => array("593", "d3bd9b53340629a9ba00ff9d3c295995"), "plugins/Live/images/file1.png" => array("637", "c1844ec7c1a8acf7573a54f78bf3565a"), @@ -1530,134 +3664,425 @@ class Manifest { "plugins/Live/images/file8.png" => array("642", "c164fa32f73b6f67012b0fde60d0e756"), "plugins/Live/images/file9.png" => array("569", "0551f3605e761068b1b1169c188cb45c"), "plugins/Live/images/paperclip.png" => array("10924", "aa65189568a2cb4c9d08af6f46926ae0"), - "plugins/Live/images/pause_disabled.gif" => array("619", "5c68e96a0ce8eac7b75fff5735dc7a3e"), - "plugins/Live/images/pause.gif" => array("669", "534b3892f5bda663651f86356df69a8d"), - "plugins/Live/images/play_disabled.gif" => array("407", "3c1374c5bfc73f12d50a97e5017a120f"), - "plugins/Live/images/play.gif" => array("666", "10ab3e64171780613434062a50378714"), + "plugins/Live/images/pause.gif" => array("1142", "defa62045c5e21609cc6b36de1d299ef"), + "plugins/Live/images/play.gif" => array("1184", "8db25ba7dc1701ed2c93a483fa25ef88"), "plugins/Live/images/returningVisitor.gif" => array("995", "dbdd14d7d5528f2c74ec8508c99aacfc"), "plugins/Live/images/unknown_avatar.jpg" => array("13984", "8151db2b0f9b45ba92f9b81c2791df94"), + "plugins/Live/images/visitorlog-hover.png" => array("1275", "f9f2f1195e80785df8338375864194ee"), + "plugins/Live/images/visitorlog.png" => array("1273", "8b07aad24ebeac1745b75e51ac439816"), "plugins/Live/images/visitor_profile_background.jpg" => array("11060", "b7eaca3d0097fe8f27d6625214092fd5"), "plugins/Live/images/visitor_profile_close.png" => array("4734", "e12231490573aa1f37d7eed6eb6867f0"), "plugins/Live/images/visitor_profile_gradient.png" => array("2840", "377a58f8be3b30690845aa71b0db0a5b"), "plugins/Live/images/visitorProfileLaunch.png" => array("661", "60ac00d9db615450c8d6090521f30330"), - "plugins/Live/javascripts/live.js" => array("8926", "7208e2076c000a03a5f9ada452d8c428"), - "plugins/Live/javascripts/visitorLog.js" => array("3150", "5d8488563053c8dd0248bdbba49aa59d"), - "plugins/Live/javascripts/visitorProfile.js" => array("10635", "6d6476652f681ef87d8eda4eecffb2e1"), - "plugins/Live/Live.php" => array("2559", "3b4cbcdd287bc9bdc96c5927a15a2a4f"), - "plugins/Live/stylesheets/live.less" => array("3761", "78a7b21ca37b37518c66eda4d07c18d7"), - "plugins/Live/stylesheets/visitor_profile.less" => array("9904", "86b092ab364a0e298e8edbfa0c6418c1"), - "plugins/Live/templates/_actionsList.twig" => array("7713", "d8c003ff4ef3b36a2f9ce6426faa09ed"), + "plugins/Live/javascripts/live.js" => array("9685", "ae040ce68a963d67c22bdb022a381e6d"), + "plugins/Live/javascripts/rowaction.js" => array("5251", "18b7c3e1e4c8c8d6166a3dae4bfa5147"), + "plugins/Live/javascripts/SegmentedVisitorLog.js" => array("4364", "75797463fd51a0d6f2a5c182cd1f0d55"), + "plugins/Live/javascripts/visitorLog.js" => array("4516", "f2cf319cb244db4cf416a165a03d07bb"), + "plugins/Live/javascripts/visitorProfile.js" => array("11418", "3a5ad6e1a4ad9229f350158c3baef574"), + "plugins/Live/lang/ar.json" => array("569", "33d6bec3b301388b39c65055496ede3c"), + "plugins/Live/lang/be.json" => array("1297", "e29628a762fdf9065dc3c98ae0a14436"), + "plugins/Live/lang/bg.json" => array("3138", "ef6e4499138b0e63a0d461c02de1ebbb"), + "plugins/Live/lang/ca.json" => array("1185", "d378d5955d92ec24ceb10fae6a10bf43"), + "plugins/Live/lang/cs.json" => array("3770", "210c04c781072c2458ec088e75874358"), + "plugins/Live/lang/da.json" => array("2408", "b017b817d3ceb28640dba2302cda16ea"), + "plugins/Live/lang/de.json" => array("3229", "fe3abf055e0d6b321232b162e45c93be"), + "plugins/Live/lang/el.json" => array("5919", "3ab40c53af951838ffe8a9e29a95b0a8"), + "plugins/Live/lang/en.json" => array("3263", "723cdbc5a5f3b2a0f7c12494ac24dfbd"), + "plugins/Live/lang/es.json" => array("3156", "e8e401f96fe3171ee92a59b6294a75d2"), + "plugins/Live/lang/et.json" => array("1742", "d7384f08389dbb596c16c6825d9899ad"), + "plugins/Live/lang/eu.json" => array("109", "2cb8dfb58b06975938fbb187ba594882"), + "plugins/Live/lang/fa.json" => array("2326", "4cb5db545c75892c0ca13b48e58f8a07"), + "plugins/Live/lang/fi.json" => array("2298", "5b38b29a235e82485d88e137442dfbcd"), + "plugins/Live/lang/fr.json" => array("3128", "0322aac0ad6a41e4385cb08123d4b4f7"), + "plugins/Live/lang/hi.json" => array("3590", "21e6d5385f0f760e20139ea4e8df8e86"), + "plugins/Live/lang/hr.json" => array("493", "8152d4c4d1c74f82c81774172e7cead3"), + "plugins/Live/lang/hu.json" => array("894", "84ad5e99775b994b3b65206d822887ad"), + "plugins/Live/lang/id.json" => array("1333", "d86685d376e44ed3701dc4d68c1853b0"), + "plugins/Live/lang/is.json" => array("352", "59989477809f2b87111ec56503dbb508"), + "plugins/Live/lang/it.json" => array("3150", "dc9e2ea6d750666312ea3efada602cb3"), + "plugins/Live/lang/ja.json" => array("3493", "799db3f13b04d5f5cfa4c95a516d53b2"), + "plugins/Live/lang/ka.json" => array("540", "adffeff87a50fa59d98614291ccadf76"), + "plugins/Live/lang/ko.json" => array("2393", "d60af1be1ca8e0162c470b949bef3e6b"), + "plugins/Live/lang/lt.json" => array("1136", "ee6a9c7a3bed5247c4de886650f0204d"), + "plugins/Live/lang/lv.json" => array("1038", "6c04429237d59404a53e63e25ae0ff6e"), + "plugins/Live/lang/nb.json" => array("1223", "b86d5a50e6f8cf4746b6eeab456a19af"), + "plugins/Live/lang/nl.json" => array("2909", "2dad4fa08871162b8d82f03773d5a449"), + "plugins/Live/lang/nn.json" => array("872", "cd83e7fc98c69c0b9e4ad6dc437de3e0"), + "plugins/Live/lang/pl.json" => array("1410", "186884661b17e7e24d0d93a9a8b08355"), + "plugins/Live/lang/pt-br.json" => array("3554", "40023def46f3697137d1ba88bcc2461f"), + "plugins/Live/lang/pt.json" => array("1057", "4a8a3501508503408ec31830d949a8ac"), + "plugins/Live/lang/ro.json" => array("2381", "08f85d0ab54ab7cc78d8e1ec3a3c9706"), + "plugins/Live/lang/ru.json" => array("4309", "d03a41308c2c465f593569282df0ef60"), + "plugins/Live/lang/sk.json" => array("973", "b6ca67aac48a4cb57551fa4b6e9926a4"), + "plugins/Live/lang/sl.json" => array("460", "061b3a57bead908537e04d024ab9ceb4"), + "plugins/Live/lang/sq.json" => array("1175", "fa4962f34416a2afc5f7c6943b2f6701"), + "plugins/Live/lang/sr.json" => array("2869", "a29517d8f640873aa50580bffccb3944"), + "plugins/Live/lang/sv.json" => array("2219", "0fcd0e85b0df98aa2487dd6bad11c28d"), + "plugins/Live/lang/ta.json" => array("396", "3c9deb2a01718415b2168ce962226f18"), + "plugins/Live/lang/te.json" => array("238", "0eb3e27379f674dc79c6eca04cd584a4"), + "plugins/Live/lang/th.json" => array("1369", "060213c8e1321af5bd13c642ccbd5a79"), + "plugins/Live/lang/tl.json" => array("2387", "67ed057885c7bfb1a5d0b31db9edc1fb"), + "plugins/Live/lang/tr.json" => array("1008", "ae7bbc7109cacdd99efd32edad1d7212"), + "plugins/Live/lang/uk.json" => array("511", "2c92888d89e1f0e364277365c4fd7e66"), + "plugins/Live/lang/vi.json" => array("2822", "f33caf394b306f163f744d2752d874b3"), + "plugins/Live/lang/zh-cn.json" => array("2018", "25de63cbce031e5d1c04fa1363f8a082"), + "plugins/Live/lang/zh-tw.json" => array("378", "fa089ec022960ba191188cb36e532663"), + "plugins/Live/Live.php" => array("2000", "8108ca577715e2c568227d3fe37a4088"), + "plugins/Live/Model.php" => array("17234", "34bb6b1c51d3e20e9199a922c9b82255"), + "plugins/Live/Reports/Base.php" => array("410", "1e3a67ccfb8ec5fe8fa1f670c93a9267"), + "plugins/Live/Reports/GetLastVisitsDetails.php" => array("1190", "2d35d705b382d0496685a3a58c4370d8"), + "plugins/Live/Reports/GetLastVisits.php" => array("439", "6bfa5fc7d51c2c4d077e1716869070b8"), + "plugins/Live/Reports/GetSimpleLastVisitCount.php" => array("2031", "975da117da4924c1927ec4d2ebee5106"), + "plugins/Live/stylesheets/live.less" => array("5385", "e662ead0322ae9b28788cbffe9110bcb"), + "plugins/Live/stylesheets/visitor_profile.less" => array("9987", "22256e62ec7d6624f29e9f6ba508d98f"), + "plugins/Live/templates/_actionsList.twig" => array("7482", "6c0bbd28a10a36c6635f1f8809b17014"), "plugins/Live/templates/ajaxTotalVisitors.twig" => array("41", "5c57d90d62eab1c2c11969f14ff6faaa"), - "plugins/Live/templates/_dataTableViz_visitorLog.twig" => array("11165", "f10f764079fc95b18bee9964e505bbd5"), - "plugins/Live/templates/getLastVisitsStart.twig" => array("9637", "66723cfc37872398f74bcebc0170fa3a"), - "plugins/Live/templates/getSimpleLastVisitCount.twig" => array("1501", "ff589f5ab71d3364bbce2b50ac635304"), - "plugins/Live/templates/getSingleVisitSummary.twig" => array("3644", "a6d27ef3106ba799ee77ab6c7e52901b"), - "plugins/Live/templates/getVisitList.twig" => array("1401", "21f5605f7c91c89b27ec184d71e89a34"), - "plugins/Live/templates/getVisitorProfilePopup.twig" => array("10894", "ae422171a4dc85bae4c491b8530a04c7"), - "plugins/Live/templates/index.twig" => array("1728", "6de08f5ddb5d6802ab97258cafbf7d13"), - "plugins/Live/templates/indexVisitorLog.twig" => array("170", "8e65a17376e8d870da1fdb791bdb019b"), - "plugins/Live/templates/_totalVisitors.twig" => array("1193", "cb5f29dc2c34e531f7e8469feb201a32"), - "plugins/Live/VisitorLog.php" => array("4032", "95e464cd76d6842f656e0b4f7bb5e9d8"), - "plugins/Live/Visitor.php" => array("35950", "df9ec109f4a08c6bc5b7c33a1b544f73"), - "plugins/Login/Auth.php" => array("3935", "449818805ed2d144bfcd22d4e881318f"), - "plugins/Login/Controller.php" => array("14593", "3ad0b3aefe6b7aec78622079aa965691"), - "plugins/Login/FormLogin.php" => array("1283", "97995c1ab8eb36c1c4a899f5f87a4e23"), - "plugins/Login/FormResetPassword.php" => array("1247", "13d7b72379ff19943051061fa0e17f4b"), - "plugins/Login/javascripts/login.js" => array("3457", "ddf60c9aab3769010b4e022369337dfb"), - "plugins/Login/Login.php" => array("4566", "48ff16be33aab8f87ad9fa99c615ab71"), - "plugins/Login/stylesheets/login.css" => array("3564", "df89b474360b0e1c59f306df7c76255c"), - "plugins/Login/templates/login.twig" => array("7493", "7c6eebc0b8825c41214dba7647238660"), - "plugins/Login/templates/resetPassword.twig" => array("389", "751937bee878c24f3b72890273548069"), - "plugins/MobileMessaging/APIException.php" => array("261", "9f8b71ac2a11ea82638ff785a1368ecc"), - "plugins/MobileMessaging/API.php" => array("12198", "36de01e312125b59016d8f2f2ad3aa58"), - "plugins/MobileMessaging/Controller.php" => array("2675", "9b26ad0184726bc42e630ebe95b2a05d"), - "plugins/MobileMessaging/CountryCallingCodes.php" => array("7207", "0f3e6aa1f434d4c93ed4dc899ec751bf"), - "plugins/MobileMessaging/GSMCharset.php" => array("2978", "1a7692e6ddd18ff57f146e9c064db4c9"), + "plugins/Live/templates/_dataTableViz_visitorLog.twig" => array("13050", "05c2ddc33f4e1d4dfc7483b0566f2940"), + "plugins/Live/templates/getLastVisitsStart.twig" => array("10539", "ebcdd335c6d201ee40d1063929bb08c6"), + "plugins/Live/templates/getSimpleLastVisitCount.twig" => array("1495", "ab295fb6b5505873cedc80050c9f8a01"), + "plugins/Live/templates/getSingleVisitSummary.twig" => array("4281", "d967deef5c280b0238e3fafabca3c85b"), + "plugins/Live/templates/getVisitList.twig" => array("1589", "26b12aea63629c905c5d4fb2c960dfd4"), + "plugins/Live/templates/getVisitorProfilePopup.twig" => array("12995", "a06243c07123d7357b8c8b4d2fc38738"), + "plugins/Live/templates/index.twig" => array("1866", "569c925c3a2f521043d412ac6b95f471"), + "plugins/Live/templates/indexVisitorLog.twig" => array("89", "fa30cab277d7c45e669b709dfbee6c1b"), + "plugins/Live/templates/_totalVisitors.twig" => array("1143", "313576d8c6cb398865e8a30fba0e4f3d"), + "plugins/Live/VisitorFactory.php" => array("1605", "0dfa69c67b7655a57034ae68f692b98d"), + "plugins/Live/VisitorInterface.php" => array("379", "3d2df929580b3fbc46622906b33fc02a"), + "plugins/Live/Visitor.php" => array("18427", "d1123d1f7131cf93ca6a7ebeb63c424a"), + "plugins/Live/VisitorProfile.php" => array("13321", "91bc888d940cfbd47b8c99d1d64d2e59"), + "plugins/Live/Visualizations/VisitorLog/Config.php" => array("913", "ea50b1c371907681d7dc5dcb611053a9"), + "plugins/Live/Visualizations/VisitorLog.php" => array("3411", "9946bec2d7f63412d1a19ebcdcd8e208"), + "plugins/Live/Widgets.php" => array("653", "d287e11f5bb2ab6dd68b4f80c62dccc2"), + "plugins/Login/Auth.php" => array("4603", "665827a8c8cc35a47d35a8344fabb7e5"), + "plugins/Login/config/config.php" => array("81", "2fb09ca13d5056999cf6c92428917d13"), + "plugins/Login/Controller.php" => array("11357", "18113cac17b494f86a468fc56961a086"), + "plugins/Login/FormLogin.php" => array("1287", "945355c423596067fe5665ba7d25e9a6"), + "plugins/Login/FormResetPassword.php" => array("1251", "c01d7ab13be7620beff1bf89ae66556c"), + "plugins/Login/javascripts/login.js" => array("3473", "b0ebcc2fc73d258f5bc22935ac9fc9ae"), + "plugins/Login/lang/am.json" => array("688", "dc990a5a6dceaee1a6916b596357e4a8"), + "plugins/Login/lang/ar.json" => array("1517", "7132f3e21def5d8eb3afa15995308df5"), + "plugins/Login/lang/be.json" => array("1581", "b6fe88375fdb18b5c51e8712fd7d4cbd"), + "plugins/Login/lang/bg.json" => array("2215", "45c08fc79a876879ef7f192e138f949e"), + "plugins/Login/lang/ca.json" => array("2177", "5c526cadef18215ad7070cbc8ee8e17b"), + "plugins/Login/lang/cs.json" => array("2386", "e14e52b77fe826e4636e451d2d4abfad"), + "plugins/Login/lang/da.json" => array("2233", "aa61181d19a6e64f2e141101ef04c0bf"), + "plugins/Login/lang/de.json" => array("2263", "64cc841ea3dca745ef7cc33f310f5e86"), + "plugins/Login/lang/el.json" => array("4149", "a333a7c0783c3b759dfadf5e39f10823"), + "plugins/Login/lang/en.json" => array("2272", "f415caf93511301195ba1aa73a7535b7"), + "plugins/Login/lang/es.json" => array("2485", "9eb9648caaef3475f597f0e98df5a436"), + "plugins/Login/lang/et.json" => array("857", "8d2fcbc84518de1bf594495e59946a11"), + "plugins/Login/lang/eu.json" => array("685", "9af1cc42feb24b97f09442b23cbf5079"), + "plugins/Login/lang/fa.json" => array("1595", "3f66aac2025d2a93e1ff822007c00ae9"), + "plugins/Login/lang/fi.json" => array("1997", "b71aadbc519808b4b6c1a486b25ee348"), + "plugins/Login/lang/fr.json" => array("2485", "a0a9012c25a165a24d93cec2fd170f58"), + "plugins/Login/lang/gl.json" => array("484", "14c5384c9e21df1c744f1bf64919f5ed"), + "plugins/Login/lang/he.json" => array("517", "25b2205569f3959b74ce42d1811a6df0"), + "plugins/Login/lang/hi.json" => array("3913", "fb8e938c7020099785fd962dab854408"), + "plugins/Login/lang/hu.json" => array("2441", "8cdd0a76c61ce83cb04e9f2f922d017e"), + "plugins/Login/lang/id.json" => array("1789", "2b3a8dd2e750fedff78ef8db49831e14"), + "plugins/Login/lang/is.json" => array("767", "f57ff54debe9b4e4ea8061093d4da359"), + "plugins/Login/lang/it.json" => array("2360", "4598a1fa4a7f2396eac4ea7b155546d5"), + "plugins/Login/lang/ja.json" => array("2954", "c42b8c8be0e07008ae398bb3f5616a73"), + "plugins/Login/lang/ka.json" => array("1536", "4ec8a81e7b40fde87178d2527fbb01da"), + "plugins/Login/lang/ko.json" => array("2761", "62cd5094760ae0112c2e8e77e23b02c2"), + "plugins/Login/lang/lt.json" => array("824", "38b41f014be5fd71afcc13eea292ee3a"), + "plugins/Login/lang/lv.json" => array("1181", "0ac7e40e8e3f89d1452dd94153593145"), + "plugins/Login/lang/nb.json" => array("2218", "a7c9704dd12e54de7ed6c1719e4afff5"), + "plugins/Login/lang/nl.json" => array("2297", "87baacade0e13b6b8fd79d6d93132920"), + "plugins/Login/lang/nn.json" => array("533", "462b6859a84689b932ba03e670f082f8"), + "plugins/Login/lang/pl.json" => array("1641", "4a4d37093aa64df1c4ea7e04918d66fc"), + "plugins/Login/lang/pt-br.json" => array("2469", "c08431805395c974bab4419ebf28ce6f"), + "plugins/Login/lang/pt.json" => array("1226", "f9f03201e390a7cbb2d0002620f710fe"), + "plugins/Login/lang/ro.json" => array("2031", "decebe1261882ca5ef58c9182a133a83"), + "plugins/Login/lang/ru.json" => array("3253", "0149720430ca393c3197642a7bec4e71"), + "plugins/Login/lang/sk.json" => array("1334", "1dcea116f37de45a829a1136c57b77ce"), + "plugins/Login/lang/sl.json" => array("1444", "adf96181d01d6f1451c026dc4c2ad335"), + "plugins/Login/lang/sq.json" => array("1246", "9133772a3af38aed3cb3f2fbd80aa8b3"), + "plugins/Login/lang/sr.json" => array("2273", "c74aa34eaa4f0285b27f74adbef901a9"), + "plugins/Login/lang/sv.json" => array("2280", "ae372c0175ea16e46e7df05cd196f576"), + "plugins/Login/lang/ta.json" => array("1080", "09e7491313b0ea9d9e2c61bae9cdb02e"), + "plugins/Login/lang/te.json" => array("268", "0bd6a4006b0cbd6cdab7743ffbd76047"), + "plugins/Login/lang/th.json" => array("2597", "552ae9f9588352da976fba2b21519808"), + "plugins/Login/lang/tl.json" => array("1838", "a61d9994b5549a43f7db3b5d83b90bc4"), + "plugins/Login/lang/tr.json" => array("1106", "7dfdf974729ba08614113e8cbf28a78d"), + "plugins/Login/lang/uk.json" => array("1106", "bd722dafba022a44ee4c593ad557e9a3"), + "plugins/Login/lang/vi.json" => array("2331", "72cd53ed9b557036efc89610d6befc11"), + "plugins/Login/lang/zh-cn.json" => array("1493", "f6188121564a95dfa4129f6bc411d25a"), + "plugins/Login/lang/zh-tw.json" => array("792", "5b9759306a9cd11ad0c1a34c95da14fc"), + "plugins/Login/Login.php" => array("3513", "dc1bde3ba24bfc34fafd722054ddce61"), + "plugins/Login/PasswordResetter.php" => array("17285", "fc0f61a9b17b989fbf8b5870ff165cbc"), + "plugins/Login/SessionInitializer.php" => array("7592", "1d1d1341aef4f676fe74659185b78238"), + "plugins/Login/stylesheets/login.less" => array("4122", "f124f0184d37e53ff316ca62e22e17c4"), + "plugins/Login/stylesheets/variables.less" => array("36", "14cf6ec689fd1977297f3001a8635047"), + "plugins/Login/templates/_formErrors.twig" => array("268", "13e001ec458c18110fdf5d384fe606c7"), + "plugins/Login/templates/login.twig" => array("7069", "39d6dcb13c718b80da7d518732a34e4f"), + "plugins/Login/templates/resetPassword.twig" => array("157", "f5cd1c27479d8b0082030afd5a235b80"), + "plugins/MobileAppMeasurable/config/test.php" => array("147", "607b21d69b85e663f2780294b1d0f58f"), + "plugins/MobileAppMeasurable/lang/cs.json" => array("246", "b5d8e530894c2740abc9b6186922bb91"), + "plugins/MobileAppMeasurable/lang/de.json" => array("226", "62882982aa1d95060da9e904398542cd"), + "plugins/MobileAppMeasurable/lang/el.json" => array("365", "c5409748789d2881cf25dfe1f087c1ba"), + "plugins/MobileAppMeasurable/lang/en.json" => array("220", "f9a3d201adcbc0cc086b649021d9cd10"), + "plugins/MobileAppMeasurable/lang/es.json" => array("248", "52764ad57e89e4f786ebdc35a85e1e0c"), + "plugins/MobileAppMeasurable/lang/fr.json" => array("255", "ae12fdfcd7334ec7d43726835dfccb91"), + "plugins/MobileAppMeasurable/lang/hi.json" => array("409", "625ff7a91f7476a5aaf0f02eae3da81f"), + "plugins/MobileAppMeasurable/lang/hu.json" => array("261", "b34f91256cf41fe3389d6235925b2920"), + "plugins/MobileAppMeasurable/lang/it.json" => array("228", "1ffab2fd99c8472b7e67537d43252d23"), + "plugins/MobileAppMeasurable/lang/ja.json" => array("298", "0a93c2193f4371482f303218457029ee"), + "plugins/MobileAppMeasurable/lang/lt.json" => array("80", "b9ba100223ebab405e5541fbdb18aafe"), + "plugins/MobileAppMeasurable/lang/nb.json" => array("234", "9ca3aa2a6071668d8461343baac0c2b9"), + "plugins/MobileAppMeasurable/lang/nl.json" => array("213", "88417890d5f3e6e9cb4d76f0cbdd5a27"), + "plugins/MobileAppMeasurable/lang/pt-br.json" => array("251", "845eb12fc3a582db3661775c0c61ac42"), + "plugins/MobileAppMeasurable/lang/sk.json" => array("81", "66ad6af475b91814bee4a6c0ecfffe40"), + "plugins/MobileAppMeasurable/lang/sr.json" => array("239", "7c5171b57297cf17e2abe0e01fff2079"), + "plugins/MobileAppMeasurable/lang/sv.json" => array("197", "341d5545a5303c135cac0e330e6f5a8d"), + "plugins/MobileAppMeasurable/MobileAppMeasurable.php" => array("252", "36bd055e8b810d23462804240688b51c"), + "plugins/MobileAppMeasurable/plugin.json" => array("169", "500a1e733294ea8a1e89be5f8dd0c5bb"), + "plugins/MobileAppMeasurable/Type.php" => array("570", "9b9ac8512c6d478ed69417dfef51c6d3"), + "plugins/MobileMessaging/APIException.php" => array("265", "a5554240ef51b59ca0864301d6fdb4c8"), + "plugins/MobileMessaging/API.php" => array("11947", "cf31d7fcd100015d2a4b84fcd9e7e71c"), + "plugins/MobileMessaging/Controller.php" => array("3983", "76c222e17e9ff4c58f6c3423214a4cd3"), + "plugins/MobileMessaging/CountryCallingCodes.php" => array("7211", "2bc593523a2f0a88ff29981e702aea1b"), + "plugins/MobileMessaging/GSMCharset.php" => array("2982", "10651d8e8ffc14df6becc4fb3d35a7af"), "plugins/MobileMessaging/images/Clockwork.png" => array("3585", "c0ae966302ddca87a6cc55abf0617efe"), "plugins/MobileMessaging/images/phone.png" => array("568", "c294efbf17ecc6c3384e1cbff1a69521"), - "plugins/MobileMessaging/javascripts/MobileMessagingSettings.js" => array("10525", "eedfe8a5ae54d77c74590698e108b258"), - "plugins/MobileMessaging/MobileMessaging.php" => array("8963", "08ac0b55c51fb2e617173ce07c0ed673"), - "plugins/MobileMessaging/ReportRenderer/ReportRendererException.php" => array("1433", "864c4f04d4bc18daa94d59927fc87ca8"), - "plugins/MobileMessaging/ReportRenderer/Sms.php" => array("4337", "8c5cd9fb9e9e88ac7490db381ac414c3"), - "plugins/MobileMessaging/SMSProvider/Clockwork.php" => array("2850", "5de85a92828a6fb7ab107fd1821efb22"), - "plugins/MobileMessaging/SMSProvider.php" => array("6529", "4265464da0ce3dd71daccd3365f8b5d3"), - "plugins/MobileMessaging/SMSProvider/StubbedProvider.php" => array("591", "ec1c6ab90be8c4ee259ee51012a116af"), + "plugins/MobileMessaging/javascripts/MobileMessagingSettings.js" => array("10529", "133b4910c76b4624039a347a407a74b7"), + "plugins/MobileMessaging/lang/bg.json" => array("4568", "17aeae02922a90f06cd7f5a77211dc8e"), + "plugins/MobileMessaging/lang/ca.json" => array("2268", "f2f27ef1306c66c73b7eaf02bdb6914a"), + "plugins/MobileMessaging/lang/cs.json" => array("4010", "488fe07cf53c5713e8e8b7e2f4ce2c07"), + "plugins/MobileMessaging/lang/da.json" => array("4007", "e71120bc47d4cfa9232038c1c61477d5"), + "plugins/MobileMessaging/lang/de.json" => array("4268", "fa6f6645ea78a9e47ae386e5f3b4a97c"), + "plugins/MobileMessaging/lang/el.json" => array("6787", "9027aba787409b155e0452cc11e5a6a2"), + "plugins/MobileMessaging/lang/en.json" => array("3855", "4c18a2215b57327b2c42c5e8095ae379"), + "plugins/MobileMessaging/lang/es.json" => array("4381", "758b13e6059477cd62a1465b69241fec"), + "plugins/MobileMessaging/lang/et.json" => array("1130", "6330281ae2d363fcd575da04609eff3a"), + "plugins/MobileMessaging/lang/fa.json" => array("4313", "268e01bb81ce84fe2f6b390d1af049f6"), + "plugins/MobileMessaging/lang/fi.json" => array("3891", "b7b493aff2118ec019d964bd947e8795"), + "plugins/MobileMessaging/lang/fr.json" => array("4386", "f9bf76d617169acb7a4797e0b0b503f9"), + "plugins/MobileMessaging/lang/hi.json" => array("7747", "8ba37ae87df1304f9c5a3191748a2c38"), + "plugins/MobileMessaging/lang/id.json" => array("4076", "57fb9a859959985f6865dcfcb2e3fec1"), + "plugins/MobileMessaging/lang/it.json" => array("4121", "23ef29e35ef0148a6981b2e0afd4bf85"), + "plugins/MobileMessaging/lang/ja.json" => array("4921", "ee3f04459a96d8daa92d016b47e76fc6"), + "plugins/MobileMessaging/lang/ko.json" => array("4382", "0ece084f41d277a3b51e984049d6b762"), + "plugins/MobileMessaging/lang/lt.json" => array("507", "75070feeed3882774b94eba3bfa4c0c2"), + "plugins/MobileMessaging/lang/nb.json" => array("1175", "b229d913e6955a395a7884e0a615d9de"), + "plugins/MobileMessaging/lang/nl.json" => array("4084", "48cd9089379941de040465288a80eb56"), + "plugins/MobileMessaging/lang/pl.json" => array("652", "638d909cfc4fc22443ef1b824a18867c"), + "plugins/MobileMessaging/lang/pt-br.json" => array("4234", "f13c2e0ce81400ae30e89861bc8ef513"), + "plugins/MobileMessaging/lang/ro.json" => array("4307", "4869fcf2ce9864572d66d77ae3b6f077"), + "plugins/MobileMessaging/lang/ru.json" => array("5683", "400956a75ed91548359bf9630736abec"), + "plugins/MobileMessaging/lang/sk.json" => array("81", "2d4743783462179612a09e31f0fe81ae"), + "plugins/MobileMessaging/lang/sl.json" => array("130", "3960f0a1532cf5a887c6d3d8c1bbacd2"), + "plugins/MobileMessaging/lang/sr.json" => array("4058", "c13b0e45c0ac5013abbc7c641aa1161c"), + "plugins/MobileMessaging/lang/sv.json" => array("4193", "65a819a729eb13d9e54536ee0558701d"), + "plugins/MobileMessaging/lang/ta.json" => array("911", "40b3abab5850a61bb44c140698c699a5"), + "plugins/MobileMessaging/lang/th.json" => array("717", "44b2beee7fed2f5d1c3c6c7c89d8e5ff"), + "plugins/MobileMessaging/lang/tl.json" => array("4501", "d82db55c46e50e436334c7af7712849d"), + "plugins/MobileMessaging/lang/tr.json" => array("1370", "4b8e69ce82be74aa339a74cea16e5f7b"), + "plugins/MobileMessaging/lang/vi.json" => array("5093", "4a13e1a3a2fd18c1df5426a41a4af309"), + "plugins/MobileMessaging/lang/zh-cn.json" => array("3475", "a34228404ed78d8348be8c8647484782"), + "plugins/MobileMessaging/Menu.php" => array("783", "58df87cb88a2cf30a5b93a83a3eebc65"), + "plugins/MobileMessaging/MobileMessaging.php" => array("8684", "d66da525026533671d16c815a6fe47e3"), + "plugins/MobileMessaging/ReportRenderer/ReportRendererException.php" => array("1722", "b27fea00845b5a3f74164848512310bf"), + "plugins/MobileMessaging/ReportRenderer/Sms.php" => array("4735", "534567079124954079627ade81aa21c5"), + "plugins/MobileMessaging/SMSProvider/Clockwork.php" => array("3951", "b392d2b941eeec9df7079d86516cbe52"), + "plugins/MobileMessaging/SMSProvider/Development.php" => array("1366", "13083dc6a9bc3915a2f09e9eba612560"), + "plugins/MobileMessaging/SMSProvider.php" => array("7340", "657f72e13d1091cd418124ab07d98757"), + "plugins/MobileMessaging/SMSProvider/StubbedProvider.php" => array("888", "759d25913ced41e15cf582086a28b432"), "plugins/MobileMessaging/stylesheets/MobileMessagingSettings.less" => array("248", "b8f6c5c9dc4d2376def8d30a436f2f2d"), - "plugins/MobileMessaging/templates/index.twig" => array("8828", "ff291f3400b4d3e6548d7d72254fcaeb"), - "plugins/MobileMessaging/templates/reportParametersScheduledReports.twig" => array("2220", "facbb81972351c7bf9552bb6d23ad6a6"), - "plugins/MobileMessaging/templates/SMSReport.twig" => array("2081", "8757ecc10c18b0db36031f0ef659756a"), + "plugins/MobileMessaging/templates/index.twig" => array("2616", "88dac87c566134f157f60f99242046ce"), + "plugins/MobileMessaging/templates/macros.twig" => array("1334", "14ac82e870abb77e997890cd9ebd4f00"), + "plugins/MobileMessaging/templates/reportParametersScheduledReports.twig" => array("2357", "d4653ab40bbc54d07077627766b00cc6"), + "plugins/MobileMessaging/templates/SMSReport.twig" => array("2189", "d059a0d95d49a43f6cf07c0c42bb567a"), + "plugins/MobileMessaging/templates/userSettings.twig" => array("5053", "000e51e7df1a0a580fbc8e11e0a0967a"), + "plugins/Monolog/config/cli.php" => array("1133", "0def0c0bb058f9900d2a2249c901d448"), + "plugins/Monolog/config/config.php" => array("3873", "23ca6b09e161cf82f0a336e7692e7ebd"), + "plugins/Monolog/config/tracker.php" => array("926", "cf2e91823caf54e37005f8296e2ded48"), + "plugins/Monolog/Formatter/LineMessageFormatter.php" => array("2603", "a7d052eb262a3a453bc7fee442b90c06"), + "plugins/Monolog/Handler/DatabaseHandler.php" => array("929", "3369587b55baec1639ab26bec0442e3b"), + "plugins/Monolog/Handler/EchoHandler.php" => array("495", "7662d87dd25ec23dea2548ae89f45047"), + "plugins/Monolog/Handler/FileHandler.php" => array("692", "c742e9ad72ba3ff5ad9ec17d1b4c0017"), + "plugins/Monolog/Handler/WebNotificationHandler.php" => array("1480", "604ffb1fc502f72881615e30cf183f3e"), + "plugins/Monolog/Monolog.php" => array("240", "421a86ee2b8ed16d923f35b87eed3b35"), + "plugins/Monolog/plugin.json" => array("60", "a07af932909e291786f7806fe9aefe66"), + "plugins/Monolog/Processor/ClassNameProcessor.php" => array("1941", "9096da098ae7bf3831131914fb3838fa"), + "plugins/Monolog/Processor/ExceptionToTextProcessor.php" => array("1498", "a5ac4a3e78e2593ca72e081c5510faf5"), + "plugins/Monolog/Processor/RequestIdProcessor.php" => array("743", "d1f7bfaa3dffe589dc41b0d36f9b6850"), + "plugins/Monolog/Processor/SprintfProcessor.php" => array("1031", "49ec19decdb0f30c999cfad4dd331104"), + "plugins/Monolog/Processor/TokenProcessor.php" => array("540", "4e3ab05f6cdb40fc66009675707f31c7"), + "plugins/Morpheus/Controller.php" => array("496", "c9ba2c6f6ac41cf8ec608dbb7ebfb88f"), + "plugins/Morpheus/fonts/piwik.eot" => array("20640", "9bbc8707a807b8b1cdea99da191f5dbe"), + "plugins/Morpheus/fonts/piwik.ttf" => array("20484", "c221e9375f094dd947af2fe1de8dbc4d"), "plugins/Morpheus/images/add.png" => array("1261", "56daa2c3de8f7091e4a796940a395399"), + "plugins/Morpheus/images/affix-arrow.png" => array("3179", "542723bef4d8a61f3dc217352dc1873a"), "plugins/Morpheus/images/annotations.png" => array("1069", "d7778841e7e56de10f4d594e56f315c8"), "plugins/Morpheus/images/annotations_starred.png" => array("248", "c55f6bd966fee694d42996d73de74e0e"), + "plugins/Morpheus/images/arr_r.png" => array("195", "2708a22e4d851aff01d4db4f2fddf1da"), + "plugins/Morpheus/images/background-submit.png" => array("1347", "b1822012b50008b3e478d7ae890ead92"), "plugins/Morpheus/images/bullet.png" => array("963", "efd0e064fa9fc92187d976b08d707855"), "plugins/Morpheus/images/calendar.gif" => array("1260", "92079affe9d57713492ba8fc8991ed15"), "plugins/Morpheus/images/chart_bar.png" => array("968", "ff2414d707510b4188795cd7ba73b092"), "plugins/Morpheus/images/chart_line_edit.png" => array("421", "d7f696960ffe8f17fedefcc42415adf4"), "plugins/Morpheus/images/chart_pie.png" => array("1223", "cf703b3878e59ffde83f5cbe6d7571b3"), - "plugins/Morpheus/images/cities.png" => array("1038", "7eaa39f8e0021507b8685e3e0b2c87e9"), "plugins/Morpheus/images/close.png" => array("1122", "9276313cd6bbff312cf65222b970c1d3"), + "plugins/Morpheus/images/collapsed_arrows.gif" => array("54", "224b095cbca536e579119a47328badda"), "plugins/Morpheus/images/configure-highlight.png" => array("356", "b7e641dd5732e92000dfedcacf0fb42d"), "plugins/Morpheus/images/configure.png" => array("1210", "2241fa7107cc901a362a54b372d9e5d4"), + "plugins/Morpheus/images/dashboard_h_bg.png" => array("162", "a6cf0f7cdf6d69edca6edf479aa888e3"), + "plugins/Morpheus/images/data_table_footer_active_item.png" => array("145", "70dcc41079a073d03fee2628dcd0cf74"), "plugins/Morpheus/images/datepicker_arr_l.png" => array("963", "e770866fb6c0b6dc9318bb635cb8b25c"), "plugins/Morpheus/images/datepicker_arr_r.png" => array("968", "324f7f5e7b2d60ac543d574fec0638fe"), + "plugins/Morpheus/images/delete.png" => array("2175", "b3b9cb547a0511ff15b5371b122a6f66"), + "plugins/Morpheus/images/download.png" => array("734", "0552d1746701df879d14c2fdf3d5ac41"), + "plugins/Morpheus/images/ecommerceAbandonedCart.gif" => array("369", "51974d5002afd9b7c8009d14a1207aad"), + "plugins/Morpheus/images/ecommerceOrder.gif" => array("570", "b0c1aa6141f0047b4bcd0cc665c945ad"), + "plugins/Morpheus/images/email.png" => array("754", "baaa6accd945fcb4480b29ab2e15bded"), + "plugins/Morpheus/images/error_medium.png" => array("2622", "d789ca042860b20782cfb8c2bfd458e0"), + "plugins/Morpheus/images/error.png" => array("1150", "16ac5f1c769e78a074144f1fe9073dfb"), + "plugins/Morpheus/images/event.png" => array("164", "8e1d701795486cdc53cbf7a5c3b4d069"), + "plugins/Morpheus/images/expanded_arrows.gif" => array("60", "a9afa92168dbbe6f693f3ff71fa27b32"), "plugins/Morpheus/images/export.png" => array("1182", "4ab0af6ccaf4cbe06da8e2f19d611893"), + "plugins/Morpheus/images/feed.png" => array("691", "55bc1130d360583e2aecbcebfbf6eda7"), "plugins/Morpheus/images/forms-sprite.png" => array("1758", "c87c3f0ae1eeb5b9f928d76a019afbea"), + "plugins/Morpheus/images/fullscreen.png" => array("346", "629df6e9cfdf1bb8d0a612f651da69e9"), "plugins/Morpheus/images/goal.png" => array("1042", "13fcf0fc4f2f5c7cbeb3b954be2302cb"), "plugins/Morpheus/images/help.png" => array("1297", "2030eb18bab29a957d84f6ab578f04a1"), + "plugins/Morpheus/images/html_icon.png" => array("3503", "45a005cf3fa96037df5d7935d26b5f7c"), + "plugins/Morpheus/images/ico_alert.png" => array("1112", "63d124bf79386ebf6285926956ff7829"), "plugins/Morpheus/images/ico_delete.png" => array("1306", "fd41409c74d29bab6be2a1f1def2f1e3"), "plugins/Morpheus/images/ico_edit.png" => array("1202", "67cfadf5265b4d2c506f3db2e1ca367a"), + "plugins/Morpheus/images/ico_info.png" => array("978", "362b1589e75a151c9e4d509615851950"), "plugins/Morpheus/images/icon-calendar.gif" => array("1260", "7acac555dc283dfb65de9f7f524e662b"), "plugins/Morpheus/images/image.png" => array("1139", "70283dccd506faa0a0a4580d822a4609"), "plugins/Morpheus/images/info.png" => array("1278", "6df252a916c3df5b269732e76ecfa50e"), + "plugins/Morpheus/images/inp_bg.png" => array("137", "1f4b8e7288c5d4dc52e44c50e0d02a9b"), + "plugins/Morpheus/images/li_dbl_gray.gif" => array("48", "5b0a692984ac5b04acc0886cd374bb85"), "plugins/Morpheus/images/link.gif" => array("1147", "6098367a5cd6a3dd93c56aced55925bf"), "plugins/Morpheus/images/loading-blue.gif" => array("723", "23f0762fea3d694b579522524bd5628f"), + "plugins/Morpheus/images/login-sprite.png" => array("10200", "a2a3520f448277c3efdb871d637fc34b"), "plugins/Morpheus/images/logo-header.png" => array("2215", "9695149e76d3080dcffd78eb45cd3735"), + "plugins/Morpheus/images/logo-marketplace.png" => array("2927", "aa3dc0cd04c23a654e7a96ceabe2a6c1"), "plugins/Morpheus/images/logo.png" => array("3902", "d11828697d06b1117c49413d77a6e9ff"), - "plugins/Morpheus/images/logo.svg" => array("2290", "d19186837572bb5994e4d6e2b463348b"), + "plugins/Morpheus/images/logo.svg" => array("2290", "3e32725576d9f37e5ba4b320019dfff7"), "plugins/Morpheus/images/maximise.png" => array("1053", "b98f8497b9b242cac626128ffd4d5ef7"), "plugins/Morpheus/images/minimise.png" => array("968", "2ad0c819d2416043d07be478ac9a9156"), - "plugins/Morpheus/images/pause_disabled.gif" => array("1141", "5dcb5e11d359f2a4e180fd7b5a3a5dfd"), - "plugins/Morpheus/images/pause.gif" => array("1142", "defa62045c5e21609cc6b36de1d299ef"), - "plugins/Morpheus/images/play_disabled.gif" => array("1155", "dd7c8730e4be8128138ee68aebd0a5b2"), - "plugins/Morpheus/images/play.gif" => array("1184", "8db25ba7dc1701ed2c93a483fa25ef88"), + "plugins/Morpheus/images/minus.png" => array("208", "3f78b8b47aa11e1e7cc30bc3a4456cf7"), + "plugins/Morpheus/images/newtab.png" => array("509", "994c19f51192a18f7b3e0bc0775313f2"), + "plugins/Morpheus/images/ok.png" => array("626", "28501b0877ea15b49c6ca58677e186c3"), + "plugins/Morpheus/images/paypal_subscribe.gif" => array("3080", "a74a883239713fb5050593c20d9fd2a5"), + "plugins/Morpheus/images/plus_blue.png" => array("157", "9d61acb98c3ac639715aba6703997ad9"), + "plugins/Morpheus/images/plus.png" => array("214", "872b70c31871f96f546816f0fbfea54d"), "plugins/Morpheus/images/refresh.png" => array("1312", "1672c3502fac047c53f2e74797fe124b"), - "plugins/Morpheus/images/regions.png" => array("1265", "1fdee0b6664804d6525dfbe12931b922"), + "plugins/Morpheus/images/reload.png" => array("892", "5a0360408c248f9cde4e0d4bad31ac00"), + "plugins/Morpheus/images/row_evolution_hover.png" => array("601", "7f6833f656aaad475e02a96ae3c0adb9"), + "plugins/Morpheus/images/row_evolution.png" => array("1934", "0e3fe13d82bc0526ed94bc079152f2f2"), + "plugins/Morpheus/images/search_bg.png" => array("374", "585beb0a278508e4fcf32f65b6a18cf4"), "plugins/Morpheus/images/search_ico.png" => array("1227", "e2da6bc860b189996bdafef30bb2e3cc"), "plugins/Morpheus/images/segment-users.png" => array("1270", "95b3e6b1ed4c79c826f59da6100a27e1"), + "plugins/Morpheus/images/select_arrow.png" => array("93", "bc88371db06a70c1e0db029e758a7627"), + "plugins/Morpheus/images/signout.png" => array("345", "595dd5a6fd609a5357264e6551e040e6"), + "plugins/Morpheus/images/sites_selection.png" => array("120", "f8f6f62a17616adce09791ffb51aab1b"), + "plugins/Morpheus/images/smileyprog_0.png" => array("4045", "0b105851f9dfc4e5a3efb933c4fa01af"), + "plugins/Morpheus/images/smileyprog_1.png" => array("4268", "cd518d27567dda069dd5fff2e3c2291f"), + "plugins/Morpheus/images/smileyprog_2.png" => array("4292", "8e61661161afe5ac8a7e2caf085fb200"), + "plugins/Morpheus/images/smileyprog_3.png" => array("4589", "1eb75d0f84042d492b7ff4a515e23ff3"), + "plugins/Morpheus/images/smileyprog_4.png" => array("4733", "1f31e1ac3c6b3602cecabf0f0c15fd1b"), "plugins/Morpheus/images/sortasc_dark.png" => array("92", "96b06c701e6ef3645c39206045821495"), "plugins/Morpheus/images/sortasc.png" => array("964", "736682039b3ce45e6565e3f871f71057"), "plugins/Morpheus/images/sortdesc_dark.png" => array("98", "dbc223ec613d5bc863e897cf3b525335"), "plugins/Morpheus/images/sortdesc.png" => array("980", "968ded7fbfb0c38d1883788c07f090a4"), + "plugins/Morpheus/images/sort_subtable_asc_light.png" => array("2866", "a8d4e15a81c63b3d7f8ddde1ad5fc78d"), + "plugins/Morpheus/images/sort_subtable_asc.png" => array("173", "457908cb087009a946c99bef46643c69"), + "plugins/Morpheus/images/sort_subtable_desc_light.png" => array("286", "ba6261eca430661a8b84155460424106"), "plugins/Morpheus/images/sort_subtable_desc.png" => array("980", "bc2a98535cbcd85c256c534627f4fffc"), + "plugins/Morpheus/images/star_empty.png" => array("658", "31809a80055eb2aa02b51e6c11ecb02d"), + "plugins/Morpheus/images/star.png" => array("757", "872b7a1a8101bcf7ef6c7cf7c8f78ff7"), + "plugins/Morpheus/images/success_medium.png" => array("1346", "28e0ba1f8492374db4946d42c69e477b"), "plugins/Morpheus/images/table_more.png" => array("1157", "720c588117a9dc781d40a42e90c40466"), "plugins/Morpheus/images/table.png" => array("1056", "375eae11704a2d2e749cdefcb26f2a39"), "plugins/Morpheus/images/tagcloud.png" => array("1098", "1c9b9a43cef5e807087cf3f69544c9ef"), + "plugins/Morpheus/images/video_play.png" => array("517", "29fd1c103c9ac9987b85e053e621ab20"), + "plugins/Morpheus/images/warning_medium.png" => array("1283", "24bc193a073997740e4aa459b2bbbbdf"), + "plugins/Morpheus/images/warning.png" => array("571", "8c4ef759f46a90e7a00e1db65e49edc9"), + "plugins/Morpheus/images/warning_small.png" => array("1083", "5ff491ccd2f32beb35d96fd79a4d7329"), "plugins/Morpheus/images/zoom-out-disabled.png" => array("1297", "81d56e2c732e3ed4bc1a1c7d94632e7b"), "plugins/Morpheus/images/zoom-out.png" => array("1300", "b598e49632becfb91970083f8a9ff62e"), + "plugins/Morpheus/javascripts/ajaxHelper.js" => array("13441", "034436b4fcbd7eb52d075c25162a7135"), "plugins/Morpheus/javascripts/jquery.icheck.min.js" => array("4005", "a31ce1654416358e8d933cbf79b5ffbd"), - "plugins/Morpheus/javascripts/morpheus.js" => array("617", "ad7fbd24380028485a5e0758e4b66e81"), - "plugins/Morpheus/plugin.json" => array("468", "e7be1bd0537250d805421d36c880232a"), - "plugins/Morpheus/stylesheets/admin.less" => array("2273", "103712f53f7978b39f5898f4581815b5"), - "plugins/Morpheus/stylesheets/charts.less" => array("3046", "c72c436df07f2c5282afa95e13722d0e"), - "plugins/Morpheus/stylesheets/colors.less" => array("1454", "6fa45b1e4c87373f790dec9d4822645d"), - "plugins/Morpheus/stylesheets/components.less" => array("8430", "66b71131d92624e3a3d5cd654e29bb3b"), - "plugins/Morpheus/stylesheets/forms.less" => array("6410", "df34cc9255b55f23c7eb82d90abe06f5"), - "plugins/Morpheus/stylesheets/map.less" => array("1824", "fd90d3d1127a9ad8b8c71b611f202904"), - "plugins/Morpheus/stylesheets/mixins.less" => array("2884", "b62ca2d8a1c8c1867a60520243a417c2"), - "plugins/Morpheus/stylesheets/popups.less" => array("1122", "24fd3106538b5a84d77c516bb647651c"), - "plugins/Morpheus/stylesheets/theme.less" => array("19045", "b9c3f6250cd4d85602a319464731c47d"), - "plugins/Morpheus/stylesheets/tooltip.less" => array("725", "8801596d6ea42a576a486f82ffe41e29"), - "plugins/Morpheus/stylesheets/typography.less" => array("2196", "582c812de3aa430bb86b54b7d82d548f"), - "plugins/MultiSites/angularjs/dashboard/dashboard-controller.js" => array("955", "7cdb911b5a86c789d1545d7c16d90084"), - "plugins/MultiSites/angularjs/dashboard/dashboard-directive.js" => array("1097", "231dadf20d40d8e78622e9b97621c026"), - "plugins/MultiSites/angularjs/dashboard/dashboard-filter.js" => array("1962", "aa3286a874ef6b3aa0f982a815feb533"), - "plugins/MultiSites/angularjs/dashboard/dashboard.html" => array("6925", "6b97a82b739ea3c1309caf7ce274982e"), - "plugins/MultiSites/angularjs/dashboard/dashboard.less" => array("2810", "f9d750db11d5a06dc3cc60f9fcd0a5e8"), - "plugins/MultiSites/angularjs/dashboard/dashboard-model.js" => array("8474", "8ae117224a8c797c54f7fe8383503368"), - "plugins/MultiSites/angularjs/site/site-directive.js" => array("1984", "4944d752571c5e40cf6717bc74ef83cf"), - "plugins/MultiSites/angularjs/site/site.html" => array("2225", "e2ce56b83fd0599b73ffb2c64c8f6d55"), - "plugins/MultiSites/API.php" => array("20412", "11973574d15341554d0bec191dfea6e4"), - "plugins/MultiSites/Controller.php" => array("2577", "b68eb4db8925d0a5f3a6a445c76c7c4d"), + "plugins/Morpheus/javascripts/layout.js" => array("731", "a7de9f66a27a08c9ff13d10c10044495"), + "plugins/Morpheus/javascripts/morpheus.js" => array("980", "e0845465115093927bfb2e53d381ddae"), + "plugins/Morpheus/javascripts/piwikHelper.js" => array("16034", "14bac403806796487a7dcfb2e26ba53e"), + "plugins/Morpheus/Menu.php" => array("2078", "8a707a73327baab752f1c3fef9385135"), + "plugins/Morpheus/plugin.json" => array("384", "483e5d60aa915757857471669e8f8c43"), + "plugins/Morpheus/stylesheets/base/bootstrap.css" => array("16271", "fa040297857faf4b690694d72414374e"), + "plugins/Morpheus/stylesheets/base/colors.less" => array("1616", "217ffc05473287287917755d76154f51"), + "plugins/Morpheus/stylesheets/base/icons.css" => array("5001", "d7c5546f75818c2bdd3d924fc79f7a6e"), + "plugins/Morpheus/stylesheets/base.less" => array("1014", "38ce225671838d302ed3858bb29735b8"), + "plugins/Morpheus/stylesheets/base/mixins.less" => array("2460", "e9152b18b2940104370f682b4f3b3df7"), + "plugins/Morpheus/stylesheets/general/_admin.less" => array("1101", "6decf79e2dca4db34f9b0c2f517ecfae"), + "plugins/Morpheus/stylesheets/general/_default.less" => array("1352", "77f6f2ec3fbe2d41afebb91f3581e94c"), + "plugins/Morpheus/stylesheets/general/_form.less" => array("2345", "b1e60376ac1036184294f631b3708167"), + "plugins/Morpheus/stylesheets/general/_forms.less" => array("11264", "8a91fa1f1bd3ca3a49d0378db0c250d6"), + "plugins/Morpheus/stylesheets/general/_jqueryUI.less" => array("6010", "da08581e50343b54f235a6b2030746f2"), + "plugins/Morpheus/stylesheets/general/_misc.less" => array("533", "2cc510998e8adc056349001eaa45e888"), + "plugins/Morpheus/stylesheets/general/_typography.less" => array("1952", "c34f7c92f732723d0c44d1686eb12f47"), + "plugins/Morpheus/stylesheets/general/_utils.less" => array("391", "4793980854730904f5ce0cb7f13082bb"), + "plugins/Morpheus/stylesheets/ieonly.css" => array("520", "58ab9def841f09e1b8c52c61be7f4e7f"), + "plugins/Morpheus/stylesheets/main.less" => array("17450", "f746f47be56c612c19ff8f0d658de268"), + "plugins/Morpheus/stylesheets/simple_structure.css" => array("2990", "3b477d6baea90f1189f80dd5aed885e5"), + "plugins/Morpheus/stylesheets/theme-advanced.less" => array("470", "122dc2b15d0da7c0f2a2d3cfdd6adb84"), + "plugins/Morpheus/stylesheets/theme.less" => array("950", "a2ce235155bbf9da6327b7962f7e4653"), + "plugins/Morpheus/stylesheets/ui/_alerts.less" => array("1370", "898f9adf86d2501e065313de82817c2e"), + "plugins/Morpheus/stylesheets/uibase/_dataTable.less" => array("46", "29c2aa49f73efe255d4202c37a942ef5"), + "plugins/Morpheus/stylesheets/uibase/_header.less" => array("608", "9f01dcd5c270cbe00679c17deb84f4aa"), + "plugins/Morpheus/stylesheets/uibase/_headerMessage.less" => array("886", "2114ba7099e5e2f5e8d4c4b02b305811"), + "plugins/Morpheus/stylesheets/uibase/_languageSelect.less" => array("373", "6eaf913c68258e2832fc4cdaeaadb52d"), + "plugins/Morpheus/stylesheets/uibase/_loading.less" => array("433", "840100352ab2e6650755d0db83c0554b"), + "plugins/Morpheus/stylesheets/uibase/_periodSelect.less" => array("1195", "326d6dab33af99cc13d04d653cca835b"), + "plugins/Morpheus/stylesheets/ui/_buttons.less" => array("1872", "e2272d8b49abac3c6b54d81b1077d63d"), + "plugins/Morpheus/stylesheets/ui/_cards.less" => array("367", "1d79af4ae8277f6f12e00672f513930b"), + "plugins/Morpheus/stylesheets/ui/_charts.less" => array("3105", "9c3d23a1f00c497d58dd16b354dbcd58"), + "plugins/Morpheus/stylesheets/ui/_code.less" => array("478", "0254dbe64a2b57ac7c4f0f45f0cc6efa"), + "plugins/Morpheus/stylesheets/ui/_components.less" => array("7546", "a598798e3a958902e51c49391c46e6ff"), + "plugins/Morpheus/stylesheets/ui/_list-group.less" => array("1252", "70534ffa2fb3b863356f53b5219a70ed"), + "plugins/Morpheus/stylesheets/ui/_map.less" => array("1788", "91883aa3951cb9b4854db124fa56f907"), + "plugins/Morpheus/stylesheets/ui/_navs.less" => array("1222", "daa093bcdbfc240dc592a4d14798a564"), + "plugins/Morpheus/stylesheets/ui/_panels.less" => array("1609", "7d10b5c5add579b055d6962737f6d2e8"), + "plugins/Morpheus/stylesheets/ui/_popups.less" => array("977", "b635f27ebe2b6721651a584db3272956"), + "plugins/Morpheus/stylesheets/ui/_progress-bars.less" => array("557", "82238eae2173a9686e3beef281059fd8"), + "plugins/Morpheus/stylesheets/ui/_tables.less" => array("444", "a907b17ce170ace4c132ad94e82206f3"), + "plugins/Morpheus/stylesheets/ui/_tooltip.less" => array("739", "9cec901eb3c8517f61d36617d53f0ca9"), + "plugins/Morpheus/templates/admin.twig" => array("1678", "91c0a2f5df78feadcb20d885a4e3b803"), + "plugins/Morpheus/templates/ajaxMacros.twig" => array("1622", "c18d96573d0073ef9cd9aa23dc3fe4dd"), + "plugins/Morpheus/templates/dashboard.twig" => array("1674", "f48d1bac03dc3ec3efcb670af1f45106"), + "plugins/Morpheus/templates/demo.twig" => array("20889", "aa7eb3a5765f2a7df252e05809c88a7a"), + "plugins/Morpheus/templates/empty.twig" => array("35", "2c6a1dccfb394fef9ef03849c39a5bec"), + "plugins/Morpheus/templates/genericForm.twig" => array("1178", "6452313a007a012880a797ccd07eb01d"), + "plugins/Morpheus/templates/_iframeBuster.twig" => array("385", "1c780792c51f5d2fecbf53c0ce0d547f"), + "plugins/Morpheus/templates/javascriptCode.tpl" => array("661", "ae4b48cd094e9bf99eb0838f145a6d74"), + "plugins/Morpheus/templates/_jsCssIncludes.twig" => array("123", "06ce23308eb531695910b50736dbf171"), + "plugins/Morpheus/templates/_jsGlobalVariables.twig" => array("2239", "6e8d7b1cf491a51cbe6ba9ea3304bfba"), + "plugins/Morpheus/templates/layout.twig" => array("2046", "db1140a954be2fc0a691f8f2f1ffb53b"), + "plugins/Morpheus/templates/macros.twig" => array("951", "428e6cd290e9021ea4724e378d7485bf"), + "plugins/Morpheus/templates/maintenance.tpl" => array("835", "2f67753751067dc3ea9ad72ff2ec60ca"), + "plugins/Morpheus/templates/settingsMacros.twig" => array("4993", "8f3196c85f2590f5c335ce9eccf32922"), + "plugins/Morpheus/templates/simpleLayoutFooter.tpl" => array("126", "a9b5ab498a462178c939ed757d9e28f1"), + "plugins/Morpheus/templates/simpleLayoutHeader.tpl" => array("559", "5fa654886c016368dd3bcade8f754530"), + "plugins/Morpheus/templates/_sparklineFooter.twig" => array("102", "a9e46848aaf5613b971827cf12ab6eaa"), + "plugins/Morpheus/templates/user.twig" => array("1627", "dd431995331969fbc329cbccee065d84"), + "plugins/MultiSites/angularjs/dashboard/dashboard.controller.js" => array("1097", "302692ccf007d92f92aa5b04a7cfc3c7"), + "plugins/MultiSites/angularjs/dashboard/dashboard.directive.html" => array("7653", "b2a445d167f9efecd32f036f501dacad"), + "plugins/MultiSites/angularjs/dashboard/dashboard.directive.js" => array("1302", "fb45e36435dc7f134980b85d8c812958"), + "plugins/MultiSites/angularjs/dashboard/dashboard.directive.less" => array("4061", "aa890703c06763410e35426ecee57078"), + "plugins/MultiSites/angularjs/dashboard/dashboard-model.service.js" => array("5722", "315db631c998d5f2b9bef77c38fcbc01"), + "plugins/MultiSites/angularjs/site/site.controller.js" => array("1774", "4f624b5b9a172cff9fbffc12e1e84540"), + "plugins/MultiSites/angularjs/site/site.directive.html" => array("2229", "e6745fbd80ccbd1b39b6cccdc6ce78ea"), + "plugins/MultiSites/angularjs/site/site.directive.js" => array("1221", "084e99a1330404cc08ac6c78b35f7a88"), + "plugins/MultiSites/API.php" => array("20945", "e6349995a70f6094c415cbd33c339d48"), + "plugins/MultiSites/Columns/Metrics/EcommerceOnlyEvolutionMetric.php" => array("1592", "697e9f18d40efa372e85d42fdbd98b44"), + "plugins/MultiSites/Columns/Website.php" => array("377", "e86cc06275de49bd800913c70d47ee1c"), + "plugins/MultiSites/Controller.php" => array("3688", "586f29ee049dd1a2c20b46ab8ff4cd9b"), + "plugins/MultiSites/Dashboard.php" => array("11255", "413ebd3ead6f7fe345a053bb65adece0"), + "plugins/MultiSites/DataTable/Filter/NestedSitesLimiter.php" => array("3459", "fe39cd263fd6f8ede554f0abf9a07e7e"), "plugins/MultiSites/images/arrow_asc.gif" => array("120", "ff5921e3047f33fa2ddc2b85c960133c"), "plugins/MultiSites/images/arrow_desc.gif" => array("130", "38ab9e8c27b8ce2d1a6b6f9753206f4b"), "plugins/MultiSites/images/arrow_down_green.png" => array("221", "9b81c5a2fb3f3b979e5d941a7b51d0e5"), @@ -1665,60 +4090,278 @@ class Manifest { "plugins/MultiSites/images/arrow_up.png" => array("222", "d5642aff98a988d93f317894b665eeb2"), "plugins/MultiSites/images/arrow_up_red.png" => array("248", "906746c2060a32f4aa0ee204b60fe027"), "plugins/MultiSites/images/door_in.png" => array("693", "e20ba15525185c16acfbf043e7b4a9cd"), - "plugins/MultiSites/images/link.gif" => array("75", "b8de0b2b517e1999b32353209be4e976"), - "plugins/MultiSites/images/loading-blue.gif" => array("1849", "483d45d0beb0b5547988926b795f8190"), "plugins/MultiSites/images/stop.png" => array("307", "031e5dba74ad3b375096806ff1662402"), - "plugins/MultiSites/MultiSites.php" => array("4567", "09b8e3130ea25bbb6eaa46058c854f2d"), - "plugins/MultiSites/templates/getSitesInfo.twig" => array("791", "93ca3b820a17e74883bd777766da5660"), - "plugins/Overlay/API.php" => array("4545", "5bdb88b44970b0286c3c6476115f7422"), + "plugins/MultiSites/lang/ar.json" => array("65", "ddf66b6f9031c5f6b3693fd337afeb36"), + "plugins/MultiSites/lang/be.json" => array("63", "18910cb7799139ca0eb27422873dd50a"), + "plugins/MultiSites/lang/bg.json" => array("304", "29d08976c767f87ef682ad174550dba6"), + "plugins/MultiSites/lang/ca.json" => array("208", "5c458253317e797a8dd48add6692efd2"), + "plugins/MultiSites/lang/cs.json" => array("411", "60f6f0cb2f91ea942564a0314fbf330a"), + "plugins/MultiSites/lang/da.json" => array("234", "d0de31281c0930ae008db8d462789165"), + "plugins/MultiSites/lang/de.json" => array("365", "0bc79298f93da6b8adb3e5a80d93cb28"), + "plugins/MultiSites/lang/el.json" => array("580", "73ebb30207a6abb72c07f86d218964cc"), + "plugins/MultiSites/lang/en.json" => array("355", "8cd33e27a2ebf51c656fc40901bdb9be"), + "plugins/MultiSites/lang/es.json" => array("406", "b7111f4b1d4416cf7e227576acc9cd2f"), + "plugins/MultiSites/lang/et.json" => array("64", "cf2971e07c4ef5ff43e5b1b4eb29755c"), + "plugins/MultiSites/lang/fa.json" => array("257", "ff4362b0ea2de2d6eb8837b214b914b5"), + "plugins/MultiSites/lang/fi.json" => array("217", "63f67b4258ed52a64e2ffd16922e0f0a"), + "plugins/MultiSites/lang/fr.json" => array("384", "764604564144d117dd9b216335c71c9b"), + "plugins/MultiSites/lang/hi.json" => array("683", "d512b69c1c7471fee33e002ddcf4ceee"), + "plugins/MultiSites/lang/hu.json" => array("60", "a740ff512c9705171c4b62b333edb87c"), + "plugins/MultiSites/lang/id.json" => array("160", "ecdeff848cc71e5f527d6d7066fa206d"), + "plugins/MultiSites/lang/it.json" => array("356", "5e89ba1077a652d8aaa18756c6bba911"), + "plugins/MultiSites/lang/ja.json" => array("508", "3c42bb17589d2aeb9971abc20a396a04"), + "plugins/MultiSites/lang/ka.json" => array("77", "e51b0df3538d836cc7dabce12ef0041f"), + "plugins/MultiSites/lang/ko.json" => array("201", "679948664abc29de69ef377cec6756eb"), + "plugins/MultiSites/lang/lt.json" => array("156", "b4e52ee7d60093cf143369ee04c6309e"), + "plugins/MultiSites/lang/nb.json" => array("367", "d3dec556b08b5114cf6e4d0052ce45c5"), + "plugins/MultiSites/lang/nl.json" => array("346", "8a03f7a0738fe9136622f9a5b44743b8"), + "plugins/MultiSites/lang/nn.json" => array("62", "0e6eeb2efc00fdbb0f59438d23079acd"), + "plugins/MultiSites/lang/pl.json" => array("151", "8015ce9472feef48aa1f3b6362428bc5"), + "plugins/MultiSites/lang/pt-br.json" => array("347", "e29a855c13be142724304884ac05976a"), + "plugins/MultiSites/lang/pt.json" => array("357", "9eb948e805403fe95ea5dbc4125cc860"), + "plugins/MultiSites/lang/ro.json" => array("256", "df0bb59748027d0c30ae82b1a59558d5"), + "plugins/MultiSites/lang/ru.json" => array("289", "10d3cef168ff1021bf137752aeaef492"), + "plugins/MultiSites/lang/sk.json" => array("59", "f41a4d77c7302f289cb11542cb7c35c6"), + "plugins/MultiSites/lang/sl.json" => array("154", "b972d2d44ddac3d577357b02f389e313"), + "plugins/MultiSites/lang/sq.json" => array("107", "316b2774c2e154195156f283ab917000"), + "plugins/MultiSites/lang/sr.json" => array("353", "9faa5fc69d13e10568cf9707f4276bdd"), + "plugins/MultiSites/lang/sv.json" => array("331", "ba99733449dad6eeb9bb72d0f86bab7e"), + "plugins/MultiSites/lang/ta.json" => array("77", "a9fdfd688a90a24a4a3c4a0a985c5028"), + "plugins/MultiSites/lang/th.json" => array("98", "736af1e3b0f66c9d375ee79d78dbde1c"), + "plugins/MultiSites/lang/tl.json" => array("261", "691d60c3f91fc3eb1e2e02c1437999bb"), + "plugins/MultiSites/lang/uk.json" => array("63", "2de84d9a90c3554211e1e7c65f34c962"), + "plugins/MultiSites/lang/vi.json" => array("384", "5425d78a3b24996cfeb903a32b8cdb24"), + "plugins/MultiSites/lang/zh-cn.json" => array("122", "394150e4ab997c82997b9fdbb2d59ce3"), + "plugins/MultiSites/lang/zh-tw.json" => array("65", "9591edddb8d69ace8ec6af0cdfa68eb8"), + "plugins/MultiSites/Menu.php" => array("614", "1180e3e901e85aabb793c3290b5eb253"), + "plugins/MultiSites/MultiSites.php" => array("3489", "f4cff91123796a2049d9a6b2a9fc0d3e"), + "plugins/MultiSites/plugin.json" => array("98", "aa920881a7a8a187157030d8506bc3e9"), + "plugins/MultiSites/Reports/Base.php" => array("1108", "b3279b84c177e802f33519759e44dc7d"), + "plugins/MultiSites/Reports/GetAll.php" => array("599", "22226ebefe4a6185f91b9a1a4bdcf9a5"), + "plugins/MultiSites/Reports/GetOne.php" => array("603", "d478f20ddff110c4193f41be3e95e675"), + "plugins/MultiSites/templates/getSitesInfo.twig" => array("820", "3d83fdba685ee6113f0def37735ce611"), + "plugins/Overlay/API.php" => array("4626", "237491732f8c9fe7b0f5db03ce452d97"), "plugins/Overlay/client/client.css" => array("2403", "b89a7f8790c46666d3f1f4bd324b0842"), - "plugins/Overlay/client/client.js" => array("7986", "c91fc2d4c5af7f909a3189cb4e6bd10b"), + "plugins/Overlay/client/client.js" => array("8151", "fd044f62b5befb56e7c85e78811c430e"), "plugins/Overlay/client/close.png" => array("655", "42492684e24356a4081134894eabeb9e"), - "plugins/Overlay/client/followingpages.js" => array("20057", "017a346250b30fe408b154388439dac4"), - "plugins/Overlay/client/linktags.eps" => array("460096", "3b89babacdf68b23c14e37619af9c6a7"), + "plugins/Overlay/client/followingpages.js" => array("20154", "fe5fb90ab64eee92a7042fd29350517a"), "plugins/Overlay/client/linktags_lessshadow.png" => array("6353", "80e04f62a5efa4ec128efb40b0670d37"), "plugins/Overlay/client/linktags_noshadow.png" => array("5355", "1471510499dfc32c978ace341095f0aa"), "plugins/Overlay/client/linktags.png" => array("6489", "1e7ee586288b9aa1d1ecb3b6cc402cae"), - "plugins/Overlay/client/linktags.psd" => array("38518", "0ad1ccd72db63437365e1d2225213afe"), "plugins/Overlay/client/loading.gif" => array("723", "6ce8f9a2c650cf90261acfc98b2edf90"), "plugins/Overlay/client/translations.js" => array("756", "b25f65e35f2091e51f3846f58f0b0843"), - "plugins/Overlay/client/urlnormalizer.js" => array("5688", "4c43dc9ca32de3fcf5f6c9fb3a93082f"), - "plugins/Overlay/Controller.php" => array("8120", "0145004a547bfc7983686f765040b2c9"), - "plugins/Overlay/images/info.png" => array("778", "3750c701d2ec35a45d289b9b9c1a0667"), + "plugins/Overlay/client/urlnormalizer.js" => array("5778", "3d380cfa1eec049ab126c343fff17aa2"), + "plugins/Overlay/config/ui-test.php" => array("371", "191feb27bd47976e34bbcc313a57561f"), + "plugins/Overlay/Controller.php" => array("8025", "c621ec8d9daab690425e7458ba61a51b"), "plugins/Overlay/images/overlay_icon_hover.png" => array("360", "7021a0169999242feade5c21859185b1"), "plugins/Overlay/images/overlay_icon.png" => array("359", "dfabdc7dd24cad1b55101fa5dc77a2ad"), - "plugins/Overlay/javascripts/Overlay_Helper.js" => array("952", "76159386fdc4544d42d58af61051f655"), - "plugins/Overlay/javascripts/Piwik_Overlay.js" => array("8715", "0757e757ec98579e800c54557633f78b"), - "plugins/Overlay/javascripts/rowaction.js" => array("1904", "47d98c45ed01ec168770ea77fa4d200b"), - "plugins/Overlay/Overlay.php" => array("1239", "5f268e56246335ca2ab65fb295d537a4"), - "plugins/Overlay/stylesheets/overlay.css" => array("2736", "c45eed3e933565c3737d3500d642fa53"), + "plugins/Overlay/javascripts/Overlay_Helper.js" => array("1136", "c1f9749adbf5d53a9dd30b36c680dfa5"), + "plugins/Overlay/javascripts/Piwik_Overlay.js" => array("10295", "e367e7590db6217c56146f69bc90939b"), + "plugins/Overlay/javascripts/rowaction.js" => array("3066", "fb1005cfd1b100fb28591743cff88182"), + "plugins/Overlay/lang/ar.json" => array("61", "d9e9d7e4e03074877e7f658201a3e52f"), + "plugins/Overlay/lang/be.json" => array("61", "51693289b7e80b0724575533dfb797b7"), + "plugins/Overlay/lang/bg.json" => array("1343", "e4e5e67ab4676e07f77f127d97b0b7df"), + "plugins/Overlay/lang/ca.json" => array("1416", "f0d332dde0e9fd7e1feab34c902437a8"), + "plugins/Overlay/lang/cs.json" => array("1619", "a9175b369dbc62769856c672ef259586"), + "plugins/Overlay/lang/da.json" => array("1305", "99cebc955006b1c7b80b27d36f5c95c9"), + "plugins/Overlay/lang/de.json" => array("1654", "8b54c56092ad479eca8fe7488f1dd8c2"), + "plugins/Overlay/lang/el.json" => array("2715", "e8d189fb3e382d38ab24b6deddc5dab1"), + "plugins/Overlay/lang/en.json" => array("1491", "775e0d6f4906cb06483e48d253bf189a"), + "plugins/Overlay/lang/es.json" => array("1744", "86bb9a733c3c22224d7cb8127ec7f321"), + "plugins/Overlay/lang/et.json" => array("340", "496965f48bbc64c4826458530dea1b2f"), + "plugins/Overlay/lang/fa.json" => array("1095", "eb903d39763174fd3878bbf6f8f74296"), + "plugins/Overlay/lang/fi.json" => array("1309", "ce317e6b6194324cf3a9b0c6989abe92"), + "plugins/Overlay/lang/fr.json" => array("1667", "8f43cb6510d8e62736a8d5fac430fbc0"), + "plugins/Overlay/lang/he.json" => array("132", "e450c25c037976df42d0ff687e9fbaa3"), + "plugins/Overlay/lang/hi.json" => array("3060", "e0ca166075ad25dcca9528384b035772"), + "plugins/Overlay/lang/hr.json" => array("57", "7c4d845d6cbbb81371b6b94e5862499d"), + "plugins/Overlay/lang/hu.json" => array("53", "ce33470291b0ba08af3e3b4a01844e9e"), + "plugins/Overlay/lang/id.json" => array("1378", "1facc291b2f244cb9c4df545575fa064"), + "plugins/Overlay/lang/is.json" => array("61", "0bb96b3ae06094bdd8c8a4a4c7d173f8"), + "plugins/Overlay/lang/it.json" => array("1584", "147a2decd9544d69be62e9f29c86064b"), + "plugins/Overlay/lang/ja.json" => array("1967", "c54512a5fb473b7661cee05b71086623"), + "plugins/Overlay/lang/ka.json" => array("79", "1387c89a97eb0d3c0a2445d5bcd60470"), + "plugins/Overlay/lang/ko.json" => array("1807", "1165bfa8dabef44280e6341817a490c6"), + "plugins/Overlay/lang/lt.json" => array("96", "799349e43625ea16725380f990484f78"), + "plugins/Overlay/lang/lv.json" => array("59", "aa06f56a303f0b4e3bd10508279e0d5b"), + "plugins/Overlay/lang/nb.json" => array("249", "895138ba151f8b2bd43c081db802afed"), + "plugins/Overlay/lang/nl.json" => array("1559", "f9c6283de64f18c0719459f56394b016"), + "plugins/Overlay/lang/nn.json" => array("54", "91153b8385cce7420ff1210b7d2aa679"), + "plugins/Overlay/lang/pl.json" => array("292", "4dcf4dbc1dba6bc04bb564d063b00b8e"), + "plugins/Overlay/lang/pt-br.json" => array("1659", "74861a9d80bfd4ceb2f8f1729f9b00f6"), + "plugins/Overlay/lang/pt.json" => array("62", "faa03c93ea22e30268f939a2d3bc9314"), + "plugins/Overlay/lang/ro.json" => array("1439", "dc0909ab3a8da37b966011e9663b0445"), + "plugins/Overlay/lang/ru.json" => array("901", "476fa3fad5ad2035f502bf6e43e8752a"), + "plugins/Overlay/lang/sk.json" => array("207", "4bd756cd1f408afb4cba35f91208c688"), + "plugins/Overlay/lang/sl.json" => array("57", "7c4d845d6cbbb81371b6b94e5862499d"), + "plugins/Overlay/lang/sq.json" => array("53", "bb9f7689f24f12acd4fdd3d34923890e"), + "plugins/Overlay/lang/sr.json" => array("1486", "f7c7f83e19ac83cf549b5515a43e1442"), + "plugins/Overlay/lang/sv.json" => array("1378", "6336acda0c7ed252f0f00f116cb72b69"), + "plugins/Overlay/lang/ta.json" => array("189", "8c3160c4bdff593a5f22026334989aab"), + "plugins/Overlay/lang/te.json" => array("151", "2282353d44e78a465898c7a26987e301"), + "plugins/Overlay/lang/th.json" => array("238", "37cd0ae58a99ffefe2f871522d83f197"), + "plugins/Overlay/lang/tl.json" => array("1393", "441e2bee7e3beb59f2c07e3110c00293"), + "plugins/Overlay/lang/tr.json" => array("54", "c09f92a572b3559df989db0b889c7950"), + "plugins/Overlay/lang/uk.json" => array("81", "e07ee2805b4e5eeea6b4c67b4787aedd"), + "plugins/Overlay/lang/vi.json" => array("1520", "84a7b61227e44ba8e93e0763e80cd4ae"), + "plugins/Overlay/lang/zh-cn.json" => array("1351", "b4faba5ad5c27e70907c9e7063c11d9f"), + "plugins/Overlay/lang/zh-tw.json" => array("55", "2384b9539d80f596d0e0dcd69f854b8e"), + "plugins/Overlay/Overlay.php" => array("996", "22c3ec1fac70537ccf132c8d9539e976"), + "plugins/Overlay/stylesheets/overlay.css" => array("2878", "9e4bcc8b98984346c868570ad81fc9b0"), "plugins/Overlay/stylesheets/showErrorWrongDomain.css" => array("225", "c816e5c78ca1e4b4cffd6a94defcd42d"), - "plugins/Overlay/templates/index_noframe.twig" => array("651", "c52979a6cf4b4271976930fee8d75c9c"), - "plugins/Overlay/templates/index.twig" => array("2884", "39ccaab561b54a8b08d2928a111df52d"), - "plugins/Overlay/templates/notifyParentIframe.twig" => array("408", "511ff3cd9499c53ca4c83bf12ca4a2d5"), - "plugins/Overlay/templates/renderSidebar.twig" => array("872", "2692f857cb9ed8c4e57e42e32f9e9d79"), - "plugins/Overlay/templates/showErrorWrongDomain.twig" => array("434", "5ea0fec6f13ac36e82316bd98faffa17"), - "plugins/PrivacyManager/Config.php" => array("3733", "cb83704c0bef57bfd13926749ff933c0"), - "plugins/PrivacyManager/Controller.php" => array("12791", "f42e2f56e22820fc57379e2ca5445728"), - "plugins/PrivacyManager/DoNotTrackHeaderChecker.php" => array("2463", "f8225b87bffb42e0f2e37677b3887991"), - "plugins/PrivacyManager/IPAnonymizer.php" => array("2588", "b1d6687046c4138308f8906e29f5aa27"), - "plugins/PrivacyManager/javascripts/privacySettings.js" => array("7303", "6bc9a333ab44580364f515dabaff87d5"), - "plugins/PrivacyManager/LogDataPurger.php" => array("11350", "1615d14d06032ab180b20ec90d07475e"), - "plugins/PrivacyManager/PrivacyManager.php" => array("17475", "20d15ebced957a28e59abfda6bb85c9b"), - "plugins/PrivacyManager/ReportsPurger.php" => array("14516", "f5927d9196cb3efb7804c537b243a0f4"), + "plugins/Overlay/templates/index_noframe.twig" => array("672", "b3133954c1e583910e749a95c3544237"), + "plugins/Overlay/templates/index.twig" => array("2926", "d6a7439f248eb604f68e261e4a5082bf"), + "plugins/Overlay/templates/notifyParentIframe.twig" => array("460", "0cec680bb7d2720b609e03da66c369b3"), + "plugins/Overlay/templates/renderSidebar.twig" => array("1132", "b12fd5abdcdc66ca5524ee34c5712538"), + "plugins/Overlay/templates/showErrorWrongDomain.twig" => array("459", "7fb5c6c719735505cf6f2b1798062c32"), + "plugins/Overlay/templates/startOverlaySession.twig" => array("1872", "c2c84428140f7416ed3db1dc54405139"), + "plugins/PiwikPro/config/test.php" => array("310", "a19efcd9ea673f4d22ed045abc7abdc2"), + "plugins/PiwikPro/images/promo.png" => array("2349", "02e043f4426dba3d50e78a3fab19134c"), + "plugins/PiwikPro/lang/en.json" => array("143", "f7a7302c6dd80293ad5e204a4ae39bd1"), + "plugins/PiwikPro/PiwikPro.php" => array("576", "72b07947a71b431c12a6b2777a685b46"), + "plugins/PiwikPro/plugin.json" => array("159", "3e06e175584efa64d393fe85cb6fdd85"), + "plugins/PiwikPro/Promo.php" => array("2424", "534c88e23069eefa0358ce7d58848394"), + "plugins/PiwikPro/stylesheets/widget.less" => array("442", "c5778dfac63acec7fccee58a208bfe11"), + "plugins/PiwikPro/templates/promoPiwikProWidget.twig" => array("375", "6276dfced7c3dd0b07ec4a2a720a2d46"), + "plugins/PiwikPro/Widgets.php" => array("1905", "5267b454d5603a822e34adbc0870a8a6"), + "plugins/PrivacyManager/Config.php" => array("3738", "282509f1a10316199332af4eeba0b94d"), + "plugins/PrivacyManager/Controller.php" => array("13262", "1b59d8c3cd2a54f9cf432454afdf7ae4"), + "plugins/PrivacyManager/DoNotTrackHeaderChecker.php" => array("4074", "f2aedad628548d446a23d9ffd8068710"), + "plugins/PrivacyManager/IPAnonymizer.php" => array("2189", "8ab77a6befebfbbff1469c6006a9d989"), + "plugins/PrivacyManager/javascripts/privacySettings.js" => array("7568", "ed58cbf9b5060eca9122bcf9b074863a"), + "plugins/PrivacyManager/lang/ar.json" => array("217", "809314835844e1a2acd3660edb06e308"), + "plugins/PrivacyManager/lang/be.json" => array("2098", "a51fd95f8bd90985ecdfcc54436a021d"), + "plugins/PrivacyManager/lang/bg.json" => array("9456", "6c7dd523d08a513832eae1a575dfc30c"), + "plugins/PrivacyManager/lang/ca.json" => array("6409", "ef1549d01c0017a0fc21a2c8129196c1"), + "plugins/PrivacyManager/lang/cs.json" => array("7420", "44bb045d6edf70386280e41b78a0d0ba"), + "plugins/PrivacyManager/lang/da.json" => array("6867", "87f9034563b262cf8a66177a992e181c"), + "plugins/PrivacyManager/lang/de.json" => array("8013", "dfb15ca264f16c6ab0d6de6b5686b889"), + "plugins/PrivacyManager/lang/el.json" => array("12893", "509d450f49afd5f7b1552eca6b7ee63e"), + "plugins/PrivacyManager/lang/en.json" => array("6934", "ed39baead0dbace2e81edf9fcb511fcd"), + "plugins/PrivacyManager/lang/es.json" => array("8203", "eb380e389adb6edd7e47df0bbce299ef"), + "plugins/PrivacyManager/lang/et.json" => array("956", "3689e009ae424e7c684eb62fe048b242"), + "plugins/PrivacyManager/lang/fa.json" => array("6883", "6deee971d6528dee2762551339a02642"), + "plugins/PrivacyManager/lang/fi.json" => array("6312", "d029e1ae5db4840fd144be9ece09c1dd"), + "plugins/PrivacyManager/lang/fr.json" => array("8389", "79a41d9b06d7dc39d75d76b05b4dc6e3"), + "plugins/PrivacyManager/lang/he.json" => array("224", "df7a315985bde3ce28adafecd1073557"), + "plugins/PrivacyManager/lang/hi.json" => array("12599", "188f4e0ce54a45407ddd0800174eba34"), + "plugins/PrivacyManager/lang/hr.json" => array("97", "f58ba946aea666e8de8d90d8f51bd1d0"), + "plugins/PrivacyManager/lang/hu.json" => array("246", "ef9e3c9c281550f0d21f6f7bad7824f2"), + "plugins/PrivacyManager/lang/id.json" => array("5916", "181e39c45e474fbbbede5a80670537bd"), + "plugins/PrivacyManager/lang/is.json" => array("159", "c457a96dc3dc2263bc65ef21c47d0f58"), + "plugins/PrivacyManager/lang/it.json" => array("7625", "df7db12eed549e60907d54797bb4faff"), + "plugins/PrivacyManager/lang/ja.json" => array("9096", "7e163a1b27a45afc2d8b5803ed767e2e"), + "plugins/PrivacyManager/lang/ka.json" => array("401", "7e76815d9e4f8ebd35e2abf63ca15918"), + "plugins/PrivacyManager/lang/ko.json" => array("8045", "84c18fdace5769b8979336462e536e7d"), + "plugins/PrivacyManager/lang/lt.json" => array("633", "862fed853b43f7562f0ac910eb04bd05"), + "plugins/PrivacyManager/lang/lv.json" => array("946", "77e7902ee18aa72a48b4b80947f77d43"), + "plugins/PrivacyManager/lang/nb.json" => array("1449", "9bb17e4c0b3bfafe423b0c6066c36be7"), + "plugins/PrivacyManager/lang/nl.json" => array("7051", "ab937ec1571403de261b8e276b7a8752"), + "plugins/PrivacyManager/lang/nn.json" => array("320", "bd5e881458c7ab1107cd72253b170e63"), + "plugins/PrivacyManager/lang/pl.json" => array("3458", "4b0bad12fdce5833b9ff6483366dbd92"), + "plugins/PrivacyManager/lang/pt-br.json" => array("7898", "e74ad54687aede8a990bc79db0782914"), + "plugins/PrivacyManager/lang/pt.json" => array("1493", "a8a3c9080275aa554f9d26a345569c13"), + "plugins/PrivacyManager/lang/ro.json" => array("7133", "c23cb39e3070bc337cfa6e4d49632c85"), + "plugins/PrivacyManager/lang/ru.json" => array("10681", "b6df20958a064d2c53b8893d9b2d1245"), + "plugins/PrivacyManager/lang/sk.json" => array("648", "2ed5e10c6b66b9408291c954ae7c3326"), + "plugins/PrivacyManager/lang/sl.json" => array("760", "e83135609652c31f8cf7de4143c83265"), + "plugins/PrivacyManager/lang/sq.json" => array("1863", "83dc4674da2a8ddf58040ec7154338e1"), + "plugins/PrivacyManager/lang/sr.json" => array("7281", "e8c64363ae78576cf27ee50b168c8cdf"), + "plugins/PrivacyManager/lang/sv.json" => array("7205", "87262d9034b1c810a2d8ab6a72a26c37"), + "plugins/PrivacyManager/lang/te.json" => array("228", "0dc1d05f984d24fd3353bd6928956c8d"), + "plugins/PrivacyManager/lang/th.json" => array("2472", "f09b5bd3978a92c08a0c887af46cee99"), + "plugins/PrivacyManager/lang/tl.json" => array("6727", "4b9478c0ef60f8340d26e7868b4b2196"), + "plugins/PrivacyManager/lang/tr.json" => array("616", "8891d799cf1d6ac86a21292be8a85f79"), + "plugins/PrivacyManager/lang/uk.json" => array("338", "8cc8f459154e9c00ebea21e68875e51c"), + "plugins/PrivacyManager/lang/vi.json" => array("7436", "18cb179b7c73a248bdfed9e47fb9e7a6"), + "plugins/PrivacyManager/lang/zh-cn.json" => array("5430", "ef1e9ea2f06ab6702c6b75cd8925525e"), + "plugins/PrivacyManager/lang/zh-tw.json" => array("151", "1f620a10e9468b3a43d289b6e0899506"), + "plugins/PrivacyManager/LogDataPurger.php" => array("5214", "b5645d500435dd86f046fa98aef11f8d"), + "plugins/PrivacyManager/Menu.php" => array("606", "c7e16bc01fb2af947113a7a9b218ce23"), + "plugins/PrivacyManager/PrivacyManager.php" => array("18559", "38fd81610c837c1c44613aca057f003f"), + "plugins/PrivacyManager/ReportsPurger.php" => array("14850", "ad32d80402f1e78dde30cac6b18f62a0"), + "plugins/PrivacyManager/Tasks.php" => array("701", "7f52adc528600755f855eed3f0cb1e45"), "plugins/PrivacyManager/templates/getDatabaseSize.twig" => array("394", "b20b279686f6084c9b6d1e054711991c"), - "plugins/PrivacyManager/templates/privacySettings.twig" => array("20783", "b213cd581d7f612668eded5b4daf9875"), - "plugins/Provider/API.php" => array("1282", "02ed45d84f2acf2d31d1ec3dfa657715"), - "plugins/Provider/Archiver.php" => array("885", "12721c47c4a162253ef42338f86d6407"), - "plugins/Provider/Controller.php" => array("442", "074ac889d8bbd8521336f33cafc5980b"), - "plugins/Provider/functions.php" => array("1874", "fcce854c96b6a99d906cb348833c6f79"), - "plugins/Provider/Provider.php" => array("7890", "bcf48128ceabc04b1295ba16cffbaf20"), - "plugins/Proxy/Controller.php" => array("3809", "92af78c5373bb29f60fa3dbbdc6e96de"), - "plugins/Proxy/Proxy.php" => array("748", "7fa5c2c560d621ea057cb3258f6ed0e4"), - "plugins/Referrers/API.php" => array("21979", "18add5717656db81281bf2aab318f4bd"), - "plugins/Referrers/Archiver.php" => array("11276", "312f7099cd0f95a633a20a20d9e0e7a3"), - "plugins/Referrers/Controller.php" => array("21890", "3d8ee0bbab081d220d5501ba2535e9a6"), - "plugins/Referrers/functions.php" => array("6981", "5bb99c15c9912d20d94aa399831c3cc3"), + "plugins/PrivacyManager/templates/privacySettings.twig" => array("19214", "0b1e72a93260dca6be36a73b5e7697b9"), + "plugins/Provider/API.php" => array("1192", "85d8114d8929a55e92121aa1bfe4b17e"), + "plugins/Provider/Archiver.php" => array("1206", "0301de3ad875ba8338828df7faa6f342"), + "plugins/Provider/Columns/Provider.php" => array("3121", "40235bcd2b003f4dc4331bb5fd81b720"), + "plugins/Provider/Controller.php" => array("257", "14d269796ec2e34356a31ba0f65dbe66"), + "plugins/Provider/functions.php" => array("1718", "abb7bbb4edbfaae06df9ded1a5c7aba8"), + "plugins/Provider/lang/am.json" => array("117", "291791d66aa097caf78e730fd9350367"), + "plugins/Provider/lang/ar.json" => array("744", "aadefe1dcba85f865c97cdb923b5b48e"), + "plugins/Provider/lang/be.json" => array("669", "49023708c4548f775f3a5691ede3e2ee"), + "plugins/Provider/lang/bg.json" => array("640", "00e1ffbe4b613d244c685b20c360caa1"), + "plugins/Provider/lang/ca.json" => array("399", "f32df5e63ff134c70e8fc5207a90c295"), + "plugins/Provider/lang/cs.json" => array("651", "aac7a90e28676047872712b9fb0ebe22"), + "plugins/Provider/lang/da.json" => array("503", "348264e58c95aa5a6f70270efa277bfe"), + "plugins/Provider/lang/de.json" => array("621", "74bb0afb1819c983349af23201f2e01b"), + "plugins/Provider/lang/el.json" => array("1002", "f9f146d5698e5dd243ce14732a0c32c7"), + "plugins/Provider/lang/en.json" => array("544", "f54b24dd2004282a8e02fca0da2e1db7"), + "plugins/Provider/lang/es.json" => array("638", "4d5fcbcb6994f9082504c10a0b349334"), + "plugins/Provider/lang/et.json" => array("116", "3017fc79c15823d758d7b2c40e84638a"), + "plugins/Provider/lang/eu.json" => array("112", "44fd1acd44276234a30c226dd6d0e22b"), + "plugins/Provider/lang/fa.json" => array("144", "cc86e295bb45ad3ba9ba0d86162c9f59"), + "plugins/Provider/lang/fi.json" => array("381", "d7ce76d8b11ec09abe316762e0e36001"), + "plugins/Provider/lang/fr.json" => array("638", "cc17e6f767d637c0bb4bb142845e8862"), + "plugins/Provider/lang/gl.json" => array("67", "026e7ddfea388c6b7a8fd61656dfae91"), + "plugins/Provider/lang/hi.json" => array("1191", "7d92824326e773c2e372646ce9f359db"), + "plugins/Provider/lang/hu.json" => array("130", "0f5819a027e320364b4fbc0ab3ee4276"), + "plugins/Provider/lang/id.json" => array("405", "d47aea611c92bdfbb8792bfb76aefd69"), + "plugins/Provider/lang/is.json" => array("104", "7949c9b77ffa7ec26ae1c2d2a525f21a"), + "plugins/Provider/lang/it.json" => array("611", "a2a84e970b4f9322c781ac2b7c71b942"), + "plugins/Provider/lang/ja.json" => array("764", "38fe8a42d281b1809c009ade75f132bf"), + "plugins/Provider/lang/ka.json" => array("153", "70ee5247470cb637de4f61733fd2359f"), + "plugins/Provider/lang/ko.json" => array("631", "530c195c5270e95942c11afe9b4f9ed9"), + "plugins/Provider/lang/lt.json" => array("111", "6f234e24add9a01756f6e23f59806b76"), + "plugins/Provider/lang/lv.json" => array("131", "aa48cda969c2dc6bb40fbc7d3cfff287"), + "plugins/Provider/lang/nb.json" => array("595", "14976d530cee609030bb168400d167b1"), + "plugins/Provider/lang/nl.json" => array("584", "e3ac8f22d3a88e3ea87ac53eb5f796a4"), + "plugins/Provider/lang/pl.json" => array("103", "4fa4a69ebd2df5bdeaa0595ee8bcec10"), + "plugins/Provider/lang/pt-br.json" => array("616", "78247792331827d10f8f5bad65a379f3"), + "plugins/Provider/lang/pt.json" => array("377", "a5d76f1e16933a5d4911e60ac95d2765"), + "plugins/Provider/lang/ro.json" => array("408", "7afb2d3cb7171ecac03c6705c3b8f1be"), + "plugins/Provider/lang/ru.json" => array("677", "72e611073439af083857bd73faa45c75"), + "plugins/Provider/lang/sk.json" => array("114", "70a0fa24d8673f272d46a63aaef4f895"), + "plugins/Provider/lang/sl.json" => array("642", "c2aa1fd3276cb51ada69c89244f21210"), + "plugins/Provider/lang/sq.json" => array("439", "a8ea30b514a2be021b4bdc7b5bb6f9b1"), + "plugins/Provider/lang/sr.json" => array("571", "3b028e20cc9201b606570f59514bb9f6"), + "plugins/Provider/lang/sv.json" => array("496", "a10b89c343ea45c4d17dd4a72768c36d"), + "plugins/Provider/lang/ta.json" => array("144", "73d291097b6b445fa30a0dc12ce91e9b"), + "plugins/Provider/lang/th.json" => array("159", "be98f042cd6704b939791bb01c20bfe9"), + "plugins/Provider/lang/tl.json" => array("541", "28722ac542adf1468039a35e645190cd"), + "plugins/Provider/lang/tr.json" => array("114", "f8893e4e3c32b792881a8f4591f7070c"), + "plugins/Provider/lang/uk.json" => array("125", "17af9a194a78480067f6ce95dde0bcc5"), + "plugins/Provider/lang/vi.json" => array("488", "ae88ce12dc83f5f364f388c6123e25b1"), + "plugins/Provider/lang/zh-cn.json" => array("319", "9a709d71a4833b889e28ffade6fa8609"), + "plugins/Provider/lang/zh-tw.json" => array("123", "472b7d00e00d26a2e056ea69210c3aae"), + "plugins/Provider/Provider.php" => array("3953", "5a2afbf9bcd08342e5a3e9e0b797e54b"), + "plugins/Provider/Reports/GetProvider.php" => array("1441", "c639c63375a4ad17277973ea392f99a4"), + "plugins/Provider/Visitor.php" => array("871", "f02f476db53ef3d649845920ba45339a"), + "plugins/Proxy/Controller.php" => array("3823", "9a1b507a1501508bb88fe820e5eb256a"), + "plugins/Proxy/plugin.json" => array("39", "390f9fd5abb660cc3b56bcda43d7e615"), + "plugins/Proxy/Proxy.php" => array("266", "a46c59933b4ce1d2accbf905a8969aa7"), + "plugins/Referrers/API.php" => array("23201", "00bf351b1310953eb2a8696c0b5ae774"), + "plugins/Referrers/Archiver.php" => array("11610", "60765c90ac1cd163eb1faa07b8e487a9"), + "plugins/Referrers/Columns/Base.php" => array("20323", "b32b820ef90292ba45e6b8fae3616fff"), + "plugins/Referrers/Columns/Campaign.php" => array("1863", "8897c4e353e3d3a82595a0c6bbb03fcc"), + "plugins/Referrers/Columns/Keyword.php" => array("1673", "6d0dc2c2b18ed489db2ba43e426d8f3b"), + "plugins/Referrers/Columns/ReferrerName.php" => array("1581", "2190fac10435a8909534b72a4eb2a936"), + "plugins/Referrers/Columns/Referrer.php" => array("380", "4a1b3ebff433500a2d83366dbe89d11a"), + "plugins/Referrers/Columns/ReferrerType.php" => array("1599", "27ed36a1b4df2916e90559948d118524"), + "plugins/Referrers/Columns/ReferrerUrl.php" => array("1101", "54b0899aed9da7863bb49efd0f07f60a"), + "plugins/Referrers/Columns/SearchEngine.php" => array("394", "9763aeabe02ca6c5372746aedfe5cbd8"), + "plugins/Referrers/Columns/SocialNetwork.php" => array("389", "755ebaae2e5308e15b0df56527380f5e"), + "plugins/Referrers/Columns/WebsitePage.php" => array("392", "23e0ad84c75bee3eceabcbdc8666bda6"), + "plugins/Referrers/Columns/Website.php" => array("1599", "060093f62dd2e98b4e39e933e37b34bb"), + "plugins/Referrers/Controller.php" => array("20234", "199b2eccea22beaee54db9a2d165503f"), + "plugins/Referrers/DataTable/Filter/KeywordNotDefined.php" => array("582", "da0b8960eac350bbffd910e94c4b1db9"), + "plugins/Referrers/DataTable/Filter/KeywordsFromSearchEngineId.php" => array("1819", "967d80878e965d628c30457ac9155363"), + "plugins/Referrers/DataTable/Filter/SearchEnginesFromKeywordId.php" => array("2043", "80f6cef775b5a8fb5d9baf32e35a2392"), + "plugins/Referrers/DataTable/Filter/SetGetReferrerTypeSubtables.php" => array("2820", "6ddb30bdc98f97d21f2ff027d4acf6bd"), + "plugins/Referrers/DataTable/Filter/UrlsForSocial.php" => array("1141", "8380453b8feb440d2747ff69ae2e0fae"), + "plugins/Referrers/DataTable/Filter/UrlsFromWebsiteId.php" => array("1270", "e813b81e0c93863324f1e6b885a5fa14"), + "plugins/Referrers/functions.php" => array("2234", "1df5df4efb92a343684ff5b45fb38d87"), "plugins/Referrers/images/searchEngines/1.cz.png" => array("736", "64629e968a100f3d52bbd645f3eeeabd"), "plugins/Referrers/images/searchEngines/abcsok.no.png" => array("734", "46c308c614869c6a7b6dea023df9fb7a"), "plugins/Referrers/images/searchEngines/alexa.com.png" => array("878", "dea186e4049c87f56d90aa473cc58e65"), @@ -1738,6 +4381,7 @@ class Manifest { "plugins/Referrers/images/searchEngines/busca.orange.es.png" => array("430", "da209f011cf118792af6682db448e0cc"), "plugins/Referrers/images/searchEngines/busca.uol.com.br.png" => array("701", "b7108a19b9c249b774f6a5777580cf18"), "plugins/Referrers/images/searchEngines/cgi.search.biglobe.ne.jp.png" => array("716", "806a4ed72d78c7b84472567d28589894"), + "plugins/Referrers/images/searchEngines/chercherfr.aguea.com.png" => array("4303", "067083a7accc683ee2d34392959887c6"), "plugins/Referrers/images/searchEngines/claro-search.com.png" => array("564", "302c71d5eb2d7216713dc6bec51d51d4"), "plugins/Referrers/images/searchEngines/daemon-search.com.png" => array("718", "c14a1c8b35e78a1d6661f7e25e370f9f"), "plugins/Referrers/images/searchEngines/digg.com.png" => array("465", "1755793491662ad23c71eb1623706dc2"), @@ -1747,6 +4391,7 @@ class Manifest { "plugins/Referrers/images/searchEngines/ecosia.org.png" => array("667", "2a93403cf58fb4ad1f7393abca387180"), "plugins/Referrers/images/searchEngines/encrypted.google.com.png" => array("545", "22c5e4db03b94c9ba501f522d8011a53"), "plugins/Referrers/images/searchEngines/eo.st.png" => array("445", "aa76f4c0c414201782d015ee304643d0"), + "plugins/Referrers/images/searchEngines/extern.peoplecheck.de.png" => array("347", "10accdc8511ec172f9a9b1b5443c996f"), "plugins/Referrers/images/searchEngines/forestle.org.png" => array("814", "3849505c44dd9af3564964b2c7f72c24"), "plugins/Referrers/images/searchEngines/fr.dir.com.png" => array("735", "a08719a48edcf34246e545276a9537b6"), "plugins/Referrers/images/searchEngines/friendfeed.com.png" => array("524", "91638ac895b524e32425e742cfd98dd0"), @@ -1757,7 +4402,9 @@ class Manifest { "plugins/Referrers/images/searchEngines/google.com.png" => array("545", "22c5e4db03b94c9ba501f522d8011a53"), "plugins/Referrers/images/searchEngines/googlesyndicatedsearch.com.png" => array("545", "22c5e4db03b94c9ba501f522d8011a53"), "plugins/Referrers/images/searchEngines/holmes.ge.png" => array("774", "89027cbd84500007fb09c4ed1a2c8273"), + "plugins/Referrers/images/searchEngines/image.search.yahoo.co.jp.png" => array("522", "76ac9b6fda30c632b0c4258d72072680"), "plugins/Referrers/images/searchEngines/images.google.com.png" => array("545", "22c5e4db03b94c9ba501f522d8011a53"), + "plugins/Referrers/images/searchEngines/images.search.biglobe.ne.jp.png" => array("808", "64a4a3d5a3487a29c35434ae1e5a9915"), "plugins/Referrers/images/searchEngines/images.search.yahoo.com.png" => array("538", "9c23aa1c6d5ad4fdea3738632364cbb2"), "plugins/Referrers/images/searchEngines/images.yandex.ru.png" => array("497", "0ff0e2ee9e0b08fbfba2eaa9f4612848"), "plugins/Referrers/images/searchEngines/infospace.com.png" => array("940", "c269cb5b0658fe1679db465613a7c406"), @@ -1765,12 +4412,15 @@ class Manifest { "plugins/Referrers/images/searchEngines/ixquick.com.png" => array("613", "203d372a14add8806719d1fe499210e4"), "plugins/Referrers/images/searchEngines/junglekey.com.png" => array("580", "498a589e8b6b01a45be9b5b090897c03"), "plugins/Referrers/images/searchEngines/jyxo.1188.cz.png" => array("401", "f1845d795944ceb17f0d2490e771331d"), + "plugins/Referrers/images/searchEngines/k9safesearch.com.png" => array("562", "ea7a3bf74a549a7d618255b94215eaf3"), "plugins/Referrers/images/searchEngines/ko.search.need2find.com.png" => array("736", "64629e968a100f3d52bbd645f3eeeabd"), + "plugins/Referrers/images/searchEngines/kwzf.net.png" => array("265", "34652cdc25d70bba01ce9ac01d929d3b"), "plugins/Referrers/images/searchEngines/lo.st.png" => array("828", "cebd310f1c9e95a2afab3699e8877e4a"), "plugins/Referrers/images/searchEngines/maps.google.com.png" => array("1132", "b95321cca7e7e4745c9478060113984e"), "plugins/Referrers/images/searchEngines/metager2.de.png" => array("556", "147f78f79ab041ce6131f310a94ad772"), "plugins/Referrers/images/searchEngines/meta.rrzn.uni-hannover.de.png" => array("281", "c851ac18187f08ac7f5860f31f5a8231"), "plugins/Referrers/images/searchEngines/meta.ua.png" => array("873", "190caab492691e94df900c74f9445f85"), + "plugins/Referrers/images/searchEngines/m.sm.cn.png" => array("649", "48f49c22225145764595e204269893a5"), "plugins/Referrers/images/searchEngines/news.google.com.png" => array("545", "22c5e4db03b94c9ba501f522d8011a53"), "plugins/Referrers/images/searchEngines/nigma.ru.png" => array("673", "41743688398e2ee8a5e30bb32f74ad34"), "plugins/Referrers/images/searchEngines/nova.rambler.ru.png" => array("765", "f74b4c361dfae2e7a1b4116ad7b28925"), @@ -1792,6 +4442,7 @@ class Manifest { "plugins/Referrers/images/searchEngines/searchalot.com.png" => array("369", "6dad79f26644dfc7640af20743994c82"), "plugins/Referrers/images/searchEngines/search.aol.com.png" => array("713", "52bf4d61fe18c1e8564c75709cfee36a"), "plugins/Referrers/images/searchEngines/searchatlas.centrum.cz.png" => array("716", "fac3ddbc5e34c229aac77d4297d09e8b"), + "plugins/Referrers/images/searchEngines/search.auone.jp.png" => array("3609", "11aec5432959244cfa3e5046cc68ce3e"), "plugins/Referrers/images/searchEngines/search.babylon.com.png" => array("930", "5db9449cdc8ab7964896f62c26bd5799"), "plugins/Referrers/images/searchEngines/search.bluewin.ch.png" => array("349", "4ce3ac28b1d3446d71c8db2335f79506"), "plugins/Referrers/images/searchEngines/search.centrum.cz.png" => array("599", "4a90a6fd8cd5a587ee3f887fb2e91a87"), @@ -1800,8 +4451,10 @@ class Manifest { "plugins/Referrers/images/searchEngines/search.daum.net.png" => array("800", "fc91668b8deb23b4b7290dceb75f2428"), "plugins/Referrers/images/searchEngines/search.earthlink.net.png" => array("564", "889aec3d7357d1c7cbb6d52225644907"), "plugins/Referrers/images/searchEngines/search.excite.it.png" => array("323", "5c915ccd6526168e81de15c7fefb050a"), + "plugins/Referrers/images/searchEngines/search.fooooo.com.png" => array("457", "39cb6a4a8b4a023c8e427b50f3179202"), "plugins/Referrers/images/searchEngines/search.freecause.com.png" => array("951", "834f2c111eed35eaf997913c20ab56ff"), "plugins/Referrers/images/searchEngines/search.free.fr.png" => array("881", "02f2297d212c26bd136a99fbc5488170"), + "plugins/Referrers/images/searchEngines/search.genieo.com.png" => array("674", "172a9fbe9867dea9a0926af1f79b3516"), "plugins/Referrers/images/searchEngines/search.goo.ne.jp.png" => array("730", "8d650ba94af30886600ca236f9fbb443"), "plugins/Referrers/images/searchEngines/search.imesh.com.png" => array("644", "2dd75a109986a2337893e3b0d0a621a1"), "plugins/Referrers/images/searchEngines/search.ke.voila.fr.png" => array("331", "5a2240c18b117cf38cc00914d01ee34e"), @@ -1812,6 +4465,7 @@ class Manifest { "plugins/Referrers/images/searchEngines/search.peoplepc.com.png" => array("832", "67f88efd19919d2292405319f6d5092d"), "plugins/Referrers/images/searchEngines/search.qip.ru.png" => array("556", "8bc854ef9a247905ed29938e69d2cea0"), "plugins/Referrers/images/searchEngines/search.rr.com.png" => array("794", "3e9a94cd34825c4246eb666338b22e6b"), + "plugins/Referrers/images/searchEngines/search.seesaa.jp.png" => array("3368", "6cb490bde2df941e985765de150d799a"), "plugins/Referrers/images/searchEngines/searchservice.myspace.com.png" => array("610", "96ecdce6052dd3a535f95ed92c056bc2"), "plugins/Referrers/images/searchEngines/search.seznam.cz.png" => array("553", "4b4f7b4eec38531fe662f678682041f8"), "plugins/Referrers/images/searchEngines/search.smartaddressbar.com.png" => array("624", "13ce4ccf571cd2f09553318b719948b2"), @@ -1820,12 +4474,14 @@ class Manifest { "plugins/Referrers/images/searchEngines/search.tiscali.it.png" => array("548", "f9fea4cbf8bc6f5748c7b1e8e5317b0e"), "plugins/Referrers/images/searchEngines/search.winamp.com.png" => array("753", "3210bbc6d253913396ca23a76ef7ae51"), "plugins/Referrers/images/searchEngines/search.www.ee.png" => array("625", "d0b310f299b5fe2f13f925b8a7fd8e66"), + "plugins/Referrers/images/searchEngines/search.yahoo.co.jp.png" => array("522", "76ac9b6fda30c632b0c4258d72072680"), "plugins/Referrers/images/searchEngines/search.yahoo.com.png" => array("522", "76ac9b6fda30c632b0c4258d72072680"), "plugins/Referrers/images/searchEngines/search.yam.com.png" => array("186", "5585e8456efc56e4ccd870fce71c349a"), "plugins/Referrers/images/searchEngines/search.yippy.com.png" => array("654", "348c872e7f49261c025074587210c494"), "plugins/Referrers/images/searchEngines/sm.aport.ru.png" => array("469", "956d1ffb844dcd9c22fc0f82dd02f6d8"), "plugins/Referrers/images/searchEngines/smart.delfi.lv.png" => array("543", "46e62fb3ebddb902b5b426dda74bdc07"), "plugins/Referrers/images/searchEngines/so.360.cn.png" => array("480", "083a258a36d5571cbb3cdbca50231f89"), + "plugins/Referrers/images/searchEngines/sp-image.search.auone.jp.png" => array("3609", "11aec5432959244cfa3e5046cc68ce3e"), "plugins/Referrers/images/searchEngines/startgoogle.startpagina.nl.png" => array("801", "f60a50c67088bd1387631ff895df1b9a"), "plugins/Referrers/images/searchEngines/start.iplay.com.png" => array("293", "96909e49478da43937ced8de17bb7b43"), "plugins/Referrers/images/searchEngines/suche.freenet.de.png" => array("719", "4fb7eeb8dbd2eefd6bb5572d87ca4be9"), @@ -1837,7 +4493,11 @@ class Manifest { "plugins/Referrers/images/searchEngines/szukaj.wp.pl.png" => array("672", "a14bb3c9901c8158d7051ed50b40a6df"), "plugins/Referrers/images/searchEngines/technorati.com.png" => array("567", "23fbfff1215e8bf0270b529849200214"), "plugins/Referrers/images/searchEngines/translate.google.com.png" => array("545", "22c5e4db03b94c9ba501f522d8011a53"), + "plugins/Referrers/images/searchEngines/videa.seznam.cz.png" => array("553", "4b4f7b4eec38531fe662f678682041f8"), "plugins/Referrers/images/searchEngines/video.google.com.png" => array("545", "22c5e4db03b94c9ba501f522d8011a53"), + "plugins/Referrers/images/searchEngines/videosearch.nifty.com.png" => array("565", "f38b9e99892cf2fdf146a8f6e2731858"), + "plugins/Referrers/images/searchEngines/video.search.yahoo.co.jp.png" => array("522", "76ac9b6fda30c632b0c4258d72072680"), + "plugins/Referrers/images/searchEngines/video.so-net.ne.jp.png" => array("3609", "22aaf56cf759644e7f0a851ad36c6ac9"), "plugins/Referrers/images/searchEngines/web.canoe.ca.png" => array("721", "7645213daa2213dc1b1361a42c11700c"), "plugins/Referrers/images/searchEngines/websearch.cs.com.png" => array("806", "daf75065560d60f37511023d8b2a4212"), "plugins/Referrers/images/searchEngines/websearch.rakuten.co.jp.png" => array("513", "e32baedde4b2a9b508336e2560a7b8ec"), @@ -1868,6 +4528,7 @@ class Manifest { "plugins/Referrers/images/searchEngines/www.exalead.fr.png" => array("880", "6cb4ba9a2fda31f0063972e6d608412e"), "plugins/Referrers/images/searchEngines/www.facebook.com.png" => array("349", "34811a0b31ca7dd2934cc02cffbcdc95"), "plugins/Referrers/images/searchEngines/www.fastbrowsersearch.com.png" => array("965", "d1616dfa7bf9d20634b52857f1c91177"), + "plugins/Referrers/images/searchEngines/www.findhurtig.dk.png" => array("615", "ff904e7690351e547b4c5d254184ae18"), "plugins/Referrers/images/searchEngines/www.fireball.de.png" => array("275", "7831b2d7ebbbacacf8ae272bb407e599"), "plugins/Referrers/images/searchEngines/www.firstsfind.com.png" => array("736", "64629e968a100f3d52bbd645f3eeeabd"), "plugins/Referrers/images/searchEngines/www.fixsuche.de.png" => array("975", "4701f16ae042e18187b99ad23da4aef0"), @@ -1878,6 +4539,7 @@ class Manifest { "plugins/Referrers/images/searchEngines/www.google.interia.pl.png" => array("690", "fa07f267bcb77246a0cf5e76a77b5c77"), "plugins/Referrers/images/searchEngines/www.goyellow.de.png" => array("778", "d410a511b82055b8817585031292756c"), "plugins/Referrers/images/searchEngines/www.gulesider.no.png" => array("575", "0eae2709e4afbe459883bcb690dbdeef"), + "plugins/Referrers/images/searchEngines/www.haosou.com.png" => array("655", "14d0039471d3d744fed5005ebd9c1f8c"), "plugins/Referrers/images/searchEngines/www.highbeam.com.png" => array("705", "d6fd2b1bd62893f37451bc0fe4d8b0a0"), "plugins/Referrers/images/searchEngines/www.hooseek.com.png" => array("548", "610bbf7fd42486596a7edf046c0aa3ba"), "plugins/Referrers/images/searchEngines/www.hotbot.com.png" => array("275", "7831b2d7ebbbacacf8ae272bb407e599"), @@ -1885,8 +4547,10 @@ class Manifest { "plugins/Referrers/images/searchEngines/www.ilse.nl.png" => array("854", "7321baaf2ff095c525fb1a27dd8a3256"), "plugins/Referrers/images/searchEngines/www.jungle-spider.de.png" => array("867", "6c675278c93d7f5145c116fd12b83076"), "plugins/Referrers/images/searchEngines/www.kataweb.it.png" => array("273", "b9f2ffb235b4459c6e2b6f3f8c811b1c"), + "plugins/Referrers/images/searchEngines/www.kensaq.com.png" => array("278", "43a8c6d6be8c6413cdd8ab0ec0f12a85"), "plugins/Referrers/images/searchEngines/www.kvasir.no.png" => array("395", "2be12aaef3172e501bb5c2960c4bde26"), "plugins/Referrers/images/searchEngines/www.latne.lv.png" => array("756", "89197fc28e878fb017d1fe0f2be63b26"), + "plugins/Referrers/images/searchEngines/www.lookany.com.png" => array("635", "3c7970ddd1509bcaa70164722e41e2e7"), "plugins/Referrers/images/searchEngines/www.looksmart.com.png" => array("423", "c63999bc972ee4181b3aed5506727684"), "plugins/Referrers/images/searchEngines/www.maailm.com.png" => array("736", "64629e968a100f3d52bbd645f3eeeabd"), "plugins/Referrers/images/searchEngines/www.mamma.com.png" => array("931", "fbd287ad4cb8ff307ddfde1a61ba6b18"), @@ -1901,14 +4565,18 @@ class Manifest { "plugins/Referrers/images/searchEngines/www.picsearch.com.png" => array("661", "7fd004f30bee9ed392c3dfdd36bc7cce"), "plugins/Referrers/images/searchEngines/www.plazoo.com.png" => array("471", "ce3c7526febd2d33468292a2cc18fd05"), "plugins/Referrers/images/searchEngines/www.qualigo.at.png" => array("627", "c793911b3efed6122c975023c6c86369"), + "plugins/Referrers/images/searchEngines/www.qwant.com.png" => array("991", "0facf32fc2519c93c1a93e11ddef4115"), "plugins/Referrers/images/searchEngines/www.searchcanvas.com.png" => array("867", "a148aa4f2ebd3a9d627b156c261b11f1"), "plugins/Referrers/images/searchEngines/www.search.ch.png" => array("344", "cbf2de2cac7cf39897ed488301b88687"), "plugins/Referrers/images/searchEngines/www.search.com.png" => array("517", "7675624d816df97773003fd968d3fb92"), "plugins/Referrers/images/searchEngines/www.searchy.co.uk.png" => array("656", "e6018e106ad0190fff95a73bb3bb31d9"), "plugins/Referrers/images/searchEngines/www.sharelook.fr.png" => array("433", "85be47a9b158874e7831982d2872c4f3"), "plugins/Referrers/images/searchEngines/www.skynet.be.png" => array("855", "9561e59ff5068d31f7a3a95bbed6bb8f"), + "plugins/Referrers/images/searchEngines/www.sm.de.png" => array("806", "4802f1828c79834917b06392673ddcfb"), "plugins/Referrers/images/searchEngines/www.sogou.com.png" => array("636", "babba0fbf8f3dc3f8e332a9bc0e0b616"), + "plugins/Referrers/images/searchEngines/www.so-net.ne.jp.png" => array("3609", "22aaf56cf759644e7f0a851ad36c6ac9"), "plugins/Referrers/images/searchEngines/www.soso.com.png" => array("895", "4cb24e3d5b5b4f434e9e77b78283fb6e"), + "plugins/Referrers/images/searchEngines/www.sputnik.ru.png" => array("372", "b8303047b8de975eed7d4b2484c1b4bb"), "plugins/Referrers/images/searchEngines/www.startsiden.no.png" => array("285", "4d50ad01809bb118e0e14b690c2898c2"), "plugins/Referrers/images/searchEngines/www.suchmaschine.com.png" => array("605", "fec00799f617313dcec453d470bcc1ec"), "plugins/Referrers/images/searchEngines/www.suchnase.de.png" => array("289", "c0f4d4e1a6d633a2de161d847e51ae38"), @@ -1918,6 +4586,7 @@ class Manifest { "plugins/Referrers/images/searchEngines/www.tixuma.de.png" => array("933", "ac483ba65b637e352bd9b7edf11e462e"), "plugins/Referrers/images/searchEngines/www.toile.com.png" => array("868", "7e46a21790c2ca55a8adacaa3ecb860f"), "plugins/Referrers/images/searchEngines/www.toolbarhome.com.png" => array("617", "d39570e56b848781f614ae70f8ccd8a9"), + "plugins/Referrers/images/searchEngines/www.toppreise.ch.png" => array("599", "58f7f59a0866454fc990edb5db2edb5f"), "plugins/Referrers/images/searchEngines/www.trouvez.com.png" => array("498", "cfcae76baa6008dbdad8d65736ca9c07"), "plugins/Referrers/images/searchEngines/www.trovarapido.com.png" => array("540", "25010fae9beae3454550b78cd1c62cca"), "plugins/Referrers/images/searchEngines/www.trusted-search.com.png" => array("736", "64629e968a100f3d52bbd645f3eeeabd"), @@ -1930,6 +4599,7 @@ class Manifest { "plugins/Referrers/images/searchEngines/www.weborama.fr.png" => array("978", "c56c4180238352c79c39105ca60c8d41"), "plugins/Referrers/images/searchEngines/www.websearch.com.png" => array("830", "b7275242d17e492422677fd66e4fe38a"), "plugins/Referrers/images/searchEngines/www.witch.de.png" => array("384", "3471981c242407d47d9786ee4b9dea4a"), + "plugins/Referrers/images/searchEngines/www.woopie.jp.png" => array("319", "6b796434e9c51a65fcfc3b51ec275ba8"), "plugins/Referrers/images/searchEngines/www.x-recherche.com.png" => array("546", "1f424da61da3eb848ecef393a6bccfc5"), "plugins/Referrers/images/searchEngines/www.yasni.de.png" => array("747", "01c5182fc922793a5b408411b53a3c6e"), "plugins/Referrers/images/searchEngines/www.yatedo.com.png" => array("647", "aa0d9cbbe207eaa5bcab22dee20185ce"), @@ -1937,6 +4607,7 @@ class Manifest { "plugins/Referrers/images/searchEngines/www.zapmeta.com.png" => array("220", "1707e427049d54e0046fdbe8d6b90250"), "plugins/Referrers/images/searchEngines/www.zoeken.nl.png" => array("598", "d26fe06a02e1b7e33e0f519af929d9a6"), "plugins/Referrers/images/searchEngines/www.zoznam.sk.png" => array("349", "b690e483bd1a89acd54f0f21531453f6"), + "plugins/Referrers/images/searchEngines/www.zxuso.com.png" => array("814", "cd6f1421a1e792776cc45fe635027f92"), "plugins/Referrers/images/searchEngines/xx.gif" => array("55", "a7bc48d335263fe532d80efb25afb2d5"), "plugins/Referrers/images/searchEngines/xx.png" => array("265", "34652cdc25d70bba01ce9ac01d929d3b"), "plugins/Referrers/images/searchEngines/yandex.ru.png" => array("497", "0ff0e2ee9e0b08fbfba2eaa9f4612848"), @@ -1948,6 +4619,7 @@ class Manifest { "plugins/Referrers/images/socials/buzznet.com.png" => array("462", "3b3e4ee1fe990041080a93bff2d34f98"), "plugins/Referrers/images/socials/classmates.com.png" => array("861", "fc6ae2a786dba146d8279e269e39bb48"), "plugins/Referrers/images/socials/douban.com.png" => array("530", "be1b7fb618ab9ea1eeb04e15b4428b1a"), + "plugins/Referrers/images/socials/dribbble.com.png" => array("3468", "b06a7ae17321542ef4a1144bc5c4e64a"), "plugins/Referrers/images/socials/facebook.com.png" => array("300", "cadba1be0b6e8d4e3ef1755f3cee55ed"), "plugins/Referrers/images/socials/flickr.com.png" => array("1120", "4bf2b10ac1b80eac5f749ea65ff808b0"), "plugins/Referrers/images/socials/flixster.com.png" => array("908", "2011245e7a16ab25fc4f0c5d21487f6a"), @@ -2011,21 +4683,207 @@ class Manifest { "plugins/Referrers/images/socials/xing.com.png" => array("675", "3764aea0fd7c26bf8bf8e95958d94fff"), "plugins/Referrers/images/socials/xx.png" => array("265", "34652cdc25d70bba01ce9ac01d929d3b"), "plugins/Referrers/images/socials/youtube.com.png" => array("695", "81125e13b69e308800a98d9752e3f48b"), - "plugins/Referrers/Referrers.php" => array("29947", "909f867aa6cc8daf48218c512f3c48de"), - "plugins/Referrers/templates/getSearchEnginesAndKeywords.twig" => array("264", "0dff654fb12896c6e24d6579f099c244"), - "plugins/Referrers/templates/index.twig" => array("6007", "644a0b2555e61f18dabca2e7239b800f"), - "plugins/Referrers/templates/indexWebsites.twig" => array("252", "bf3a7aa50faea341e0603b53ff14e4d5"), - "plugins/ScheduledReports/API.php" => array("36820", "527f23578d1ff32edacf572ebef32fb0"), - "plugins/ScheduledReports/config/tcpdf_config.php" => array("6402", "923e6b4e2b61bbc44fb7882d911e438c"), - "plugins/ScheduledReports/Controller.php" => array("3576", "10ce18981e80bb5f89eb9532805f46cb"), - "plugins/ScheduledReports/javascripts/pdf.js" => array("7001", "f42863d0418f199dcef8af7e46d1c0bc"), - "plugins/ScheduledReports/ScheduledReports.php" => array("25874", "523d4cdbf0cc7407353b5958875acb9e"), - "plugins/ScheduledReports/templates/_addReport.twig" => array("8636", "00ecae5fe8b8aa647bcd0efa248e2bc0"), - "plugins/ScheduledReports/templates/index.twig" => array("1937", "0aaa4e1d71cbcb85794f920f6530e953"), - "plugins/ScheduledReports/templates/_listReports.twig" => array("4267", "b554291be9228ef8cb7d250da9449288"), + "plugins/Referrers/lang/am.json" => array("1319", "74aeb79061a9f7980e4a479e667f99f0"), + "plugins/Referrers/lang/ar.json" => array("1376", "105da72dfff5e89e6ce49fd2262f89c4"), + "plugins/Referrers/lang/be.json" => array("4815", "6fd2956224c3c7c5c850a181acee5e80"), + "plugins/Referrers/lang/bg.json" => array("5454", "e98e050bdb024e9dce5992b9a36d9118"), + "plugins/Referrers/lang/bn.json" => array("80", "db197e85fdbbd4e756b819124366bc69"), + "plugins/Referrers/lang/bs.json" => array("68", "a90299482b2190c29a37b7001978899e"), + "plugins/Referrers/lang/ca.json" => array("4162", "acd480649a55d65a5b4fb9636d77b547"), + "plugins/Referrers/lang/cs.json" => array("4634", "156abc5e71c370f39e060f47e965b0fd"), + "plugins/Referrers/lang/cy.json" => array("62", "2afe272bf41b7ebc82c2d3b04cfab1ce"), + "plugins/Referrers/lang/da.json" => array("4386", "7d41d5d4428d2b9ced6a41a8d7acb045"), + "plugins/Referrers/lang/de.json" => array("4675", "d3b56923df2d534748b9bca9b00d9f23"), + "plugins/Referrers/lang/el.json" => array("7531", "0a7a49b8036380d12b6cb58daf1eac45"), + "plugins/Referrers/lang/en.json" => array("4292", "897aff931f8ef2338becf6a1479b27c9"), + "plugins/Referrers/lang/es.json" => array("4899", "f985b18a5bc1ae7bf97e55e3218ae7a9"), + "plugins/Referrers/lang/et.json" => array("1546", "bcf02932fa3afc8cee6c29a1d51efb14"), + "plugins/Referrers/lang/eu.json" => array("1112", "5978f6a531b85f7f90aeb686cda1852f"), + "plugins/Referrers/lang/fa.json" => array("2958", "dc2a43201c2fbe11817c142d6b1300f0"), + "plugins/Referrers/lang/fi.json" => array("4005", "8c5b13f1e94f37a274fc6c988abf9f27"), + "plugins/Referrers/lang/fr.json" => array("4876", "3d34028beec5207de8550dc556f1012d"), + "plugins/Referrers/lang/gl.json" => array("661", "81146e1ab1c11e0b68dd5acac7859e13"), + "plugins/Referrers/lang/he.json" => array("623", "42ddffa848048372355b3fa7c969e6f1"), + "plugins/Referrers/lang/hi.json" => array("8121", "37a67f3937d581252f8e036b30c490b5"), + "plugins/Referrers/lang/hr.json" => array("68", "a90299482b2190c29a37b7001978899e"), + "plugins/Referrers/lang/hu.json" => array("1229", "2228b133e88f8fe9c00eb9f6fb33c2b2"), + "plugins/Referrers/lang/id.json" => array("4224", "9f5385e3e204ed68979993126da34ce2"), + "plugins/Referrers/lang/is.json" => array("1135", "9f87814372127588c947ce3551781f1f"), + "plugins/Referrers/lang/it.json" => array("4642", "f6edd2f734f653cd93d601ee14ce5d1b"), + "plugins/Referrers/lang/ja.json" => array("5434", "1e23caf248428b86169e6dec0a4e03f1"), + "plugins/Referrers/lang/ka.json" => array("2120", "6b901993e679c9b51d2047b74c075b8f"), + "plugins/Referrers/lang/ko.json" => array("4683", "5435783c11c0446113bb692484494a09"), + "plugins/Referrers/lang/lt.json" => array("1335", "06ad375be2c47dbb598c90e2abe738f3"), + "plugins/Referrers/lang/lv.json" => array("878", "8a9e610587f18a681335a542f71b2d96"), + "plugins/Referrers/lang/nb.json" => array("1529", "a02dc36387fa88c8f46c1bb1c75c5b0b"), + "plugins/Referrers/lang/nl.json" => array("3865", "f1a9344ddf36e7bf660f25807b25db5f"), + "plugins/Referrers/lang/nn.json" => array("1095", "4abea66972f71de7c83446b05fa61ff0"), + "plugins/Referrers/lang/pl.json" => array("1295", "c7ca997c45ba0267fd49a0e11035d5f3"), + "plugins/Referrers/lang/pt-br.json" => array("4730", "32d57b0f7df32ce622822f1d635dd0eb"), + "plugins/Referrers/lang/pt.json" => array("3228", "199a7dd0fb2040cef23ff4350f6d25b8"), + "plugins/Referrers/lang/ro.json" => array("4568", "a129d19cbc0220c554374fabd35c7fce"), + "plugins/Referrers/lang/ru.json" => array("6044", "b371412481bbb12456eded1bc33eeca1"), + "plugins/Referrers/lang/sk.json" => array("1157", "f922110ffec6d07f0a13a71b356ddc6c"), + "plugins/Referrers/lang/sl.json" => array("1205", "4025d2526859f214b977c0b7aaff812e"), + "plugins/Referrers/lang/sq.json" => array("4740", "d0726f05e47e5f0c3e1ea308fed4a170"), + "plugins/Referrers/lang/sr.json" => array("4299", "5ffe015cace29fa5f7e0779500d896cc"), + "plugins/Referrers/lang/sv.json" => array("4564", "067975ce23e047a1bed0c3ae5fbaa26d"), + "plugins/Referrers/lang/ta.json" => array("92", "a60df0b9b505623acf5a71b8d291b838"), + "plugins/Referrers/lang/te.json" => array("728", "78101568ff9880e1f27718c2a60daad5"), + "plugins/Referrers/lang/th.json" => array("2074", "94d13608c29f5cccecd88d9541b4b918"), + "plugins/Referrers/lang/tl.json" => array("4819", "8d62f7492426c047fd9250249806cc2f"), + "plugins/Referrers/lang/tr.json" => array("803", "9712aed9bb762998d6bb1d61467faca0"), + "plugins/Referrers/lang/uk.json" => array("1514", "7a5477c6e4de34e531a3ecb00133bb32"), + "plugins/Referrers/lang/vi.json" => array("5215", "1124a168b18ec3de04c4036f85a8263b"), + "plugins/Referrers/lang/zh-cn.json" => array("3703", "51aa372912a13a5a902ef779e465d97c"), + "plugins/Referrers/lang/zh-tw.json" => array("1214", "43fa3a38fa4324edf273b8fa55524891"), + "plugins/Referrers/Menu.php" => array("801", "27065aa16ab18837785157fcfc9451ba"), + "plugins/Referrers/Referrers.php" => array("3792", "a6c200ff5d701c6c471e9de007cb292a"), + "plugins/Referrers/Reports/Base.php" => array("345", "5994a3be08e49ec36e0dd7a355521dfe"), + "plugins/Referrers/Reports/GetAll.php" => array("1577", "32039fa958d2cca13f424ab530cf9820"), + "plugins/Referrers/Reports/GetCampaigns.php" => array("1248", "6a50b8b3a532f2b1798c00f9590722fe"), + "plugins/Referrers/Reports/GetKeywordsFromCampaignId.php" => array("1082", "67239b696cac58542f462713f0794abe"), + "plugins/Referrers/Reports/GetKeywordsFromSearchEngineId.php" => array("957", "bd5d8e7adc3a92773fef2472bcc3bd0f"), + "plugins/Referrers/Reports/GetKeywords.php" => array("1268", "a84d93a581095b5cac5c8d4493d5b678"), + "plugins/Referrers/Reports/GetReferrerType.php" => array("3193", "e7a5d81bab9db51345e431743b8b420b"), + "plugins/Referrers/Reports/GetSearchEnginesFromKeywordId.php" => array("957", "bfea66b6b2f00c1f2829b56360e82e61"), + "plugins/Referrers/Reports/GetSearchEngines.php" => array("1328", "ad1622ee6e7ba9fe061c143dba0902e4"), + "plugins/Referrers/Reports/GetSocials.php" => array("1689", "0c66e975c41cc44899bd12e82a8a0e61"), + "plugins/Referrers/Reports/GetUrlsForSocial.php" => array("990", "a7625d5bf3e903790113ffbdc73ea91e"), + "plugins/Referrers/Reports/GetUrlsFromWebsiteId.php" => array("1013", "d43e3f00f394626b3ccfc08b52f88e25"), + "plugins/Referrers/Reports/GetWebsites.php" => array("1368", "e4d92bc946e5066e62f574998039b153"), + "plugins/Referrers/SearchEngine.php" => array("16436", "330441a60f79d6ec50cf0b0a77688580"), + "plugins/Referrers/Segment.php" => array("378", "15bbfa8d4f76728d090205460d729761"), + "plugins/Referrers/Social.php" => array("4854", "e0268c6580feca9f8e24a3b35415bed8"), + "plugins/Referrers/Tasks.php" => array("1538", "0f729d7ea814561cb28418f85bfb79cd"), + "plugins/Referrers/templates/allReferrers.twig" => array("334", "7ffdef581995e3e54d753c4d99683310"), + "plugins/Referrers/templates/getSearchEnginesAndKeywords.twig" => array("324", "062ef8b47602e9f3a0d8df19ec2a435f"), + "plugins/Referrers/templates/index.twig" => array("5062", "1c6a02d8f411168e845c1367085f5f00"), + "plugins/Referrers/templates/indexWebsites.twig" => array("312", "642f16c5967e4b240e91a694629576f7"), + "plugins/Referrers/Visitor.php" => array("3334", "e3917b74d5efb808e314d6ee141d5004"), + "plugins/Referrers/Widgets.php" => array("495", "0d5c03f515c6e8d89abb9f54b2f2e506"), + "plugins/Resolution/API.php" => array("1596", "bc1de72bda535c962bf0e9a318d4a7d9"), + "plugins/Resolution/Archiver.php" => array("2402", "ffe3cd86c92db72352dba826e3f184a0"), + "plugins/Resolution/Columns/Configuration.php" => array("398", "ce5b99c5387b19038a1f2d3669d04e86"), + "plugins/Resolution/Columns/Resolution.php" => array("1316", "cb8bb69e41bd94680e93682a20cb87c8"), + "plugins/Resolution/functions.php" => array("696", "fb2714f750edecaf56aad0def12c1783"), + "plugins/Resolution/lang/am.json" => array("332", "a5f3bf7460697faec0a4c8c8eb3e88c9"), + "plugins/Resolution/lang/ar.json" => array("397", "61869f55444144c98d63279f19fbdbe7"), + "plugins/Resolution/lang/be.json" => array("797", "b535208adff9daa2a056a95e54d03873"), + "plugins/Resolution/lang/bg.json" => array("870", "fd8ab42de4993732c999637be35e3af2"), + "plugins/Resolution/lang/ca.json" => array("553", "1674f467618a607cdfd2afd1e7bee4fd"), + "plugins/Resolution/lang/cs.json" => array("618", "e31cb163800ee1c9eec646fb34a3f4cc"), + "plugins/Resolution/lang/da.json" => array("616", "2575e8b31373bc06a85afdc8a3fe18ec"), + "plugins/Resolution/lang/de.json" => array("631", "4697d61c7a60c8a0c17aa2603612f6cc"), + "plugins/Resolution/lang/el.json" => array("907", "8cced77232ed90ba584a4466b1133ba0"), + "plugins/Resolution/lang/en.json" => array("616", "35c5154ca93e6bee0dd65ad78614da06"), + "plugins/Resolution/lang/es.json" => array("669", "77b6a5b24e4270f2ef9ab001558264b0"), + "plugins/Resolution/lang/et.json" => array("337", "51f1f638c0ef8bba6c02cbcec69a2c7a"), + "plugins/Resolution/lang/eu.json" => array("316", "a1268cb3436febb91efec821d0dd07f5"), + "plugins/Resolution/lang/fa.json" => array("664", "6b4e01dbe0fed17a19e54007337b7c16"), + "plugins/Resolution/lang/fi.json" => array("523", "812565161f2588c8754460f2c5448b54"), + "plugins/Resolution/lang/fr.json" => array("664", "130ffc148c155835d6ef993127c11af9"), + "plugins/Resolution/lang/gl.json" => array("660", "5ce5b18f801442cda007a7a92f84cbdb"), + "plugins/Resolution/lang/he.json" => array("86", "d63356e1508251174c66489f50349e9f"), + "plugins/Resolution/lang/hi.json" => array("975", "47882dbecc8c68c5a9a7a3c5ce0dc684"), + "plugins/Resolution/lang/hr.json" => array("311", "b16e21708d86ab3ca2e9de72f2fd30ec"), + "plugins/Resolution/lang/hu.json" => array("716", "ddd0b91b351e773bbe0516222313435f"), + "plugins/Resolution/lang/id.json" => array("499", "9ca71c77145db6617d09c34f8cb30f79"), + "plugins/Resolution/lang/is.json" => array("306", "d10368aa7f387ad841c3cd71e43ff446"), + "plugins/Resolution/lang/it.json" => array("648", "2fe0f92718c647c4dd26f1f615b0ebfc"), + "plugins/Resolution/lang/ja.json" => array("645", "aebc0e9888d6d94875e7a21e81bf8c38"), + "plugins/Resolution/lang/ka.json" => array("512", "f76803729a6b3e722191f1704fe18c1f"), + "plugins/Resolution/lang/ko.json" => array("599", "9f75b6bc9d8d5fca18ec90b290695908"), + "plugins/Resolution/lang/lt.json" => array("322", "e15eb9e5a3a65deb19a1df38a9987271"), + "plugins/Resolution/lang/lv.json" => array("584", "7c60fa28a96d540aacdb4db37925d45b"), + "plugins/Resolution/lang/nb.json" => array("617", "974906f074e19afdc01906131bde53c5"), + "plugins/Resolution/lang/nl.json" => array("611", "f818f260415b4c6b9bd572d29c514742"), + "plugins/Resolution/lang/nn.json" => array("316", "15ca6132f3639a184e541a7866a62221"), + "plugins/Resolution/lang/pl.json" => array("333", "bc6d92ce275584cb651e87231c499009"), + "plugins/Resolution/lang/pt-br.json" => array("658", "698fce24416cde994650122affa9f2ce"), + "plugins/Resolution/lang/pt.json" => array("586", "bf282d2c6fdb8e322b62369440a67894"), + "plugins/Resolution/lang/ro.json" => array("564", "b1ff4c92abaf30fcd411362b627f7baa"), + "plugins/Resolution/lang/ru.json" => array("936", "90c36b6e0e6b891bf5ab71245d65a9e4"), + "plugins/Resolution/lang/sk.json" => array("666", "e720e2ffa64915e520808890f3c3e58e"), + "plugins/Resolution/lang/sl.json" => array("243", "a55b17fce3ea1c43ab8a28ac46a4f215"), + "plugins/Resolution/lang/sq.json" => array("632", "a59733a22faad4a4de47803bff9d4c7a"), + "plugins/Resolution/lang/sr.json" => array("607", "0004a1429a6d652488dde934b10d7369"), + "plugins/Resolution/lang/sv.json" => array("638", "9b3427c7ef39e97d268e0e5cf116ed2f"), + "plugins/Resolution/lang/te.json" => array("144", "ce28dff437ac7da3430f22c405256b1a"), + "plugins/Resolution/lang/th.json" => array("473", "42769e0c67ddabfe7c78a3d2a540b37f"), + "plugins/Resolution/lang/tl.json" => array("551", "19122bb8e56af5566c491830fbde8bc8"), + "plugins/Resolution/lang/tr.json" => array("342", "0d527a33d6aebca932cf8a4c32217936"), + "plugins/Resolution/lang/uk.json" => array("446", "79ecd5946e0530617662a42c6413f635"), + "plugins/Resolution/lang/vi.json" => array("639", "d92029180d9281466b1a86ac7e5f3733"), + "plugins/Resolution/lang/zh-cn.json" => array("477", "f4dbb9150ea97c42f0f3efe1af453203"), + "plugins/Resolution/lang/zh-tw.json" => array("549", "021fdd2c78d7e2158d1b9a00ff4e3ea4"), + "plugins/Resolution/Reports/Base.php" => array("800", "254465a552f6f00b337537e67d9a69b1"), + "plugins/Resolution/Reports/GetConfiguration.php" => array("1141", "933d4976dccac955d986791288e0ef29"), + "plugins/Resolution/Reports/GetResolution.php" => array("1016", "553bfba338a53b0d83eb7bd6bc3a6c1c"), + "plugins/Resolution/Resolution.php" => array("1108", "92561250f314054883e0af687b1cc8e5"), + "plugins/Resolution/Segment.php" => array("374", "6748efd168f63060b7e83ac63b142ce1"), + "plugins/Resolution/Visitor.php" => array("532", "db940e92fe48b762a810308670920963"), + "plugins/ScheduledReports/API.php" => array("37088", "a15da1f8801068b2ed8ed7f4ce6183ae"), + "plugins/ScheduledReports/config/tcpdf_config.php" => array("6380", "279f679a4f0c2f7651fac7d597c62773"), + "plugins/ScheduledReports/Controller.php" => array("3554", "bd2e9c819deb7221f7734bcbb8dddd4f"), + "plugins/ScheduledReports/javascripts/pdf.js" => array("7129", "d981d220fa7c3fe008dcfb7393c0a952"), + "plugins/ScheduledReports/lang/ar.json" => array("2011", "a5643a5f968fafa5c6e6111b68fa2c6f"), + "plugins/ScheduledReports/lang/be.json" => array("2670", "aa24633e81e98c7a29f4d4819b5e8962"), + "plugins/ScheduledReports/lang/bg.json" => array("3963", "cfb6e82fa19d751d89efb99e6ad39e64"), + "plugins/ScheduledReports/lang/ca.json" => array("2877", "51c03a09db41280b2980a2a0fca2cde4"), + "plugins/ScheduledReports/lang/cs.json" => array("3909", "eacf5175c34bd22dfccb98c59ca63a4d"), + "plugins/ScheduledReports/lang/da.json" => array("3404", "963ae79e25bf3a4be18a870dbe110e02"), + "plugins/ScheduledReports/lang/de.json" => array("3982", "2a7bba84a8a0cdd80c5523745ab911e6"), + "plugins/ScheduledReports/lang/el.json" => array("6252", "f186b86420c4d1b774bcb4a98c5ec3eb"), + "plugins/ScheduledReports/lang/en.json" => array("3572", "6e1cb16cc05348a401e27a8a5c250121"), + "plugins/ScheduledReports/lang/es.json" => array("4035", "69365ecf63489ec3a20ad79e08305cdb"), + "plugins/ScheduledReports/lang/et.json" => array("1832", "96194e052f9987192b02345a7af4bd3f"), + "plugins/ScheduledReports/lang/fa.json" => array("3676", "3c9d2e229691d6cab0f9b800aa2d7b28"), + "plugins/ScheduledReports/lang/fi.json" => array("3353", "a121ccb2d2002e89685c25b3c65ee21b"), + "plugins/ScheduledReports/lang/fr.json" => array("4221", "7621378d0ea7007caff0867da7d9e402"), + "plugins/ScheduledReports/lang/he.json" => array("197", "e4595abb26084e88c0cb46cc8d7091f6"), + "plugins/ScheduledReports/lang/hi.json" => array("5950", "0c043642de03e50a37e4511b0998ce91"), + "plugins/ScheduledReports/lang/hu.json" => array("852", "4cc5ff246abfa6a2dfe9a8f519656572"), + "plugins/ScheduledReports/lang/id.json" => array("3059", "4198c7dafcf7cedc06c5be96327f7c85"), + "plugins/ScheduledReports/lang/is.json" => array("783", "df435a4e0341dbdfc8256deb4fce7b7f"), + "plugins/ScheduledReports/lang/it.json" => array("3885", "dc1392acd384fc2f59ddc6f479fa8a05"), + "plugins/ScheduledReports/lang/ja.json" => array("4473", "05cdc92c78ae4741837e0bcf6105b247"), + "plugins/ScheduledReports/lang/ka.json" => array("1475", "0e4238accd4e08de3ddd1f2f612f0d8a"), + "plugins/ScheduledReports/lang/ko.json" => array("4023", "a648949eb73d495c50b1fd4ef899e1d0"), + "plugins/ScheduledReports/lang/lt.json" => array("1239", "2304ded28ceb3550ec974a9d4ca889dd"), + "plugins/ScheduledReports/lang/lv.json" => array("654", "8c0fc54d27ed8b89892b4e9ed2c4dd5a"), + "plugins/ScheduledReports/lang/nb.json" => array("3703", "12e045b2a57d52611d8329198d3ac2e2"), + "plugins/ScheduledReports/lang/nl.json" => array("3207", "cf311d9878a84625aa6f2e668c056987"), + "plugins/ScheduledReports/lang/pl.json" => array("1998", "baee59f38f4f18c303ed898058b7a1f8"), + "plugins/ScheduledReports/lang/pt-br.json" => array("4037", "e08f31627c98077e8ede397649601048"), + "plugins/ScheduledReports/lang/pt.json" => array("2237", "903e89c5e99968dce107bc2524ffacae"), + "plugins/ScheduledReports/lang/ro.json" => array("3619", "1a9be01a75c0773bc3928f6b703e0a24"), + "plugins/ScheduledReports/lang/ru.json" => array("5035", "57ad2fd03ac17158ce5532904522ff34"), + "plugins/ScheduledReports/lang/sk.json" => array("838", "9c77d454833847609446581085c22144"), + "plugins/ScheduledReports/lang/sl.json" => array("1142", "c02b13f49ccb879cf80d063b3b00fbbc"), + "plugins/ScheduledReports/lang/sq.json" => array("2208", "6cb8a855ec4835b903ca5aa01ca9a209"), + "plugins/ScheduledReports/lang/sr.json" => array("3809", "5081b7daf22e0f1706960c7479dde823"), + "plugins/ScheduledReports/lang/sv.json" => array("3561", "a6766ee96351e434b9eef4e4e47f376d"), + "plugins/ScheduledReports/lang/ta.json" => array("337", "edf463627a0e5b6041be29dd0594f196"), + "plugins/ScheduledReports/lang/te.json" => array("224", "f9f54eb2166fd2fe22fd20fcbfd00523"), + "plugins/ScheduledReports/lang/th.json" => array("1692", "5abcb1c05cb73720124094fc9ae320c2"), + "plugins/ScheduledReports/lang/tl.json" => array("3602", "5021ee48e1bf0a716bf38b9c0f92acea"), + "plugins/ScheduledReports/lang/tr.json" => array("3421", "37c2e07355ff686ca84340233b6b06ee"), + "plugins/ScheduledReports/lang/uk.json" => array("1000", "f0ab0565835b9593193d85b19cba803c"), + "plugins/ScheduledReports/lang/vi.json" => array("4065", "344658b9e7bf03f3a160a66be6d74d65"), + "plugins/ScheduledReports/lang/zh-cn.json" => array("3276", "b51d3f3b99084093ab92302081240db4"), + "plugins/ScheduledReports/lang/zh-tw.json" => array("706", "88859a3f3b89830edb1a98301c1a5d0e"), + "plugins/ScheduledReports/Menu.php" => array("2574", "aaf18b6851c4c2b61c97c18208b74eb4"), + "plugins/ScheduledReports/Model.php" => array("2326", "f777a1aa75c08372001dbcd6b931b5e6"), + "plugins/ScheduledReports/ScheduledReports.php" => array("25886", "a59fe7ef757eaa170880932f41ac14fe"), + "plugins/ScheduledReports/stylesheets/scheduledreports.less" => array("138", "da856e2be51af7fb4f4b0d52361de00b"), + "plugins/ScheduledReports/Tasks.php" => array("872", "63f4b96bbcb9a970be79a9cd804936d7"), + "plugins/ScheduledReports/templates/_addReport.twig" => array("8379", "692bf8fa770d6a4e0d22d7ec209bd3dd"), + "plugins/ScheduledReports/templates/index.twig" => array("1918", "c701765a258c320eb092a44f22d32f0a"), + "plugins/ScheduledReports/templates/_listReports.twig" => array("4315", "2b64617f52037898a293c92088f34146"), "plugins/ScheduledReports/templates/reportParametersScheduledReports.twig" => array("3848", "4b32be0f5bb38a54ac06b276a962aae6"), - "plugins/SegmentEditor/API.php" => array("10874", "52a80f940f2bd60881732aa28d7def09"), - "plugins/SegmentEditor/Controller.php" => array("386", "f565883b4e48442253c328ab00016755"), + "plugins/SegmentEditor/API.php" => array("13087", "f1e1b35f3a8dd2cf63071910571f25e9"), + "plugins/SegmentEditor/config/config.php" => array("129", "5fa87c0b335d27e96dcd2b011cb53187"), "plugins/SegmentEditor/images/ajax-loader.gif" => array("847", "30d8e72bfdae694b1938658e1b087df0"), "plugins/SegmentEditor/images/bg-inverted-corners.png" => array("968", "b441529c920b6ce5ca36d62b08a73fa2"), "plugins/SegmentEditor/images/bg-segment-search.png" => array("1068", "938234a77114aff5541b1b9a47acf1f9"), @@ -2033,48 +4891,263 @@ class Manifest { "plugins/SegmentEditor/images/close_btn.png" => array("928", "58c02c2bf48632f89a571f8065dfedfa"), "plugins/SegmentEditor/images/close.png" => array("288", "122f9ccffeba88ccdc3eb2ef41b518bc"), "plugins/SegmentEditor/images/dashboard_h_bg_hover.png" => array("378", "41c5d393f8c12cd2cc727fada94b154a"), + "plugins/SegmentEditor/images/edit_segment.png" => array("1138", "9c4ac04dcfec6a9afa73d2f81c225a0a"), "plugins/SegmentEditor/images/icon-users.png" => array("1728", "89e68113ed647295901322eea82ff8ed"), "plugins/SegmentEditor/images/reset_search.png" => array("1021", "7e761f3444bf4edd4cd1779801c963bd"), "plugins/SegmentEditor/images/search_btn.png" => array("2825", "2f11b8a2a361aa4eb350d33048ea56c9"), "plugins/SegmentEditor/images/segment-close.png" => array("1302", "28e429768ba7f35ea4c48930848c2a26"), "plugins/SegmentEditor/images/segment-move.png" => array("1447", "5c8a0111446ce9eaf70cd0cb82211ded"), - "plugins/SegmentEditor/javascripts/Segmentation.js" => array("50044", "f8db88f49b88e433bb74ad004b81b2e1"), - "plugins/SegmentEditor/Model.php" => array("2610", "b8de12b8dcb6d85d01ce48c2c7d35f4d"), - "plugins/SegmentEditor/SegmentEditor.php" => array("3196", "88b61079a22b9489913274251b4b0539"), - "plugins/SegmentEditor/SegmentSelectorControl.php" => array("4496", "9f540610044cf02578bfdfbcf3d1edff"), - "plugins/SegmentEditor/stylesheets/segmentation.less" => array("14770", "3bccb8665591b5904b8fd1641aae90a2"), - "plugins/SegmentEditor/templates/_segmentSelector.twig" => array("9000", "46fd47d6e427123362e9f39dd2c58099"), - "plugins/SEO/API.php" => array("3604", "29f8089a50cfe5e5796c097813a92341"), - "plugins/SEO/Controller.php" => array("1195", "93cc25b095aa6bfe89d91088ee620964"), + "plugins/SegmentEditor/javascripts/Segmentation.js" => array("58430", "36fa36297a8afdf6901b7af64215b1c1"), + "plugins/SegmentEditor/lang/bg.json" => array("1969", "3ecf292ed923c0e1905a7ad5365f6d95"), + "plugins/SegmentEditor/lang/cs.json" => array("2594", "c7ac21951e8a995ae4547d2b8374afce"), + "plugins/SegmentEditor/lang/da.json" => array("2068", "a0c63eaf285e6b53c0e558a640cfd948"), + "plugins/SegmentEditor/lang/de.json" => array("2620", "b551a17f563afe63edf48e4cc4e2f216"), + "plugins/SegmentEditor/lang/el.json" => array("3964", "6d0274ff4f25b033eab1ed0d7dc05d9c"), + "plugins/SegmentEditor/lang/en.json" => array("2509", "5ce5e3ce90fa52a90b7019e3ae383846"), + "plugins/SegmentEditor/lang/es.json" => array("2290", "e82d17bf0579bc01e0fbe8fd6a6cd14e"), + "plugins/SegmentEditor/lang/et.json" => array("994", "bc1adb9ff16258a98d5d2a4852993bbd"), + "plugins/SegmentEditor/lang/fa.json" => array("1682", "c5295dcc6d0f9ff7bc3a2205975c5050"), + "plugins/SegmentEditor/lang/fi.json" => array("1680", "cd1ce69735e5d349b4d6265b179b1f8a"), + "plugins/SegmentEditor/lang/fr.json" => array("2689", "3516e7d63d8a734a539cbd755a7d387a"), + "plugins/SegmentEditor/lang/he.json" => array("138", "87bb59ad5aac18e12d4352305b1eac65"), + "plugins/SegmentEditor/lang/hi.json" => array("2583", "67cc865a83fb5abb56e4beafe1e2b31e"), + "plugins/SegmentEditor/lang/id.json" => array("1315", "e2011d85fe9463f919981caa6e2f4508"), + "plugins/SegmentEditor/lang/it.json" => array("2593", "8af4ea90e82f320a4c910c94179c30f3"), + "plugins/SegmentEditor/lang/ja.json" => array("2801", "23c4b4dee6ec28b7c0313aca28ecae50"), + "plugins/SegmentEditor/lang/lt.json" => array("60", "a032bad1e567a5a433e38c5d01823c4d"), + "plugins/SegmentEditor/lang/nb.json" => array("711", "c0de93d5afc3ecdf59aa8c5bf5bd79ac"), + "plugins/SegmentEditor/lang/nl.json" => array("2290", "0918b51b9fa209ac220e1d9a173702f3"), + "plugins/SegmentEditor/lang/pl.json" => array("646", "576c5dd4d6c04858c3c95e83b69d8547"), + "plugins/SegmentEditor/lang/pt-br.json" => array("2665", "39bd0a728763ec5a58950635f0bc73a7"), + "plugins/SegmentEditor/lang/ro.json" => array("2058", "e1bb24a50c72c9ffe0d3d9cb5d1b6393"), + "plugins/SegmentEditor/lang/ru.json" => array("1869", "f483ae42c1f41087a776c7b2dfcce102"), + "plugins/SegmentEditor/lang/sk.json" => array("131", "854b921f49ec7cccaf215c90181c4e16"), + "plugins/SegmentEditor/lang/sl.json" => array("73", "664b8f5e885759b9f29913dc11dee146"), + "plugins/SegmentEditor/lang/sr.json" => array("2139", "259d6b34fa6025188f5858a297f34989"), + "plugins/SegmentEditor/lang/sv.json" => array("2146", "b0c222607314aaf0137cde368c5047de"), + "plugins/SegmentEditor/lang/tl.json" => array("1951", "31a54f7116495d8af13aaee3fa22ff03"), + "plugins/SegmentEditor/lang/tr.json" => array("185", "6948557ebdaecde3119e7dec722cf00b"), + "plugins/SegmentEditor/lang/vi.json" => array("1475", "aa433a3c8c8efe97c8d27d53d3691068"), + "plugins/SegmentEditor/lang/zh-cn.json" => array("1194", "0a6cd9707feca364a4b4d612f3f87ff8"), + "plugins/SegmentEditor/Model.php" => array("5228", "47d5471dc65332bef99e54a6acff730e"), + "plugins/SegmentEditor/SegmentEditor.php" => array("2698", "229034aad9ea97b32c3399623201037d"), + "plugins/SegmentEditor/SegmentFormatter.php" => array("4873", "dd3d69a381e4d25690cd874dfbdb2401"), + "plugins/SegmentEditor/SegmentList.php" => array("660", "a61dbd361a5efd8ca330420b0acf541a"), + "plugins/SegmentEditor/SegmentQueryDecorator.php" => array("2154", "f091c4d9762cf20aee9bd6782d6ac842"), + "plugins/SegmentEditor/SegmentSelectorControl.php" => array("4921", "cb6d94e089431ba6a5b9690484b210af"), + "plugins/SegmentEditor/Services/StoredSegmentService.php" => array("1176", "99e5c5b08a8594f0ff8b87897b88e486"), + "plugins/SegmentEditor/stylesheets/segmentation.less" => array("16182", "a0d5729f895d5907f0b40ac68db43338"), + "plugins/SegmentEditor/templates/_segmentSelector.twig" => array("10683", "261a3f3e8540c5ecd53051ba447d5c1d"), + "plugins/SEO/API.php" => array("2091", "6d827230e8d7a5eabdf04f24c54fb1ba"), "plugins/SEO/images/majesticseo.png" => array("674", "a771319c2aa22f4b4744f76e59ec5fb3"), "plugins/SEO/images/whois.png" => array("928", "ef67a4e9689efda71625a2ef894fb700"), - "plugins/SEO/javascripts/rank.js" => array("802", "8a49f8635a4d501d73868dcfc415e4e5"), - "plugins/SEO/MajesticClient.php" => array("3132", "2973973ceba8173cab44fd0cac09f869"), - "plugins/SEO/RankChecker.php" => array("10231", "72e8c9795cb7a537ff80bee9e5890d77"), - "plugins/SEO/SEO.php" => array("1194", "bb40b45a7b5ef22684fc1370fd5553ba"), - "plugins/SEO/templates/getRank.twig" => array("2900", "a686edd3171736d456f4ec6062206819"), - "plugins/SitesManager/API.php" => array("55565", "1d1299fa3efe085855b42f4134a8b0fc"), - "plugins/SitesManager/Controller.php" => array("6317", "bac508220402a58b5c67442b641d8c09"), - "plugins/SitesManager/javascripts/SitesManager.js" => array("22790", "153a9bd5ec22c2f98791989235a5289d"), - "plugins/SitesManager/SitesManager.php" => array("7094", "4e0830e699db4d59d697efac994a8858"), - "plugins/SitesManager/stylesheets/SitesManager.less" => array("1059", "2c47402c996f5783383b5dd9bdb41048"), + "plugins/SEO/javascripts/rank.js" => array("818", "63808a20196095da1b3eed55c2f33ea9"), + "plugins/SEO/lang/ar.json" => array("303", "de4823a6371f37cb9adba7d39a28b248"), + "plugins/SEO/lang/be.json" => array("245", "e86487c0e51a146ce77d5d2ff3b3b65c"), + "plugins/SEO/lang/bg.json" => array("716", "843fe90327b61feda6733036a3bcf110"), + "plugins/SEO/lang/ca.json" => array("369", "d73c3c164dd89bf750cb9317fe132790"), + "plugins/SEO/lang/cs.json" => array("598", "4087086c642a4e0156519c9351a6166b"), + "plugins/SEO/lang/da.json" => array("362", "2b2e9eb29db7359cbce7865ec04a79db"), + "plugins/SEO/lang/de.json" => array("570", "ef941c4516cc98cb5622d68a49070eb0"), + "plugins/SEO/lang/el.json" => array("793", "1a1b127a744449aba538751030e176a3"), + "plugins/SEO/lang/en.json" => array("529", "b62ae0f0d3f92e4bd0aafcb72acd658d"), + "plugins/SEO/lang/es.json" => array("599", "be1a22181b6538c94ccf4ac07bf8eef7"), + "plugins/SEO/lang/et.json" => array("332", "7202407bb7ee67cc812199deb37a7dc9"), + "plugins/SEO/lang/fa.json" => array("464", "ef3bbfd749c448c41dd54446bfc213a0"), + "plugins/SEO/lang/fi.json" => array("357", "d6d2155d3733febc94af23a6c7682ce9"), + "plugins/SEO/lang/fr.json" => array("582", "ec429758d298a82bf47f7a94f1812ac7"), + "plugins/SEO/lang/he.json" => array("51", "5073510f41062ead5e88a925347e5130"), + "plugins/SEO/lang/hi.json" => array("1007", "a5c1a9549f79a7d009e7525e208fc6a3"), + "plugins/SEO/lang/hu.json" => array("260", "6e8ac7427fd4aec458fa49734ef7daf3"), + "plugins/SEO/lang/id.json" => array("362", "730855aa5109a5de479cd8a78152472c"), + "plugins/SEO/lang/it.json" => array("552", "10567a190422e8b9ffdc191cfeef03ac"), + "plugins/SEO/lang/ja.json" => array("648", "d6d7b786c3d683ac0ac148ae6f7f51e5"), + "plugins/SEO/lang/ka.json" => array("292", "597df00e8a846b024ecf1a3b40bfb3cc"), + "plugins/SEO/lang/ko.json" => array("582", "ac4c000cf07bc091a2bd9eb7251f3a0a"), + "plugins/SEO/lang/lt.json" => array("231", "690a0e34489d072fd0c5b00c0cd763b9"), + "plugins/SEO/lang/lv.json" => array("199", "aee4271562513439c06a1a7c0fea3cab"), + "plugins/SEO/lang/nb.json" => array("566", "32a47e709b9a5a06ef53d0706ccab394"), + "plugins/SEO/lang/nl.json" => array("554", "9605807b46a8d15e2af014d4033c9a5e"), + "plugins/SEO/lang/pl.json" => array("208", "876c984ecad0d4c2cfcd36b60387bb37"), + "plugins/SEO/lang/pt-br.json" => array("555", "c0b66c24ef4648d123077b223d4e3236"), + "plugins/SEO/lang/pt.json" => array("249", "464372e1e620cb30e93925ec70e2cd3a"), + "plugins/SEO/lang/ro.json" => array("381", "cf1ae825a24c07696a02412895f57f5c"), + "plugins/SEO/lang/ru.json" => array("462", "948d41720f82b243bcb1fe6088f36b29"), + "plugins/SEO/lang/sk.json" => array("220", "515e684909aa69a5d645134cd67f8c6e"), + "plugins/SEO/lang/sl.json" => array("332", "ab75da704cec1c78d542c41fb8367b59"), + "plugins/SEO/lang/sq.json" => array("221", "b65a052c181a47c2d4d20023818920cf"), + "plugins/SEO/lang/sr.json" => array("571", "517f2d4ad69302a6058ffeb685b5ddb5"), + "plugins/SEO/lang/sv.json" => array("347", "787e301e7dcf35cb6e6711e80f27a6cc"), + "plugins/SEO/lang/te.json" => array("130", "8a281c2dfa6f4e2dcc3e5858ec66c0cc"), + "plugins/SEO/lang/th.json" => array("297", "cfe2ac84521475629b6bf611fdc27415"), + "plugins/SEO/lang/tl.json" => array("383", "d21e2a94d98155b1a7733a28da09c8aa"), + "plugins/SEO/lang/tr.json" => array("226", "cd2c642e70b9a3e14a56d0c5076c3ee5"), + "plugins/SEO/lang/uk.json" => array("229", "778ffbc113ad96a352a6ad90b4341dab"), + "plugins/SEO/lang/vi.json" => array("424", "e66c4d8b38389daa7de73cf23d887672"), + "plugins/SEO/lang/zh-cn.json" => array("512", "ba71162ff226dfccfb19226605b00500"), + "plugins/SEO/lang/zh-tw.json" => array("211", "aeeacf894db92f5687e58ddda18b241e"), + "plugins/SEO/Metric/Aggregator.php" => array("1610", "7dfcb8851083af11976b81e180121909"), + "plugins/SEO/Metric/Alexa.php" => array("1457", "bad9fc70b86cc8648290804b6b4d3c8d"), + "plugins/SEO/Metric/Bing.php" => array("1550", "94a87eec8b697b66d975b251471d9e2a"), + "plugins/SEO/Metric/Dmoz.php" => array("1628", "35a86d4b5878fa151f3511d4cad69cc2"), + "plugins/SEO/Metric/DomainAge.php" => array("3748", "d9d27a46635dedccc04a22c3cf5ce3e8"), + "plugins/SEO/Metric/Google.php" => array("5144", "c67c209775737c359827c204ff236256"), + "plugins/SEO/Metric/Metric.php" => array("2594", "c7b1d3d18142fd1ffd9cbb132809e63e"), + "plugins/SEO/Metric/MetricsProvider.php" => array("377", "e9f1e43594708cfaca9ab95075412eba"), + "plugins/SEO/Metric/ProviderCache.php" => array("932", "7fb255339d181acbaa5f27f357e0eef3"), + "plugins/SEO/SEO.php" => array("230", "1e2706a6b02044ac609ebdd8f4b62a1e"), + "plugins/SEO/templates/getRank.twig" => array("2584", "753dd24642b9a5d31771fa5591208f62"), + "plugins/SEO/Widgets.php" => array("1325", "275e1f7a24ce53b766b9c776ba308c79"), + "plugins/SitesManager/angularjs/sites-manager/api-core.service.js" => array("741", "1131a9cc43d9683c7848ae6ea06f6cfe"), + "plugins/SitesManager/angularjs/sites-manager/api-helper.service.js" => array("2051", "2ada3913ab7512fb0a1cec6111991f08"), + "plugins/SitesManager/angularjs/sites-manager/api-site.service.js" => array("1409", "7e3086e9bd490e05a0007dd2ba9f9080"), + "plugins/SitesManager/angularjs/sites-manager/edit-trigger.directive.js" => array("767", "eb0325962d424df3291dfb535b5ac2a4"), + "plugins/SitesManager/angularjs/sites-manager/multiline-field.directive.html" => array("138", "1bd07714603c13ac08dede575488e978"), + "plugins/SitesManager/angularjs/sites-manager/multiline-field.directive.js" => array("1418", "9c6ab6cbeb40dc773ee8366b4ed43e0d"), + "plugins/SitesManager/angularjs/sites-manager/sites-manager-admin-sites-model.js" => array("3148", "efcc94e3ffd2629e6700d7fbfbc252b4"), + "plugins/SitesManager/angularjs/sites-manager/sites-manager.controller.js" => array("10214", "e634f4b6883df2e2efc6a26c93d5c0b9"), + "plugins/SitesManager/angularjs/sites-manager/sites-manager-site.controller.js" => array("7249", "1f751cb056b3ac98440eef886abeb9fe"), + "plugins/SitesManager/angularjs/sites-manager/sites-manager-type-model.js" => array("1400", "ad362878482a3ddd04c67121e9061300"), + "plugins/SitesManager/API.php" => array("56101", "208be0295749a8b97b5e5f30dbad0463"), + "plugins/SitesManager/Controller.php" => array("6386", "a09423058a3c7c80ccff48643f0dc1ea"), + "plugins/SitesManager/lang/am.json" => array("1486", "36f212cae0e47f317b1b787bcbae8bce"), + "plugins/SitesManager/lang/ar.json" => array("6061", "b5230290b25886199c11da570d6b12f3"), + "plugins/SitesManager/lang/be.json" => array("7108", "e63e3ced4717673ac5c2b93844b155f7"), + "plugins/SitesManager/lang/bg.json" => array("8582", "8e46d5c7912cd4223b22add1bebb736b"), + "plugins/SitesManager/lang/bn.json" => array("118", "120dbaa081631531efcfbd39bc28fa80"), + "plugins/SitesManager/lang/bs.json" => array("84", "d62fe6a4a6608b971b6aea2effeaf6ae"), + "plugins/SitesManager/lang/ca.json" => array("6913", "0c45f458f98e61cfc1e4626f7b7d1077"), + "plugins/SitesManager/lang/cs.json" => array("8987", "c4f3ca9e3bf72eead5427ccb7eb0ec49"), + "plugins/SitesManager/lang/cy.json" => array("68", "a9847ef3b4ee461e40f655a614c975a1"), + "plugins/SitesManager/lang/da.json" => array("7169", "7751a4e7006ef94e75f68fcc3806ebf0"), + "plugins/SitesManager/lang/de.json" => array("9102", "e7563669e60eedca4b985c86669ceb91"), + "plugins/SitesManager/lang/el.json" => array("15424", "431159db9cbf2fc1e614c3689166cdf2"), + "plugins/SitesManager/lang/en.json" => array("8480", "dfabd1722e326b9dd37b3018df98bbac"), + "plugins/SitesManager/lang/es.json" => array("8690", "663a7ab8567c941299b2f819b7e91b99"), + "plugins/SitesManager/lang/et.json" => array("1976", "ff15f29e191a05c5f4f0a69f4d778da6"), + "plugins/SitesManager/lang/eu.json" => array("1452", "f0885dd53ced5c9b6fb3d8bb462cab05"), + "plugins/SitesManager/lang/fa.json" => array("7533", "69d417927feb2082f19d226a20837804"), + "plugins/SitesManager/lang/fi.json" => array("7025", "bd5652ee51bb76ea3461334e0a8b054b"), + "plugins/SitesManager/lang/fr.json" => array("8729", "ef1d31832a19699f3010715734756ceb"), + "plugins/SitesManager/lang/gl.json" => array("694", "917fe86b1c57670acb23cfe4dc1bd228"), + "plugins/SitesManager/lang/he.json" => array("132", "b14a42cdf5fa4dbef115e2fcece32630"), + "plugins/SitesManager/lang/hi.json" => array("13887", "bbe28a4c3b5ece854ad67074a35e2927"), + "plugins/SitesManager/lang/hr.json" => array("274", "aef9ad84b218dea0492b22004f5a2ee5"), + "plugins/SitesManager/lang/hu.json" => array("4501", "66040f051d7617851dbd9b7b71df7483"), + "plugins/SitesManager/lang/id.json" => array("7059", "2fe54619e6e55e0c11bfc26a7f159b4b"), + "plugins/SitesManager/lang/is.json" => array("447", "76a1b6e1eac4473756b39e88e52f234a"), + "plugins/SitesManager/lang/it.json" => array("8545", "b4798b9eebb53120748475950d6d9a36"), + "plugins/SitesManager/lang/ja.json" => array("10389", "d4d4b1d4cd7a694b17eb7a0b82e58ff0"), + "plugins/SitesManager/lang/ka.json" => array("8684", "5a7cbe257e41fb20e70d9c8888a8a030"), + "plugins/SitesManager/lang/ko.json" => array("9443", "1f90c9483520d75f237dbb6efd19d12b"), + "plugins/SitesManager/lang/lt.json" => array("4031", "f4e6ef62637daaaadf27f4291e72c7b0"), + "plugins/SitesManager/lang/lv.json" => array("2925", "3e75617ab4d21ea49b8f6cb625382f55"), + "plugins/SitesManager/lang/nb.json" => array("7900", "75ac2a3a52f1f2c64196c00d3d7ae335"), + "plugins/SitesManager/lang/nl.json" => array("6593", "2cd7f16bc3782ab56b46ebf085047531"), + "plugins/SitesManager/lang/nn.json" => array("1748", "25371bf5dbcb7d642a69b92e49527202"), + "plugins/SitesManager/lang/pl.json" => array("4943", "16772f0c955bc106c4806d89e820cd6c"), + "plugins/SitesManager/lang/pt-br.json" => array("9296", "148a69513cb17811f631c0f777c0120f"), + "plugins/SitesManager/lang/pt.json" => array("4941", "bc5a430d5d1b7f9c99b5d6e1be7d7356"), + "plugins/SitesManager/lang/ro.json" => array("7847", "6fdf4bf4df0b93d547dab77c96093cc9"), + "plugins/SitesManager/lang/ru.json" => array("11726", "fecbbf83188a6ad2cdf280878200de99"), + "plugins/SitesManager/lang/sk.json" => array("1144", "d996cc9792e837b953beab528a29faaf"), + "plugins/SitesManager/lang/sl.json" => array("2187", "223abd60fa8a3ea2fe6fd9afd8699017"), + "plugins/SitesManager/lang/sq.json" => array("7243", "90decf712ee0230def4dba81eca2053a"), + "plugins/SitesManager/lang/sr.json" => array("8068", "f245eda5b504562367011d047cf0655d"), + "plugins/SitesManager/lang/sv.json" => array("8192", "024df9cb6630f5491e90f24fe6bf92f0"), + "plugins/SitesManager/lang/ta.json" => array("127", "5f9c30b2a49b0d4f836a1548782d4769"), + "plugins/SitesManager/lang/te.json" => array("303", "bb38cf75220b9cd0912f83c59b90438e"), + "plugins/SitesManager/lang/th.json" => array("7613", "a6d01b8dbfb48c47cf02a8e8046994af"), + "plugins/SitesManager/lang/tl.json" => array("7827", "5f8f02a32e3a1eb71ebce7611ae1b3aa"), + "plugins/SitesManager/lang/tr.json" => array("2195", "9026c6b17bb3941c5398dae41518e0de"), + "plugins/SitesManager/lang/uk.json" => array("5986", "04cf77c7f67702f5801b288e4f2e6c2e"), + "plugins/SitesManager/lang/vi.json" => array("8653", "713e75f7e3ec471660ccaad77c5027f5"), + "plugins/SitesManager/lang/zh-cn.json" => array("6010", "62a4bc3c975d11d35ffa1bd31cdd2560"), + "plugins/SitesManager/lang/zh-tw.json" => array("3541", "288c1c1273963548908ab8aaaf45c31f"), + "plugins/SitesManager/Menu.php" => array("1564", "ae8a759ae208e0952673d700297cdfb0"), + "plugins/SitesManager/Model.php" => array("11891", "def7b0b0a0cce1e31de2d334ddaa256e"), + "plugins/SitesManager/SitesManager.php" => array("14692", "8d6bd3a1f7473d0938e63aa177f4409f"), + "plugins/SitesManager/SiteUrls.php" => array("5727", "ef055d33644c1805646f28cca87151d3"), + "plugins/SitesManager/stylesheets/SitesManager.less" => array("2376", "dcb0347253a14600120146e9e147b2f7"), + "plugins/SitesManager/templates/dialogs/dialogs.html" => array("169", "7f31f2ae1a6d9957ee957c18fd595748"), + "plugins/SitesManager/templates/dialogs/edit-dialog.html" => array("185", "a1311883efc1468013ad6a276be3953b"), + "plugins/SitesManager/templates/dialogs/remove-dialog.html" => array("286", "df63b9966b7f91e850d01cf169106cac"), "plugins/SitesManager/templates/displayJavascriptCode.twig" => array("121", "ab4d91574f554b6e4ff05cae26373902"), - "plugins/SitesManager/templates/_displayJavascriptCode.twig" => array("731", "cc93bf9bffdbd5efc0ff98a0bee83e86"), - "plugins/SitesManager/templates/index.twig" => array("19864", "71b4a9079d4e90951e1b83571f72a64f"), - "plugins/Transitions/API.php" => array("25342", "1d0bf029a35fa8cad6444271571d8388"), - "plugins/Transitions/Controller.php" => array("3778", "f926b1496c2db36deb6f7f2ee1d48e36"), + "plugins/SitesManager/templates/_displayJavascriptCode.twig" => array("742", "97d7f49f49c8d08f90292ccd870d45e6"), + "plugins/SitesManager/templates/global-settings.html" => array("4853", "4e0c44e362de36f6d72d291c5a811f12"), + "plugins/SitesManager/templates/help/excluded-ip-help.html" => array("240", "148a2b037fdc6f2171956b32260e88c5"), + "plugins/SitesManager/templates/help/excluded-query-parameters-help.html" => array("230", "9dd06435c7171f949b197dd39a7ef3f7"), + "plugins/SitesManager/templates/help/excluded-user-agents-help.html" => array("241", "7679168a9072470f7a3008667ead635a"), + "plugins/SitesManager/templates/help/timezone-help.html" => array("534", "c0603d04709369ca3f61d56bb4c104c0"), + "plugins/SitesManager/templates/index.html" => array("879", "cc23f0d33cfc39d0c10a9ca1dd4464bd"), + "plugins/SitesManager/templates/index.twig" => array("237", "d6f90ea01c3c4dcee0c21d2b9387a1e5"), + "plugins/SitesManager/templates/loading.html" => array("267", "1bc83652de333a8c6c69ccf806d5f101"), + "plugins/SitesManager/templates/measurable_type_settings.twig" => array("196", "c1d747512209de1a85e8fd7c76749173"), + "plugins/SitesManager/templates/sites-list/add-entity-dialog.html" => array("718", "c78f02bba8e5b5427cb19acc45671dcc"), + "plugins/SitesManager/templates/sites-list/add-site-link.html" => array("1795", "3af8acdd8005abe8651586f260cc754a"), + "plugins/SitesManager/templates/sites-list/site-fields.html" => array("7448", "41af2f047263099a353508bafb8764ef"), + "plugins/SitesManager/templates/sites-list/site-search-field.html" => array("2446", "8ed6d30747ad0f30686a980ef727f67a"), + "plugins/SitesManager/templates/sites-list/sites-list.html" => array("580", "884f853d72b92e4e572ade62df8febc8"), + "plugins/SitesManager/templates/sites-manager-header.html" => array("739", "22852a615d0e43cb810d35915559f5ab"), + "plugins/SitesManager/templates/siteWithoutData.twig" => array("1521", "a1ae84f720a36eb3e763f9232dced1cb"), + "plugins/SitesManager/Tracker/SitesManagerRequestProcessor.php" => array("2515", "8f75aaf544c4fd51e21ee042fbf4c679"), + "plugins/Transitions/API.php" => array("25718", "d68bb5c44a87a35beb45af581450fcc9"), + "plugins/Transitions/Controller.php" => array("3782", "fcd68506281bcbb0ccf3cd5986b66611"), "plugins/Transitions/images/transitions_icon_hover.png" => array("647", "2ec41fe6df63da14f7c187b067b94cc9"), "plugins/Transitions/images/transitions_icon.png" => array("643", "fbf901428436a1ba3bbcc5c3081b6f24"), - "plugins/Transitions/javascripts/transitions.js" => array("56161", "97aee28fdcf5a08f9ca8cc9e744c3eb8"), - "plugins/Transitions/stylesheets/_transitionColors.less" => array("1677", "4c6eb4b6425faabe6d2eb4e1821904ef"), - "plugins/Transitions/stylesheets/transitions.less" => array("3702", "b58c35379c79ce4060216479e0310c2f"), + "plugins/Transitions/javascripts/transitions.js" => array("57413", "cb7ca73a1f44e4fd9eee60ee3b9419c4"), + "plugins/Transitions/lang/bg.json" => array("1622", "926261ec980e03506aa55c4100a5d6cf"), + "plugins/Transitions/lang/ca.json" => array("1337", "d0267f9c934c24ba8ef5d95cb496e8d0"), + "plugins/Transitions/lang/cs.json" => array("1546", "af0bee5caaf1ae79b72e89672bc9f266"), + "plugins/Transitions/lang/da.json" => array("1346", "e476282900d8787f8549b1ce4105e75c"), + "plugins/Transitions/lang/de.json" => array("1571", "e02e9c83c3317c518ca4a37072e2c611"), + "plugins/Transitions/lang/el.json" => array("2253", "2c4d48d060a26d6339ed117f8b0be981"), + "plugins/Transitions/lang/en.json" => array("1469", "fa5ea3dfbf32d9fdbb11910092046e7f"), + "plugins/Transitions/lang/es.json" => array("1597", "d342fcae3dc8fbc06ef7aae9107adb47"), + "plugins/Transitions/lang/et.json" => array("1218", "ad19775e0e8dd8a7f7dd4c037fad0e8f"), + "plugins/Transitions/lang/fa.json" => array("1474", "04aecb3bbf1e98d35dc94ff7a0435ad6"), + "plugins/Transitions/lang/fi.json" => array("1351", "131e353f5c2aef6d30ad8d9bc658d6ce"), + "plugins/Transitions/lang/fr.json" => array("1627", "3090f3efed5aaa701c307d4885286007"), + "plugins/Transitions/lang/hi.json" => array("1986", "1061f4a650baf71b14c3f1b0688df286"), + "plugins/Transitions/lang/id.json" => array("1364", "ae6f9b163163fecff43bc9c4dd2f0dcb"), + "plugins/Transitions/lang/it.json" => array("1519", "a0f70813bca9daefeceeda3555a41434"), + "plugins/Transitions/lang/ja.json" => array("1658", "188636e33b73c8ca3d53dbc8e4ac7689"), + "plugins/Transitions/lang/ko.json" => array("1588", "8c9e8933fac03d148b0522e1fd331ace"), + "plugins/Transitions/lang/lt.json" => array("91", "bd63b630d828acfa66e88f5bbd8816a4"), + "plugins/Transitions/lang/nb.json" => array("1495", "bc4e7a1668e84b1dfeae15ac2f1afb3f"), + "plugins/Transitions/lang/nl.json" => array("1542", "93986cbf2169f0bd3d472b30402dbb40"), + "plugins/Transitions/lang/pl.json" => array("625", "ab4416854faf53567f7a464cd0a36a1d"), + "plugins/Transitions/lang/pt-br.json" => array("1590", "7daedb026521640c54da7c1f53157ba7"), + "plugins/Transitions/lang/ro.json" => array("1422", "942dbf654d122aac487a8f1143c14bb9"), + "plugins/Transitions/lang/ru.json" => array("1803", "3be0be5b403b09765408819d28adcf1a"), + "plugins/Transitions/lang/sl.json" => array("789", "3731f5b31b1e801e69047b0f126ff7ea"), + "plugins/Transitions/lang/sr.json" => array("1420", "5e8d3ab9f787c1e6dc89b3c745040aaf"), + "plugins/Transitions/lang/sv.json" => array("1365", "efef60fe66d17dde1e38c8a11ba82d8c"), + "plugins/Transitions/lang/th.json" => array("240", "44265d1594c5ef469b39fec39889c0a4"), + "plugins/Transitions/lang/tl.json" => array("1442", "172803664a2819d2afa20d4f76524848"), + "plugins/Transitions/lang/tr.json" => array("86", "44b9124a4f1dc5c0331871d904c4926c"), + "plugins/Transitions/lang/vi.json" => array("1581", "877ccb46f8ee65dca0f3634bdcd15c8a"), + "plugins/Transitions/lang/zh-cn.json" => array("1413", "c5809e6f23a7312f758537e0dd109174"), + "plugins/Transitions/stylesheets/_transitionColors.less" => array("1675", "5505d5f0b87669aff5031329217a886b"), + "plugins/Transitions/stylesheets/transitions.less" => array("3712", "e37a4e328945fe0358862fcc0ba9d742"), "plugins/Transitions/templates/renderPopover.twig" => array("2576", "81bba58121f174ab9edf66c6560f1815"), - "plugins/Transitions/Transitions.php" => array("1125", "efe0685071d15cd7ce16c94116ed71bc"), - "plugins/UserCountry/API.php" => array("8863", "a79d65ab6a040442be62b43d687e01cb"), - "plugins/UserCountry/Archiver.php" => array("6221", "191beb3c68d3dddc8a3407f0e4c33594"), - "plugins/UserCountry/Controller.php" => array("14875", "3b186fb085a8147c30078f722f7bccfc"), - "plugins/UserCountry/functions.php" => array("5232", "de661ba6505b4d27286e6e8543295d65"), - "plugins/UserCountry/GeoIPAutoUpdater.php" => array("23171", "102290b02300a18c821aa1b46ef294d5"), + "plugins/Transitions/Transitions.php" => array("1113", "2916d6fd161bb8b881597667ccbabe5c"), + "plugins/UserCountry/API.php" => array("9154", "0fdd794cf2990d991b664cd002908cca"), + "plugins/UserCountry/Archiver.php" => array("6582", "7cacc1e82a4df5be2b9a36ea2a3e2ff9"), + "plugins/UserCountry/Columns/Base.php" => array("2483", "90698596f0dce9b56ee99927d9a68bc3"), + "plugins/UserCountry/Columns/City.php" => array("1993", "68c489b47e02f3948d79d58ae63deab4"), + "plugins/UserCountry/Columns/Continent.php" => array("386", "2f3b66902c9e183014455826e1c39ad2"), + "plugins/UserCountry/Columns/Country.php" => array("4594", "2819bc14f193d92b1eb94f9225fb824a"), + "plugins/UserCountry/Columns/Latitude.php" => array("2140", "94b80ed029f3c8587037b5777245d5f8"), + "plugins/UserCountry/Columns/Longitude.php" => array("2045", "d035e537179cad24291df586384a0c62"), + "plugins/UserCountry/Columns/Provider.php" => array("1460", "307308bf7c810ecc28c47eb331ee5ea0"), + "plugins/UserCountry/Columns/Region.php" => array("2027", "709a8d32a4e1f1f9fcfe9a30355f228e"), + "plugins/UserCountry/Commands/AttributeHistoricalDataWithLocations.php" => array("7120", "833ccd4390615b3893a50a699f34bfb6"), + "plugins/UserCountry/config/config.php" => array("154", "15ae36c439bf4fe91a5676847723c74d"), + "plugins/UserCountry/Controller.php" => array("14198", "b57147c18111eca0abd0b5687656fa29"), + "plugins/UserCountry/Diagnostic/GeolocationDiagnostic.php" => array("2438", "f440781fa310b2b736270a80553d2a08"), + "plugins/UserCountry/functions.php" => array("5585", "8549c101ea50e8abd5d5d83820a9c1c3"), + "plugins/UserCountry/GeoIPAutoUpdater.php" => array("23487", "410a4b6b63485c59ff2c95bd0cbd2104"), "plugins/UserCountry/images/flags/a1.png" => array("290", "f1c31042bade52bcf13d8bd778fcf37c"), "plugins/UserCountry/images/flags/a2.png" => array("290", "f1c31042bade52bcf13d8bd778fcf37c"), "plugins/UserCountry/images/flags/ac.png" => array("545", "373b8f3d6c92338b228a160e2a0d87fc"), @@ -2349,27 +5422,123 @@ class Manifest { "plugins/UserCountry/images/flags/zm.png" => array("359", "96b272c89f9775f2a8aca88497d660ae"), "plugins/UserCountry/images/flags/zr.png" => array("380", "7bd1bcc513aac082fa6f6292b72edc0c"), "plugins/UserCountry/images/flags/zw.png" => array("462", "29cc6d7fa4fb68eb909da5b1ee142bdd"), - "plugins/UserCountry/javascripts/userCountry.js" => array("8251", "cba3f6cb36fe9e75404d48a96ec2349b"), - "plugins/UserCountry/LocationProvider/Default.php" => array("3251", "31708eccdddce97672027891f9a6888b"), - "plugins/UserCountry/LocationProvider/GeoIp/Pecl.php" => array("11193", "e705086e0d6a9ed88a0571e58c7ee291"), - "plugins/UserCountry/LocationProvider/GeoIp.php" => array("8983", "f2c5d51c4d954ba83a6b79f52142e270"), - "plugins/UserCountry/LocationProvider/GeoIp/Php.php" => array("13353", "524c43039566f77e0bb99b97d0d89408"), - "plugins/UserCountry/LocationProvider/GeoIp/ServerBased.php" => array("10066", "cd4ab8bff2cfc6a1de0b660622771679"), - "plugins/UserCountry/LocationProvider.php" => array("16287", "a884a71eb8a2c99dd5e856d0c0120710"), - "plugins/UserCountryMap/Controller.php" => array("11481", "c1083ef590a08229977f908487188697"), - "plugins/UserCountryMap/images/cities.png" => array("267", "05566bd830e4a3225fa746eafc2534b1"), + "plugins/UserCountry/javascripts/userCountry.js" => array("8267", "cb3c3cb212c03e00536cbebc888deeb5"), + "plugins/UserCountry/lang/am.json" => array("209", "4fe1372aa4aa41e3aeb59dd6ae925542"), + "plugins/UserCountry/lang/ar.json" => array("2228", "da0167056dfeadaab40ed609fa247b80"), + "plugins/UserCountry/lang/be.json" => array("529", "688c6bbec8c9080f2444764fa1f856bd"), + "plugins/UserCountry/lang/bg.json" => array("12884", "0e15b91460d0d9069a41cfeeba92bb87"), + "plugins/UserCountry/lang/ca.json" => array("10390", "752a8be8d7826cd7b4a92eb1049a176f"), + "plugins/UserCountry/lang/cs.json" => array("10898", "b7f79cfca0a3e183393a23c313c56e50"), + "plugins/UserCountry/lang/da.json" => array("10550", "3be401c0b5a90efebbfc55bd2e6b0eac"), + "plugins/UserCountry/lang/de.json" => array("11891", "bccb6abd9a42a1ed0a8b686ab2b22ee4"), + "plugins/UserCountry/lang/el.json" => array("18727", "e48cc40b72bd5da77cae6eaa83b2a92d"), + "plugins/UserCountry/lang/en.json" => array("10458", "7052b8b6a0ea19839743b696d65f1502"), + "plugins/UserCountry/lang/es.json" => array("11542", "9f9bba21fef8e16700602a2af8393888"), + "plugins/UserCountry/lang/et.json" => array("1608", "833789878b781618e3ed2fdcd4629085"), + "plugins/UserCountry/lang/eu.json" => array("195", "0d2f93219b13b870cb96d505a621e928"), + "plugins/UserCountry/lang/fa.json" => array("4712", "f59aa17d52e23804715af4ad162708cd"), + "plugins/UserCountry/lang/fi.json" => array("10486", "e640144b64c01495a57d7a2336b1d8b3"), + "plugins/UserCountry/lang/fr.json" => array("12210", "88c822fc759f119ef06df1d9ce22aecb"), + "plugins/UserCountry/lang/gl.json" => array("868", "ce930a5574ebee9a475869836167c9dd"), + "plugins/UserCountry/lang/he.json" => array("510", "2d92b7104ca95bde8dbe89dd577469d9"), + "plugins/UserCountry/lang/hi.json" => array("17407", "af0a076e360fe64e3feb8ac638226e38"), + "plugins/UserCountry/lang/hr.json" => array("323", "9898a220a28b06b9eb2dd9603ceadc3f"), + "plugins/UserCountry/lang/hu.json" => array("392", "2872053a604b9664d62959c88adf12f0"), + "plugins/UserCountry/lang/id.json" => array("9783", "e3b86c4279c11462f14e9062d1d21a9e"), + "plugins/UserCountry/lang/is.json" => array("353", "d606b0b36de35ae12dc63e3d8a278f95"), + "plugins/UserCountry/lang/it.json" => array("11524", "60978bdf77c20037eb4b419ba5b40e77"), + "plugins/UserCountry/lang/ja.json" => array("14056", "4a6413aac4a31b296610c17e526dcaff"), + "plugins/UserCountry/lang/ka.json" => array("566", "de4b81622c4cb07e3c9da2f46af7b641"), + "plugins/UserCountry/lang/ko.json" => array("12182", "dc31f65812ea48ca261943134e944dd4"), + "plugins/UserCountry/lang/lt.json" => array("776", "cf0b9ac7158294dd828bc11d40fcd957"), + "plugins/UserCountry/lang/lv.json" => array("370", "e0a637cbc85dd855cba3c41bc66c57fd"), + "plugins/UserCountry/lang/nb.json" => array("1954", "fb3a0e91c6d6a79115dfb7dcdfbf0e99"), + "plugins/UserCountry/lang/nl.json" => array("7645", "155a5fb86f41059788b27cab3a4b31b5"), + "plugins/UserCountry/lang/nn.json" => array("376", "037dc5fcc10f8c3275effbd4008350f1"), + "plugins/UserCountry/lang/pl.json" => array("5433", "9f8794a7b14a34a0a414f6e179c6b417"), + "plugins/UserCountry/lang/pt-br.json" => array("11585", "9b864ebc8f5fad3f366a45df330c4375"), + "plugins/UserCountry/lang/pt.json" => array("405", "8243fce35e7a3cd627c77a75eba2d100"), + "plugins/UserCountry/lang/ro.json" => array("11165", "1859ea0135e6c3434c15b35a2c6eed4a"), + "plugins/UserCountry/lang/ru.json" => array("15412", "2469ffe73d14bfc3074e27b7835729c7"), + "plugins/UserCountry/lang/sk.json" => array("325", "9d0fce406a63ebfad432f05fbc3cfac9"), + "plugins/UserCountry/lang/sl.json" => array("488", "632d0e5632b77400e892d8d90d04eb7e"), + "plugins/UserCountry/lang/sq.json" => array("390", "fe9617663560bb393d4f6a0c30afff2d"), + "plugins/UserCountry/lang/sr.json" => array("10318", "4c482db8ad7eb31bfff7e843b5016f60"), + "plugins/UserCountry/lang/sv.json" => array("10883", "827c3c0f1b20ac88c3913d082d629a74"), + "plugins/UserCountry/lang/ta.json" => array("282", "93670005ee2ef3c0bc3784d4bc4d3142"), + "plugins/UserCountry/lang/te.json" => array("395", "b23c84f0d9b2124cdb4b530bfeef4e78"), + "plugins/UserCountry/lang/th.json" => array("1085", "e2ebc562abf5d6bda48bee8bbcd0f673"), + "plugins/UserCountry/lang/tl.json" => array("10338", "312a953f8d84dba5d1cb9b90100438ea"), + "plugins/UserCountry/lang/tr.json" => array("561", "624f86102cc1566053ccd30e94f3f898"), + "plugins/UserCountry/lang/uk.json" => array("467", "c83c72d7ec724a545f5fe9407fc18f05"), + "plugins/UserCountry/lang/vi.json" => array("12709", "7e077aa291b29e4e577cb98b8cb6d7e1"), + "plugins/UserCountry/lang/zh-cn.json" => array("9201", "d892ed415e60d4a37b3eb78e9b832de9"), + "plugins/UserCountry/lang/zh-tw.json" => array("271", "70ee304be66e431549262d5db6dc49c8"), + "plugins/UserCountry/LocationProvider/DefaultProvider.php" => array("3273", "debc74a9a4ad8a478c8e85ba0f365131"), + "plugins/UserCountry/LocationProvider/GeoIp/Pecl.php" => array("11216", "a3615106f9cffc1428b7909f4e6bfec0"), + "plugins/UserCountry/LocationProvider/GeoIp.php" => array("8987", "7d6ed9d777eff0ab7ed2682185fc39e1"), + "plugins/UserCountry/LocationProvider/GeoIp/Php.php" => array("14620", "2453b05419c7256257873efd93bd54f4"), + "plugins/UserCountry/LocationProvider/GeoIp/ServerBased.php" => array("10430", "466b505acf078a26ddbd496373292fd8"), + "plugins/UserCountry/LocationProvider.php" => array("16366", "4ca54d7b5d3e50c48f076e08998dc705"), + "plugins/UserCountryMap/Controller.php" => array("13061", "dfa6f8cfb6fa9ede9039b23a1cc4d0d0"), + "plugins/UserCountryMap/images/cities.png" => array("1038", "7eaa39f8e0021507b8685e3e0b2c87e9"), "plugins/UserCountryMap/images/realtimemap-loading.gif" => array("308", "a41ca826560fe6eaeb46dd69b6d9dba2"), - "plugins/UserCountryMap/images/regions.png" => array("296", "98c1643253f8198b91297c5db80be6b0"), - "plugins/UserCountryMap/images/zoom-out-disabled.png" => array("270", "153e42ba1158ec475aa0e7e0f67acaac"), - "plugins/UserCountryMap/javascripts/realtime-map.js" => array("27190", "de5fbfd35a558cc047e3491a7f4f2e08"), - "plugins/UserCountryMap/javascripts/vendor/chroma.min.js" => array("25434", "33d7d9ebf37530751174e8ac63d83c95"), + "plugins/UserCountryMap/images/regions.png" => array("1265", "1fdee0b6664804d6525dfbe12931b922"), + "plugins/UserCountryMap/images/zoom-out-disabled.png" => array("1297", "81d56e2c732e3ed4bc1a1c7d94632e7b"), + "plugins/UserCountryMap/javascripts/realtime-map.js" => array("28098", "d13861676e7eb84433694494cbbb3af2"), "plugins/UserCountryMap/javascripts/vendor/jquery.qtip.min.js" => array("24441", "9b50d81ac4cb3194777a05429abb39d3"), - "plugins/UserCountryMap/javascripts/vendor/kartograph.min.js" => array("67544", "457051a2a34be85bbc921d0fdcb94351"), + "plugins/UserCountryMap/javascripts/vendor/kartograph.min.js" => array("67046", "25dd1ee7ea9be84160d1cee89ffe7d5d"), "plugins/UserCountryMap/javascripts/vendor/raphael.min.js" => array("90648", "3af49700d08ae8f43d613218eec1f754"), - "plugins/UserCountryMap/javascripts/visitor-map.js" => array("71971", "d9d908d5c59527221ccd4098fcbcea48"), - "plugins/UserCountryMap/stylesheets/map.css" => array("1504", "843c9412e98b9da0ece4b8533017ddd8"), - "plugins/UserCountryMap/stylesheets/realtime-map.less" => array("3192", "c367e7663058456db63ffdcac5af6fea"), - "plugins/UserCountryMap/stylesheets/visitor-map.less" => array("4529", "659ac501b37cd5b2ee9dbdf78829f8ed"), + "plugins/UserCountryMap/javascripts/visitor-map.js" => array("74657", "1689420292b24bceffb6c7deaf9bfa55"), + "plugins/UserCountryMap/lang/ar.json" => array("61", "0487964b7483feb043b25762c5c3f072"), + "plugins/UserCountryMap/lang/be.json" => array("61", "dd4263fdd6cc1caf9a3cb571687b8aa4"), + "plugins/UserCountryMap/lang/bg.json" => array("1090", "9d2e775c7af3aaecb97ce860f1426e9d"), + "plugins/UserCountryMap/lang/ca.json" => array("55", "e1103b85f2500b0409fef3e1555b24c6"), + "plugins/UserCountryMap/lang/cs.json" => array("1053", "1ccff2afb609d419fba82b1cf1aafcda"), + "plugins/UserCountryMap/lang/da.json" => array("850", "99ea8e23b82e8f4876f9e57bd8d1d0db"), + "plugins/UserCountryMap/lang/de.json" => array("1047", "72b138467a631973f0799ee1ab7828f8"), + "plugins/UserCountryMap/lang/el.json" => array("1556", "e4eb04f2f0729a7c7ec7c861ac75017a"), + "plugins/UserCountryMap/lang/en.json" => array("976", "97d903ef5ea29aeec00be157aa595d11"), + "plugins/UserCountryMap/lang/es.json" => array("1072", "ce15e88373b52fa3021958daa5f1f2bd"), + "plugins/UserCountryMap/lang/et.json" => array("700", "9bb7503698ea74afb0f4c88bdc7cf6bd"), + "plugins/UserCountryMap/lang/fa.json" => array("770", "0e3e4c0d8de72d0cb8a290c202589f05"), + "plugins/UserCountryMap/lang/fi.json" => array("786", "3690e874d2e989d29160aaa9ed4f2218"), + "plugins/UserCountryMap/lang/fr.json" => array("1063", "f8cb6faee400e22bea30e9eadd4d42cc"), + "plugins/UserCountryMap/lang/he.json" => array("257", "932f6202a5d3b6be73fc6325618d5e15"), + "plugins/UserCountryMap/lang/hi.json" => array("1460", "3e9ed24df39e30f8f10bdd46125b71d0"), + "plugins/UserCountryMap/lang/hr.json" => array("75", "dec57d727e509b3fd6980cac1e2657ba"), + "plugins/UserCountryMap/lang/hu.json" => array("59", "86b3d3d6f7163c4075149e89b19cb28f"), + "plugins/UserCountryMap/lang/id.json" => array("1026", "5ba82b93951526dc1cfc796289bba212"), + "plugins/UserCountryMap/lang/it.json" => array("1029", "58be5179a4e7c3cead5e9edcf0c19f38"), + "plugins/UserCountryMap/lang/ja.json" => array("1139", "6b8ae0fafe98835b883d46be0fc33f4d"), + "plugins/UserCountryMap/lang/ka.json" => array("63", "f951e48941a86f2ce75027addc276681"), + "plugins/UserCountryMap/lang/ko.json" => array("1048", "0f00191b51d265026705193d8a903bc7"), + "plugins/UserCountryMap/lang/lt.json" => array("235", "3a8fb4db724569c968fd1d19b4ee358d"), + "plugins/UserCountryMap/lang/lv.json" => array("56", "c7647191700c173f1ceb998e12d933a5"), + "plugins/UserCountryMap/lang/nb.json" => array("1001", "d1dad7c76cc07320665fbb1cd7af661e"), + "plugins/UserCountryMap/lang/nl.json" => array("1064", "b5bbc4bbeee4725f642dbf8be03965c9"), + "plugins/UserCountryMap/lang/pl.json" => array("661", "127a61efb09654d880c6084495ffe805"), + "plugins/UserCountryMap/lang/pt-br.json" => array("1040", "b12e2a9b6f3480705e4c67298327d973"), + "plugins/UserCountryMap/lang/pt.json" => array("55", "e1103b85f2500b0409fef3e1555b24c6"), + "plugins/UserCountryMap/lang/ro.json" => array("775", "d89c75bde81f6bc507943f76d0448cb1"), + "plugins/UserCountryMap/lang/ru.json" => array("1147", "45920ebd58f478e13e6320132a0008d4"), + "plugins/UserCountryMap/lang/sk.json" => array("299", "576950d21eb3a97e131e6c2ffb19d6cd"), + "plugins/UserCountryMap/lang/sl.json" => array("222", "522a0ab2237f8310eac6784cb398040f"), + "plugins/UserCountryMap/lang/sq.json" => array("57", "21286ba5f979c7aef7424078910869c6"), + "plugins/UserCountryMap/lang/sr.json" => array("987", "778b30fb4645224ce50fc44b9ea57412"), + "plugins/UserCountryMap/lang/sv.json" => array("948", "bffa47fd41329547ff352d72c128a733"), + "plugins/UserCountryMap/lang/te.json" => array("60", "ca24573a8e35e07e8e62829533521e30"), + "plugins/UserCountryMap/lang/th.json" => array("69", "175502debba30f502be3c7baa7c647a2"), + "plugins/UserCountryMap/lang/tl.json" => array("932", "515929ed758ce6597a659232eaeade6b"), + "plugins/UserCountryMap/lang/tr.json" => array("103", "121a79353e2bfd8ac7ca0f62fd0d57e0"), + "plugins/UserCountryMap/lang/uk.json" => array("61", "dd4263fdd6cc1caf9a3cb571687b8aa4"), + "plugins/UserCountryMap/lang/vi.json" => array("917", "0c3ecbd95d01a4bf71ae6e6d8a8b2997"), + "plugins/UserCountryMap/lang/zh-cn.json" => array("714", "6b9e57bf513673a399a47416ba2c4c73"), + "plugins/UserCountryMap/lang/zh-tw.json" => array("57", "36988d75192ecdfe4fea0421209dcac5"), + "plugins/UserCountryMap/Menu.php" => array("667", "544c906d2c0506c785ec21051f7949b3"), + "plugins/UserCountryMap/stylesheets/map.css" => array("1503", "3dde855d124691ccd8b31b6e2d741f39"), + "plugins/UserCountryMap/stylesheets/realtime-map.less" => array("3391", "2a16f028ef313a3d7243a29134eba270"), + "plugins/UserCountryMap/stylesheets/visitor-map.less" => array("4974", "19cadbdd00b7731a37ddbfe16b4d7c8c"), "plugins/UserCountryMap/svg/AFG.svg" => array("23731", "2093e033509062e7928f7917fa7a33c5"), "plugins/UserCountryMap/svg/AF.svg" => array("40727", "4bb303e7ee6eb2b29caec82db8843123"), "plugins/UserCountryMap/svg/AGO.svg" => array("11968", "820cf71105c9c3c1339ba3740ccc9ea8"), @@ -2550,393 +5719,675 @@ class Manifest { "plugins/UserCountryMap/svg/ZAF.svg" => array("10216", "ea4566c8b873e02a443efde301ebafd3"), "plugins/UserCountryMap/svg/ZMB.svg" => array("11277", "e79d992f232139d9bb4c971c57fd1803"), "plugins/UserCountryMap/svg/ZWE.svg" => array("10120", "00eb861cd623329b917a94aacaf09b97"), - "plugins/UserCountryMap/templates/realtimeMap.twig" => array("1455", "aa4b22cc760a6dcc5690a6420aaafce8"), - "plugins/UserCountryMap/templates/visitorMap.twig" => array("5543", "0b26ebd13591d082358483483427eaf6"), - "plugins/UserCountryMap/UserCountryMap.php" => array("2953", "6cf2a6f2c7e90fbbcfae3edd21419454"), + "plugins/UserCountryMap/templates/realtimeMap.twig" => array("1454", "2cb85cc3414ff5ba3e42a369cadc5606"), + "plugins/UserCountryMap/templates/visitorMap.twig" => array("5185", "ba8626133460e538cfc1b54fec23c808"), + "plugins/UserCountryMap/UserCountryMap.php" => array("2749", "86d7911e914a5461afdedbcab8b5cd94"), + "plugins/UserCountry/Menu.php" => array("830", "37abf12815a42db274d30881c5ede89f"), + "plugins/UserCountry/Reports/Base.php" => array("2602", "2b708cab0863d1e3ce3240f33d489c02"), + "plugins/UserCountry/Reports/GetCity.php" => array("1312", "dad364dd1108e7e2bddf12b32c9bb8ea"), + "plugins/UserCountry/Reports/GetContinent.php" => array("1403", "152c4a7afa114c6afdf8fd45c745928a"), + "plugins/UserCountry/Reports/GetCountry.php" => array("1822", "c6834895f42b93ab6b268f96d41b5504"), + "plugins/UserCountry/Reports/GetRegion.php" => array("1324", "cb75b052f580ad70802963f82249b733"), + "plugins/UserCountry/Segment.php" => array("376", "91525bd815987da3760803330424db5b"), "plugins/UserCountry/stylesheets/userCountry.less" => array("1122", "17952f305542ff7aa15daf21f7d1deda"), - "plugins/UserCountry/templates/adminIndex.twig" => array("6899", "4ec9965bec4e48d45de0e029b51ab6d9"), + "plugins/UserCountry/Tasks.php" => array("466", "686a2d7cd2b7d9282369e3bfed4c530d"), + "plugins/UserCountry/templates/adminIndex.twig" => array("6686", "b543e048170c631386740a86b56ef4c4"), "plugins/UserCountry/templates/getGeoIpUpdaterManageScreen.twig" => array("48", "b3ffc6408921cada77599d32ae546511"), - "plugins/UserCountry/templates/index.twig" => array("785", "12f762bb3dff29bb477eff7fff7238c2"), - "plugins/UserCountry/templates/_updaterManage.twig" => array("3168", "87f3d8ef21402d491ec9c19203fd4ed7"), + "plugins/UserCountry/templates/index.twig" => array("929", "3986e5864bf49ee5c9f1108af3d420db"), + "plugins/UserCountry/templates/_updaterManage.twig" => array("3004", "048c9980cbce0a3f84c0a6ac02354211"), "plugins/UserCountry/templates/_updaterNextRunTime.twig" => array("386", "9761b8bd24bd0ebd0461a560dcb23d22"), - "plugins/UserCountry/UserCountry.php" => array("21375", "6a03bbebc2f88b09ddc859d37e8b1aa3"), - "plugins/UserSettings/API.php" => array("10763", "f32d5ea9844711996d18085b7d5ec1b9"), - "plugins/UserSettings/Archiver.php" => array("6825", "1cd51e9b6b66f6a066e301b058c1fbd4"), - "plugins/UserSettings/Controller.php" => array("2023", "0628ed431135bd3fac8ad989088df785"), - "plugins/UserSettings/functions.php" => array("6653", "3444e02fdef24b992dd36e164e3fa10a"), - "plugins/UserSettings/images/browsers/AA.gif" => array("1092", "dc62eff78dac919b00e60c5fb3e6266d"), - "plugins/UserSettings/images/browsers/AB.gif" => array("1064", "1123da862a558b1ccd8f9008c5c4fdcb"), - "plugins/UserSettings/images/browsers/AG.gif" => array("351", "6e793ad6ad5c69abc499422d6a43d836"), - "plugins/UserSettings/images/browsers/AM.gif" => array("198", "18c54fc3197f6e1533c06b0923db3bd0"), - "plugins/UserSettings/images/browsers/AN.gif" => array("144", "a1264f47256ff5b0d4feeeac833e7a96"), - "plugins/UserSettings/images/browsers/AR.gif" => array("1057", "377249d199156ee602e669c0afc02945"), - "plugins/UserSettings/images/browsers/AV.gif" => array("151", "f459c84d6ab90fdf0f35d20f9f82626d"), - "plugins/UserSettings/images/browsers/AW.gif" => array("574", "e4e4fa2ef432f2a86086ec58f4b27ab1"), - "plugins/UserSettings/images/browsers/B2.gif" => array("1070", "e1b9f6b47cffbdf05b4159d6a31b21ae"), - "plugins/UserSettings/images/browsers/BB.gif" => array("576", "d40bd99ba1dd881b164907341ec2079d"), - "plugins/UserSettings/images/browsers/BD.gif" => array("1051", "4a8ebfcd7aad4c1004b7f82b954b0a69"), - "plugins/UserSettings/images/browsers/BE.gif" => array("1042", "103994d17de92aa261c8034a8e35a84f"), - "plugins/UserSettings/images/browsers/BP.gif" => array("1070", "e1b9f6b47cffbdf05b4159d6a31b21ae"), - "plugins/UserSettings/images/browsers/BX.gif" => array("522", "7302ad862f4007c23efb73acbd41f5c0"), - "plugins/UserSettings/images/browsers/CA.gif" => array("573", "739fca054b61f68657b0bd349c958a86"), - "plugins/UserSettings/images/browsers/CD.gif" => array("1045", "b5484f5fc254abd52cdc94f378be977a"), - "plugins/UserSettings/images/browsers/CF.gif" => array("1074", "e349a7dbdadf2fca33cca13287b0eba8"), - "plugins/UserSettings/images/browsers/CH.gif" => array("1074", "e349a7dbdadf2fca33cca13287b0eba8"), - "plugins/UserSettings/images/browsers/CK.gif" => array("1024", "b6d4ebb0394c48dfcb5f21475de5c79b"), - "plugins/UserSettings/images/browsers/CM.gif" => array("1074", "e349a7dbdadf2fca33cca13287b0eba8"), - "plugins/UserSettings/images/browsers/CN.gif" => array("998", "cd878afb8cc56e7c8c6caef6d5fb2ba4"), - "plugins/UserSettings/images/browsers/CO.gif" => array("1042", "195487c9db2e19ffae3fd443b9266e62"), - "plugins/UserSettings/images/browsers/CP.gif" => array("998", "af47e47253591c272a458c4645fbaf5c"), - "plugins/UserSettings/images/browsers/CS.gif" => array("549", "eb5151f2f46fc09d687ce27becefb831"), - "plugins/UserSettings/images/browsers/DF.gif" => array("545", "f4b65ebcf304f1675088d029ed613d28"), - "plugins/UserSettings/images/browsers/DI.gif" => array("1068", "4eceaf5fd7808c422b67026c9329e16f"), - "plugins/UserSettings/images/browsers/EL.gif" => array("90", "b515db820d883f921d05d15a34dda7f9"), - "plugins/UserSettings/images/browsers/EP.gif" => array("316", "660436cc97429ef52365a01084b40ee0"), - "plugins/UserSettings/images/browsers/ES.gif" => array("1013", "92ada780fce5e3ffc318de634eba4d21"), - "plugins/UserSettings/images/browsers/FB.gif" => array("254", "24663f1949bae4fb7ecd0504c9c872fd"), - "plugins/UserSettings/images/browsers/FD.gif" => array("1050", "a569dc9dbe1b95ce2763bee8dbdc5850"), - "plugins/UserSettings/images/browsers/FE.gif" => array("550", "a016a52d476c4be7943e68ccf2056db1"), - "plugins/UserSettings/images/browsers/FF.gif" => array("197", "6b4eecf673d5461aebdd432bca086e8c"), - "plugins/UserSettings/images/browsers/FL.gif" => array("1034", "0ce889dc81db377eeb00f76f72942274"), - "plugins/UserSettings/images/browsers/FN.gif" => array("1033", "e87473b14680939e58a9a94e9dcd39e3"), - "plugins/UserSettings/images/browsers/GA.gif" => array("159", "576c4646cf6938dd2079516293d3a3f9"), - "plugins/UserSettings/images/browsers/GE.gif" => array("997", "a3d96e8576f273ecc4a864d24620fbf2"), - "plugins/UserSettings/images/browsers/HA.gif" => array("1009", "fccae707e311bd009be90a7b38000082"), - "plugins/UserSettings/images/browsers/HJ.gif" => array("1022", "8c3019d1e0867e8455d0abf1ad3eb531"), - "plugins/UserSettings/images/browsers/IA.gif" => array("391", "1836a7db0bc6a4a4d8b3d6063346e3f5"), - "plugins/UserSettings/images/browsers/IB.gif" => array("168", "b091c3e8ce2789017d581089028fa1cc"), - "plugins/UserSettings/images/browsers/IC.gif" => array("131", "26a6ff98d316092214c9dc9e7be45224"), - "plugins/UserSettings/images/browsers/ID.gif" => array("1057", "6f1f33dc4bd104e0a60000ce49e719a9"), - "plugins/UserSettings/images/browsers/IE.gif" => array("999", "5e002ee72167a3a78e2252766fde1046"), - "plugins/UserSettings/images/browsers/IR.gif" => array("610", "565b1e3acd514c1c9b88d6c2b0ec0c4d"), - "plugins/UserSettings/images/browsers/IW.gif" => array("1066", "8d3376b6699ccd4533191b38d7f6b8c4"), - "plugins/UserSettings/images/browsers/KI.gif" => array("1050", "497b4bc9b58c113ae72763559321071b"), - "plugins/UserSettings/images/browsers/KM.gif" => array("180", "3daa5fa7553d448cd280612491e8a6ea"), - "plugins/UserSettings/images/browsers/KO.gif" => array("986", "0d15a2d4a73582d1df109e0ff3463fd3"), - "plugins/UserSettings/images/browsers/KP.gif" => array("1037", "f29fb41537f7df91d827c3f6eea076a0"), - "plugins/UserSettings/images/browsers/KZ.gif" => array("1061", "cdc654ad5f3f5fdda6ac69ea7d7dbe31"), - "plugins/UserSettings/images/browsers/LI.gif" => array("104", "a3b302b3fffc9ed032add149d4ace210"), - "plugins/UserSettings/images/browsers/LS.gif" => array("1086", "c61646736ea4872a7e58bcd29716d778"), - "plugins/UserSettings/images/browsers/LX.gif" => array("104", "a3b302b3fffc9ed032add149d4ace210"), - "plugins/UserSettings/images/browsers/MC.gif" => array("1023", "0e17db9ed1e06feb1d23d134ce34693d"), - "plugins/UserSettings/images/browsers/MI.gif" => array("1025", "5e63fceac90a88f1db14b4e8ee44201d"), - "plugins/UserSettings/images/browsers/MO.gif" => array("192", "67b5dac21e8f2243a955f1d9df7ef67e"), - "plugins/UserSettings/images/browsers/MS.gif" => array("1094", "265861a05c27b23013cb6ae3c428dff0"), - "plugins/UserSettings/images/browsers/MX.gif" => array("985", "4f6f87c42bf5c6bfc2b63925da5e40c1"), - "plugins/UserSettings/images/browsers/NB.gif" => array("977", "d2fac7549889df9f1c0863b424543c6f"), - "plugins/UserSettings/images/browsers/NF.gif" => array("612", "7cb0d2713e9faf25b766ca0d13cf456b"), - "plugins/UserSettings/images/browsers/NL.gif" => array("1081", "f66412328676120ba3cc0eb987c16158"), - "plugins/UserSettings/images/browsers/NP.gif" => array("1020", "ba7d68a0f9c11647abba2f8454a7c34c"), - "plugins/UserSettings/images/browsers/NS.gif" => array("98", "cd8d53ec12b64294d16769dfeeaf07c7"), - "plugins/UserSettings/images/browsers/OB.gif" => array("1010", "67b1d28deccc92200bdc3ce4a86612c3"), - "plugins/UserSettings/images/browsers/ON.gif" => array("635", "f7d7eb7f8cec24f0c192e30fe29ea320"), - "plugins/UserSettings/images/browsers/OP.gif" => array("987", "ac0432440ad48154a6434675b1d9c27a"), - "plugins/UserSettings/images/browsers/OR.gif" => array("1024", "30e874c346325cd40cf58f98944ea603"), - "plugins/UserSettings/images/browsers/OV.gif" => array("978", "7e98fecee01438d561b791339281bd47"), - "plugins/UserSettings/images/browsers/OW.gif" => array("197", "b66e88cbb941f9ac326f43e0d993e572"), - "plugins/UserSettings/images/browsers/PL.gif" => array("1058", "759fa0100429b3b3a4dc88894454fd8a"), - "plugins/UserSettings/images/browsers/PM.gif" => array("1082", "271bd4b89d06a9f9a9553b5da9053bd3"), - "plugins/UserSettings/images/browsers/PO.gif" => array("1065", "ac773ea28693335de8e8ac62f7d20a0d"), - "plugins/UserSettings/images/browsers/PU.gif" => array("1094", "1ea4a15e1b326c28158b137e4e8d07af"), - "plugins/UserSettings/images/browsers/PW.gif" => array("1082", "ac66922861d77e9949a72f86622e0c2f"), - "plugins/UserSettings/images/browsers/PX.gif" => array("170", "0bd86aa95e1ae0d5975cdc2d93210e30"), - "plugins/UserSettings/images/browsers/RK.gif" => array("1035", "6b87220087449e134062214fbf72f5a8"), - "plugins/UserSettings/images/browsers/SA.gif" => array("1008", "921467088fc56c5c9cdd0bb6e58250bf"), - "plugins/UserSettings/images/browsers/SF.gif" => array("190", "589361249f74319b57ea98d6408bc4b3"), - "plugins/UserSettings/images/browsers/SL.gif" => array("900", "1210e399e978978390cfdfd9d79159e6"), - "plugins/UserSettings/images/browsers/SM.gif" => array("391", "1836a7db0bc6a4a4d8b3d6063346e3f5"), - "plugins/UserSettings/images/browsers/TB.gif" => array("1014", "79bf7ed3ad92d3da09737d2fcd3913aa"), - "plugins/UserSettings/images/browsers/TI.gif" => array("595", "2c09db5f54b47472971d863e183159a8"), - "plugins/UserSettings/images/browsers/TZ.gif" => array("973", "5858a8b149e45749424bdf2da7ebefa3"), - "plugins/UserSettings/images/browsers/UC.gif" => array("994", "d9622ea01cb9093592858da53443c200"), - "plugins/UserSettings/images/browsers/UN.gif" => array("80", "c4a7f0e333a6079fdb0b6595e11bca74"), - "plugins/UserSettings/images/browsers/UNK.gif" => array("80", "c4a7f0e333a6079fdb0b6595e11bca74"), - "plugins/UserSettings/images/browsers/WE.gif" => array("1012", "cce9216ee7bd3ef52a46003d249ab540"), - "plugins/UserSettings/images/browsers/WO.gif" => array("1065", "ed1504717c9af523e30c33908126c4ad"), - "plugins/UserSettings/images/browsers/WP.gif" => array("982", "5bba1edfb42ce1b96551f81af0be08a1"), - "plugins/UserSettings/images/browsers/YA.gif" => array("1048", "8d94386ab4796664de7b897dd2106c9c"), - "plugins/UserSettings/images/os/3DS.gif" => array("1085", "262b44579aadcf90973653ff3e759cc7"), - "plugins/UserSettings/images/os/AIX.gif" => array("176", "58a60503a8e92493153694d1d97d2f6d"), - "plugins/UserSettings/images/os/AMG.gif" => array("1001", "5d67b7bb52ed746480573c1600d9a34c"), - "plugins/UserSettings/images/os/AMI.gif" => array("1055", "ef341c4cc2ec3bbf860c7d3bfe685326"), - "plugins/UserSettings/images/os/AND.gif" => array("144", "a1264f47256ff5b0d4feeeac833e7a96"), - "plugins/UserSettings/images/os/ARL.gif" => array("947", "913d273e01b9031f5113fb82ce63a591"), - "plugins/UserSettings/images/os/BBX.gif" => array("590", "e5cff6836abf100d9d8310d9dbb9f5d4"), - "plugins/UserSettings/images/os/BEO.gif" => array("1035", "ae4420933ac47a072d0f47759ef830c2"), - "plugins/UserSettings/images/os/BLB.gif" => array("576", "d40bd99ba1dd881b164907341ec2079d"), - "plugins/UserSettings/images/os/BSD.gif" => array("1016", "1dc9b76bb3fc8f5529e9abe9ac8841fa"), - "plugins/UserSettings/images/os/BTR.gif" => array("946", "cbf9b74ee2db7714ed6d6432eff3f7c8"), - "plugins/UserSettings/images/os/CES.gif" => array("1011", "0cdd142972c3cc89b2ceb3b7b97e1321"), - "plugins/UserSettings/images/os/COS.gif" => array("1074", "e349a7dbdadf2fca33cca13287b0eba8"), - "plugins/UserSettings/images/os/DFB.gif" => array("326", "d61f11a900d520ef515eaa139176a5f1"), - "plugins/UserSettings/images/os/DSI.gif" => array("1076", "5c475f3ba76f4ec3b626e720574bcb37"), - "plugins/UserSettings/images/os/FED.gif" => array("1022", "e86e6f5aec6c32de7eca913a9f91c3ab"), - "plugins/UserSettings/images/os/FOS.gif" => array("197", "6b4eecf673d5461aebdd432bca086e8c"), - "plugins/UserSettings/images/os/GNT.gif" => array("1075", "4196a85df43e6a5593941dcf8262416f"), - "plugins/UserSettings/images/os/GTV.gif" => array("1614", "a032dd001e1a5755201a6263a669ca49"), - "plugins/UserSettings/images/os/HPX.gif" => array("191", "999717c37d76ca099a06cf77a781bdbf"), - "plugins/UserSettings/images/os/IOS.gif" => array("591", "0bd1b8e09506ae6f9ed6ab1b18acf552"), - "plugins/UserSettings/images/os/IPA.gif" => array("587", "9f247437bc140cc6e70ec59f34bcddb1"), - "plugins/UserSettings/images/os/IPD.gif" => array("351", "a215ada2aefcbca876055fe7a2f7c039"), - "plugins/UserSettings/images/os/IPH.gif" => array("577", "49805f402375692d40635ada7cc0f472"), - "plugins/UserSettings/images/os/IRI.gif" => array("152", "5e631b5adc35a05ae0d85815829c6f48"), - "plugins/UserSettings/images/os/KBT.gif" => array("998", "6ecd8b978a51fb4fd6ec94f9b820ae1d"), - "plugins/UserSettings/images/os/KNO.gif" => array("985", "b4595a673edf60051636fedf418eda30"), - "plugins/UserSettings/images/os/LBT.gif" => array("951", "ed14ac9707a0e01f4557ac9e8508dea3"), - "plugins/UserSettings/images/os/LIN.gif" => array("170", "19039ee87d8fccdba6a391ada5656de7"), - "plugins/UserSettings/images/os/MAC.gif" => array("171", "03548481597f28751368e26be49aea99"), - "plugins/UserSettings/images/os/MAE.gif" => array("137", "84600277bad6751b68b15586bca50aef"), - "plugins/UserSettings/images/os/MDR.gif" => array("918", "62f5e501d28e25fff6c3a75631c7c208"), - "plugins/UserSettings/images/os/MIN.gif" => array("1009", "153385eff242c4e10ea3835a17a65f0e"), - "plugins/UserSettings/images/os/NBS.gif" => array("168", "fc0d4fcb57c98f3a4dd6eab8e71ebd6a"), - "plugins/UserSettings/images/os/NDS.gif" => array("1061", "16bc6e0960747b402441c40e419a7d53"), - "plugins/UserSettings/images/os/OBS.gif" => array("571", "fcdb547b7ab768e131ba592e8733c75c"), - "plugins/UserSettings/images/os/OS2.gif" => array("162", "ee37bab155ad46f2530e7586f9971656"), - "plugins/UserSettings/images/os/POS.gif" => array("1060", "96b06842dc1cc80a8bb283ee8f4be320"), - "plugins/UserSettings/images/os/PPY.gif" => array("1037", "1bc770c1bd83e6cfdc5087b00b44cb9d"), - "plugins/UserSettings/images/os/PS3.gif" => array("628", "7aca5b93e7cc8142e2ab578e7aae7dc8"), - "plugins/UserSettings/images/os/PSP.gif" => array("592", "f92da90c6c6ea808422184c314215465"), - "plugins/UserSettings/images/os/PSV.gif" => array("200", "d82e64a4f0aeec6e4931a41988680af3"), - "plugins/UserSettings/images/os/QNX.gif" => array("241", "51ceb87cd6268837d830b225cb6c8120"), - "plugins/UserSettings/images/os/RHT.gif" => array("952", "72c775bb0f2b8ad388ee513577abc8ec"), - "plugins/UserSettings/images/os/ROS.gif" => array("956", "9a294e5c701171c8c777ce563d88d924"), - "plugins/UserSettings/images/os/SAF.gif" => array("242", "600259fe739536945b6f5cd5c9d3489b"), - "plugins/UserSettings/images/os/SBA.gif" => array("990", "be3cfde24b6517884d73270a20e9a06f"), - "plugins/UserSettings/images/os/SLW.gif" => array("883", "37ca3a7bdb0eeea402252c80dca89369"), - "plugins/UserSettings/images/os/SOS.gif" => array("1036", "686261ea170398b2eb078b67a24f4276"), - "plugins/UserSettings/images/os/SSE.gif" => array("1066", "8a6b48a38ee8ecca0fd14092872dba12"), - "plugins/UserSettings/images/os/SYL.gif" => array("1017", "5c73fe766f50d4a697dbdeac254db9e2"), - "plugins/UserSettings/images/os/SYM.gif" => array("1042", "a8d404e43206c52a7e5f3e6e7e469eea"), - "plugins/UserSettings/images/os/T64.gif" => array("220", "8e42601e52784216ed71b8e8de17f82e"), - "plugins/UserSettings/images/os/TIZ.gif" => array("958", "dbc584b7603e8865c9477b18a6fbbb7d"), - "plugins/UserSettings/images/os/UBT.gif" => array("986", "56d67af2d61927c290b0e5af8e8ef6fc"), - "plugins/UserSettings/images/os/UNK.gif" => array("80", "c4a7f0e333a6079fdb0b6595e11bca74"), - "plugins/UserSettings/images/os/VMS.gif" => array("572", "da6881ce3b86fdbea70ae1b405f9c40a"), - "plugins/UserSettings/images/os/W2K.gif" => array("185", "5e7141d138d3f7afc0a113c21a8b2d9b"), - "plugins/UserSettings/images/os/W61.gif" => array("1060", "721b79d12ac825df25f356f5a7714f74"), - "plugins/UserSettings/images/os/W65.gif" => array("1060", "721b79d12ac825df25f356f5a7714f74"), - "plugins/UserSettings/images/os/W75.gif" => array("1089", "8d69225f3ebb1fe2d2b6d4e2e1687b80"), - "plugins/UserSettings/images/os/W95.gif" => array("185", "5e7141d138d3f7afc0a113c21a8b2d9b"), - "plugins/UserSettings/images/os/W98.gif" => array("185", "5e7141d138d3f7afc0a113c21a8b2d9b"), - "plugins/UserSettings/images/os/WCE.gif" => array("185", "5e7141d138d3f7afc0a113c21a8b2d9b"), - "plugins/UserSettings/images/os/WI7.gif" => array("191", "53d14246a8e5a46e927a87648111a0d3"), - "plugins/UserSettings/images/os/WI8.gif" => array("925", "d9f78bbd9009c721cf5d64b056679a19"), - "plugins/UserSettings/images/os/WII.gif" => array("617", "d8f2cae9e8e7723241c6c862f12c7511"), - "plugins/UserSettings/images/os/WIN.gif" => array("191", "53d14246a8e5a46e927a87648111a0d3"), - "plugins/UserSettings/images/os/WIU.gif" => array("310", "394c491524ac263e1c4fcedb1480d281"), - "plugins/UserSettings/images/os/WME.gif" => array("1025", "27c3894e3585f86f9283374ed7776c3a"), - "plugins/UserSettings/images/os/WMO.gif" => array("1060", "721b79d12ac825df25f356f5a7714f74"), - "plugins/UserSettings/images/os/WNT.gif" => array("185", "5e7141d138d3f7afc0a113c21a8b2d9b"), - "plugins/UserSettings/images/os/WOS.gif" => array("70", "31e5c59d2fc5b195c5ea4f1afd878e04"), - "plugins/UserSettings/images/os/WP7.gif" => array("1089", "8d69225f3ebb1fe2d2b6d4e2e1687b80"), - "plugins/UserSettings/images/os/WPH.gif" => array("1089", "8d69225f3ebb1fe2d2b6d4e2e1687b80"), - "plugins/UserSettings/images/os/WRT.gif" => array("925", "d9f78bbd9009c721cf5d64b056679a19"), - "plugins/UserSettings/images/os/WS3.gif" => array("191", "53d14246a8e5a46e927a87648111a0d3"), - "plugins/UserSettings/images/os/WVI.gif" => array("191", "53d14246a8e5a46e927a87648111a0d3"), - "plugins/UserSettings/images/os/WXP.gif" => array("191", "53d14246a8e5a46e927a87648111a0d3"), - "plugins/UserSettings/images/os/XBT.gif" => array("968", "c0f572e03ffaf7d38c78fcbb7fba84cf"), - "plugins/UserSettings/images/os/XBX.gif" => array("1043", "e3e0eaa5daa2903bab1e0e9ea3ef1d46"), - "plugins/UserSettings/images/os/YNS.gif" => array("913", "f4d8502e11b209c209fcee3312a33139"), - "plugins/UserSettings/images/plugins/cookie.gif" => array("211", "9e564884defc036134b19ab38c192a6b"), - "plugins/UserSettings/images/plugins/director.gif" => array("198", "952c4a0e083ed089ca5c8ab2804c35ab"), - "plugins/UserSettings/images/plugins/flash.gif" => array("1018", "f315906f425f089dd4664ee530c691bd"), - "plugins/UserSettings/images/plugins/gears.gif" => array("558", "d4ec944ef01420637a709619f067953e"), - "plugins/UserSettings/images/plugins/java.gif" => array("565", "b4d24f76a64e2df762292e892e9215c4"), - "plugins/UserSettings/images/plugins/pdf.gif" => array("1021", "79b3d68c112942cefbb24dda9b421464"), - "plugins/UserSettings/images/plugins/quicktime.gif" => array("1003", "0feda8dc4ddec39e35b6f4925603c8bd"), - "plugins/UserSettings/images/plugins/realplayer.gif" => array("1025", "95739f527d29cab050a3d4eff35e93c7"), - "plugins/UserSettings/images/plugins/silverlight.gif" => array("1012", "8449c3a43f42c44bc5b82948d179b4b4"), - "plugins/UserSettings/images/plugins/windowsmedia.gif" => array("1026", "a374aec2d5488c5dc8d4eaf21ec59728"), - "plugins/UserSettings/images/screens/dual.gif" => array("1082", "7903e070f4c4aa7933030e547e78073e"), - "plugins/UserSettings/images/screens/mobile.gif" => array("324", "04942ef60dd75380cfd682314e87e857"), - "plugins/UserSettings/images/screens/normal.gif" => array("1088", "b0c3de8704745e58c67844e54282ee42"), - "plugins/UserSettings/images/screens/unknown.gif" => array("80", "c4a7f0e333a6079fdb0b6595e11bca74"), - "plugins/UserSettings/images/screens/wide.gif" => array("1025", "c0104958e6fb23668d0406fd4d89095e"), - "plugins/UserSettings/templates/index.twig" => array("985", "64dbe064f25bce041a9ea0ac4b401509"), - "plugins/UserSettings/UserSettings.php" => array("16493", "546f136ebeb5cbe7e1ffa364be6f1fdc"), - "plugins/UsersManager/API.php" => array("21685", "ff11d871854f92e6e309b563ba4ed8cd"), - "plugins/UsersManager/Controller.php" => array("12751", "27d35da3855e76319f08f5cfeb4e24cd"), - "plugins/UsersManager/images/add.png" => array("1366", "79f8c85304af007fb571bd67b5b84a65"), + "plugins/UserCountry/UserCountry.php" => array("4285", "bffbc382752561cc071e9b4341b83ec5"), + "plugins/UserCountry/VisitorGeolocator.php" => array("10882", "d05221ed2456380605b5ee6857634e27"), + "plugins/UserCountry/Visitor.php" => array("2558", "0dbbfa0030de5317128be97d97c2b475"), + "plugins/UserLanguage/API.php" => array("2116", "1314ee077057973e23efe20f7447e050"), + "plugins/UserLanguage/Archiver.php" => array("3003", "2eca610090869ff2f14aead682d90f51"), + "plugins/UserLanguage/Columns/Language.php" => array("1777", "f7639231c9d299b15ad77589a647bf4c"), + "plugins/UserLanguage/functions.php" => array("1726", "c105d7757352549ff9d55a4e72349dd9"), + "plugins/UserLanguage/lang/am.json" => array("77", "50ea2510c809e5191cf9005f906f609f"), + "plugins/UserLanguage/lang/ar.json" => array("240", "e064c44491011846dbd5fa40cd2f7690"), + "plugins/UserLanguage/lang/be.json" => array("73", "7838a25874e18e0e93f41a1a53db9bcb"), + "plugins/UserLanguage/lang/bg.json" => array("141", "06cbf050cd902edf4c1a41a6e7359143"), + "plugins/UserLanguage/lang/ca.json" => array("74", "517d9a647ca634e725d934ec3080b4a1"), + "plugins/UserLanguage/lang/cs.json" => array("208", "46b68267c8ed894345aa6b207ca65444"), + "plugins/UserLanguage/lang/da.json" => array("203", "31a371933ea1723393bdda93c71307d8"), + "plugins/UserLanguage/lang/de.json" => array("222", "f6960e2af8690d3a534b91b184668858"), + "plugins/UserLanguage/lang/el.json" => array("339", "4cfc41ed7db488ba5dbd54b9e63a528b"), + "plugins/UserLanguage/lang/en.json" => array("204", "fc78445517e40377464d578229eb376c"), + "plugins/UserLanguage/lang/es.json" => array("224", "2be0cb2d3c5e4f173d0cfd223ad54706"), + "plugins/UserLanguage/lang/et.json" => array("116", "82eb2cbee9421ec8b46cfa84cc81720d"), + "plugins/UserLanguage/lang/eu.json" => array("73", "156cfb5560818d5004456163b6665e26"), + "plugins/UserLanguage/lang/fa.json" => array("123", "69593e002f7db732fee224a35256a0e3"), + "plugins/UserLanguage/lang/fi.json" => array("113", "41bc41ca424b42b5488b36f904064df4"), + "plugins/UserLanguage/lang/fr.json" => array("223", "b4b952edf04e549d4cb9727bb99902af"), + "plugins/UserLanguage/lang/gl.json" => array("221", "6cec01749ee22927adeb7883700e6709"), + "plugins/UserLanguage/lang/he.json" => array("71", "7885719b1507af9af987e2a61d6d2cef"), + "plugins/UserLanguage/lang/hi.json" => array("342", "5396ac5662df6bff367adb5b9c7665d4"), + "plugins/UserLanguage/lang/hr.json" => array("211", "4d38af3ae41a3e9d47947f6caa30e327"), + "plugins/UserLanguage/lang/hu.json" => array("69", "5150359cdf58dc2816fc2d398eab725f"), + "plugins/UserLanguage/lang/id.json" => array("200", "5cea9c9d2f9c4fda153979b9d13c0235"), + "plugins/UserLanguage/lang/is.json" => array("75", "944b8498ab8f90c72e71386ed88bd8cb"), + "plugins/UserLanguage/lang/it.json" => array("220", "1fbf80f80d056f978a179a80ba87aeb0"), + "plugins/UserLanguage/lang/ja.json" => array("245", "45de0b2711432c65fbe0ffb22ed7605d"), + "plugins/UserLanguage/lang/ka.json" => array("83", "1611da157c35c952be9613cea9d60e68"), + "plugins/UserLanguage/lang/ko.json" => array("203", "751f2e18aa2a5febc323f77c0e747128"), + "plugins/UserLanguage/lang/lt.json" => array("118", "45a234ce3d8e6d30d561d819c32c7f1e"), + "plugins/UserLanguage/lang/lv.json" => array("70", "443d78e2ba274a9fe627d652cd890d83"), + "plugins/UserLanguage/lang/nb.json" => array("211", "1068d9a06f96f67d811b8d69f5bb0857"), + "plugins/UserLanguage/lang/nl.json" => array("206", "5e1a3ddde02fa61bffd3640b3929d2ec"), + "plugins/UserLanguage/lang/nn.json" => array("68", "dafd2eddbee8c3f8675af50aeda6e597"), + "plugins/UserLanguage/lang/pl.json" => array("215", "aa81c2937cbf5150a5cba693cdba793a"), + "plugins/UserLanguage/lang/pt-br.json" => array("214", "6008163185e1c3980153851af96c3d41"), + "plugins/UserLanguage/lang/pt.json" => array("75", "bdc7c957b8a00b6bae753cec6f77e098"), + "plugins/UserLanguage/lang/ro.json" => array("117", "62924385db942a0bc1d7a2f1f1c05e8a"), + "plugins/UserLanguage/lang/ru.json" => array("268", "5849e16f274ad6d1162f071bdd21d066"), + "plugins/UserLanguage/lang/sk.json" => array("210", "46cc32df6fa5d8c27cc4dbf2dbe27dc0"), + "plugins/UserLanguage/lang/sl.json" => array("118", "61fc91fbdb8a80e1e34f38edd929f1fa"), + "plugins/UserLanguage/lang/sq.json" => array("67", "c706a736e887902410bed30205cddeed"), + "plugins/UserLanguage/lang/sr.json" => array("210", "bc1412286e2300440cad5297b86bb283"), + "plugins/UserLanguage/lang/sv.json" => array("193", "9c9980b72928ef2e9cacb99330c73b2e"), + "plugins/UserLanguage/lang/ta.json" => array("308", "be2eda126052cf72cf873826f8cb1e42"), + "plugins/UserLanguage/lang/te.json" => array("89", "a4badd5969e35c356b8040ca0d679688"), + "plugins/UserLanguage/lang/th.json" => array("82", "3791e882e71c4c7c54d306bb1e47b076"), + "plugins/UserLanguage/lang/tl.json" => array("116", "5be2bcd21245c36a5181fc6d88765cba"), + "plugins/UserLanguage/lang/tr.json" => array("66", "5986ed03ce0c063eef3955d7c72d8992"), + "plugins/UserLanguage/lang/uk.json" => array("73", "edebfb5f4507b44ad19149cbe77ca606"), + "plugins/UserLanguage/lang/vi.json" => array("130", "660023f218a9ef33161d88b5c2f4fa91"), + "plugins/UserLanguage/lang/zh-cn.json" => array("116", "daadb80a393e6527df81fe8aca18cb70"), + "plugins/UserLanguage/lang/zh-tw.json" => array("185", "c2c575d43b2da8c9503fc21479419a6c"), + "plugins/UserLanguage/Reports/Base.php" => array("411", "203a69e3d30dad6caed2d323a3e7c4ff"), + "plugins/UserLanguage/Reports/GetLanguageCode.php" => array("773", "ef24e69a8558efbcb62cd08c4e422b0d"), + "plugins/UserLanguage/Reports/GetLanguage.php" => array("1254", "0ae540fb19b4bd3dbbf9ff38418b11e8"), + "plugins/UserLanguage/UserLanguage.php" => array("1525", "26277ce1b1312efa6337ae3b14a0f2a6"), + "plugins/UserLanguage/Visitor.php" => array("637", "682e01ae0c2046efc8de13e638809b9c"), + "plugins/UsersManager/API.php" => array("26904", "4ddfd742012972557b4000749eb3937e"), + "plugins/UsersManager/Controller.php" => array("17108", "7b256dd874a1d14f8662b46c87ac7c4d"), "plugins/UsersManager/images/no-access.png" => array("653", "e92421cec0f4c4f344b00a2ff72f96ec"), "plugins/UsersManager/images/ok.png" => array("851", "94672a5d1482b1afaafce7802155f518"), - "plugins/UsersManager/javascripts/usersManager.js" => array("10779", "e4711ac89dcb705f5002c39a26608dbe"), - "plugins/UsersManager/javascripts/usersSettings.js" => array("3254", "6f10ea2cac2509e07988389078687af4"), - "plugins/UsersManager/LastSeenTimeLogger.php" => array("2296", "11eb62ec3cc2588fb571769330aefbba"), - "plugins/UsersManager/Model.php" => array("9294", "93aa4779b992fc0991aaa9a6cc8e533e"), - "plugins/UsersManager/stylesheets/usersManager.less" => array("400", "34e7c27854b8a5e2b5c2e55df5de755c"), - "plugins/UsersManager/templates/index.twig" => array("9706", "50e07c93f864be7e52418c05d96c5588"), - "plugins/UsersManager/templates/userSettings.twig" => array("9580", "25708d48635daf6f1114220df0c43fc2"), - "plugins/UsersManager/UsersManager.php" => array("5028", "42c49b25bd17cc81567855b42e0e4736"), - "plugins/VisitFrequency/API.php" => array("2542", "bd9a2e5d38ce65bc72af6abfe65166a6"), - "plugins/VisitFrequency/Controller.php" => array("4225", "9316375706f2da88c7243aa2e703d362"), + "plugins/UsersManager/javascripts/giveViewAccess.js" => array("5315", "9cd65b92613cc3199b7383483e0d50f7"), + "plugins/UsersManager/javascripts/usersManager.js" => array("11248", "6daffb27ea6a2015249fb5806143fa9f"), + "plugins/UsersManager/javascripts/usersSettings.js" => array("3341", "3ab8e2b943e9243915dd162d28653a44"), + "plugins/UsersManager/lang/am.json" => array("1835", "3d0c0aa3aab5c0562cd1ed0fa1716525"), + "plugins/UsersManager/lang/ar.json" => array("4096", "a0ee1715e62db81681ab991c501a6eef"), + "plugins/UsersManager/lang/be.json" => array("5049", "42a56bd1b8c3f79d30064a1b961a1c02"), + "plugins/UsersManager/lang/bg.json" => array("8143", "3e420c866a0892442e8987715a7988d2"), + "plugins/UsersManager/lang/bs.json" => array("97", "4dfd11c5c330b2adf981bc2c1bcbe4c8"), + "plugins/UsersManager/lang/ca.json" => array("3909", "5060b33f6e6ba32a7915c6e89307e672"), + "plugins/UsersManager/lang/cs.json" => array("7790", "11c02b05cb4915c515109c6a755e4173"), + "plugins/UsersManager/lang/da.json" => array("5738", "744f37c4c2b09c4e20b62d3b15c99881"), + "plugins/UsersManager/lang/de.json" => array("7206", "7154583aba4ef0b6a8cf4c336772f7e7"), + "plugins/UsersManager/lang/el.json" => array("12719", "695852310a9dc98abdef80981cbeecec"), + "plugins/UsersManager/lang/en.json" => array("7192", "962d7ffa4610a6eeba787d3fbd15738a"), + "plugins/UsersManager/lang/es.json" => array("6353", "eaae4c6fac6763badac90feced16ea6b"), + "plugins/UsersManager/lang/et.json" => array("2761", "b3fba88a6633d1559753efac1de3568c"), + "plugins/UsersManager/lang/eu.json" => array("1524", "d9f296ac1e15b969b3f05830da31cd9b"), + "plugins/UsersManager/lang/fa.json" => array("5617", "199d0fa6796bb5db093ea3cf4b95c0b7"), + "plugins/UsersManager/lang/fi.json" => array("5844", "3c32208081ed506c0b5d5789bdba17c4"), + "plugins/UsersManager/lang/fr.json" => array("6633", "90451fc72ef58fc94ac08ca7c10d31ab"), + "plugins/UsersManager/lang/gl.json" => array("1272", "d93072ac7f3695fa877bdca302ec4005"), + "plugins/UsersManager/lang/he.json" => array("381", "4c2311bec3c380920eadf0dc3daa6bb8"), + "plugins/UsersManager/lang/hi.json" => array("2279", "06a3c3da358376eb0518b9e1de237c4b"), + "plugins/UsersManager/lang/hr.json" => array("397", "062659f1845602d8e3085d0f3176ebad"), + "plugins/UsersManager/lang/hu.json" => array("3763", "9494e22107493145ac5545b15f5fc3d2"), + "plugins/UsersManager/lang/id.json" => array("4152", "33a98d86a6cc80c1343eb276f3ab0f08"), + "plugins/UsersManager/lang/is.json" => array("915", "5e7a748872f8b468a6a8a74bb41b57c3"), + "plugins/UsersManager/lang/it.json" => array("6459", "eb3178db7e5a06bcb218ce810c166925"), + "plugins/UsersManager/lang/ja.json" => array("7650", "5d324b04def21e6b338e9cb3445ae143"), + "plugins/UsersManager/lang/ka.json" => array("7783", "6cba383ab0dec6d60bcfe5a2e4d2cdf9"), + "plugins/UsersManager/lang/ko.json" => array("7182", "9a5e7e94aadb15f959b79d4e832d3787"), + "plugins/UsersManager/lang/lt.json" => array("4352", "f1fc1013e8dc3e51b5b7f991ad3e8270"), + "plugins/UsersManager/lang/lv.json" => array("3518", "aa74674018fce7f0ffc906377c7564c8"), + "plugins/UsersManager/lang/nb.json" => array("6035", "76c94a91f96c81ca4c24534524dc9249"), + "plugins/UsersManager/lang/nl.json" => array("4963", "440e1d01c6f56bff5a9bee31b15387c8"), + "plugins/UsersManager/lang/nn.json" => array("2846", "cfd8265eb96cb596b19602f72e28dc9e"), + "plugins/UsersManager/lang/pl.json" => array("4662", "90f8bc467d5af79ce2b6ca7cf98b0a2c"), + "plugins/UsersManager/lang/pt-br.json" => array("7815", "325dc7f1083cbf4351a90716c215bbea"), + "plugins/UsersManager/lang/pt.json" => array("4070", "4ff409b51db56fd59da780a57edf9241"), + "plugins/UsersManager/lang/ro.json" => array("6137", "a268fc98779cac49f0c5bb6b9de93bf4"), + "plugins/UsersManager/lang/ru.json" => array("9243", "25e74abf9d744eaba3614833a2353e43"), + "plugins/UsersManager/lang/sk.json" => array("2058", "0817dad62b9feb5288845797e0ba2ec3"), + "plugins/UsersManager/lang/sl.json" => array("1759", "212508815037820774d081cbb79e7db7"), + "plugins/UsersManager/lang/sq.json" => array("6490", "d47bb1f909c1961cffd2f9313df6410e"), + "plugins/UsersManager/lang/sr.json" => array("6034", "39199015c046fa4d7b93e3d0c4f3d3a3"), + "plugins/UsersManager/lang/sv.json" => array("7189", "7c4f2a41ac73e9615fc0ff32dee994a8"), + "plugins/UsersManager/lang/ta.json" => array("190", "9979b6abfcecefa85689b816a60ae616"), + "plugins/UsersManager/lang/te.json" => array("1105", "367dac75ecf70e15604315645e3da89a"), + "plugins/UsersManager/lang/th.json" => array("5669", "bbb513ec7fdb801efe50d3b6f730bde9"), + "plugins/UsersManager/lang/tl.json" => array("6424", "5e221e8da6a1d013d8524e9e5801cbab"), + "plugins/UsersManager/lang/tr.json" => array("2402", "02c132faa99d618a4427e209d9b754cd"), + "plugins/UsersManager/lang/uk.json" => array("4866", "0d4b98dd7920a8cb476cffd8cb71f4fd"), + "plugins/UsersManager/lang/vi.json" => array("5089", "f7abdc3c84dc642a5284352076c05504"), + "plugins/UsersManager/lang/zh-cn.json" => array("3647", "91f9e787e6ffb9ac6ffc2c2e154d0821"), + "plugins/UsersManager/lang/zh-tw.json" => array("5457", "18cb02cac0bd4c120f53cd729da85dd9"), + "plugins/UsersManager/LastSeenTimeLogger.php" => array("2242", "3b6e4ff03a54a9bf8d9cf9a6ce041b41"), + "plugins/UsersManager/Menu.php" => array("1010", "5e086c12e6bc6bb495b7099998c6430b"), + "plugins/UsersManager/Model.php" => array("9600", "ad998674846ffad441089d2e68c36fe4"), + "plugins/UsersManager/stylesheets/usersManager.less" => array("956", "054253daacd3aab5b21ef4e58ab537a5"), + "plugins/UsersManager/Tasks.php" => array("1509", "702658103000bd2464c3ed624d561816"), + "plugins/UsersManager/templates/anonymousSettings.twig" => array("3393", "c28fdcd11fc0cd0e7b3c1aa8f0f1c503"), + "plugins/UsersManager/templates/index.twig" => array("10810", "33285385f311ba9b04aec43df9dec955"), + "plugins/UsersManager/templates/noWebsiteAdminAccess.twig" => array("180", "0c0214158072b04b1d14c52b22bd434e"), + "plugins/UsersManager/templates/userSettings.twig" => array("5912", "93ea4ccd70a4caf66b5d94b7e44d1e93"), + "plugins/UsersManager/UserAccessFilter.php" => array("6105", "91cc7237150225eba83680096476e3c3"), + "plugins/UsersManager/UserPreferences.php" => array("4781", "0047722509afe73752d470ba80ceb2cc"), + "plugins/UsersManager/UsersManager.php" => array("6130", "02b42bcc2c4c72af584b622d1115cd69"), + "plugins/VisitFrequency/API.php" => array("2494", "6c28ba5bf7257a4e6221680b2ef1ee7e"), + "plugins/VisitFrequency/Columns/Metrics/ReturningMetric.php" => array("1719", "8e84485b2fea401070935da2a3f49e8c"), + "plugins/VisitFrequency/Controller.php" => array("4705", "8ee641b7023c537354660da86c13fab1"), + "plugins/VisitFrequency/lang/am.json" => array("798", "a9859a73bc78617ffd803e95bcd2ff79"), + "plugins/VisitFrequency/lang/ar.json" => array("1198", "deb18579c7dfaf65ffa0ef33f949ec35"), + "plugins/VisitFrequency/lang/be.json" => array("1799", "bdbdcdc6d183ef9a522ee597a5d031de"), + "plugins/VisitFrequency/lang/bg.json" => array("2463", "3f9fcb66521f74ee65a9546a6ba14d1d"), + "plugins/VisitFrequency/lang/ca.json" => array("1749", "23e895f0e37c0fee0ecfe34396fe783d"), + "plugins/VisitFrequency/lang/cs.json" => array("1993", "3957b6f8acf88374ec950a67e871a082"), + "plugins/VisitFrequency/lang/da.json" => array("1942", "c2a832ff46010fa5a0170f3f88b039f6"), + "plugins/VisitFrequency/lang/de.json" => array("1955", "459e05fea9a2c5d3594c5fa31cc448ee"), + "plugins/VisitFrequency/lang/el.json" => array("3063", "6ae86fc68cf1cef613f4b6e9a67d7cd6"), + "plugins/VisitFrequency/lang/en.json" => array("1760", "3b9f1cd6153315a58bc87344ac44dee4"), + "plugins/VisitFrequency/lang/es.json" => array("2009", "6f8c86c7d432d4a97b3ee5e66b9daebe"), + "plugins/VisitFrequency/lang/et.json" => array("1490", "4b1338d3cc516798e8bf61d4f3ba20d4"), + "plugins/VisitFrequency/lang/eu.json" => array("629", "80d25672d23f99900d600e74c41a9dae"), + "plugins/VisitFrequency/lang/fa.json" => array("2051", "fc7d6db8e3938727a5eb7001ad53b41e"), + "plugins/VisitFrequency/lang/fi.json" => array("1719", "f8aca2bab1f30235a50a390ba6e1a37a"), + "plugins/VisitFrequency/lang/fr.json" => array("1924", "82a428868de4deb20037fc025bc21202"), + "plugins/VisitFrequency/lang/gl.json" => array("330", "6045c8bd81c8d43858e3dc36af5bed75"), + "plugins/VisitFrequency/lang/he.json" => array("1370", "dfda4eef55185249389951ef98e40761"), + "plugins/VisitFrequency/lang/hi.json" => array("1929", "c2c8ea8a22ce24a4fdf31a80a358eb7e"), + "plugins/VisitFrequency/lang/hu.json" => array("2108", "112b6f75cfa92a5c54aaf193fa6723dc"), + "plugins/VisitFrequency/lang/id.json" => array("1802", "0a6ccab9fdc91c9bd56cca673637a2f1"), + "plugins/VisitFrequency/lang/is.json" => array("917", "e68a026be89ee9311b72cf05c12be207"), + "plugins/VisitFrequency/lang/it.json" => array("1895", "76b7b17ec1ad9998de6e8fc953c04a3d"), + "plugins/VisitFrequency/lang/ja.json" => array("2067", "164e5522f1eadc10b3fbe30194e85039"), + "plugins/VisitFrequency/lang/ka.json" => array("1861", "1e4b03a089f4027d2873d22e09d986ea"), + "plugins/VisitFrequency/lang/ko.json" => array("1754", "654f966b91026e065ec36f65fe29b150"), + "plugins/VisitFrequency/lang/lt.json" => array("1056", "2682b7818b5d94480549d4d5994fa6ad"), + "plugins/VisitFrequency/lang/lv.json" => array("346", "d17722b45616b503c30f7911fbb50162"), + "plugins/VisitFrequency/lang/nb.json" => array("1905", "42c2e3b332d4fc4ccbfb7f3c1d34b748"), + "plugins/VisitFrequency/lang/nl.json" => array("1897", "f827574fe70366591b3a789de8b0bdff"), + "plugins/VisitFrequency/lang/nn.json" => array("831", "019470ce322dd67ec38f8eb8bc1f488f"), + "plugins/VisitFrequency/lang/pl.json" => array("1404", "b24df0deddc51993113ccec9b8a14c04"), + "plugins/VisitFrequency/lang/pt-br.json" => array("1921", "7a6a045393dc746ee1f5ee036ede8643"), + "plugins/VisitFrequency/lang/pt.json" => array("1745", "30b8356585dbef8c1dd66a109fcd46e3"), + "plugins/VisitFrequency/lang/ro.json" => array("1694", "84af23d7f4010aa88a61af2e40aa32fb"), + "plugins/VisitFrequency/lang/ru.json" => array("2579", "fe177dd25410bad14a8196a7e95d92e3"), + "plugins/VisitFrequency/lang/sk.json" => array("1063", "2b50df44f0ac54a815a8d106119259b0"), + "plugins/VisitFrequency/lang/sl.json" => array("676", "379c4fbeec8ed70ce48ffa21da5dda48"), + "plugins/VisitFrequency/lang/sq.json" => array("1915", "be4cc1fe0b79dab9eef965b8df432145"), + "plugins/VisitFrequency/lang/sr.json" => array("1862", "0b1507255059863347122e5480cee4ab"), + "plugins/VisitFrequency/lang/sv.json" => array("1914", "8877825e6b1f1d402f101d3d8f36c0bb"), + "plugins/VisitFrequency/lang/te.json" => array("162", "0dd6ceeec79a95fda3f32b77df471a2d"), + "plugins/VisitFrequency/lang/th.json" => array("1755", "2bd6b9a79fc4634d6b1f9f1fb5f2ef2b"), + "plugins/VisitFrequency/lang/tl.json" => array("1883", "7fbd1b7a02b97b60d44c677a6cd3763b"), + "plugins/VisitFrequency/lang/uk.json" => array("1452", "5548d05e52df69d1e56e60d678ed1b63"), + "plugins/VisitFrequency/lang/vi.json" => array("2121", "3106558cabcc4f39f1b6d2a6308087f6"), + "plugins/VisitFrequency/lang/zh-cn.json" => array("1449", "fe61a6fea25c19789e69f5206df5a248"), + "plugins/VisitFrequency/lang/zh-tw.json" => array("912", "253a1b8535ae50b253c6f657f8b49872"), + "plugins/VisitFrequency/Menu.php" => array("456", "f7c343844315ced9fd4e3044e56a504c"), + "plugins/VisitFrequency/Reports/Get.php" => array("1233", "eeffeef5d65fa53e6d8cda49b943f131"), "plugins/VisitFrequency/templates/getSparklines.twig" => array("48", "1d2ec22a548fb6662979f19d9d3fc329"), "plugins/VisitFrequency/templates/index.twig" => array("338", "3eca543636e9b14ecb0208b6e03bf283"), - "plugins/VisitFrequency/templates/_sparklines.twig" => array("1064", "09eecc1ac6d13181fbec549185cacb88"), - "plugins/VisitFrequency/VisitFrequency.php" => array("2625", "84fd90736051331df11ca37a64917cea"), - "plugins/VisitorInterest/API.php" => array("4802", "3ea0c6255ab8ebb2deb7c600e300460a"), - "plugins/VisitorInterest/Archiver.php" => array("5175", "66968c131f5b948ecb49dbf5bdb4fc5f"), - "plugins/VisitorInterest/Controller.php" => array("1730", "99828a062bfe59a5dccafa741e001bff"), - "plugins/VisitorInterest/templates/index.twig" => array("533", "5f1579ad5941e520b87275aff6cbb729"), - "plugins/VisitorInterest/VisitorInterest.php" => array("10661", "d019fd8854d7087dd4948f230f6ae158"), - "plugins/VisitsSummary/API.php" => array("6176", "0780ead1ba4050b502a18c37f8c0f322"), - "plugins/VisitsSummary/Controller.php" => array("8277", "31b7cdca8c5b9293af2e2eeedf6adf60"), - "plugins/VisitsSummary/stylesheets/datatable.less" => array("113", "8ffed702781ee6746a4701fc5f5ff9f1"), + "plugins/VisitFrequency/templates/_sparklines.twig" => array("1487", "3ce7ebe07cfe9337971db098cc135df1"), + "plugins/VisitFrequency/VisitFrequency.php" => array("1397", "e47b957afe5e64e6d0b54f3adacd8860"), + "plugins/VisitFrequency/Widgets.php" => array("594", "7646664109f21ffc118b2ce85c66a7df"), + "plugins/VisitorInterest/API.php" => array("3958", "9137dfafe816d1608f20528ff2c93cef"), + "plugins/VisitorInterest/Archiver.php" => array("5527", "060776e5305e07c01c01e051945ead09"), + "plugins/VisitorInterest/Columns/PagesPerVisit.php" => array("408", "fa0253477d763a94a9cb1aaade3d6a4b"), + "plugins/VisitorInterest/Columns/VisitDuration.php" => array("408", "7eac5c276a814fc60094d09edaaf7278"), + "plugins/VisitorInterest/Columns/VisitsByDaysSinceLastVisit.php" => array("1234", "29760577f0c11fa637004fc340c776c8"), + "plugins/VisitorInterest/Columns/VisitsbyVisitNumber.php" => array("413", "7d1b07dbd69cbc756b29b918496af7e5"), + "plugins/VisitorInterest/Controller.php" => array("822", "1485acb7828a011fd838c6ef569abb5b"), + "plugins/VisitorInterest/lang/am.json" => array("498", "afdfea0e933b04482bc373c006f13a65"), + "plugins/VisitorInterest/lang/ar.json" => array("637", "1f1d823648ba3f171cd0ff6f586d8cbf"), + "plugins/VisitorInterest/lang/be.json" => array("1643", "0181fe9941e495bfce160b5bdf4ebe17"), + "plugins/VisitorInterest/lang/bg.json" => array("2820", "a5e9d04df1857d75aaef21225f5ede79"), + "plugins/VisitorInterest/lang/ca.json" => array("1672", "529d1689bc1819f0b3c82515a5fa3e6c"), + "plugins/VisitorInterest/lang/cs.json" => array("1824", "8eceff8a5e140e0b7169203fcfe42ecf"), + "plugins/VisitorInterest/lang/da.json" => array("1532", "fc4c9a91ac394fe24e0904a33a5502cd"), + "plugins/VisitorInterest/lang/de.json" => array("1843", "14a32a4855c62d0fcabc1289d03defcf"), + "plugins/VisitorInterest/lang/el.json" => array("2995", "f181eb3e7e3e2871608bbe3652d01ccf"), + "plugins/VisitorInterest/lang/en.json" => array("1692", "2a8f5210478e60165901708989446c2f"), + "plugins/VisitorInterest/lang/es.json" => array("1917", "544493024d3e7dccd2b3d5badeb08c9d"), + "plugins/VisitorInterest/lang/et.json" => array("853", "31c91bcdd93c6aa3fda8b7580e24ee4c"), + "plugins/VisitorInterest/lang/eu.json" => array("399", "ff5a86f49b98ba62ed463a30eeb5d483"), + "plugins/VisitorInterest/lang/fa.json" => array("2463", "da4857ff78103b7ecf095968a62680d2"), + "plugins/VisitorInterest/lang/fi.json" => array("1566", "8149e552599cc1aab9bfb202c9633b68"), + "plugins/VisitorInterest/lang/fr.json" => array("1911", "596e512c54b5a210ec2242cc89afa56f"), + "plugins/VisitorInterest/lang/gl.json" => array("265", "10d2e5e5891b97f278cf016e7a9f8e2c"), + "plugins/VisitorInterest/lang/he.json" => array("914", "0806ed577bfb934546a2b68b0d149bc1"), + "plugins/VisitorInterest/lang/hi.json" => array("1458", "f94b3777b165e7bdae161992c82127b7"), + "plugins/VisitorInterest/lang/hu.json" => array("576", "4814e4f7c51de86440335075b9db9ade"), + "plugins/VisitorInterest/lang/id.json" => array("1723", "acbb1cf16f7d671139105972e4d3ac44"), + "plugins/VisitorInterest/lang/is.json" => array("594", "9c997e535f53fd53c847d06a2d063484"), + "plugins/VisitorInterest/lang/it.json" => array("1888", "554aa06794f2253ea5640a54daf3d46b"), + "plugins/VisitorInterest/lang/ja.json" => array("1997", "2c8b553d60095a8a207732db7c2e48ad"), + "plugins/VisitorInterest/lang/ka.json" => array("907", "fe850779ddceb12f5f06ff2ec9a92474"), + "plugins/VisitorInterest/lang/ko.json" => array("1791", "a350eee566fc2170cde9ed969c7bc5fa"), + "plugins/VisitorInterest/lang/lt.json" => array("591", "defa79e7db518b0f02b47e689b7e7be4"), + "plugins/VisitorInterest/lang/lv.json" => array("629", "91a3ec187fd616b4f2aa21b97c38705e"), + "plugins/VisitorInterest/lang/nb.json" => array("522", "f00612c03981b13c275da2039bd43817"), + "plugins/VisitorInterest/lang/nl.json" => array("1796", "a6d9331c4d6abcce288a9d13a3fc3559"), + "plugins/VisitorInterest/lang/nn.json" => array("441", "3007e17d0f9e356c470b9722519d5b6a"), + "plugins/VisitorInterest/lang/pl.json" => array("829", "2776307a742aa80afc6b1b344986c2a0"), + "plugins/VisitorInterest/lang/pt-br.json" => array("1874", "1e53ad464d172a9dbaecf64fb8fcbf0c"), + "plugins/VisitorInterest/lang/pt.json" => array("1625", "2b18a3b066e37320b3dae8559c6e2aa4"), + "plugins/VisitorInterest/lang/ro.json" => array("1863", "c2f2419c3231615ad20080c6f533ca58"), + "plugins/VisitorInterest/lang/ru.json" => array("2618", "a707d8056b92b5b394c2b6285e2516f4"), + "plugins/VisitorInterest/lang/sk.json" => array("562", "910500aaf48cdfddf1590d92b8540f07"), + "plugins/VisitorInterest/lang/sl.json" => array("414", "6baaefa875ac81abf3ff4dd7ff48e764"), + "plugins/VisitorInterest/lang/sq.json" => array("1874", "94b583a751564bc1e4629df2feb00533"), + "plugins/VisitorInterest/lang/sr.json" => array("1782", "6c7b51cf03c3413c23ed47600445f55a"), + "plugins/VisitorInterest/lang/sv.json" => array("1546", "48a62afa1ca5bb629e3848a7b451956f"), + "plugins/VisitorInterest/lang/te.json" => array("716", "36f8b9dda75a02a390ee8423dbdd4e38"), + "plugins/VisitorInterest/lang/th.json" => array("806", "55256a306457753ce0ea351ab00a02e6"), + "plugins/VisitorInterest/lang/tl.json" => array("1749", "66b1a2039ebc58011f4c8f329c7626a2"), + "plugins/VisitorInterest/lang/tr.json" => array("77", "49a918373e5e6ee2107f1cbb6411910e"), + "plugins/VisitorInterest/lang/uk.json" => array("768", "fae5267e3a6056c21927ade3e315a3ff"), + "plugins/VisitorInterest/lang/vi.json" => array("2107", "66fe048c34313d5ee8da4de1bc5ceac7"), + "plugins/VisitorInterest/lang/zh-cn.json" => array("1564", "ebdad05e43f93969d7cb252b1f6b55e8"), + "plugins/VisitorInterest/lang/zh-tw.json" => array("566", "51bc1ac32fb46ada56e03c7e432dd3bb"), + "plugins/VisitorInterest/Menu.php" => array("497", "0452125d2d3ab8282525e16440a3947e"), + "plugins/VisitorInterest/Reports/Base.php" => array("348", "7f833e7b0b4cb002b23b795c51dd9732"), + "plugins/VisitorInterest/Reports/GetNumberOfVisitsByDaysSinceLast.php" => array("1751", "5d84b654a0706b5c7bf837b45bd59c8b"), + "plugins/VisitorInterest/Reports/GetNumberOfVisitsByVisitCount.php" => array("2149", "8be0eda6024ed40c5c71c06ef16247f2"), + "plugins/VisitorInterest/Reports/GetNumberOfVisitsPerPage.php" => array("2191", "a2f0e5ba10fbc497a5ba5c4e0cd5e4ee"), + "plugins/VisitorInterest/Reports/GetNumberOfVisitsPerVisitDuration.php" => array("2178", "715febeef8de9ecf5ceaeaa44c0d4a17"), + "plugins/VisitorInterest/templates/index.twig" => array("797", "b156d17107b581ab0561a093258faf9e"), + "plugins/VisitorInterest/VisitorInterest.php" => array("1154", "fc2248cc0407547b7db88d6ec52684ab"), + "plugins/VisitsSummary/API.php" => array("5049", "214a3706209f84e1f9c4b76ee3482079"), + "plugins/VisitsSummary/Controller.php" => array("9868", "7423407f657042195d81f78457ec6569"), + "plugins/VisitsSummary/lang/am.json" => array("683", "95bb188cae7671973785a03cd7141b41"), + "plugins/VisitsSummary/lang/ar.json" => array("999", "d0469f57660ddc2e53a4643d2a6d9edc"), + "plugins/VisitsSummary/lang/be.json" => array("1197", "316cd998746f6b5d49bdd616f8c4710f"), + "plugins/VisitsSummary/lang/bg.json" => array("1998", "afca35714f91a922d3e9918dc7d667b7"), + "plugins/VisitsSummary/lang/ca.json" => array("1459", "bb74dfabbddfe36a3266035c42f238bb"), + "plugins/VisitsSummary/lang/cs.json" => array("1681", "d45c2e323bcad9d70ebe8e8def9b29b1"), + "plugins/VisitsSummary/lang/da.json" => array("1511", "ee3363348f80c06fbcc04efe39a7cfa3"), + "plugins/VisitsSummary/lang/de.json" => array("1636", "944f6b097be7bc032bc49067583ac6e0"), + "plugins/VisitsSummary/lang/el.json" => array("2284", "ab5ac17a7425d9ca6b32ebc6b269027b"), + "plugins/VisitsSummary/lang/en.json" => array("1487", "59e85705ac628c7cc62600e029c03875"), + "plugins/VisitsSummary/lang/es.json" => array("1724", "8ccec3ba38e4a6cc24983f879af374d0"), + "plugins/VisitsSummary/lang/et.json" => array("1498", "b68ca41e08e12cad7f5fabae0102f3b4"), + "plugins/VisitsSummary/lang/eu.json" => array("570", "30a19c808a0900e8b2224957c64fb53c"), + "plugins/VisitsSummary/lang/fa.json" => array("1758", "bacdc64912aa3ed0f29cd65b86e9bfa6"), + "plugins/VisitsSummary/lang/fi.json" => array("1435", "74c29d5a69da3a9e71ffa6868d762caf"), + "plugins/VisitsSummary/lang/fr.json" => array("1605", "a0626a91ae4c36296fd884c5b254a953"), + "plugins/VisitsSummary/lang/gl.json" => array("430", "3c44a9ce700a5eba98bd899135907019"), + "plugins/VisitsSummary/lang/he.json" => array("1671", "748f3e58ef11f1ab392ca15f1327fe55"), + "plugins/VisitsSummary/lang/hi.json" => array("1332", "1fa9f07ca3ef6ad2dd9f6a8d651f8911"), + "plugins/VisitsSummary/lang/hr.json" => array("232", "10a6841c24a56e63761167d7aba590ad"), + "plugins/VisitsSummary/lang/hu.json" => array("930", "7db40333dfc4f7beab9fda4d1f5ae0f4"), + "plugins/VisitsSummary/lang/id.json" => array("1430", "26ccbec8c76f7112a898d6c3e53aa92c"), + "plugins/VisitsSummary/lang/is.json" => array("808", "36f485cfbeb59de812b4955a8f8ea4d8"), + "plugins/VisitsSummary/lang/it.json" => array("1587", "e4ba42096a5e91905a20374bd947d84a"), + "plugins/VisitsSummary/lang/ja.json" => array("1725", "72680af67935ac523352cca11cfc386e"), + "plugins/VisitsSummary/lang/ka.json" => array("1396", "e18109b461fb37ed5e25ee2cf3b33b35"), + "plugins/VisitsSummary/lang/ko.json" => array("1421", "40029cb612d69f30d5caef9042f7901f"), + "plugins/VisitsSummary/lang/lt.json" => array("831", "1aa05ed183d3cc0e092ab5f9c38142de"), + "plugins/VisitsSummary/lang/lv.json" => array("869", "1566193268254c5ac4ea525de8e074ad"), + "plugins/VisitsSummary/lang/nb.json" => array("1544", "17c8f53a35093a031614225774262d0a"), + "plugins/VisitsSummary/lang/nl.json" => array("1462", "c520e3ab809e31ab2a7e5dae337bb3d5"), + "plugins/VisitsSummary/lang/nn.json" => array("491", "46f1d9417854a76b9e73313ca7805d2a"), + "plugins/VisitsSummary/lang/pl.json" => array("1362", "fc72929ab84f35a06d4cf972bf5203af"), + "plugins/VisitsSummary/lang/pt-br.json" => array("1677", "265b2ca1de10d0f4660e62c8e8965017"), + "plugins/VisitsSummary/lang/pt.json" => array("1284", "10cc9e895c9df899b2f593886221294a"), + "plugins/VisitsSummary/lang/ro.json" => array("1441", "8e2ecc5c23fc7e11dff151fef38e672e"), + "plugins/VisitsSummary/lang/ru.json" => array("1945", "cfa4de675f6c6fbdca14b3dce0839d17"), + "plugins/VisitsSummary/lang/sk.json" => array("1423", "6532d18470ca616282efd9306acfb2c0"), + "plugins/VisitsSummary/lang/sl.json" => array("1520", "9fb2d68168c96f0119664325da2911d6"), + "plugins/VisitsSummary/lang/sq.json" => array("1621", "4ce588f7382a41d2bed48102f584cfe7"), + "plugins/VisitsSummary/lang/sr.json" => array("1567", "d9cd3917d83d90c375350c551c0c00b7"), + "plugins/VisitsSummary/lang/sv.json" => array("1353", "b6507b7a0bd88d1abb8a60a6573e905f"), + "plugins/VisitsSummary/lang/te.json" => array("307", "1944e2cb1637e070151edce17324fb6f"), + "plugins/VisitsSummary/lang/th.json" => array("1977", "d3ae23b81f246c720c628968ddf3aa27"), + "plugins/VisitsSummary/lang/tl.json" => array("1560", "56ce03bca820bae83fe6ce20e1653db6"), + "plugins/VisitsSummary/lang/tr.json" => array("847", "97da6cae97b07bf589d81b2b36a96c64"), + "plugins/VisitsSummary/lang/uk.json" => array("1200", "d554426c5ea139b9966e8c915cf38d69"), + "plugins/VisitsSummary/lang/vi.json" => array("1700", "daa62d16b28e8d3ca5c2752a05e3765c"), + "plugins/VisitsSummary/lang/zh-cn.json" => array("1407", "ec61165828183621eb9f5a55f9c1bb4a"), + "plugins/VisitsSummary/lang/zh-tw.json" => array("733", "598ade1ce62708d54fcf5a5b5860625d"), + "plugins/VisitsSummary/Menu.php" => array("506", "0710e47aeb8b3c349b9ea40030da7ca8"), + "plugins/VisitsSummary/Reports/Get.php" => array("2366", "4e6c3c79f9e717c6b317e6fa75e404cd"), + "plugins/VisitsSummary/stylesheets/datatable.less" => array("151", "a57b5fd08b0941f5005128ba0551cd69"), "plugins/VisitsSummary/templates/getSparklines.twig" => array("47", "f8630d90a9c3ab9ba0d5929d537ec5a1"), - "plugins/VisitsSummary/templates/index.twig" => array("634", "6ff7b1f46d57ca9e0a4b7e6293ffa81c"), - "plugins/VisitsSummary/templates/_sparklines.twig" => array("3505", "6f587c7c41c00b990065c5cde5a505cd"), - "plugins/VisitsSummary/VisitsSummary.php" => array("2864", "820091576b5aaef9873de6b4d1a8ad43"), - "plugins/VisitTime/API.php" => array("5806", "04226b406a95196b3ac7d653cb79b814"), - "plugins/VisitTime/Archiver.php" => array("2924", "cb73804d891689ebc1a79cf3ff6d6e2a"), - "plugins/VisitTime/Controller.php" => array("960", "6dce46cb47d8892f3e96e13d606b2f99"), - "plugins/VisitTime/functions.php" => array("800", "02b598cca67def3060e538d359da9c76"), - "plugins/VisitTime/templates/index.twig" => array("316", "483e2fc50e9e905201576c1638326782"), - "plugins/VisitTime/VisitTime.php" => array("9503", "867e6b57fb7e4f64219471c6f4ffe6b1"), - "plugins/Widgetize/Controller.php" => array("2404", "0d55e61621a887b086c60d086b66a271"), - "plugins/Widgetize/javascripts/widgetize.js" => array("3815", "b142cb72e9609949b895f517f1f19271"), - "plugins/Widgetize/stylesheets/widgetize.less" => array("523", "92ccef42a032893e61115dc1804a9d64"), + "plugins/VisitsSummary/templates/index.twig" => array("349", "5cbe6df89771d9549db8faafb746bad8"), + "plugins/VisitsSummary/templates/_sparklines.twig" => array("4398", "53c6da3a10a869c566cdb779c8bc971f"), + "plugins/VisitsSummary/VisitsSummary.php" => array("2145", "5a1ee76b23a9ce0b9601f59bcd6b9fea"), + "plugins/VisitsSummary/Widgets.php" => array("608", "2d6e35f428d1e5280d161650ae20bdc5"), + "plugins/VisitTime/API.php" => array("6074", "010ae0b7781b783bdce3a52ac4ab74cd"), + "plugins/VisitTime/Archiver.php" => array("3276", "9b987a4345d50fe02080f425fb474eae"), + "plugins/VisitTime/Columns/DayOfTheWeek.php" => array("385", "59e81797ab2a10eaf11a0349c96b34ec"), + "plugins/VisitTime/Columns/LocalTime.php" => array("1255", "8966f16bd24d3713fb365e8099398e7a"), + "plugins/VisitTime/Columns/ServerTime.php" => array("881", "eef4020f76634e51531077331c423936"), + "plugins/VisitTime/Controller.php" => array("616", "d32dcd20b20c59052a3263a05d61bc8e"), + "plugins/VisitTime/DataTable/Filter/AddSegmentByLabelInUTC.php" => array("1615", "ab85eb8bedf769a5657350caa617ccb6"), + "plugins/VisitTime/functions.php" => array("814", "1a1e626c952c35280b4597e4fa698d9d"), + "plugins/VisitTime/lang/am.json" => array("482", "1eaa48e10fd46aa9141202948faac467"), + "plugins/VisitTime/lang/ar.json" => array("518", "0fb1bbda44532b3e9856e17b12133564"), + "plugins/VisitTime/lang/be.json" => array("919", "15986698eb18b2e9e087139221906d3e"), + "plugins/VisitTime/lang/bg.json" => array("1330", "829c0b77f6aaf1d27ebd99feedd8f36d"), + "plugins/VisitTime/lang/ca.json" => array("1078", "7f429c772c0649e401558a44f5b78d8b"), + "plugins/VisitTime/lang/cs.json" => array("1061", "edd403c4231200553b316645097bfd48"), + "plugins/VisitTime/lang/da.json" => array("823", "d3e939b8bc7091225ea1485938d8564d"), + "plugins/VisitTime/lang/de.json" => array("1022", "e3616fbd13084f5b93ad01450c3e5c64"), + "plugins/VisitTime/lang/el.json" => array("1628", "bcd424c580d590c3e2c3032c33719a3f"), + "plugins/VisitTime/lang/en.json" => array("980", "de0fb9e0b33a27416c14726a3770f1d7"), + "plugins/VisitTime/lang/es.json" => array("1098", "0ef15eaa7c146ba28c5b40ec923a11d8"), + "plugins/VisitTime/lang/et.json" => array("535", "3662f0859e1af6708fb219cb8d3150c2"), + "plugins/VisitTime/lang/eu.json" => array("393", "9f5ecbb6d22606ea3c2d6023e8ad2972"), + "plugins/VisitTime/lang/fa.json" => array("791", "8764d8a0acc845389625bf2afd70ed1c"), + "plugins/VisitTime/lang/fi.json" => array("917", "9796ae4df82ea679b491e14e6e8dead6"), + "plugins/VisitTime/lang/fr.json" => array("1094", "8a3bb592a1f712437ef2b1ce7b5cdc7d"), + "plugins/VisitTime/lang/gl.json" => array("275", "e6d4286f68a37aa7d7f1d199d720c820"), + "plugins/VisitTime/lang/he.json" => array("697", "280fd4ba5d51b4444de2fef21ce2090e"), + "plugins/VisitTime/lang/hi.json" => array("965", "9aed89a9b6414c25df1948a2aaa47720"), + "plugins/VisitTime/lang/hr.json" => array("184", "e9e7516866e20384b70cb593c2786fed"), + "plugins/VisitTime/lang/hu.json" => array("399", "65e91f558b76fb802a09e499fb67d3ac"), + "plugins/VisitTime/lang/id.json" => array("936", "ac2c49496871e94abb959d8fe806d54b"), + "plugins/VisitTime/lang/is.json" => array("434", "3518d51dbd6dea8bcf57d24bd57c6dd2"), + "plugins/VisitTime/lang/it.json" => array("1035", "bb65167c969ed9a9fc78b41f0ce5a0ee"), + "plugins/VisitTime/lang/ja.json" => array("1129", "ecf14c867fe3ba60956f55132cb5d0ee"), + "plugins/VisitTime/lang/ka.json" => array("638", "84043e3892c979036cf6136b28b3777f"), + "plugins/VisitTime/lang/ko.json" => array("1043", "2387b4ef39343156633025e584a0e637"), + "plugins/VisitTime/lang/lt.json" => array("400", "5c7aa20069a61d497ac8ff4464d0a8fa"), + "plugins/VisitTime/lang/lv.json" => array("356", "8006109a2c5b7d50f331390f14ed7aff"), + "plugins/VisitTime/lang/nb.json" => array("970", "ee4580069107496e616d3327b19877f2"), + "plugins/VisitTime/lang/nl.json" => array("1047", "c9ade79ff3e45553c4b500d847ae196b"), + "plugins/VisitTime/lang/nn.json" => array("370", "f800a45a1be2412d45cc92eb1c5a597a"), + "plugins/VisitTime/lang/pl.json" => array("941", "b8f172d048662f6d488457b43857c594"), + "plugins/VisitTime/lang/pt-br.json" => array("1066", "2a6e717d03ab2df03ecaad894ce81084"), + "plugins/VisitTime/lang/pt.json" => array("671", "c89aea6a09dfb23ab31f54c03e964d66"), + "plugins/VisitTime/lang/ro.json" => array("921", "fcf1f9cc5d77efd77a19108ec99f4afd"), + "plugins/VisitTime/lang/ru.json" => array("1514", "fedada6c82c2df94a7c110056105de9a"), + "plugins/VisitTime/lang/sk.json" => array("1135", "1acb5e15832f250ecdbc2623f2a18ef5"), + "plugins/VisitTime/lang/sl.json" => array("403", "fe14d32caff9b64877d2324fda7b6a06"), + "plugins/VisitTime/lang/sq.json" => array("1062", "29de50a0144b38a0cb8a46862978b160"), + "plugins/VisitTime/lang/sr.json" => array("1004", "c4e88f7a7aee5415848920ea167a5c23"), + "plugins/VisitTime/lang/sv.json" => array("816", "7b08f8edad6e16ebf9047666dad73321"), + "plugins/VisitTime/lang/te.json" => array("121", "292486d6c3dabf467531a70ab9b1ae9e"), + "plugins/VisitTime/lang/th.json" => array("771", "8e708b71f78ca16a1815e23da1e366d5"), + "plugins/VisitTime/lang/tl.json" => array("1025", "1109aba0ca4bd1fb458d29b6efee5acb"), + "plugins/VisitTime/lang/tr.json" => array("482", "4e8e6df7cb01d0bd2f7b1b616470ab55"), + "plugins/VisitTime/lang/uk.json" => array("603", "44e42f732f705f5735aae016eebc0ef7"), + "plugins/VisitTime/lang/vi.json" => array("1165", "92db76916cf5237f390c8e5555897fe9"), + "plugins/VisitTime/lang/zh-cn.json" => array("943", "4e599e4fcc90c513691d34dc94a1182d"), + "plugins/VisitTime/lang/zh-tw.json" => array("457", "5fce2cf23ee4619d236e6769fac4705e"), + "plugins/VisitTime/Menu.php" => array("442", "42932ba63205e7be66f5d2a0922630d8"), + "plugins/VisitTime/Reports/Base.php" => array("1166", "bc120438f994f8c80aa63fda1a4bbc42"), + "plugins/VisitTime/Reports/GetByDayOfWeek.php" => array("2464", "b716571c46f6bf99cc8d1634b96c5fed"), + "plugins/VisitTime/Reports/GetVisitInformationPerLocalTime.php" => array("1660", "bb960f50c1d116f8f562fec276c8cf58"), + "plugins/VisitTime/Reports/GetVisitInformationPerServerTime.php" => array("1395", "d06171b29984d756e1eff0438f23dc40"), + "plugins/VisitTime/Segment.php" => array("364", "2b87bc68bc89dd748fe4c4c1292e9307"), + "plugins/VisitTime/templates/index.twig" => array("376", "282d1db3099e4873ebdb62ff4626230f"), + "plugins/VisitTime/VisitTime.php" => array("770", "6e144b89f92ca6502e37641e0f3a3992"), + "plugins/WebsiteMeasurable/lang/ar.json" => array("287", "9fceb0168b7ae814f3db1c0905b900af"), + "plugins/WebsiteMeasurable/lang/cs.json" => array("234", "b1ca395da313b787c5bd776fe379a806"), + "plugins/WebsiteMeasurable/lang/de.json" => array("241", "a88c319060045f8102b3e778a80db5c0"), + "plugins/WebsiteMeasurable/lang/el.json" => array("318", "056249dba1b5e37cd2350123d3e2402c"), + "plugins/WebsiteMeasurable/lang/en.json" => array("205", "b5b236a0200702e47de4e3442b7688c9"), + "plugins/WebsiteMeasurable/lang/es.json" => array("248", "b55bbe3ed7c7a96ea4ca8b758129300a"), + "plugins/WebsiteMeasurable/lang/fr.json" => array("233", "2b982424900b23ecb41c51a90a344b2c"), + "plugins/WebsiteMeasurable/lang/hi.json" => array("345", "a0308856b388de1cbb6b10b01ce1b0d2"), + "plugins/WebsiteMeasurable/lang/it.json" => array("213", "0996c72c89196bb4a7054de5466d5c33"), + "plugins/WebsiteMeasurable/lang/ja.json" => array("268", "ac4a735ad4dc4b18ebbecc698d5e4032"), + "plugins/WebsiteMeasurable/lang/ko.json" => array("239", "69849b5fc39ec4146d2b75089cd5f513"), + "plugins/WebsiteMeasurable/lang/lt.json" => array("67", "1c7597052fed5f2b6e23d41db0631c1a"), + "plugins/WebsiteMeasurable/lang/nb.json" => array("206", "d7ad676270c449f757d1c5916ecc3f7f"), + "plugins/WebsiteMeasurable/lang/nl.json" => array("224", "c1c6fc151842f4c96a9602129f6ef82b"), + "plugins/WebsiteMeasurable/lang/pt-br.json" => array("218", "9f16ef13cf9ff52276fada1d1e06dac6"), + "plugins/WebsiteMeasurable/lang/sk.json" => array("70", "4d159495da698d8c89c9c2f7d1f4d9cf"), + "plugins/WebsiteMeasurable/lang/sq.json" => array("91", "6971d9b145bbf208a8d722ef0900eae6"), + "plugins/WebsiteMeasurable/lang/sr.json" => array("195", "76be52d2eccc109d33645856c7eb1c0a"), + "plugins/WebsiteMeasurable/lang/sv.json" => array("203", "e5b776a87c0b70317e11626e92b66d00"), + "plugins/WebsiteMeasurable/lang/zh-tw.json" => array("194", "60b9e75d35d0dc739604bb76585d7cb0"), + "plugins/WebsiteMeasurable/plugin.json" => array("115", "7b57e4c193b1dadadf5bbe594e50cb9b"), + "plugins/WebsiteMeasurable/Type.php" => array("617", "305d1866aed3319ac2cbae25eb36f4b3"), + "plugins/WebsiteMeasurable/WebsiteMeasurable.php" => array("248", "5c4838b5efab69a3ccdee2488fecb4cf"), + "plugins/Widgetize/Controller.php" => array("1483", "97ff09cc8ccc84c5d5e431985bf7cec3"), + "plugins/Widgetize/javascripts/widgetize.js" => array("3849", "cc6cb621ea94dcf209dd0f168d6daa8b"), + "plugins/Widgetize/lang/ar.json" => array("91", "2e866e6ca9ac7423ea6dfb3022a0daaf"), + "plugins/Widgetize/lang/be.json" => array("95", "b82c96a69e9e2adb903f801aa0c73bfc"), + "plugins/Widgetize/lang/bg.json" => array("283", "784986c5791da36845e7ebcca2693f47"), + "plugins/Widgetize/lang/bn.json" => array("111", "a0496832d1185c1d7e0f9c08c130c74b"), + "plugins/Widgetize/lang/bs.json" => array("80", "cecce6df714236a71e26e4cf711f536e"), + "plugins/Widgetize/lang/ca.json" => array("219", "c5f9487c34e64886a301938ee0c6994b"), + "plugins/Widgetize/lang/cs.json" => array("341", "ddf3650a718eb72da2a5a582e8515972"), + "plugins/Widgetize/lang/cy.json" => array("83", "244418c080929068ea509c751a7e8245"), + "plugins/Widgetize/lang/da.json" => array("325", "4e5560af41a0473e4279e844fd8f4772"), + "plugins/Widgetize/lang/de.json" => array("343", "6863f2acf4bc08ef4bc302f0c3c3cd79"), + "plugins/Widgetize/lang/el.json" => array("557", "d52daed2944c5f27a95dba781565e2ec"), + "plugins/Widgetize/lang/en.json" => array("299", "ae26c857aef0f39c6e43d6e38b2464e1"), + "plugins/Widgetize/lang/es.json" => array("374", "c636c0581d8331d459103d8355e3a1c4"), + "plugins/Widgetize/lang/et.json" => array("72", "1a12e6d331bff21deda92908466b86c4"), + "plugins/Widgetize/lang/fa.json" => array("103", "50a0eb99db2519d488b134b3ebd60311"), + "plugins/Widgetize/lang/fi.json" => array("187", "62308e80c4d3a738ee0937ce86635c8a"), + "plugins/Widgetize/lang/fr.json" => array("360", "d2b73c5d4f6cd1e7765b88b3938b4046"), + "plugins/Widgetize/lang/gl.json" => array("78", "adb1f2936252409ffb735625a8377acf"), + "plugins/Widgetize/lang/he.json" => array("230", "0646e7104eb5033b71f62b778c28068a"), + "plugins/Widgetize/lang/hi.json" => array("662", "d38065a18769f8c858702b18e4d69635"), + "plugins/Widgetize/lang/hr.json" => array("80", "cecce6df714236a71e26e4cf711f536e"), + "plugins/Widgetize/lang/hu.json" => array("81", "2071513091be50b5ecc6bb11e9c8a033"), + "plugins/Widgetize/lang/id.json" => array("215", "1ea959dffec2c88103b7e544505e7823"), + "plugins/Widgetize/lang/is.json" => array("79", "e6f66da3731e91b279a0814ec7c3a283"), + "plugins/Widgetize/lang/it.json" => array("313", "901d1124af046ff979754723411be4b3"), + "plugins/Widgetize/lang/ja.json" => array("465", "4c9e9db01634528eda2b838beabe9df4"), + "plugins/Widgetize/lang/ka.json" => array("117", "9f5f6cd4c0830bf4d773a05853642f62"), + "plugins/Widgetize/lang/ko.json" => array("238", "bd6d600f45e69e1ccf4f1040b3ceeaa9"), + "plugins/Widgetize/lang/lt.json" => array("81", "43064f72119027d705552d01a907b62f"), + "plugins/Widgetize/lang/lv.json" => array("78", "b1806cc2716a1374c005b6f6c494c7eb"), + "plugins/Widgetize/lang/nb.json" => array("329", "19a21cdd6a1e6f81ffaedb392308f801"), + "plugins/Widgetize/lang/nl.json" => array("309", "6b8baa5e3e9940f6b0f397c1e1351c64"), + "plugins/Widgetize/lang/nn.json" => array("77", "6067d7ac7d157def1d54912902dc2f90"), + "plugins/Widgetize/lang/pl.json" => array("79", "93a8e5b46e7ae26f266f1284022eaac1"), + "plugins/Widgetize/lang/pt-br.json" => array("333", "37cbb10a9150d32b70c30f25970aad9d"), + "plugins/Widgetize/lang/pt.json" => array("78", "6ab16058baa2943b6a88221b2c6431f3"), + "plugins/Widgetize/lang/ro.json" => array("214", "15193282c8563815c59b1a6012e7a63a"), + "plugins/Widgetize/lang/ru.json" => array("501", "ebf6f3713dc00c9dfff91b5ac6f2f16f"), + "plugins/Widgetize/lang/sk.json" => array("79", "f28df735ea65e1c83aa36cfb6d015657"), + "plugins/Widgetize/lang/sl.json" => array("76", "96e61c13660c3168abb8a2b90f5f5f13"), + "plugins/Widgetize/lang/sq.json" => array("81", "1d22364c4ec0de96fa6d875efc8474d9"), + "plugins/Widgetize/lang/sr.json" => array("309", "9590a0a3718f7fdefc6c205abb10bf71"), + "plugins/Widgetize/lang/sv.json" => array("214", "01ad02d3f2bb1c7f14d762043fcfda21"), + "plugins/Widgetize/lang/th.json" => array("112", "775812ca6cf15e1d4f55b34f5ee560a5"), + "plugins/Widgetize/lang/tl.json" => array("213", "cf180efdb48d1665edbca98a68e788a6"), + "plugins/Widgetize/lang/tr.json" => array("76", "785efc82871ca9d3540b8692ce116641"), + "plugins/Widgetize/lang/uk.json" => array("94", "5cb2179f8e7b2e5b5b88f11ecb44ceb9"), + "plugins/Widgetize/lang/vi.json" => array("249", "e04cc5b270885aee0da4b2fe75d9738b"), + "plugins/Widgetize/lang/zh-cn.json" => array("202", "3ad7870c7a3679c88816e99416b4cbe1"), + "plugins/Widgetize/lang/zh-tw.json" => array("140", "b3e480ae12469801fd4e71b5d58f43f2"), + "plugins/Widgetize/Menu.php" => array("566", "3734232593cf3775e43f9e9165c4315e"), + "plugins/Widgetize/stylesheets/widgetize.less" => array("623", "a4404bf0c1fb2b21918d96fbac277c9d"), "plugins/Widgetize/templates/iframe_empty.twig" => array("17", "8a9aa8c925c824c53de88a0feca71be5"), - "plugins/Widgetize/templates/iframe.twig" => array("740", "b0e678fca11277a139e66eeaba321684"), - "plugins/Widgetize/templates/index.twig" => array("3680", "2e7dd9cc68bf352c6a219c989f020b16"), - "plugins/Widgetize/templates/testJsInclude1.twig" => array("662", "be3cc3e650ab96eade1172057d2577dc"), - "plugins/Widgetize/templates/testJsInclude2.twig" => array("709", "429886f9491d07389f73d803a0737a9b"), - "plugins/Widgetize/Widgetize.php" => array("2089", "9f1aa8d49cd9bdc7dd6e42991c8b982a"), - "plugins/Zeitgeist/images/affix-arrow.png" => array("3179", "542723bef4d8a61f3dc217352dc1873a"), - "plugins/Zeitgeist/images/annotations.png" => array("1627", "d8dab24c4203b462aa522642fcdd6947"), - "plugins/Zeitgeist/images/annotations_starred.png" => array("1618", "9fcbf35415857fb2a89435106641b0be"), - "plugins/Zeitgeist/images/arr_r.png" => array("195", "2708a22e4d851aff01d4db4f2fddf1da"), - "plugins/Zeitgeist/images/background-submit.png" => array("1347", "b1822012b50008b3e478d7ae890ead92"), - "plugins/Zeitgeist/images/chart_bar.png" => array("170", "05be19310c2236ac9c62e661bae95989"), - "plugins/Zeitgeist/images/chart_line_edit.png" => array("993", "7d7239985b27ced21c55d6de9810284c"), - "plugins/Zeitgeist/images/chart_pie.png" => array("355", "de043ca76bd7419b20150ceafbcf9312"), - "plugins/Zeitgeist/images/close.png" => array("1089", "47fc9f47a998804a1cb4e5728377a1fe"), - "plugins/Zeitgeist/images/collapsed_arrows.gif" => array("54", "224b095cbca536e579119a47328badda"), - "plugins/Zeitgeist/images/configure-highlight.png" => array("933", "a975c7cf6f594786581d997c1b2cc485"), - "plugins/Zeitgeist/images/configure.png" => array("387", "1e0d61849e3012c025554edd97f9cdd8"), - "plugins/Zeitgeist/images/dashboard_h_bg_hover.png" => array("333", "d062d8b2b65f012a196878c2f05db335"), - "plugins/Zeitgeist/images/dashboard_h_bg.png" => array("162", "a6cf0f7cdf6d69edca6edf479aa888e3"), - "plugins/Zeitgeist/images/data_table_footer_active_item.png" => array("145", "70dcc41079a073d03fee2628dcd0cf74"), - "plugins/Zeitgeist/images/datepicker_arr_l.png" => array("191", "10c37d1f62b0c21bc99ef3c3fabe4f7e"), - "plugins/Zeitgeist/images/datepicker_arr_r.png" => array("196", "e2f8672222d50df21b718654c424dd5f"), - "plugins/Zeitgeist/images/delete.png" => array("2175", "b3b9cb547a0511ff15b5371b122a6f66"), - "plugins/Zeitgeist/images/download.png" => array("734", "0552d1746701df879d14c2fdf3d5ac41"), - "plugins/Zeitgeist/images/ecommerceAbandonedCart.gif" => array("369", "51974d5002afd9b7c8009d14a1207aad"), - "plugins/Zeitgeist/images/ecommerceOrder.gif" => array("570", "b0c1aa6141f0047b4bcd0cc665c945ad"), - "plugins/Zeitgeist/images/email.png" => array("754", "baaa6accd945fcb4480b29ab2e15bded"), - "plugins/Zeitgeist/images/error_medium.png" => array("2622", "d789ca042860b20782cfb8c2bfd458e0"), - "plugins/Zeitgeist/images/error.png" => array("1150", "16ac5f1c769e78a074144f1fe9073dfb"), - "plugins/Zeitgeist/images/event.png" => array("164", "8e1d701795486cdc53cbf7a5c3b4d069"), - "plugins/Zeitgeist/images/expanded_arrows.gif" => array("60", "a9afa92168dbbe6f693f3ff71fa27b32"), - "plugins/Zeitgeist/images/export.png" => array("219", "bfcbff8f64765ec78685d04283857b9b"), - "plugins/Zeitgeist/images/feed.png" => array("691", "55bc1130d360583e2aecbcebfbf6eda7"), - "plugins/Zeitgeist/images/fullscreen.png" => array("346", "629df6e9cfdf1bb8d0a612f651da69e9"), - "plugins/Zeitgeist/images/goal.png" => array("270", "bc6edcd9d776933ea6b6cea5be1c6383"), - "plugins/Zeitgeist/images/help.png" => array("942", "471c83040c41ec0922eb4540992bcd1f"), - "plugins/Zeitgeist/images/html_icon.png" => array("3503", "45a005cf3fa96037df5d7935d26b5f7c"), - "plugins/Zeitgeist/images/ico_alert.png" => array("1112", "63d124bf79386ebf6285926956ff7829"), - "plugins/Zeitgeist/images/ico_delete.png" => array("231", "4065203e7b7471539eeff819b4cf443b"), - "plugins/Zeitgeist/images/ico_edit.png" => array("255", "32e1c11ad294948cfedff7c4daf76dad"), - "plugins/Zeitgeist/images/ico_info.png" => array("978", "362b1589e75a151c9e4d509615851950"), - "plugins/Zeitgeist/images/icon-calendar.gif" => array("331", "a0cffdd9fcf6552dea43658fc217732b"), - "plugins/Zeitgeist/images/image.png" => array("306", "2ea0351a26cdc37e0359ab011b832902"), - "plugins/Zeitgeist/images/inp_bg.png" => array("137", "1f4b8e7288c5d4dc52e44c50e0d02a9b"), - "plugins/Zeitgeist/images/li_dbl_gray.gif" => array("48", "5b0a692984ac5b04acc0886cd374bb85"), - "plugins/Zeitgeist/images/link.gif" => array("75", "b8de0b2b517e1999b32353209be4e976"), - "plugins/Zeitgeist/images/loading-blue.gif" => array("723", "6ce8f9a2c650cf90261acfc98b2edf90"), - "plugins/Zeitgeist/images/login-sprite.png" => array("10200", "a2a3520f448277c3efdb871d637fc34b"), - "plugins/Zeitgeist/images/logo-header.png" => array("3682", "f3a7921898ffcd44e413015b596c4e68"), - "plugins/Zeitgeist/images/logo-marketplace.png" => array("2927", "aa3dc0cd04c23a654e7a96ceabe2a6c1"), - "plugins/Zeitgeist/images/logo.png" => array("11152", "bc2b73b0541589e617fbb66b54ccc7ad"), - "plugins/Zeitgeist/images/logo.svg" => array("4044", "7f9586818f589f658c4dd7e7e5802274"), - "plugins/Zeitgeist/images/maximise.png" => array("3182", "e0018bf302603d234ae3d31c44fb3d1e"), - "plugins/Zeitgeist/images/minimise.png" => array("2869", "e746d4f1bfcc4d565a526e40918bcd0e"), - "plugins/Zeitgeist/images/minus.png" => array("176", "e010a638230ff96610f0ebffdfced0ac"), - "plugins/Zeitgeist/images/newtab.png" => array("509", "994c19f51192a18f7b3e0bc0775313f2"), - "plugins/Zeitgeist/images/ok.png" => array("626", "28501b0877ea15b49c6ca58677e186c3"), - "plugins/Zeitgeist/images/paypal_subscribe.gif" => array("3080", "a74a883239713fb5050593c20d9fd2a5"), - "plugins/Zeitgeist/images/plus_blue.png" => array("157", "9d61acb98c3ac639715aba6703997ad9"), - "plugins/Zeitgeist/images/plus.png" => array("174", "f867099b8f18cd9f989f1fcfb77cba37"), - "plugins/Zeitgeist/images/refresh.png" => array("2978", "b821bde25f3e4dcd0a7f6b9bd37f57b3"), - "plugins/Zeitgeist/images/reload.png" => array("892", "5a0360408c248f9cde4e0d4bad31ac00"), - "plugins/Zeitgeist/images/row_evolution_hover.png" => array("601", "7f6833f656aaad475e02a96ae3c0adb9"), - "plugins/Zeitgeist/images/row_evolution.png" => array("1934", "0e3fe13d82bc0526ed94bc079152f2f2"), - "plugins/Zeitgeist/images/search_bg.png" => array("384", "3105cf7e5419bef110507a682f28fab7"), - "plugins/Zeitgeist/images/search_ico.png" => array("175", "b74de7cb2b8cfddb82b95e63cd492286"), - "plugins/Zeitgeist/images/sites_selection.png" => array("120", "f8f6f62a17616adce09791ffb51aab1b"), - "plugins/Zeitgeist/images/smileyprog_0.png" => array("4045", "0b105851f9dfc4e5a3efb933c4fa01af"), - "plugins/Zeitgeist/images/smileyprog_1.png" => array("4268", "cd518d27567dda069dd5fff2e3c2291f"), - "plugins/Zeitgeist/images/smileyprog_2.png" => array("4292", "8e61661161afe5ac8a7e2caf085fb200"), - "plugins/Zeitgeist/images/smileyprog_3.png" => array("4589", "1eb75d0f84042d492b7ff4a515e23ff3"), - "plugins/Zeitgeist/images/smileyprog_4.png" => array("4733", "1f31e1ac3c6b3602cecabf0f0c15fd1b"), - "plugins/Zeitgeist/images/sortasc.png" => array("173", "bf15b38f5921052cd7d402a5a6e7542d"), - "plugins/Zeitgeist/images/sortdesc.png" => array("171", "3c918bc6390e75b19094de3b3db01578"), - "plugins/Zeitgeist/images/sort_subtable_asc_light.png" => array("2866", "a8d4e15a81c63b3d7f8ddde1ad5fc78d"), - "plugins/Zeitgeist/images/sort_subtable_asc.png" => array("173", "457908cb087009a946c99bef46643c69"), - "plugins/Zeitgeist/images/sort_subtable_desc_light.png" => array("286", "ba6261eca430661a8b84155460424106"), - "plugins/Zeitgeist/images/sort_subtable_desc.png" => array("171", "63b74c05d158e1bf7ed0172142991b24"), - "plugins/Zeitgeist/images/star_empty.png" => array("658", "31809a80055eb2aa02b51e6c11ecb02d"), - "plugins/Zeitgeist/images/star.png" => array("757", "872b7a1a8101bcf7ef6c7cf7c8f78ff7"), - "plugins/Zeitgeist/images/success_medium.png" => array("1346", "28e0ba1f8492374db4946d42c69e477b"), - "plugins/Zeitgeist/images/table_more.png" => array("200", "a8d8a758c97a1342864b646469059a02"), - "plugins/Zeitgeist/images/table.png" => array("151", "327ee0e75605ab865796053f2c0aebf1"), - "plugins/Zeitgeist/images/tagcloud.png" => array("202", "127389e4f7146b1322dd1873ee89235b"), - "plugins/Zeitgeist/images/video_play.png" => array("517", "29fd1c103c9ac9987b85e053e621ab20"), - "plugins/Zeitgeist/images/warning_medium.png" => array("1283", "24bc193a073997740e4aa459b2bbbbdf"), - "plugins/Zeitgeist/images/warning.png" => array("571", "8c4ef759f46a90e7a00e1db65e49edc9"), - "plugins/Zeitgeist/images/warning_small.png" => array("1083", "5ff491ccd2f32beb35d96fd79a4d7329"), - "plugins/Zeitgeist/images/zoom-out.png" => array("289", "b91cfbc280cfbf59fddc41348a78f2b6"), - "plugins/Zeitgeist/javascripts/ajaxHelper.js" => array("11827", "15e4b7bb70b671885e1597b7a61221bf"), - "plugins/Zeitgeist/javascripts/piwikHelper.js" => array("14986", "56e3acb719973a17f35316b74c155a3c"), - "plugins/Zeitgeist/plugin.json" => array("156", "90381b40a6f3b6e60f3d214bccdac5ab"), - "plugins/Zeitgeist/stylesheets/base.less" => array("555", "8bc4b5c0409a957679d70f19ab14a660"), - "plugins/Zeitgeist/stylesheets/general/_default.less" => array("2087", "47a06608a3652d1fd86254e314f81a5c"), - "plugins/Zeitgeist/stylesheets/general/_form.less" => array("2389", "6d43004e4fad124d15e45246e77bac1b"), - "plugins/Zeitgeist/stylesheets/general/_jqueryUI.less" => array("5147", "130ef76ab7dc45bbd50fcb4318aa5ada"), - "plugins/Zeitgeist/stylesheets/general/_misc.less" => array("576", "214e501d23f7e3f95f71644fbb50c886"), - "plugins/Zeitgeist/stylesheets/general/_utils.less" => array("391", "75d6698e72c26ffd6b562c3d738021e0"), - "plugins/Zeitgeist/stylesheets/ieonly.css" => array("506", "6a9862fb951ee731f968fee99d2480b3"), - "plugins/Zeitgeist/stylesheets/rtl.css" => array("53", "1be4a6f544e3ed971b966692b07d2c2d"), - "plugins/Zeitgeist/stylesheets/simple_structure.css" => array("1592", "e46a7d0ea302017e776fccc7b3db6060"), - "plugins/Zeitgeist/stylesheets/ui/_dataTable.less" => array("46", "29c2aa49f73efe255d4202c37a942ef5"), - "plugins/Zeitgeist/stylesheets/ui/_header.less" => array("739", "969c90051d99a050d9588f0bd02c4f82"), - "plugins/Zeitgeist/stylesheets/ui/_headerMessage.less" => array("1179", "16bf5e43fce5c2c39ff393c6c743a1aa"), - "plugins/Zeitgeist/stylesheets/ui/_languageSelect.less" => array("745", "6dd7f3399125f54809bda579420c1d6c"), - "plugins/Zeitgeist/stylesheets/ui/_loading.less" => array("360", "a48e9d0b63da915547a353ca0ace1656"), - "plugins/Zeitgeist/stylesheets/ui/_periodSelect.less" => array("1453", "07709490ca6206728d5274115a4cf71e"), - "plugins/Zeitgeist/stylesheets/ui/_siteSelect.less" => array("3503", "d0f0fec0d21d3013cc5e2540aff9f95c"), - "plugins/Zeitgeist/templates/admin.twig" => array("2040", "3801afc5349d0540bf749b5133d25ce0"), - "plugins/Zeitgeist/templates/ajaxMacros.twig" => array("632", "b6179aa5cdabd0f26b15ba6387f5af22"), - "plugins/Zeitgeist/templates/dashboard.twig" => array("2097", "0bb68e2e0d617932e57f3390d35aaccc"), - "plugins/Zeitgeist/templates/empty.twig" => array("35", "2c6a1dccfb394fef9ef03849c39a5bec"), - "plugins/Zeitgeist/templates/genericForm.twig" => array("991", "9e6a247b129a8b3932e2928273cc2b2f"), - "plugins/Zeitgeist/templates/_iframeBuster.twig" => array("385", "1c780792c51f5d2fecbf53c0ce0d547f"), - "plugins/Zeitgeist/templates/javascriptCode.tpl" => array("631", "8b239b1782acbb256597e965c49a7f92"), - "plugins/Zeitgeist/templates/_jsCssIncludes.twig" => array("278", "64e8a45c1d762222f4d3ac0fc072afbe"), - "plugins/Zeitgeist/templates/_jsGlobalVariables.twig" => array("1668", "4a163e13cc7cbf4bbf2723260f38765e"), - "plugins/Zeitgeist/templates/macros.twig" => array("891", "3343e6b0da8c13cd7f5d73bffcc86b49"), - "plugins/Zeitgeist/templates/_piwikTag.twig" => array("1217", "4d267c8a76a1c2212f160267095998ae"), - "plugins/Zeitgeist/templates/simpleLayoutFooter.tpl" => array("23", "c64111f363b9e1a0b0ea6d1c8ccbb9bd"), - "plugins/Zeitgeist/templates/simpleLayoutHeader.tpl" => array("511", "422d59273c966199cc15449891d697bb"), - "plugins/Zeitgeist/templates/_sparklineFooter.twig" => array("102", "a9e46848aaf5613b971827cf12ab6eaa"), - "README.md" => array("3813", "5ba34ead653bdb77ce52a3a4eeb221ba"), - "tests/README.md" => array("5681", "d9e5829add2c89dc0066f0299d82d7ed"), - "vendor/composer/autoload_classmap.php" => array("510", "91d089f53c9e426510a6f0292c655676"), - "vendor/composer/autoload_files.php" => array("255", "2cc6f4ba7d74bf4e09bff1ad7ed04816"), - "vendor/composer/autoload_namespaces.php" => array("341", "41fba06e8ccbd2eb2cf63e44a65345cd"), - "vendor/composer/autoload_psr4.php" => array("143", "dd3a00f0d13eb29781edd8c77d4c5100"), - "vendor/composer/ClassLoader.php" => array("11571", "3adcacc118804f98f1fd888e2575f00a"), - "vendor/composer/installed.json" => array("9348", "a1d1990a1a293648031fd49bc098b419"), - "vendor/leafo/lessphp/composer.json" => array("544", "e0d0ef78bbb2d2ea3db1be3ea7a34253"), - "vendor/leafo/lessphp/docs/docs.md" => array("38626", "b2eb5cf232f2d136fd4d14ba4f9c45f5"), - "vendor/leafo/lessphp/.gitignore" => array("71", "a51238606af0d1eb55ce05237e6778eb"), - "vendor/leafo/lessphp/lessc.inc.php" => array("91826", "b8ddd7795cbca49cad99e4a977d0b13d"), - "vendor/leafo/lessphp/lessify" => array("414", "e8e87d48dd91f4838219419395f1b996"), - "vendor/leafo/lessphp/lessify.inc.php" => array("9696", "e0c246fc5d113d6c42417b96f2e27b54"), - "vendor/leafo/lessphp/LICENSE" => array("33650", "2887747a1404ae4fb71a95d440d3778f"), - "vendor/leafo/lessphp/Makefile" => array("57", "bf733f8f889f8351526b07f50abf1dc1"), - "vendor/leafo/lessphp/package.sh" => array("758", "6bcb58ac88b574f30424245f65b2655d"), - "vendor/leafo/lessphp/plessc" => array("4911", "fb3df9045ff35eed6f8202c27a86ef14"), - "vendor/leafo/lessphp/README.md" => array("2817", "1fae9319ae33c7596e67d6adba644e13"), - "vendor/leafo/lessphp/.travis.yml" => array("57", "a37e9778c469a928db223337a03a9f04"), + "plugins/Widgetize/templates/iframe.twig" => array("791", "251a4b25bdab652b0bc6aaf41e4a8d70"), + "plugins/Widgetize/templates/index.twig" => array("3786", "c0964e648beb9609062d01fae7314f04"), + "plugins/Widgetize/Widgetize.php" => array("1823", "7fd3dd5cf9d2aa937c5807890fbd8d11"), + "PRIVACY.md" => array("4569", "641005cb30c18f6dc21dfd6e38a26e63"), + "README.md" => array("5355", "55c1ff24dade2aad1d0cf0607eeeeaee"), + "SECURITY.md" => array("1052", "5db5e0999e33a658dcdced04fe8d379d"), + "tests/README.md" => array("10421", "4a3f0037410bd36d024ce57839df7790"), + "vendor/composer/autoload_classmap.php" => array("253953", "09c7aadb018fb5b86f1a965fbd325d93"), + "vendor/composer/autoload_files.php" => array("325", "e6877f0ffe07bea4ee911abc9f9e95d1"), + "vendor/composer/autoload_namespaces.php" => array("1371", "70c4f68924ff8347a15eb07b7bd398e5"), + "vendor/composer/autoload_psr4.php" => array("885", "87c829bdd1cc35b90920cb50be6917f3"), + "vendor/composer/ClassLoader.php" => array("12466", "c67ebce5ff31e99311ceb750202adf2e"), + "vendor/composer/include_paths.php" => array("311", "9b2938881ce7597ff4f18807e52c4c05"), + "vendor/composer/installed.json" => array("46622", "bdec9a359fc85bc8ee0713b0232fb841"), + "vendor/composer/LICENSE" => array("1075", "084a034acbad39464e3df608c6dc064f"), + "vendor/container-interop/container-interop/composer.json" => array("304", "73b1b3bf2ddf4a1700c7acd74f8c3a2f"), + "vendor/container-interop/container-interop/docs/ContainerInterface.md" => array("5654", "a7329a38490dfa651289fb4500492c93"), + "vendor/container-interop/container-interop/docs/ContainerInterface-meta.md" => array("5342", "18278724c8f939ad8d74c2dfb577567f"), + "vendor/container-interop/container-interop/docs/Delegate-lookup.md" => array("2945", "893605b82872d606fa505f69dfb76245"), + "vendor/container-interop/container-interop/docs/Delegate-lookup-meta.md" => array("12499", "29f3e7bffa0896938e540d8919a8d0e8"), + "vendor/container-interop/container-interop/docs/images/interoperating_containers.png" => array("35971", "c8b901686c1055ce702cb43514655cbb"), + "vendor/container-interop/container-interop/docs/images/priority.png" => array("22949", "30083d1c148c9b9af6c2cdd64909bb57"), + "vendor/container-interop/container-interop/docs/images/side_by_side_containers.png" => array("22519", "9dd487bddd70c3ea19250cc2f10db192"), + "vendor/container-interop/container-interop/LICENSE" => array("1084", "a46a6933adb89c294f06f5420be849ea"), + "vendor/container-interop/container-interop/README.md" => array("3437", "6a46d69c1aa85e1b4e78a666f1d95d3f"), + "vendor/container-interop/container-interop/src/Interop/Container/ContainerInterface.php" => array("997", "45c3d55095602e2b5eadeb2f1b89d559"), + "vendor/container-interop/container-interop/src/Interop/Container/Exception/ContainerException.php" => array("253", "cc68c5023d35b24079a8966814310a1a"), + "vendor/container-interop/container-interop/src/Interop/Container/Exception/NotFoundException.php" => array("252", "bd2f95f06efcf84ae3cbca2ec4c875a0"), + "vendor/doctrine/annotations/composer.json" => array("976", "d54f54b6e09d8e243f1f50860a5432d7"), + "vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php" => array("1438", "2921761bd793c75cd5736f6886034042"), + "vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php" => array("1386", "0e523c927fc96fba49c0ff88f5fbd552"), + "vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php" => array("2599", "4e5dc8aab54f185d16858383e058bd30"), + "vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php" => array("5828", "27c858a1fa6e2836d123230cf1ed6f53"), + "vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php" => array("1866", "01c531f662d3b74854dc3c4774ec0a29"), + "vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php" => array("2494", "d20195a2215b1ea1c80dc810ce153592"), + "vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php" => array("12321", "95cf187ece8e402e81e028883b9b1b5b"), + "vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php" => array("4742", "019b497f1119f9e9ef4b34fee33648bc"), + "vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Required.php" => array("1274", "061739a3e0a2b49d16bed325deadb8a9"), + "vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php" => array("3294", "cac0c81c2a99b485d5e7d2cd7a35651a"), + "vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php" => array("6406", "f7f402bc030b13cab6c762199a8560d3"), + "vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php" => array("4059", "e773f401f139da60cd435f94c891e71d"), + "vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php" => array("38074", "31e63ae286df03e6c529b26b90f9f47c"), + "vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php" => array("8847", "461fa02ad73cafba36e31bec21fedece"), + "vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php" => array("3297", "86216f4ba484e600c72789f9ae7a55fe"), + "vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php" => array("2932", "d7bcd01f570e3e5a09b2e2ccfb911297"), + "vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php" => array("3527", "533c811e390904fcf708a92442bfcf1a"), + "vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php" => array("3657", "1ba46f12b1838f7da056c397007249ee"), + "vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php" => array("6119", "a60e19676ed026c65ef8929f4d86fed4"), + "vendor/doctrine/annotations/LICENSE" => array("1065", "2e75234cfca1e55b1cdce86615dccac9"), + "vendor/doctrine/annotations/README.md" => array("597", "82fa1351fe81ec873f57ad390ce69087"), + "vendor/doctrine/cache/build.properties" => array("143", "a5ab454bd1375b26122ae9489a049a65"), + "vendor/doctrine/cache/build.xml" => array("4132", "6c5ebac561ef7f795786ea6e7d178c61"), + "vendor/doctrine/cache/composer.json" => array("1085", "38d7ca768bc6ff2705611fd63cfce4e0"), + "vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php" => array("3157", "03f8af6da8380c598d8329cadc1889b1"), + "vendor/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php" => array("2459", "eb22acd84aa21dc269e8397f5cb82a97"), + "vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php" => array("3812", "fb7942f78f96a07f94c32dfe8f018fdf"), + "vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php" => array("7715", "20659fab0c93218b3cec54780d6af6c6"), + "vendor/doctrine/cache/lib/Doctrine/Common/Cache/ChainCache.php" => array("3786", "edaa0cfacbb1269da34784f33399ee11"), + "vendor/doctrine/cache/lib/Doctrine/Common/Cache/ClearableCache.php" => array("1542", "4881af3c08654515587a962d6a113bc0"), + "vendor/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php" => array("3176", "293e1d41557bf6b9ab5aef606c5e2b6a"), + "vendor/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php" => array("6469", "ba3a8959403481cf9db9c1faa3337b51"), + "vendor/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php" => array("2914", "f0c1a19e62a4d973daa5b638b7bfef42"), + "vendor/doctrine/cache/lib/Doctrine/Common/Cache/FlushableCache.php" => array("1391", "743326be176ade9f1a8dcc5cbebcf01e"), + "vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php" => array("3303", "d298a5f9cf639cbc5acd12238d80a7a7"), + "vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php" => array("3494", "5da2adb68cab71b74634bf2a0336ca39"), + "vendor/doctrine/cache/lib/Doctrine/Common/Cache/MongoDBCache.php" => array("5894", "01a73549b122634ecd5accdd0158a8c0"), + "vendor/doctrine/cache/lib/Doctrine/Common/Cache/MultiGetCache.php" => array("1603", "c97c479fcc9506c7474b114cba01a34d"), + "vendor/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php" => array("3354", "50838f97a46c320ac9881a11890093af"), + "vendor/doctrine/cache/lib/Doctrine/Common/Cache/PredisCache.php" => array("2277", "d49230c8cc60b70f1e5a2605885fb899"), + "vendor/doctrine/cache/lib/Doctrine/Common/Cache/RedisCache.php" => array("3639", "f9ce73e547a6a36c2fdb9e11d1ed2cc5"), + "vendor/doctrine/cache/lib/Doctrine/Common/Cache/RiakCache.php" => array("6810", "ca83e1086997bee24b0ac1da228cea7d"), + "vendor/doctrine/cache/lib/Doctrine/Common/Cache/SQLite3Cache.php" => array("5418", "27b869f86cc14381659397e56afc46d9"), + "vendor/doctrine/cache/lib/Doctrine/Common/Cache/Version.php" => array("1074", "02171d579a106229195944e167af7559"), + "vendor/doctrine/cache/lib/Doctrine/Common/Cache/VoidCache.php" => array("1937", "377ba38efc6aa8723cedd1b7d7b93b45"), + "vendor/doctrine/cache/lib/Doctrine/Common/Cache/WinCacheCache.php" => array("2671", "98c21113bee715fc23c1010927bc0804"), + "vendor/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php" => array("3161", "7bb6d247fd3010c037562b16195f0943"), + "vendor/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php" => array("2212", "ea438b0409996d058d562326a737dade"), + "vendor/doctrine/cache/LICENSE" => array("1065", "c781539da0fb35b5c5ad0fb72172ded6"), + "vendor/doctrine/cache/phpunit.xml.dist" => array("709", "614bfe87dcae0543274c4cb8ad35e86a"), + "vendor/doctrine/cache/README.md" => array("695", "7b5db3fa29c926511a6c72395c22f9d9"), + "vendor/doctrine/cache/UPGRADE.md" => array("726", "125ec12b697e4354b91d6e773f736472"), + "vendor/doctrine/lexer/composer.json" => array("738", "1f7629cdf37c77c53466fd4b66953a3d"), + "vendor/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php" => array("8084", "411100e0261c6a0de208a365fa1ede43"), + "vendor/doctrine/lexer/LICENSE" => array("1065", "2e75234cfca1e55b1cdce86615dccac9"), + "vendor/doctrine/lexer/README.md" => array("171", "3b9da77a5f3d874fcd05a0c9d5a51f91"), + "vendor/leafo/lessphp/composer.json" => array("546", "35dd24b2cac6551084bdf16744063267"), + "vendor/leafo/lessphp/docs/docs.md" => array("38664", "559d78269160f4dfed1bbe2e79233382"), + "vendor/leafo/lessphp/lessc.inc.php" => array("94979", "b91341fb580a2f2822e2868752e2b1a0"), + "vendor/leafo/lessphp/lessify" => array("413", "13835afac8dfcb883bb82827c40d6078"), + "vendor/leafo/lessphp/lessify.inc.php" => array("9686", "2fe52e278dac0f2b4c9a6169f7998b9f"), + "vendor/leafo/lessphp/LICENSE" => array("33647", "0eff5073f6de1624855d6de9ede3a60a"), + "vendor/leafo/lessphp/Makefile" => array("56", "11e5785c86764d87414ea98ba982c27a"), + "vendor/leafo/lessphp/package.sh" => array("797", "a27f7c79b86003d6c240e143dfa9a1fb"), + "vendor/leafo/lessphp/plessc" => array("4911", "be257c8e42dc4af90a3d1751ac9c85e1"), + "vendor/leafo/lessphp/README.md" => array("2823", "1542980726ea76f22c4d66979c0e5e0a"), + "vendor/mnapoli/phpdocreader/composer.json" => array("337", "5b20114af2f19f21e095340ad7d8fe9f"), + "vendor/mnapoli/phpdocreader/LICENSE" => array("1059", "7206d0fc5fca0b131ec0764753a95102"), + "vendor/mnapoli/phpdocreader/phpunit.xml.dist" => array("581", "75af35c6ca36f3406e4ceda2397a5741"), + "vendor/mnapoli/phpdocreader/README.md" => array("1539", "5bc299c03ded36dd6cefb1e9337cea8e"), + "vendor/mnapoli/phpdocreader/src/PhpDocReader/AnnotationException.php" => array("138", "97a7e0551660425c8a7f27cf04613d79"), + "vendor/mnapoli/phpdocreader/src/PhpDocReader/PhpDocReader.php" => array("8565", "94c5c1fcecc2b2af2d4d470ecf629917"), + "vendor/monolog/monolog/CHANGELOG.mdown" => array("14423", "cf86c859791758af76b7d27857b8b7e1"), + "vendor/monolog/monolog/composer.json" => array("2427", "d8432f4f488f0d8a246b62fd4bc6d296"), + "vendor/monolog/monolog/doc/01-usage.md" => array("8373", "87ca7ebcac15522f556963ed51c6fc09"), + "vendor/monolog/monolog/doc/02-handlers-formatters-processors.md" => array("8742", "f9da532c46e65aa49f3da5fc83fe138f"), + "vendor/monolog/monolog/doc/03-utilities.md" => array("798", "c4d3031fb9b82d8b5a0dbfa2de11ea80"), + "vendor/monolog/monolog/doc/04-extending.md" => array("2159", "22cf44916f0e58c64d69e9243cfd2a9a"), + "vendor/monolog/monolog/doc/sockets.md" => array("1023", "099d9fc2bd124a1d9f4e4f3d760428ad"), + "vendor/monolog/monolog/LICENSE" => array("1063", "b774b694b61e60534c6165cc6fea26c2"), + "vendor/monolog/monolog/phpunit.xml.dist" => array("460", "cd6f51f27a721b92f87c95c7fb6e2d32"), + "vendor/monolog/monolog/README.mdown" => array("4047", "b49184292c80bd09b7f6cade7952fe88"), + "vendor/monolog/monolog/src/Monolog/ErrorHandler.php" => array("7674", "39bb8b4fc9ef5cc91603e99e5b919940"), + "vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php" => array("2071", "71556b69ade93eb7eb296611dcde581e"), + "vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php" => array("1743", "cdf84d2666304c10184eb4ee0787e4be"), + "vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php" => array("2548", "ba10a9093d8c9fe1a2b2acbbc52803fd"), + "vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php" => array("787", "6393c1db9899b0f9e3fc4ad3d6a898dd"), + "vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php" => array("3304", "d440fde52d75340eb1a307cba0970e64"), + "vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php" => array("4532", "b8f7335803da9715cfc9284f87ddeccd"), + "vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php" => array("2765", "e95ef9e43b17778829fc6fbceb7600a2"), + "vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php" => array("4679", "aa5d7736bc7e9c03bd4cb8a6de05c745"), + "vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php" => array("1326", "17c423e50573a684d3f51539d6f27558"), + "vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php" => array("5315", "e1a2a4f21d7d874af2a05855eb002a39"), + "vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php" => array("3260", "c9de870b2087e9881756ccddcb751987"), + "vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php" => array("5681", "4bdb115724509f1158372799fcdeb874"), + "vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php" => array("1040", "9934bbe7fbf1a7536cbabf0bb31fac8b"), + "vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php" => array("3234", "0a57bdcd42584e1b41e4e093631a22f6"), + "vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php" => array("4048", "ef58ebfe96434d93ff4152662939652c"), + "vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php" => array("1498", "cfdd846fac8ce3cb15afe9188575ed37"), + "vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php" => array("2877", "551fa48711e5e1142432094b3af97727"), + "vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php" => array("2750", "5123da7e55282796202c2bc224815c93"), + "vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php" => array("6213", "951155eebf37067d40ecea01665ab451"), + "vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php" => array("3436", "f6c37a9f3618fb4f32f572262c81d9ca"), + "vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php" => array("5339", "3c79764d7480846b0bd0c3bba5d7386c"), + "vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php" => array("1952", "d0457dc4ae523c5b1d6291ab46e4a62e"), + "vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php" => array("4543", "63b6f8364862bed65a29a8d95ec1dc76"), + "vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php" => array("1487", "4b4aafdac634c3314ad07ceb7d158a3f"), + "vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php" => array("1000", "abc48686f395a089e55a2ec1a4fe4b78"), + "vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php" => array("2159", "a7f97af3c1967d4ee7699059071681bd"), + "vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php" => array("3417", "303b76d4f8d8f6f7a92150e83894d2f1"), + "vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php" => array("2380", "1854e5b1850705f60afdeb45cf80cd77"), + "vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php" => array("4383", "883312f37345d1a10bd6dfc5210ed999"), + "vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php" => array("651", "6bf84f27f4b7a0016afa63430cf6f8be"), + "vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php" => array("1927", "6827ce52b4bc0d222300712248c1b40d"), + "vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php" => array("759", "f31a9ad59022efe0de63808d275a0031"), + "vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php" => array("5504", "6132332bdf5482db41668e241dca2662"), + "vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php" => array("5466", "8b326519d23fccc55225f797f5f499fc"), + "vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php" => array("3362", "5e872dfb71c9c5e3dd53ac0035893a31"), + "vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php" => array("3357", "94c7a4507949751276a6fef75b92a529"), + "vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php" => array("2068", "780522085f93612bab462ff2538b4d6b"), + "vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php" => array("1889", "4bc9e4378c31a77b5e94e94b4b70ef9a"), + "vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php" => array("2597", "ea71b279940a8c6b3e8bec2f8ab73ab5"), + "vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php" => array("9737", "c3122d27dcebcca8f0945f8c5fac4fe8"), + "vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php" => array("2184", "1521d3a1b2b74166bb4fe3656b0dc969"), + "vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php" => array("1605", "4f41355fbf6aca980dc7def2da449a14"), + "vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php" => array("2619", "beee53f1c85666d49978d0d8c0324a2c"), + "vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php" => array("1295", "54cee36784ef08875244672c7002252c"), + "vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php" => array("2156", "cd5054f63dc7b68f28f75ad6e115d5fa"), + "vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php" => array("450", "99ec7108652f07752a6e4585f781ee92"), + "vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php" => array("1384", "2a8a13591a8be648d0558bbe942f85d8"), + "vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php" => array("4920", "6fdae5a11f32fc764fcffe120fe2b25a"), + "vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php" => array("5735", "2b728877c085a706cdcc2685f841c4eb"), + "vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php" => array("957", "d2f5ef3d84e4f42bcff3084a5d8fbb98"), + "vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php" => array("9954", "1bc4357a76a9411de757cf4d30402bfc"), + "vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php" => array("1433", "541ce4f4b61a8531b8fdf5eaa0328fe2"), + "vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php" => array("6631", "bd6eb75321034f790d48d44b450e3f94"), + "vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php" => array("6334", "7be2e7dfa61880e81ed209478600ea0d"), + "vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php" => array("2893", "e636e693cb9556553ec382dec10e64c3"), + "vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php" => array("2612", "8a1ace7cd474ff3fed86e56cd7d8ff6f"), + "vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php" => array("4531", "b7a31238c5d06f097e1f6541cba8c850"), + "vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php" => array("2674", "85fc2f7a5cf4f34749aa70cbc8c732e6"), + "vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php" => array("8677", "a42c874dc0fa8adff4513a54c51b6700"), + "vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php" => array("7348", "6aef6bf09be47775be2a208b9b49377d"), + "vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php" => array("4554", "841979a4004cbb61b984d1df390b54d8"), + "vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php" => array("2673", "4d572880045b536e0b05a371e76e0a82"), + "vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php" => array("1848", "7a0bad8d1fb73a94adeb0d398eb42ad9"), + "vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php" => array("2036", "b08b66b2cad7d5b49ff5cfab85d0e5c4"), + "vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php" => array("1401", "3585d916f9c3280d9d2c61abc6aa8c0a"), + "vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php" => array("4657", "ee8545a99923fc654730f839cd9bafe5"), + "vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php" => array("1343", "cc7c8c6047b554c24a682575a53aa4bc"), + "vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php" => array("2240", "dc2ee6716c9dee2ef3d420c41ac0dc2b"), + "vendor/monolog/monolog/src/Monolog/Logger.php" => array("17796", "e423649d9768bc945e5d21d71c75133b"), + "vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php" => array("1406", "34832c01dc9cc6500d9cbc0ac648c276"), + "vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php" => array("3055", "1ef85be69eebdbad632f50bd1af4b880"), + "vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php" => array("790", "b753a93665221b4ec3bb9b7e673a94a6"), + "vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php" => array("1821", "85e6b9ede44b839f6224eb1d442aece5"), + "vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php" => array("771", "6497ea95b20d9ae90826819cbc6d0676"), + "vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php" => array("574", "a8b0bbeab67f2bfcc0712d369357fefa"), + "vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php" => array("1272", "2511e6abb86e0eee16edcf818fa68d57"), + "vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php" => array("820", "f4abf7ee4c7b8fd5afa3b44c4e7e665c"), + "vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php" => array("944", "8872a7a39362441f4deea3ef866a0b9f"), + "vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php" => array("2876", "bbaa327b04a82135d5d93a5951e17fae"), + "vendor/monolog/monolog/src/Monolog/Registry.php" => array("4024", "efd8f92882e827054fd5d442fb8c06b4"), "vendor/mustangostang/spyc/composer.json" => array("588", "c3ed21823389befa4e8c8b81d0f3be63"), "vendor/mustangostang/spyc/COPYING" => array("1077", "b0987325db5fa8e52b7076860d8e2e0a"), "vendor/mustangostang/spyc/examples/yaml-dump.php" => array("974", "c18b833f0057beb6a5845bf4335c68e0"), @@ -2947,133 +6398,440 @@ class Manifest { "vendor/mustangostang/spyc/README" => array("5772", "d426a4028be21f70da24b9593df400a1"), "vendor/mustangostang/spyc/Spyc.php" => array("31690", "1ff392881801c81f27aa8ea2e2bd8f2a"), "vendor/mustangostang/spyc/spyc.yaml" => array("3609", "796845442758ffc5887b517580d960f1"), - "vendor/piwik/device-detector/composer.json" => array("958", "8c2246083e5b6d8752263c1074522890"), - "vendor/piwik/device-detector/DeviceDetector.php" => array("31360", "54677c9e65f2db309b04c0ef43d856b8"), - "vendor/piwik/device-detector/.gitignore" => array("30", "5a959e73a428396446cf2fc274e91c26"), - "vendor/piwik/device-detector/README.md" => array("391", "0a04395636c6a38d3f2b5fbaddaf054c"), - "vendor/piwik/device-detector/regexes/browsers.yml" => array("10214", "a745684b695696c1318751816b6ca365"), - "vendor/piwik/device-detector/regexes/mobiles.yml" => array("27043", "a9b5c80b5d331af8f1e8734265993ac0"), - "vendor/piwik/device-detector/regexes/oss.yml" => array("8511", "1b49d6cab78daeb3d7fe5fdca86a2ce8"), - "vendor/piwik/device-detector/regexes/televisions.yml" => array("3886", "6ed102a54f0be2e7413a5799b0f32a02"), - "vendor/piwik/device-detector/.travis.yml" => array("155", "f7214c64927a7b580f239487842ddc32"), - "vendor/symfony/console/Symfony/Component/Console/Application.php" => array("35538", "d7fa5d3e678f32b207a275d566bd226b"), - "vendor/symfony/console/Symfony/Component/Console/CHANGELOG.md" => array("1682", "e2b61d6b18ded64f0686599db5c7fb5b"), - "vendor/symfony/console/Symfony/Component/Console/Command/Command.php" => array("16439", "80d4d8e858f5a83230701aed15e6f9ff"), - "vendor/symfony/console/Symfony/Component/Console/Command/HelpCommand.php" => array("2655", "d10467979ea85e92951425fd351347ac"), - "vendor/symfony/console/Symfony/Component/Console/Command/ListCommand.php" => array("2752", "7f0bd50020e8b9c39d79692f3e32db8f"), - "vendor/symfony/console/Symfony/Component/Console/composer.json" => array("869", "9e146f5ae7085917809444efe2d868c6"), - "vendor/symfony/console/Symfony/Component/Console/ConsoleEvents.php" => array("1528", "10001de60b8e31092612e73dc02300d7"), - "vendor/symfony/console/Symfony/Component/Console/Descriptor/ApplicationDescription.php" => array("3513", "e5ffa840297bdf9232762c38218846dc"), + "vendor/pear/archive_tar/Archive/Tar.php" => array("82096", "89b230679f31da6f8dbdea25095f4ca9"), + "vendor/pear/archive_tar/composer.json" => array("1298", "b6aee05802817df0d650c33f0bcad5b9"), + "vendor/pear/archive_tar/docs/Archive_Tar.txt" => array("19110", "2fb90f0be7089a45c09a0d1182792419"), + "vendor/pear/archive_tar/package.xml" => array("13704", "93251d391179f24fca007a351c371a86"), + "vendor/pear/archive_tar/README.md" => array("787", "6d781414f77bf296d8cb8adb887280b6"), + "vendor/pear/archive_tar/scripts/phptar.in" => array("5086", "58e1e54d0bde1da8df7bc8714ca2405d"), + "vendor/pear/archive_tar/sync-php4" => array("180", "b8da2130b2b0f925d71f192f8d1d7fbb"), + "vendor/pear/console_getopt/composer.json" => array("885", "df3e25b43a37d97cb30ca6f388cc5434"), + "vendor/pear/console_getopt/Console/Getopt.php" => array("13412", "013530beb065fb8885f70af40b6c91fa"), + "vendor/pear/console_getopt/LICENSE" => array("1303", "ecc3a78fa3a689a22f34a11c5fbba675"), + "vendor/pear/console_getopt/package.xml" => array("6645", "8f9f4d05b41a964f02c88103291e0582"), + "vendor/pear/console_getopt/README.rst" => array("676", "d67e2a1237dc6ccdcb278aaabbef75f8"), + "vendor/pear/pear-core-minimal/composer.json" => array("799", "1da3ec0821b35b0caf98438e7d0bfb16"), + "vendor/pear/pear-core-minimal/copy-from-pear-core.sh" => array("223", "6f29ef0e0b810f9cd60092b0e984bb2d"), + "vendor/pear/pear-core-minimal/README.rst" => array("645", "2b962148ccdae13496feb9c6dcce0065"), + "vendor/pear/pear-core-minimal/src/OS/Guess.php" => array("10597", "39ae18d707e1aa6493a304cfc05d6e8b"), + "vendor/pear/pear-core-minimal/src/PEAR/Error.php" => array("323", "4139228d6b0e8de4bce672ffb0e48b2d"), + "vendor/pear/pear-core-minimal/src/PEAR/ErrorStack.php" => array("33807", "971bcf5708fcfab66a5e0761584a52a6"), + "vendor/pear/pear-core-minimal/src/PEAR.php" => array("35478", "dbcaa6e38da7f082d2004bcc030394e2"), + "vendor/pear/pear-core-minimal/src/System.php" => array("20299", "1729151097130d198ee7ac7755eb8586"), + "vendor/pear/pear_exception/composer.json" => array("962", "687020e9a73effe827e6ebc8e1b1bbdd"), + "vendor/pear/pear_exception/LICENSE" => array("1477", "45b44486d8090de17b2a8b4211fab247"), + "vendor/pear/pear_exception/package.xml" => array("3191", "cbf90547a920d34fab8018de219a14c4"), + "vendor/pear/pear_exception/PEAR/Exception.php" => array("15395", "9781b1d3656eb64a92430c97ee9a0c83"), + "vendor/php-di/invoker/composer.json" => array("658", "70958991141163ea5b8f5c121244e18d"), + "vendor/php-di/invoker/CONTRIBUTING.md" => array("834", "aa65bb191159fd722e2e7596bb1a8436"), + "vendor/php-di/invoker/doc/parameter-resolvers.md" => array("3474", "c5ff2cfdde5590e46b6319c787396bff"), + "vendor/php-di/invoker/LICENSE" => array("1077", "d1af6aad6f0f8f4e625c884b610e3c52"), + "vendor/php-di/invoker/README.md" => array("7746", "e72fea98e20f061b5ce241e43ba6ee42"), + "vendor/php-di/invoker/src/CallableResolver.php" => array("3946", "0022235457a99ab50eaa70e525de3bc7"), + "vendor/php-di/invoker/src/Exception/InvocationException.php" => array("184", "19cfae03502d258fbfe71463ac26898b"), + "vendor/php-di/invoker/src/Exception/NotCallableException.php" => array("204", "3c0ada74d8fbd10420393473eafa8a53"), + "vendor/php-di/invoker/src/Exception/NotEnoughParametersException.php" => array("231", "d4087c9580404276196e8e12d291f215"), + "vendor/php-di/invoker/src/InvokerInterface.php" => array("764", "3fdae5ae733c062d8079bfc88a4761e5"), + "vendor/php-di/invoker/src/Invoker.php" => array("3389", "8e07688dee37b43b24b41184d5cfc821"), + "vendor/php-di/invoker/src/ParameterResolver/AssociativeArrayResolver.php" => array("1128", "5af2f36e60a183ca9405bef1815ee096"), + "vendor/php-di/invoker/src/ParameterResolver/Container/ParameterNameContainerResolver.php" => array("1339", "d0e48cb8de070fa0e794092fdc7509cc"), + "vendor/php-di/invoker/src/ParameterResolver/Container/TypeHintContainerResolver.php" => array("1387", "d535f0145bdf7d310f42842d0a72bd84"), + "vendor/php-di/invoker/src/ParameterResolver/DefaultValueResolver.php" => array("1154", "b472d4c6750f78d0fb41ca4fbda44161"), + "vendor/php-di/invoker/src/ParameterResolver/NumericArrayResolver.php" => array("1089", "cea858228812ef19d33bed176cf48427"), + "vendor/php-di/invoker/src/ParameterResolver/ParameterResolver.php" => array("1006", "ef78d686a08ca6a4b8e7d92a3b678df8"), + "vendor/php-di/invoker/src/ParameterResolver/ResolverChain.php" => array("1756", "efd0937a455fb68686fe44fb16f4dff8"), + "vendor/php-di/invoker/src/Reflection/CallableReflection.php" => array("1596", "db85dc1d9338d816c1ca64f4504e167c"), + "vendor/php-di/php-di/404.md" => array("20", "53eebfedc3afd2e33a7816dd46c5d8c1"), + "vendor/php-di/php-di/change-log.md" => array("14539", "a4826d6c255b6fe0ff4b3b78f3f09664"), + "vendor/php-di/php-di/composer.json" => array("1248", "9e4e4bb2c1a9f107f7d5d7ae026dc5db"), + "vendor/php-di/php-di/CONTRIBUTING.md" => array("1424", "07edb8d01e3f9a43a81650bc6fff7cc2"), + "vendor/php-di/php-di/couscous.yml" => array("3390", "095d8175f02dc19339c8b830dca50af9"), + "vendor/php-di/php-di/LICENSE" => array("1089", "e9ca821bc26512de64035c2dff023766"), + "vendor/php-di/php-di/phpunit.xml.dist" => array("846", "68fd99ec416c05fdfbd14ebc01baf79e"), + "vendor/php-di/php-di/README.md" => array("1652", "aa6599e26e370dcca073059c8ac2e972"), + "vendor/php-di/php-di/src/DI/Annotation/Injectable.php" => array("1564", "a649507af04313d36219606ce8b8f8ac"), + "vendor/php-di/php-di/src/DI/Annotation/Inject.php" => array("2180", "16c5aa80f44a769eb120bc13723e60ae"), + "vendor/php-di/php-di/src/DI/ContainerBuilder.php" => array("7098", "33128814f59c0c89a9b00046dba87718"), + "vendor/php-di/php-di/src/DI/Container.php" => array("11467", "6f74af5cb5dd3be16c6feb9df2a0ce32"), + "vendor/php-di/php-di/src/DI/Debug.php" => array("766", "57998d756b251418d76b979ee11b9a2d"), + "vendor/php-di/php-di/src/DI/Definition/AbstractFunctionCallDefinition.php" => array("1758", "4e0197bff28c8e4b7594c6fc49ad8150"), + "vendor/php-di/php-di/src/DI/Definition/AliasDefinition.php" => array("1221", "2e207ee83335a8d1c4e0ef0e629f0515"), + "vendor/php-di/php-di/src/DI/Definition/ArrayDefinitionExtension.php" => array("1384", "85e1fc5fa91f66e88740f11615bc0273"), + "vendor/php-di/php-di/src/DI/Definition/ArrayDefinition.php" => array("1114", "adbba3c11308afac58777e09ec0b21da"), + "vendor/php-di/php-di/src/DI/Definition/CacheableDefinition.php" => array("367", "317f61260e34be82d3e0a19fd7cd4807"), + "vendor/php-di/php-di/src/DI/Definition/DecoratorDefinition.php" => array("934", "a823779ec18874db0f9e3700d505cb27"), + "vendor/php-di/php-di/src/DI/Definition/Definition.php" => array("575", "227cf34a88d987ef6dea42f1865f9959"), + "vendor/php-di/php-di/src/DI/Definition/Dumper/AliasDefinitionDumper.php" => array("1161", "e14829316ea2a98c5aa568460879155f"), + "vendor/php-di/php-di/src/DI/Definition/Dumper/ArrayDefinitionDumper.php" => array("1599", "ee762c38f7c54f976d6d26cddcf38420"), + "vendor/php-di/php-di/src/DI/Definition/Dumper/DecoratorDefinitionDumper.php" => array("934", "11795066b6be08d2676fa98299caeceb"), + "vendor/php-di/php-di/src/DI/Definition/Dumper/DefinitionDumperDispatcher.php" => array("1968", "7e7772483137360c7e1b59a8162c1186"), + "vendor/php-di/php-di/src/DI/Definition/Dumper/DefinitionDumper.php" => array("614", "1c996de13ce6788b4bbb21e53f386c49"), + "vendor/php-di/php-di/src/DI/Definition/Dumper/EnvironmentVariableDefinitionDumper.php" => array("1838", "1c0c55487ee6f943f0133967ec360856"), + "vendor/php-di/php-di/src/DI/Definition/Dumper/FactoryDefinitionDumper.php" => array("878", "fd18114ba4d1c7e1ceb70b154aee7942"), + "vendor/php-di/php-di/src/DI/Definition/Dumper/ObjectDefinitionDumper.php" => array("4920", "7c01645ecf385b75cd4423ddadd682ef"), + "vendor/php-di/php-di/src/DI/Definition/Dumper/StringDefinitionDumper.php" => array("892", "08f398896bc4fdf4ae9d9e8e9a2a82b9"), + "vendor/php-di/php-di/src/DI/Definition/Dumper/ValueDefinitionDumper.php" => array("1009", "7a937d6b1452dddf4be5356694ae2898"), + "vendor/php-di/php-di/src/DI/Definition/EntryReference.php" => array("1025", "9bb3949a05980d1afae22d18a9196ce7"), + "vendor/php-di/php-di/src/DI/Definition/EnvironmentVariableDefinition.php" => array("2702", "9d9be1f4ff32f6294524eba202bcf63d"), + "vendor/php-di/php-di/src/DI/Definition/Exception/AnnotationException.php" => array("408", "993733253f9090e36b63896a541ee115"), + "vendor/php-di/php-di/src/DI/Definition/Exception/DefinitionException.php" => array("654", "6f1f3f131dbc672365960fb2f97a29b9"), + "vendor/php-di/php-di/src/DI/Definition/FactoryDefinition.php" => array("1525", "2c560419808d1bb58e3d84d22d5dedc8"), + "vendor/php-di/php-di/src/DI/Definition/HasSubDefinition.php" => array("581", "333a29cbde70869767073170aaf78f00"), + "vendor/php-di/php-di/src/DI/Definition/Helper/ArrayDefinitionExtensionHelper.php" => array("1005", "c699cc76c5e3c8597aae6273a4fd783e"), + "vendor/php-di/php-di/src/DI/Definition/Helper/DefinitionHelper.php" => array("522", "6e62c3521c6e0e86dc7371c0b5796e3c"), + "vendor/php-di/php-di/src/DI/Definition/Helper/EnvironmentVariableDefinitionHelper.php" => array("1866", "e2a7f7c9c1248cd25bf2f9edfdcef7ec"), + "vendor/php-di/php-di/src/DI/Definition/Helper/FactoryDefinitionHelper.php" => array("1577", "fd405e53eb6595412d3f5532a7a7c10b"), + "vendor/php-di/php-di/src/DI/Definition/Helper/ObjectDefinitionHelper.php" => array("7966", "619d38517ca09368ee8f31e507a373a7"), + "vendor/php-di/php-di/src/DI/Definition/Helper/StringDefinitionHelper.php" => array("800", "6d4813e6a29f43853ab96f959e461715"), + "vendor/php-di/php-di/src/DI/Definition/Helper/ValueDefinitionHelper.php" => array("822", "ef79a8e4605949145459ff3f3cadb0d7"), + "vendor/php-di/php-di/src/DI/Definition/InstanceDefinition.php" => array("1432", "9b05df639543fcea340c3b78e2af13df"), + "vendor/php-di/php-di/src/DI/Definition/ObjectDefinition/MethodInjection.php" => array("1263", "8cbcee076348cf0fee058fc4e1df4678"), + "vendor/php-di/php-di/src/DI/Definition/ObjectDefinition.php" => array("7369", "6d87f62a5ad8c98f1982a5d2b4867072"), + "vendor/php-di/php-di/src/DI/Definition/ObjectDefinition/PropertyInjection.php" => array("1160", "a39f0832b01ba023d1c115bd9b4a2cea"), + "vendor/php-di/php-di/src/DI/Definition/Resolver/AliasResolver.php" => array("2004", "ddb9ff616e356a98d55103bd04a3bc56"), + "vendor/php-di/php-di/src/DI/Definition/Resolver/ArrayResolver.php" => array("2874", "f2f1a9e357438dbb622619813ee92c11"), + "vendor/php-di/php-di/src/DI/Definition/Resolver/DecoratorResolver.php" => array("3196", "6b9b3339281035c33a0d8f57cf602fd4"), + "vendor/php-di/php-di/src/DI/Definition/Resolver/DefinitionResolver.php" => array("1301", "f6346d70e76ed669ddea8a22fef7f7e3"), + "vendor/php-di/php-di/src/DI/Definition/Resolver/EnvironmentVariableResolver.php" => array("2757", "7cbd3677eff8a427b26cfa06f9e52fda"), + "vendor/php-di/php-di/src/DI/Definition/Resolver/FactoryResolver.php" => array("2273", "1d228640232beae237033e31e35cdf8c"), + "vendor/php-di/php-di/src/DI/Definition/Resolver/InstanceInjector.php" => array("1894", "5ab05bfca01ed507dc51304321b034f1"), + "vendor/php-di/php-di/src/DI/Definition/Resolver/ObjectCreator.php" => array("8786", "958834666773c79ada983384697f94c0"), + "vendor/php-di/php-di/src/DI/Definition/Resolver/ParameterResolver.php" => array("4840", "9eed801d2d4fbd35582a97f168fe604d"), + "vendor/php-di/php-di/src/DI/Definition/Resolver/ResolverDispatcher.php" => array("5022", "b7491e8572f1f51773b76b68eaf6f0b5"), + "vendor/php-di/php-di/src/DI/Definition/Resolver/StringResolver.php" => array("2773", "5a8629f9870aa106d85582ac40ba86fa"), + "vendor/php-di/php-di/src/DI/Definition/Resolver/ValueResolver.php" => array("1492", "fcef9edb8985c574fb7246e056ab0a2e"), + "vendor/php-di/php-di/src/DI/Definition/Source/AnnotationReader.php" => array("9399", "5417670e190f1e454477f846eb50f330"), + "vendor/php-di/php-di/src/DI/Definition/Source/Autowiring.php" => array("1770", "06f0314c312c478397ce1f77ac78c862"), + "vendor/php-di/php-di/src/DI/Definition/Source/CachedDefinitionSource.php" => array("2338", "2bd54552181ff0cc376fa5c0b917b252"), + "vendor/php-di/php-di/src/DI/Definition/Source/DefinitionArray.php" => array("4162", "511d81cbd4bdb16edd659c583756d22f"), + "vendor/php-di/php-di/src/DI/Definition/Source/DefinitionFile.php" => array("1626", "1143fe16f6e091941f619deaea65654e"), + "vendor/php-di/php-di/src/DI/Definition/Source/DefinitionSource.php" => array("714", "f12a05e8a7c4167568747e1b3fc9c215"), + "vendor/php-di/php-di/src/DI/Definition/Source/MutableDefinitionSource.php" => array("323", "13a13f4eb78b57d6e44a10efe2df977c"), + "vendor/php-di/php-di/src/DI/Definition/Source/SourceChain.php" => array("2992", "0e8fd49a4d7e8e6602372d34490524f6"), + "vendor/php-di/php-di/src/DI/Definition/StringDefinition.php" => array("1137", "0140f6c08739a8f9036e149473645ec0"), + "vendor/php-di/php-di/src/DI/Definition/ValueDefinition.php" => array("1194", "58aee00fbb192bf5d5ed60d29532bd48"), + "vendor/php-di/php-di/src/DI/DependencyException.php" => array("390", "1e33f301d9c0dd5987e28e82341f7f6b"), + "vendor/php-di/php-di/src/DI/FactoryInterface.php" => array("1167", "9ec67d26328639027ff0a752b927c241"), + "vendor/php-di/php-di/src/DI/functions.php" => array("4807", "0eae8278e0966ea9405e4c1f88d9ee43"), + "vendor/php-di/php-di/src/DI/Invoker/DefinitionParameterResolver.php" => array("1133", "6300f6cdfaf3a51665ce801a633251dc"), + "vendor/php-di/php-di/src/DI/InvokerInterface.php" => array("366", "40a97309af2e2d56892c7cd0e4cff272"), + "vendor/php-di/php-di/src/DI/NotFoundException.php" => array("458", "580d4be2332c070f76b5af2db1845b5a"), + "vendor/php-di/php-di/src/DI/Proxy/ProxyFactory.php" => array("2827", "a87b6cc9165a3bf2c07532364114a27c"), + "vendor/php-di/php-di/src/DI/Reflection/CallableReflectionFactory.php" => array("1451", "b2f87d9ec62f8d6c93b100b1dfbbcbc9"), + "vendor/php-di/php-di/src/DI/Scope.php" => array("1144", "33a4763935b7ea01d952465f92a9d2c1"), + "vendor/piwik/cache/composer.json" => array("717", "2b18598296132f7f95a154dc35a61386"), + "vendor/piwik/cache/phpunit.xml" => array("550", "003027148995b036415ae0647b810ee8"), + "vendor/piwik/cache/README.md" => array("6517", "fd4c05f9ea47a3bcd51729b3761c3e0b"), + "vendor/piwik/cache/src/Backend/ArrayCache.php" => array("875", "64ddb89e65e0df769276de14f1498c8e"), + "vendor/piwik/cache/src/Backend/Chained.php" => array("2412", "c45449fa8ef3d2fca6f8c8bab8fbf4dd"), + "vendor/piwik/cache/src/Backend/Factory/BackendNotFoundException.php" => array("268", "a5f04138c2c789de2fb730f5b20c056c"), + "vendor/piwik/cache/src/Backend/Factory.php" => array("2789", "aafe84e187fff1228b9b26920413f6fb"), + "vendor/piwik/cache/src/Backend/File.php" => array("4118", "8ccf9b65576f59432907befe7cbac4ba"), + "vendor/piwik/cache/src/Backend/NullCache.php" => array("698", "49ee0dbf7afb707c16b4251a499d9b17"), + "vendor/piwik/cache/src/Backend.php" => array("1589", "2e298daefdf9d01869cdbeb7e9915b0d"), + "vendor/piwik/cache/src/Backend/Redis.php" => array("763", "65fba9eb867ef95b81c23521a0ffd2e6"), + "vendor/piwik/cache/src/Cache.php" => array("1547", "2b6476b6f29c5ecdf904f38440dfec4b"), + "vendor/piwik/cache/src/Eager.php" => array("3720", "9a170a6070a7461c1b93eb47dc48c834"), + "vendor/piwik/cache/src/Lazy.php" => array("3282", "a05d654dd3e12ae3d9781c0d4f00db47"), + "vendor/piwik/cache/src/Transient.php" => array("1817", "1d148ba926ddf1423f61fb673f4d0a1a"), + "vendor/piwik/decompress/composer.json" => array("483", "05300623a40c5013c688fc5dee95c021"), + "vendor/piwik/decompress/libs/PclZip/gnu-lgpl.txt" => array("26934", "f14599a2f089f6ff8c97e2baa4e3d575"), + "vendor/piwik/decompress/libs/PclZip/pclzip.lib.php" => array("191558", "2b8146f5bdef4dc695aae6214f26b30b"), + "vendor/piwik/decompress/libs/PclZip/readme.txt" => array("22011", "0d82536577908a1f78e1b5c6220f5810"), + "vendor/piwik/decompress/libs/README.md" => array("519", "ec1c1d348903b869537db50ba8a06f0c"), + "vendor/piwik/decompress/phpunit.xml" => array("748", "ff1c22662a32f9cbff16d4871e35091c"), + "vendor/piwik/decompress/README.md" => array("1503", "fa831c7578e414bded48acff72dad814"), + "vendor/piwik/decompress/src/DecompressInterface.php" => array("834", "b851f409181f672b91e5226c830abb6f"), + "vendor/piwik/decompress/src/Gzip.php" => array("1593", "80069c36d39643d408115e07fd953091"), + "vendor/piwik/decompress/src/PclZip.php" => array("2120", "d1bcb655c3199852124209dab0edc2e2"), + "vendor/piwik/decompress/src/Tar.php" => array("1900", "73250e2a65b9fcbf7e8d292d97947e31"), + "vendor/piwik/decompress/src/ZipArchive.php" => array("4479", "9e49a7fdbc3f4893f762ec659dcb4096"), + "vendor/piwik/device-detector/Cache/Cache.php" => array("437", "eec9f30425cf26ba1d8041f6809abf9f"), + "vendor/piwik/device-detector/Cache/StaticCache.php" => array("1137", "ff8384ab56487fc7f0fc437ecbaf999a"), + "vendor/piwik/device-detector/composer.json" => array("1226", "ebdbab53b79d86e3324bf9dee6dc97c0"), + "vendor/piwik/device-detector/DeviceDetector.php" => array("22355", "9bc6e15dba074c3c34974c31b32f3721"), + "vendor/piwik/device-detector/Parser/Bot.php" => array("1893", "f247ddac0519f237b153f238511692cb"), + "vendor/piwik/device-detector/Parser/Client/Browser/Engine.php" => array("1900", "71ac08bbe06236c1d6243ab3ddf8f491"), + "vendor/piwik/device-detector/Parser/Client/Browser.php" => array("8064", "59eb45133a212a31de70a075bdd00b5b"), + "vendor/piwik/device-detector/Parser/Client/ClientParserAbstract.php" => array("2164", "7c3b7230ea579204c3198fc87a811a46"), + "vendor/piwik/device-detector/Parser/Client/FeedReader.php" => array("510", "23cdf73457e711c0715c88b3495f0c89"), + "vendor/piwik/device-detector/Parser/Client/Library.php" => array("501", "01fea395b442c264240bce59c4e5e052"), + "vendor/piwik/device-detector/Parser/Client/MediaPlayer.php" => array("512", "8f18f6ba20b9bc0277d20caf0c378e2b"), + "vendor/piwik/device-detector/Parser/Client/MobileApp.php" => array("505", "ce24881b79f45cd29b8cd8d19eb25a39"), + "vendor/piwik/device-detector/Parser/Client/PIM.php" => array("502", "624de8fdf2d3651f625d97d371a781fa"), + "vendor/piwik/device-detector/Parser/Device/Camera.php" => array("639", "2d75efc730ad7b1dbdfa526abb26a50d"), + "vendor/piwik/device-detector/Parser/Device/CarBrowser.php" => array("662", "19c0a0f23049caee76ec6702b1b6fe21"), + "vendor/piwik/device-detector/Parser/Device/Console.php" => array("644", "a1f0f07e4c4e907406bad5e66e6d9d4e"), + "vendor/piwik/device-detector/Parser/Device/DeviceParserAbstract.php" => array("13570", "46aa1d592b94d621facfa929deb936df"), + "vendor/piwik/device-detector/Parser/Device/HbbTv.php" => array("1292", "f6deedef0e099c1087d809afa5c44fa5"), + "vendor/piwik/device-detector/Parser/Device/Mobile.php" => array("488", "b786e48000d4181635a8f37c5ae99977"), + "vendor/piwik/device-detector/Parser/Device/PortableMediaPlayer.php" => array("707", "fab6e15110c98f4be460d5d648a9b431"), + "vendor/piwik/device-detector/Parser/OperatingSystem.php" => array("7415", "c84ba83c516f9371469ed9619cd10dac"), + "vendor/piwik/device-detector/Parser/ParserAbstract.php" => array("7947", "0d9449011522ef41658fd64a8d1c1d5a"), + "vendor/piwik/device-detector/Parser/VendorFragment.php" => array("1101", "339d091036b4b79ee78f10b5506cf1c2"), + "vendor/piwik/device-detector/README.md" => array("10665", "803aa5cade40980b86b10350dadf28f9"), + "vendor/piwik/device-detector/regexes/bots.yml" => array("26699", "536855d5eee9e1eb1e06f64f1e546843"), + "vendor/piwik/device-detector/regexes/client/browser_engine.yml" => array("511", "0ec16431362040744f39b37acff85514"), + "vendor/piwik/device-detector/regexes/client/browsers.yml" => array("15839", "1d52696b74bef999dafe94a6f83350e1"), + "vendor/piwik/device-detector/regexes/client/feed_readers.yml" => array("2735", "538eadcf87556fa24ec5c4452a5f1f00"), + "vendor/piwik/device-detector/regexes/client/libraries.yml" => array("764", "b453f519f3c00edcc991b57d6128b232"), + "vendor/piwik/device-detector/regexes/client/mediaplayers.yml" => array("1579", "fadb1da67c146d6ab9829163fa73c4f1"), + "vendor/piwik/device-detector/regexes/client/mobile_apps.yml" => array("1079", "14229579b2655fcd20f8dd7e2731175d"), + "vendor/piwik/device-detector/regexes/client/pim.yml" => array("887", "64677339838f0c66a4aaacf662987aaa"), + "vendor/piwik/device-detector/regexes/device/cameras.yml" => array("651", "9be6bf6a9a52a23d4ceed4910ff8de22"), + "vendor/piwik/device-detector/regexes/device/car_browsers.yml" => array("299", "5e1268cea5085840117e29791ee533c3"), + "vendor/piwik/device-detector/regexes/device/consoles.yml" => array("754", "8df4d3b748f9c08fdc807c029082c48f"), + "vendor/piwik/device-detector/regexes/device/mobiles.yml" => array("116747", "f302406bf965dd1371e94f7324ec9d01"), + "vendor/piwik/device-detector/regexes/device/portable_media_player.yml" => array("1547", "b8c2bf3e673d81c057959f344b52c255"), + "vendor/piwik/device-detector/regexes/device/televisions.yml" => array("4717", "7172420aadfb4b118f00f01bbdb485cb"), + "vendor/piwik/device-detector/regexes/oss.yml" => array("10703", "fe4ac9986e1febe3bd166758510eec1d"), + "vendor/piwik/device-detector/regexes/vendorfragments.yml" => array("870", "c0c55dce0e8b49145dc8b4df89aac9cc"), + "vendor/piwik/ini/composer.json" => array("418", "24d3ce247f96f7a69de1b20ce3dcabc9"), + "vendor/piwik/ini/phpunit.xml" => array("550", "003027148995b036415ae0647b810ee8"), + "vendor/piwik/ini/README.md" => array("2131", "b691656f2c2006359dba19e76b76ae69"), + "vendor/piwik/ini/src/IniReader.php" => array("10920", "f7dcad9c3d95f8527200c47145f72d68"), + "vendor/piwik/ini/src/IniReadingException.php" => array("280", "c4bc3c466314e91bd910fc28bb6d0564"), + "vendor/piwik/ini/src/IniWriter.php" => array("3138", "57246595dfb934634692eb9767f7fbc0"), + "vendor/piwik/ini/src/IniWritingException.php" => array("280", "2f0377f8c02b1fd71017ccdf02d38cf9"), + "vendor/piwik/network/composer.json" => array("392", "41b50bd22c70ad245bd095191a560e2e"), + "vendor/piwik/network/phpunit.xml" => array("587", "4bf37072775ecb24b3a27216c4dab4d4"), + "vendor/piwik/network/README.md" => array("1764", "1752a4a860b758599dcaa7c318a73073"), + "vendor/piwik/network/src/IP.php" => array("5576", "13a89aaa67d90828d5cdc6ce1ffee4ab"), + "vendor/piwik/network/src/IPUtils.php" => array("5828", "dfbfeca489cddcffe690715f9505f243"), + "vendor/piwik/network/src/IPv4.php" => array("850", "58d5ed5212bcf801fcb1b12cf9e686c8"), + "vendor/piwik/network/src/IPv6.php" => array("1820", "49b818a413746ce19c0ce3cd09d78b84"), + "vendor/piwik/piwik-php-tracker/composer.json" => array("658", "47974c3d4bd1134b8f977f04030ac5ae"), + "vendor/piwik/piwik-php-tracker/LICENSE" => array("1503", "ea8df6f6cdb84d038a21520c73be939b"), + "vendor/piwik/piwik-php-tracker/PiwikTracker.php" => array("64003", "b7a27650db6279e0614c575d70294a70"), + "vendor/piwik/piwik-php-tracker/README.md" => array("705", "445bbdb60454851043b3c4801897ebd6"), + "vendor/piwik/referrer-spam-blacklist/composer.json" => array("150", "3c4ddde1c99338292d3b052154aaa19f"), + "vendor/piwik/referrer-spam-blacklist/CONTRIBUTING.md" => array("865", "0ccaa1b51b12b0bba85c158a24debd5d"), + "vendor/piwik/referrer-spam-blacklist/README.md" => array("4111", "123aa46dc6c0cdc79cbc36d95fa7dd7c"), + "vendor/piwik/referrer-spam-blacklist/spammers.txt" => array("5378", "2319d199710ad61b6ff5c59d15b8aec3"), + "vendor/piwik/searchengine-and-social-list/composer.json" => array("159", "6912ff808dee09bd8b1a065e9e77eea1"), + "vendor/piwik/searchengine-and-social-list/README.md" => array("4787", "3971f2f0bc9cb731b7238664eccfafd6"), + "vendor/piwik/searchengine-and-social-list/SearchEngines.yml" => array("36892", "06aec8f4a7419888ccf060c7887f0ba8"), + "vendor/piwik/searchengine-and-social-list/Socials.yml" => array("2088", "2c156f1e654854d8893520c6bca4bea3"), + "vendor/psr/log/composer.json" => array("358", "de1539d2aa7830d13a968d33d1039f1a"), + "vendor/psr/log/LICENSE" => array("1085", "1a74629072fd794937be394ab689327e"), + "vendor/psr/log/Psr/Log/AbstractLogger.php" => array("3024", "a57c1be541193b72d09307bb0dfb9ed2"), + "vendor/psr/log/Psr/Log/InvalidArgumentException.php" => array("96", "7d2f0bd1583524d739fff12f0507de65"), + "vendor/psr/log/Psr/Log/LoggerAwareInterface.php" => array("288", "3ba5ffac8108e1da7657b1fad8651900"), + "vendor/psr/log/Psr/Log/LoggerAwareTrait.php" => array("351", "5b3adf6c4f09c61d7488b0f9ac2c696a"), + "vendor/psr/log/Psr/Log/LoggerInterface.php" => array("2965", "023885df6a26d8137d5a13da51f066d2"), + "vendor/psr/log/Psr/Log/LoggerTrait.php" => array("3288", "1cb8db6d0b81cf85f81b6c7c09db7a9a"), + "vendor/psr/log/Psr/Log/LogLevel.php" => array("312", "19ab55cc711ed2f3ab2ec72e7f0600cb"), + "vendor/psr/log/Psr/Log/NullLogger.php" => array("641", "e71559fea0239b7441d221f8c7beae5b"), + "vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php" => array("3874", "867f36a94c35322470458f4eba246458"), + "vendor/psr/log/README.md" => array("1088", "144a71a4e1f9c67ac79751acc37614c4"), + "vendor/symfony/console/Symfony/Component/Console/Application.php" => array("37790", "e975b9d1b85d9acff879cb363e565da8"), + "vendor/symfony/console/Symfony/Component/Console/CHANGELOG.md" => array("2099", "19e8a451cd9f8adbc4b1c1c499e8a4a6"), + "vendor/symfony/console/Symfony/Component/Console/Command/Command.php" => array("17872", "7379e2a29cd8ebe710f31247a1950233"), + "vendor/symfony/console/Symfony/Component/Console/Command/HelpCommand.php" => array("2658", "5a868d08465065ebc2d1e85d64dd6cf7"), + "vendor/symfony/console/Symfony/Component/Console/Command/ListCommand.php" => array("2748", "56678782b084fd8ff7b73248d6463ada"), + "vendor/symfony/console/Symfony/Component/Console/composer.json" => array("1057", "d8d71cb83b8f287e4df6564cb9026dfa"), + "vendor/symfony/console/Symfony/Component/Console/ConsoleEvents.php" => array("1591", "0a76aea6561a0b6c62971aa2cbd2b058"), + "vendor/symfony/console/Symfony/Component/Console/Descriptor/ApplicationDescription.php" => array("3611", "3849ebf225805d613401ac9160d92c89"), "vendor/symfony/console/Symfony/Component/Console/Descriptor/DescriptorInterface.php" => array("737", "d5b9abe215ed28ac8ca7fe9d72fc9ddd"), - "vendor/symfony/console/Symfony/Component/Console/Descriptor/Descriptor.php" => array("3516", "e08a534aa99b2e90d1538a0c166973e5"), - "vendor/symfony/console/Symfony/Component/Console/Descriptor/JsonDescriptor.php" => array("4981", "a997223049fcf34294263e3a6c37e230"), - "vendor/symfony/console/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php" => array("4910", "bec9d13cc9cb6e778c4f4083199137b6"), - "vendor/symfony/console/Symfony/Component/Console/Descriptor/TextDescriptor.php" => array("8184", "97b571928164645af31610ea97bc43fb"), - "vendor/symfony/console/Symfony/Component/Console/Descriptor/XmlDescriptor.php" => array("9712", "a9214dd6cf39faa121dc4de62996071a"), - "vendor/symfony/console/Symfony/Component/Console/Event/ConsoleCommandEvent.php" => array("448", "2832db2aee4207de5d53d012b314436d"), + "vendor/symfony/console/Symfony/Component/Console/Descriptor/Descriptor.php" => array("3530", "23c50f3de98246e9b48913f74c2bdb4c"), + "vendor/symfony/console/Symfony/Component/Console/Descriptor/JsonDescriptor.php" => array("4909", "9f49ee47be6e7370625caaa7bcf00836"), + "vendor/symfony/console/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php" => array("4911", "5f5217660c4680db12a74ece15ae743d"), + "vendor/symfony/console/Symfony/Component/Console/Descriptor/TextDescriptor.php" => array("9127", "a59f225a378c9940570228ae64ac2b40"), + "vendor/symfony/console/Symfony/Component/Console/Descriptor/XmlDescriptor.php" => array("9728", "cc0c1389ddab8272f805ed40ef008a33"), + "vendor/symfony/console/Symfony/Component/Console/Event/ConsoleCommandEvent.php" => array("1336", "9334cbd4f82765230bb6f5a1e985a167"), "vendor/symfony/console/Symfony/Component/Console/Event/ConsoleEvent.php" => array("1464", "aaaa8c390cd99e01d7bb2649a735e469"), - "vendor/symfony/console/Symfony/Component/Console/Event/ConsoleExceptionEvent.php" => array("1601", "22e5c027468f33b0bd20540b41af7a74"), - "vendor/symfony/console/Symfony/Component/Console/Event/ConsoleTerminateEvent.php" => array("1318", "82fbfa586f39a859d66a45221cb773c1"), - "vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterInterface.php" => array("1763", "75e7364468e427f080f86354f9bab9b9"), - "vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatter.php" => array("6341", "304b90bba47ad3ed5534a37ec1647980"), + "vendor/symfony/console/Symfony/Component/Console/Event/ConsoleExceptionEvent.php" => array("1597", "2940244a154aeb8bfbcd9cf8d805702a"), + "vendor/symfony/console/Symfony/Component/Console/Event/ConsoleTerminateEvent.php" => array("1306", "44e02b0b4064120d43fcf762a80d7331"), + "vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterInterface.php" => array("1754", "7daf3d13402d56dd1e3e20435e5414fb"), + "vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatter.php" => array("6457", "b8fcb83352e271ebf4a24ec52174b48e"), "vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyleInterface.php" => array("1439", "091bf3ec019e14a48b1f61ef7cb6871a"), - "vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyle.php" => array("5926", "4d0b8e5a84926a6013be77aa123e61eb"), - "vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyleStack.php" => array("2788", "27952fd939968e3615ce7ada4c761e0e"), - "vendor/symfony/console/Symfony/Component/Console/.gitignore" => array("34", "a1155c508134e9bda943ae266aee1819"), - "vendor/symfony/console/Symfony/Component/Console/Helper/DescriptorHelper.php" => array("2561", "d5bb73eb73014de416365c07cd81cac5"), - "vendor/symfony/console/Symfony/Component/Console/Helper/DialogHelper.php" => array("16446", "ae0bb3641414453024a3d71275abd1c1"), - "vendor/symfony/console/Symfony/Component/Console/Helper/FormatterHelper.php" => array("2236", "86ebeb32b3353bc7ba4363bf7a05fa2f"), + "vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyle.php" => array("6876", "1fbf2f24197148dafcab33a47a01e1fa"), + "vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyleStack.php" => array("2789", "f4e0c65c42142834b564e341a63d6a7e"), + "vendor/symfony/console/Symfony/Component/Console/Helper/DebugFormatterHelper.php" => array("4179", "1f30fb180be772d344aba52ea74abb60"), + "vendor/symfony/console/Symfony/Component/Console/Helper/DescriptorHelper.php" => array("2592", "622aab20b7a9c4b69f581c3a5d67e650"), + "vendor/symfony/console/Symfony/Component/Console/Helper/DialogHelper.php" => array("16529", "4b35ff40c568a40763e3fcb707e545f9"), + "vendor/symfony/console/Symfony/Component/Console/Helper/FormatterHelper.php" => array("2325", "81f2ef763d845157091ac229f0a4c35e"), "vendor/symfony/console/Symfony/Component/Console/Helper/HelperInterface.php" => array("1011", "e9012e6f3559129d513da4ba75ebf4e9"), - "vendor/symfony/console/Symfony/Component/Console/Helper/Helper.php" => array("1451", "426298ce1ccd85ab527a5cef78f9a33b"), - "vendor/symfony/console/Symfony/Component/Console/Helper/HelperSet.php" => array("2526", "9c0badab04f9b052cec6a06c3f11fb07"), - "vendor/symfony/console/Symfony/Component/Console/Helper/InputAwareHelper.php" => array("747", "c75592e1f3b10c1f35961c50d55ae58d"), - "vendor/symfony/console/Symfony/Component/Console/Helper/ProgressHelper.php" => array("11887", "c6c85dbb2f08fa52bc6739ba6896bdc9"), - "vendor/symfony/console/Symfony/Component/Console/Helper/TableHelper.php" => array("12820", "1671e0f028279a642e6c0f7dcaa474c4"), - "vendor/symfony/console/Symfony/Component/Console/Input/ArgvInput.php" => array("10680", "e95b97c2b870d6223e84a995c93f16ce"), - "vendor/symfony/console/Symfony/Component/Console/Input/ArrayInput.php" => array("5925", "1e51135fb1c605381452c4d0cae3f1c4"), - "vendor/symfony/console/Symfony/Component/Console/Input/InputArgument.php" => array("3287", "c2f88afd2a0f1601dcfd3cce07092197"), + "vendor/symfony/console/Symfony/Component/Console/Helper/Helper.php" => array("3148", "3a0c5d5521d3a6d8800346547253ed7b"), + "vendor/symfony/console/Symfony/Component/Console/Helper/HelperSet.php" => array("2523", "d4bcdcc2056214dc911aa3a95b3a707b"), + "vendor/symfony/console/Symfony/Component/Console/Helper/InputAwareHelper.php" => array("747", "46d23e6342ebb9cf17e7363b2295f5ba"), + "vendor/symfony/console/Symfony/Component/Console/Helper/ProcessHelper.php" => array("4825", "f8014e14d56132a3324e8fa4f9a46a86"), + "vendor/symfony/console/Symfony/Component/Console/Helper/ProgressBar.php" => array("17141", "3646dc3f3ade7c1d51a233177ccd3ac3"), + "vendor/symfony/console/Symfony/Component/Console/Helper/ProgressHelper.php" => array("12079", "ecde8c07f3709cfa3350a34b45ad7eb6"), + "vendor/symfony/console/Symfony/Component/Console/Helper/QuestionHelper.php" => array("12608", "ccd04b6cf3e259fa277cc0eb6ed37023"), + "vendor/symfony/console/Symfony/Component/Console/Helper/TableHelper.php" => array("5895", "ad3438727bab52219452d8a08f6e5b93"), + "vendor/symfony/console/Symfony/Component/Console/Helper/Table.php" => array("10349", "965b29a7911faef3b468c6003fd7dd7e"), + "vendor/symfony/console/Symfony/Component/Console/Helper/TableSeparator.php" => array("405", "531d3585fbc7e64c7e5f11f259dab7eb"), + "vendor/symfony/console/Symfony/Component/Console/Helper/TableStyle.php" => array("4943", "814b0ebf864228fc5fabdb0cee6858f2"), + "vendor/symfony/console/Symfony/Component/Console/Input/ArgvInput.php" => array("10696", "b322c20a800cf1f6563e8fb787094fa6"), + "vendor/symfony/console/Symfony/Component/Console/Input/ArrayInput.php" => array("5963", "0709a96fc72a94d72e22f1da7e1fc126"), + "vendor/symfony/console/Symfony/Component/Console/Input/InputArgument.php" => array("3263", "9b4a500384e6bbbbb456730f5f45173a"), "vendor/symfony/console/Symfony/Component/Console/Input/InputAwareInterface.php" => array("606", "a67cb5e626983b20e2c03b618d7ab012"), - "vendor/symfony/console/Symfony/Component/Console/Input/InputDefinition.php" => array("12328", "74e92dce0c01082eb3d96959f3268509"), - "vendor/symfony/console/Symfony/Component/Console/Input/InputInterface.php" => array("4150", "9a40ecfaca79fa6bed0e459c035fdb5c"), - "vendor/symfony/console/Symfony/Component/Console/Input/InputOption.php" => array("5974", "2712657bb89ede768dfb9d0eb1f1d256"), - "vendor/symfony/console/Symfony/Component/Console/Input/Input.php" => array("6144", "87ba519b169574bdcaabaef68740989a"), - "vendor/symfony/console/Symfony/Component/Console/Input/StringInput.php" => array("2811", "eaf496e1188492448d9cfe8dfbcede6a"), - "vendor/symfony/console/Symfony/Component/Console/LICENSE" => array("1065", "09ce405e925cdeb923da1789121864c7"), + "vendor/symfony/console/Symfony/Component/Console/Input/InputDefinition.php" => array("12149", "cda35eaf829825f8c3753fa291d866b5"), + "vendor/symfony/console/Symfony/Component/Console/Input/InputInterface.php" => array("4125", "a9f0876e2ef074464cde27a9c5120981"), + "vendor/symfony/console/Symfony/Component/Console/Input/InputOption.php" => array("5946", "2206c5212d4030b5f74dc6b15cc69225"), + "vendor/symfony/console/Symfony/Component/Console/Input/Input.php" => array("6120", "e99cc00559eda36a99ccfa518d6b0518"), + "vendor/symfony/console/Symfony/Component/Console/Input/StringInput.php" => array("2725", "19e11d9e2720769350efc35a403b658c"), + "vendor/symfony/console/Symfony/Component/Console/LICENSE" => array("1065", "56dedd4bd25ecd034ac4e1c17ebba0cc"), + "vendor/symfony/console/Symfony/Component/Console/Logger/ConsoleLogger.php" => array("3813", "ff6421408fcb0ee16bc7be84d66cfe8a"), "vendor/symfony/console/Symfony/Component/Console/Output/BufferedOutput.php" => array("872", "07bca2bc77753b0b0cc4502f109c4a8e"), "vendor/symfony/console/Symfony/Component/Console/Output/ConsoleOutputInterface.php" => array("843", "9cf400160437ff90febb665841a968fe"), - "vendor/symfony/console/Symfony/Component/Console/Output/ConsoleOutput.php" => array("2941", "64e00ce49a3a47e8ea7b8885f10c1ee6"), - "vendor/symfony/console/Symfony/Component/Console/Output/NullOutput.php" => array("1748", "561f460b93be0c27aa9be546af49e9ec"), - "vendor/symfony/console/Symfony/Component/Console/Output/OutputInterface.php" => array("2893", "ac6d776cf82c8b887395c54482f17bd7"), - "vendor/symfony/console/Symfony/Component/Console/Output/Output.php" => array("4155", "15c209a7581a632e6979d5441a02f241"), - "vendor/symfony/console/Symfony/Component/Console/Output/StreamOutput.php" => array("3149", "dc57630f625779ad86f6f3d8240ad0c3"), - "vendor/symfony/console/Symfony/Component/Console/phpunit.xml.dist" => array("873", "c94fc23909e8f1d27908cd0486653611"), - "vendor/symfony/console/Symfony/Component/Console/README.md" => array("1959", "70a92aecafcdc1f689a2a798acccefe4"), - "vendor/symfony/console/Symfony/Component/Console/Shell.php" => array("6395", "fedc847f6c992a242e45a167b0cf1866"), - "vendor/symfony/console/Symfony/Component/Console/Tester/ApplicationTester.php" => array("3430", "f2c7b88716f13cb54825a933832a74a8"), - "vendor/symfony/console/Symfony/Component/Console/Tester/CommandTester.php" => array("3612", "9b7bf197b61b1b3c23772c57a2ec3ee5"), + "vendor/symfony/console/Symfony/Component/Console/Output/ConsoleOutput.php" => array("3678", "3d93df750561afef312f476bd7a8de16"), + "vendor/symfony/console/Symfony/Component/Console/Output/NullOutput.php" => array("2015", "82d9a6c1fb3894504d7ee349d0bc77f8"), + "vendor/symfony/console/Symfony/Component/Console/Output/OutputInterface.php" => array("2849", "836c4843f80a794acc7aa0e1e2d42b15"), + "vendor/symfony/console/Symfony/Component/Console/Output/Output.php" => array("4153", "f44fc2d4776c81356966aedef42dce28"), + "vendor/symfony/console/Symfony/Component/Console/Output/StreamOutput.php" => array("2999", "8152ff1f16ba69cea7677ed6f5a65d08"), + "vendor/symfony/console/Symfony/Component/Console/phpunit.xml.dist" => array("826", "8ccc6b24534ec4ac815e779ca43c079a"), + "vendor/symfony/console/Symfony/Component/Console/Question/ChoiceQuestion.php" => array("3876", "80aec3f68cab6e7b27983d10f1ee5a11"), + "vendor/symfony/console/Symfony/Component/Console/Question/ConfirmationQuestion.php" => array("1316", "20d79db7cf694c34ddd252715756e9c3"), + "vendor/symfony/console/Symfony/Component/Console/Question/Question.php" => array("5484", "dc78751c1dfe8d6453816296a4868e5e"), + "vendor/symfony/console/Symfony/Component/Console/README.md" => array("1906", "81b8ef7ae615346a92585a2db6b26ec5"), + "vendor/symfony/console/Symfony/Component/Console/Shell.php" => array("6392", "f5705c7c034ea72937ab718bed705229"), + "vendor/symfony/console/Symfony/Component/Console/Tester/ApplicationTester.php" => array("3419", "f9f7325bd54a1986ceb41f929986915b"), + "vendor/symfony/console/Symfony/Component/Console/Tester/CommandTester.php" => array("3659", "d0dd63b3455ec5862200c13c308d25cc"), + "vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/CHANGELOG.md" => array("696", "745dd467bb8d944ee82b38034a3b71d2"), + "vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/composer.json" => array("1142", "f2ff5d79977cd6bc0d3b60cf1b1894bf"), + "vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/ContainerAwareEventDispatcher.php" => array("6573", "0bcadf91749e82c0e6f3ad64c89be078"), + "vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcherInterface.php" => array("803", "aa09cc5a555cfad4239045906e2850b0"), + "vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php" => array("10273", "c4e84cc92b835e7acc5d4a5df9b25aef"), + "vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Debug/WrappedListener.php" => array("1749", "b25eea7b4663606e7ee568151f63f246"), + "vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php" => array("4299", "737fe94715322e3c7d2b1a06da312f3f"), + "vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventDispatcherInterface.php" => array("3029", "571cc8d5ac702f06eacaa849e3774226"), + "vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventDispatcher.php" => array("5588", "726413ae13dfec061f23b8fff4235d25"), + "vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Event.php" => array("3207", "ec4077bd7b6bf237bec7136cbef589e0"), + "vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventSubscriberInterface.php" => array("1581", "c14793051c0916ae555a170db42d635c"), + "vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/GenericEvent.php" => array("3903", "57592b7539db9e18dc64528de170050a"), + "vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.php" => array("2240", "2aa90b7e3856053d0d6100d6e13832f8"), + "vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/LICENSE" => array("1065", "56dedd4bd25ecd034ac4e1c17ebba0cc"), + "vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/phpunit.xml.dist" => array("834", "80e32ada6de2dd8b634ed666650fc87e"), + "vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/README.md" => array("629", "ce7f9b787b9c507c07d1e6e36254a7e7"), + "vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/CHANGELOG.md" => array("219", "094d6ae95193dc93574972855a7d4832"), + "vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/composer.json" => array("1373", "65c151dab71c7917546af9ce2dc17642"), + "vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/Formatter/ConsoleFormatter.php" => array("1611", "770271a4d444a5edd938fde8d3329f95"), + "vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php" => array("1866", "802bc632e18f892ac6149df81b785064"), + "vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php" => array("5877", "71bc89a0d486b815c92a6552599e9652"), + "vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/Handler/DebugHandler.php" => array("1446", "450ee4aa4f8ff115dc3299b5dcdb52d2"), + "vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/Handler/FingersCrossed/NotFoundActivationStrategy.php" => array("1584", "1e0758ae14cad212d3c83378934145d6"), + "vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php" => array("1939", "7fc6f9e4753cf67533a0ca3ca0615508"), + "vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/Handler/SwiftMailerHandler.php" => array("2235", "65d9b7f8bd3838197c58f97476af73a8"), + "vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/LICENSE" => array("1065", "56dedd4bd25ecd034ac4e1c17ebba0cc"), + "vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/Logger.php" => array("2446", "27c5f10435a78e290ec8ac3cfc248318"), + "vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/phpunit.xml.dist" => array("775", "fdf89201b4cf373b7a34e2c47857fa95"), + "vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/Processor/WebProcessor.php" => array("967", "fdbdb9d4ee923c4c54375369f6cbd68f"), + "vendor/symfony/monolog-bridge/Symfony/Bridge/Monolog/README.md" => array("253", "6ed8293be5da72ba7c55961f646c4fd8"), + "vendor/tecnickcom/tcpdf/CHANGELOG.TXT" => array("116016", "e819e4201ca72583101a5419252fb5d4"), + "vendor/tecnickcom/tcpdf/composer.json" => array("944", "0eedf4e9c9c3e3fc74587f2ecb50684b"), + "vendor/tecnickcom/tcpdf/config/tcpdf_config.php" => array("5371", "34c21a79ab0bc8332ea5c142e4b8d636"), + "vendor/tecnickcom/tcpdf/fonts/aealarabiya.ctg.z" => array("1849", "0eac1ff3b999791227dadfb340ddd83f"), + "vendor/tecnickcom/tcpdf/fonts/aealarabiya.php" => array("35225", "c49883919b7912057b1ffad674a9c7c2"), + "vendor/tecnickcom/tcpdf/fonts/aealarabiya.z" => array("56189", "18ca47dea418152b135e93b582f84d3b"), + "vendor/tecnickcom/tcpdf/fonts/dejavusans.ctg.z" => array("10454", "4856e25c5027ef93e512646becf3eda5"), + "vendor/tecnickcom/tcpdf/fonts/dejavusans.php" => array("202079", "8b75ae7921f26b4f4b11d18ed921248e"), + "vendor/tecnickcom/tcpdf/fonts/dejavusans.z" => array("375806", "f910decf31ee5f189c5397ee0937794a"), + "vendor/tecnickcom/tcpdf/fonts/freesans.ctg.z" => array("8661", "be9c85808d55cbef8619a67495a43fbc"), + "vendor/tecnickcom/tcpdf/fonts/freesans.php" => array("165789", "4fd59032d7c3a59fd45028bafa245721"), + "vendor/tecnickcom/tcpdf/fonts/freesans.z" => array("807705", "bc1ca4370455ca07e2c22b8c5c5d3cce"), + "vendor/tecnickcom/tcpdf/fonts/freeserif.ctg.z" => array("12610", "0daa9bbabdda3c85e5060086916447d5"), + "vendor/tecnickcom/tcpdf/fonts/freeserif.php" => array("242895", "02aac38356af65808e08e21f1ddd225d"), + "vendor/tecnickcom/tcpdf/fonts/freeserif.z" => array("1835770", "084dca967854129a0aaec1713a46733e"), + "vendor/tecnickcom/tcpdf/fonts/helveticabi.php" => array("2589", "c22fdc8941f2956e0930b20105870468"), + "vendor/tecnickcom/tcpdf/fonts/helveticab.php" => array("2580", "3daad3713df02c15beebd09ceecacacd"), + "vendor/tecnickcom/tcpdf/fonts/helveticai.php" => array("2584", "e0a7f23376f50de631db93814aff2e35"), + "vendor/tecnickcom/tcpdf/fonts/helvetica.php" => array("2575", "2a315fa2593161154c319788f0ef2127"), + "vendor/tecnickcom/tcpdf/fonts/hysmyeongjostdmedium.php" => array("1829", "51f6fe162641de3714866950d5eff4e8"), + "vendor/tecnickcom/tcpdf/fonts/kozgopromedium.php" => array("3577", "2c5e8a67d1a805aae9842bbad59a873f"), + "vendor/tecnickcom/tcpdf/fonts/kozminproregular.php" => array("3454", "78fdf805f1cea6cd01912192821ec734"), + "vendor/tecnickcom/tcpdf/fonts/msungstdlight.php" => array("1550", "c940b153fb6c5b3498efa181881b5b6c"), + "vendor/tecnickcom/tcpdf/fonts/stsongstdlight.php" => array("1627", "eb85dc872664c0769e9fab1b7540b4d5"), + "vendor/tecnickcom/tcpdf/fonts/symbol.php" => array("2555", "20e28c8b386ddbb38ead777f717d7c44"), + "vendor/tecnickcom/tcpdf/fonts/zapfdingbats.php" => array("2552", "191b3c2e856e750c06c0ba7987f902fb"), + "vendor/tecnickcom/tcpdf/include/barcodes/datamatrix.php" => array("42800", "9a29da1e201fb23de4f499adbb9f6a71"), + "vendor/tecnickcom/tcpdf/include/barcodes/pdf417.php" => array("53882", "3abe66ba8da6b6bf9cf1c6b0e907d51d"), + "vendor/tecnickcom/tcpdf/include/barcodes/qrcode.php" => array("80083", "f214c50485dc7301cdd70b23e46193a0"), + "vendor/tecnickcom/tcpdf/include/sRGB.icc" => array("3048", "060e79448f1454582be37b3de490da2f"), + "vendor/tecnickcom/tcpdf/include/tcpdf_colors.php" => array("14699", "cacdbe68a428ae36151a3d1152b2b77b"), + "vendor/tecnickcom/tcpdf/include/tcpdf_filters.php" => array("14682", "fb794db6e06fa3cf7479fc889894caf3"), + "vendor/tecnickcom/tcpdf/include/tcpdf_font_data.php" => array("313432", "8f83bbc144d70505672f82679546c72d"), + "vendor/tecnickcom/tcpdf/include/tcpdf_fonts.php" => array("95920", "a70cd0b384c720e631792fb309443ac7"), + "vendor/tecnickcom/tcpdf/include/tcpdf_images.php" => array("11509", "72ef804a6aa7becfeda43428ec55489b"), + "vendor/tecnickcom/tcpdf/include/tcpdf_static.php" => array("107704", "ff9d445fef5a526e78949020d4c21b7e"), + "vendor/tecnickcom/tcpdf/LICENSE.TXT" => array("43636", "5c87b66a5358ebcc495b03e0afcd342c"), + "vendor/tecnickcom/tcpdf/README.TXT" => array("5631", "aa8e457a1186b5cd5937806123e6411d"), + "vendor/tecnickcom/tcpdf/tcpdf_autoconfig.php" => array("7155", "bcb93bbeb8cf2831e49ff5541d277a1f"), + "vendor/tecnickcom/tcpdf/tcpdf_barcodes_1d.php" => array("73406", "d306e9ad7b8b67464493c3281417afdc"), + "vendor/tecnickcom/tcpdf/tcpdf_barcodes_2d.php" => array("14683", "17bfd10e3232de9145f5b74a6ef6afac"), + "vendor/tecnickcom/tcpdf/tcpdf_import.php" => array("3327", "6bb88a8a3d69511d1bf9e7af12ab5f47"), + "vendor/tecnickcom/tcpdf/tcpdf_parser.php" => array("27636", "2018d958a0c5723149e988ee5d2290d1"), + "vendor/tecnickcom/tcpdf/tcpdf.php" => array("897169", "7d6a2e3735c4aaf11e08a35c5ce31405"), + "vendor/tecnickcom/tcpdf/tools/convert_fonts_examples.txt" => array("2003", "01d1bb3c8c8bdb35f3837e2715dbe681"), + "vendor/tecnickcom/tcpdf/tools/.htaccess" => array("14", "183e8e4abc660eaba3c3da4bb82b0bcf"), + "vendor/tecnickcom/tcpdf/tools/tcpdf_addfont.php" => array("7449", "8a55d83a4002cf045b586982b64c8356"), "vendor/tedivm/jshrink/composer.json" => array("418", "b7603f6ba06352f101cc74dc6b72c5b1"), - "vendor/tedivm/jshrink/.gitignore" => array("70", "416a3b6d772480ee1f7833d8a790a6a2"), "vendor/tedivm/jshrink/LICENSE" => array("1493", "0a8fd596034db85a8ae22c1d2330edfb"), - "vendor/tedivm/jshrink/README.md" => array("745", "57a8f87308e7b475d9b34689d8c1f381"), + "vendor/tedivm/jshrink/package.xml" => array("7686", "7f1efac19edc2eb5be7b21f9457e1ab8"), + "vendor/tedivm/jshrink/phpunit.xml.dist" => array("414", "77a32cc6e305e2c1a2ac61b3262b2e46"), + "vendor/tedivm/jshrink/README.md" => array("1893", "eff0027af64c410aa1243fb370088565"), "vendor/tedivm/jshrink/src/JShrink/Minifier.php" => array("11416", "d245eafc47812bd47bf18fb4ede5508d"), - "vendor/tedivm/jshrink/.travis.yml" => array("59", "cbac6ff01d60d6cf2045eb78150dabe3"), - "vendor/twig/twig/CHANGELOG" => array("32353", "03b21b00ace91062257dd2855ee196cf"), - "vendor/twig/twig/composer.json" => array("1102", "b2d4cf29abf14c07d3eace63816c58c3"), - "vendor/twig/twig/.editorconfig" => array("224", "c1be7a30bae43bc6616f3f266e199030"), + "vendor/twig/twig/CHANGELOG" => array("37100", "fa3a21578ca0fd4228094277c7390bae"), + "vendor/twig/twig/composer.json" => array("1193", "14ec2cf5c9e4c889948dd5bca4068781"), "vendor/twig/twig/ext/twig/config.m4" => array("221", "20ad1d1005402766ddd16b7110aaf4ca"), "vendor/twig/twig/ext/twig/config.w32" => array("149", "2b1bdfd1a2b8c54966b04f7d143c6632"), - "vendor/twig/twig/ext/twig/.gitignore" => array("328", "7567b95d7259dea2d26ea326730c357d"), - "vendor/twig/twig/ext/twig/LICENSE" => array("1527", "c74e1ac7caf1b0a49bd267b04f9205de"), - "vendor/twig/twig/ext/twig/php_twig.h" => array("1113", "dfc045ec57553713c0db3688a1e69500"), - "vendor/twig/twig/ext/twig/twig.c" => array("30216", "0c2a8fd5aabd9417c98c271b625ab36d"), - "vendor/twig/twig/.gitignore" => array("27", "e8e8b05dcada28af47c4a7f92a2106c0"), - "vendor/twig/twig/lib/Twig/Autoloader.php" => array("1161", "636fed90535196cf757198769e7f26bd"), - "vendor/twig/twig/lib/Twig/CompilerInterface.php" => array("779", "5c6bd37854846a6b1df5e948f2129d53"), - "vendor/twig/twig/lib/Twig/Compiler.php" => array("6906", "61f080c47c7a7f8cd7b9b19a0b418aaf"), - "vendor/twig/twig/lib/Twig/Environment.php" => array("36964", "1a9da7fe09974a4423d8a844fc9de7bb"), + "vendor/twig/twig/ext/twig/php_twig.h" => array("1205", "d6c87ec218f9f3f888c49d2caeb2e3b3"), + "vendor/twig/twig/ext/twig/twig.c" => array("31861", "f9d3c4a10bdf7e014e7ce1182d742b2a"), + "vendor/twig/twig/lib/Twig/Autoloader.php" => array("1440", "ed056f0873067ac38e7f929f09393860"), + "vendor/twig/twig/lib/Twig/BaseNodeVisitor.php" => array("1794", "7000bc1312328eb8d35ebb2c926e1fa0"), + "vendor/twig/twig/lib/Twig/Cache/Filesystem.php" => array("2461", "28874b26b33726bcffed71b2a4660eb3"), + "vendor/twig/twig/lib/Twig/CacheInterface.php" => array("1391", "3032bd05a36c6e5f5f1ffa105fd071b4"), + "vendor/twig/twig/lib/Twig/Cache/Null.php" => array("758", "f755e8a4987054919cd07b518e0b6f3d"), + "vendor/twig/twig/lib/Twig/CompilerInterface.php" => array("782", "3cda46652dfae43db0ba6edad95d0d86"), + "vendor/twig/twig/lib/Twig/Compiler.php" => array("7069", "05cd6f7a7672f9039e4f326fc07f68da"), + "vendor/twig/twig/lib/Twig/Environment.php" => array("42395", "219dd14ce6b053e7c34f1b114e752b81"), "vendor/twig/twig/lib/Twig/Error/Loader.php" => array("946", "0297a085a7341500baa1706db2917a59"), - "vendor/twig/twig/lib/Twig/Error.php" => array("7496", "08acca21a4a57fc7bdecf80411e6d00e"), + "vendor/twig/twig/lib/Twig/Error.php" => array("7541", "d11a146c72d5131bdf14627be19c8dbc"), "vendor/twig/twig/lib/Twig/Error/Runtime.php" => array("395", "3a4c02d5a2b37fb343b6e9fb7056e3e7"), "vendor/twig/twig/lib/Twig/Error/Syntax.php" => array("428", "41d4a9282175957c316b0ffb313d03e0"), - "vendor/twig/twig/lib/Twig/ExistsLoaderInterface.php" => array("692", "2dcb498891d0bea0a55754869618e032"), - "vendor/twig/twig/lib/Twig/ExpressionParser.php" => array("23710", "3fa6eab1d6d8c63b5014f7cadaeb3b58"), - "vendor/twig/twig/lib/Twig/Extension/Core.php" => array("49755", "479a2f77269f5b255b7896096aa8f2d8"), - "vendor/twig/twig/lib/Twig/Extension/Debug.php" => array("2009", "01bfdc7bef74ae207b06874d839acb43"), - "vendor/twig/twig/lib/Twig/Extension/Escaper.php" => array("2788", "1f2be1edc3e5c9fba1aa58976638d8ff"), - "vendor/twig/twig/lib/Twig/ExtensionInterface.php" => array("2091", "8bed99e211ba66f478342efc9faa4013"), + "vendor/twig/twig/lib/Twig/ExistsLoaderInterface.php" => array("692", "7d81a10f4354e32d79d3e376f2318725"), + "vendor/twig/twig/lib/Twig/ExpressionParser.php" => array("25995", "d541aa69c5bcf4ad0967b71bd863c99a"), + "vendor/twig/twig/lib/Twig/Extension/Core.php" => array("53684", "bccaef36e110b05573a5488b16f071ae"), + "vendor/twig/twig/lib/Twig/Extension/Debug.php" => array("2009", "fd47103168ab6df5e91eeea3b5429aa6"), + "vendor/twig/twig/lib/Twig/Extension/Escaper.php" => array("3251", "95bae54d6f7c535e8051d9dd83067068"), + "vendor/twig/twig/lib/Twig/ExtensionInterface.php" => array("2012", "9733ec61d5bfd0d75cd800a9f382e0ab"), "vendor/twig/twig/lib/Twig/Extension/Optimizer.php" => array("664", "90d559bc3f34fd2952905ddd72f906b3"), - "vendor/twig/twig/lib/Twig/Extension.php" => array("2132", "4a3d6c03eb002ca4e15bb4bd87f33679"), - "vendor/twig/twig/lib/Twig/Extension/Sandbox.php" => array("2636", "1cdfeb7346b86fa17eddfd8717d57667"), - "vendor/twig/twig/lib/Twig/Extension/Staging.php" => array("2096", "6fc289bff03eacfd4a2136ba9a503633"), - "vendor/twig/twig/lib/Twig/Extension/StringLoader.php" => array("1474", "15f299f6e4c3ce7acf21f0fda6d63e37"), - "vendor/twig/twig/lib/Twig/FilterCallableInterface.php" => array("473", "557ea6fcaa5b7b2c503a497a3b931267"), - "vendor/twig/twig/lib/Twig/Filter/Function.php" => array("748", "260ca9d38bdc329f4a82405dc11bb1a4"), - "vendor/twig/twig/lib/Twig/FilterInterface.php" => array("846", "6e43136a50b94a80951dd34da661ccc8"), - "vendor/twig/twig/lib/Twig/Filter/Method.php" => array("930", "98d91cb41f5a5a9a4d67f7e967698141"), - "vendor/twig/twig/lib/Twig/Filter/Node.php" => array("731", "6e30432a47d6bff1ddea8e428c2b3276"), - "vendor/twig/twig/lib/Twig/Filter.php" => array("1869", "ffcbc10fbc3456d605d3f8510c60210d"), - "vendor/twig/twig/lib/Twig/FunctionCallableInterface.php" => array("479", "2f8d67c0e76b701a5e4c76f8bf685797"), - "vendor/twig/twig/lib/Twig/Function/Function.php" => array("784", "3aff76b4acc885f1f0f34f915c718daf"), - "vendor/twig/twig/lib/Twig/FunctionInterface.php" => array("804", "1a989db9f04110dff68441ab95849661"), - "vendor/twig/twig/lib/Twig/Function/Method.php" => array("966", "9f5db96bdcddf2cdecf4ac03cffe5072"), - "vendor/twig/twig/lib/Twig/Function/Node.php" => array("739", "e429ee124350f3a9b95f9d6d461d1ca3"), - "vendor/twig/twig/lib/Twig/Function.php" => array("1628", "cdf7c64b75e20ebe85739da5b308addc"), - "vendor/twig/twig/lib/Twig/LexerInterface.php" => array("761", "cd9cf96833fea0cc3e1657a1abb62e83"), - "vendor/twig/twig/lib/Twig/Lexer.php" => array("16213", "f3ebd10d22c844d1ba4f73326b5b77ba"), - "vendor/twig/twig/lib/Twig/Loader/Array.php" => array("2364", "47e8b8acf9d123975c3336f739911375"), - "vendor/twig/twig/lib/Twig/Loader/Chain.php" => array("3636", "82fc84ebb25255ca44b0451ea00a8e03"), - "vendor/twig/twig/lib/Twig/Loader/Filesystem.php" => array("6040", "38c92a7d80cae890eed1bb48fede872a"), - "vendor/twig/twig/lib/Twig/LoaderInterface.php" => array("1334", "72f395318a031756c876a56cc37a5260"), - "vendor/twig/twig/lib/Twig/Loader/String.php" => array("1334", "d60a08d13f3b0183be2602df9fdfcf6a"), + "vendor/twig/twig/lib/Twig/Extension.php" => array("1148", "cb89750141475a8da0f5cbd54c801e6a"), + "vendor/twig/twig/lib/Twig/Extension/Profiler.php" => array("1083", "3eb9d6c19a4852e3261e88a7b26b7bb7"), + "vendor/twig/twig/lib/Twig/Extension/Sandbox.php" => array("2649", "3cf44ff9868c7bb151386bbbe72a281c"), + "vendor/twig/twig/lib/Twig/Extension/Staging.php" => array("2112", "7370912826a6907da8c8e2fc6cdc5456"), + "vendor/twig/twig/lib/Twig/Extension/StringLoader.php" => array("1090", "f1c63b3d9dff53f28ed3e8c1e0cd25dd"), + "vendor/twig/twig/lib/Twig/FileExtensionEscapingStrategy.php" => array("1456", "4b03368c8d273e6e343b4308194d26ba"), + "vendor/twig/twig/lib/Twig/FilterCallableInterface.php" => array("476", "2249cea150fbb3fbc9d8410938df4c1c"), + "vendor/twig/twig/lib/Twig/Filter/Function.php" => array("913", "e1370773a69ab83e6d38948a53c2d903"), + "vendor/twig/twig/lib/Twig/FilterInterface.php" => array("849", "f918648cbc5e14f4432e474e294158ae"), + "vendor/twig/twig/lib/Twig/Filter/Method.php" => array("1093", "e9a7acdf358c7a99c39f6a87aa865f0e"), + "vendor/twig/twig/lib/Twig/Filter/Node.php" => array("892", "1bb35e563eb3f0f964cb60229d21b83a"), + "vendor/twig/twig/lib/Twig/Filter.php" => array("2004", "0f368adbba33e48dde2386318534d2e3"), + "vendor/twig/twig/lib/Twig/FunctionCallableInterface.php" => array("482", "3117a9ec03f6951ffe11252e56efce55"), + "vendor/twig/twig/lib/Twig/Function/Function.php" => array("953", "c5f288891c97519e984b28537cebbf24"), + "vendor/twig/twig/lib/Twig/FunctionInterface.php" => array("807", "343da1424a370c83629cad6b32f81d69"), + "vendor/twig/twig/lib/Twig/Function/Method.php" => array("1133", "0df07b30b73880c422d6a9c4d48a0e77"), + "vendor/twig/twig/lib/Twig/Function/Node.php" => array("904", "e92534d2821ef62bb2386bfdd6e59880"), + "vendor/twig/twig/lib/Twig/Function.php" => array("1775", "08b10c651f2722d7736f4b7c301e870f"), + "vendor/twig/twig/lib/Twig/LexerInterface.php" => array("764", "23c121f90dd026acd6681a8b55ba14c2"), + "vendor/twig/twig/lib/Twig/Lexer.php" => array("16251", "66279d3d814bc08c8028af5933e626ee"), + "vendor/twig/twig/lib/Twig/Loader/Array.php" => array("2389", "a272ca0f665eb402ca464ebea719db98"), + "vendor/twig/twig/lib/Twig/Loader/Chain.php" => array("3713", "18752a499b5ca69311869087efcb3712"), + "vendor/twig/twig/lib/Twig/Loader/Filesystem.php" => array("6966", "796a73abba9983282efd0c4b89a2cc07"), + "vendor/twig/twig/lib/Twig/LoaderInterface.php" => array("1365", "3e997f5d76dd381d63d22480233124d4"), + "vendor/twig/twig/lib/Twig/Loader/String.php" => array("1511", "a3f6aa9f268c2c05659366d221ab2dc0"), "vendor/twig/twig/lib/Twig/Markup.php" => array("764", "7ebff11af28eeedb1fdfe31d92132d20"), - "vendor/twig/twig/lib/Twig/Node/AutoEscape.php" => array("950", "25ae0b7054bb57ea1228774e9e64910a"), - "vendor/twig/twig/lib/Twig/Node/Block.php" => array("1080", "756f2c4dc092046f77c40c0a18e15308"), - "vendor/twig/twig/lib/Twig/Node/BlockReference.php" => array("915", "602786b6a5c05060cc5d55b384cb3f43"), + "vendor/twig/twig/lib/Twig/Node/AutoEscape.php" => array("960", "c12a2f1a4b99560b89b9e9fdc5c3cda7"), + "vendor/twig/twig/lib/Twig/Node/Block.php" => array("1090", "3f09071711cbe03a25b0473633372f5b"), + "vendor/twig/twig/lib/Twig/Node/BlockReference.php" => array("925", "66d4e1224ffbc78e7e4c730f21457d4e"), "vendor/twig/twig/lib/Twig/Node/Body.php" => array("337", "3768ca70d202fd3b29e02f0049ae5432"), - "vendor/twig/twig/lib/Twig/Node/Do.php" => array("839", "033e1b69c2ba4b06b81a00f9a6b34964"), - "vendor/twig/twig/lib/Twig/Node/Embed.php" => array("1181", "b155f10a68234505e1b9da02e2521116"), - "vendor/twig/twig/lib/Twig/Node/Expression/Array.php" => array("2350", "00d9b12693a06114f73ec464d9ef6099"), - "vendor/twig/twig/lib/Twig/Node/Expression/AssignName.php" => array("616", "42e94b8e2a7a849e517ba1f4762feb6a"), + "vendor/twig/twig/lib/Twig/Node/CheckSecurity.php" => array("3002", "cb5bed1e9f5dc05e2a296f2f356bba99"), + "vendor/twig/twig/lib/Twig/Node/Do.php" => array("849", "f8f840206cf6baf3296ab3aaff5fc7a1"), + "vendor/twig/twig/lib/Twig/Node/Embed.php" => array("1305", "23f161e670e5c6e1f6026eeba0699076"), + "vendor/twig/twig/lib/Twig/Node/Expression/Array.php" => array("2360", "4171ae568bdaba3311a26af08d1a53f1"), + "vendor/twig/twig/lib/Twig/Node/Expression/AssignName.php" => array("626", "a90452f1d9bbfd90e668c5629fb284d7"), "vendor/twig/twig/lib/Twig/Node/Expression/Binary/Add.php" => array("413", "5d16cd53c83be9b5d332f2e6a80d124f"), "vendor/twig/twig/lib/Twig/Node/Expression/Binary/And.php" => array("414", "308e349bc7dae1d6e8ffe563a0e059fc"), "vendor/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseAnd.php" => array("420", "621c82ab6fafa611309b08ce5f060f0c"), @@ -3081,120 +6839,130 @@ class Manifest { "vendor/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseXor.php" => array("420", "ff650ff1dcad63ac4dbc9dd57df5eef2"), "vendor/twig/twig/lib/Twig/Node/Expression/Binary/Concat.php" => array("416", "702c3a6603b0138b5a6b3d82e80e2a05"), "vendor/twig/twig/lib/Twig/Node/Expression/Binary/Div.php" => array("413", "5b28cc3d88ddfae12b04f22a4f2860c2"), - "vendor/twig/twig/lib/Twig/Node/Expression/Binary/EndsWith.php" => array("758", "0147cde001fa855a79e976f5110745fc"), + "vendor/twig/twig/lib/Twig/Node/Expression/Binary/EndsWith.php" => array("892", "656c53f7693e079734394b3745044b2d"), "vendor/twig/twig/lib/Twig/Node/Expression/Binary/Equal.php" => array("389", "3d26fb48b35b0f5ad315d296299a4c20"), - "vendor/twig/twig/lib/Twig/Node/Expression/Binary/FloorDiv.php" => array("673", "0910da99e3e01329fec591b7950dfb4f"), + "vendor/twig/twig/lib/Twig/Node/Expression/Binary/FloorDiv.php" => array("683", "df9f4cfa7582ee392095a5235e0f4928"), "vendor/twig/twig/lib/Twig/Node/Expression/Binary/GreaterEqual.php" => array("396", "eecadb39bce3c07af85c8a03e735a326"), "vendor/twig/twig/lib/Twig/Node/Expression/Binary/Greater.php" => array("390", "6209c99dc1bce32340da0bebb5895bf3"), - "vendor/twig/twig/lib/Twig/Node/Expression/Binary/In.php" => array("772", "1c6b54c6128846b57bab85686a34bc73"), + "vendor/twig/twig/lib/Twig/Node/Expression/Binary/In.php" => array("782", "c7510af3489b87ec2154f25041eedd5e"), "vendor/twig/twig/lib/Twig/Node/Expression/Binary/LessEqual.php" => array("393", "e4fd68cfbded83d91cfafafc81e80311"), "vendor/twig/twig/lib/Twig/Node/Expression/Binary/Less.php" => array("387", "d2df3932fc9d04720399302098258a18"), "vendor/twig/twig/lib/Twig/Node/Expression/Binary/Matches.php" => array("662", "ab9d3177aa110a6de020e466a6db65b3"), "vendor/twig/twig/lib/Twig/Node/Expression/Binary/Mod.php" => array("413", "4e61aee836424a723f1c0314f32b3ec3"), "vendor/twig/twig/lib/Twig/Node/Expression/Binary/Mul.php" => array("413", "a8d6077b84b988aef093159e4521969f"), "vendor/twig/twig/lib/Twig/Node/Expression/Binary/NotEqual.php" => array("392", "e5cf8bb39c1bce092fb84cbe5dbcf1f6"), - "vendor/twig/twig/lib/Twig/Node/Expression/Binary/NotIn.php" => array("780", "99281fa54772d040df9301b9018875e6"), + "vendor/twig/twig/lib/Twig/Node/Expression/Binary/NotIn.php" => array("790", "162e482008c8f13c44b39a361982455e"), "vendor/twig/twig/lib/Twig/Node/Expression/Binary/Or.php" => array("413", "835aa4edcbe9290749ca3e6f56c1fcd7"), - "vendor/twig/twig/lib/Twig/Node/Expression/Binary.php" => array("1028", "5753c84484d2fd6b4ca0920fb30513da"), - "vendor/twig/twig/lib/Twig/Node/Expression/Binary/Power.php" => array("764", "c960e3c92ae6311d280f491658355c63"), - "vendor/twig/twig/lib/Twig/Node/Expression/Binary/Range.php" => array("766", "3362365c37d9cf6d3e56c1eb0dbd104f"), - "vendor/twig/twig/lib/Twig/Node/Expression/Binary/StartsWith.php" => array("669", "8d5900c492c79a667e2739a36e354d56"), + "vendor/twig/twig/lib/Twig/Node/Expression/Binary.php" => array("1038", "e94cfe28739090c3a4d0c02d62b11fa4"), + "vendor/twig/twig/lib/Twig/Node/Expression/Binary/Power.php" => array("774", "e8b1beacdce801928fcf5a3f41770db2"), + "vendor/twig/twig/lib/Twig/Node/Expression/Binary/Range.php" => array("776", "a89823fdc86c5d92240961148ba5d4a0"), + "vendor/twig/twig/lib/Twig/Node/Expression/Binary/StartsWith.php" => array("881", "d415d9719a3f33f9bcbb3cbd71d1ca3a"), "vendor/twig/twig/lib/Twig/Node/Expression/Binary/Sub.php" => array("413", "47650251c8b7e7c2b82df3a699d10cd6"), - "vendor/twig/twig/lib/Twig/Node/Expression/BlockReference.php" => array("1390", "b5cdd67c38367d62735ea829a4be6c53"), - "vendor/twig/twig/lib/Twig/Node/Expression/Call.php" => array("6426", "145a35b399a10d2d893802028d4d236f"), + "vendor/twig/twig/lib/Twig/Node/Expression/BlockReference.php" => array("1396", "039c443e43555e2400f5d0512068d3dc"), + "vendor/twig/twig/lib/Twig/Node/Expression/Call.php" => array("9542", "4f379f0639b7b20662f0a33d49559e73"), "vendor/twig/twig/lib/Twig/Node/Expression/Conditional.php" => array("902", "94611e1b6bbc76388011855f0348fc2e"), "vendor/twig/twig/lib/Twig/Node/Expression/Constant.php" => array("557", "b3703810a66fd8d2fe792ed95e9949ea"), - "vendor/twig/twig/lib/Twig/Node/Expression/ExtensionReference.php" => array("806", "11d53bdac885a7604336297228ac7091"), + "vendor/twig/twig/lib/Twig/Node/Expression/ExtensionReference.php" => array("816", "629b4b904799751bf0f9b860fff3c2cf"), "vendor/twig/twig/lib/Twig/Node/Expression/Filter/Default.php" => array("1583", "81eb255cf59793aa37f7ab73df5b8f47"), - "vendor/twig/twig/lib/Twig/Node/Expression/Filter.php" => array("1378", "859a893a66e17778142c059668f5ffd2"), - "vendor/twig/twig/lib/Twig/Node/Expression/Function.php" => array("1249", "f5dce5675c390ae1d35497c7f76cf22a"), - "vendor/twig/twig/lib/Twig/Node/Expression/GetAttr.php" => array("2240", "bf8ac92138c4f5cdab11de9a7aadff46"), + "vendor/twig/twig/lib/Twig/Node/Expression/Filter.php" => array("1511", "460bd0c8461592d59f5652034c2b592c"), + "vendor/twig/twig/lib/Twig/Node/Expression/Function.php" => array("1388", "166e90837f902588071783d187027d4c"), + "vendor/twig/twig/lib/Twig/Node/Expression/GetAttr.php" => array("2314", "652744703da073f2139df43702efefb4"), "vendor/twig/twig/lib/Twig/Node/Expression/MethodCall.php" => array("1203", "ec007ddb0878bb9bc7017a12c6e4c080"), - "vendor/twig/twig/lib/Twig/Node/Expression/Name.php" => array("2872", "09b1ba6d503efb4d48a166fcf5c74a6f"), - "vendor/twig/twig/lib/Twig/Node/Expression/Parent.php" => array("1230", "c6628a0c055557b1ac412590ce76e4af"), + "vendor/twig/twig/lib/Twig/Node/Expression/Name.php" => array("3284", "fb2a40e9cfbe28602786e6d650b74045"), + "vendor/twig/twig/lib/Twig/Node/Expression/Parent.php" => array("1236", "1c6002bf3cb8ffff85c4db193663294a"), "vendor/twig/twig/lib/Twig/Node/Expression.php" => array("415", "d2fcc744c0d3ece6f32c398ae50e2cb5"), "vendor/twig/twig/lib/Twig/Node/Expression/TempName.php" => array("594", "312be4fbbf9f33caec6b64733e8f7c35"), "vendor/twig/twig/lib/Twig/Node/Expression/Test/Constant.php" => array("1130", "cea24b966c64017afa46413e189956b7"), "vendor/twig/twig/lib/Twig/Node/Expression/Test/Defined.php" => array("1607", "28345503b665a4d6edbbd7b06428718a"), - "vendor/twig/twig/lib/Twig/Node/Expression/Test/Divisibleby.php" => array("747", "cf1043b7f1479d71150e6202be527bef"), + "vendor/twig/twig/lib/Twig/Node/Expression/Test/Divisibleby.php" => array("748", "0d047f7788b0e83479551206536c3662"), "vendor/twig/twig/lib/Twig/Node/Expression/Test/Even.php" => array("636", "b67fd353edb6c3804d8ab2d41c6f8cbb"), "vendor/twig/twig/lib/Twig/Node/Expression/Test/Null.php" => array("618", "d0fe20539e14edd2085ec6b0e139b415"), "vendor/twig/twig/lib/Twig/Node/Expression/Test/Odd.php" => array("633", "5bb79634c28ab1e54b7ca7ded7ee9f28"), - "vendor/twig/twig/lib/Twig/Node/Expression/Test.php" => array("1036", "a0b2bd4d99f906c4857f057c7bb4864e"), + "vendor/twig/twig/lib/Twig/Node/Expression/Test.php" => array("1163", "179eae0507a747cebddd73eab56f6d22"), "vendor/twig/twig/lib/Twig/Node/Expression/Test/Sameas.php" => array("690", "0126ce6f962cf906f8f2370133c3ddff"), "vendor/twig/twig/lib/Twig/Node/Expression/Unary/Neg.php" => array("404", "08e8112e6fabf7eba63b3a19672706c9"), "vendor/twig/twig/lib/Twig/Node/Expression/Unary/Not.php" => array("404", "0d3a4717baffc2ec33b373bb265a6413"), - "vendor/twig/twig/lib/Twig/Node/Expression/Unary.php" => array("754", "b712815036f4e0cbca846e3088cb3fc6"), + "vendor/twig/twig/lib/Twig/Node/Expression/Unary.php" => array("709", "2b004cc15d141f618a5e8f2c22e2305b"), "vendor/twig/twig/lib/Twig/Node/Expression/Unary/Pos.php" => array("404", "e35ab856982e8b07351a893609751c26"), - "vendor/twig/twig/lib/Twig/Node/Flush.php" => array("731", "4fd771f76093c1b60f1ddc1535295c19"), - "vendor/twig/twig/lib/Twig/Node/ForLoop.php" => array("1623", "f2fabfada4aac6f3358d99e39170508d"), - "vendor/twig/twig/lib/Twig/Node/For.php" => array("4367", "687d9e44460eaa3b47e2848b79e5011a"), - "vendor/twig/twig/lib/Twig/Node/If.php" => array("1723", "2263178b12b0fa6609b90cd5ee7dba8d"), - "vendor/twig/twig/lib/Twig/Node/Import.php" => array("1289", "479ef20c51f0e633c7044adead4d0456"), - "vendor/twig/twig/lib/Twig/Node/Include.php" => array("2897", "37a4f90abba8755171ada5732c4bda64"), - "vendor/twig/twig/lib/Twig/NodeInterface.php" => array("649", "2105584cf35b89563c3f86b647ce1f43"), - "vendor/twig/twig/lib/Twig/Node/Macro.php" => array("2655", "8cbf723f3f7518f120f1c17fc033b62e"), - "vendor/twig/twig/lib/Twig/Node/Module.php" => array("12182", "1702dccd13b5a0bfc63a7096327eea0b"), + "vendor/twig/twig/lib/Twig/Node/Flush.php" => array("741", "03eb18a04ac1322f21ac8091feb85dcb"), + "vendor/twig/twig/lib/Twig/Node/ForLoop.php" => array("1633", "8b5c430f031903f112c44e0546b4c592"), + "vendor/twig/twig/lib/Twig/Node/For.php" => array("4312", "53b1e061b662f69da0a3a84a10297b75"), + "vendor/twig/twig/lib/Twig/Node/If.php" => array("1733", "83fd51054492861e13430c3ce2f10cfa"), + "vendor/twig/twig/lib/Twig/Node/Import.php" => array("1439", "b978e1b7870956095be615c9172bec56"), + "vendor/twig/twig/lib/Twig/Node/Include.php" => array("2538", "6ce31c0fff8adff66d0b3ee38d4dd9a7"), + "vendor/twig/twig/lib/Twig/NodeInterface.php" => array("662", "18e4ff5e65c12efabb1b04cbe1a158e1"), + "vendor/twig/twig/lib/Twig/Node/Macro.php" => array("3509", "81cd380d11eed35ce31702ef3a0b2eb0"), + "vendor/twig/twig/lib/Twig/Node/Module.php" => array("13122", "252f9162f4fa9026e15569209903e84a"), "vendor/twig/twig/lib/Twig/NodeOutputInterface.php" => array("351", "2125948f894bcba40e4dd4bc906d47e2"), - "vendor/twig/twig/lib/Twig/Node.php" => array("5801", "e24ef0947fe4eb47966813c035b21058"), - "vendor/twig/twig/lib/Twig/Node/Print.php" => array("934", "fe93dc5534fd3c96eccfc4b6f3d5f6b9"), - "vendor/twig/twig/lib/Twig/Node/SandboxedModule.php" => array("2042", "c0d63404cf94209400098023f29d35c4"), - "vendor/twig/twig/lib/Twig/Node/SandboxedPrint.php" => array("1597", "b465d85ba2b5016df5c4f78a14bb184c"), - "vendor/twig/twig/lib/Twig/Node/Sandbox.php" => array("1259", "3dae5d4756cd400f5f26b18d744212cd"), - "vendor/twig/twig/lib/Twig/Node/Set.php" => array("3224", "d0e25c6c5329877865cc4d18cd53c473"), + "vendor/twig/twig/lib/Twig/Node.php" => array("5782", "b86d9604c81d34c5ac7176098291dbfb"), + "vendor/twig/twig/lib/Twig/Node/Print.php" => array("944", "304c80e1a81f270fba5200c30b919f1c"), + "vendor/twig/twig/lib/Twig/Node/SandboxedPrint.php" => array("1639", "0189a34f2472a321646d5797902de1b0"), + "vendor/twig/twig/lib/Twig/Node/Sandbox.php" => array("1269", "129cb6cd4c57c500622b15233009a925"), + "vendor/twig/twig/lib/Twig/Node/Set.php" => array("3234", "a8fdcf7f0924d16b20cf337c7b0a8a4b"), "vendor/twig/twig/lib/Twig/Node/SetTemp.php" => array("840", "5c93ae290cfe346e9a2d155494f28626"), - "vendor/twig/twig/lib/Twig/Node/Spaceless.php" => array("972", "6b38e3713a5ffda4e7197602f31122e7"), - "vendor/twig/twig/lib/Twig/Node/Text.php" => array("872", "a4dc26dca8d39676f578401e19a05e12"), - "vendor/twig/twig/lib/Twig/NodeTraverser.php" => array("2354", "2a1673a9e56b086c2773d07ca47b9d02"), - "vendor/twig/twig/lib/Twig/NodeVisitor/Escaper.php" => array("5366", "9fe8e3ad07965534c4724211c33daafc"), - "vendor/twig/twig/lib/Twig/NodeVisitorInterface.php" => array("1328", "5efd03cd67a21bf09a6e09be86b570a8"), - "vendor/twig/twig/lib/Twig/NodeVisitor/Optimizer.php" => array("7982", "b57d389aa29b600141e2e3e94327c9c6"), - "vendor/twig/twig/lib/Twig/NodeVisitor/SafeAnalysis.php" => array("4594", "cba1963c52ef6763d47fff8cd7ee6d05"), - "vendor/twig/twig/lib/Twig/NodeVisitor/Sandbox.php" => array("2600", "c823c1317891b559dae63ee717a1b2fe"), - "vendor/twig/twig/lib/Twig/ParserInterface.php" => array("733", "eca931dfd8457bbbb98720da37ae683b"), - "vendor/twig/twig/lib/Twig/Parser.php" => array("11763", "b0d037510f563ebbef2fd4e87eb470ec"), + "vendor/twig/twig/lib/Twig/Node/Spaceless.php" => array("982", "0b2160f8c47033d0a6756333636d5bca"), + "vendor/twig/twig/lib/Twig/Node/Text.php" => array("882", "3bd0ee146116e0aa21e1822a02242cb0"), + "vendor/twig/twig/lib/Twig/NodeTraverser.php" => array("2365", "6bbfc50054836007c8cf42c073d729b3"), + "vendor/twig/twig/lib/Twig/NodeVisitor/Escaper.php" => array("4917", "d32e9f3460e315752b1aa8addbad0307"), + "vendor/twig/twig/lib/Twig/NodeVisitorInterface.php" => array("1324", "b82cb0e406b2cf95947c705cfd7b226f"), + "vendor/twig/twig/lib/Twig/NodeVisitor/Optimizer.php" => array("9058", "dac3694eed8cb5006fa3531f836642ca"), + "vendor/twig/twig/lib/Twig/NodeVisitor/SafeAnalysis.php" => array("4847", "430600aeeec9772b821e55ceb278105c"), + "vendor/twig/twig/lib/Twig/NodeVisitor/Sandbox.php" => array("2372", "d9e126ead14400900d08bf5352f79927"), + "vendor/twig/twig/lib/Twig/ParserInterface.php" => array("736", "7b01b047dc4e9e7c63bdf2cad6cca089"), + "vendor/twig/twig/lib/Twig/Parser.php" => array("12087", "8f63128e7c06ee432e8c766abe6d2e50"), + "vendor/twig/twig/lib/Twig/Profiler/Dumper/Blackfire.php" => array("1960", "64f4301ca251c43d1a2bc52045f486c4"), + "vendor/twig/twig/lib/Twig/Profiler/Dumper/Html.php" => array("1439", "4f8115d6c38bf2a805a3d05234baaecf"), + "vendor/twig/twig/lib/Twig/Profiler/Dumper/Text.php" => array("1986", "ac5796497aa637098c9fb1beeefe7626"), + "vendor/twig/twig/lib/Twig/Profiler/Node/EnterProfile.php" => array("1223", "4a259d3710d066c0340272c164d9ac48"), + "vendor/twig/twig/lib/Twig/Profiler/Node/LeaveProfile.php" => array("772", "039b88bfb04233fb5fee0ab5e42fd817"), + "vendor/twig/twig/lib/Twig/Profiler/NodeVisitor/Profiler.php" => array("2334", "2a75334fe100fdd54fe3d62f6c6dc701"), + "vendor/twig/twig/lib/Twig/Profiler/Profile.php" => array("3676", "66def9e5042d51b2a4ea7e27b1784e34"), "vendor/twig/twig/lib/Twig/Sandbox/SecurityError.php" => array("384", "2038673c16bf93db76dd4e8085bb1199"), + "vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedFilterError.php" => array("776", "a1dc41649daba29cd350f17f19584db7"), + "vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedFunctionError.php" => array("788", "836916750b2284010b903ca967b0b411"), + "vendor/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedTagError.php" => array("748", "c7b865fb29c6e585a4b5442eed42db23"), "vendor/twig/twig/lib/Twig/Sandbox/SecurityPolicyInterface.php" => array("560", "d556afa8416c83b79752040a10ece3d4"), - "vendor/twig/twig/lib/Twig/Sandbox/SecurityPolicy.php" => array("3708", "28281244b087a7de31fde6203829fd83"), - "vendor/twig/twig/lib/Twig/SimpleFilter.php" => array("2117", "f539a1cf697d6df8dbe0a1ab20fa60c4"), - "vendor/twig/twig/lib/Twig/SimpleFunction.php" => array("1872", "3288da22d048babb201c4c8b21383672"), - "vendor/twig/twig/lib/Twig/SimpleTest.php" => array("920", "67c237535f937d19f9acbabe4bee2b7c"), - "vendor/twig/twig/lib/Twig/TemplateInterface.php" => array("1244", "58ada114eb5a42306e3b510af39dfeae"), - "vendor/twig/twig/lib/Twig/Template.php" => array("15939", "259e9b0fa9da7dc774a9eaf5c8a5667f"), - "vendor/twig/twig/lib/Twig/TestCallableInterface.php" => array("432", "680324d83f36b80a71cc3e18ce6da3bf"), - "vendor/twig/twig/lib/Twig/Test/Function.php" => array("705", "c5256cfda774aad3796e83c5ccc76df1"), - "vendor/twig/twig/lib/Twig/Test/IntegrationTestCase.php" => array("5645", "4e4581eabad75b9cbeb41c4f716e62eb"), - "vendor/twig/twig/lib/Twig/TestInterface.php" => array("506", "c64d3221a3e0ac3a1a27f629529ca22f"), - "vendor/twig/twig/lib/Twig/Test/Method.php" => array("887", "b05886b7c3bb0d5a4b4fd871a0de612e"), - "vendor/twig/twig/lib/Twig/Test/Node.php" => array("688", "41a3b3e4657168e15d1b1613e5ae689a"), - "vendor/twig/twig/lib/Twig/Test/NodeTestCase.php" => array("1593", "fcf420a35fd3b0a9117bff01ec7df9d2"), - "vendor/twig/twig/lib/Twig/Test.php" => array("753", "907df6428c547e9a5af3a07ea99c31a1"), - "vendor/twig/twig/lib/Twig/TokenParser/AutoEscape.php" => array("2611", "999df38e7f5a2dc1a0d255515e35f6d5"), - "vendor/twig/twig/lib/Twig/TokenParser/Block.php" => array("2622", "91f80e924e22f40739703995e18d6bcb"), - "vendor/twig/twig/lib/Twig/TokenParserBrokerInterface.php" => array("1270", "0a586d723c9c4a0eee1fbb8e39bad88e"), - "vendor/twig/twig/lib/Twig/TokenParserBroker.php" => array("3879", "29a8948f912da59f4bcab2a5d718bb59"), + "vendor/twig/twig/lib/Twig/Sandbox/SecurityPolicy.php" => array("3781", "8777cac171b4e8f1c182e9d3fbdee403"), + "vendor/twig/twig/lib/Twig/SimpleFilter.php" => array("2474", "eb763eed98fb37876ae1289cf2afa96b"), + "vendor/twig/twig/lib/Twig/SimpleFunction.php" => array("2237", "bc68c2d38574020ccbbd8f103ccce6b2"), + "vendor/twig/twig/lib/Twig/SimpleTest.php" => array("1307", "ce84b1599e58d33b4fde25badae5bdc5"), + "vendor/twig/twig/lib/Twig/TemplateInterface.php" => array("1243", "3391f37e99f475da27edaaf3ce8ffe86"), + "vendor/twig/twig/lib/Twig/Template.php" => array("20363", "3fab800221b474a9491909b22e133df8"), + "vendor/twig/twig/lib/Twig/TestCallableInterface.php" => array("435", "6c282931db6133a729a713d246d6fc6c"), + "vendor/twig/twig/lib/Twig/Test/Function.php" => array("866", "e7358794a78cc2b7b858abccbc5c6d74"), + "vendor/twig/twig/lib/Twig/Test/IntegrationTestCase.php" => array("7639", "d59404075c2598718e9aa390a2514e42"), + "vendor/twig/twig/lib/Twig/TestInterface.php" => array("509", "dc9086f80f2e7bd7a552b76c080c9f33"), + "vendor/twig/twig/lib/Twig/Test/Method.php" => array("1046", "5c078658129d554b298c110468174d0a"), + "vendor/twig/twig/lib/Twig/Test/Node.php" => array("816", "575c0dba0cc29e772ebad48895c96566"), + "vendor/twig/twig/lib/Twig/Test/NodeTestCase.php" => array("1700", "76dbdba1e96927a3c7ae6654ae4bb81d"), + "vendor/twig/twig/lib/Twig/Test.php" => array("905", "3fe6d271859891bc6feef9eaa4fa0fde"), + "vendor/twig/twig/lib/Twig/TokenParser/AutoEscape.php" => array("2754", "ead0b2dea144a4ff2465f5438c895a29"), + "vendor/twig/twig/lib/Twig/TokenParser/Block.php" => array("2628", "f85e7a6f2f30a61704b010eb164c032b"), + "vendor/twig/twig/lib/Twig/TokenParserBrokerInterface.php" => array("1273", "d628516ee447f244512ec26d2c745bc2"), + "vendor/twig/twig/lib/Twig/TokenParserBroker.php" => array("4101", "3ada35cdc18bdb38b7cdfbe54540a767"), "vendor/twig/twig/lib/Twig/TokenParser/Do.php" => array("980", "87cfd1e3e9c357fa6707126c4708f5e0"), "vendor/twig/twig/lib/Twig/TokenParser/Embed.php" => array("1947", "f59d933b6e44590ed33aa6dff014fcc1"), "vendor/twig/twig/lib/Twig/TokenParser/Extends.php" => array("1355", "83f589057c3b2ed4b0ee6fcd9c7140ce"), "vendor/twig/twig/lib/Twig/TokenParser/Filter.php" => array("1702", "947049e09760ed46c9195e435fe4bc52"), "vendor/twig/twig/lib/Twig/TokenParser/Flush.php" => array("905", "b6f0a3ec7581d0414899fc50bdaad814"), "vendor/twig/twig/lib/Twig/TokenParser/For.php" => array("4799", "c4ae472d2b3821f8ae56f86464cc295e"), - "vendor/twig/twig/lib/Twig/TokenParser/From.php" => array("1793", "e180cccf0c62b7cad73c63edb4005baa"), + "vendor/twig/twig/lib/Twig/TokenParser/From.php" => array("2037", "fc8ff0c6ad54f030f67b3611b2446e97"), "vendor/twig/twig/lib/Twig/TokenParser/If.php" => array("2709", "35fdb85b56353849c394a9e4da7fe715"), "vendor/twig/twig/lib/Twig/TokenParser/Import.php" => array("1296", "3e7397da9c81999b32a1b637871db37c"), "vendor/twig/twig/lib/Twig/TokenParser/Include.php" => array("1846", "aafd1e3b2b6e6b4223ddc3c0eff599a0"), - "vendor/twig/twig/lib/Twig/TokenParserInterface.php" => array("953", "62c183068343c7dbcad42a1cc4b27581"), - "vendor/twig/twig/lib/Twig/TokenParser/Macro.php" => array("2023", "5f0a590691c64a79a1031a7ebe1da557"), - "vendor/twig/twig/lib/Twig/TokenParser.php" => array("662", "83224f13e3f3e14ed5e7c0d1c567a9c6"), + "vendor/twig/twig/lib/Twig/TokenParserInterface.php" => array("966", "6aae321543a7847609cedfc3396d18ee"), + "vendor/twig/twig/lib/Twig/TokenParser/Macro.php" => array("2029", "c84e6bfb37cf2b3f06eed5b21c55e177"), + "vendor/twig/twig/lib/Twig/TokenParser.php" => array("675", "1cdcd32c05c0b10558619580f63532d2"), "vendor/twig/twig/lib/Twig/TokenParser/Sandbox.php" => array("1950", "4e7a32b6b2436967b85ac5bee63f99da"), - "vendor/twig/twig/lib/Twig/TokenParser/Set.php" => array("2286", "9bb035bb1ad955d96257a8a5e0e6b496"), + "vendor/twig/twig/lib/Twig/TokenParser/Set.php" => array("2286", "fb3639fc5ff4871428008527cbdf8b10"), "vendor/twig/twig/lib/Twig/TokenParser/Spaceless.php" => array("1392", "ab7d1d53dd9b4e7628837e7af93b3db3"), "vendor/twig/twig/lib/Twig/TokenParser/Use.php" => array("2135", "7bd09009867e76e0b006586fa3fc6275"), - "vendor/twig/twig/lib/Twig/Token.php" => array("6211", "4b3f25c7d4929c29712d25711bc87768"), - "vendor/twig/twig/lib/Twig/TokenStream.php" => array("3989", "8eb8e69d38ec4f6ee0b73d716d064957"), + "vendor/twig/twig/lib/Twig/Token.php" => array("6038", "0840e8a6e9c1cf6603fb0a3f1e771bf2"), + "vendor/twig/twig/lib/Twig/TokenStream.php" => array("3950", "5ce8f2e90ee93b6772509ad0dcfe2f67"), + "vendor/twig/twig/lib/Twig/Util/DeprecationCollector.php" => array("2123", "26540ffa664d8f81cc308da12f6add77"), + "vendor/twig/twig/lib/Twig/Util/TemplateDirIterator.php" => array("497", "958ac185752b7b3e1fdd7c18138ff173"), "vendor/twig/twig/LICENSE" => array("1497", "1886505263500ef827db124cf26c2408"), - "vendor/twig/twig/phpunit.xml.dist" => array("651", "64f59fc76504c822331e5e5eccc3e1cf"), + "vendor/twig/twig/phpunit.xml.dist" => array("652", "d665ebeeddfb06ed1efbdda2f1baa54f"), "vendor/twig/twig/README.rst" => array("486", "32d5a3ca77dace5b9255842b15c55699"), - "vendor/twig/twig/.travis.yml" => array("440", "c60b7a77686a5d1a4201f9fbc4c88dbb"), ); } diff --git a/www/analytics/console b/www/analytics/console index 0e3dc3ff..47b8ba59 100755 --- a/www/analytics/console +++ b/www/analytics/console @@ -3,26 +3,25 @@ if (!defined('PIWIK_DOCUMENT_ROOT')) { define('PIWIK_DOCUMENT_ROOT', dirname(__FILE__) == '/' ? '' : dirname(__FILE__)); } + +if (file_exists(PIWIK_DOCUMENT_ROOT . '/bootstrap.php')) { + require_once PIWIK_DOCUMENT_ROOT . '/bootstrap.php'; +} + if (!defined('PIWIK_INCLUDE_PATH')) { define('PIWIK_INCLUDE_PATH', PIWIK_DOCUMENT_ROOT); } -if (!defined('PIWIK_USER_PATH')) { - define('PIWIK_USER_PATH', PIWIK_DOCUMENT_ROOT); -} -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(); +require_once PIWIK_INCLUDE_PATH . '/core/bootstrap.php'; if (!Piwik\Common::isPhpCliMode()) { exit; } +if (!defined('PIWIK_ENABLE_ERROR_HANDLER') || PIWIK_ENABLE_ERROR_HANDLER) { + Piwik\ErrorHandler::registerErrorHandler(); + Piwik\ExceptionHandler::setUp(); +} + $console = new Piwik\Console(); -$console->init(); -$console->run(); \ No newline at end of file +$console->run(); diff --git a/www/analytics/core/.htaccess b/www/analytics/core/.htaccess index 6cd2e134..f4e970ee 100644 --- a/www/analytics/core/.htaccess +++ b/www/analytics/core/.htaccess @@ -1,13 +1,8 @@ - -Deny from all - - - -Deny from all - - - -Deny from all - + + Deny from all + + = 2.4> + Require all denied + diff --git a/www/analytics/core/API/ApiRenderer.php b/www/analytics/core/API/ApiRenderer.php new file mode 100644 index 00000000..36f84e63 --- /dev/null +++ b/www/analytics/core/API/ApiRenderer.php @@ -0,0 +1,131 @@ +request = $request; + $this->init(); + } + + protected function init() + { + } + + abstract public function sendHeader(); + + public function renderSuccess($message) + { + return 'Success:' . $message; + } + + public function renderException($message, \Exception $exception) + { + return $message; + } + + public function renderScalar($scalar) + { + $dataTable = new DataTable\Simple(); + $dataTable->addRowsFromArray(array($scalar)); + return $this->renderDataTable($dataTable); + } + + public function renderDataTable($dataTable) + { + $renderer = $this->buildDataTableRenderer($dataTable); + return $renderer->render(); + } + + public function renderArray($array) + { + $renderer = $this->buildDataTableRenderer($array); + return $renderer->render(); + } + + public function renderObject($object) + { + $exception = new Exception('The API cannot handle this data structure.'); + return $this->renderException($exception->getMessage(), $exception); + } + + public function renderResource($resource) + { + $exception = new Exception('The API cannot handle this data structure.'); + return $this->renderException($exception->getMessage(), $exception); + } + + /** + * @param $dataTable + * @return Renderer + */ + protected function buildDataTableRenderer($dataTable) + { + $format = self::getFormatFromClass(get_class($this)); + if ($format == 'json2') { + $format = 'json'; + } + + $renderer = Renderer::factory($format); + $renderer->setTable($dataTable); + $renderer->setRenderSubTables(Common::getRequestVar('expanded', false, 'int', $this->request)); + $renderer->setHideIdSubDatableFromResponse(Common::getRequestVar('hideIdSubDatable', false, 'int', $this->request)); + + return $renderer; + } + + /** + * @param string $format + * @param array $request + * @return ApiRenderer + * @throws Exception + */ + public static function factory($format, $request) + { + $formatToCheck = '\\' . ucfirst(strtolower($format)); + + $rendererClassnames = Plugin\Manager::getInstance()->findMultipleComponents('Renderer', 'Piwik\\API\\ApiRenderer'); + + foreach ($rendererClassnames as $klassName) { + if (Common::stringEndsWith($klassName, $formatToCheck)) { + return new $klassName($request); + } + } + + $availableRenderers = array(); + foreach ($rendererClassnames as $rendererClassname) { + $availableRenderers[] = self::getFormatFromClass($rendererClassname); + } + + $availableRenderers = implode(', ', $availableRenderers); + Common::sendHeader('Content-Type: text/plain; charset=utf-8'); + throw new Exception(Piwik::translate('General_ExceptionInvalidRendererFormat', array($format, $availableRenderers))); + } + + private static function getFormatFromClass($klassname) + { + $klass = explode('\\', $klassname); + + return strtolower(end($klass)); + } +} diff --git a/www/analytics/core/API/CORSHandler.php b/www/analytics/core/API/CORSHandler.php new file mode 100644 index 00000000..779794df --- /dev/null +++ b/www/analytics/core/API/CORSHandler.php @@ -0,0 +1,41 @@ +domains = Url::getCorsHostsFromConfig(); + } + + public function handle() + { + // allow Piwik to serve data to all domains + if (in_array("*", $this->domains)) { + header('Access-Control-Allow-Origin: *'); + return; + } + + // specifically allow if it is one of the whitelisted CORS domains + if (!empty($_SERVER['HTTP_ORIGIN'])) { + $origin = $_SERVER['HTTP_ORIGIN']; + if (in_array($origin, $this->domains, true)) { + header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']); + } + } + } +} diff --git a/www/analytics/core/API/DataTableGenericFilter.php b/www/analytics/core/API/DataTableGenericFilter.php index 965e4fde..5d4ba494 100644 --- a/www/analytics/core/API/DataTableGenericFilter.php +++ b/www/analytics/core/API/DataTableGenericFilter.php @@ -1,6 +1,6 @@ request = $request; + $this->report = $report; } /** @@ -37,6 +54,16 @@ class DataTableGenericFilter $this->applyGenericFilters($table); } + /** + * Makes sure a set of filters are not run. + * + * @param string[] $filterNames The name of each filter to disable. + */ + public function disableFilters($filterNames) + { + $this->disabledFilters = array_unique(array_merge($this->disabledFilters, $filterNames)); + } + /** * Returns an array containing the information of the generic Filter * to be applied automatically to the data resulting from the API calls. @@ -51,43 +78,54 @@ class DataTableGenericFilter */ public static function getGenericFiltersInformation() { - if (is_null(self::$genericFiltersInfo)) { - self::$genericFiltersInfo = array( - 'Pattern' => array( - 'filter_column' => array('string', 'label'), - 'filter_pattern' => array('string'), - ), - 'PatternRecursive' => array( - 'filter_column_recursive' => array('string', 'label'), - 'filter_pattern_recursive' => array('string'), - ), - 'ExcludeLowPopulation' => array( - 'filter_excludelowpop' => array('string'), - 'filter_excludelowpop_value' => array('float', '0'), - ), - 'AddColumnsProcessedMetrics' => array( - 'filter_add_columns_when_show_all_columns' => array('integer') - ), - 'AddColumnsProcessedMetricsGoal' => array( - 'filter_update_columns_when_show_all_goals' => array('integer'), - 'idGoal' => array('string', AddColumnsProcessedMetricsGoal::GOALS_OVERVIEW), - ), - 'Sort' => array( - 'filter_sort_column' => array('string'), - 'filter_sort_order' => array('string', 'desc'), - ), - 'Truncate' => array( - 'filter_truncate' => array('integer'), - ), - 'Limit' => array( - 'filter_offset' => array('integer', '0'), - 'filter_limit' => array('integer'), - 'keep_summary_row' => array('integer', '0'), - ), - ); + return array( + array('Pattern', + array( + 'filter_column' => array('string', 'label'), + 'filter_pattern' => array('string') + )), + array('PatternRecursive', + array( + 'filter_column_recursive' => array('string', 'label'), + 'filter_pattern_recursive' => array('string'), + )), + array('ExcludeLowPopulation', + array( + 'filter_excludelowpop' => array('string'), + 'filter_excludelowpop_value' => array('float', '0'), + )), + array('Sort', + array( + 'filter_sort_column' => array('string'), + 'filter_sort_order' => array('string', 'desc'), + )), + array('Truncate', + array( + 'filter_truncate' => array('integer'), + )), + array('Limit', + array( + 'filter_offset' => array('integer', '0'), + 'filter_limit' => array('integer'), + 'keep_summary_row' => array('integer', '0'), + )) + ); + } + + private function getGenericFiltersHavingDefaultValues() + { + $filters = self::getGenericFiltersInformation(); + + if ($this->report && $this->report->getDefaultSortColumn()) { + foreach ($filters as $index => $filter) { + if ($filter[0] === 'Sort') { + $filters[$index][1]['filter_sort_column'] = array('string', $this->report->getDefaultSortColumn()); + $filters[$index][1]['filter_sort_order'] = array('string', $this->report->getDefaultSortOrder()); + } + } } - return self::$genericFiltersInfo; + return $filters; } /** @@ -107,13 +145,20 @@ class DataTableGenericFilter return; } - $genericFilters = self::getGenericFiltersInformation(); + $genericFilters = $this->getGenericFiltersHavingDefaultValues(); $filterApplied = false; - foreach ($genericFilters as $filterName => $parameters) { + foreach ($genericFilters as $filterMeta) { + $filterName = $filterMeta[0]; + $filterParams = $filterMeta[1]; $filterParameters = array(); $exceptionRaised = false; - foreach ($parameters as $name => $info) { + + if (in_array($filterName, $this->disabledFilters)) { + continue; + } + + foreach ($filterParams as $name => $info) { // parameter type to cast to $type = $info[0]; @@ -123,12 +168,6 @@ class DataTableGenericFilter $defaultValue = $info[1]; } - // third element in the array, if it exists, overrides the name of the request variable - $varName = $name; - if (isset($info[2])) { - $varName = $info[2]; - } - try { $value = Common::getRequestVar($name, $defaultValue, $type, $this->request); settype($value, $type); @@ -144,6 +183,45 @@ class DataTableGenericFilter $filterApplied = true; } } + return $filterApplied; } + + public function areProcessedMetricsNeededFor($metrics) + { + $columnQueryParameters = array( + 'filter_column', + 'filter_column_recursive', + 'filter_excludelowpop', + 'filter_sort_column' + ); + + foreach ($columnQueryParameters as $queryParamName) { + $queryParamValue = Common::getRequestVar($queryParamName, false, $type = null, $this->request); + if (!empty($queryParamValue) + && $this->containsProcessedMetric($metrics, $queryParamValue) + ) { + return true; + } + } + + return false; + } + + /** + * @param ProcessedMetric[] $metrics + * @param string $name + * @return bool + */ + private function containsProcessedMetric($metrics, $name) + { + foreach ($metrics as $metric) { + if ($metric instanceof ProcessedMetric + && $metric->getName() == $name + ) { + return true; + } + } + return false; + } } diff --git a/www/analytics/core/API/DataTableManipulator.php b/www/analytics/core/API/DataTableManipulator.php index 5ebdd2fb..d084d6d1 100644 --- a/www/analytics/core/API/DataTableManipulator.php +++ b/www/analytics/core/API/DataTableManipulator.php @@ -1,6 +1,6 @@ manipulateDataTableMap($dataTable); - } else if ($dataTable instanceof DataTable) { + } elseif ($dataTable instanceof DataTable) { return $this->manipulateDataTable($dataTable); } else { return $dataTable; @@ -90,7 +89,7 @@ abstract class DataTableManipulator * Manipulates a single DataTable instance. Derived classes must define * this function. */ - protected abstract function manipulateDataTable($dataTable); + abstract protected function manipulateDataTable($dataTable); /** * Load the subtable for a row. @@ -124,7 +123,7 @@ abstract class DataTableManipulator } } - $method = $this->getApiMethodForSubtable(); + $method = $this->getApiMethodForSubtable($request); return $this->callApiAndReturnDataTable($this->apiModule, $method, $request); } @@ -136,19 +135,34 @@ abstract class DataTableManipulator * @param $request * @return */ - protected abstract function manipulateSubtableRequest($request); + abstract protected function manipulateSubtableRequest($request); /** * Extract the API method for loading subtables from the meta data * + * @throws Exception * @return string */ - private function getApiMethodForSubtable() + private function getApiMethodForSubtable($request) { if (!$this->apiMethodForSubtable) { - $meta = API::getInstance()->getMetadata('all', $this->apiModule, $this->apiMethod); + if (!empty($request['idSite'])) { + $idSite = $request['idSite']; + } else { + $idSite = 'all'; + } - if(empty($meta)) { + $apiParameters = array(); + if (!empty($request['idDimension'])) { + $apiParameters['idDimension'] = $request['idDimension']; + } + if (!empty($request['idGoal'])) { + $apiParameters['idGoal'] = $request['idGoal']; + } + + $meta = API::getInstance()->getMetadata($idSite, $this->apiModule, $this->apiMethod, $apiParameters); + + if (empty($meta)) { throw new Exception(sprintf( "The DataTable cannot be manipulated: Metadata for report %s.%s could not be found. You can define the metadata in a hook, see example at: http://developer.piwik.org/api-reference/events#apigetreportmetadata", $this->apiModule, $this->apiMethod @@ -171,6 +185,8 @@ abstract class DataTableManipulator $request = $this->manipulateSubtableRequest($request); $request['serialize'] = 0; $request['expanded'] = 0; + $request['format'] = 'original'; + $request['format_metrics'] = 0; // don't want to run recursive filters on the subtables as they are loaded, // otherwise the result will be empty in places (or everywhere). instead we @@ -179,14 +195,8 @@ abstract class DataTableManipulator $dataTable = Proxy::getInstance()->call($class, $method, $request); $response = new ResponseBuilder($format = 'original', $request); - $dataTable = $response->getResponse($dataTable); - - if (Common::getRequestVar('disable_queued_filters', 0, 'int', $request) == 0) { - if (method_exists($dataTable, 'applyQueuedFilters')) { - $dataTable->applyQueuedFilters(); - } - } - + $response->disableSendHeader(); + $dataTable = $response->getResponse($dataTable, $apiModule, $method); return $dataTable; } } diff --git a/www/analytics/core/API/DataTableManipulator/Flattener.php b/www/analytics/core/API/DataTableManipulator/Flattener.php index 20fe2c02..a976b7a6 100644 --- a/www/analytics/core/API/DataTableManipulator/Flattener.php +++ b/www/analytics/core/API/DataTableManipulator/Flattener.php @@ -1,6 +1,6 @@ apiModule == 'Actions' || $this->apiMethod == 'getWebsites') { - $this->recursiveLabelSeparator = '/'; - } + $this->recursiveLabelSeparator = $recursiveLabelSeparator; return $this->manipulate($dataTable); } @@ -72,9 +71,10 @@ class Flattener extends DataTableManipulator } $newDataTable = $dataTable->getEmptyClone($keepFilters); - foreach ($dataTable->getRows() as $row) { - $this->flattenRow($row, $newDataTable); + foreach ($dataTable->getRows() as $rowId => $row) { + $this->flattenRow($row, $rowId, $newDataTable); } + return $newDataTable; } @@ -84,15 +84,21 @@ class Flattener extends DataTableManipulator * @param string $labelPrefix * @param bool $parentLogo */ - private function flattenRow(Row $row, DataTable $dataTable, + private function flattenRow(Row $row, $rowId, DataTable $dataTable, $labelPrefix = '', $parentLogo = false) { $label = $row->getColumn('label'); if ($label !== false) { $label = trim($label); - if (substr($label, 0, 1) == '/' && $this->recursiveLabelSeparator == '/') { - $label = substr($label, 1); + + if ($this->recursiveLabelSeparator == '/') { + if (substr($label, 0, 1) == '/') { + $label = substr($label, 1); + } elseif ($rowId === DataTable::ID_SUMMARY_ROW && $labelPrefix && $label != DataTable::LABEL_SUMMARY_ROW) { + $label = ' - ' . $label; + } } + $label = $labelPrefix . $label; $row->setColumn('label', $label); } @@ -103,7 +109,16 @@ class Flattener extends DataTableManipulator $row->setMetadata('logo', $logo); } - $subTable = $this->loadSubtable($dataTable, $row); + /** @var DataTable $subTable */ + $subTable = $row->getSubtable(); + + if ($subTable) { + $subTable->applyQueuedFilters(); + $row->deleteMetadata('idsubdatatable_in_db'); + } else { + $subTable = $this->loadSubtable($dataTable, $row); + } + $row->removeSubtable(); if ($subTable === null) { @@ -117,8 +132,8 @@ class Flattener extends DataTableManipulator $dataTable->addRow($row); } $prefix = $label . $this->recursiveLabelSeparator; - foreach ($subTable->getRows() as $row) { - $this->flattenRow($row, $dataTable, $prefix, $logo); + foreach ($subTable->getRows() as $rowId => $row) { + $this->flattenRow($row, $rowId, $dataTable, $prefix, $logo); } } } @@ -127,6 +142,7 @@ class Flattener extends DataTableManipulator * Remove the flat parameter from the subtable request * * @param array $request + * @return array */ protected function manipulateSubtableRequest($request) { diff --git a/www/analytics/core/API/DataTableManipulator/LabelFilter.php b/www/analytics/core/API/DataTableManipulator/LabelFilter.php index 05c594b3..c691398a 100644 --- a/www/analytics/core/API/DataTableManipulator/LabelFilter.php +++ b/www/analytics/core/API/DataTableManipulator/LabelFilter.php @@ -1,6 +1,6 @@ '; + const TERMINAL_OPERATOR = '@'; private $labels; private $addLabelIndex; @@ -63,6 +64,10 @@ class LabelFilter extends DataTableManipulator */ private function doFilterRecursiveDescend($labelParts, $dataTable) { + // we need to make sure to rebuild the index as some filters change the label column directly via + // $row->setColumn('label', '') which would not be noticed in the label index otherwise. + $dataTable->rebuildIndex(); + // search for the first part of the tree search $labelPart = array_shift($labelParts); @@ -101,6 +106,9 @@ class LabelFilter extends DataTableManipulator protected function manipulateSubtableRequest($request) { unset($request['label']); + unset($request['flat']); + $request['totals'] = 0; + $request['filter_sort_column'] = ''; // do not sort, we only want to find a matching column return $request; } @@ -111,16 +119,22 @@ class LabelFilter extends DataTableManipulator * Note: The HTML Encoded version must be tried first, since in ResponseBuilder the $label is unsanitized * via Common::unsanitizeLabelParameter. * - * @param string $label + * @param string $originalLabel * @return array */ - private function getLabelVariations($label) + private function getLabelVariations($originalLabel) { static $pageTitleReports = array('getPageTitles', 'getEntryPageTitles', 'getExitPageTitles'); + $originalLabel = trim($originalLabel); + + $isTerminal = substr($originalLabel, 0, 1) == self::TERMINAL_OPERATOR; + if ($isTerminal) { + $originalLabel = substr($originalLabel, 1); + } + $variations = array(); - $label = urldecode($label); - $label = trim($label); + $label = trim(urldecode($originalLabel)); $sanitizedLabel = Common::sanitizeInputValue($label); $variations[] = $sanitizedLabel; @@ -128,13 +142,20 @@ class LabelFilter extends DataTableManipulator if ($this->apiModule == 'Actions' && in_array($this->apiMethod, $pageTitleReports) ) { - // special case: the Actions.getPageTitles report prefixes some labels with a blank. - // the blank might be passed by the user but is removed in Request::getRequestArrayFromString. - $variations[] = ' ' . $sanitizedLabel; - $variations[] = ' ' . $label; + if ($isTerminal) { + array_unshift($variations, ' ' . $sanitizedLabel); + array_unshift($variations, ' ' . $label); + } else { + // special case: the Actions.getPageTitles report prefixes some labels with a blank. + // the blank might be passed by the user but is removed in Request::getRequestArrayFromString. + $variations[] = ' ' . $sanitizedLabel; + $variations[] = ' ' . $label; + } } $variations[] = $label; + $variations = array_unique($variations); + return $variations; } diff --git a/www/analytics/core/API/DataTableManipulator/ReportTotalsCalculator.php b/www/analytics/core/API/DataTableManipulator/ReportTotalsCalculator.php index ee289d8b..83fef53a 100644 --- a/www/analytics/core/API/DataTableManipulator/ReportTotalsCalculator.php +++ b/www/analytics/core/API/DataTableManipulator/ReportTotalsCalculator.php @@ -1,6 +1,6 @@ [summed value] * @var array */ - private static $reportMetadata = array(); + private $totals = array(); + + /** + * @var Report + */ + private $report; + + /** + * Constructor + * + * @param bool $apiModule + * @param bool $apiMethod + * @param array $request + * @param Report $report + */ + public function __construct($apiModule = false, $apiMethod = false, $request = array(), $report = null) + { + parent::__construct($apiModule, $apiMethod, $request); + $this->report = $report; + } /** * @param DataTable $table @@ -46,7 +61,7 @@ class ReportTotalsCalculator extends DataTableManipulator try { return $this->manipulate($table); - } catch(\Exception $e) { + } catch (\Exception $e) { // eg. requests with idSubtable may trigger this exception // (where idSubtable was removed in // ?module=API&method=Events.getNameFromCategoryId&idSubtable=1&secondaryDimension=eventName&format=XML&idSite=1&period=day&date=yesterday&flat=0 @@ -62,75 +77,32 @@ class ReportTotalsCalculator extends DataTableManipulator */ protected function manipulateDataTable($dataTable) { - $report = $this->findCurrentReport(); - - if (!empty($report) && empty($report['dimension'])) { + if (!empty($this->report) && !$this->report->getDimension() && !$this->isAllMetricsReport()) { // we currently do not calculate the total value for reports having no dimension return $dataTable; } - // Array [readableMetric] => [summed value] - $totalValues = array(); - + $this->totals = array(); $firstLevelTable = $this->makeSureToWorkOnFirstLevelDataTable($dataTable); $metricsToCalculate = Metrics::getMetricIdsToProcessReportTotal(); + $metricNames = array(); foreach ($metricsToCalculate as $metricId) { - if (!$this->hasDataTableMetric($firstLevelTable, $metricId)) { - continue; - } + $metricNames[$metricId] = Metrics::getReadableColumnName($metricId); + } - foreach ($firstLevelTable->getRows() as $row) { - $totalValues = $this->sumColumnValueToTotal($row, $metricId, $totalValues); + foreach ($firstLevelTable->getRows() as $row) { + $columns = $row->getColumns(); + foreach ($metricNames as $metricId => $metricName) { + $this->sumColumnValueToTotal($columns, $metricId, $metricName); } } - $dataTable->setMetadata('totals', $totalValues); + $dataTable->setMetadata('totals', $this->totals); return $dataTable; } - private function hasDataTableMetric(DataTable $dataTable, $metricId) - { - $firstRow = $dataTable->getFirstRow(); - - if (empty($firstRow)) { - return false; - } - - if (false === $this->getColumn($firstRow, $metricId)) { - return false; - } - - return true; - } - - /** - * Returns column from a given row. - * Will work with 2 types of datatable - * - raw datatables coming from the archive DB, which columns are int indexed - * - datatables processed resulting of API calls, which columns have human readable english names - * - * @param Row|array $row - * @param int $columnIdRaw see consts in Metrics:: - * @return mixed Value of column, false if not found - */ - private function getColumn($row, $columnIdRaw) - { - $columnIdReadable = Metrics::getReadableColumnName($columnIdRaw); - - if ($row instanceof Row) { - $raw = $row->getColumn($columnIdRaw); - if ($raw !== false) { - return $raw; - } - - return $row->getColumn($columnIdReadable); - } - - return false; - } - private function makeSureToWorkOnFirstLevelDataTable($table) { if (!array_key_exists('idSubtable', $this->request)) { @@ -144,8 +116,8 @@ class ReportTotalsCalculator extends DataTableManipulator $module = $this->apiModule; $action = $this->apiMethod; } else { - $module = $firstLevelReport['module']; - $action = $firstLevelReport['action']; + $module = $firstLevelReport->getModule(); + $action = $firstLevelReport->getAction(); } $request = $this->request; @@ -164,33 +136,56 @@ class ReportTotalsCalculator extends DataTableManipulator } } - return $this->callApiAndReturnDataTable($module, $action, $request); + $table = $this->callApiAndReturnDataTable($module, $action, $request); + + if ($table instanceof DataTable\Map) { + $table = $table->mergeChildren(); + } + + return $table; } - private function sumColumnValueToTotal(Row $row, $metricId, $totalValues) + private function sumColumnValueToTotal($columns, $metricId, $metricName) { - $value = $this->getColumn($row, $metricId); - - if (false === $value) { - - return $totalValues; + $value = false; + if (array_key_exists($metricId, $columns)) { + $value = $columns[$metricId]; } - $metricName = Metrics::getReadableColumnName($metricId); + if ($value === false) { + // we do not add $metricId to $possibleMetricNames for a small performance improvement since in most cases + // $metricId should be present in $columns so we avoid this foreach loop + $possibleMetricNames = array( + $metricName, + // TODO: this and below is a hack to get report totals to work correctly w/ MultiSites.getAll. can be corrected + // when all metrics are described by Metadata classes & internal naming quirks are handled by core system. + 'Goal_' . $metricName, + 'Actions_' . $metricName + ); + foreach ($possibleMetricNames as $possibleMetricName) { + if (array_key_exists($possibleMetricName, $columns)) { + $value = $columns[$possibleMetricName]; + break; + } + } - if (array_key_exists($metricName, $totalValues)) { - $totalValues[$metricName] += $value; + if ($value === false) { + return; + } + } + + if (array_key_exists($metricName, $this->totals)) { + $this->totals[$metricName] += $value; } else { - $totalValues[$metricName] = $value; + $this->totals[$metricName] = $value; } - - return $totalValues; } /** * Make sure to get all rows of the first level table. * * @param array $request + * @return array */ protected function manipulateSubtableRequest($request) { @@ -198,6 +193,7 @@ class ReportTotalsCalculator extends DataTableManipulator $request['expanded'] = 0; $request['filter_limit'] = -1; $request['filter_offset'] = 0; + $request['filter_sort_column'] = ''; $parametersToRemove = array('flat'); @@ -213,38 +209,21 @@ class ReportTotalsCalculator extends DataTableManipulator return $request; } - private function getReportMetadata() - { - if (!empty(static::$reportMetadata)) { - return static::$reportMetadata; - } - - static::$reportMetadata = API::getInstance()->getReportMetadata(); - - return static::$reportMetadata; - } - - private function findCurrentReport() - { - foreach ($this->getReportMetadata() as $report) { - if ($this->apiMethod == $report['action'] - && $this->apiModule == $report['module']) { - - return $report; - } - } - } - private function findFirstLevelReport() { - foreach ($this->getReportMetadata() as $report) { - if (!empty($report['actionToLoadSubTables']) - && $this->apiMethod == $report['actionToLoadSubTables'] - && $this->apiModule == $report['module'] + foreach (Report::getAllReports() as $report) { + $actionToLoadSubtables = $report->getActionToLoadSubTables(); + if ($actionToLoadSubtables == $this->apiMethod + && $this->apiModule == $report->getModule() ) { - return $report; } } + return null; + } + + private function isAllMetricsReport() + { + return $this->report->getModule() == 'API' && $this->report->getAction() == 'get'; } } diff --git a/www/analytics/core/API/DataTablePostProcessor.php b/www/analytics/core/API/DataTablePostProcessor.php new file mode 100644 index 00000000..9a673116 --- /dev/null +++ b/www/analytics/core/API/DataTablePostProcessor.php @@ -0,0 +1,436 @@ +apiModule = $apiModule; + $this->apiMethod = $apiMethod; + $this->setRequest($request); + + $this->report = Report::factory($apiModule, $apiMethod); + $this->apiInconsistencies = new Inconsistencies(); + $this->setFormatter(new Formatter()); + } + + public function setFormatter(Formatter $formatter) + { + $this->formatter = $formatter; + } + + public function setRequest($request) + { + $this->request = $request; + } + + public function setCallbackBeforeGenericFilters($callbackBeforeGenericFilters) + { + $this->callbackBeforeGenericFilters = $callbackBeforeGenericFilters; + } + + public function setCallbackAfterGenericFilters($callbackAfterGenericFilters) + { + $this->callbackAfterGenericFilters = $callbackAfterGenericFilters; + } + + /** + * Apply post-processing logic to a DataTable of a report for an API request. + * + * @param DataTableInterface $dataTable The data table to process. + * @return DataTableInterface A new data table. + */ + public function process(DataTableInterface $dataTable) + { + // TODO: when calculating metrics before hand, only calculate for needed metrics, not all. NOTE: + // this is non-trivial since it will require, eg, to make sure processed metrics aren't added + // after pivotBy is handled. + $dataTable = $this->applyPivotByFilter($dataTable); + $dataTable = $this->applyTotalsCalculator($dataTable); + $dataTable = $this->applyFlattener($dataTable); + + if ($this->callbackBeforeGenericFilters) { + call_user_func($this->callbackBeforeGenericFilters, $dataTable); + } + + $dataTable = $this->applyGenericFilters($dataTable); + $this->applyComputeProcessedMetrics($dataTable); + + if ($this->callbackAfterGenericFilters) { + call_user_func($this->callbackAfterGenericFilters, $dataTable); + } + + // we automatically safe decode all datatable labels (against xss) + $dataTable->queueFilter('SafeDecodeLabel'); + $dataTable = $this->convertSegmentValueToSegment($dataTable); + $dataTable = $this->applyQueuedFilters($dataTable); + $dataTable = $this->applyRequestedColumnDeletion($dataTable); + $dataTable = $this->applyLabelFilter($dataTable); + $dataTable = $this->applyMetricsFormatting($dataTable); + return $dataTable; + } + + private function convertSegmentValueToSegment(DataTableInterface $dataTable) + { + $dataTable->filter('AddSegmentBySegmentValue', array($this->report)); + $dataTable->filter('ColumnCallbackDeleteMetadata', array('segmentValue')); + + return $dataTable; + } + + /** + * @param DataTableInterface $dataTable + * @return DataTableInterface + */ + public function applyPivotByFilter(DataTableInterface $dataTable) + { + $pivotBy = Common::getRequestVar('pivotBy', false, 'string', $this->request); + if (!empty($pivotBy)) { + $this->applyComputeProcessedMetrics($dataTable); + + $reportId = $this->apiModule . '.' . $this->apiMethod; + $pivotByColumn = Common::getRequestVar('pivotByColumn', false, 'string', $this->request); + $pivotByColumnLimit = Common::getRequestVar('pivotByColumnLimit', false, 'int', $this->request); + + $dataTable->filter('ColumnCallbackDeleteMetadata', array('segmentValue')); + $dataTable->filter('ColumnCallbackDeleteMetadata', array('segment')); + $dataTable->filter('PivotByDimension', array($reportId, $pivotBy, $pivotByColumn, $pivotByColumnLimit, + PivotByDimension::isSegmentFetchingEnabledInConfig())); + } + return $dataTable; + } + + /** + * @param DataTableInterface $dataTable + * @return DataTable|DataTableInterface|DataTable\Map + */ + public function applyFlattener($dataTable) + { + if (Common::getRequestVar('flat', '0', 'string', $this->request) == '1') { + $flattener = new Flattener($this->apiModule, $this->apiMethod, $this->request); + if (Common::getRequestVar('include_aggregate_rows', '0', 'string', $this->request) == '1') { + $flattener->includeAggregateRows(); + } + + $recursiveLabelSeparator = ' - '; + if ($this->report) { + $recursiveLabelSeparator = $this->report->getRecursiveLabelSeparator(); + } + + $dataTable = $flattener->flatten($dataTable, $recursiveLabelSeparator); + } + return $dataTable; + } + + /** + * @param DataTableInterface $dataTable + * @return DataTableInterface + */ + public function applyTotalsCalculator($dataTable) + { + if (1 == Common::getRequestVar('totals', '1', 'integer', $this->request)) { + $calculator = new ReportTotalsCalculator($this->apiModule, $this->apiMethod, $this->request, $this->report); + $dataTable = $calculator->calculate($dataTable); + } + return $dataTable; + } + + /** + * @param DataTableInterface $dataTable + * @return DataTableInterface + */ + public function applyGenericFilters($dataTable) + { + // if the flag disable_generic_filters is defined we skip the generic filters + if (0 == Common::getRequestVar('disable_generic_filters', '0', 'string', $this->request)) { + $this->applyProcessedMetricsGenericFilters($dataTable); + + $genericFilter = new DataTableGenericFilter($this->request, $this->report); + + $self = $this; + $report = $this->report; + $dataTable->filter(function (DataTable $table) use ($genericFilter, $report, $self) { + $processedMetrics = Report::getProcessedMetricsForTable($table, $report); + if ($genericFilter->areProcessedMetricsNeededFor($processedMetrics)) { + $self->computeProcessedMetrics($table); + } + }); + + $label = self::getLabelFromRequest($this->request); + if (!empty($label)) { + $genericFilter->disableFilters(array('Limit', 'Truncate')); + } + + $genericFilter->filter($dataTable); + } + + return $dataTable; + } + + /** + * @param DataTableInterface $dataTable + * @return DataTableInterface + */ + public function applyProcessedMetricsGenericFilters($dataTable) + { + $addNormalProcessedMetrics = null; + try { + $addNormalProcessedMetrics = Common::getRequestVar( + 'filter_add_columns_when_show_all_columns', null, 'integer', $this->request); + } catch (Exception $ex) { + // ignore + } + + if ($addNormalProcessedMetrics !== null) { + $dataTable->filter('AddColumnsProcessedMetrics', array($addNormalProcessedMetrics)); + } + + $addGoalProcessedMetrics = null; + try { + $addGoalProcessedMetrics = Common::getRequestVar( + 'filter_update_columns_when_show_all_goals', null, 'integer', $this->request); + } catch (Exception $ex) { + // ignore + } + + if ($addGoalProcessedMetrics !== null) { + $idGoal = Common::getRequestVar( + 'idGoal', DataTable\Filter\AddColumnsProcessedMetricsGoal::GOALS_OVERVIEW, 'string', $this->request); + + $dataTable->filter('AddColumnsProcessedMetricsGoal', array($ignore = true, $idGoal)); + } + + return $dataTable; + } + + /** + * @param DataTableInterface $dataTable + * @return DataTableInterface + */ + public function applyQueuedFilters($dataTable) + { + // if the flag disable_queued_filters is defined we skip the filters that were queued + if (Common::getRequestVar('disable_queued_filters', 0, 'int', $this->request) == 0) { + $dataTable->applyQueuedFilters(); + } + return $dataTable; + } + + /** + * @param DataTableInterface $dataTable + * @return DataTableInterface + */ + public function applyRequestedColumnDeletion($dataTable) + { + // use the ColumnDelete filter if hideColumns/showColumns is provided (must be done + // after queued filters are run so processed metrics can be removed, too) + $hideColumns = Common::getRequestVar('hideColumns', '', 'string', $this->request); + $showColumns = Common::getRequestVar('showColumns', '', 'string', $this->request); + $showRawMetrics = Common::getRequestVar('showRawMetrics', 0, 'int', $this->request); + if (!empty($hideColumns) + || !empty($showColumns) + ) { + $dataTable->filter('ColumnDelete', array($hideColumns, $showColumns)); + } else if ($showRawMetrics !== 1) { + $this->removeTemporaryMetrics($dataTable); + } + + return $dataTable; + } + + /** + * @param DataTableInterface $dataTable + */ + public function removeTemporaryMetrics(DataTableInterface $dataTable) + { + $allColumns = !empty($this->report) ? $this->report->getAllMetrics() : array(); + + $report = $this->report; + $dataTable->filter(function (DataTable $table) use ($report, $allColumns) { + $processedMetrics = Report::getProcessedMetricsForTable($table, $report); + + $allTemporaryMetrics = array(); + foreach ($processedMetrics as $metric) { + $allTemporaryMetrics = array_merge($allTemporaryMetrics, $metric->getTemporaryMetrics()); + } + + if (!empty($allTemporaryMetrics)) { + $table->filter('ColumnDelete', array($allTemporaryMetrics)); + } + }); + } + + /** + * @param DataTableInterface $dataTable + * @return DataTableInterface + */ + public function applyLabelFilter($dataTable) + { + $label = self::getLabelFromRequest($this->request); + + // apply label filter: only return rows matching the label parameter (more than one if more than one label) + if (!empty($label)) { + $addLabelIndex = Common::getRequestVar('labelFilterAddLabelIndex', 0, 'int', $this->request) == 1; + + $filter = new LabelFilter($this->apiModule, $this->apiMethod, $this->request); + $dataTable = $filter->filter($label, $dataTable, $addLabelIndex); + } + return $dataTable; + } + + /** + * @param DataTableInterface $dataTable + * @return DataTableInterface + */ + public function applyMetricsFormatting($dataTable) + { + $formatMetrics = Common::getRequestVar('format_metrics', 0, 'string', $this->request); + if ($formatMetrics == '0') { + return $dataTable; + } + + // in Piwik 2.X & below, metrics are not formatted in API responses except for percents. + // this code implements this inconsistency + $onlyFormatPercents = $formatMetrics === 'bc'; + + $metricsToFormat = null; + if ($onlyFormatPercents) { + $metricsToFormat = $this->apiInconsistencies->getPercentMetricsToFormat(); + } + + $dataTable->filter(array($this->formatter, 'formatMetrics'), array($this->report, $metricsToFormat)); + return $dataTable; + } + + /** + * Returns the value for the label query parameter which can be either a string + * (ie, label=...) or array (ie, label[]=...). + * + * @param array $request + * @return array + */ + public static function getLabelFromRequest($request) + { + $label = Common::getRequestVar('label', array(), 'array', $request); + if (empty($label)) { + $label = Common::getRequestVar('label', '', 'string', $request); + if (!empty($label)) { + $label = array($label); + } + } + + $label = self::unsanitizeLabelParameter($label); + return $label; + } + + public static function unsanitizeLabelParameter($label) + { + // this is needed because Proxy uses Common::getRequestVar which in turn + // uses Common::sanitizeInputValue. This causes the > that separates recursive labels + // to become > and we need to undo that here. + $label = str_replace( htmlentities('>'), '>', $label); + return $label; + } + + public function computeProcessedMetrics(DataTable $dataTable) + { + if ($dataTable->getMetadata(self::PROCESSED_METRICS_COMPUTED_FLAG)) { + return; + } + + /** @var ProcessedMetric[] $processedMetrics */ + $processedMetrics = Report::getProcessedMetricsForTable($dataTable, $this->report); + if (empty($processedMetrics)) { + return; + } + + $dataTable->setMetadata(self::PROCESSED_METRICS_COMPUTED_FLAG, true); + + foreach ($processedMetrics as $name => $processedMetric) { + if (!$processedMetric->beforeCompute($this->report, $dataTable)) { + continue; + } + + foreach ($dataTable->getRows() as $row) { + if ($row->getColumn($name) === false) { // only compute the metric if it has not been computed already + $computedValue = $processedMetric->compute($row); + if ($computedValue !== false) { + $row->addColumn($name, $computedValue); + } + + $subtable = $row->getSubtable(); + if (!empty($subtable)) { + $this->computeProcessedMetrics($subtable); + } + } + } + } + } + + public function applyComputeProcessedMetrics(DataTableInterface $dataTable) + { + $dataTable->filter(array($this, 'computeProcessedMetrics')); + } +} diff --git a/www/analytics/core/API/DocumentationGenerator.php b/www/analytics/core/API/DocumentationGenerator.php index ecfece70..60807267 100644 --- a/www/analytics/core/API/DocumentationGenerator.php +++ b/www/analytics/core/API/DocumentationGenerator.php @@ -1,6 +1,6 @@ Common::getRequestVar('idSite', 1, 'int'), - 'period' => Common::getRequestVar('period', 'day', 'string'), - 'date' => Common::getRequestVar('date', 'today', 'string') - ); foreach (Proxy::getInstance()->getMetadata() as $class => $info) { $moduleName = Proxy::getInstance()->getModuleNameFromClassName($class); - if (in_array($moduleName, $this->modulesToHide)) { + $rClass = new ReflectionClass($class); + + if (!Piwik::hasUserSuperUserAccess() && $this->checkIfClassCommentContainsHideAnnotation($rClass)) { continue; } - $toc .= "$moduleName
"; - $str .= "\n

Module " . $moduleName . "

"; - $str .= "
" . $info['__documentation'] . "
"; - foreach ($info as $methodName => $infoMethod) { - if ($methodName == '__documentation') { - continue; - } - $params = $this->getParametersString($class, $methodName); - $str .= "\n
- $moduleName.$methodName " . $params . ""; - $str .= ''; - if ($outputExampleUrls) { - // we prefix all URLs with $prefixUrls - // used when we include this output in the Piwik official documentation for example - $str .= ""; - $exampleUrl = $this->getExampleUrl($class, $methodName, $parametersToSet); - if ($exampleUrl !== false) { - $lastNUrls = ''; - if (preg_match('/(&period)|(&date)/', $exampleUrl)) { - $exampleUrlRss1 = $prefixUrls . $this->getExampleUrl($class, $methodName, array('date' => 'last10', 'period' => 'day') + $parametersToSet); - $exampleUrlRss2 = $prefixUrls . $this->getExampleUrl($class, $methodName, array('date' => 'last5', 'period' => 'week',) + $parametersToSet); - $lastNUrls = ", RSS of the last 10 days"; - } - $exampleUrl = $prefixUrls . $exampleUrl; - $str .= " [ Example in - XML, - Json, - Tsv (Excel) - $lastNUrls - ]"; - } else { - $str .= " [ No example available ]"; - } - $str .= ""; - } - $str .= ''; - $str .= "
\n"; + $toDisplay = $this->prepareModulesAndMethods($info, $moduleName); + foreach ($toDisplay as $moduleName => $methods) { + $toc .= $this->prepareModuleToDisplay($moduleName); + $str .= $this->prepareMethodToDisplay($moduleName, $info, $methods, $class, $outputExampleUrls, $prefixUrls); } - $str .= '
↑ Back to top
'; } $str = "

Quick access to APIs

$toc $str"; + + return $str; + } + + public function prepareModuleToDisplay($moduleName) + { + return "$moduleName
"; + } + + public function prepareMethodToDisplay($moduleName, $info, $methods, $class, $outputExampleUrls, $prefixUrls) + { + $str = ''; + $str .= "\n

Module " . $moduleName . "

"; + $info['__documentation'] = $this->checkDocumentation($info['__documentation']); + $str .= "
" . $info['__documentation'] . "
"; + foreach ($methods as $methodName) { + if (Proxy::getInstance()->isDeprecatedMethod($class, $methodName)) { + continue; + } + + $params = $this->getParametersString($class, $methodName); + + $str .= "\n
- $moduleName.$methodName " . $params . ""; + $str .= ''; + if ($outputExampleUrls) { + $str .= $this->addExamples($class, $methodName, $prefixUrls); + } + $str .= ''; + $str .= "
\n"; + } + + return $str; + } + + public function prepareModulesAndMethods($info, $moduleName) + { + $toDisplay = array(); + + foreach ($info as $methodName => $infoMethod) { + if ($methodName == '__documentation') { + continue; + } + $toDisplay[$moduleName][] = $methodName; + } + + return $toDisplay; + } + + public function addExamples($class, $methodName, $prefixUrls) + { + $token_auth = "&token_auth=" . Piwik::getCurrentUserTokenAuth(); + $parametersToSet = array( + 'idSite' => Common::getRequestVar('idSite', 1, 'int'), + 'period' => Common::getRequestVar('period', 'day', 'string'), + 'date' => Common::getRequestVar('date', 'today', 'string') + ); + $str = ''; +// used when we include this output in the Piwik official documentation for example + $str .= ""; + $exampleUrl = $this->getExampleUrl($class, $methodName, $parametersToSet); + if ($exampleUrl !== false) { + $lastNUrls = ''; + if (preg_match('/(&period)|(&date)/', $exampleUrl)) { + $exampleUrlRss = $prefixUrls . $this->getExampleUrl($class, $methodName, array('date' => 'last10', 'period' => 'day') + $parametersToSet); + $lastNUrls = ", RSS of the last 10 days"; + } + $exampleUrl = $prefixUrls . $exampleUrl; + $str .= " [ Example in + XML, + Json, + Tsv (Excel) + $lastNUrls + ]"; + } else { + $str .= " [ No example available ]"; + } + $str .= ""; + return $str; + } + + /** + * Check if Class contains @hide + * + * @param ReflectionClass $rClass instance of ReflectionMethod + * @return bool + */ + public function checkIfClassCommentContainsHideAnnotation(ReflectionClass $rClass) + { + return false !== strstr($rClass->getDocComment(), '@hide'); + } + + /** + * Check if documentation contains @hide annotation and deletes it + * + * @param $moduleToCheck + * @return mixed + */ + public function checkDocumentation($moduleToCheck) + { + if (strpos($moduleToCheck, '@hide') == true) { + $moduleToCheck = str_replace(strtok(strstr($moduleToCheck, '@hide'), "\n"), "", $moduleToCheck); + } + return $moduleToCheck; + } + + private function getInterfaceString($moduleName, $class, $info, $parametersToSet, $outputExampleUrls, $prefixUrls) + { + $str = ''; + + $str .= "\n

Module " . $moduleName . "

"; + $str .= "
" . $info['__documentation'] . "
"; + foreach ($info as $methodName => $infoMethod) { + if ($methodName == '__documentation') { + continue; + } + + if (Proxy::getInstance()->isDeprecatedMethod($class, $methodName)) { + continue; + } + + $str .= $this->getMethodString($moduleName, $class, $parametersToSet, $outputExampleUrls, $prefixUrls, $methodName, $str); + } + + $str .= '
↑ Back to top
'; + return $str; } @@ -136,6 +225,7 @@ class DocumentationGenerator 'ip' => '194.57.91.215', 'idSites' => '1,2', 'idAlert' => '1', + 'seconds' => '3600', // 'segmentName' => 'browserCode', ); @@ -169,8 +259,8 @@ class DocumentationGenerator $aParameters = Proxy::getInstance()->getParametersList($class, $methodName); // Kindly force some known generic parameters to appear in the final list // the parameter 'format' can be set to all API methods (used in tests) - // the parameter 'hideIdSubDatable' is used for integration tests only - // the parameter 'serialize' sets php outputs human readable, used in integration tests and debug + // the parameter 'hideIdSubDatable' is used for system tests only + // the parameter 'serialize' sets php outputs human readable, used in system tests and debug // the parameter 'language' sets the language for the response (eg. country names) // the parameter 'flat' reduces a hierarchical table to a single level by concatenating labels // the parameter 'include_aggregate_rows' can be set to include inner nodes in flat reports @@ -183,12 +273,25 @@ class DocumentationGenerator $aParameters['label'] = false; $aParameters['flat'] = false; $aParameters['include_aggregate_rows'] = false; - $aParameters['filter_limit'] = false; //@review without adding this, I can not set filter_limit in $otherRequestParameters integration tests - $aParameters['filter_sort_column'] = false; //@review without adding this, I can not set filter_sort_column in $otherRequestParameters integration tests + $aParameters['filter_offset'] = false; //@review without adding this, I can not set filter_offset in $otherRequestParameters system tests + $aParameters['filter_limit'] = false; //@review without adding this, I can not set filter_limit in $otherRequestParameters system tests + $aParameters['filter_sort_column'] = false; //@review without adding this, I can not set filter_sort_column in $otherRequestParameters system tests + $aParameters['filter_sort_order'] = false; //@review without adding this, I can not set filter_sort_order in $otherRequestParameters system tests + $aParameters['filter_excludelowpop'] = false; //@review without adding this, I can not set filter_sort_order in $otherRequestParameters system tests + $aParameters['filter_excludelowpop_value'] = false; //@review without adding this, I can not set filter_sort_order in $otherRequestParameters system tests + $aParameters['filter_column_recursive'] = false; //@review without adding this, I can not set filter_sort_order in $otherRequestParameters system tests + $aParameters['filter_pattern_recursive'] = false; //@review without adding this, I can not set filter_sort_order in $otherRequestParameters system tests $aParameters['filter_truncate'] = false; $aParameters['hideColumns'] = false; $aParameters['showColumns'] = false; $aParameters['filter_pattern_recursive'] = false; + $aParameters['pivotBy'] = false; + $aParameters['pivotByColumn'] = false; + $aParameters['pivotByColumnLimit'] = false; + $aParameters['disable_queued_filters'] = false; + $aParameters['disable_generic_filters'] = false; + $aParameters['expanded'] = false; + $aParameters['idDimenson'] = false; $moduleName = Proxy::getInstance()->getModuleNameFromClassName($class); $aParameters = array_merge(array('module' => 'API', 'method' => $moduleName . '.' . $methodName), $aParameters); @@ -235,4 +338,43 @@ class DocumentationGenerator $sParameters = implode(", ", $asParameters); return "($sParameters)"; } + + private function getMethodString($moduleName, $class, $parametersToSet, $outputExampleUrls, $prefixUrls, $methodName) + { + $str = ''; + $token_auth = "&token_auth=" . Piwik::getCurrentUserTokenAuth(); + + $params = $this->getParametersString($class, $methodName); + $str .= "\n
- $moduleName.$methodName " . $params . ""; + $str .= ''; + + if ($outputExampleUrls) { + // we prefix all URLs with $prefixUrls + // used when we include this output in the Piwik official documentation for example + $str .= ""; + $exampleUrl = $this->getExampleUrl($class, $methodName, $parametersToSet); + if ($exampleUrl !== false) { + $lastNUrls = ''; + if (preg_match('/(&period)|(&date)/', $exampleUrl)) { + $exampleUrlRss = $prefixUrls . $this->getExampleUrl($class, $methodName, array('date' => 'last10', 'period' => 'day') + $parametersToSet); + $lastNUrls = ", RSS of the last 10 days"; + } + $exampleUrl = $prefixUrls . $exampleUrl; + $str .= " [ Example in + XML, + Json, + Tsv (Excel) + $lastNUrls + ]"; + } else { + $str .= " [ No example available ]"; + } + $str .= ""; + } + + $str .= ''; + $str .= "
\n"; + + return $str; + } } diff --git a/www/analytics/core/API/Inconsistencies.php b/www/analytics/core/API/Inconsistencies.php new file mode 100644 index 00000000..36c85bb9 --- /dev/null +++ b/www/analytics/core/API/Inconsistencies.php @@ -0,0 +1,42 @@ +noDefaultValue = new NoDefaultValue(); } @@ -78,12 +75,14 @@ class Proxy extends Singleton $this->checkClassIsSingleton($className); $rClass = new ReflectionClass($className); - foreach ($rClass->getMethods() as $method) { - $this->loadMethodMetadata($className, $method); - } + if (!$this->shouldHideAPIMethod($rClass->getDocComment())) { + foreach ($rClass->getMethods() as $method) { + $this->loadMethodMetadata($className, $method); + } - $this->setDocumentation($rClass, $className); - $this->alreadyRegistered[$className] = true; + $this->setDocumentation($rClass, $className); + $this->alreadyRegistered[$className] = true; + } } /** @@ -164,11 +163,11 @@ class Proxy extends Singleton /** * Triggered before an API request is dispatched. - * + * * This event can be used to modify the arguments passed to one or more API methods. - * + * * **Example** - * + * * Piwik::addAction('API.Request.dispatch', function (&$parameters, $pluginName, $methodName) { * if ($pluginName == 'Actions') { * if ($methodName == 'getPageUrls') { @@ -178,7 +177,7 @@ class Proxy extends Singleton * } * } * }); - * + * * @param array &$finalParameters List of parameters that will be passed to the API method. * @param string $pluginName The name of the plugin the API method belongs to. * @param string $methodName The name of the API method that will be called. @@ -187,20 +186,20 @@ class Proxy extends Singleton /** * Triggered before an API request is dispatched. - * + * * This event exists for convenience and is triggered directly after the {@hook API.Request.dispatch} * event is triggered. It can be used to modify the arguments passed to a **single** API method. - * + * * _Note: This is can be accomplished with the {@hook API.Request.dispatch} event as well, however * event handlers for that event will have to do more work._ - * + * * **Example** - * + * * Piwik::addAction('API.Actions.getPageUrls', function (&$parameters) { * // force use of a single website. for some reason. * $parameters['idSite'] = 1; * }); - * + * * @param array &$finalParameters List of parameters that will be passed to the API method. */ Piwik::postEvent(sprintf('API.%s.%s', $pluginName, $methodName), array(&$finalParameters)); @@ -218,16 +217,16 @@ class Proxy extends Singleton /** * Triggered directly after an API request is dispatched. - * + * * This event exists for convenience and is triggered immediately before the * {@hook API.Request.dispatch.end} event. It can be used to modify the output of a **single** * API method. - * + * * _Note: This can be accomplished with the {@hook API.Request.dispatch.end} event as well, * however event handlers for that event will have to do more work._ * * **Example** - * + * * // append (0 hits) to the end of row labels whose row has 0 hits * Piwik::addAction('API.Actions.getPageUrls', function (&$returnValue, $info)) { * $returnValue->filter('ColumnCallbackReplace', 'label', function ($label, $hits) { @@ -238,13 +237,13 @@ class Proxy extends Singleton * } * }, null, array('nb_hits')); * } - * + * * @param mixed &$returnedValue The API method's return value. Can be an object, such as a * {@link Piwik\DataTable DataTable} instance. * could be a {@link Piwik\DataTable DataTable}. * @param array $extraInfo An array holding information regarding the API request. Will * contain the following data: - * + * * - **className**: The namespace-d class name of the API instance * that's being called. * - **module**: The name of the plugin the API request was @@ -257,20 +256,20 @@ class Proxy extends Singleton /** * Triggered directly after an API request is dispatched. - * + * * This event can be used to modify the output of any API method. - * + * * **Example** - * + * * // append (0 hits) to the end of row labels whose row has 0 hits for any report that has the 'nb_hits' metric - * Piwik::addAction('API.Actions.getPageUrls', function (&$returnValue, $info)) { + * Piwik::addAction('API.Actions.getPageUrls.end', function (&$returnValue, $info)) { * // don't process non-DataTable reports and reports that don't have the nb_hits column * if (!($returnValue instanceof DataTableInterface) * || in_array('nb_hits', $returnValue->getColumns()) * ) { * return; * } - * + * * $returnValue->filter('ColumnCallbackReplace', 'label', function ($label, $hits) { * if ($hits === 0) { * return $label . " (0 hits)"; @@ -279,12 +278,12 @@ class Proxy extends Singleton * } * }, null, array('nb_hits')); * } - * + * * @param mixed &$returnedValue The API method's return value. Can be an object, such as a * {@link Piwik\DataTable DataTable} instance. * @param array $extraInfo An array holding information regarding the API request. Will * contain the following data: - * + * * - **className**: The namespace-d class name of the API instance * that's being called. * - **module**: The name of the plugin the API request was @@ -323,6 +322,14 @@ class Proxy extends Singleton return $this->metadataArray[$class][$name]['parameters']; } + /** + * Check if given method name is deprecated or not. + */ + public function isDeprecatedMethod($class, $methodName) + { + return $this->metadataArray[$class][$methodName]['isDeprecated']; + } + /** * Returns the 'moduleName' part of '\\Piwik\\Plugins\\moduleName\\API' * @@ -378,7 +385,6 @@ class Proxy extends Singleton $requestValue = Common::getRequestVar($name, null, null, $parametersRequest); } else { try { - if ($name == 'segment' && !empty($parametersRequest['segment'])) { // segment parameter is an exception: we do not want to sanitize user input or it would break the segment encoding $requestValue = ($parametersRequest['segment']); @@ -405,7 +411,7 @@ class Proxy extends Singleton } /** - * Includes the class API by looking up plugins/UserSettings/API.php + * Includes the class API by looking up plugins/xxx/API.php * * @param string $fileName api class name eg. "API" * @throws Exception @@ -428,29 +434,27 @@ class Proxy extends Singleton */ private function loadMethodMetadata($class, $method) { - if ($method->isPublic() - && !$method->isConstructor() - && $method->getName() != 'getInstance' - && false === strstr($method->getDocComment(), '@deprecated') - && (!$this->hideIgnoredFunctions || false === strstr($method->getDocComment(), '@ignore')) - ) { - $name = $method->getName(); - $parameters = $method->getParameters(); - - $aParameters = array(); - foreach ($parameters as $parameter) { - $nameVariable = $parameter->getName(); - - $defaultValue = $this->noDefaultValue; - if ($parameter->isDefaultValueAvailable()) { - $defaultValue = $parameter->getDefaultValue(); - } - - $aParameters[$nameVariable] = $defaultValue; - } - $this->metadataArray[$class][$name]['parameters'] = $aParameters; - $this->metadataArray[$class][$name]['numberOfRequiredParameters'] = $method->getNumberOfRequiredParameters(); + if (!$this->checkIfMethodIsAvailable($method)) { + return; } + $name = $method->getName(); + $parameters = $method->getParameters(); + $docComment = $method->getDocComment(); + + $aParameters = array(); + foreach ($parameters as $parameter) { + $nameVariable = $parameter->getName(); + + $defaultValue = $this->noDefaultValue; + if ($parameter->isDefaultValueAvailable()) { + $defaultValue = $parameter->getDefaultValue(); + } + + $aParameters[$nameVariable] = $defaultValue; + } + $this->metadataArray[$class][$name]['parameters'] = $aParameters; + $this->metadataArray[$class][$name]['numberOfRequiredParameters'] = $method->getNumberOfRequiredParameters(); + $this->metadataArray[$class][$name]['isDeprecated'] = false !== strstr($docComment, '@deprecated'); } /** @@ -468,15 +472,56 @@ class Proxy extends Singleton } /** - * Returns the number of required parameters (parameters without default values). - * - * @param string $class The class name - * @param string $name The method name - * @return int The number of required parameters + * @param $docComment + * @return bool */ - private function getNumberOfRequiredParameters($class, $name) + public function shouldHideAPIMethod($docComment) { - return $this->metadataArray[$class][$name]['numberOfRequiredParameters']; + $hideLine = strstr($docComment, '@hide'); + + if ($hideLine === false) { + return false; + } + + $hideLine = trim($hideLine); + $hideLine .= ' '; + + $token = trim(strtok($hideLine, " "), "\n"); + + $hide = false; + + if (!empty($token)) { + /** + * This event exists for checking whether a Plugin API class or a Plugin API method tagged + * with a `@hideXYZ` should be hidden in the API listing. + * + * @param bool &$hide whether to hide APIs tagged with $token should be displayed. + */ + Piwik::postEvent(sprintf('API.DocumentationGenerator.%s', $token), array(&$hide)); + } + + return $hide; + } + + /** + * @param ReflectionMethod $method + * @return bool + */ + protected function checkIfMethodIsAvailable(ReflectionMethod $method) + { + if (!$method->isPublic() || $method->isConstructor() || $method->getName() === 'getInstance') { + return false; + } + + if ($this->hideIgnoredFunctions && false !== strstr($method->getDocComment(), '@ignore')) { + return false; + } + + if ($this->shouldHideAPIMethod($method->getDocComment())) { + return false; + } + + return true; } /** @@ -500,7 +545,7 @@ class Proxy extends Singleton private function checkClassIsSingleton($className) { if (!method_exists($className, "getInstance")) { - throw new Exception("$className that provide an API must be Singleton and have a 'static public function getInstance()' method."); + throw new Exception("$className that provide an API must be Singleton and have a 'public static function getInstance()' method."); } } } diff --git a/www/analytics/core/API/Request.php b/www/analytics/core/API/Request.php index 68cd3fff..6ddb8333 100644 --- a/www/analytics/core/API/Request.php +++ b/www/analytics/core/API/Request.php @@ -1,6 +1,6 @@ process(); * echo $result; - * + * * **Getting a unrendered DataTable** - * + * * // use the convenience method 'processRequest' - * $dataTable = Request::processRequest('UserSettings.getWideScreen', array( + * $dataTable = Request::processRequest('UserLanguage.getLanguage', array( * 'idSite' => 1, * 'date' => 'yesterday', * 'period' => 'week', * 'filter_limit' => 5, * 'filter_offset' => 0 - * + * * 'format' => 'original', // this is the important bit * )); * echo "This DataTable has " . $dataTable->getRowsCount() . " rows."; @@ -69,41 +69,46 @@ use Piwik\UrlHelper; */ class Request { - protected $request = null; + private $request = null; /** * Converts the supplied request string into an array of query paramater name/value * mappings. The current query parameters (everything in `$_GET` and `$_POST`) are * forwarded to request array before it is returned. * - * @param string|array $request The base request string or array, eg, - * `'module=UserSettings&action=getWidescreen'`. + * @param string|array|null $request The base request string or array, eg, + * `'module=UserLanguage&action=getLanguage'`. + * @param array $defaultRequest Default query parameters. If a query parameter is absent in `$request`, it will be loaded + * from this. Defaults to `$_GET + $_POST`. * @return array */ - static public function getRequestArrayFromString($request) + public static function getRequestArrayFromString($request, $defaultRequest = null) { - $defaultRequest = $_GET + $_POST; + if ($defaultRequest === null) { + $defaultRequest = self::getDefaultRequest(); - $requestRaw = self::getRequestParametersGET(); - if (!empty($requestRaw['segment'])) { - $defaultRequest['segment'] = $requestRaw['segment']; + $requestRaw = self::getRequestParametersGET(); + if (!empty($requestRaw['segment'])) { + $defaultRequest['segment'] = $requestRaw['segment']; + } + + if (!isset($defaultRequest['format_metrics'])) { + $defaultRequest['format_metrics'] = 'bc'; + } } $requestArray = $defaultRequest; if (!is_null($request)) { if (is_array($request)) { - $url = array(); - foreach ($request as $key => $value) { - $url[] = $key . "=" . $value; - } - $request = implode("&", $url); + $requestParsed = $request; + } else { + $request = trim($request); + $request = str_replace(array("\n", "\t"), '', $request); + + $requestParsed = UrlHelper::getArrayFromQueryString($request); } - $request = trim($request); - $request = str_replace(array("\n", "\t"), '', $request); - - $requestParsed = UrlHelper::getArrayFromQueryString($request); $requestArray = $requestParsed + $defaultRequest; } @@ -119,14 +124,17 @@ class Request * Constructor. * * @param string|array $request Query string that defines the API call (must at least contain a **method** parameter), - * eg, `'method=UserSettings.getWideScreen&idSite=1&date=yesterday&period=week&format=xml'` + * eg, `'method=UserLanguage.getLanguage&idSite=1&date=yesterday&period=week&format=xml'` * If a request is not provided, then we use the values in the `$_GET` and `$_POST` * superglobals. + * @param array $defaultRequest Default query parameters. If a query parameter is absent in `$request`, it will be loaded + * from this. Defaults to `$_GET + $_POST`. */ - public function __construct($request = null) + public function __construct($request = null, $defaultRequest = null) { - $this->request = self::getRequestArrayFromString($request); + $this->request = self::getRequestArrayFromString($request, $defaultRequest); $this->sanitizeRequest(); + $this->renameModuleAndActionInRequest(); } /** @@ -134,19 +142,23 @@ class Request * we rewrite to correct renamed plugin: Referrers * * @param $module - * @return string + * @param $action + * @return array( $module, $action ) * @ignore */ - public static function renameModule($module) + public static function getRenamedModuleAndAction($module, $action) { - $moduleToRedirect = array( - 'Referers' => 'Referrers', - 'PDFReports' => 'ScheduledReports', - ); - if (isset($moduleToRedirect[$module])) { - return $moduleToRedirect[$module]; - } - return $module; + /** + * This event is posted in the Request dispatcher and can be used + * to overwrite the Module and Action to dispatch. + * This is useful when some Controller methods or API methods have been renamed or moved to another plugin. + * + * @param $module string + * @param $action string + */ + Piwik::postEvent('Request.getRenamedModuleAndAction', array(&$module, &$action)); + + return array($module, $action); } /** @@ -168,9 +180,9 @@ class Request /** * Dispatches the API request to the appropriate API method and returns the result * after post-processing. - * + * * Post-processing includes: - * + * * - flattening if **flat** is 0 * - running generic filters unless **disable_generic_filters** is set to 1 * - URL decoding label column values @@ -178,10 +190,10 @@ class Request * - removing columns based on the values of the **hideColumns** and **showColumns** query parameters * - filtering rows if the **label** query parameter is set * - converting the result to the appropriate format (ie, XML, JSON, etc.) - * + * * If `'original'` is supplied for the output format, the result is returned as a PHP * object. - * + * * @throws PluginDeactivatedException if the module plugin is not activated. * @throws Exception if the requested API method cannot be called, if required parameters for the * API method are missing or if the API method throws an exception and the **format** @@ -196,42 +208,90 @@ class Request // create the response $response = new ResponseBuilder($outputFormat, $this->request); + $corsHandler = new CORSHandler(); + $corsHandler->handle(); + + $tokenAuth = Common::getRequestVar('token_auth', '', 'string', $this->request); + $shouldReloadAuth = false; + try { // read parameters $moduleMethod = Common::getRequestVar('method', null, 'string', $this->request); list($module, $method) = $this->extractModuleAndMethod($moduleMethod); + list($module, $method) = self::getRenamedModuleAndAction($module, $method); + + PluginManager::getInstance()->checkIsPluginActivated($module); - $module = $this->renameModule($module); + $apiClassName = self::getClassNameAPI($module); - if (!\Piwik\Plugin\Manager::getInstance()->isPluginActivated($module)) { - throw new PluginDeactivatedException($module); + if ($shouldReloadAuth = self::shouldReloadAuthUsingTokenAuth($this->request)) { + $access = Access::getInstance(); + $tokenAuthToRestore = $access->getTokenAuth(); + $hadSuperUserAccess = $access->hasSuperUserAccess(); + self::forceReloadAuthUsingTokenAuth($tokenAuth); } - $apiClassName = $this->getClassNameAPI($module); - - self::reloadAuthUsingTokenAuth($this->request); // call the method $returnedValue = Proxy::getInstance()->call($apiClassName, $method, $this->request); $toReturn = $response->getResponse($returnedValue, $module, $method); } catch (Exception $e) { + Log::debug($e); + $toReturn = $response->getResponseException($e); } + + if ($shouldReloadAuth) { + $this->restoreAuthUsingTokenAuth($tokenAuthToRestore, $hadSuperUserAccess); + } + return $toReturn; } + private function restoreAuthUsingTokenAuth($tokenToRestore, $hadSuperUserAccess) + { + // if we would not make sure to unset super user access, the tokenAuth would be not authenticated and any + // token would just keep super user access (eg if the token that was reloaded before had super user access) + Access::getInstance()->setSuperUserAccess(false); + + // we need to restore by reloading the tokenAuth as some permissions could have been removed in the API + // request etc. Otherwise we could just store a clone of Access::getInstance() and restore here + self::forceReloadAuthUsingTokenAuth($tokenToRestore); + + if ($hadSuperUserAccess && !Access::getInstance()->hasSuperUserAccess()) { + // we are in context of `doAsSuperUser()` and need to restore this behaviour + Access::getInstance()->setSuperUserAccess(true); + } + } + /** * Returns the name of a plugin's API class by plugin name. - * + * * @param string $plugin The plugin name, eg, `'Referrers'`. * @return string The fully qualified API class name, eg, `'\Piwik\Plugins\Referrers\API'`. */ - static public function getClassNameAPI($plugin) + public static function getClassNameAPI($plugin) { return sprintf('\Piwik\Plugins\%s\API', $plugin); } + /** + * Detect if request is an API request. Meaning the module is 'API' and an API method having a valid format was + * specified. + * + * @param array $request eg array('module' => 'API', 'method' => 'Test.getMethod') + * @return bool + * @throws Exception + */ + public static function isApiRequest($request) + { + $module = Common::getRequestVar('module', '', 'string', $request); + $method = Common::getRequestVar('method', '', 'string', $request); + + return $module === 'API' && !empty($method) && (count(explode('.', $method)) === 2); + } + /** * If the token_auth is found in the $request parameter, * the current session will be authenticated using this token_auth. @@ -241,28 +301,60 @@ class Request * @return void * @ignore */ - static public function reloadAuthUsingTokenAuth($request = null) + public static function reloadAuthUsingTokenAuth($request = null) { // if a token_auth is specified in the API request, we load the right permissions $token_auth = Common::getRequestVar('token_auth', '', 'string', $request); - if ($token_auth) { - /** - * Triggered when authenticating an API request, but only if the **token_auth** - * query parameter is found in the request. - * - * Plugins that provide authentication capabilities should subscribe to this event - * and make sure the global authentication object (the object returned by `Registry::get('auth')`) - * is setup to use `$token_auth` when its `authenticate()` method is executed. - * - * @param string $token_auth The value of the **token_auth** query parameter. - */ - Piwik::postEvent('API.Request.authenticate', array($token_auth)); - Access::getInstance()->reloadAccess(); - SettingsServer::raiseMemoryLimitIfNecessary(); + if (self::shouldReloadAuthUsingTokenAuth($request)) { + self::forceReloadAuthUsingTokenAuth($token_auth); } } + /** + * The current session will be authenticated using this token_auth. + * It will overwrite the previous Auth object. + * + * @param string $tokenAuth + * @return void + */ + private static function forceReloadAuthUsingTokenAuth($tokenAuth) + { + /** + * Triggered when authenticating an API request, but only if the **token_auth** + * query parameter is found in the request. + * + * Plugins that provide authentication capabilities should subscribe to this event + * and make sure the global authentication object (the object returned by `StaticContainer::get('Piwik\Auth')`) + * is setup to use `$token_auth` when its `authenticate()` method is executed. + * + * @param string $token_auth The value of the **token_auth** query parameter. + */ + Piwik::postEvent('API.Request.authenticate', array($tokenAuth)); + Access::getInstance()->reloadAccess(); + SettingsServer::raiseMemoryLimitIfNecessary(); + } + + private static function shouldReloadAuthUsingTokenAuth($request) + { + if (is_null($request)) { + $request = self::getDefaultRequest(); + } + + if (!isset($request['token_auth'])) { + // no token is given so we just keep the current loaded user + return false; + } + + // a token is specified, we need to reload auth in case it is different than the current one, even if it is empty + $tokenAuth = Common::getRequestVar('token_auth', '', 'string', $request); + + // not using !== is on purpose as getTokenAuth() might return null whereas $tokenAuth is '' . In this case + // we do not need to reload. + + return $tokenAuth != Access::getInstance()->getTokenAuth(); + } + /** * Returns array($class, $method) from the given string $class.$method * @@ -286,18 +378,23 @@ class Request * @param string $method The API method to call, ie, `'Actions.getPageTitles'`. * @param array $paramOverride The parameter name-value pairs to use instead of what's * in `$_GET` & `$_POST`. + * @param array $defaultRequest Default query parameters. If a query parameter is absent in `$request`, it will be loaded + * from this. Defaults to `$_GET + $_POST`. + * + * To avoid using any parameters from $_GET or $_POST, set this to an empty `array()`. * @return mixed The result of the API request. See {@link process()}. */ - public static function processRequest($method, $paramOverride = array()) + public static function processRequest($method, $paramOverride = array(), $defaultRequest = null) { $params = array(); $params['format'] = 'original'; + $params['serialize'] = '0'; $params['module'] = 'API'; $params['method'] = $method; $params = $paramOverride + $params; // process request - $request = new Request($params); + $request = new Request($params, $defaultRequest); return $request->process(); } @@ -305,7 +402,7 @@ class Request * Returns the original request parameters in the current query string as an array mapping * query parameter names with values. The result of this function will not be affected * by any modifications to `$_GET` and will not include parameters in `$_POST`. - * + * * @return array */ public static function getRequestParametersGET() @@ -343,7 +440,7 @@ class Request // unless the filter param was in $queryParams $genericFiltersInfo = DataTableGenericFilter::getGenericFiltersInformation(); foreach ($genericFiltersInfo as $filter) { - foreach ($filter as $queryParamName => $queryParamInfo) { + foreach ($filter[1] as $queryParamName => $queryParamInfo) { if (!isset($params[$queryParamName])) { $params[$queryParamName] = null; } @@ -379,10 +476,10 @@ class Request /** * Returns the segment query parameter from the original request, without modifications. - * + * * @return array|bool */ - static public function getRawSegmentFromRequest() + public static function getRawSegmentFromRequest() { // we need the URL encoded segment parameter, we fetch it from _SERVER['QUERY_STRING'] instead of default URL decoded _GET $segmentRaw = false; @@ -395,4 +492,23 @@ class Request } return $segmentRaw; } + + private function renameModuleAndActionInRequest() + { + if (empty($this->request['apiModule'])) { + return; + } + if (empty($this->request['apiAction'])) { + $this->request['apiAction'] = null; + } + list($this->request['apiModule'], $this->request['apiAction']) = $this->getRenamedModuleAndAction($this->request['apiModule'], $this->request['apiAction']); + } + + /** + * @return array + */ + private static function getDefaultRequest() + { + return $_GET + $_POST; + } } diff --git a/www/analytics/core/API/ResponseBuilder.php b/www/analytics/core/API/ResponseBuilder.php index 692a2ceb..3fe13f96 100644 --- a/www/analytics/core/API/ResponseBuilder.php +++ b/www/analytics/core/API/ResponseBuilder.php @@ -1,6 +1,6 @@ request = $request; $this->outputFormat = $outputFormat; + $this->request = $request; + $this->apiRenderer = ApiRenderer::factory($outputFormat, $request); + } + + public function disableSendHeader() + { + $this->sendHeader = false; + } + + public function disableDataTablePostProcessor() + { + $this->postProcessDataTable = false; } /** @@ -70,61 +82,21 @@ class ResponseBuilder $this->apiModule = $apiModule; $this->apiMethod = $apiMethod; - if($this->outputFormat == 'original') { - @header('Content-Type: text/plain; charset=utf-8'); - } - return $this->renderValue($value); - } + $this->sendHeaderIfEnabled(); - /** - * Returns an error $message in the requested $format - * - * @param Exception $e - * @throws Exception - * @return string - */ - public function getResponseException(Exception $e) - { - $format = strtolower($this->outputFormat); - - if ($format == 'original') { - throw $e; - } - - try { - $renderer = Renderer::factory($format); - } catch (Exception $exceptionRenderer) { - return "Error: " . $e->getMessage() . " and: " . $exceptionRenderer->getMessage(); - } - - $e = $this->decorateExceptionWithDebugTrace($e); - - $renderer->setException($e); - - if ($format == 'php') { - $renderer->setSerialize($this->caseRendererPHPSerialize()); - } - - return $renderer->renderException(); - } - - /** - * @param $value - * @return string - */ - protected function renderValue($value) - { // when null or void is returned from the api call, we handle it as a successful operation if (!isset($value)) { - return $this->handleSuccess(); + if (ob_get_contents()) { + return null; + } + + return $this->apiRenderer->renderSuccess('ok'); } // If the returned value is an object DataTable we // apply the set of generic filters if asked in the URL // and we render the DataTable according to the format specified in the URL - if ($value instanceof DataTable - || $value instanceof DataTable\Map - ) { + if ($value instanceof DataTableInterface) { return $this->handleDataTable($value); } @@ -137,26 +109,39 @@ class ResponseBuilder return $this->handleArray($value); } - // original data structure requested, we return without process - if ($this->outputFormat == 'original') { - return $value; + if (is_object($value)) { + return $this->apiRenderer->renderObject($value); } - if (is_object($value) - || is_resource($value) - ) { - return $this->getResponseException(new Exception('The API cannot handle this data structure.')); + if (is_resource($value)) { + return $this->apiRenderer->renderResource($value); } - // bool // integer // float // serialized object - return $this->handleScalar($value); + return $this->apiRenderer->renderScalar($value); + } + + /** + * Returns an error $message in the requested $format + * + * @param Exception $e + * @throws Exception + * @return string + */ + public function getResponseException(Exception $e) + { + $e = $this->decorateExceptionWithDebugTrace($e); + $message = $this->formatExceptionMessage($e); + + $this->sendHeaderIfEnabled(); + + return $this->apiRenderer->renderException($message, $e); } /** * @param Exception $e * @return Exception */ - protected function decorateExceptionWithDebugTrace(Exception $e) + private function decorateExceptionWithDebugTrace(Exception $e) { // If we are in tests, show full backtrace if (defined('PIWIK_PATH_TEST_TO_ROOT')) { @@ -165,314 +150,109 @@ class ResponseBuilder } else { $message = $e->getMessage() . "\n \n --> To temporarily debug this error further, set const PIWIK_PRINT_ERROR_BACKTRACE=true; in index.php"; } + return new Exception($message); } + return $e; } - /** - * Returns true if the user requested to serialize the output data (&serialize=1 in the request) - * - * @param mixed $defaultSerializeValue Default value in case the user hasn't specified a value - * @return bool - */ - protected function caseRendererPHPSerialize($defaultSerializeValue = 1) + private function formatExceptionMessage(Exception $exception) { - $serialize = Common::getRequestVar('serialize', $defaultSerializeValue, 'int', $this->request); - if ($serialize) { + $message = $exception->getMessage(); + if (\Piwik_ShouldPrintBackTraceWithMessage()) { + $message .= "\n" . $exception->getTraceAsString(); + } + + return Renderer::formatValueXml($message); + } + + private function handleDataTable(DataTableInterface $datatable) + { + if ($this->postProcessDataTable) { + $postProcessor = new DataTablePostProcessor($this->apiModule, $this->apiMethod, $this->request); + $datatable = $postProcessor->process($datatable); + } + + return $this->apiRenderer->renderDataTable($datatable); + } + + private function handleArray($array) + { + $firstArray = null; + $firstKey = null; + if (!empty($array)) { + $firstArray = reset($array); + $firstKey = key($array); + } + + $isAssoc = !empty($firstArray) && is_numeric($firstKey) && is_array($firstArray) && count(array_filter(array_keys($firstArray), 'is_string')); + + if (is_numeric($firstKey)) { + $columns = Common::getRequestVar('filter_column', false, 'array', $this->request); + $pattern = Common::getRequestVar('filter_pattern', '', 'string', $this->request); + + if ($columns != array(false) && $pattern !== '') { + $pattern = new Pattern(new DataTable(), $columns, $pattern); + $array = $pattern->filterArray($array); + } + + $limit = Common::getRequestVar('filter_limit', -1, 'integer', $this->request); + $offset = Common::getRequestVar('filter_offset', '0', 'integer', $this->request); + + if ($this->shouldApplyLimitOnArray($limit, $offset)) { + $array = array_slice($array, $offset, $limit, $preserveKeys = false); + } + } + + if ($isAssoc) { + $hideColumns = Common::getRequestVar('hideColumns', '', 'string', $this->request); + $showColumns = Common::getRequestVar('showColumns', '', 'string', $this->request); + if ($hideColumns !== '' || $showColumns !== '') { + $columnDelete = new ColumnDelete(new DataTable(), $hideColumns, $showColumns); + $array = $columnDelete->filter($array); + } + } + + return $this->apiRenderer->renderArray($array); + } + + private function shouldApplyLimitOnArray($limit, $offset) + { + if ($limit === -1) { + // all fields are requested + return false; + } + + if ($offset > 0) { + // an offset is specified, we have to apply the limit return true; } - return false; + + // "api_datatable_default_limit" is set by API\Controller if no filter_limit is specified by the user. + // it holds the number of the configured default limit. + $limitSetBySystem = Common::getRequestVar('api_datatable_default_limit', -2, 'integer', $this->request); + + // we ignore the limit if the datatable_default_limit was set by the system as this default filter_limit is + // only meant for dataTables but not for arrays. This way we stay BC as filter_limit was not applied pre + // Piwik 2.6 and some fixes were made in Piwik 2.13. + $wasFilterLimitSetBySystem = $limitSetBySystem !== -2; + + // we check for "$limitSetBySystem === $limit" as an API method could request another API method with + // another limit. In this case we need to apply it again. + $isLimitStillDefaultLimit = $limitSetBySystem === $limit; + + if ($wasFilterLimitSetBySystem && $isLimitStillDefaultLimit) { + return false; + } + + return true; } - /** - * Apply the specified renderer to the DataTable - * - * @param DataTable|array $dataTable - * @return string - */ - protected function getRenderedDataTable($dataTable) + private function sendHeaderIfEnabled() { - $format = strtolower($this->outputFormat); - - // if asked for original dataStructure - if ($format == 'original') { - // by default "original" data is not serialized - if ($this->caseRendererPHPSerialize($defaultSerialize = 0)) { - $dataTable = serialize($dataTable); - } - return $dataTable; + if ($this->sendHeader) { + $this->apiRenderer->sendHeader(); } - - $method = Common::getRequestVar('method', '', 'string', $this->request); - - $renderer = Renderer::factory($format); - $renderer->setTable($dataTable); - $renderer->setRenderSubTables(Common::getRequestVar('expanded', false, 'int', $this->request)); - $renderer->setHideIdSubDatableFromResponse(Common::getRequestVar('hideIdSubDatable', false, 'int', $this->request)); - - if ($format == 'php') { - $renderer->setSerialize($this->caseRendererPHPSerialize()); - $renderer->setPrettyDisplay(Common::getRequestVar('prettyDisplay', false, 'int', $this->request)); - } else if ($format == 'html') { - $renderer->setTableId($this->request['method']); - } else if ($format == 'csv' || $format == 'tsv') { - $renderer->setConvertToUnicode(Common::getRequestVar('convertToUnicode', true, 'int', $this->request)); - } - - // prepare translation of column names - if ($format == 'html' || $format == 'csv' || $format == 'tsv' || $format = 'rss') { - $renderer->setApiMethod($method); - $renderer->setIdSite(Common::getRequestVar('idSite', false, 'int', $this->request)); - $renderer->setTranslateColumnNames(Common::getRequestVar('translateColumnNames', false, 'int', $this->request)); - } - - return $renderer->render(); - } - - /** - * Returns a success $message in the requested $format - * - * @param string $message - * @return string - */ - protected function handleSuccess($message = 'ok') - { - // return a success message only if no content has already been buffered, useful when APIs return raw text or html content to the browser - if (!ob_get_contents()) { - switch ($this->outputFormat) { - case 'xml': - @header("Content-Type: text/xml;charset=utf-8"); - $return = - "\n" . - "\n" . - "\t\n" . - ""; - break; - case 'json': - @header("Content-Type: application/json"); - $return = '{"result":"success", "message":"' . $message . '"}'; - break; - case 'php': - $return = array('result' => 'success', 'message' => $message); - if ($this->caseRendererPHPSerialize()) { - $return = serialize($return); - } - break; - - case 'csv': - @header("Content-Type: application/vnd.ms-excel"); - @header("Content-Disposition: attachment; filename=piwik-report-export.csv"); - $return = "message\n" . $message; - break; - - default: - $return = 'Success:' . $message; - break; - } - return $return; - } - } - - /** - * Converts the given scalar to an data table - * - * @param mixed $scalar - * @return string - */ - protected function handleScalar($scalar) - { - $dataTable = new Simple(); - $dataTable->addRowsFromArray(array($scalar)); - return $this->getRenderedDataTable($dataTable); - } - - /** - * Handles the given data table - * - * @param DataTable $datatable - * @return string - */ - protected function handleDataTable($datatable) - { - // if requested, flatten nested tables - if (Common::getRequestVar('flat', '0', 'string', $this->request) == '1') { - $flattener = new Flattener($this->apiModule, $this->apiMethod, $this->request); - if (Common::getRequestVar('include_aggregate_rows', '0', 'string', $this->request) == '1') { - $flattener->includeAggregateRows(); - } - $datatable = $flattener->flatten($datatable); - } - - if (1 == Common::getRequestVar('totals', '1', 'integer', $this->request)) { - $genericFilter = new ReportTotalsCalculator($this->apiModule, $this->apiMethod, $this->request); - $datatable = $genericFilter->calculate($datatable); - } - - // if the flag disable_generic_filters is defined we skip the generic filters - if (0 == Common::getRequestVar('disable_generic_filters', '0', 'string', $this->request)) { - $genericFilter = new DataTableGenericFilter($this->request); - $genericFilter->filter($datatable); - } - - // we automatically safe decode all datatable labels (against xss) - $datatable->queueFilter('SafeDecodeLabel'); - - // if the flag disable_queued_filters is defined we skip the filters that were queued - if (Common::getRequestVar('disable_queued_filters', 0, 'int', $this->request) == 0) { - $datatable->applyQueuedFilters(); - } - - // use the ColumnDelete filter if hideColumns/showColumns is provided (must be done - // after queued filters are run so processed metrics can be removed, too) - $hideColumns = Common::getRequestVar('hideColumns', '', 'string', $this->request); - $showColumns = Common::getRequestVar('showColumns', '', 'string', $this->request); - if ($hideColumns !== '' || $showColumns !== '') { - $datatable->filter('ColumnDelete', array($hideColumns, $showColumns)); - } - - // apply label filter: only return rows matching the label parameter (more than one if more than one label) - $label = $this->getLabelFromRequest($this->request); - if (!empty($label)) { - $addLabelIndex = Common::getRequestVar('labelFilterAddLabelIndex', 0, 'int', $this->request) == 1; - - $filter = new LabelFilter($this->apiModule, $this->apiMethod, $this->request); - $datatable = $filter->filter($label, $datatable, $addLabelIndex); - } - return $this->getRenderedDataTable($datatable); - } - - /** - * Converts the given simple array to a data table - * - * @param array $array - * @return string - */ - protected function handleArray($array) - { - if ($this->outputFormat == 'original') { - // we handle the serialization. Because some php array have a very special structure that - // couldn't be converted with the automatic DataTable->addRowsFromSimpleArray - // the user may want to request the original PHP data structure serialized by the API - // in case he has to setup serialize=1 in the URL - if ($this->caseRendererPHPSerialize($defaultSerialize = 0)) { - return serialize($array); - } - return $array; - } - - $multiDimensional = $this->handleMultiDimensionalArray($array); - if ($multiDimensional !== false) { - return $multiDimensional; - } - - return $this->getRenderedDataTable($array); - } - - /** - * Is this a multi dimensional array? - * Multi dim arrays are not supported by the Datatable renderer. - * We manually render these. - * - * array( - * array( - * 1, - * 2 => array( 1, - * 2 - * ) - * ), - * array( 2, - * 3 - * ) - * ); - * - * @param array $array - * @return string|bool false if it isn't a multidim array - */ - protected function handleMultiDimensionalArray($array) - { - $first = reset($array); - foreach ($array as $first) { - if (is_array($first)) { - foreach ($first as $key => $value) { - // Yes, this is a multi dim array - if (is_array($value)) { - switch ($this->outputFormat) { - case 'json': - @header("Content-Type: application/json"); - return self::convertMultiDimensionalArrayToJson($array); - break; - - case 'php': - if ($this->caseRendererPHPSerialize($defaultSerialize = 0)) { - return serialize($array); - } - return $array; - - case 'xml': - @header("Content-Type: text/xml;charset=utf-8"); - return $this->getRenderedDataTable($array); - default: - break; - } - } - } - } - } - return false; - } - - /** - * Render a multidimensional array to Json - * Handle DataTable|Set elements in the first dimension only, following case does not work: - * array( - * array( - * DataTable, - * 2 => array( - * 1, - * 2 - * ), - * ), - * ); - * - * @param array $array can contain scalar, arrays, DataTable and Set - * @return string - */ - public static function convertMultiDimensionalArrayToJson($array) - { - $jsonRenderer = new Json(); - $jsonRenderer->setTable($array); - $renderedReport = $jsonRenderer->render(); - return $renderedReport; - } - - /** - * Returns the value for the label query parameter which can be either a string - * (ie, label=...) or array (ie, label[]=...). - * - * @param array $request - * @return array - */ - static public function getLabelFromRequest($request) - { - $label = Common::getRequestVar('label', array(), 'array', $request); - if (empty($label)) { - $label = Common::getRequestVar('label', '', 'string', $request); - if (!empty($label)) { - $label = array($label); - } - } - - $label = self::unsanitizeLabelParameter($label); - return $label; - } - - static public function unsanitizeLabelParameter($label) - { - // this is needed because Proxy uses Common::getRequestVar which in turn - // uses Common::sanitizeInputValue. This causes the > that separates recursive labels - // to become > and we need to undo that here. - $label = Common::unsanitizeInputValues($label); - return $label; } } diff --git a/www/analytics/core/Access.php b/www/analytics/core/Access.php index 71a5dd5f..881810bf 100644 --- a/www/analytics/core/Access.php +++ b/www/analytics/core/Access.php @@ -1,6 +1,6 @@ resetSites(); + } + + private function resetSites() { $this->idsitesByAccess = array( 'view' => array(), @@ -138,15 +130,22 @@ class Access */ public function reloadAccess(Auth $auth = null) { - if (!is_null($auth)) { + $this->resetSites(); + + if (isset($auth)) { $this->auth = $auth; } - // if the Auth wasn't set, we may be in the special case of setSuperUser(), otherwise we fail - if (is_null($this->auth)) { - if ($this->hasSuperUserAccess()) { - return $this->reloadAccessSuperUser(); - } + if ($this->hasSuperUserAccess()) { + $this->makeSureLoginNameIsSet(); + return true; + } + + $this->token_auth = null; + $this->login = null; + + // if the Auth wasn't set, we may be in the special case of setSuperUser(), otherwise we fail TODO: docs + review + if ($this->auth === null) { return false; } @@ -156,28 +155,23 @@ class Access if (!$result->wasAuthenticationSuccessful()) { return false; } + $this->login = $result->getIdentity(); $this->token_auth = $result->getTokenAuth(); // case the superUser is logged in if ($result->hasSuperUserAccess()) { - return $this->reloadAccessSuperUser(); + $this->setSuperUserAccess(true); } - // in case multiple calls to API using different tokens, we ensure we reset it as not SU - $this->setSuperUserAccess(false); - // we join with site in case there are rows in access for an idsite that doesn't exist anymore - // (backward compatibility ; before we deleted the site without deleting rows in _access table) - $accessRaw = $this->getRawSitesWithSomeViewAccess($this->login); - foreach ($accessRaw as $access) { - $this->idsitesByAccess[$access['access']][] = $access['idsite']; - } return true; } public function getRawSitesWithSomeViewAccess($login) { - return Db::fetchAll(self::getSqlAccessSite("access, t2.idsite"), $login); + $sql = self::getSqlAccessSite("access, t2.idsite"); + + return Db::fetchAll($sql, $login); } /** @@ -188,29 +182,50 @@ class Access */ public static function getSqlAccessSite($select) { - return "SELECT " . $select . " - FROM " . Common::prefixTable('access') . " as t1 - JOIN " . Common::prefixTable('site') . " as t2 USING (idsite) " . - " WHERE login = ?"; + $access = Common::prefixTable('access'); + $siteTable = Common::prefixTable('site'); + + return "SELECT " . $select . " FROM " . $access . " as t1 + JOIN " . $siteTable . " as t2 USING (idsite) WHERE login = ?"; } /** - * Reload Super User access + * Make sure a login name is set * - * @return bool + * @return true */ - protected function reloadAccessSuperUser() + protected function makeSureLoginNameIsSet() { - $this->hasSuperUserAccess = true; - - try { - $allSitesId = Plugins\SitesManager\API::getInstance()->getAllSitesId(); - } catch (\Exception $e) { - $allSitesId = array(); + if (empty($this->login)) { + // flag to force non empty login so Super User is not mistaken for anonymous + $this->login = 'super user was set'; } - $this->idsitesByAccess['superuser'] = $allSitesId; + } - return true; + protected function loadSitesIfNeeded() + { + if ($this->hasSuperUserAccess) { + if (empty($this->idsitesByAccess['superuser'])) { + try { + $allSitesId = Plugins\SitesManager\API::getInstance()->getAllSitesId(); + } catch (\Exception $e) { + $allSitesId = array(); + } + $this->idsitesByAccess['superuser'] = $allSitesId; + } + } elseif (isset($this->login)) { + if (empty($this->idsitesByAccess['view']) + && empty($this->idsitesByAccess['admin'])) { + + // we join with site in case there are rows in access for an idsite that doesn't exist anymore + // (backward compatibility ; before we deleted the site without deleting rows in _access table) + $accessRaw = $this->getRawSitesWithSomeViewAccess($this->login); + + foreach ($accessRaw as $access) { + $this->idsitesByAccess[$access['access']][] = $access['idsite']; + } + } + } } /** @@ -221,12 +236,12 @@ class Access */ public function setSuperUserAccess($bool = true) { - if ($bool) { - $this->reloadAccessSuperUser(); - } else { - $this->hasSuperUserAccess = false; - $this->idsitesByAccess['superuser'] = array(); + $this->hasSuperUserAccess = (bool) $bool; + if ($bool) { + $this->makeSureLoginNameIsSet(); + } else { + $this->resetSites(); } } @@ -269,6 +284,8 @@ class Access */ public function getSitesIdWithAtLeastViewAccess() { + $this->loadSitesIfNeeded(); + return array_unique(array_merge( $this->idsitesByAccess['view'], $this->idsitesByAccess['admin'], @@ -284,13 +301,14 @@ class Access */ public function getSitesIdWithAdminAccess() { + $this->loadSitesIfNeeded(); + return array_unique(array_merge( $this->idsitesByAccess['admin'], $this->idsitesByAccess['superuser']) ); } - /** * Returns an array of ID sites for which the user has a VIEW access only. * @@ -300,6 +318,8 @@ class Access */ public function getSitesIdWithViewAccess() { + $this->loadSitesIfNeeded(); + return $this->idsitesByAccess['view']; } @@ -315,6 +335,22 @@ class Access } } + /** + * Returns `true` if the current user has admin access to at least one site. + * + * @return bool + */ + public function isUserHasSomeAdminAccess() + { + if ($this->hasSuperUserAccess()) { + return true; + } + + $idSitesAccessible = $this->getSitesIdWithAdminAccess(); + + return count($idSitesAccessible) > 0; + } + /** * If the user doesn't have an ADMIN access for at least one website, throws an exception * @@ -322,11 +358,7 @@ class Access */ public function checkUserHasSomeAdminAccess() { - if ($this->hasSuperUserAccess()) { - return; - } - $idSitesAccessible = $this->getSitesIdWithAdminAccess(); - if (count($idSitesAccessible) == 0) { + if (!$this->isUserHasSomeAdminAccess()) { throw new NoAccessException(Piwik::translate('General_ExceptionPrivilegeAtLeastOneWebsite', array('admin'))); } } @@ -341,7 +373,9 @@ class Access if ($this->hasSuperUserAccess()) { return; } + $idSitesAccessible = $this->getSitesIdWithAtLeastViewAccess(); + if (count($idSitesAccessible) == 0) { throw new NoAccessException(Piwik::translate('General_ExceptionPrivilegeAtLeastOneWebsite', array('view'))); } @@ -359,8 +393,10 @@ class Access if ($this->hasSuperUserAccess()) { return; } + $idSites = $this->getIdSites($idSites); $idSitesAccessible = $this->getSitesIdWithAdminAccess(); + foreach ($idSites as $idsite) { if (!in_array($idsite, $idSitesAccessible)) { throw new NoAccessException(Piwik::translate('General_ExceptionPrivilegeAccessWebsite', array("'admin'", $idsite))); @@ -380,8 +416,10 @@ class Access if ($this->hasSuperUserAccess()) { return; } + $idSites = $this->getIdSites($idSites); $idSitesAccessible = $this->getSitesIdWithAtLeastViewAccess(); + foreach ($idSites as $idsite) { if (!in_array($idsite, $idSitesAccessible)) { throw new NoAccessException(Piwik::translate('General_ExceptionPrivilegeAccessWebsite', array("'view'", $idsite))); @@ -401,11 +439,41 @@ class Access } $idSites = Site::getIdSitesFromIdSitesString($idSites); + if (empty($idSites)) { throw new NoAccessException("The parameter 'idSite=' is missing from the request."); } + return $idSites; } + + /** + * Executes a callback with superuser privileges, making sure those privileges are rescinded + * before this method exits. Privileges will be rescinded even if an exception is thrown. + * + * @param callback $function The callback to execute. Should accept no arguments. + * @return mixed The result of `$function`. + * @throws Exception rethrows any exceptions thrown by `$function`. + * @api + */ + public static function doAsSuperUser($function) + { + $isSuperUser = self::getInstance()->hasSuperUserAccess(); + + self::getInstance()->setSuperUserAccess(true); + + try { + $result = $function(); + } catch (Exception $ex) { + self::getInstance()->setSuperUserAccess($isSuperUser); + + throw $ex; + } + + self::getInstance()->setSuperUserAccess($isSuperUser); + + return $result; + } } /** diff --git a/www/analytics/core/Application/Environment.php b/www/analytics/core/Application/Environment.php new file mode 100644 index 00000000..74388358 --- /dev/null +++ b/www/analytics/core/Application/Environment.php @@ -0,0 +1,246 @@ +environment = $environment; + $this->definitions = $definitions; + } + + /** + * Initializes the kernel globals and DI container. + */ + public function init() + { + $this->invokeBeforeContainerCreatedHook(); + + $this->container = $this->createContainer(); + + StaticContainer::push($this->container); + + $this->validateEnvironment(); + + $this->invokeEnvironmentBootstrappedHook(); + + Piwik::postEvent('Environment.bootstrapped'); // this event should be removed eventually + } + + /** + * Destroys an environment. MUST be called when embedding environments. + */ + public function destroy() + { + StaticContainer::pop(); + } + + /** + * Returns the DI container. All Piwik objects for a specific Piwik instance should be stored + * in this container. + * + * @return Container + */ + public function getContainer() + { + return $this->container; + } + + /** + * @link http://php-di.org/doc/container-configuration.html + */ + private function createContainer() + { + $pluginList = $this->getPluginListCached(); + $settings = $this->getGlobalSettingsCached(); + + $extraDefinitions = $this->getExtraDefinitionsFromManipulators(); + $definitions = array_merge(StaticContainer::getDefinitions(), $extraDefinitions, array($this->definitions)); + + $environments = array($this->environment); + $environments = array_merge($environments, $this->getExtraEnvironmentsFromManipulators()); + + $containerFactory = new ContainerFactory($pluginList, $settings, $environments, $definitions); + return $containerFactory->create(); + } + + protected function getGlobalSettingsCached() + { + if ($this->globalSettingsProvider === null) { + $original = $this->getGlobalSettings(); + $globalSettingsProvider = $this->getGlobalSettingsProviderOverride($original); + + $this->globalSettingsProvider = $globalSettingsProvider ?: $original; + } + return $this->globalSettingsProvider; + } + + protected function getPluginListCached() + { + if ($this->pluginList === null) { + $pluginList = $this->getPluginListOverride(); + $this->pluginList = $pluginList ?: $this->getPluginList(); + } + return $this->pluginList; + } + + /** + * Returns the kernel global GlobalSettingsProvider object. Derived classes can override this method + * to provide a different implementation. + * + * @return null|GlobalSettingsProvider + */ + protected function getGlobalSettings() + { + return new GlobalSettingsProvider(); + } + + /** + * Returns the kernel global PluginList object. Derived classes can override this method to + * provide a different implementation. + * + * @return PluginList + */ + protected function getPluginList() + { + // TODO: in tracker should only load tracker plugins. can't do properly until tracker entrypoint is encapsulated. + return new PluginList($this->getGlobalSettingsCached()); + } + + private function validateEnvironment() + { + /** @var EnvironmentValidator $validator */ + $validator = $this->container->get('Piwik\Application\Kernel\EnvironmentValidator'); + $validator->validate(); + } + + /** + * @param EnvironmentManipulator $manipulator + * @internal + */ + public static function setGlobalEnvironmentManipulator(EnvironmentManipulator $manipulator) + { + self::$globalEnvironmentManipulator = $manipulator; + } + + private function getGlobalSettingsProviderOverride(GlobalSettingsProvider $original) + { + if (self::$globalEnvironmentManipulator) { + return self::$globalEnvironmentManipulator->makeGlobalSettingsProvider($original); + } else { + return null; + } + } + + private function invokeBeforeContainerCreatedHook() + { + if (self::$globalEnvironmentManipulator) { + return self::$globalEnvironmentManipulator->beforeContainerCreated(); + } + } + + private function getExtraDefinitionsFromManipulators() + { + if (self::$globalEnvironmentManipulator) { + return self::$globalEnvironmentManipulator->getExtraDefinitions(); + } else { + return array(); + } + } + + private function invokeEnvironmentBootstrappedHook() + { + if (self::$globalEnvironmentManipulator) { + self::$globalEnvironmentManipulator->onEnvironmentBootstrapped(); + } + } + + private function getExtraEnvironmentsFromManipulators() + { + if (self::$globalEnvironmentManipulator) { + return self::$globalEnvironmentManipulator->getExtraEnvironments(); + } else { + return array(); + } + } + + private function getPluginListOverride() + { + if (self::$globalEnvironmentManipulator) { + return self::$globalEnvironmentManipulator->makePluginList($this->getGlobalSettingsCached()); + } else { + return null; + } + } +} diff --git a/www/analytics/core/Application/EnvironmentManipulator.php b/www/analytics/core/Application/EnvironmentManipulator.php new file mode 100644 index 00000000..15be5ac0 --- /dev/null +++ b/www/analytics/core/Application/EnvironmentManipulator.php @@ -0,0 +1,59 @@ +settingsProvider = $settingsProvider; + $this->translator = $translator; + } + + public function validate() + { + $inTrackerRequest = SettingsServer::isTrackerApiRequest(); + $inConsole = Common::isPhpCliMode(); + + $this->checkConfigFileExists($this->settingsProvider->getPathGlobal()); + $this->checkConfigFileExists($this->settingsProvider->getPathLocal(), $startInstaller = !$inTrackerRequest && !$inConsole); + } + + /** + * @param $path + * @param bool $startInstaller + * @throws \Exception + */ + private function checkConfigFileExists($path, $startInstaller = false) + { + if (is_readable($path)) { + return; + } + + $message = $this->translator->translate('General_ExceptionConfigurationFileNotFound', array($path)); + if (Common::isPhpCliMode()) { + $message .= "\n" . $this->translator->translate('General_ExceptionConfigurationFileNotFound2', array($path, get_current_user())); + } + + $exception = new \Exception($message); + + if ($startInstaller) { + /** + * Triggered when the configuration file cannot be found or read, which usually + * means Piwik is not installed yet. + * + * This event can be used to start the installation process or to display a custom error message. + * + * @param \Exception $exception The exception that was thrown by `Config::getInstance()`. + */ + Piwik::postEvent('Config.NoConfigurationFile', array($exception), $pending = true); + } else { + throw $exception; + } + } +} diff --git a/www/analytics/core/Application/Kernel/GlobalSettingsProvider.php b/www/analytics/core/Application/Kernel/GlobalSettingsProvider.php new file mode 100644 index 00000000..f459e793 --- /dev/null +++ b/www/analytics/core/Application/Kernel/GlobalSettingsProvider.php @@ -0,0 +1,111 @@ + value pairs. Setting values can + * be primitive values or arrays of primitive values. + * + * Uses the config.ini.php, common.ini.php and global.ini.php files to provide global settings. + * + * At the moment a singleton instance of this class is used in order to get tests to pass. + */ +class GlobalSettingsProvider +{ + /** + * @var IniFileChain + */ + protected $iniFileChain; + + /** + * @var string + */ + protected $pathGlobal = null; + + /** + * @var string + */ + protected $pathCommon = null; + + /** + * @var string + */ + protected $pathLocal = null; + + /** + * @param string|null $pathGlobal Path to the global.ini.php file. Or null to use the default. + * @param string|null $pathLocal Path to the config.ini.php file. Or null to use the default. + * @param string|null $pathCommon Path to the common.ini.php file. Or null to use the default. + */ + public function __construct($pathGlobal = null, $pathLocal = null, $pathCommon = null) + { + $this->pathGlobal = $pathGlobal ?: Config::getGlobalConfigPath(); + $this->pathCommon = $pathCommon ?: Config::getCommonConfigPath(); + $this->pathLocal = $pathLocal ?: Config::getLocalConfigPath(); + + $this->iniFileChain = new IniFileChain(); + $this->reload(); + } + + public function reload($pathGlobal = null, $pathLocal = null, $pathCommon = null) + { + $this->pathGlobal = $pathGlobal ?: $this->pathGlobal; + $this->pathCommon = $pathCommon ?: $this->pathCommon; + $this->pathLocal = $pathLocal ?: $this->pathLocal; + + $this->iniFileChain->reload(array($this->pathGlobal, $this->pathCommon), $this->pathLocal); + } + + /** + * Returns a settings section. + * + * @param string $name + * @return array + */ + public function &getSection($name) + { + $section =& $this->iniFileChain->get($name); + return $section; + } + + /** + * Sets a settings section. + * + * @param string $name + * @param array $value + */ + public function setSection($name, $value) + { + $this->iniFileChain->set($name, $value); + } + + public function getIniFileChain() + { + return $this->iniFileChain; + } + + public function getPathGlobal() + { + return $this->pathGlobal; + } + + public function getPathLocal() + { + return $this->pathLocal; + } + + public function getPathCommon() + { + return $this->pathCommon; + } +} diff --git a/www/analytics/core/Application/Kernel/PluginList.php b/www/analytics/core/Application/Kernel/PluginList.php new file mode 100644 index 00000000..5d263a6e --- /dev/null +++ b/www/analytics/core/Application/Kernel/PluginList.php @@ -0,0 +1,120 @@ +settings = $settings; + } + + /** + * Returns the list of plugins that should be loaded. Used by the container factory to + * load plugin specific DI overrides. + * + * @return string[] + */ + public function getActivatedPlugins() + { + $section = $this->settings->getSection('Plugins'); + return @$section['Plugins'] ?: array(); + } + + /** + * Returns the list of plugins that are bundled with Piwik. + * + * @return string[] + */ + public function getPluginsBundledWithPiwik() + { + $pathGlobal = $this->settings->getPathGlobal(); + + $section = $this->settings->getIniFileChain()->getFrom($pathGlobal, 'Plugins'); + return $section['Plugins']; + } + + /** + * Returns the plugins bundled with core package that are disabled by default. + * + * @return string[] + */ + public function getCorePluginsDisabledByDefault() + { + return array_merge($this->corePluginsDisabledByDefault, $this->coreThemesDisabledByDefault); + } + + /** + * Sorts an array of plugins in the order they should be loaded. + * + * @params string[] $plugins + * @return \string[] + */ + public function sortPlugins(array $plugins) + { + $global = $this->getPluginsBundledWithPiwik(); + if (empty($global)) { + return $plugins; + } + + // we need to make sure a possibly disabled plugin will be still loaded before any 3rd party plugin + $global = array_merge($global, $this->corePluginsDisabledByDefault); + + $global = array_values($global); + $plugins = array_values($plugins); + + $defaultPluginsLoadedFirst = array_intersect($global, $plugins); + + $otherPluginsToLoadAfterDefaultPlugins = array_diff($plugins, $defaultPluginsLoadedFirst); + + // sort by name to have a predictable order for those extra plugins + sort($otherPluginsToLoadAfterDefaultPlugins); + + $sorted = array_merge($defaultPluginsLoadedFirst, $otherPluginsToLoadAfterDefaultPlugins); + + return $sorted; + } +} diff --git a/www/analytics/core/Archive.php b/www/analytics/core/Archive.php index e5884aea..2efe2303 100644 --- a/www/analytics/core/Archive.php +++ b/www/analytics/core/Archive.php @@ -1,6 +1,6 @@ getDataTableFromNumeric(array('nb_visits', 'nb_actions')); - * + * * // all sites and multiple dates * $archive = Archive::build($idSite = 'all', $period = 'month', $date = '2013-01-02,2013-03-08'); * return $archive->getDataTableFromNumeric(array('nb_visits', 'nb_actions')); - * + * * **_Querying and using metrics immediately_** - * + * * // one site and one period * $archive = Archive::build($idSite = 1, $period = 'week', $date = '2013-03-08'); * $data = $archive->getNumeric(array('nb_visits', 'nb_actions')); - * + * * $visits = $data['nb_visits']; * $actions = $data['nb_actions']; * @@ -68,41 +69,40 @@ use Piwik\Period\Range; * // multiple sites and multiple dates * $archive = Archive::build($idSite = '1,2,3', $period = 'month', $date = '2013-01-02,2013-03-08'); * $data = $archive->getNumeric('nb_visits'); - * + * * $janSite1Visits = $data['1']['2013-01-01,2013-01-31']['nb_visits']; * $febSite1Visits = $data['1']['2013-02-01,2013-02-28']['nb_visits']; * // ... etc. - * + * * **_Querying for reports_** - * + * * $archive = Archive::build($idSite = 1, $period = 'week', $date = '2013-03-08'); * $dataTable = $archive->getDataTable('MyPlugin_MyReport'); * // ... manipulate $dataTable ... * return $dataTable; - * + * * **_Querying a report for an API method_** - * + * * public function getMyReport($idSite, $period, $date, $segment = false, $expanded = false) * { * $dataTable = Archive::getDataTableFromArchive('MyPlugin_MyReport', $idSite, $period, $date, $segment, $expanded); * $dataTable->queueFilter('ReplaceColumnNames'); * return $dataTable; * } - * + * * **_Querying data for multiple range periods_** - * + * * // get data for first range * $archive = Archive::build($idSite = 1, $period = 'range', $date = '2013-03-08,2013-03-12'); * $dataTable = $archive->getDataTableFromNumeric(array('nb_visits', 'nb_actions')); - * + * * // get data for second range * $archive = Archive::build($idSite = 1, $period = 'range', $date = '2013-03-15,2013-03-20'); * $dataTable = $archive->getDataTableFromNumeric(array('nb_visits', 'nb_actions')); - * + * * * [1]: The archiving process will not be launched if browser archiving is disabled - * and the current request came from a browser (and not the **archive.php** cron - * script). + * and the current request came from a browser. * * * @api @@ -162,6 +162,16 @@ class Archive */ private $params; + /** + * @var \Piwik\Cache\Cache + */ + private static $cache; + + /** + * @var ArchiveInvalidator + */ + private $invalidator; + /** * @param Parameters $params * @param bool $forceIndexedBySite Whether to force index the result of a query by site ID. @@ -173,6 +183,8 @@ class Archive $this->params = $params; $this->forceIndexedBySite = $forceIndexedBySite; $this->forceIndexedByDate = $forceIndexedByDate; + + $this->invalidator = StaticContainer::get('Piwik\Archive\ArchiveInvalidator'); } /** @@ -192,37 +204,42 @@ class Archive * or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD'). * @param bool|false|string $segment Segment definition or false if no segment should be used. {@link Piwik\Segment} * @param bool|false|string $_restrictSitesToLogin Used only when running as a scheduled task. - * @param bool $skipAggregationOfSubTables Whether the archive, when it is processed, should also aggregate all sub-tables - * @return Archive + * @return static */ - public static function build($idSites, $period, $strDate, $segment = false, $_restrictSitesToLogin = false, $skipAggregationOfSubTables = false) + public static function build($idSites, $period, $strDate, $segment = false, $_restrictSitesToLogin = false) { $websiteIds = Site::getIdSitesFromIdSitesString($idSites, $_restrictSitesToLogin); + $timezone = false; + if (count($websiteIds) == 1) { + $timezone = Site::getTimezoneFor($websiteIds[0]); + } + if (Period::isMultiplePeriod($strDate, $period)) { - $oPeriod = new Range($period, $strDate); + $oPeriod = PeriodFactory::build($period, $strDate, $timezone); $allPeriods = $oPeriod->getSubperiods(); } else { - $timezone = count($websiteIds) == 1 ? Site::getTimezoneFor($websiteIds[0]) : false; - $oPeriod = Period::makePeriodFromQueryParams($timezone, $period, $strDate); + $oPeriod = PeriodFactory::makePeriodFromQueryParams($timezone, $period, $strDate); $allPeriods = array($oPeriod); } - $segment = new Segment($segment, $websiteIds); - $idSiteIsAll = $idSites == self::REQUEST_ALL_WEBSITES_FLAG; + + $segment = new Segment($segment, $websiteIds); + $idSiteIsAll = $idSites == self::REQUEST_ALL_WEBSITES_FLAG; $isMultipleDate = Period::isMultiplePeriod($strDate, $period); - return Archive::factory($segment, $allPeriods, $websiteIds, $idSiteIsAll, $isMultipleDate, $skipAggregationOfSubTables); + + return static::factory($segment, $allPeriods, $websiteIds, $idSiteIsAll, $isMultipleDate); } /** * Returns a new Archive instance that will query archive data for the given set of * sites and periods, using an optional segment. - * + * * This method uses an array of Period instances and a Segment instance, instead of strings * like {@link build()}. - * + * * If you want to create an Archive instance using data found in query parameters, * use {@link build()}. - * + * * @param Segment $segment The segment to use. For no segment, use `new Segment('', $idSites)`. * @param array $periods An array of Period instances. * @param array $idSites An array of site IDs (eg, `array(1, 2, 3)`). @@ -232,41 +249,42 @@ class Archive * @param bool $isMultipleDate Whether multiple dates are being queried or not. If true, then * the result of querying functions will be indexed by period, * regardless of whether `count($periods) == 1`. - * @param bool $skipAggregationOfSubTables Whether the archive should skip aggregation of all sub-tables * * @return Archive */ - public static function factory(Segment $segment, array $periods, array $idSites, $idSiteIsAll = false, $isMultipleDate = false, $skipAggregationOfSubTables = false) + public static function factory(Segment $segment, array $periods, array $idSites, $idSiteIsAll = false, $isMultipleDate = false) { $forceIndexedBySite = false; $forceIndexedByDate = false; + if ($idSiteIsAll || count($idSites) > 1) { $forceIndexedBySite = true; } + if (count($periods) > 1 || $isMultipleDate) { $forceIndexedByDate = true; } - $params = new Parameters($idSites, $periods, $segment, $skipAggregationOfSubTables); + $params = new Parameters($idSites, $periods, $segment); - return new Archive($params, $forceIndexedBySite, $forceIndexedByDate); + return new static($params, $forceIndexedBySite, $forceIndexedByDate); } /** * Queries and returns metric data in an array. - * + * * If multiple sites were requested in {@link build()} or {@link factory()} the result will * be indexed by site ID. - * + * * If multiple periods were requested in {@link build()} or {@link factory()} the result will * be indexed by period. - * + * * The site ID index is always first, so if multiple sites & periods were requested, the result * will be indexed by site ID first, then period. - * + * * @param string|array $names One or more archive names, eg, `'nb_visits'`, `'Referrers_distinctKeywords'`, * etc. - * @return false|numeric|array `false` if there is no data to return, a single numeric value if we're not querying + * @return false|integer|array `false` if there is no data to return, a single numeric value if we're not querying * for multiple sites/periods, or an array if multiple sites, periods or names are * queried for. */ @@ -288,50 +306,22 @@ class Archive return $result; } - /** - * Queries and returns blob data in an array. - * - * Reports are stored in blobs as serialized arrays of {@link DataTable\Row} instances, but this - * data can technically be anything. In other words, you can store whatever you want - * as archive data blobs. - * - * If multiple sites were requested in {@link build()} or {@link factory()} the result will - * be indexed by site ID. - * - * If multiple periods were requested in {@link build()} or {@link factory()} the result will - * be indexed by period. - * - * The site ID index is always first, so if multiple sites & periods were requested, the result - * will be indexed by site ID first, then period. - * - * @param string|array $names One or more archive names, eg, `'Referrers_keywordBySearchEngine'`. - * @param null|string $idSubtable If we're returning serialized DataTable data, then this refers - * to the subtable ID to return. If set to 'all', all subtables - * of each requested report are returned. - * @return array An array of appropriately indexed blob data. - */ - public function getBlob($names, $idSubtable = null) - { - $data = $this->get($names, 'blob', $idSubtable); - return $data->getIndexedArray($this->getResultIndices()); - } - /** * Queries and returns metric data in a DataTable instance. - * + * * If multiple sites were requested in {@link build()} or {@link factory()} the result will * be a DataTable\Map that is indexed by site ID. - * + * * If multiple periods were requested in {@link build()} or {@link factory()} the result will * be a {@link DataTable\Map} that is indexed by period. - * + * * The site ID index is always first, so if multiple sites & periods were requested, the result * will be a {@link DataTable\Map} indexed by site ID which contains {@link DataTable\Map} instances that are * indexed by period. - * + * * _Note: Every DataTable instance returned will have at most one row in it. The contents of each * row will be the requested metrics for the appropriate site and period._ - * + * * @param string|array $names One or more archive names, eg, 'nb_visits', 'Referrers_distinctKeywords', * etc. * @return DataTable|DataTable\Map A DataTable if multiple sites and periods were not requested. @@ -343,22 +333,40 @@ class Archive return $data->getDataTable($this->getResultIndices()); } + /** + * Similar to {@link getDataTableFromNumeric()} but merges all children on the created DataTable. + * + * This is the same as doing `$this->getDataTableFromNumeric()->mergeChildren()` but this way it is much faster. + * + * @return DataTable|DataTable\Map + * + * @internal Currently only used by MultiSites.getAll plugin. Feel free to remove internal tag if needed somewhere + * else. If no longer needed by MultiSites.getAll please remove this method. If you need this to work in + * a bit different way feel free to refactor as always. + */ + public function getDataTableFromNumericAndMergeChildren($names) + { + $data = $this->get($names, 'numeric'); + $resultIndexes = $this->getResultIndices(); + return $data->getMergedDataTable($resultIndexes); + } + /** * Queries and returns one or more reports as DataTable instances. - * + * * This method will query blob data that is a serialized array of of {@link DataTable\Row}'s and * unserialize it. - * + * * If multiple sites were requested in {@link build()} or {@link factory()} the result will * be a {@link DataTable\Map} that is indexed by site ID. - * + * * If multiple periods were requested in {@link build()} or {@link factory()} the result will * be a DataTable\Map that is indexed by period. - * + * * The site ID index is always first, so if multiple sites & periods were requested, the result * will be a {@link DataTable\Map} indexed by site ID which contains {@link DataTable\Map} instances that are * indexed by period. - * + * * @param string $name The name of the record to get. This method can only query one record at a time. * @param int|string|null $idSubtable The ID of the subtable to get (if any). * @return DataTable|DataTable\Map A DataTable if multiple sites and periods were not requested. @@ -372,13 +380,13 @@ class Archive /** * Queries and returns one report with all of its subtables loaded. - * + * * If multiple sites were requested in {@link build()} or {@link factory()} the result will * be a DataTable\Map that is indexed by site ID. - * + * * If multiple periods were requested in {@link build()} or {@link factory()} the result will * be a DataTable\Map that is indexed by period. - * + * * The site ID index is always first, so if multiple sites & periods were requested, the result * will be a {@link DataTable\Map indexed} by site ID which contains {@link DataTable\Map} instances that are * indexed by period. @@ -400,16 +408,18 @@ class Archive /** * Returns the list of plugins that archive the given reports. - * + * * @param array $archiveNames * @return array */ private function getRequestedPlugins($archiveNames) { $result = array(); + foreach ($archiveNames as $name) { $result[] = self::getPluginForReport($name); } + return array_unique($result); } @@ -427,7 +437,7 @@ class Archive /** * Helper function that creates an Archive instance and queries for report data using * query parameter data. API methods can use this method to reduce code redundancy. - * + * * @param string $name The name of the report to return. * @param int|string|array $idSite @see {@link build()} * @param string $period @see {@link build()} @@ -435,21 +445,19 @@ class Archive * @param string $segment @see {@link build()} * @param bool $expanded If true, loads all subtables. See {@link getDataTableExpanded()} * @param int|null $idSubtable See {@link getDataTableExpanded()} - * @param bool $skipAggregationOfSubTables Whether or not we should skip the aggregation of all sub-tables and only aggregate parent DataTable. * @param int|null $depth See {@link getDataTableExpanded()} + * @throws \Exception * @return DataTable|DataTable\Map See {@link getDataTable()} and * {@link getDataTableExpanded()} for more * information + * @deprecated Since Piwik 2.12.0 Use Archive::createDataTableFromArchive() instead */ public static function getDataTableFromArchive($name, $idSite, $period, $date, $segment, $expanded, - $idSubtable = null, $skipAggregationOfSubTables = false, $depth = null) + $idSubtable = null, $depth = null) { Piwik::checkUserHasViewAccess($idSite); - if($skipAggregationOfSubTables && ($expanded || $idSubtable)) { - throw new \Exception("Not expected to skipAggregationOfSubTables when expanded=1 or idSubtable is set."); - } - $archive = Archive::build($idSite, $period, $date, $segment, $_restrictSitesToLogin = false, $skipAggregationOfSubTables); + $archive = Archive::build($idSite, $period, $date, $segment, $_restrictSitesToLogin = false); if ($idSubtable === false) { $idSubtable = null; } @@ -465,9 +473,102 @@ class Archive return $dataTable; } - private function appendIdSubtable($recordName, $id) + /** + * Helper function that creates an Archive instance and queries for report data using + * query parameter data. API methods can use this method to reduce code redundancy. + * + * @param string $recordName The name of the report to return. + * @param int|string|array $idSite @see {@link build()} + * @param string $period @see {@link build()} + * @param string $date @see {@link build()} + * @param string $segment @see {@link build()} + * @param bool $expanded If true, loads all subtables. See {@link getDataTableExpanded()} + * @param bool $flat If true, loads all subtables and disabled all recursive filters. + * @param int|null $idSubtable See {@link getDataTableExpanded()} + * @param int|null $depth See {@link getDataTableExpanded()} + * @return DataTable|DataTable\Map + */ + public static function createDataTableFromArchive($recordName, $idSite, $period, $date, $segment, $expanded = false, $flat = false, $idSubtable = null, $depth = null) { - return $recordName . "_" . $id; + if ($flat && !$idSubtable) { + $expanded = true; + } + + $dataTable = self::getDataTableFromArchive($recordName, $idSite, $period, $date, $segment, $expanded, $idSubtable, $depth); + + $dataTable->queueFilter('ReplaceColumnNames'); + + if ($expanded) { + $dataTable->queueFilterSubtables('ReplaceColumnNames'); + } + + if ($flat) { + $dataTable->disableRecursiveFilters(); + } + + return $dataTable; + } + + private function getSiteIdsThatAreRequestedInThisArchiveButWereNotInvalidatedYet() + { + if (is_null(self::$cache)) { + self::$cache = Cache::getTransientCache(); + } + + $id = 'Archive.SiteIdsOfRememberedReportsInvalidated'; + + if (!self::$cache->contains($id)) { + self::$cache->save($id, array()); + } + + $siteIdsAlreadyHandled = self::$cache->fetch($id); + $siteIdsRequested = $this->params->getIdSites(); + + foreach ($siteIdsRequested as $index => $siteIdRequested) { + $siteIdRequested = (int) $siteIdRequested; + + if (in_array($siteIdRequested, $siteIdsAlreadyHandled)) { + unset($siteIdsRequested[$index]); // was already handled previously, do not do it again + } else { + $siteIdsAlreadyHandled[] = $siteIdRequested; // we will handle this id this time + } + } + + self::$cache->save($id, $siteIdsAlreadyHandled); + + return $siteIdsRequested; + } + + private function invalidatedReportsIfNeeded() + { + $siteIdsRequested = $this->getSiteIdsThatAreRequestedInThisArchiveButWereNotInvalidatedYet(); + + if (empty($siteIdsRequested)) { + return; // all requested site ids were already handled + } + + $sitesPerDays = $this->invalidator->getRememberedArchivedReportsThatShouldBeInvalidated(); + + foreach ($sitesPerDays as $date => $siteIds) { + if (empty($siteIds)) { + continue; + } + + $siteIdsToActuallyInvalidate = array_intersect($siteIds, $siteIdsRequested); + + if (empty($siteIdsToActuallyInvalidate)) { + continue; // all site ids that should be handled are already handled + } + + try { + $this->invalidator->markArchivesAsInvalidated($siteIdsToActuallyInvalidate, array(Date::factory($date)), false); + } catch (\Exception $e) { + Site::clearCache(); + throw $e; + } + } + + Site::clearCache(); } /** @@ -477,7 +578,7 @@ class Archive * @param null|int $idSubtable * @return Archive\DataCollection */ - private function get($archiveNames, $archiveDataType, $idSubtable = null) + protected function get($archiveNames, $archiveDataType, $idSubtable = null) { if (!is_array($archiveNames)) { $archiveNames = array($archiveNames); @@ -487,35 +588,41 @@ class Archive if ($idSubtable !== null && $idSubtable != self::ID_SUBTABLE_LOAD_ALL_SUBTABLES ) { - foreach ($archiveNames as &$name) { - $name = $this->appendIdsubtable($name, $idSubtable); + // this is also done in ArchiveSelector. It should be actually only done in ArchiveSelector but DataCollection + // does require to have the subtableId appended. Needs to be changed in refactoring to have it only in one + // place. + $dataNames = array(); + foreach ($archiveNames as $name) { + $dataNames[] = ArchiveSelector::appendIdsubtable($name, $idSubtable); } + } else { + $dataNames = $archiveNames; } $result = new Archive\DataCollection( - $archiveNames, $archiveDataType, $this->params->getIdSites(), $this->params->getPeriods(), $defaultRow = null); + $dataNames, $archiveDataType, $this->params->getIdSites(), $this->params->getPeriods(), $defaultRow = null); $archiveIds = $this->getArchiveIds($archiveNames); + if (empty($archiveIds)) { return $result; } - $loadAllSubtables = $idSubtable == self::ID_SUBTABLE_LOAD_ALL_SUBTABLES; - $archiveData = ArchiveSelector::getArchiveData($archiveIds, $archiveNames, $archiveDataType, $loadAllSubtables); + $archiveData = ArchiveSelector::getArchiveData($archiveIds, $archiveNames, $archiveDataType, $idSubtable); + + $isNumeric = $archiveDataType == 'numeric'; + foreach ($archiveData as $row) { // values are grouped by idsite (site ID), date1-date2 (date range), then name (field name) - $idSite = $row['idsite']; - $periodStr = $row['date1'] . "," . $row['date2']; + $periodStr = $row['date1'] . ',' . $row['date2']; - if ($archiveDataType == 'numeric') { - $value = $this->formatNumericValue($row['value']); + if ($isNumeric) { + $row['value'] = $this->formatNumericValue($row['value']); } else { - $value = $this->uncompress($row['value']); - $result->addMetadata($idSite, $periodStr, 'ts_archived', $row['ts_archived']); + $result->addMetadata($row['idsite'], $periodStr, DataTable::ARCHIVED_DATE_METADATA_NAME, $row['ts_archived']); } - $resultRow = & $result->get($idSite, $periodStr); - $resultRow[$row['name']] = $value; + $result->set($row['idsite'], $periodStr, $row['name'], $row['value']); } return $result; @@ -526,6 +633,9 @@ class Archive * queried. This function will use the idarchive cache if it has the right data, * query archive tables for IDs w/o launching archiving, or launch archiving and * get the idarchive from ArchiveProcessor instances. + * + * @param string $archiveNames + * @return array */ private function getArchiveIds($archiveNames) { @@ -533,7 +643,7 @@ class Archive // figure out which archives haven't been processed (if an archive has been processed, // then we have the archive IDs in $this->idarchives) - $doneFlags = array(); + $doneFlags = array(); $archiveGroups = array(); foreach ($plugins as $plugin) { $doneFlag = $this->getDoneStringForPlugin($plugin); @@ -542,7 +652,7 @@ class Archive if (!isset($this->idarchives[$doneFlag])) { $archiveGroup = $this->getArchiveGroupOfPlugin($plugin); - if($archiveGroup == self::ARCHIVE_ALL_PLUGINS_FLAG) { + if ($archiveGroup == self::ARCHIVE_ALL_PLUGINS_FLAG) { $archiveGroup = reset($plugins); } $archiveGroups[] = $archiveGroup; @@ -560,19 +670,7 @@ class Archive } } - // order idarchives by the table month they belong to - $idArchivesByMonth = array(); - foreach (array_keys($doneFlags) as $doneFlag) { - if (empty($this->idarchives[$doneFlag])) { - continue; - } - - foreach ($this->idarchives[$doneFlag] as $dateRange => $idarchives) { - foreach ($idarchives as $id) { - $idArchivesByMonth[$dateRange][] = $id; - } - } - } + $idArchivesByMonth = $this->getIdArchivesByMonth($doneFlags); return $idArchivesByMonth; } @@ -587,6 +685,8 @@ class Archive */ private function cacheArchiveIdsAfterLaunching($archiveGroups, $plugins) { + $this->invalidatedReportsIfNeeded(); + $today = Date::today(); foreach ($this->params->getPeriods() as $period) { @@ -600,14 +700,14 @@ class Archive // we already know there are no stats for this period // we add one day to make sure we don't miss the day of the website creation if ($twoDaysAfterPeriod->isEarlier($site->getCreationDate())) { - Log::verbose("Archive site %s, %s (%s) skipped, archive is before the website was created.", + Log::debug("Archive site %s, %s (%s) skipped, archive is before the website was created.", $idSite, $period->getLabel(), $period->getPrettyString()); continue; } // if the starting date is in the future we know there is no visiidsite = ?t if ($twoDaysBeforePeriod->isLater($today)) { - Log::verbose("Archive site %s, %s (%s) skipped, archive is after today.", + Log::debug("Archive site %s, %s (%s) skipped, archive is after today.", $idSite, $period->getLabel(), $period->getPrettyString()); continue; } @@ -627,7 +727,7 @@ class Archive private function cacheArchiveIdsWithoutLaunching($plugins) { $idarchivesByReport = ArchiveSelector::getArchiveIds( - $this->params->getIdSites(), $this->params->getPeriods(), $this->params->getSegment(), $plugins, $this->params->isSkipAggregationOfSubTables()); + $this->params->getIdSites(), $this->params->getPeriods(), $this->params->getSegment(), $plugins); // initialize archive ID cache for each report foreach ($plugins as $plugin) { @@ -655,8 +755,7 @@ class Archive $this->params->getIdSites(), $this->params->getSegment(), $this->getPeriodLabel(), - $plugin, - $this->params->isSkipAggregationOfSubTables() + $plugin ); } @@ -707,11 +806,6 @@ class Archive return round((float)$value, 2); } - private function uncompress($data) - { - return @gzuncompress($data); - } - /** * Initializes the archive ID cache ($this->idarchives) for a particular 'done' flag. * @@ -723,6 +817,8 @@ class Archive * If this function is not called, then periods with no visits will not add * entries to the cache. If the archive is used again, SQL will be executed to * try and find the archive IDs even though we know there are none. + * + * @param string $doneFlag */ private function initializeArchiveIdCache($doneFlag) { @@ -745,7 +841,10 @@ class Archive */ private function getArchiveGroupOfPlugin($plugin) { - if ($this->getPeriodLabel() != 'range') { + $periods = $this->params->getPeriods(); + $periodLabel = reset($periods)->getLabel(); + + if (Rules::shouldProcessReportsAllPlugins($this->params->getIdSites(), $this->params->getSegment(), $periodLabel)) { return self::ARCHIVE_ALL_PLUGINS_FLAG; } @@ -755,7 +854,7 @@ class Archive /** * Returns the name of the plugin that archives a given report. * - * @param string $report Archive data name, eg, `'nb_visits'`, `'UserSettings_...'`, etc. + * @param string $report Archive data name, eg, `'nb_visits'`, `'DevicesDetection_...'`, etc. * @return string Plugin name. * @throws \Exception If a plugin cannot be found or if the plugin for the report isn't * activated. @@ -766,9 +865,9 @@ class Archive if (in_array($report, Metrics::getVisitsMetricNames())) { $report = 'VisitsSummary_CoreMetrics'; } // Goal_* metrics are processed by the Goals plugin (HACK) - else if (strpos($report, 'Goal_') === 0) { + elseif (strpos($report, 'Goal_') === 0) { $report = 'Goals_Metrics'; - } else if (strrpos($report, '_returning') === strlen($report) - strlen('_returning')) { // HACK + } elseif (strrpos($report, '_returning') === strlen($report) - strlen('_returning')) { // HACK $report = 'VisitFrequency_Metrics'; } @@ -789,7 +888,7 @@ class Archive */ private function prepareArchive(array $archiveGroups, Site $site, Period $period) { - $parameters = new ArchiveProcessor\Parameters($site, $period, $this->params->getSegment(), $this->params->isSkipAggregationOfSubTables()); + $parameters = new ArchiveProcessor\Parameters($site, $period, $this->params->getSegment()); $archiveLoader = new ArchiveProcessor\Loader($parameters); $periodString = $period->getRangeString(); @@ -801,9 +900,37 @@ class Archive $idArchive = $archiveLoader->prepareArchive($plugin); - if($idArchive) { + if ($idArchive) { $this->idarchives[$doneFlag][$periodString][] = $idArchive; } } } + + private function getIdArchivesByMonth($doneFlags) + { + // order idarchives by the table month they belong to + $idArchivesByMonth = array(); + + foreach (array_keys($doneFlags) as $doneFlag) { + if (empty($this->idarchives[$doneFlag])) { + continue; + } + + foreach ($this->idarchives[$doneFlag] as $dateRange => $idarchives) { + foreach ($idarchives as $id) { + $idArchivesByMonth[$dateRange][] = $id; + } + } + } + + return $idArchivesByMonth; + } + + /** + * @internal + */ + public static function clearStaticCache() + { + self::$cache = null; + } } diff --git a/www/analytics/core/Archive/ArchiveInvalidator.php b/www/analytics/core/Archive/ArchiveInvalidator.php new file mode 100644 index 00000000..6ecc5b87 --- /dev/null +++ b/www/analytics/core/Archive/ArchiveInvalidator.php @@ -0,0 +1,317 @@ +model = $model; + } + + public function rememberToInvalidateArchivedReportsLater($idSite, Date $date) + { + $key = $this->buildRememberArchivedReportId($idSite, $date->toString()); + $value = Option::get($key); + + // we do not really have to get the value first. we could simply always try to call set() and it would update or + // insert the record if needed but we do not want to lock the table (especially since there are still some + // MyISAM installations) + + if (false === $value) { + Option::set($key, '1'); + } + } + + public function getRememberedArchivedReportsThatShouldBeInvalidated() + { + $reports = Option::getLike($this->rememberArchivedReportIdStart . '%_%'); + + $sitesPerDay = array(); + + foreach ($reports as $report => $value) { + $report = str_replace($this->rememberArchivedReportIdStart, '', $report); + $report = explode('_', $report); + $siteId = (int) $report[0]; + $date = $report[1]; + + if (empty($sitesPerDay[$date])) { + $sitesPerDay[$date] = array(); + } + + $sitesPerDay[$date][] = $siteId; + } + + return $sitesPerDay; + } + + private function buildRememberArchivedReportId($idSite, $date) + { + $id = $this->buildRememberArchivedReportIdForSite($idSite); + $id .= '_' . trim($date); + + return $id; + } + + private function buildRememberArchivedReportIdForSite($idSite) + { + return $this->rememberArchivedReportIdStart . (int) $idSite; + } + + public function forgetRememberedArchivedReportsToInvalidateForSite($idSite) + { + $id = $this->buildRememberArchivedReportIdForSite($idSite) . '_%'; + Option::deleteLike($id); + } + + /** + * @internal + */ + public function forgetRememberedArchivedReportsToInvalidate($idSite, Date $date) + { + $id = $this->buildRememberArchivedReportId($idSite, $date->toString()); + + Option::delete($id); + } + + /** + * @param $idSites int[] + * @param $dates Date[] + * @param $period string + * @param $segment Segment + * @param bool $cascadeDown + * @return InvalidationResult + * @throws \Exception + */ + public function markArchivesAsInvalidated(array $idSites, array $dates, $period, Segment $segment = null, $cascadeDown = false) + { + $invalidationInfo = new InvalidationResult(); + + $datesToInvalidate = $this->removeDatesThatHaveBeenPurged($dates, $invalidationInfo); + + if (empty($period)) { + // if the period is empty, we don't need to cascade in any way, since we'll remove all periods + $periodDates = $this->getDatesByYearMonthAndPeriodType($dates); + } else { + $periods = $this->getPeriodsToInvalidate($datesToInvalidate, $period, $cascadeDown); + $periodDates = $this->getPeriodDatesByYearMonthAndPeriodType($periods); + } + + $periodDates = $this->getUniqueDates($periodDates); + $this->markArchivesInvalidated($idSites, $periodDates, $segment); + + $yearMonths = array_keys($periodDates); + $this->markInvalidatedArchivesForReprocessAndPurge($idSites, $yearMonths); + + foreach ($idSites as $idSite) { + foreach ($dates as $date) { + $this->forgetRememberedArchivedReportsToInvalidate($idSite, $date); + } + } + + return $invalidationInfo; + } + + /** + * @param string[][][] $periodDates + * @return string[][][] + */ + private function getUniqueDates($periodDates) + { + $result = array(); + foreach ($periodDates as $yearMonth => $periodsByYearMonth) { + foreach ($periodsByYearMonth as $periodType => $periods) { + $result[$yearMonth][$periodType] = array_unique($periods); + } + } + return $result; + } + + /** + * @param Date[] $dates + * @param string $periodType + * @param bool $cascadeDown + * @return Period[] + */ + private function getPeriodsToInvalidate($dates, $periodType, $cascadeDown) + { + $periodsToInvalidate = array(); + + foreach ($dates as $date) { + if ($periodType == 'range') { + $date = $date . ',' . $date; + } + + $period = Period\Factory::build($periodType, $date); + $periodsToInvalidate[] = $period; + + if ($cascadeDown) { + $periodsToInvalidate = array_merge($periodsToInvalidate, $period->getAllOverlappingChildPeriods()); + } + + if ($periodType != 'year' + && $periodType != 'range' + ) { + $periodsToInvalidate[] = Period\Factory::build('year', $date); + } + } + + return $periodsToInvalidate; + } + + /** + * @param Period[] $periods + * @return string[][][] + */ + private function getPeriodDatesByYearMonthAndPeriodType($periods) + { + $result = array(); + foreach ($periods as $period) { + $date = $period->getDateStart(); + $periodType = $period->getId(); + + $yearMonth = ArchiveTableCreator::getTableMonthFromDate($date); + $result[$yearMonth][$periodType][] = $date->toString(); + } + return $result; + } + + /** + * Called when deleting all periods. + * + * @param Date[] $dates + * @return string[][][] + */ + private function getDatesByYearMonthAndPeriodType($dates) + { + $result = array(); + foreach ($dates as $date) { + $yearMonth = ArchiveTableCreator::getTableMonthFromDate($date); + $result[$yearMonth][null][] = $date->toString(); + + // since we're removing all periods, we must make sure to remove year periods as well. + // this means we have to make sure the january table is processed. + $janYearMonth = $date->toString('Y') . '_01'; + $result[$janYearMonth][null][] = $date->toString(); + } + return $result; + } + + /** + * @param int[] $idSites + * @param string[][][] $dates + * @throws \Exception + */ + private function markArchivesInvalidated($idSites, $dates, Segment $segment = null) + { + $archiveNumericTables = ArchiveTableCreator::getTablesArchivesInstalled($type = ArchiveTableCreator::NUMERIC_TABLE); + foreach ($archiveNumericTables as $table) { + $tableDate = ArchiveTableCreator::getDateFromTableName($table); + if (empty($dates[$tableDate])) { + continue; + } + + $this->model->updateArchiveAsInvalidated($table, $idSites, $dates[$tableDate], $segment); + } + } + + /** + * @param Date[] $dates + * @param InvalidationResult $invalidationInfo + * @return \Piwik\Date[] + */ + private function removeDatesThatHaveBeenPurged($dates, InvalidationResult $invalidationInfo) + { + $this->findOlderDateWithLogs($invalidationInfo); + + $result = array(); + foreach ($dates as $date) { + // we should only delete reports for dates that are more recent than N days + if ($invalidationInfo->minimumDateWithLogs + && $date->isEarlier($invalidationInfo->minimumDateWithLogs) + ) { + $invalidationInfo->warningDates[] = $date->toString(); + continue; + } + + $result[] = $date; + $invalidationInfo->processedDates[] = $date->toString(); + } + return $result; + } + + private function findOlderDateWithLogs(InvalidationResult $info) + { + // If using the feature "Delete logs older than N days"... + $purgeDataSettings = PrivacyManager::getPurgeDataSettings(); + $logsDeletedWhenOlderThanDays = (int)$purgeDataSettings['delete_logs_older_than']; + $logsDeleteEnabled = $purgeDataSettings['delete_logs_enable']; + + if ($logsDeleteEnabled + && $logsDeletedWhenOlderThanDays + ) { + $info->minimumDateWithLogs = Date::factory('today')->subDay($logsDeletedWhenOlderThanDays); + } + } + + /** + * @param array $idSites + * @param array $yearMonths + */ + private function markInvalidatedArchivesForReprocessAndPurge(array $idSites, $yearMonths) + { + $store = new SitesToReprocessDistributedList(); + $store->add($idSites); + + $archivesToPurge = new ArchivesToPurgeDistributedList(); + $archivesToPurge->add($yearMonths); + } +} diff --git a/www/analytics/core/Archive/ArchiveInvalidator/InvalidationResult.php b/www/analytics/core/Archive/ArchiveInvalidator/InvalidationResult.php new file mode 100644 index 00000000..517e1138 --- /dev/null +++ b/www/analytics/core/Archive/ArchiveInvalidator/InvalidationResult.php @@ -0,0 +1,56 @@ +warningDates) { + $output[] = 'Warning: the following Dates have not been invalidated, because they are earlier than your Log Deletion limit: ' . + implode(", ", $this->warningDates) . + "\n The last day with logs is " . $this->minimumDateWithLogs . ". " . + "\n Please disable 'Delete old Logs' or set it to a higher deletion threshold (eg. 180 days or 365 years).'."; + } + + $output[] = "Success. The following dates were invalidated successfully: " . implode(", ", $this->processedDates); + return $output; + } +} \ No newline at end of file diff --git a/www/analytics/core/Archive/ArchivePurger.php b/www/analytics/core/Archive/ArchivePurger.php new file mode 100644 index 00000000..078203cb --- /dev/null +++ b/www/analytics/core/Archive/ArchivePurger.php @@ -0,0 +1,272 @@ +model = $model ?: new Model(); + + $this->purgeCustomRangesOlderThan = $purgeCustomRangesOlderThan ?: self::getDefaultCustomRangeToPurgeAgeThreshold(); + + $this->yesterday = Date::factory('yesterday'); + $this->today = Date::factory('today'); + $this->now = time(); + $this->logger = $logger ?: StaticContainer::get('Psr\Log\LoggerInterface'); + } + + /** + * Purge all invalidate archives for whom there are newer, valid archives from the archive + * table that stores data for `$date`. + * + * @param Date $date The date identifying the archive table. + * @return int The total number of archive rows deleted (from both the blog & numeric tables). + */ + public function purgeInvalidatedArchivesFrom(Date $date) + { + $numericTable = ArchiveTableCreator::getNumericTable($date); + + // we don't want to do an INNER JOIN on every row in a archive table that can potentially have tens to hundreds of thousands of rows, + // so we first look for sites w/ invalidated archives, and use this as a constraint in getInvalidatedArchiveIdsSafeToDelete() below. + // the constraint will hit an INDEX and speed up the inner join that happens in getInvalidatedArchiveIdsSafeToDelete(). + $idSites = $this->model->getSitesWithInvalidatedArchive($numericTable); + if (empty($idSites)) { + $this->logger->debug("No sites with invalidated archives found in {table}.", array('table' => $numericTable)); + return 0; + } + + $archiveIds = $this->model->getInvalidatedArchiveIdsSafeToDelete($numericTable, $idSites); + if (empty($archiveIds)) { + $this->logger->debug("No invalidated archives found in {table} with newer, valid archives.", array('table' => $numericTable)); + return 0; + } + + $this->logger->info("Found {countArchiveIds} invalidated archives safe to delete in {table}.", array( + 'table' => $numericTable, 'countArchiveIds' => count($archiveIds) + )); + + $deletedRowCount = $this->deleteArchiveIds($date, $archiveIds); + + $this->logger->debug("Deleted {count} rows in {table} and its associated blob table.", array( + 'table' => $numericTable, 'count' => $deletedRowCount + )); + + return $deletedRowCount; + } + + /** + * Removes the outdated archives for the given month. + * (meaning they are marked with a done flag of ArchiveWriter::DONE_OK_TEMPORARY or ArchiveWriter::DONE_ERROR) + * + * @param Date $dateStart Only the month will be used + * @return int Returns the total number of rows deleted. + */ + public function purgeOutdatedArchives(Date $dateStart) + { + $purgeArchivesOlderThan = $this->getOldestTemporaryArchiveToKeepThreshold(); + $deletedRowCount = 0; + + $idArchivesToDelete = $this->getOutdatedArchiveIds($dateStart, $purgeArchivesOlderThan); + if (!empty($idArchivesToDelete)) { + $deletedRowCount = $this->deleteArchiveIds($dateStart, $idArchivesToDelete); + + $this->logger->info("Deleted {count} rows in archive tables (numeric + blob) for {date}.", array( + 'count' => $deletedRowCount, + 'date' => $dateStart + )); + } else { + $this->logger->debug("No outdated archives found in archive numeric table for {date}.", array('date' => $dateStart)); + } + + $this->logger->debug("Purging temporary archives: done [ purged archives older than {date} in {yearMonth} ] [Deleted IDs: {deletedIds}]", array( + 'date' => $purgeArchivesOlderThan, + 'yearMonth' => $dateStart->toString('Y-m'), + 'deletedIds' => implode(',', $idArchivesToDelete) + )); + + return $deletedRowCount; + } + + protected function getOutdatedArchiveIds(Date $date, $purgeArchivesOlderThan) + { + $archiveTable = ArchiveTableCreator::getNumericTable($date); + + $result = $this->model->getTemporaryArchivesOlderThan($archiveTable, $purgeArchivesOlderThan); + + $idArchivesToDelete = array(); + if (!empty($result)) { + foreach ($result as $row) { + $idArchivesToDelete[] = $row['idarchive']; + } + } + + return $idArchivesToDelete; + } + + /** + * Deleting "Custom Date Range" reports after 1 day, since they can be re-processed and would take up un-necessary space. + * + * @param $date Date + * @return int The total number of rows deleted from both the numeric & blob table. + */ + public function purgeArchivesWithPeriodRange(Date $date) + { + $numericTable = ArchiveTableCreator::getNumericTable($date); + $blobTable = ArchiveTableCreator::getBlobTable($date); + + $deletedCount = $this->model->deleteArchivesWithPeriod( + $numericTable, $blobTable, Piwik::$idPeriods['range'], $this->purgeCustomRangesOlderThan); + + $level = $deletedCount == 0 ? LogLevel::DEBUG : LogLevel::INFO; + $this->logger->log($level, "Purged {count} range archive rows from {numericTable} & {blobTable}.", array( + 'count' => $deletedCount, + 'numericTable' => $numericTable, + 'blobTable' => $blobTable + )); + + $this->logger->debug(" [ purged archives older than {threshold} ]", array('threshold' => $this->purgeCustomRangesOlderThan)); + + return $deletedCount; + } + + /** + * Deletes by batches Archive IDs in the specified month, + * + * @param Date $date + * @param $idArchivesToDelete + * @return int Number of rows deleted from both numeric + blob table. + */ + protected function deleteArchiveIds(Date $date, $idArchivesToDelete) + { + $batches = array_chunk($idArchivesToDelete, 1000); + $numericTable = ArchiveTableCreator::getNumericTable($date); + $blobTable = ArchiveTableCreator::getBlobTable($date); + + $deletedCount = 0; + foreach ($batches as $idsToDelete) { + $deletedCount += $this->model->deleteArchiveIds($numericTable, $blobTable, $idsToDelete); + } + return $deletedCount; + } + + /** + * Returns a timestamp indicating outdated archives older than this timestamp (processed before) can be purged. + * + * @return int|bool Outdated archives older than this timestamp should be purged + */ + protected function getOldestTemporaryArchiveToKeepThreshold() + { + $temporaryArchivingTimeout = Rules::getTodayArchiveTimeToLive(); + if (Rules::isBrowserTriggerEnabled()) { + // If Browser Archiving is enabled, it is likely there are many more temporary archives + // We delete more often which is safe, since reports are re-processed on demand + return Date::factory($this->now - 2 * $temporaryArchivingTimeout)->getDateTime(); + } + + // If cron core:archive command is building the reports, we should keep all temporary reports from today + return $this->yesterday->getDateTime(); + } + + private static function getDefaultCustomRangeToPurgeAgeThreshold() + { + $daysRangesValid = Config::getInstance()->General['purge_date_range_archives_after_X_days']; + return Date::factory('today')->subDay($daysRangesValid)->getDateTime(); + } + + /** + * For tests. + * + * @param Date $yesterday + */ + public function setYesterdayDate(Date $yesterday) + { + $this->yesterday = $yesterday; + } + + /** + * For tests. + * + * @param Date $today + */ + public function setTodayDate(Date $today) + { + $this->today = $today; + } + + /** + * For tests. + * + * @param int $now + */ + public function setNow($now) + { + $this->now = $now; + } +} diff --git a/www/analytics/core/Archive/Chunk.php b/www/analytics/core/Archive/Chunk.php new file mode 100644 index 00000000..70afec44 --- /dev/null +++ b/www/analytics/core/Archive/Chunk.php @@ -0,0 +1,144 @@ +getAppendix() . $start . '_' . $end; + } + + /** + * Moves the given blobs into chunks and assigns a proper record name containing the chunk number. + * + * @param string $recordName The original archive record name, eg 'Actions_ActionsUrl' + * @param array $blobs An array containg a mapping of tableIds to blobs. Eg array(0 => 'blob', 1 => 'subtableBlob', ...) + * @return array An array where each blob is moved into a chunk, indexed by recordNames. + * eg array('Actions_ActionsUrl_chunk_0_99' => array(0 => 'blob', 1 => 'subtableBlob', ...), + * 'Actions_ActionsUrl_chunk_100_199' => array(...)) + */ + public function moveArchiveBlobsIntoChunks($recordName, $blobs) + { + $chunks = array(); + + foreach ($blobs as $tableId => $blob) { + $name = $this->getRecordNameForTableId($recordName, $tableId); + + if (!array_key_exists($name, $chunks)) { + $chunks[$name] = array(); + } + + $chunks[$name][$tableId] = $blob; + } + + return $chunks; + } + + /** + * Detects whether a recordName like 'Actions_ActionUrls_chunk_0_99' or 'Actions_ActionUrls' belongs to a + * chunk or not. + * + * To be a valid recordName that belongs to a chunk it must end with '_chunk_NUMERIC_NUMERIC'. + * + * @param string $recordName + * @return bool + */ + public function isRecordNameAChunk($recordName) + { + $posAppendix = $this->getEndPosOfChunkAppendix($recordName); + + if (false === $posAppendix) { + return false; + } + + // will contain "0_99" of "chunk_0_99" + $blobId = substr($recordName, $posAppendix); + + return $this->isChunkRange($blobId); + } + + private function isChunkRange($blobId) + { + $blobId = explode('_', $blobId); + + return 2 === count($blobId) && is_numeric($blobId[0]) && is_numeric($blobId[1]); + } + + /** + * When having a record like 'Actions_ActionUrls_chunk_0_99" it will return the raw recordName 'Actions_ActionUrls'. + * + * @param string $recordName + * @return string + */ + public function getRecordNameWithoutChunkAppendix($recordName) + { + if (!$this->isRecordNameAChunk($recordName)) { + return $recordName; + } + + $posAppendix = $this->getStartPosOfChunkAppendix($recordName); + + if (false === $posAppendix) { + return $recordName; + } + + return substr($recordName, 0, $posAppendix); + } + + /** + * Returns the string that is appended to the original record name. This appendix identifes a record name is a + * chunk. + * @return string + */ + public function getAppendix() + { + return '_' . self::ARCHIVE_APPENDIX_SUBTABLES . '_'; + } + + private function getStartPosOfChunkAppendix($recordName) + { + return strpos($recordName, $this->getAppendix()); + } + + private function getEndPosOfChunkAppendix($recordName) + { + $pos = strpos($recordName, $this->getAppendix()); + + if ($pos === false) { + return false; + } + + return $pos + strlen($this->getAppendix()); + } +} diff --git a/www/analytics/core/Archive/DataCollection.php b/www/analytics/core/Archive/DataCollection.php index 5f57e6c3..36ba553f 100644 --- a/www/analytics/core/Archive/DataCollection.php +++ b/www/analytics/core/Archive/DataCollection.php @@ -1,6 +1,6 @@ data[$idSite][$period]; } + /** + * Set data for a specific site & period. If there is no data for the given site ID & period, + * it is set to the default row. + * + * @param int $idSite + * @param string $period eg, '2012-01-01,2012-01-31' + * @param string $name eg 'nb_visits' + * @param string $value eg 5 + */ + public function set($idSite, $period, $name, $value) + { + $row = & $this->get($idSite, $period); + $row[$name] = $value; + } + /** * Adds a new metadata to the data for specific site & period. If there is no * data for the given site ID & period, it is set to the default row. @@ -188,6 +203,7 @@ class DataCollection $this->putRowInIndex($result, $indexKeys, $row, $idSite, $period); } } + return $result; } @@ -208,9 +224,27 @@ class DataCollection $this->dataNames, $this->dataType, $this->sitesId, $this->periods, $this->defaultRow); $index = $this->getIndexedArray($resultIndices); + return $dataTableFactory->make($index, $resultIndices); } + /** + * See {@link DataTableFactory::makeMerged()} + * + * @param array $resultIndices + * @return DataTable|DataTable\Map + * @throws Exception + */ + public function getMergedDataTable($resultIndices) + { + $dataTableFactory = new DataTableFactory( + $this->dataNames, $this->dataType, $this->sitesId, $this->periods, $this->defaultRow); + + $index = $this->getIndexedArray($resultIndices); + + return $dataTableFactory->makeMerged($index, $resultIndices); + } + /** * Returns archive data as a DataTable indexed by metadata. Indexed data will * be represented by Map instances. Each DataTable will have @@ -249,6 +283,7 @@ class DataCollection $dataTableFactory->useSubtable($idSubTable); $index = $this->getIndexedArray($resultIndices); + return $dataTableFactory->make($index, $resultIndices); } @@ -296,12 +331,16 @@ class DataCollection if ($metadataName == DataTableFactory::TABLE_METADATA_SITE_INDEX) { $indexKeyValues = array_values($this->sitesId); - } else if ($metadataName == DataTableFactory::TABLE_METADATA_PERIOD_INDEX) { + } elseif ($metadataName == DataTableFactory::TABLE_METADATA_PERIOD_INDEX) { $indexKeyValues = array_keys($this->periods); } - foreach ($indexKeyValues as $key) { - $result[$key] = $this->createOrderedIndex($metadataNamesToIndexBy); + if (empty($metadataNamesToIndexBy)) { + $result = array_fill_keys($indexKeyValues, array()); + } else { + foreach ($indexKeyValues as $key) { + $result[$key] = $this->createOrderedIndex($metadataNamesToIndexBy); + } } } @@ -318,7 +357,7 @@ class DataCollection foreach ($metadataNamesToIndexBy as $metadataName) { if ($metadataName == DataTableFactory::TABLE_METADATA_SITE_INDEX) { $key = $idSite; - } else if ($metadataName == DataTableFactory::TABLE_METADATA_PERIOD_INDEX) { + } elseif ($metadataName == DataTableFactory::TABLE_METADATA_PERIOD_INDEX) { $key = $period; } else { $key = $row[self::METADATA_CONTAINER_ROW_KEY][$metadataName]; diff --git a/www/analytics/core/Archive/DataTableFactory.php b/www/analytics/core/Archive/DataTableFactory.php index 44beba02..e8b17b67 100644 --- a/www/analytics/core/Archive/DataTableFactory.php +++ b/www/analytics/core/Archive/DataTableFactory.php @@ -1,6 +1,6 @@ defaultRow = $defaultRow; } + /** + * Returns the ID of the site a table is related to based on the 'site' metadata entry, + * or null if there is none. + * + * @param DataTable $table + * @return int|null + */ + public static function getSiteIdFromMetadata(DataTable $table) + { + $site = $table->getMetadata('site'); + if (empty($site)) { + return null; + } else { + return $site->getId(); + } + } + /** * Tells the factory instance to expand the DataTables that are created by * creating subtables and setting the subtable IDs of rows w/ subtables correctly. @@ -128,6 +145,11 @@ class DataTableFactory $this->idSubtable = $idSubtable; } + private function isNumericDataType() + { + return $this->dataType == 'numeric'; + } + /** * Creates a DataTable|Set instance using an index of * archive data. @@ -139,21 +161,63 @@ class DataTableFactory */ public function make($index, $resultIndices) { + $keyMetadata = $this->getDefaultMetadata(); + if (empty($resultIndices)) { // for numeric data, if there's no index (and thus only 1 site & period in the query), // we want to display every queried metric name if (empty($index) - && $this->dataType == 'numeric' + && $this->isNumericDataType() ) { $index = $this->defaultRow; } - $dataTable = $this->createDataTable($index, $keyMetadata = array()); + $dataTable = $this->createDataTable($index, $keyMetadata); } else { - $dataTable = $this->createDataTableMapFromIndex($index, $resultIndices, $keyMetadata = array()); + $dataTable = $this->createDataTableMapFromIndex($index, $resultIndices, $keyMetadata); + } + + return $dataTable; + } + + /** + * Creates a merged DataTable|Map instance using an index of archive data similar to {@link make()}. + * + * Whereas {@link make()} creates a Map for each result index (period and|or site), this will only create a Map + * for a period result index and move all site related indices into one dataTable. This is the same as doing + * `$dataTableFactory->make()->mergeChildren()` just much faster. It is mainly useful for reports across many sites + * eg `MultiSites.getAll`. Was done as part of https://github.com/piwik/piwik/issues/6809 + * + * @param array $index @see DataCollection + * @param array $resultIndices an array mapping metadata names with pretty metadata labels. + * + * @return DataTable|DataTable\Map + * @throws \Exception + */ + public function makeMerged($index, $resultIndices) + { + if (!$this->isNumericDataType()) { + throw new \Exception('This method is supposed to work with non-numeric data types but it is not tested. To use it, remove this exception and write tests to be sure it works.'); + } + + $hasSiteIndex = isset($resultIndices[self::TABLE_METADATA_SITE_INDEX]); + $hasPeriodIndex = isset($resultIndices[self::TABLE_METADATA_PERIOD_INDEX]); + + $isNumeric = $this->isNumericDataType(); + // to be backwards compatible use a Simple table if needed as it will be formatted differently + $useSimpleDataTable = !$hasSiteIndex && $isNumeric; + + if (!$hasSiteIndex) { + $firstIdSite = reset($this->sitesId); + $index = array($firstIdSite => $index); + } + + if ($hasPeriodIndex) { + $dataTable = $this->makeMergedTableWithPeriodAndSiteIndex($index, $resultIndices, $useSimpleDataTable, $isNumeric); + } else { + $dataTable = $this->makeMergedWithSiteIndex($index, $useSimpleDataTable, $isNumeric); } - $this->transformMetadata($dataTable); return $dataTable; } @@ -171,16 +235,16 @@ class DataTableFactory * @param array $blobRow * @return DataTable|DataTable\Map */ - private function makeFromBlobRow($blobRow) + private function makeFromBlobRow($blobRow, $keyMetadata) { if ($blobRow === false) { return new DataTable(); } if (count($this->dataNames) === 1) { - return $this->makeDataTableFromSingleBlob($blobRow); + return $this->makeDataTableFromSingleBlob($blobRow, $keyMetadata); } else { - return $this->makeIndexedByRecordNameDataTable($blobRow); + return $this->makeIndexedByRecordNameDataTable($blobRow, $keyMetadata); } } @@ -192,7 +256,7 @@ class DataTableFactory * @param array $blobRow * @return DataTable */ - private function makeDataTableFromSingleBlob($blobRow) + private function makeDataTableFromSingleBlob($blobRow, $keyMetadata) { $recordName = reset($this->dataNames); if ($this->idSubtable !== null) { @@ -206,7 +270,7 @@ class DataTableFactory } // set table metadata - $table->setMetadataValues(DataCollection::getDataRowMetadata($blobRow)); + $table->setAllTableMetadata(array_merge(DataCollection::getDataRowMetadata($blobRow), $keyMetadata)); if ($this->expandDataTable) { $table->enableRecursiveFilters(); @@ -223,12 +287,12 @@ class DataTableFactory * @param array $blobRow * @return DataTable\Map */ - private function makeIndexedByRecordNameDataTable($blobRow) + private function makeIndexedByRecordNameDataTable($blobRow, $keyMetadata) { $table = new DataTable\Map(); $table->setKeyName('recordName'); - $tableMetadata = DataCollection::getDataRowMetadata($blobRow); + $tableMetadata = array_merge(DataCollection::getDataRowMetadata($blobRow), $keyMetadata); foreach ($blobRow as $name => $blob) { $newTable = DataTable::fromSerializedArray($blob); @@ -248,23 +312,23 @@ class DataTableFactory * @param array $keyMetadata The metadata to add to the table when it's created. * @return DataTable\Map */ - private function createDataTableMapFromIndex($index, $resultIndices, $keyMetadata = array()) + private function createDataTableMapFromIndex($index, $resultIndices, $keyMetadata) { - $resultIndexLabel = reset($resultIndices); + $result = new DataTable\Map(); + $result->setKeyName(reset($resultIndices)); $resultIndex = key($resultIndices); array_shift($resultIndices); - $result = new DataTable\Map(); - $result->setKeyName($resultIndexLabel); + $hasIndices = !empty($resultIndices); foreach ($index as $label => $value) { - $keyMetadata[$resultIndex] = $label; + $keyMetadata[$resultIndex] = $this->createTableIndexMetadata($resultIndex, $label); - if (empty($resultIndices)) { - $newTable = $this->createDataTable($value, $keyMetadata); - } else { + if ($hasIndices) { $newTable = $this->createDataTableMapFromIndex($value, $resultIndices, $keyMetadata); + } else { + $newTable = $this->createDataTable($value, $keyMetadata); } $result->addTable($newTable, $this->prettifyIndexLabel($resultIndex, $label)); @@ -273,6 +337,15 @@ class DataTableFactory return $result; } + private function createTableIndexMetadata($resultIndex, $label) + { + if ($resultIndex === DataTableFactory::TABLE_METADATA_SITE_INDEX) { + return new Site($label); + } elseif ($resultIndex === DataTableFactory::TABLE_METADATA_PERIOD_INDEX) { + return $this->periods[$label]; + } + } + /** * Creates a DataTable instance from an index row. * @@ -283,11 +356,11 @@ class DataTableFactory private function createDataTable($data, $keyMetadata) { if ($this->dataType == 'blob') { - $result = $this->makeFromBlobRow($data); + $result = $this->makeFromBlobRow($data, $keyMetadata); } else { - $result = $this->makeFromMetricsArray($data); + $result = $this->makeFromMetricsArray($data, $keyMetadata); } - $this->setTableMetadata($keyMetadata, $result); + return $result; } @@ -307,7 +380,7 @@ class DataTableFactory && $treeLevel >= $this->maxSubtableDepth ) { // unset the subtables so DataTableManager doesn't throw - foreach ($dataTable->getRows() as $row) { + foreach ($dataTable->getRowsWithoutSummaryRow() as $row) { $row->removeSubtable(); } @@ -316,7 +389,7 @@ class DataTableFactory $dataName = reset($this->dataNames); - foreach ($dataTable->getRows() as $row) { + foreach ($dataTable->getRowsWithoutSummaryRow() as $row) { $sid = $row->getIdSubDataTable(); if ($sid === null) { continue; @@ -340,17 +413,12 @@ class DataTableFactory } } - /** - * Converts site IDs and period string ranges into Site instances and - * Period instances in DataTable metadata. - */ - private function transformMetadata($table) + private function getDefaultMetadata() { - $periods = $this->periods; - $table->filter(function ($table) use ($periods) { - $table->setMetadata(DataTableFactory::TABLE_METADATA_SITE_INDEX, new Site($table->getMetadata(DataTableFactory::TABLE_METADATA_SITE_INDEX))); - $table->setMetadata(DataTableFactory::TABLE_METADATA_PERIOD_INDEX, $periods[$table->getMetadata(DataTableFactory::TABLE_METADATA_PERIOD_INDEX)]); - }); + return array( + DataTableFactory::TABLE_METADATA_SITE_INDEX => new Site(reset($this->sitesId)), + DataTableFactory::TABLE_METADATA_PERIOD_INDEX => reset($this->periods), + ); } /** @@ -368,39 +436,16 @@ class DataTableFactory return $label; } - /** - * @param $keyMetadata - * @param $result - */ - private function setTableMetadata($keyMetadata, $result) - { - if (!isset($keyMetadata[DataTableFactory::TABLE_METADATA_SITE_INDEX])) { - $keyMetadata[DataTableFactory::TABLE_METADATA_SITE_INDEX] = reset($this->sitesId); - } - - if (!isset($keyMetadata[DataTableFactory::TABLE_METADATA_PERIOD_INDEX])) { - reset($this->periods); - $keyMetadata[DataTableFactory::TABLE_METADATA_PERIOD_INDEX] = key($this->periods); - } - - // Note: $result can be a DataTable\Map - $result->filter(function ($table) use ($keyMetadata) { - foreach ($keyMetadata as $name => $value) { - $table->setMetadata($name, $value); - } - }); - } - /** * @param $data * @return DataTable\Simple */ - private function makeFromMetricsArray($data) + private function makeFromMetricsArray($data, $keyMetadata) { $table = new DataTable\Simple(); if (!empty($data)) { - $table->setAllTableMetadata(DataCollection::getDataRowMetadata($data)); + $table->setAllTableMetadata(array_merge(DataCollection::getDataRowMetadata($data), $keyMetadata)); DataCollection::removeMetadataFromDataRow($data); @@ -412,15 +457,89 @@ class DataTableFactory // w/o this code, an empty array would be created, and other parts of Piwik // would break. if (count($this->dataNames) == 1 - && $this->dataType == 'numeric' + && $this->isNumericDataType() ) { $name = reset($this->dataNames); $table->addRow(new Row(array(Row::COLUMNS => array($name => 0)))); } + + $table->setAllTableMetadata($keyMetadata); } $result = $table; return $result; } -} + private function makeMergedTableWithPeriodAndSiteIndex($index, $resultIndices, $useSimpleDataTable, $isNumeric) + { + $map = new DataTable\Map(); + $map->setKeyName($resultIndices[self::TABLE_METADATA_PERIOD_INDEX]); + + // we save all tables of the map in this array to be able to add rows fast + $tables = array(); + + foreach ($this->periods as $range => $period) { + // as the resulting table is "merged", we do only set Period metedata and no metadata for site. Instead each + // row will have an idsite metadata entry. + $metadata = array(self::TABLE_METADATA_PERIOD_INDEX => $period); + + if ($useSimpleDataTable) { + $table = new DataTable\Simple(); + } else { + $table = new DataTable(); + } + + $table->setAllTableMetadata($metadata); + $map->addTable($table, $this->prettifyIndexLabel(self::TABLE_METADATA_PERIOD_INDEX, $range)); + + $tables[$range] = $table; + } + + foreach ($index as $idsite => $table) { + $rowMeta = array('idsite' => $idsite); + + foreach ($table as $range => $row) { + if (!empty($row)) { + $tables[$range]->addRow(new Row(array( + Row::COLUMNS => $row, + Row::METADATA => $rowMeta) + )); + } elseif ($isNumeric) { + $tables[$range]->addRow(new Row(array( + Row::COLUMNS => $this->defaultRow, + Row::METADATA => $rowMeta) + )); + } + } + } + + return $map; + } + + private function makeMergedWithSiteIndex($index, $useSimpleDataTable, $isNumeric) + { + if ($useSimpleDataTable) { + $table = new DataTable\Simple(); + } else { + $table = new DataTable(); + } + + $table->setAllTableMetadata(array(DataTableFactory::TABLE_METADATA_PERIOD_INDEX => reset($this->periods))); + + foreach ($index as $idsite => $row) { + if (!empty($row)) { + $table->addRow(new Row(array( + Row::COLUMNS => $row, + Row::METADATA => array('idsite' => $idsite)) + )); + } elseif ($isNumeric) { + $table->addRow(new Row(array( + Row::COLUMNS => $this->defaultRow, + Row::METADATA => array('idsite' => $idsite)) + )); + } + } + + return $table; + } +} diff --git a/www/analytics/core/Archive/Parameters.php b/www/analytics/core/Archive/Parameters.php index 1f700819..905166de 100644 --- a/www/analytics/core/Archive/Parameters.php +++ b/www/analytics/core/Archive/Parameters.php @@ -1,6 +1,6 @@ segment; } - public function __construct($idSites, $periods, Segment $segment, $skipAggregationOfSubTables) + public function __construct($idSites, $periods, Segment $segment) { $this->idSites = $idSites; $this->periods = $periods; $this->segment = $segment; - $this->skipAggregationOfSubTables = $skipAggregationOfSubTables; } public function getPeriods() @@ -63,11 +56,4 @@ class Parameters { return $this->idSites; } - - public function isSkipAggregationOfSubTables() - { - return $this->skipAggregationOfSubTables; - } - } - diff --git a/www/analytics/core/ArchiveProcessor.php b/www/analytics/core/ArchiveProcessor.php index 20c4c2df..f24b4993 100644 --- a/www/analytics/core/ArchiveProcessor.php +++ b/www/analytics/core/ArchiveProcessor.php @@ -1,6 +1,6 @@ getProcessor(); - * + * * $myFancyMetric = // ... calculate the metric value ... * $archiveProcessor->insertNumericRecord('MyPlugin_myFancyMetric', $myFancyMetric); * } - * + * * **Inserting serialized DataTables** - * + * * // function in an Archiver descendant * public function aggregateDayReport() * { * $archiveProcessor = $this->getProcessor(); - * + * * $maxRowsInTable = Config::getInstance()->General['datatable_archiving_maximum_rows_standard'];j - * + * * $dataTable = // ... build by aggregating visits ... * $serializedData = $dataTable->getSerialized($maxRowsInTable, $maxRowsInSubtable = $maxRowsInTable, * $columnToSortBy = Metrics::INDEX_NB_VISITS); - * + * * $archiveProcessor->insertBlobRecords('MyPlugin_myFancyReport', $serializedData); * } - * + * * **Aggregating archive data** - * + * * // function in Archiver descendant * public function aggregateMultipleReports() * { * $archiveProcessor = $this->getProcessor(); - * + * * // aggregate a metric * $archiveProcessor->aggregateNumericMetrics('MyPlugin_myFancyMetric'); * $archiveProcessor->aggregateNumericMetrics('MyPlugin_mySuperFancyMetric', 'max'); - * - * // aggregate a report + * + * // aggregate a report * $archiveProcessor->aggregateDataTableRecords('MyPlugin_myFancyReport'); * } - * + * */ class ArchiveProcessor { /** * @var \Piwik\DataAccess\ArchiveWriter */ - protected $archiveWriter; + private $archiveWriter; /** * @var \Piwik\DataAccess\LogAggregator */ - protected $logAggregator; + private $logAggregator; /** * @var Archive @@ -94,28 +92,41 @@ class ArchiveProcessor /** * @var Parameters */ - protected $params; + private $params; /** * @var int */ - protected $numberOfVisits = false; - protected $numberOfVisitsConverted = false; + private $numberOfVisits = false; - public function __construct(Parameters $params, ArchiveWriter $archiveWriter) + private $numberOfVisitsConverted = false; + + /** + * If true, unique visitors are not calculated when we are aggregating data for multiple sites. + * The `[General] enable_processing_unique_visitors_multiple_sites` INI config option controls + * the value of this variable. + * + * @var bool + */ + private $skipUniqueVisitorsCalculationForMultipleSites = true; + + public function __construct(Parameters $params, ArchiveWriter $archiveWriter, LogAggregator $logAggregator) { $this->params = $params; - $this->logAggregator = new LogAggregator($params); + $this->logAggregator = $logAggregator; $this->archiveWriter = $archiveWriter; + + $this->skipUniqueVisitorsCalculationForMultipleSites = Rules::shouldSkipUniqueVisitorsCalculationForMultipleSites(); } protected function getArchive() { - if(empty($this->archive)) { + if (empty($this->archive)) { $subPeriods = $this->params->getSubPeriods(); - $idSites = $this->params->getIdSites(); + $idSites = $this->params->getIdSites(); $this->archive = Archive::factory($this->params->getSegment(), $subPeriods, $idSites); } + return $this->archive; } @@ -155,7 +166,8 @@ class ArchiveProcessor * @var array */ protected static $columnsToRenameAfterAggregation = array( - Metrics::INDEX_NB_UNIQ_VISITORS => Metrics::INDEX_SUM_DAILY_NB_UNIQ_VISITORS + Metrics::INDEX_NB_UNIQ_VISITORS => Metrics::INDEX_SUM_DAILY_NB_UNIQ_VISITORS, + Metrics::INDEX_NB_USERS => Metrics::INDEX_SUM_DAILY_NB_USERS, ); /** @@ -172,8 +184,11 @@ class ArchiveProcessor * @param array $columnsToRenameAfterAggregation Columns mapped to new names for columns that must change names * when summed because they cannot be summed, eg, * `array('nb_uniq_visitors' => 'sum_daily_nb_uniq_visitors')`. + * @param bool|array $countRowsRecursive if set to true, will calculate the recursive rows count for all record names + * which makes it slower. If you only need it for some records pass an array of + * recordNames that defines for which ones you need a recursive row count. * @return array Returns the row counts of each aggregated report before truncation, eg, - * + * * array( * 'report1' => array('level0' => $report1->getRowsCount, * 'recursive' => $report1->getRowsCountRecursive()), @@ -188,25 +203,23 @@ class ArchiveProcessor $maximumRowsInSubDataTable = null, $columnToSortByBeforeTruncation = null, &$columnsAggregationOperation = null, - $columnsToRenameAfterAggregation = null) + $columnsToRenameAfterAggregation = null, + $countRowsRecursive = true) { if (!is_array($recordNames)) { $recordNames = array($recordNames); } + $nameToCount = array(); foreach ($recordNames as $recordName) { $latestUsedTableId = Manager::getInstance()->getMostRecentTableId(); $table = $this->aggregateDataTableRecord($recordName, $columnsAggregationOperation, $columnsToRenameAfterAggregation); - $rowsCount = $table->getRowsCount(); - $nameToCount[$recordName]['level0'] = $rowsCount; - - $rowsCountRecursive = $rowsCount; - if($this->isAggregateSubTables()) { - $rowsCountRecursive = $table->getRowsCountRecursive(); + $nameToCount[$recordName]['level0'] = $table->getRowsCount(); + if ($countRowsRecursive === true || (is_array($countRowsRecursive) && in_array($recordName, $countRowsRecursive))) { + $nameToCount[$recordName]['recursive'] = $table->getRowsCountRecursive(); } - $nameToCount[$recordName]['recursive'] = $rowsCountRecursive; $blob = $table->getSerialized($maximumRowsInDataTableLevelZero, $maximumRowsInSubDataTable, $columnToSortByBeforeTruncation); Common::destroy($table); @@ -228,12 +241,12 @@ class ArchiveProcessor * @return array|int Returns the array of aggregate values. If only one metric was aggregated, * the aggregate value will be returned as is, not in an array. * For example, if `array('nb_visits', 'nb_hits')` is supplied for `$columns`, - * + * * array( * 'nb_visits' => 3040, * 'nb_hits' => 405 * ) - * + * * could be returned. If `array('nb_visits')` or `'nb_visits'` is used for `$columns`, * then `3040` would be returned. * @api @@ -242,7 +255,8 @@ class ArchiveProcessor { $metrics = $this->getAggregatedNumericMetrics($columns, $operationToApply); - foreach($metrics as $column => $value) { + foreach ($metrics as $column => $value) { + $value = Common::forceDotAsSeparatorForDecimalPoint($value); $this->archiveWriter->insertRecord($column, $value); } // if asked for only one field to sum @@ -256,7 +270,7 @@ class ArchiveProcessor public function getNumberOfVisits() { - if($this->numberOfVisits === false) { + if ($this->numberOfVisits === false) { throw new Exception("visits should have been set here"); } return $this->numberOfVisits; @@ -273,7 +287,7 @@ class ArchiveProcessor * * @param array $numericRecords A name-value mapping of numeric values that should be * archived, eg, - * + * * array('Referrers_distinctKeywords' => 23, 'Referrers_distinctCampaigns' => 234) * @api */ @@ -297,6 +311,8 @@ class ArchiveProcessor public function insertNumericRecord($name, $value) { $value = round($value, 2); + $value = Common::forceDotAsSeparatorForDecimalPoint($value); + $this->archiveWriter->insertRecord($name, $value); } @@ -328,20 +344,53 @@ class ArchiveProcessor */ protected function aggregateDataTableRecord($name, $columnsAggregationOperation = null, $columnsToRenameAfterAggregation = null) { - if($this->isAggregateSubTables()) { - // By default we shall aggregate all sub-tables. - $dataTable = $this->getArchive()->getDataTableExpanded($name, $idSubTable = null, $depth = null, $addMetadataSubtableId = false); - } else { - // In some cases (eg. Actions plugin when period=range), - // for better performance we will only aggregate the parent table - $dataTable = $this->getArchive()->getDataTable($name, $idSubTable = null); + // By default we shall aggregate all sub-tables. + $dataTable = $this->getArchive()->getDataTableExpanded($name, $idSubTable = null, $depth = null, $addMetadataSubtableId = false); + + $columnsRenamed = false; + + if ($dataTable instanceof Map) { + $columnsRenamed = true; + // see https://github.com/piwik/piwik/issues/4377 + $self = $this; + $dataTable->filter(function ($table) use ($self, $columnsToRenameAfterAggregation) { + + if ($self->areColumnsNotAlreadyRenamed($table)) { + /** + * This makes archiving and range dates a lot faster. Imagine we archive a week, then we will + * rename all columns of each 7 day archives. Afterwards we know the columns will be replaced in a + * week archive. When generating month archives, which uses mostly week archives, we do not have + * to replace those columns for the week archives again since we can be sure they were already + * replaced. Same when aggregating year and range archives. This can save up 10% or more when + * aggregating Month, Year and Range archives. + */ + $self->renameColumnsAfterAggregation($table, $columnsToRenameAfterAggregation); + } + }); } $dataTable = $this->getAggregatedDataTableMap($dataTable, $columnsAggregationOperation); - $this->renameColumnsAfterAggregation($dataTable, $columnsToRenameAfterAggregation); + + if (!$columnsRenamed) { + $this->renameColumnsAfterAggregation($dataTable, $columnsToRenameAfterAggregation); + } + return $dataTable; } + /** + * Note: public only for use in closure in PHP 5.3. + * + * @param $table + * @return \Piwik\Period + */ + public function areColumnsNotAlreadyRenamed($table) + { + $period = $table->getMetadata(DataTableFactory::TABLE_METADATA_PERIOD_INDEX); + + return !$period || $period->getLabel() === 'day'; + } + protected function getOperationForColumns($columns, $defaultOperation) { $operationForColumn = array(); @@ -357,18 +406,54 @@ class ArchiveProcessor protected function enrichWithUniqueVisitorsMetric(Row $row) { - if(!$this->getParams()->isSingleSite() ) { - // we only compute unique visitors for a single site + // skip unique visitors metrics calculation if calculating for multiple sites is disabled + if (!$this->getParams()->isSingleSite() + && $this->skipUniqueVisitorsCalculationForMultipleSites + ) { return; } - if ( $row->getColumn('nb_uniq_visitors') !== false) { - if (SettingsPiwik::isUniqueVisitorsEnabled($this->getParams()->getPeriod()->getLabel())) { - $uniqueVisitors = (float)$this->computeNbUniqVisitors(); - $row->setColumn('nb_uniq_visitors', $uniqueVisitors); - } else { - $row->deleteColumn('nb_uniq_visitors'); - } + + if ($row->getColumn('nb_uniq_visitors') === false + && $row->getColumn('nb_users') === false + ) { + return; } + + if (!SettingsPiwik::isUniqueVisitorsEnabled($this->getParams()->getPeriod()->getLabel())) { + $row->deleteColumn('nb_uniq_visitors'); + $row->deleteColumn('nb_users'); + return; + } + + $metrics = array( + Metrics::INDEX_NB_USERS + ); + + if ($this->getParams()->isSingleSite()) { + $uniqueVisitorsMetric = Metrics::INDEX_NB_UNIQ_VISITORS; + } else { + if (!SettingsPiwik::isSameFingerprintAcrossWebsites()) { + throw new Exception("Processing unique visitors across websites is enabled for this instance, + but to process this metric you must first set enable_fingerprinting_across_websites=1 + in the config file, under the [Tracker] section."); + } + $uniqueVisitorsMetric = Metrics::INDEX_NB_UNIQ_FINGERPRINTS; + } + $metrics[] = $uniqueVisitorsMetric; + + $uniques = $this->computeNbUniques($metrics); + + // see edge case as described in https://github.com/piwik/piwik/issues/9357 where uniq_visitors might be higher + // than visits because we archive / process it after nb_visits. Between archiving nb_visits and nb_uniq_visitors + // there could have been a new visit leading to a higher nb_unique_visitors than nb_visits which is not possible + // by definition. In this case we simply use the visits metric instead of unique visitors metric. + $visits = $row->getColumn('nb_visits'); + if ($visits !== false && $uniques[$uniqueVisitorsMetric] !== false) { + $uniques[$uniqueVisitorsMetric] = min($uniques[$uniqueVisitorsMetric], $visits); + } + + $row->setColumn('nb_uniq_visitors', $uniques[$uniqueVisitorsMetric]); + $row->setColumn('nb_users', $uniques[Metrics::INDEX_NB_USERS]); } protected function guessOperationForColumn($column) @@ -388,14 +473,15 @@ class ArchiveProcessor * This is the only Period metric (ie. week/month/year/range) that we process from the logs directly, * since unique visitors cannot be summed like other metrics. * - * @return int + * @param array Metrics Ids for which to aggregates count of values + * @return array of metrics, where the key is metricid and the value is the metric value */ - protected function computeNbUniqVisitors() + protected function computeNbUniques($metrics) { $logAggregator = $this->getLogAggregator(); - $query = $logAggregator->queryVisitsByDimension(array(), false, array(), array(Metrics::INDEX_NB_UNIQ_VISITORS)); + $query = $logAggregator->queryVisitsByDimension(array(), false, array(), $metrics); $data = $query->fetch(); - return $data[Metrics::INDEX_NB_UNIQ_VISITORS]; + return $data; } /** @@ -409,15 +495,18 @@ class ArchiveProcessor protected function getAggregatedDataTableMap($data, $columnsAggregationOperation) { $table = new DataTable(); + if (!empty($columnsAggregationOperation)) { $table->setMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME, $columnsAggregationOperation); } + if ($data instanceof DataTable\Map) { // as $date => $tableToSum $this->aggregatedDataTableMapsAsOne($data, $table); } else { - $table->addDataTable($data, $this->isAggregateSubTables()); + $table->addDataTable($data); } + return $table; } @@ -429,22 +518,33 @@ class ArchiveProcessor protected function aggregatedDataTableMapsAsOne(Map $map, DataTable $aggregated) { foreach ($map->getDataTables() as $tableToAggregate) { - if($tableToAggregate instanceof Map) { + if ($tableToAggregate instanceof Map) { $this->aggregatedDataTableMapsAsOne($tableToAggregate, $aggregated); } else { - $aggregated->addDataTable($tableToAggregate, $this->isAggregateSubTables()); + $aggregated->addDataTable($tableToAggregate); } } } - protected function renameColumnsAfterAggregation(DataTable $table, $columnsToRenameAfterAggregation = null) + /** + * Note: public only for use in closure in PHP 5.3. + */ + public function renameColumnsAfterAggregation(DataTable $table, $columnsToRenameAfterAggregation = null) { // Rename columns after aggregation if (is_null($columnsToRenameAfterAggregation)) { $columnsToRenameAfterAggregation = self::$columnsToRenameAfterAggregation; } - foreach ($columnsToRenameAfterAggregation as $oldName => $newName) { - $table->renameColumn($oldName, $newName, $this->isAggregateSubTables()); + + foreach ($table->getRows() as $row) { + foreach ($columnsToRenameAfterAggregation as $oldName => $newName) { + $row->renameColumn($oldName, $newName); + } + + $subTable = $row->getSubtable(); + if ($subTable) { + $this->renameColumnsAfterAggregation($subTable, $columnsToRenameAfterAggregation); + } } } @@ -453,6 +553,7 @@ class ArchiveProcessor if (!is_array($columns)) { $columns = array($columns); } + $operationForColumn = $this->getOperationForColumns($columns, $operationToApply); $dataTable = $this->getArchive()->getDataTableFromNumeric($columns); @@ -463,11 +564,11 @@ class ArchiveProcessor } $rowMetrics = $results->getFirstRow(); - if($rowMetrics === false) { + if ($rowMetrics === false) { $rowMetrics = new Row; } $this->enrichWithUniqueVisitorsMetric($rowMetrics); - $this->renameColumnsAfterAggregation($results); + $this->renameColumnsAfterAggregation($results, self::$columnsToRenameAfterAggregation); $metrics = $rowMetrics->getColumns(); @@ -476,14 +577,7 @@ class ArchiveProcessor $metrics[$name] = 0; } } + return $metrics; } - - /** - * @return bool - */ - protected function isAggregateSubTables() - { - return !$this->getParams()->isSkipAggregationOfSubTables(); - } } diff --git a/www/analytics/core/ArchiveProcessor/Loader.php b/www/analytics/core/ArchiveProcessor/Loader.php index 03a054a6..c08bf2c5 100644 --- a/www/analytics/core/ArchiveProcessor/Loader.php +++ b/www/analytics/core/ArchiveProcessor/Loader.php @@ -1,18 +1,20 @@ params, $this->isArchiveTemporary()); + if ($this->mustProcessVisitCount($visits) || $this->doesRequestedPluginIncludeVisitsSummary() ) { @@ -114,14 +119,14 @@ class Loader $visits = $metrics['nb_visits']; $visitsConverted = $metrics['nb_visits_converted']; } - if ($this->isThereSomeVisits($visits)) { + + if ($this->isThereSomeVisits($visits) + || $this->shouldArchiveForSiteEvenWhenNoVisits() + ) { $pluginsArchiver->callAggregateAllPlugins($visits, $visitsConverted); } - $idArchive = $pluginsArchiver->finalizeArchive(); - if (!$this->params->isSingleSiteDayArchive() && $visits) { - ArchiveSelector::purgeOutdatedArchives($this->params->getPeriod()->getDateStart()); - } + $idArchive = $pluginsArchiver->finalizeArchive(); return array($idArchive, $visits); } @@ -139,11 +144,13 @@ class Loader { $period = $this->params->getPeriod()->getLabel(); $debugSetting = 'always_archive_data_period'; // default + if ($period == 'day') { $debugSetting = 'always_archive_data_day'; } elseif ($period == 'range') { $debugSetting = 'always_archive_data_range'; } + return (bool) Config::getInstance()->Debug[$debugSetting]; } @@ -165,9 +172,11 @@ class Loader } $idAndVisits = ArchiveSelector::getArchiveIdAndVisits($this->params, $minDatetimeArchiveProcessedUTC); + if (!$idAndVisits) { return $noArchiveFound; } + return $idAndVisits; } @@ -186,19 +195,27 @@ class Loader // Permanent archive return $endDateTimestamp; } + + $dateStart = $this->params->getDateStart(); + $period = $this->params->getPeriod(); + $segment = $this->params->getSegment(); + $site = $this->params->getSite(); + // Temporary archive - return Rules::getMinTimeProcessedForTemporaryArchive($this->params->getDateStart(), $this->params->getPeriod(), $this->params->getSegment(), $this->params->getSite()); + return Rules::getMinTimeProcessedForTemporaryArchive($dateStart, $period, $segment, $site); } protected static function determineIfArchivePermanent(Date $dateEnd) { $now = time(); $endTimestampUTC = strtotime($dateEnd->getDateEndUTC()); + if ($endTimestampUTC <= $now) { // - if the period we are looking for is finished, we look for a ts_archived that // is greater than the last day of the archive return $endTimestampUTC; } + return false; } @@ -207,8 +224,30 @@ class Loader if (is_null($this->temporaryArchive)) { throw new \Exception("getMinTimeArchiveProcessed() should be called prior to isArchiveTemporary()"); } + return $this->temporaryArchive; } -} + private function shouldArchiveForSiteEvenWhenNoVisits() + { + $idSitesToArchive = $this->getIdSitesToArchiveWhenNoVisits(); + return in_array($this->params->getSite()->getId(), $idSitesToArchive); + } + private function getIdSitesToArchiveWhenNoVisits() + { + $cache = Cache::getTransientCache(); + $cacheKey = 'Archiving.getIdSitesToArchiveWhenNoVisits'; + + if (!$cache->contains($cacheKey)) { + $idSites = array(); + + // leaving undocumented unless decided otherwise + Piwik::postEvent('Archiving.getIdSitesToArchiveWhenNoVisits', array(&$idSites)); + + $cache->save($cacheKey, $idSites); + } + + return $cache->fetch($cacheKey); + } +} diff --git a/www/analytics/core/ArchiveProcessor/Parameters.php b/www/analytics/core/ArchiveProcessor/Parameters.php index 9d9a9088..4edd1f4b 100644 --- a/www/analytics/core/ArchiveProcessor/Parameters.php +++ b/www/analytics/core/ArchiveProcessor/Parameters.php @@ -1,6 +1,6 @@ site = $site; $this->period = $period; $this->segment = $segment; - $this->skipAggregationOfSubTables = $skipAggregationOfSubTables; } /** @@ -91,7 +90,7 @@ class Parameters */ public function getSubPeriods() { - if($this->getPeriod()->getLabel() == 'day') { + if ($this->getPeriod()->getLabel() == 'day') { return array( $this->getPeriod() ); } return $this->getPeriod()->getSubperiods(); @@ -169,18 +168,13 @@ class Parameters return count($this->getIdSites()) == 1; } - public function isSkipAggregationOfSubTables() - { - return $this->skipAggregationOfSubTables; - } - public function logStatusDebug($isTemporary) { $temporary = 'definitive archive'; if ($isTemporary) { $temporary = 'temporary archive'; } - Log::verbose( + Log::debug( "%s archive, idSite = %d (%s), segment '%s', report = '%s', UTC datetime [%s -> %s]", $this->getPeriod()->getLabel(), $this->getSite()->getId(), diff --git a/www/analytics/core/ArchiveProcessor/PluginsArchiver.php b/www/analytics/core/ArchiveProcessor/PluginsArchiver.php index 82f361f7..28c90cb1 100644 --- a/www/analytics/core/ArchiveProcessor/PluginsArchiver.php +++ b/www/analytics/core/ArchiveProcessor/PluginsArchiver.php @@ -1,6 +1,6 @@ archiveWriter = new ArchiveWriter($this->params, $isTemporaryArchive); $this->archiveWriter->initNewArchive(); - $this->archiveProcessor = new ArchiveProcessor($this->params, $this->archiveWriter); + $this->logAggregator = new LogAggregator($params); + + $this->archiveProcessor = new ArchiveProcessor($this->params, $this->archiveWriter, $this->logAggregator); $this->isSingleSiteDayArchive = $this->params->isSingleSiteDayArchive(); } @@ -57,7 +68,9 @@ class PluginsArchiver */ public function callAggregateCoreMetrics() { - if($this->isSingleSiteDayArchive) { + $this->logAggregator->setQueryOriginHint('Core'); + + if ($this->isSingleSiteDayArchive) { $metrics = $this->aggregateDayVisitsMetrics(); } else { $metrics = $this->aggregateMultipleVisitsMetrics(); @@ -81,27 +94,57 @@ class PluginsArchiver */ public function callAggregateAllPlugins($visits, $visitsConverted) { + Log::debug("PluginsArchiver::%s: Initializing archiving process for all plugins [visits = %s, visits converted = %s]", + __FUNCTION__, $visits, $visitsConverted); + $this->archiveProcessor->setNumberOfVisits($visits, $visitsConverted); $archivers = $this->getPluginArchivers(); - foreach($archivers as $pluginName => $archiverClass) { - + foreach ($archivers as $pluginName => $archiverClass) { // We clean up below all tables created during this function call (and recursive calls) $latestUsedTableId = Manager::getInstance()->getMostRecentTableId(); /** @var Archiver $archiver */ $archiver = new $archiverClass($this->archiveProcessor); - if(!$archiver->isEnabled()) { + if (!$archiver->isEnabled()) { + Log::debug("PluginsArchiver::%s: Skipping archiving for plugin '%s'.", __FUNCTION__, $pluginName); continue; } - if($this->shouldProcessReportsForPlugin($pluginName)) { - if($this->isSingleSiteDayArchive) { - $archiver->aggregateDayReport(); - } else { - $archiver->aggregateMultipleReports(); + + if ($this->shouldProcessReportsForPlugin($pluginName)) { + + $this->logAggregator->setQueryOriginHint($pluginName); + + try { + $timer = new Timer(); + if ($this->isSingleSiteDayArchive) { + Log::debug("PluginsArchiver::%s: Archiving day reports for plugin '%s'.", __FUNCTION__, $pluginName); + + $archiver->aggregateDayReport(); + } else { + Log::debug("PluginsArchiver::%s: Archiving period reports for plugin '%s'.", __FUNCTION__, $pluginName); + + $archiver->aggregateMultipleReports(); + } + + $this->logAggregator->setQueryOriginHint(''); + + Log::debug("PluginsArchiver::%s: %s while archiving %s reports for plugin '%s'.", + __FUNCTION__, + $timer->getMemoryLeak(), + $this->params->getPeriod()->getLabel(), + $pluginName + ); + } catch (Exception $e) { + $className = get_class($e); + $exception = new $className($e->getMessage() . " - caused by plugin $pluginName", $e->getCode(), $e); + + throw $exception; } + } else { + Log::debug("PluginsArchiver::%s: Not archiving reports for plugin '%s'.", __FUNCTION__, $pluginName); } Manager::getInstance()->deleteAll($latestUsedTableId); @@ -111,7 +154,7 @@ class PluginsArchiver public function finalizeArchive() { - $this->params->logStatusDebug( $this->archiveWriter->isArchiveTemporary ); + $this->params->logStatusDebug($this->archiveWriter->isArchiveTemporary); $this->archiveWriter->finalizeArchive(); return $this->archiveWriter->getIdArchive(); } @@ -193,5 +236,4 @@ class PluginsArchiver $metrics = $this->archiveProcessor->aggregateNumericMetrics($toSum); return $metrics; } - } diff --git a/www/analytics/core/ArchiveProcessor/Rules.php b/www/analytics/core/ArchiveProcessor/Rules.php index 6bcd5fbd..0ec92f75 100644 --- a/www/analytics/core/ArchiveProcessor/Rules.php +++ b/www/analytics/core/ArchiveProcessor/Rules.php @@ -1,6 +1,6 @@ getHash() . '.' . $plugin . $partial ; + return 'done' . $segment->getHash() . '.' . $plugin ; } - private static function getDoneFlagArchiveContainsAllPlugins(Segment $segment) + public static function getDoneFlagArchiveContainsAllPlugins(Segment $segment) { return 'done' . $segment->getHash(); } /** - * @param $plugin - * @param $isSkipAggregationOfSubTables - * @return string - */ - private static function isFlagArchivePartial($plugin, $isSkipAggregationOfSubTables) - { - $partialArchive = ''; - if ($plugin != "VisitsSummary" // VisitsSummary is always called when segmenting and should not have its own .partial archive - && $isSkipAggregationOfSubTables - ) { - $partialArchive = '.partial'; - } - return $partialArchive; - } - - /** + * Return done flags used to tell how the archiving process for a specific archive was completed, + * * @param array $plugins * @param $segment * @return array */ - public static function getDoneFlags(array $plugins, Segment $segment, $isSkipAggregationOfSubTables) + public static function getDoneFlags(array $plugins, Segment $segment) { $doneFlags = array(); $doneAllPlugins = self::getDoneFlagArchiveContainsAllPlugins($segment); @@ -124,58 +106,12 @@ class Rules $plugins = array_unique($plugins); foreach ($plugins as $plugin) { - $doneOnePlugin = self::getDoneFlagArchiveContainsOnePlugin($segment, $plugin, $isSkipAggregationOfSubTables); + $doneOnePlugin = self::getDoneFlagArchiveContainsOnePlugin($segment, $plugin); $doneFlags[$plugin] = $doneOnePlugin; } return $doneFlags; } - /** - * Given a monthly archive table, will delete all reports that are now outdated, - * or reports that ended with an error - * - * @param \Piwik\Date $date - * @return int|bool False, or timestamp indicating which archives to delete - */ - public static function shouldPurgeOutdatedArchives(Date $date) - { - if (self::$purgeDisabledByTests) { - return false; - } - $key = self::FLAG_TABLE_PURGED . "blob_" . $date->toString('Y_m'); - $timestamp = Option::get($key); - - // we shall purge temporary archives after their timeout is finished, plus an extra 6 hours - // in case archiving is disabled or run once a day, we give it this extra time to run - // and re-process more recent records... - $temporaryArchivingTimeout = self::getTodayArchiveTimeToLive(); - $hoursBetweenPurge = 6; - $purgeEveryNSeconds = max($temporaryArchivingTimeout, $hoursBetweenPurge * 3600); - - // we only delete archives if we are able to process them, otherwise, the browser might process reports - // when &segment= is specified (or custom date range) and would below, delete temporary archives that the - // browser is not able to process until next cron run (which could be more than 1 hour away) - if (self::isRequestAuthorizedToArchive() - && (!$timestamp - || $timestamp < time() - $purgeEveryNSeconds) - ) { - Option::set($key, time()); - - if (self::isBrowserTriggerEnabled()) { - // If Browser Archiving is enabled, it is likely there are many more temporary archives - // We delete more often which is safe, since reports are re-processed on demand - $purgeArchivesOlderThan = Date::factory(time() - 2 * $temporaryArchivingTimeout)->getDateTime(); - } else { - // If archive.php via Cron is building the reports, we should keep all temporary reports from today - $purgeArchivesOlderThan = Date::factory('today')->getDateTime(); - } - return $purgeArchivesOlderThan; - } - - Log::info("Purging temporary archives: skipped."); - return false; - } - public static function getMinTimeProcessedForTemporaryArchive( Date $dateStart, \Piwik\Period $period, Segment $segment, Site $site) { @@ -213,30 +149,45 @@ class Rules { $uiSettingIsEnabled = Controller::isGeneralSettingsAdminEnabled(); - if($uiSettingIsEnabled) { + if ($uiSettingIsEnabled) { $timeToLive = Option::get(self::OPTION_TODAY_ARCHIVE_TTL); if ($timeToLive !== false) { return $timeToLive; } } + return self::getTodayArchiveTimeToLiveDefault(); + } + + public static function getTodayArchiveTimeToLiveDefault() + { return Config::getInstance()->General['time_before_today_archive_considered_outdated']; } public static function isArchivingDisabledFor(array $idSites, Segment $segment, $periodLabel) { + $generalConfig = Config::getInstance()->General; + if ($periodLabel == 'range') { - return false; + if (!isset($generalConfig['archiving_range_force_on_browser_request']) + || $generalConfig['archiving_range_force_on_browser_request'] != false + ) { + return false; + } else { + Log::debug("Not forcing archiving for range period."); + } } + $processOneReportOnly = !self::shouldProcessReportsAllPlugins($idSites, $segment, $periodLabel); $isArchivingDisabled = !self::isRequestAuthorizedToArchive() || self::$archivingDisabledByTests; - if ($processOneReportOnly) { - + if ($processOneReportOnly + && $periodLabel != 'range' + ) { // When there is a segment, we disable archiving when browser_archiving_disabled_enforce applies if (!$segment->isEmpty() && $isArchivingDisabled - && Config::getInstance()->General['browser_archiving_disabled_enforce'] - && !SettingsServer::isArchivePhpTriggered() // Only applies when we are not running archive.php + && $generalConfig['browser_archiving_disabled_enforce'] + && !SettingsServer::isArchivePhpTriggered() // Only applies when we are not running core:archive command ) { Log::debug("Archiving is disabled because of config setting browser_archiving_disabled_enforce=1"); return true; @@ -248,7 +199,7 @@ class Rules return $isArchivingDisabled; } - protected static function isRequestAuthorizedToArchive() + public static function isRequestAuthorizedToArchive() { return Rules::isBrowserTriggerEnabled() || SettingsServer::isArchivePhpTriggered(); } @@ -257,7 +208,7 @@ class Rules { $uiSettingIsEnabled = Controller::isGeneralSettingsAdminEnabled(); - if($uiSettingIsEnabled) { + if ($uiSettingIsEnabled) { $browserArchivingEnabled = Option::get(self::OPTION_BROWSER_TRIGGER_ARCHIVING); if ($browserArchivingEnabled !== false) { return (bool)$browserArchivingEnabled; @@ -275,6 +226,18 @@ class Rules Cache::clearCacheGeneral(); } + /** + * Returns true if the archiving process should skip the calculation of unique visitors + * across several sites. The `[General] enable_processing_unique_visitors_multiple_sites` + * INI config option controls the value of this variable. + * + * @return bool + */ + public static function shouldSkipUniqueVisitorsCalculationForMultipleSites() + { + return Config::getInstance()->General['enable_processing_unique_visitors_multiple_sites'] != 1; + } + /** * @param array $idSites * @param Segment $segment @@ -294,11 +257,24 @@ class Rules // Turns out the getString() above returns the URL decoded segment string $segmentsToProcessUrlDecoded = array_map('urldecode', $segmentsToProcess); - if (in_array($segment, $segmentsToProcess) - || in_array($segment, $segmentsToProcessUrlDecoded) - ) { - return true; + return in_array($segment, $segmentsToProcess) + || in_array($segment, $segmentsToProcessUrlDecoded); + } + + /** + * Returns done flag values allowed to be selected + * + * @return string + */ + public static function getSelectableDoneFlagValues() + { + $possibleValues = array(ArchiveWriter::DONE_OK, ArchiveWriter::DONE_OK_TEMPORARY); + + if (!Rules::isRequestAuthorizedToArchive()) { + //If request is not authorized to archive then fetch also invalidated archives + $possibleValues[] = ArchiveWriter::DONE_INVALIDATED; } - return false; + + return $possibleValues; } } diff --git a/www/analytics/core/Archiver/Request.php b/www/analytics/core/Archiver/Request.php new file mode 100644 index 00000000..e1d1909d --- /dev/null +++ b/www/analytics/core/Archiver/Request.php @@ -0,0 +1,48 @@ +url = $url; + } + + public function before($callable) + { + $this->before = $callable; + } + + public function start() + { + if ($this->before) { + $callable = $this->before; + $callable(); + } + } + + public function __toString() + { + return $this->url; + } +} diff --git a/www/analytics/core/AssetManager.php b/www/analytics/core/AssetManager.php index 972c84c3..407a2058 100644 --- a/www/analytics/core/AssetManager.php +++ b/www/analytics/core/AssetManager.php @@ -1,6 +1,6 @@ cacheBuster = UIAssetCacheBuster::getInstance(); - $this->minimalStylesheetFetcher = new StaticUIAssetFetcher(array('plugins/Zeitgeist/stylesheets/base.less'), array(), $this->theme); + $this->minimalStylesheetFetcher = new StaticUIAssetFetcher(array('plugins/Morpheus/stylesheets/base.less', 'plugins/Morpheus/stylesheets/general/_forms.less'), array(), $this->theme); $theme = Manager::getInstance()->getThemeEnabled(); - if(!empty($theme)) { + if (!empty($theme)) { $this->theme = new Theme(); } } @@ -121,14 +120,11 @@ class AssetManager extends Singleton $result = ""; if ($this->isMergedAssetsDisabled()) { - $this->getMergedCoreJSAsset()->delete(); $this->getMergedNonCoreJSAsset()->delete(); $result .= $this->getIndividualJsIncludes(); - } else { - $result .= sprintf(self::JS_IMPORT_DIRECTIVE, self::GET_CORE_JS_MODULE_ACTION); $result .= sprintf(self::JS_IMPORT_DIRECTIVE, self::GET_NON_CORE_JS_MODULE_ACTION); } @@ -201,13 +197,13 @@ class AssetManager extends Singleton { $loadedPlugins = array(); - foreach(Manager::getInstance()->getPluginsLoadedAndActivated() as $plugin) { - + foreach (Manager::getInstance()->getPluginsLoadedAndActivated() as $plugin) { $pluginName = $plugin->getPluginName(); $pluginIsCore = Manager::getInstance()->isPluginBundledWithCore($pluginName); - if(($pluginIsCore && $core) || (!$pluginIsCore && !$core)) + if (($pluginIsCore && $core) || (!$pluginIsCore && !$core)) { $loadedPlugins[] = $pluginName; + } } return $loadedPlugins; @@ -220,23 +216,15 @@ class AssetManager extends Singleton { $assetsToRemove = array($this->getMergedStylesheetAsset()); - if($pluginName) { - - if($this->pluginContainsJScriptAssets($pluginName)) { - - PiwikConfig::getInstance()->init(); - if(Manager::getInstance()->isPluginBundledWithCore($pluginName)) { - + if ($pluginName) { + if ($this->pluginContainsJScriptAssets($pluginName)) { + if (Manager::getInstance()->isPluginBundledWithCore($pluginName)) { $assetsToRemove[] = $this->getMergedCoreJSAsset(); - } else { - $assetsToRemove[] = $this->getMergedNonCoreJSAsset(); } } - } else { - $assetsToRemove[] = $this->getMergedCoreJSAsset(); $assetsToRemove[] = $this->getMergedNonCoreJSAsset(); } @@ -252,8 +240,7 @@ class AssetManager extends Singleton */ public function getAssetDirectory() { - $mergedFileDirectory = PIWIK_USER_PATH . "/tmp/assets"; - $mergedFileDirectory = SettingsPiwik::rewriteTmpPathWithHostname($mergedFileDirectory); + $mergedFileDirectory = StaticContainer::get('path.tmp') . '/assets'; if (!is_dir($mergedFileDirectory)) { Filesystem::mkdir($mergedFileDirectory); @@ -273,7 +260,15 @@ class AssetManager extends Singleton */ public function isMergedAssetsDisabled() { - return Config::getInstance()->Debug['disable_merged_assets']; + if (Config::getInstance()->Development['disable_merged_assets'] == 1) { + return true; + } + + if (isset($_GET['disable_merged_assets']) && $_GET['disable_merged_assets'] == 1) { + return true; + } + + return false; } /** @@ -311,7 +306,6 @@ class AssetManager extends Singleton $jsIncludeString = ''; foreach ($assetFetcher->getCatalog()->getAssets() as $jsFile) { - $jsFile->validateFile(); $jsIncludeString = $jsIncludeString . sprintf(self::JS_IMPORT_DIRECTIVE, $jsFile->getRelativeLocation()); } @@ -339,7 +333,7 @@ class AssetManager extends Singleton try { $assets = $fetcher->getCatalog()->getAssets(); - } catch(\Exception $e) { + } catch (\Exception $e) { // This can happen when a plugin is not valid (eg. Piwik 1.x format) // When posting the event to the plugin, it returns an exception "Plugin has not been loaded" return false; @@ -347,14 +341,14 @@ class AssetManager extends Singleton $plugin = Manager::getInstance()->getLoadedPlugin($pluginName); - if($plugin->isTheme()) { - + if ($plugin->isTheme()) { $theme = Manager::getInstance()->getTheme($pluginName); $javaScriptFiles = $theme->getJavaScriptFiles(); - if(!empty($javaScriptFiles)) + if (!empty($javaScriptFiles)) { $assets = array_merge($assets, $javaScriptFiles); + } } return !empty($assets); @@ -363,9 +357,9 @@ class AssetManager extends Singleton /** * @param UIAsset[] $uiAssets */ - private function removeAssets($uiAssets) + public function removeAssets($uiAssets) { - foreach($uiAssets as $uiAsset) { + foreach ($uiAssets as $uiAsset) { $uiAsset->delete(); } } @@ -373,7 +367,7 @@ class AssetManager extends Singleton /** * @return UIAsset */ - private function getMergedStylesheetAsset() + public function getMergedStylesheetAsset() { return $this->getMergedUIAsset(self::MERGED_CSS_FILE); } diff --git a/www/analytics/core/AssetManager/UIAsset.php b/www/analytics/core/AssetManager/UIAsset.php index 4361812e..c380ed4a 100644 --- a/www/analytics/core/AssetManager/UIAsset.php +++ b/www/analytics/core/AssetManager/UIAsset.php @@ -1,6 +1,6 @@ content = $content; diff --git a/www/analytics/core/AssetManager/UIAsset/OnDiskUIAsset.php b/www/analytics/core/AssetManager/UIAsset/OnDiskUIAsset.php index f008f8aa..cbd702a6 100644 --- a/www/analytics/core/AssetManager/UIAsset/OnDiskUIAsset.php +++ b/www/analytics/core/AssetManager/UIAsset/OnDiskUIAsset.php @@ -1,6 +1,6 @@ baseDirectory = $baseDirectory; $this->relativeLocation = $fileLocation; @@ -50,20 +51,23 @@ class OnDiskUIAsset extends UIAsset public function validateFile() { - if (!$this->assetIsReadable()) + if (!$this->assetIsReadable()) { throw new Exception("The ui asset with 'href' = " . $this->getAbsoluteLocation() . " is not readable"); + } } public function delete() { if ($this->exists()) { - - if (!unlink($this->getAbsoluteLocation())) + try { + Filesystem::remove($this->getAbsoluteLocation()); + } catch (Exception $e) { throw new Exception("Unable to delete merged file : " . $this->getAbsoluteLocation() . ". Please delete the file and refresh"); + } // try to remove compressed version of the merged file. - @unlink($this->getAbsoluteLocation() . ".deflate"); - @unlink($this->getAbsoluteLocation() . ".gz"); + Filesystem::remove($this->getAbsoluteLocation() . ".deflate", true); + Filesystem::remove($this->getAbsoluteLocation() . ".gz", true); } } @@ -77,8 +81,9 @@ class OnDiskUIAsset extends UIAsset $newFile = @fopen($this->getAbsoluteLocation(), "w"); - if (!$newFile) - throw new Exception ("The file : " . $newFile . " can not be opened in write mode."); + if (!$newFile) { + throw new Exception("The file : " . $newFile . " can not be opened in write mode."); + } fwrite($newFile, $content); diff --git a/www/analytics/core/AssetManager/UIAssetCacheBuster.php b/www/analytics/core/AssetManager/UIAssetCacheBuster.php index 800de15d..fcbd0720 100644 --- a/www/analytics/core/AssetManager/UIAssetCacheBuster.php +++ b/www/analytics/core/AssetManager/UIAssetCacheBuster.php @@ -1,6 +1,6 @@ catalogSorter = $catalogSorter; } @@ -33,8 +38,10 @@ class UIAssetCatalog */ public function addUIAsset($uiAsset) { - if(!$this->assetAlreadyInCatalog($uiAsset)) { + $location = $uiAsset->getAbsoluteLocation(); + if (!$this->assetAlreadyInCatalog($location)) { + $this->existingAssetLocations[] = $location; $this->uiAssets[] = $uiAsset; } } @@ -59,12 +66,8 @@ class UIAssetCatalog * @param UIAsset $uiAsset * @return boolean */ - private function assetAlreadyInCatalog($uiAsset) + private function assetAlreadyInCatalog($location) { - foreach($this->uiAssets as $existingAsset) - if($uiAsset->getAbsoluteLocation() == $existingAsset->getAbsoluteLocation()) - return true; - - return false; + return in_array($location, $this->existingAssetLocations); } } diff --git a/www/analytics/core/AssetManager/UIAssetCatalogSorter.php b/www/analytics/core/AssetManager/UIAssetCatalogSorter.php index 65882744..fece3da0 100644 --- a/www/analytics/core/AssetManager/UIAssetCatalogSorter.php +++ b/www/analytics/core/AssetManager/UIAssetCatalogSorter.php @@ -1,6 +1,6 @@ priorityOrder = $priorityOrder; } @@ -31,12 +31,11 @@ class UIAssetCatalogSorter { $sortedCatalog = new UIAssetCatalog($this); foreach ($this->priorityOrder as $filePattern) { - - $assetsMatchingPattern = array_filter($uiAssetCatalog->getAssets(), function($uiAsset) use ($filePattern) { + $assetsMatchingPattern = array_filter($uiAssetCatalog->getAssets(), function ($uiAsset) use ($filePattern) { return preg_match('~^' . $filePattern . '~', $uiAsset->getRelativeLocation()); }); - foreach($assetsMatchingPattern as $assetMatchingPattern) { + foreach ($assetsMatchingPattern as $assetMatchingPattern) { $sortedCatalog->addUIAsset($assetMatchingPattern); } } diff --git a/www/analytics/core/AssetManager/UIAssetFetcher.php b/www/analytics/core/AssetManager/UIAssetFetcher.php index 6a51018d..6d710e13 100644 --- a/www/analytics/core/AssetManager/UIAssetFetcher.php +++ b/www/analytics/core/AssetManager/UIAssetFetcher.php @@ -1,6 +1,6 @@ plugins = $plugins; $this->theme = $theme; @@ -56,8 +56,9 @@ abstract class UIAssetFetcher */ public function getCatalog() { - if($this->catalog == null) + if ($this->catalog == null) { $this->createCatalog(); + } return $this->catalog; } @@ -89,7 +90,6 @@ abstract class UIAssetFetcher private function populateCatalog() { foreach ($this->fileLocations as $fileLocation) { - $newUIAsset = new OnDiskUIAsset($this->getBaseDirectory(), $fileLocation); $this->catalog->addUIAsset($newUIAsset); } diff --git a/www/analytics/core/AssetManager/UIAssetFetcher/JScriptUIAssetFetcher.php b/www/analytics/core/AssetManager/UIAssetFetcher/JScriptUIAssetFetcher.php index 700e2432..aed31925 100644 --- a/www/analytics/core/AssetManager/UIAssetFetcher/JScriptUIAssetFetcher.php +++ b/www/analytics/core/AssetManager/UIAssetFetcher/JScriptUIAssetFetcher.php @@ -1,6 +1,6 @@ plugins)) { + if (!empty($this->plugins)) { /** * Triggered when gathering the list of all JavaScript files needed by Piwik @@ -31,7 +29,7 @@ class JScriptUIAssetFetcher extends UIAssetFetcher * plugin's root directory. * * _Note: While you are developing your plugin you should enable the config setting - * `[Debug] disable_merged_assets` so JavaScript files will be reloaded immediately + * `[Development] disable_merged_assets` so JavaScript files will be reloaded immediately * after every change._ * * **Example** @@ -53,17 +51,14 @@ class JScriptUIAssetFetcher extends UIAssetFetcher protected function addThemeFiles() { $theme = $this->getTheme(); - if(!$theme) { + if (!$theme) { return; } - if(in_array($theme->getThemeName(), $this->plugins)) { - + if (in_array($theme->getThemeName(), $this->plugins)) { $jsInThemes = $this->getTheme()->getJavaScriptFiles(); - if(!empty($jsInThemes)) { - - foreach($jsInThemes as $jsFile) { - + if (!empty($jsInThemes)) { + foreach ($jsInThemes as $jsFile) { $this->fileLocations[] = $jsFile; } } @@ -73,13 +68,17 @@ class JScriptUIAssetFetcher extends UIAssetFetcher protected function getPriorityOrder() { return array( - 'libs/jquery/jquery.js', - 'libs/jquery/jquery-ui.js', + 'libs/bower_components/jquery/dist/jquery.min.js', + 'libs/bower_components/jquery-ui/ui/minified/jquery-ui.min.js', 'libs/jquery/jquery.browser.js', 'libs/', + 'js/', + 'piwik.js', 'plugins/CoreHome/javascripts/require.js', - 'plugins/Zeitgeist/javascripts/piwikHelper.js', - 'plugins/Zeitgeist/javascripts/', + 'plugins/Morpheus/javascripts/piwikHelper.js', + 'plugins/Morpheus/javascripts/jquery.icheck.min.js', + 'plugins/Morpheus/javascripts/morpheus.js', + 'plugins/Morpheus/javascripts/', 'plugins/CoreHome/javascripts/uiControl.js', 'plugins/CoreHome/javascripts/broadcast.js', 'plugins/CoreHome/javascripts/', // load CoreHome JS before other plugins diff --git a/www/analytics/core/AssetManager/UIAssetFetcher/StaticUIAssetFetcher.php b/www/analytics/core/AssetManager/UIAssetFetcher/StaticUIAssetFetcher.php index 0d9027f1..cdebbb1e 100644 --- a/www/analytics/core/AssetManager/UIAssetFetcher/StaticUIAssetFetcher.php +++ b/www/analytics/core/AssetManager/UIAssetFetcher/StaticUIAssetFetcher.php @@ -1,6 +1,6 @@ getTheme(); + $themeName = $theme->getThemeName(); + + $order = array( 'libs/', 'plugins/CoreHome/stylesheets/color_manager.css', // must be before other Piwik stylesheets - 'plugins/Zeitgeist/stylesheets/base.less', - 'plugins/Zeitgeist/stylesheets/', - 'plugins/', - 'plugins/Dashboard/stylesheets/dashboard.less', - 'tests/', + 'plugins/Morpheus/stylesheets/base.less', ); + + if ($themeName === 'Morpheus') { + $order[] = 'plugins\/((?!Morpheus).)*\/'; + } else { + $order[] = sprintf('plugins\/((?!(Morpheus)|(%s)).)*\/', $themeName); + } + + $order = array_merge( + $order, + array( + 'plugins/Dashboard/stylesheets/dashboard.less', + 'tests/', + ) + ); + + return $order; } protected function retrieveFileLocations() @@ -55,9 +70,13 @@ class StylesheetUIAssetFetcher extends UIAssetFetcher protected function addThemeFiles() { + $theme = $this->getTheme(); + if (!$theme) { + return; + } $themeStylesheet = $this->getTheme()->getStylesheet(); - if($themeStylesheet) { + if ($themeStylesheet) { $this->fileLocations[] = $themeStylesheet; } } diff --git a/www/analytics/core/AssetManager/UIAssetMerger.php b/www/analytics/core/AssetManager/UIAssetMerger.php index fd98da90..645f003d 100644 --- a/www/analytics/core/AssetManager/UIAssetMerger.php +++ b/www/analytics/core/AssetManager/UIAssetMerger.php @@ -1,6 +1,6 @@ mergedAsset = $mergedAsset; $this->assetFetcher = $assetFetcher; @@ -48,8 +45,9 @@ abstract class UIAssetMerger public function generateFile() { - if(!$this->shouldGenerate()) + if (!$this->shouldGenerate()) { return; + } $this->mergedContent = $this->getMergedAssets(); @@ -95,8 +93,9 @@ abstract class UIAssetMerger protected function getConcatenatedAssets() { - if(empty($this->mergedContent)) + if (empty($this->mergedContent)) { $this->concatenateAssets(); + } return $this->mergedContent; } @@ -106,7 +105,6 @@ abstract class UIAssetMerger $mergedContent = ''; foreach ($this->getAssetCatalog()->getAssets() as $uiAsset) { - $uiAsset->validateFile(); $content = $this->processFileContent($uiAsset); @@ -137,8 +135,9 @@ abstract class UIAssetMerger */ private function shouldGenerate() { - if(!$this->mergedAsset->exists()) + if (!$this->mergedAsset->exists()) { return true; + } return !$this->isFileUpToDate(); } @@ -161,19 +160,11 @@ abstract class UIAssetMerger return false; } - /** - * @return boolean - */ - private function isMergedAssetsDisabled() - { - return AssetManager::getInstance()->isMergedAssetsDisabled(); - } - private function adjustPaths() { $theme = $this->assetFetcher->getTheme(); // During installation theme is not yet ready - if($theme) { + if ($theme) { $this->mergedContent = $this->assetFetcher->getTheme()->rewriteAssetsPathToTheme($this->mergedContent); } } @@ -188,8 +179,9 @@ abstract class UIAssetMerger */ protected function getCacheBusterValue() { - if(empty($this->cacheBusterValue)) + if (empty($this->cacheBusterValue)) { $this->cacheBusterValue = $this->generateCacheBuster(); + } return $this->cacheBusterValue; } @@ -198,12 +190,4 @@ abstract class UIAssetMerger { $this->mergedContent = $this->getPreamble() . $this->mergedContent; } - - /** - * @return boolean - */ - private function shouldCompareExistingVersion() - { - return $this->isMergedAssetsDisabled(); - } } diff --git a/www/analytics/core/AssetManager/UIAssetMerger/JScriptUIAssetMerger.php b/www/analytics/core/AssetManager/UIAssetMerger/JScriptUIAssetMerger.php index 93b7d7bf..8dd25017 100644 --- a/www/analytics/core/AssetManager/UIAssetMerger/JScriptUIAssetMerger.php +++ b/www/analytics/core/AssetManager/UIAssetMerger/JScriptUIAssetMerger.php @@ -1,6 +1,6 @@ getConcatenatedAssets(); - - return str_replace("\n", "\r\n", $concatenatedAssets); + return $this->getConcatenatedAssets(); } protected function generateCacheBuster() { $cacheBuster = $this->cacheBuster->piwikVersionBasedCacheBuster($this->getPlugins()); - return "/* Piwik Javascript - cb=" . $cacheBuster . "*/\r\n"; + return "/* Piwik Javascript - cb=" . $cacheBuster . "*/\n"; } protected function getPreamble() @@ -57,7 +55,7 @@ class JScriptUIAssetMerger extends UIAssetMerger { $plugins = $this->getPlugins(); - if(!empty($plugins)) { + if (!empty($plugins)) { /** * Triggered after all the JavaScript files Piwik uses are minified and merged into a @@ -74,15 +72,16 @@ class JScriptUIAssetMerger extends UIAssetMerger public function getFileSeparator() { - return PHP_EOL; + return "\n"; } protected function processFileContent($uiAsset) { $content = $uiAsset->getContent(); - if (!$this->assetMinifier->isMinifiedJs($content)) + if (!$this->assetMinifier->isMinifiedJs($content)) { $content = $this->assetMinifier->minifyJs($content); + } return $content; } diff --git a/www/analytics/core/AssetManager/UIAssetMerger/StylesheetUIAssetMerger.php b/www/analytics/core/AssetManager/UIAssetMerger/StylesheetUIAssetMerger.php index 29e487df..5161b2f1 100644 --- a/www/analytics/core/AssetManager/UIAssetMerger/StylesheetUIAssetMerger.php +++ b/www/analytics/core/AssetManager/UIAssetMerger/StylesheetUIAssetMerger.php @@ -1,6 +1,6 @@ getAssetCatalog()->getAssets() as $uiAsset) { - - $content = $uiAsset->getContent(); - if (false !== strpos($content, '@import')) { - $this->lessCompiler->addImportDir(dirname($uiAsset->getAbsoluteLocation())); - } - - } - - return $this->lessCompiler->compile($this->getConcatenatedAssets()); + // note: we're using setImportDir on purpose (not addImportDir) + $this->lessCompiler->setImportDir(PIWIK_USER_PATH); + $concatenatedAssets = $this->getConcatenatedAssets(); + return $this->lessCompiler->compile($concatenatedAssets); } /** @@ -87,46 +81,71 @@ class StylesheetUIAssetMerger extends UIAssetMerger protected function processFileContent($uiAsset) { - return $this->rewriteCssPathsDirectives($uiAsset); + $pathsRewriter = $this->getCssPathsRewriter($uiAsset); + $content = $uiAsset->getContent(); + $content = $this->rewriteCssImagePaths($content, $pathsRewriter); + $content = $this->rewriteCssImportPaths($content, $pathsRewriter); + return $content; } /** - * Rewrite css url directives + * Rewrite CSS url() directives + * + * @param string $content + * @param callable $pathsRewriter + * @return string + */ + private function rewriteCssImagePaths($content, $pathsRewriter) + { + $content = preg_replace_callback("/(url\(['\"]?)([^'\")]*)/", $pathsRewriter, $content); + return $content; + } + + /** + * Rewrite CSS import directives + * + * @param string $content + * @param callable $pathsRewriter + * @return string + */ + private function rewriteCssImportPaths($content, $pathsRewriter) + { + $content = preg_replace_callback("/(@import \")([^\")]*)/", $pathsRewriter, $content); + return $content; + } + + /** + * Rewrite CSS url directives * - rewrites paths defined relatively to their css/less definition file * - rewrite windows directory separator \\ to / * * @param UIAsset $uiAsset - * @return string + * @return \Closure */ - private function rewriteCssPathsDirectives($uiAsset) + private function getCssPathsRewriter($uiAsset) { - static $rootDirectoryLength = null; - if (is_null($rootDirectoryLength)) { - $rootDirectoryLength = self::countDirectoriesInPathToRoot($uiAsset); - } - $baseDirectory = dirname($uiAsset->getRelativeLocation()); - $content = preg_replace_callback( - "/(url\(['\"]?)([^'\")]*)/", - function ($matches) use ($rootDirectoryLength, $baseDirectory) { - $absolutePath = realpath(PIWIK_USER_PATH . "/$baseDirectory/" . $matches[2]); + return function ($matches) use ($baseDirectory) { + $absolutePath = PIWIK_USER_PATH . "/$baseDirectory/" . $matches[2]; - if($absolutePath) { + // Allow to import extension less file + if (strpos($matches[2], '.') === false) { + $absolutePath .= '.less'; + } - $relativePath = substr($absolutePath, $rootDirectoryLength); + // Prevent from rewriting full path + $absolutePath = realpath($absolutePath); + if ($absolutePath) { + $relativePath = $baseDirectory . "/" . $matches[2]; + $relativePath = str_replace('\\', '/', $relativePath); + $publicPath = $matches[1] . $relativePath; + } else { + $publicPath = $matches[1] . $matches[2]; + } - $relativePath = str_replace('\\', '/', $relativePath); - - return $matches[1] . $relativePath; - - } else { - return $matches[1] . $matches[2]; - } - }, - $uiAsset->getContent() - ); - return $content; + return $publicPath; + }; } /** @@ -138,7 +157,7 @@ class StylesheetUIAssetMerger extends UIAssetMerger $rootDirectory = realpath($uiAsset->getBaseDirectory()); if ($rootDirectory != PATH_SEPARATOR - && substr_compare($rootDirectory, PATH_SEPARATOR, -1)) { + && substr($rootDirectory, -strlen(PATH_SEPARATOR)) !== PATH_SEPARATOR) { $rootDirectory .= PATH_SEPARATOR; } $rootDirectoryLen = strlen($rootDirectory); diff --git a/www/analytics/core/AssetManager/UIAssetMinifier.php b/www/analytics/core/AssetManager/UIAssetMinifier.php index 34006f5a..99eebe0a 100644 --- a/www/analytics/core/AssetManager/UIAssetMinifier.php +++ b/www/analytics/core/AssetManager/UIAssetMinifier.php @@ -1,6 +1,6 @@ php composer.phar update"); + if (!class_exists("JShrink\\Minifier")) { + throw new Exception("JShrink could not be found, maybe you are using Piwik from git and need to update Composer. $ php composer.phar update"); + } } - } diff --git a/www/analytics/core/Auth.php b/www/analytics/core/Auth.php index b86af5d1..0ea9503a 100644 --- a/www/analytics/core/Auth.php +++ b/www/analytics/core/Auth.php @@ -1,6 +1,6 @@ setLogin('user'); + * $auth->setPassword('password'); + * $result = $auth->authenticate(); + * + * // authenticating by token auth + * $auth = StaticContainer::get('Piwik\Auth'); + * $auth->setLogin('user'); + * $auth->setTokenAuth('...'); + * $result = $auth->authenticate(); + * + * @api */ interface Auth { /** - * Authentication module's name, e.g., "Login" + * Must return the Authentication module's name, e.g., `"Login"`. * * @return string */ public function getName(); /** - * Authenticates user - * - * @return AuthResult - */ - public function authenticate(); - - /** - * Authenticates the user and initializes the session. - */ - public function initSession($login, $md5Password, $rememberMe); - - /** - * Accessor to set authentication token. If set, you can authenticate the tokenAuth by calling the authenticate() - * method afterwards. + * Sets the authentication token to authenticate with. * * @param string $token_auth authentication token */ public function setTokenAuth($token_auth); /** - * Accessor to set login name + * Returns the login of the user being authenticated. * - * @param string $login user login + * @return string + */ + public function getLogin(); + + /** + * Returns the secret used to calculate a user's token auth. + * + * A users token auth is generated using the user's login and this secret. The secret + * should be specific to the user and not easily guessed. Piwik's default Auth implementation + * uses an MD5 hash of a user's password. + * + * @return string + * @throws Exception if the token auth secret does not exist or cannot be obtained. + */ + public function getTokenAuthSecret(); + + /** + * Sets the login name to authenticate with. + * + * @param string $login The username. */ public function setLogin($login); + + /** + * Sets the password to authenticate with. + * + * @param string $password Password (not hashed). + */ + public function setPassword($password); + + /** + * Sets the hash of the password to authenticate with. The hash will be an MD5 hash. + * + * @param string $passwordHash The hashed password. + * @throws Exception if authentication by hashed password is not supported. + */ + public function setPasswordHash($passwordHash); + + /** + * Authenticates a user using the login and password set using the setters. Can also authenticate + * via token auth if one is set and no password is set. + * + * Note: this method must successfully authenticate if the token auth supplied is a special hash + * of the user's real token auth. This is because the SessionInitializer class stores a + * hash of the token auth in the session cookie. You can calculate the token auth hash using the + * {@link Piwik\Plugins\Login\SessionInitializer::getHashTokenAuth()} method. + * + * @return AuthResult + * @throws Exception if the Auth implementation has an invalid state (ie, no login + * was specified). Note: implementations are not **required** to throw + * exceptions for invalid state, but they are allowed to. + */ + public function authenticate(); } /** - * Authentication result + * Authentication result. This is what is returned by authentication attempts using {@link Auth} + * implementations. * + * @api */ class AuthResult { diff --git a/www/analytics/core/BaseFactory.php b/www/analytics/core/BaseFactory.php new file mode 100644 index 00000000..24425c8f --- /dev/null +++ b/www/analytics/core/BaseFactory.php @@ -0,0 +1,60 @@ +flushAll(); + self::getTransientCache()->flushAll(); + self::getEagerCache()->flushAll(); + } + + /** + * @param $type + * @return Cache\Backend + */ + public static function buildBackend($type) + { + $factory = new Cache\Backend\Factory(); + $options = self::getOptions($type); + + $backend = $factory->buildBackend($type, $options); + + return $backend; + } + + private static function getOptions($type) + { + $options = self::getBackendOptions($type); + + switch ($type) { + case 'file': + + $options = array('directory' => StaticContainer::get('path.cache')); + break; + + case 'chained': + + foreach ($options['backends'] as $backend) { + $options[$backend] = self::getOptions($backend); + } + + break; + + case 'redis': + + if (!empty($options['timeout'])) { + $options['timeout'] = (float)Common::forceDotAsSeparatorForDecimalPoint($options['timeout']); + } + + break; + } + + return $options; + } + + private static function getBackendOptions($backend) + { + $key = ucfirst($backend) . 'Cache'; + $options = Config::getInstance()->$key; + + return $options; + } +} diff --git a/www/analytics/core/CacheFile.php b/www/analytics/core/CacheFile.php deleted file mode 100644 index 043770b3..00000000 --- a/www/analytics/core/CacheFile.php +++ /dev/null @@ -1,206 +0,0 @@ -cachePath = SettingsPiwik::rewriteTmpPathWithHostname($cachePath); - - if ($timeToLiveInSeconds < self::MINIMUM_TTL) { - $timeToLiveInSeconds = self::MINIMUM_TTL; - } - $this->ttl = $timeToLiveInSeconds; - } - - /** - * Function to fetch a cache entry - * - * @param string $id The cache entry ID - * @return array|bool False on error, or array the cache content - */ - public function get($id) - { - if (empty($id)) { - return false; - } - $id = $this->cleanupId($id); - - $cache_complete = false; - $content = ''; - $expires_on = false; - - // We are assuming that most of the time cache will exists - $cacheFilePath = $this->cachePath . $id . '.php'; - if (self::$invalidateOpCacheBeforeRead) { - $this->opCacheInvalidate($cacheFilePath); - } - - $ok = @include($cacheFilePath); - - if ($ok && $cache_complete == true) { - - if (empty($expires_on) - || $expires_on < time() - ) { - return false; - } - return $content; - } - - return false; - } - - private function getExpiresTime() - { - return time() + $this->ttl; - } - - protected function cleanupId($id) - { - if (!Filesystem::isValidFilename($id)) { - throw new Exception("Invalid cache ID request $id"); - } - return $id; - } - - /** - * A function to store content a cache entry. - * - * @param string $id The cache entry ID - * @param array $content The cache content - * @throws \Exception - * @return bool True if the entry was succesfully stored - */ - public function set($id, $content) - { - if (empty($id)) { - return false; - } - if (!is_dir($this->cachePath)) { - Filesystem::mkdir($this->cachePath); - } - if (!is_writable($this->cachePath)) { - return false; - } - $id = $this->cleanupId($id); - - $id = $this->cachePath . $id . '.php'; - - if (is_object($content)) { - throw new \Exception('You cannot use the CacheFile to cache an object, only arrays, strings and numbers.'); - } - - $cache_literal = "<" . "?php\n"; - $cache_literal .= "$" . "content = " . var_export($content, true) . ";\n"; - $cache_literal .= "$" . "expires_on = " . $this->getExpiresTime() . ";\n"; - $cache_literal .= "$" . "cache_complete = true;\n"; - $cache_literal .= "?" . ">"; - - // Write cache to a temp file, then rename it, overwriting the old cache - // On *nix systems this should guarantee atomicity - $tmp_filename = tempnam($this->cachePath, 'tmp_'); - @chmod($tmp_filename, 0640); - if ($fp = @fopen($tmp_filename, 'wb')) { - @fwrite($fp, $cache_literal, strlen($cache_literal)); - @fclose($fp); - - if (!@rename($tmp_filename, $id)) { - // On some systems rename() doesn't overwrite destination - @unlink($id); - if (!@rename($tmp_filename, $id)) { - // Make sure that no temporary file is left over - // if the destination is not writable - @unlink($tmp_filename); - } - } - - $this->opCacheInvalidate($id); - - return true; - } - return false; - } - - /** - * A function to delete a single cache entry - * - * @param string $id The cache entry ID - * @return bool True if the entry was succesfully deleted - */ - public function delete($id) - { - if (empty($id)) { - return false; - } - $id = $this->cleanupId($id); - - $filename = $this->cachePath . $id . '.php'; - if (file_exists($filename)) { - $this->opCacheInvalidate($filename); - @unlink($filename); - return true; - } - return false; - } - - /** - * A function to delete all cache entries in the directory - */ - public function deleteAll() - { - $self = $this; - $beforeUnlink = function ($path) use ($self) { - $self->opCacheInvalidate($path); - }; - - Filesystem::unlinkRecursive($this->cachePath, $deleteRootToo = false, $beforeUnlink); - } - - public function opCacheInvalidate($filepath) - { - if (function_exists('opcache_invalidate') - && is_file($filepath) - ) { - opcache_invalidate($filepath, $force = true); - } - } -} diff --git a/www/analytics/core/CacheId.php b/www/analytics/core/CacheId.php new file mode 100644 index 00000000..cc346bee --- /dev/null +++ b/www/analytics/core/CacheId.php @@ -0,0 +1,29 @@ +getLoadedPluginsName(); + $cacheId = $cacheId . '-' . md5(implode('', $pluginNames)); + $cacheId = self::languageAware($cacheId); + + return $cacheId; + } +} diff --git a/www/analytics/core/CliMulti.php b/www/analytics/core/CliMulti.php index 653ceccf..ef360f02 100644 --- a/www/analytics/core/CliMulti.php +++ b/www/analytics/core/CliMulti.php @@ -1,19 +1,23 @@ supportsAsync = $this->supportsAsync(); @@ -44,26 +68,38 @@ class CliMulti { * If multi cli is not supported (eg windows) it will initiate an HTTP request instead (not async). * * @param string[] $piwikUrls An array of urls, for instance: - * array('http://www.example.com/piwik?module=API...') + * + * `array('http://www.example.com/piwik?module=API...')` + * + * **Make sure query parameter values are properly encoded in the URLs.** + * * @return array The response of each URL in the same order as the URLs. The array can contain null values in case * there was a problem with a request, for instance if the process died unexpected. */ public function request(array $piwikUrls) { - $this->start($piwikUrls); + $chunks = array($piwikUrls); + if ($this->concurrentProcessesLimit) { + $chunks = array_chunk($piwikUrls, $this->concurrentProcessesLimit); + } - do { - usleep(100000); // 100 * 1000 = 100ms - } while (!$this->hasFinished()); - - $results = $this->getResponse($piwikUrls); - $this->cleanup(); - - self::cleanupNotRemovedFiles(); + $results = array(); + foreach ($chunks as $urlsChunk) { + $results = array_merge($results, $this->requestUrls($urlsChunk)); + } return $results; } + /** + * Forwards the given configuration options to the PHP cli command. + * @param string $phpCliOptions eg "-d memory_limit=8G -c=path/to/php.ini" + */ + public function setPhpCliConfigurationOptions($phpCliOptions) + { + $this->phpCliOptions = (string) $phpCliOptions; + } + /** * Ok, this sounds weird. Why should we care about ssl certificates when we are in CLI mode? It is needed for * our simple fallback mode for Windows where we initiate HTTP requests instead of CLI. @@ -74,28 +110,51 @@ class CliMulti { $this->acceptInvalidSSLCertificate = $acceptInvalidSSLCertificate; } + /** + * @param $limit int Maximum count of requests to issue in parallel + */ + public function setConcurrentProcessesLimit($limit) + { + $this->concurrentProcessesLimit = $limit; + } + + public function runAsSuperUser($runAsSuperUser = true) + { + $this->runAsSuperUser = $runAsSuperUser; + } + private function start($piwikUrls) { foreach ($piwikUrls as $index => $url) { - $cmdId = $this->generateCommandId($url) . $index; - $output = new Output($cmdId); - - if ($this->supportsAsync) { - $this->executeAsyncCli($url, $output, $cmdId); - } else { - $this->executeNotAsyncHttp($url, $output); + if ($url instanceof Request) { + $url->start(); } - $this->outputs[] = $output; + $cmdId = $this->generateCommandId($url) . $index; + $this->executeUrlCommand($cmdId, $url); } } + private function executeUrlCommand($cmdId, $url) + { + $output = new Output($cmdId); + + if ($this->supportsAsync) { + $this->executeAsyncCli($url, $output, $cmdId); + } else { + $this->executeNotAsyncHttp($url, $output); + } + + $this->outputs[] = $output; + } + private function buildCommand($hostname, $query, $outputFile) { $bin = $this->findPhpBinary(); + $superuserCommand = $this->runAsSuperUser ? "--superuser" : ""; - return sprintf('%s -q %s/console climulti:request --piwik-domain=%s %s > %s 2>&1 &', - $bin, PIWIK_INCLUDE_PATH, escapeshellarg($hostname), escapeshellarg($query), $outputFile); + return sprintf('%s %s %s/console climulti:request -q --piwik-domain=%s %s %s > %s 2>&1 &', + $bin, $this->phpCliOptions, PIWIK_INCLUDE_PATH, escapeshellarg($hostname), $superuserCommand, escapeshellarg($query), $outputFile); } private function getResponse() @@ -119,7 +178,6 @@ class CliMulti { // ==> declare the process as finished $process->finishProcess(); continue; - } elseif (!$hasStarted) { return false; } @@ -128,6 +186,14 @@ class CliMulti { return false; } + $pid = $process->getPid(); + foreach ($this->outputs as $output) { + if ($output->getOutputId() === $pid && $output->isAbnormal()) { + $process->finishProcess(); + return true; + } + } + if ($process->hasFinished()) { // prevent from checking this process over and over again unset($this->processes[$index]); @@ -146,11 +212,15 @@ class CliMulti { * What is missing under windows? Detection whether a process is still running in Process::isProcessStillRunning * and how to send a process into background in start() */ - private function supportsAsync() + public function supportsAsync() { - return !SettingsServer::isWindows() - && Process::isSupported() - && $this->findPhpBinary(); + return Process::isSupported() && !Common::isPhpCgiType() && $this->findPhpBinary(); + } + + private function findPhpBinary() + { + $cliPhp = new CliPhp(); + return $cliPhp->findPhpBinary(); } private function cleanup() @@ -176,61 +246,33 @@ class CliMulti { $timeOneWeekAgo = strtotime('-1 week'); $files = _glob(self::getTmpPath() . '/*'); - if(empty($files)) { + if (empty($files)) { return; } foreach ($files as $file) { - $timeLastModified = filemtime($file); + if (file_exists($file)) { + $timeLastModified = filemtime($file); - if ($timeOneWeekAgo > $timeLastModified) { - unlink($file); + if ($timeLastModified !== false && $timeOneWeekAgo > $timeLastModified) { + unlink($file); + } } } } public static function getTmpPath() { - $dir = PIWIK_INCLUDE_PATH . '/tmp/climulti'; - return SettingsPiwik::rewriteTmpPathWithHostname($dir); - } - - private function findPhpBinary() - { - if (defined('PHP_BINARY') && false === strpos(PHP_BINARY, 'fpm')) { - return PHP_BINARY; - } - - $bin = ''; - - if (!empty($_SERVER['_']) && Common::isPhpCliMode()) { - $bin = $this->getPhpCommandIfValid($_SERVER['_']); - } - - if (empty($bin) && !empty($_SERVER['argv'][0]) && Common::isPhpCliMode()) { - $bin = $this->getPhpCommandIfValid($_SERVER['argv'][0]); - } - - if (empty($bin)) { - $bin = shell_exec('which php'); - } - - if (empty($bin)) { - $bin = shell_exec('which php5'); - } - - if (!empty($bin)) { - return trim($bin); - } + return StaticContainer::get('path.tmp') . '/climulti'; } private function executeAsyncCli($url, Output $output, $cmdId) { $this->processes[] = new Process($cmdId); - $url = $this->appendTestmodeParamToUrlIfNeeded($url); - $query = UrlHelper::getQueryFromUrl($url, array('pid' => $cmdId)); - $hostname = UrlHelper::getHostFromUrl($url); + $url = $this->appendTestmodeParamToUrlIfNeeded($url); + $query = UrlHelper::getQueryFromUrl($url, array('pid' => $cmdId)); + $hostname = Url::getHost($checkIfTrusted = false); $command = $this->buildCommand($hostname, $query, $output->getPathToFile()); Log::debug($command); @@ -239,6 +281,29 @@ class CliMulti { private function executeNotAsyncHttp($url, Output $output) { + $piwikUrl = $this->urlToPiwik ?: SettingsPiwik::getPiwikUrl(); + if (empty($piwikUrl)) { + $piwikUrl = 'http://' . Url::getHost() . '/'; + } + + $url = $piwikUrl . $url; + if (Config::getInstance()->General['force_ssl'] == 1) { + $url = str_replace("http://", "https://", $url); + } + + if ($this->runAsSuperUser) { + $tokenAuths = self::getSuperUserTokenAuths(); + $tokenAuth = reset($tokenAuths); + + if (strpos($url, '?') === false) { + $url .= '?'; + } else { + $url .= '&'; + } + + $url .= 'token_auth=' . $tokenAuth; + } + try { Log::debug("Execute HTTP API request: " . $url); $response = Http::sendHttpRequestBy('curl', $url, $timeout = 0, $userAgent = null, $destinationPath = null, $file = null, $followDepth = 0, $acceptLanguage = false, $this->acceptInvalidSSLCertificate); @@ -246,19 +311,21 @@ class CliMulti { } catch (\Exception $e) { $message = "Got invalid response from API request: $url. "; - if (empty($response)) { + if (isset($response) && empty($response)) { $message .= "The response was empty. This usually means a server error. This solution to this error is generally to increase the value of 'memory_limit' in your php.ini file. Please check your Web server Error Log file for more details."; } else { $message .= "Response was '" . $e->getMessage() . "'"; } $output->write($message); + + Log::debug($e); } } private function appendTestmodeParamToUrlIfNeeded($url) { - $isTestMode = $url && false !== strpos($url, 'tests/PHPUnit/proxy'); + $isTestMode = defined('PIWIK_TEST_MODE'); if ($isTestMode && false === strpos($url, '?')) { $url .= "?testmode=1"; @@ -269,12 +336,42 @@ class CliMulti { return $url; } - private function getPhpCommandIfValid($path) + /** + * @param array $piwikUrls + * @return array + */ + private function requestUrls(array $piwikUrls) { - if (!empty($path) && is_executable($path)) { - if (0 === strpos($path, PHP_BINDIR) && false === strpos($path, 'phpunit')) { - return $path; - } - } + $this->start($piwikUrls); + + do { + usleep(100000); // 100 * 1000 = 100ms + } while (!$this->hasFinished()); + + $results = $this->getResponse($piwikUrls); + $this->cleanup(); + + self::cleanupNotRemovedFiles(); + + return $results; + } + + private static function getSuperUserTokenAuths() + { + $tokens = array(); + + /** + * Used to be in CronArchive, moved to CliMulti. + * + * @ignore + */ + Piwik::postEvent('CronArchive.getTokenAuth', array(&$tokens)); + + return $tokens; + } + + public function setUrlToPiwik($urlToPiwik) + { + $this->urlToPiwik = $urlToPiwik; } } diff --git a/www/analytics/core/CliMulti/CliPhp.php b/www/analytics/core/CliMulti/CliPhp.php new file mode 100644 index 00000000..c405d66a --- /dev/null +++ b/www/analytics/core/CliMulti/CliPhp.php @@ -0,0 +1,101 @@ +isHhvmBinary(PHP_BINARY)) { + return PHP_BINARY . ' --php'; + } + + if ($this->isValidPhpType(PHP_BINARY)) { + return PHP_BINARY . ' -q'; + } + } + + $bin = ''; + + if (!empty($_SERVER['_']) && Common::isPhpCliMode()) { + $bin = $this->getPhpCommandIfValid($_SERVER['_']); + } + + if (empty($bin) && !empty($_SERVER['argv'][0]) && Common::isPhpCliMode()) { + $bin = $this->getPhpCommandIfValid($_SERVER['argv'][0]); + } + + if (!$this->isValidPhpType($bin)) { + $bin = shell_exec('which php'); + } + + if (!$this->isValidPhpType($bin)) { + $bin = shell_exec('which php5'); + } + + if (!$this->isValidPhpType($bin)) { + return false; + } + + $bin = trim($bin); + + if (!$this->isValidPhpVersion($bin)) { + return false; + } + + $bin .= ' -q'; + + return $bin; + } + + private function isHhvmBinary($bin) + { + return false !== strpos($bin, 'hhvm'); + } + + private function isValidPhpVersion($bin) + { + global $piwik_minimumPHPVersion; + $cliVersion = $this->getPhpVersion($bin); + $isCliVersionValid = version_compare($piwik_minimumPHPVersion, $cliVersion) <= 0; + return $isCliVersionValid; + } + + private function isValidPhpType($path) + { + return !empty($path) + && false === strpos($path, 'fpm') + && false === strpos($path, 'cgi') + && false === strpos($path, 'phpunit'); + } + + private function getPhpCommandIfValid($path) + { + if (!empty($path) && is_executable($path)) { + if (0 === strpos($path, PHP_BINDIR) && $this->isValidPhpType($path)) { + return $path; + } + } + return null; + } + + /** + * @param string $bin PHP binary + * @return string + */ + private function getPhpVersion($bin) + { + $command = sprintf("%s -r 'echo phpversion();'", $bin); + $version = shell_exec($command); + return $version; + } +} diff --git a/www/analytics/core/CliMulti/Output.php b/www/analytics/core/CliMulti/Output.php index e060bc49..b97df746 100644 --- a/www/analytics/core/CliMulti/Output.php +++ b/www/analytics/core/CliMulti/Output.php @@ -1,6 +1,6 @@ tmpFile = $dir . '/' . $outputId . '.output'; + Filesystem::mkdir($dir); + + $this->tmpFile = $dir . '/' . $outputId . '.output'; + $this->outputId = $outputId; + } + + public function getOutputId() + { + return $this->outputId; } public function write($content) @@ -35,6 +44,13 @@ class Output { return $this->tmpFile; } + public function isAbnormal() + { + $size = Filesystem::getFileSize($this->tmpFile, 'MB'); + + return $size !== null && $size >= 100; + } + public function exists() { return file_exists($this->tmpFile); @@ -49,5 +65,4 @@ class Output { { Filesystem::deleteFileIfExists($this->tmpFile); } - } diff --git a/www/analytics/core/CliMulti/Process.php b/www/analytics/core/CliMulti/Process.php index 1f0059ea..e1d702fd 100644 --- a/www/analytics/core/CliMulti/Process.php +++ b/www/analytics/core/CliMulti/Process.php @@ -1,6 +1,6 @@ isSupported = self::isSupported(); $this->pidFile = $pidDir . '/' . $pid . '.pid'; $this->timeCreation = time(); + $this->pid = $pid; $this->markAsNotStarted(); } + public function getPid() + { + return $this->pid; + } + private function markAsNotStarted() { $content = $this->getPidFileContent(); @@ -97,6 +104,11 @@ class Process return false; } + if (!$this->pidFileSizeIsNormal()) { + $this->finishProcess(); + return false; + } + if ($this->isProcessStillRunning($content)) { return true; } @@ -108,6 +120,13 @@ class Process return false; } + private function pidFileSizeIsNormal() + { + $size = Filesystem::getFileSize($this->pidFile); + + return $size !== null && $size < 500; + } + public function finishProcess() { Filesystem::deleteFileIfExists($this->pidFile); @@ -125,7 +144,7 @@ class Process } $lockedPID = trim($content); - $runningPIDs = explode("\n", trim( `ps -e | awk '{print $1}'` )); + $runningPIDs = self::getRunningProcesses(); return !empty($lockedPID) && in_array($lockedPID, $runningPIDs); } @@ -154,17 +173,30 @@ class Process return false; } - if (static::commandExists('ps') && self::returnsSuccessCode('ps') && self::commandExists('awk')) { + if (!self::commandExists('ps') || !self::returnsSuccessCode('ps') || !self::commandExists('awk')) { + return false; + } + + if (count(self::getRunningProcesses()) > 0) { return true; } - return false; + if (!self::isProcFSMounted()) { + return false; + } + + return true; } private static function isSystemNotSupported() { - $uname = shell_exec('uname -a'); - if(strpos($uname, 'synology') !== false) { + $uname = @shell_exec('uname -a 2> /dev/null'); + + if (empty($uname)) { + $uname = php_uname(); + } + + if (strpos($uname, 'synology') !== false) { return true; } return false; @@ -175,12 +207,12 @@ class Process $command = 'shell_exec'; $disabled = explode(',', ini_get('disable_functions')); $disabled = array_map('trim', $disabled); - return in_array($command, $disabled); + return in_array($command, $disabled) || !function_exists($command); } private static function returnsSuccessCode($command) { - $exec = $command . ' > /dev/null 2>&1 & echo $?'; + $exec = $command . ' > /dev/null 2>&1; echo $?'; $returnCode = shell_exec($exec); $returnCode = trim($returnCode); return 0 == (int) $returnCode; @@ -188,8 +220,38 @@ class Process private static function commandExists($command) { - $result = shell_exec('which ' . escapeshellarg($command)); + $result = @shell_exec('which ' . escapeshellarg($command) . ' 2> /dev/null'); return !empty($result); } -} \ No newline at end of file + + /** + * ps -e requires /proc + * @return bool + */ + private static function isProcFSMounted() + { + if (is_resource(@fopen('/proc', 'r'))) { + return true; + } + // Testing if /proc is a resource with @fopen fails on systems with open_basedir set. + // by using stat we not only test the existance of /proc but also confirm it's a 'proc' filesystem + $type = @shell_exec('stat -f -c "%T" /proc 2>/dev/null'); + return strpos($type, 'proc') === 0; + } + + /** + * @return int[] The ids of the currently running processes + */ + public static function getRunningProcesses() + { + $ids = explode("\n", trim(`ps ex 2>/dev/null | awk '{print $1}' 2>/dev/null`)); + + $ids = array_map('intval', $ids); + $ids = array_filter($ids, function ($id) { + return $id > 0; + }); + + return $ids; + } +} diff --git a/www/analytics/core/CliMulti/RequestCommand.php b/www/analytics/core/CliMulti/RequestCommand.php index f6ccfd34..7ae2fc44 100644 --- a/www/analytics/core/CliMulti/RequestCommand.php +++ b/www/analytics/core/CliMulti/RequestCommand.php @@ -1,6 +1,6 @@ setName('climulti:request'); $this->setDescription('Parses and executes the given query. See Piwik\CliMulti. Intended only for system usage.'); - $this->addArgument('url-query', null, InputOption::VALUE_REQUIRED, 'Piwik URL query string, for instance: "module=API&method=API.getPiwikVersion&token_auth=123456789"'); + $this->addArgument('url-query', InputArgument::REQUIRED, 'Piwik URL query string, for instance: "module=API&method=API.getPiwikVersion&token_auth=123456789"'); + $this->addOption('superuser', null, InputOption::VALUE_NONE, 'If supplied, runs the code as superuser.'); } protected function execute(InputInterface $input, OutputInterface $output) { + $this->recreateContainerWithWebEnvironment(); + $this->initHostAndQueryString($input); if ($this->isTestModeEnabled()) { - Config::getInstance()->setTestEnvironment(); - $indexFile = '/tests/PHPUnit/proxy/index.php'; + $indexFile = '/tests/PHPUnit/proxy/'; + + $this->resetDatabase(); } else { - $indexFile = '/index.php'; + $indexFile = '/'; } + $indexFile .= 'index.php'; + if (!empty($_GET['pid'])) { $process = new Process($_GET['pid']); @@ -52,6 +66,16 @@ class RequestCommand extends ConsoleCommand $process->startProcess(); } + if ($input->getOption('superuser')) { + StaticContainer::addDefinitions(array( + 'observers.global' => \DI\add(array( + array('Environment.bootstrapped', function () { + Access::getInstance()->setSuperUserAccess(true); + }) + )), + )); + } + require_once PIWIK_INCLUDE_PATH . $indexFile; if (!empty($process)) { @@ -75,10 +99,30 @@ class RequestCommand extends ConsoleCommand Url::setHost($hostname); $query = $input->getArgument('url-query'); - $query = UrlHelper::getArrayFromQueryString($query); + $query = UrlHelper::getArrayFromQueryString($query); // NOTE: this method can create the StaticContainer now foreach ($query as $name => $value) { $_GET[$name] = $value; } } -} \ No newline at end of file + /** + * We will be simulating an HTTP request here (by including index.php). + * + * To avoid weird side-effects (e.g. the logging output messing up the HTTP response on the CLI output) + * we need to recreate the container with the default environment instead of the CLI environment. + */ + private function recreateContainerWithWebEnvironment() + { + StaticContainer::clearContainer(); + Log::unsetInstance(); + + $this->environment = new Environment(null); + $this->environment->init(); + } + + private function resetDatabase() + { + Option::clearCache(); + Db::destroyDatabaseObject(); + } +} diff --git a/www/analytics/core/Columns/Dimension.php b/www/analytics/core/Columns/Dimension.php new file mode 100644 index 00000000..c89ae637 --- /dev/null +++ b/www/analytics/core/Columns/Dimension.php @@ -0,0 +1,242 @@ +setSegment('exitPageUrl'); + * $segment->setName('Actions_ColumnExitPageURL'); + * $segment->setCategory('General_Visit'); + * $this->addSegment($segment); + * ``` + */ + protected function configureSegments() + { + } + + /** + * Check whether a dimension has overwritten a specific method. + * @param $method + * @return bool + * @ignore + */ + public function hasImplementedEvent($method) + { + $method = new \ReflectionMethod($this, $method); + $declaringClass = $method->getDeclaringClass(); + + return 0 === strpos($declaringClass->name, 'Piwik\Plugins'); + } + + /** + * Adds a new segment. The segment type will be set to 'dimension' automatically if not already set. + * @param Segment $segment + * @api + */ + protected function addSegment(Segment $segment) + { + $type = $segment->getType(); + + if (empty($type)) { + $segment->setType(Segment::TYPE_DIMENSION); + } + + $this->segments[] = $segment; + } + + /** + * Get the list of configured segments. + * @return Segment[] + * @ignore + */ + public function getSegments() + { + if (empty($this->segments)) { + $this->configureSegments(); + } + + return $this->segments; + } + + /** + * Get the name of the dimension column. + * @return string + * @ignore + */ + public function getColumnName() + { + return $this->columnName; + } + + /** + * Check whether the dimension has a column type configured + * @return bool + * @ignore + */ + public function hasColumnType() + { + return !empty($this->columnType); + } + + /** + * Get the translated name of the dimension. Defaults to an empty string. + * @return string + * @api + */ + public function getName() + { + return ''; + } + + /** + * Returns a unique string ID for this dimension. The ID is built using the namespaced class name + * of the dimension, but is modified to be more human readable. + * + * @return string eg, `"Referrers.Keywords"` + * @throws Exception if the plugin and simple class name of this instance cannot be determined. + * This would only happen if the dimension is located in the wrong directory. + * @api + */ + public function getId() + { + $className = get_class($this); + + // parse plugin name & dimension name + $regex = "/Piwik\\\\Plugins\\\\([^\\\\]+)\\\\" . self::COMPONENT_SUBNAMESPACE . "\\\\([^\\\\]+)/"; + if (!preg_match($regex, $className, $matches)) { + throw new Exception("'$className' is located in the wrong directory."); + } + + $pluginName = $matches[1]; + $dimensionName = $matches[2]; + + return $pluginName . '.' . $dimensionName; + } + + /** + * Gets an instance of all available visit, action and conversion dimension. + * @return Dimension[] + */ + public static function getAllDimensions() + { + $dimensions = array(); + + foreach (VisitDimension::getAllDimensions() as $dimension) { + $dimensions[] = $dimension; + } + + foreach (ActionDimension::getAllDimensions() as $dimension) { + $dimensions[] = $dimension; + } + + foreach (ConversionDimension::getAllDimensions() as $dimension) { + $dimensions[] = $dimension; + } + + return $dimensions; + } + + public static function getDimensions(Plugin $plugin) + { + $dimensions = array(); + + foreach (VisitDimension::getDimensions($plugin) as $dimension) { + $dimensions[] = $dimension; + } + + foreach (ActionDimension::getDimensions($plugin) as $dimension) { + $dimensions[] = $dimension; + } + + foreach (ConversionDimension::getDimensions($plugin) as $dimension) { + $dimensions[] = $dimension; + } + + return $dimensions; + } + + /** + * Creates a Dimension instance from a string ID (see {@link getId()}). + * + * @param string $dimensionId See {@link getId()}. + * @return Dimension|null The created instance or null if there is no Dimension for + * $dimensionId or if the plugin that contains the Dimension is + * not loaded. + * @api + */ + public static function factory($dimensionId) + { + list($module, $dimension) = explode('.', $dimensionId); + return ComponentFactory::factory($module, $dimension, __CLASS__); + } + + /** + * Returns the name of the plugin that contains this Dimension. + * + * @return string + * @throws Exception if the Dimension is not located within a Plugin module. + * @api + */ + public function getModule() + { + $id = $this->getId(); + if (empty($id)) { + throw new Exception("Invalid dimension ID: '$id'."); + } + + $parts = explode('.', $id); + return reset($parts); + } +} diff --git a/www/analytics/core/Columns/Updater.php b/www/analytics/core/Columns/Updater.php new file mode 100644 index 00000000..8dea10de --- /dev/null +++ b/www/analytics/core/Columns/Updater.php @@ -0,0 +1,372 @@ +visitDimensions = $visitDimensions; + $this->actionDimensions = $actionDimensions; + $this->conversionDimensions = $conversionDimensions; + } + + public function getMigrationQueries(PiwikUpdater $updater) + { + $sqls = array(); + + $changingColumns = $this->getUpdates($updater); + + foreach ($changingColumns as $table => $columns) { + if (empty($columns) || !is_array($columns)) { + continue; + } + + $sqls["ALTER TABLE `" . Common::prefixTable($table) . "` " . implode(', ', $columns)] = array('1091', '1060'); + } + + return $sqls; + } + + public function doUpdate(PiwikUpdater $updater) + { + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); + } + + private function getVisitDimensions() + { + // see eg https://github.com/piwik/piwik/issues/8399 we fetch them only on demand to improve performance + if (!isset($this->visitDimensions)) { + $this->visitDimensions = VisitDimension::getAllDimensions(); + } + + return $this->visitDimensions; + } + + private function getActionDimensions() + { + // see eg https://github.com/piwik/piwik/issues/8399 we fetch them only on demand to improve performance + if (!isset($this->actionDimensions)) { + $this->actionDimensions = ActionDimension::getAllDimensions(); + } + + return $this->actionDimensions; + } + + private function getConversionDimensions() + { + // see eg https://github.com/piwik/piwik/issues/8399 we fetch them only on demand to improve performance + if (!isset($this->conversionDimensions)) { + $this->conversionDimensions = ConversionDimension::getAllDimensions(); + } + + return $this->conversionDimensions; + } + + private function getUpdates(PiwikUpdater $updater) + { + $visitColumns = DbHelper::getTableColumns(Common::prefixTable('log_visit')); + $actionColumns = DbHelper::getTableColumns(Common::prefixTable('log_link_visit_action')); + $conversionColumns = DbHelper::getTableColumns(Common::prefixTable('log_conversion')); + + $allUpdatesToRun = array(); + + foreach ($this->getVisitDimensions() as $dimension) { + $updates = $this->getUpdatesForDimension($updater, $dimension, 'log_visit.', $visitColumns, $conversionColumns); + $allUpdatesToRun = $this->mixinUpdates($allUpdatesToRun, $updates); + } + + foreach ($this->getActionDimensions() as $dimension) { + $updates = $this->getUpdatesForDimension($updater, $dimension, 'log_link_visit_action.', $actionColumns); + $allUpdatesToRun = $this->mixinUpdates($allUpdatesToRun, $updates); + } + + foreach ($this->getConversionDimensions() as $dimension) { + $updates = $this->getUpdatesForDimension($updater, $dimension, 'log_conversion.', $conversionColumns); + $allUpdatesToRun = $this->mixinUpdates($allUpdatesToRun, $updates); + } + + return $allUpdatesToRun; + } + + /** + * @param ActionDimension|ConversionDimension|VisitDimension $dimension + * @param string $componentPrefix + * @param array $existingColumnsInDb + * @param array $conversionColumns + * @return array + */ + private function getUpdatesForDimension(PiwikUpdater $updater, $dimension, $componentPrefix, $existingColumnsInDb, $conversionColumns = array()) + { + $column = $dimension->getColumnName(); + $componentName = $componentPrefix . $column; + + if (!$updater->hasNewVersion($componentName)) { + return array(); + } + + if (array_key_exists($column, $existingColumnsInDb)) { + if ($dimension instanceof VisitDimension) { + $sqlUpdates = $dimension->update($conversionColumns); + } else { + $sqlUpdates = $dimension->update(); + } + } else { + $sqlUpdates = $dimension->install(); + } + + return $sqlUpdates; + } + + private function mixinUpdates($allUpdatesToRun, $updatesFromDimension) + { + if (!empty($updatesFromDimension)) { + foreach ($updatesFromDimension as $table => $col) { + if (empty($allUpdatesToRun[$table])) { + $allUpdatesToRun[$table] = $col; + } else { + $allUpdatesToRun[$table] = array_merge($allUpdatesToRun[$table], $col); + } + } + } + + return $allUpdatesToRun; + } + + public function getAllVersions(PiwikUpdater $updater) + { + // to avoid having to load all dimensions on each request we check if there were any changes on the file system + // can easily save > 100ms for each request + $cachedTimes = self::getCachedDimensionFileChanges(); + $currentTimes = self::getCurrentDimensionFileChanges(); + $diff = array_diff_assoc($currentTimes, $cachedTimes); + + if (empty($diff)) { + return array(); + } + + $versions = array(); + + $visitColumns = DbHelper::getTableColumns(Common::prefixTable('log_visit')); + $actionColumns = DbHelper::getTableColumns(Common::prefixTable('log_link_visit_action')); + $conversionColumns = DbHelper::getTableColumns(Common::prefixTable('log_conversion')); + + foreach ($this->getVisitDimensions() as $dimension) { + $versions = $this->mixinVersions($updater, $dimension, VisitDimension::INSTALLER_PREFIX, $visitColumns, $versions); + } + + foreach ($this->getActionDimensions() as $dimension) { + $versions = $this->mixinVersions($updater, $dimension, ActionDimension::INSTALLER_PREFIX, $actionColumns, $versions); + } + + foreach ($this->getConversionDimensions() as $dimension) { + $versions = $this->mixinVersions($updater, $dimension, ConversionDimension::INSTALLER_PREFIX, $conversionColumns, $versions); + } + + return $versions; + } + + /** + * @param ActionDimension|ConversionDimension|VisitDimension $dimension + * @param string $componentPrefix + * @param array $columns + * @param array $versions + * @return array The modified versions array + */ + private function mixinVersions(PiwikUpdater $updater, $dimension, $componentPrefix, $columns, $versions) + { + $columnName = $dimension->getColumnName(); + + // dimensions w/o columns do not need DB updates + if (!$columnName || !$dimension->hasColumnType()) { + return $versions; + } + + $component = $componentPrefix . $columnName; + $version = $dimension->getVersion(); + + // if the column exists in the table, but has no associated version, and was one of the core columns + // that was moved when the dimension refactor took place, then: + // - set the installed version in the DB to the current code version + // - and do not check for updates since we just set the version to the latest + if (array_key_exists($columnName, $columns) + && false === $updater->getCurrentComponentVersion($component) + && self::wasDimensionMovedFromCoreToPlugin($component, $version) + ) { + $updater->markComponentSuccessfullyUpdated($component, $version); + return $versions; + } + + $versions[$component] = $version; + + return $versions; + } + + public static function isDimensionComponent($name) + { + return 0 === strpos($name, 'log_visit.') + || 0 === strpos($name, 'log_conversion.') + || 0 === strpos($name, 'log_conversion_item.') + || 0 === strpos($name, 'log_link_visit_action.'); + } + + public static function wasDimensionMovedFromCoreToPlugin($name, $version) + { + // maps names of core dimension columns that were part of the original dimension refactor with their + // initial "version" strings. The '1' that is sometimes appended to the end of the string (sometimes seen as + // NULL1) is from individual dimension "versioning" logic (eg, see VisitDimension::getVersion()) + $initialCoreDimensionVersions = array( + 'log_visit.config_resolution' => 'VARCHAR(9) NOT NULL', + 'log_visit.config_device_brand' => 'VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL', + 'log_visit.config_device_model' => 'VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL', + 'log_visit.config_windowsmedia' => 'TINYINT(1) NOT NULL', + 'log_visit.config_silverlight' => 'TINYINT(1) NOT NULL', + 'log_visit.config_java' => 'TINYINT(1) NOT NULL', + 'log_visit.config_gears' => 'TINYINT(1) NOT NULL', + 'log_visit.config_pdf' => 'TINYINT(1) NOT NULL', + 'log_visit.config_quicktime' => 'TINYINT(1) NOT NULL', + 'log_visit.config_realplayer' => 'TINYINT(1) NOT NULL', + 'log_visit.config_device_type' => 'TINYINT( 100 ) NULL DEFAULT NULL', + 'log_visit.visitor_localtime' => 'TIME NOT NULL', + 'log_visit.location_region' => 'char(2) DEFAULT NULL1', + 'log_visit.visitor_days_since_last' => 'SMALLINT(5) UNSIGNED NOT NULL', + 'log_visit.location_longitude' => 'float(10, 6) DEFAULT NULL1', + 'log_visit.visit_total_events' => 'SMALLINT(5) UNSIGNED NOT NULL', + 'log_visit.config_os_version' => 'VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL', + 'log_visit.location_city' => 'varchar(255) DEFAULT NULL1', + 'log_visit.location_country' => 'CHAR(3) NOT NULL1', + 'log_visit.location_latitude' => 'float(10, 6) DEFAULT NULL1', + 'log_visit.config_flash' => 'TINYINT(1) NOT NULL', + 'log_visit.config_director' => 'TINYINT(1) NOT NULL', + 'log_visit.visit_total_time' => 'SMALLINT(5) UNSIGNED NOT NULL', + 'log_visit.visitor_count_visits' => 'SMALLINT(5) UNSIGNED NOT NULL1', + 'log_visit.visit_entry_idaction_name' => 'INTEGER(11) UNSIGNED NOT NULL', + 'log_visit.visit_entry_idaction_url' => 'INTEGER(11) UNSIGNED NOT NULL', + 'log_visit.visitor_returning' => 'TINYINT(1) NOT NULL1', + 'log_visit.visitor_days_since_order' => 'SMALLINT(5) UNSIGNED NOT NULL1', + 'log_visit.visit_goal_buyer' => 'TINYINT(1) NOT NULL', + 'log_visit.visit_first_action_time' => 'DATETIME NOT NULL', + 'log_visit.visit_goal_converted' => 'TINYINT(1) NOT NULL', + 'log_visit.visitor_days_since_first' => 'SMALLINT(5) UNSIGNED NOT NULL1', + 'log_visit.visit_exit_idaction_name' => 'INTEGER(11) UNSIGNED NOT NULL', + 'log_visit.visit_exit_idaction_url' => 'INTEGER(11) UNSIGNED NULL DEFAULT 0', + 'log_visit.config_browser_version' => 'VARCHAR(20) NOT NULL', + 'log_visit.config_browser_name' => 'VARCHAR(10) NOT NULL', + 'log_visit.config_browser_engine' => 'VARCHAR(10) NOT NULL', + 'log_visit.location_browser_lang' => 'VARCHAR(20) NOT NULL', + 'log_visit.config_os' => 'CHAR(3) NOT NULL', + 'log_visit.config_cookie' => 'TINYINT(1) NOT NULL', + 'log_visit.referer_url' => 'TEXT NOT NULL', + 'log_visit.visit_total_searches' => 'SMALLINT(5) UNSIGNED NOT NULL', + 'log_visit.visit_total_actions' => 'SMALLINT(5) UNSIGNED NOT NULL', + 'log_visit.referer_keyword' => 'VARCHAR(255) NULL1', + 'log_visit.referer_name' => 'VARCHAR(70) NULL1', + 'log_visit.referer_type' => 'TINYINT(1) UNSIGNED NULL1', + 'log_visit.user_id' => 'VARCHAR(200) NULL', + 'log_link_visit_action.idaction_name' => 'INTEGER(10) UNSIGNED', + 'log_link_visit_action.idaction_url' => 'INTEGER(10) UNSIGNED DEFAULT NULL', + 'log_link_visit_action.server_time' => 'DATETIME NOT NULL', + 'log_link_visit_action.time_spent_ref_action' => 'INTEGER(10) UNSIGNED NOT NULL', + 'log_link_visit_action.idaction_event_action' => 'INTEGER(10) UNSIGNED DEFAULT NULL', + 'log_link_visit_action.idaction_event_category' => 'INTEGER(10) UNSIGNED DEFAULT NULL', + 'log_conversion.revenue_discount' => 'float default NULL', + 'log_conversion.revenue' => 'float default NULL', + 'log_conversion.revenue_shipping' => 'float default NULL', + 'log_conversion.revenue_subtotal' => 'float default NULL', + 'log_conversion.revenue_tax' => 'float default NULL', + ); + + if (!array_key_exists($name, $initialCoreDimensionVersions)) { + return false; + } + + return strtolower($initialCoreDimensionVersions[$name]) === strtolower($version); + } + + public function onNoUpdateAvailable($versionsThatWereChecked) + { + if (!empty($versionsThatWereChecked)) { + // invalidate cache only if there were actually file changes before, otherwise we write the cache on each + // request. There were versions checked only if there was a file change but no update, meaning we can + // set the cache and declare this state as "no update available". + self::cacheCurrentDimensionFileChanges(); + } + } + + private static function getCurrentDimensionFileChanges() + { + $files = Filesystem::globr(PIWIK_INCLUDE_PATH . '/plugins/*/Columns', '*.php'); + + $times = array(); + foreach ($files as $file) { + $times[$file] = filemtime($file); + } + + return $times; + } + + private static function cacheCurrentDimensionFileChanges() + { + $changes = self::getCurrentDimensionFileChanges(); + + $cache = self::buildCache(); + $cache->save(self::$cacheId, $changes); + } + + private static function buildCache() + { + return PiwikCache::getEagerCache(); + } + + private static function getCachedDimensionFileChanges() + { + $cache = self::buildCache(); + + if ($cache->contains(self::$cacheId)) { + return $cache->fetch(self::$cacheId); + } + + return array(); + } +} diff --git a/www/analytics/core/Common.php b/www/analytics/core/Common.php index 876f6ff8..7992275a 100644 --- a/www/analytics/core/Common.php +++ b/www/analytics/core/Common.php @@ -1,6 +1,6 @@ isPluginActivated('Goals'); + return Plugin\Manager::getInstance()->isPluginActivated('Goals'); } public static function isActionsPluginEnabled() { - return \Piwik\Plugin\Manager::getInstance()->isPluginActivated('Actions'); + return Plugin\Manager::getInstance()->isPluginActivated('Actions'); } /** @@ -127,21 +130,44 @@ class Common return self::$isCliMode; } - $remoteAddr = @$_SERVER['REMOTE_ADDR']; - return PHP_SAPI == 'cli' || - (!strncmp(PHP_SAPI, 'cgi', 3) && empty($remoteAddr)); + if(PHP_SAPI == 'cli'){ + return true; + } + + if(self::isPhpCgiType() && (!isset($_SERVER['REMOTE_ADDR']) || empty($_SERVER['REMOTE_ADDR']))){ + return true; + } + + return false; } /** - * Returns true if the current request is a console command, eg. ./console xx:yy + * Returns true if PHP is executed as CGI type. + * + * @since added in 0.4.4 + * @return bool true if PHP invoked as a CGI + */ + public static function isPhpCgiType() + { + $sapiType = php_sapi_name(); + + return substr($sapiType, 0, 3) === 'cgi'; + } + + /** + * Returns true if the current request is a console command, eg. + * ./console xx:yy + * or + * php console xx:yy + * * @return bool */ public static function isRunningConsoleCommand() { - $searched = '/console'; + $searched = 'console'; $consolePos = strpos($_SERVER['SCRIPT_NAME'], $searched); $expectedConsolePos = strlen($_SERVER['SCRIPT_NAME']) - strlen($searched); - $isScriptIsConsole = $consolePos == $expectedConsolePos; + $isScriptIsConsole = ($consolePos === $expectedConsolePos); return self::isPhpCliMode() && $isScriptIsConsole; } @@ -151,7 +177,7 @@ class Common /** * Multi-byte substr() - works with UTF-8. - * + * * Calls `mb_substr` if available and falls back to `substr` if it's not. * * @param string $string @@ -175,7 +201,7 @@ class Common /** * Multi-byte strlen() - works with UTF-8 - * + * * Calls `mb_substr` if available and falls back to `substr` if not. * * @param string $string @@ -193,9 +219,9 @@ class Common /** * Multi-byte strtolower() - works with UTF-8. - * + * * Calls `mb_strtolower` if available and falls back to `strtolower` if not. - * + * * @param string $string * @return string * @api @@ -215,18 +241,18 @@ class Common /** * Sanitizes a string to help avoid XSS vulnerabilities. - * + * * This function is automatically called when {@link getRequestVar()} is called, * so you should not normally have to use it. - * + * * This function should be used when outputting data that isn't escaped and was * obtained from the user (for example when using the `|raw` twig filter on goal names). - * + * * _NOTE: Sanitized input should not be used directly in an SQL query; SQL placeholders * should still be used._ - * + * * **Implementation Details** - * + * * - [htmlspecialchars](http://php.net/manual/en/function.htmlspecialchars.php) is used to escape text. * - Single quotes are not escaped so **Piwik's amazing community** will still be * **Piwik's amazing community**. @@ -246,10 +272,11 @@ class Common if (is_numeric($value)) { return $value; } elseif (is_string($value)) { - $value = self::sanitizeInputValue($value); + $value = self::sanitizeString($value); + + if (!$alreadyStripslashed) { + // a JSON array was already stripslashed, don't do it again for each value - if (!$alreadyStripslashed) // a JSON array was already stripslashed, don't do it again for each value - { $value = self::undoMagicQuotes($value); } } elseif (is_array($value)) { @@ -272,20 +299,32 @@ class Common } /** - * Sanitize a single input value + * Sanitize a single input value and removes line breaks, tabs and null characters. * * @param string $value * @return string sanitized input */ public static function sanitizeInputValue($value) + { + $value = self::sanitizeLineBreaks($value); + $value = self::sanitizeString($value); + return $value; + } + + /** + * Sanitize a single input value + * + * @param $value + * @return string + */ + private static function sanitizeString($value) { // $_GET and $_REQUEST already urldecode()'d // decode // note: before php 5.2.7, htmlspecialchars() double encodes &#x hex items $value = html_entity_decode($value, self::HTML_ENCODING_QUOTE_STYLE, 'UTF-8'); - // filter - $value = self::sanitizeLineBreaks($value); + $value = self::sanitizeNullBytes($value); // escape $tmp = @htmlspecialchars($value, self::HTML_ENCODING_QUOTE_STYLE, 'UTF-8'); @@ -295,15 +334,17 @@ class Common // convert and escape $value = utf8_encode($value); $tmp = htmlspecialchars($value, self::HTML_ENCODING_QUOTE_STYLE, 'UTF-8'); + return $tmp; } return $tmp; } /** * Unsanitizes a single input value and returns the result. - * + * * @param string $value * @return string unsanitized input + * @api */ public static function unsanitizeInputValue($value) { @@ -315,10 +356,10 @@ class Common * * This method should be used when you need to unescape data that was obtained from * the user. - * + * * Some data in Piwik is stored sanitized (such as site name). In this case you may * have to use this method to unsanitize it in order to, for example, output it in JSON. - * + * * @param string|array $value The data to unsanitize. If an array is passed, the * array is sanitized recursively. Key values are not unsanitized. * @return string|array The unsanitized data. @@ -345,28 +386,42 @@ class Common */ private static function undoMagicQuotes($value) { - return version_compare(PHP_VERSION, '5.4', '<') - && get_magic_quotes_gpc() - ? stripslashes($value) - : $value; - } + static $shouldUndo; + + if (!isset($shouldUndo)) { + $shouldUndo = version_compare(PHP_VERSION, '5.4', '<') && get_magic_quotes_gpc(); + } + + if ($shouldUndo) { + $value = stripslashes($value); + } - /** - * - * @param string - * @return string Line breaks and line carriage removed - */ - public static function sanitizeLineBreaks($value) - { - $value = str_replace(array("\n", "\r", "\0"), '', $value); return $value; } + /** + * @param string $value + * @return string Line breaks and line carriage removed + */ + public static function sanitizeLineBreaks($value) + { + return str_replace(array("\n", "\r"), '', $value); + } + + /** + * @param string $value + * @return string Null bytes removed + */ + public static function sanitizeNullBytes($value) + { + return str_replace(array("\0"), '', $value); + } + /** * Gets a sanitized request parameter by name from the `$_GET` and `$_POST` superglobals. - * + * * Use this function to get request parameter values. **_NEVER use `$_GET` and `$_POST` directly._** - * + * * If the variable cannot be found, and a default value was not provided, an exception is raised. * * _See {@link sanitizeInputValues()} to learn more about sanitization._ @@ -376,7 +431,7 @@ class Common * @param string|null $varDefault The value to return if the request parameter cannot be found or has an empty value. * @param string|null $varType Expected type of the request variable. This parameters value must be one of the following: * `'array'`, `'int'`, `'integer'`, `'string'`, `'json'`. - * + * * If `'json'`, the string value will be `json_decode`-d and then sanitized. * @param array|null $requestArrayToUse The array to use instead of `$_GET` and `$_POST`. * @throws Exception If the request parameter doesn't exist and there is no default value, or if the request parameter @@ -389,6 +444,7 @@ class Common if (is_null($requestArrayToUse)) { $requestArrayToUse = $_GET + $_POST; } + $varDefault = self::sanitizeInputValues($varDefault); if ($varType === 'int') { // settype accepts only integer @@ -420,22 +476,36 @@ class Common // we deal w/ json differently if ($varType == 'json') { $value = self::undoMagicQuotes($requestArrayToUse[$varName]); - $value = self::json_decode($value, $assoc = true); + $value = json_decode($value, $assoc = true); return self::sanitizeInputValues($value, $alreadyStripslashed = true); } $value = self::sanitizeInputValues($requestArrayToUse[$varName]); - if (!is_null($varType)) { + if (isset($varType)) { $ok = false; if ($varType === 'string') { - if (is_string($value)) $ok = true; + if (is_string($value) || is_int($value)) { + $ok = true; + } elseif (is_float($value)) { + $value = Common::forceDotAsSeparatorForDecimalPoint($value); + $ok = true; + } } elseif ($varType === 'integer') { - if ($value == (string)(int)$value) $ok = true; + if ($value == (string)(int)$value) { + $ok = true; + } } elseif ($varType === 'float') { - if ($value == (string)(float)$value) $ok = true; + $valueToCompare = (string)(float)$value; + $valueToCompare = Common::forceDotAsSeparatorForDecimalPoint($valueToCompare); + + if ($value == $valueToCompare) { + $ok = true; + } } elseif ($varType === 'array') { - if (is_array($value)) $ok = true; + if (is_array($value)) { + $ok = true; + } } else { throw new Exception("\$varType specified is not known. It should be one of the following: array, int, integer, float, string"); } @@ -452,6 +522,7 @@ class Common } settype($value, $varType); } + return $value; } @@ -466,11 +537,17 @@ class Common */ public static function generateUniqId() { - return md5(uniqid(rand(), true)); + if (function_exists('mt_rand')) { + $rand = mt_rand(); + } else { + $rand = rand(); + } + + return md5(uniqid($rand, true)); } /** - * Configureable hash() algorithm (defaults to md5) + * Configurable hash() algorithm (defaults to md5) * * @param string $str String to be hashed * @param bool $raw_output @@ -479,14 +556,16 @@ class Common public static function hash($str, $raw_output = false) { static $hashAlgorithm = null; + if (is_null($hashAlgorithm)) { $hashAlgorithm = @Config::getInstance()->General['hash_algorithm']; } if ($hashAlgorithm) { $hash = @hash($hashAlgorithm, $str, $raw_output); - if ($hash !== false) + if ($hash !== false) { return $hash; + } } return md5($str, $raw_output); @@ -503,16 +582,13 @@ class Common public static function getRandomString($length = 16, $alphabet = "abcdefghijklmnoprstuvwxyz0123456789") { $chars = $alphabet; - $str = ''; - - list($usec, $sec) = explode(" ", microtime()); - $seed = ((float)$sec + (float)$usec) * 100000; - mt_srand($seed); + $str = ''; for ($i = 0; $i < $length; $i++) { $rand_key = mt_rand(0, strlen($chars) - 1); $str .= substr($chars, $rand_key, 1); } + return str_shuffle($str); } @@ -554,27 +630,22 @@ class Common ) { throw new Exception("visitorId is expected to be a " . Tracker::LENGTH_HEX_ID_STRING . " hex char string"); } + return self::hex2bin($id); } /** - * Convert IP address (in network address format) to presentation format. - * This is a backward compatibility function for code that only expects - * IPv4 addresses (i.e., doesn't support IPv6). + * Converts a User ID string to the Visitor ID Binary representation. * - * @see IP::N2P() - * - * This function does not support the long (or its string representation) - * returned by the built-in ip2long() function, from Piwik 1.3 and earlier. - * - * @deprecated 1.4 - * - * @param string $ip IP address in network address format + * @param $userId * @return string */ - public static function long2ip($ip) + public static function convertUserIdToVisitorIdBin($userId) { - return IP::long2ip($ip); + require_once PIWIK_INCLUDE_PATH . '/libs/PiwikTracker/PiwikTracker.php'; + $userIdHashed = \PiwikTracker::getUserIdHashed($userId); + + return self::convertVisitorIdToBin($userIdHashed); } /** @@ -653,14 +724,14 @@ class Common /** * Returns the list of parent classes for the given class. * - * @param string $klass A class name. + * @param string $class A class name. * @return string[] The list of parent classes in order from highest ancestor to the descended class. */ - public static function getClassLineage($klass) + public static function getClassLineage($class) { - $klasses = array_merge(array($klass), array_values(class_parents($klass, $autoload = false))); + $classes = array_merge(array($class), array_values(class_parents($class, $autoload = false))); - return array_reverse($klasses); + return array_reverse($classes); } /* @@ -672,14 +743,16 @@ class Common * * @see core/DataFiles/Countries.php * - * @return array Array of 3 letter continent codes + * @return array Array of 3 letter continent codes + * + * @deprecated Use Piwik\Intl\Data\Provider\RegionDataProvider instead. + * @see \Piwik\Intl\Data\Provider\RegionDataProvider::getContinentList() */ public static function getContinentsList() { - require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/Countries.php'; - - $continentsList = $GLOBALS['Piwik_ContinentList']; - return $continentsList; + /** @var RegionDataProvider $dataProvider */ + $dataProvider = StaticContainer::get('Piwik\Intl\Data\Provider\RegionDataProvider'); + return $dataProvider->getContinentList(); } /** @@ -688,19 +761,16 @@ class Common * @see core/DataFiles/Countries.php * * @param bool $includeInternalCodes - * @return array Array of (2 letter ISO codes => 3 letter continent code) + * @return array Array of (2 letter ISO codes => 3 letter continent code) + * + * @deprecated Use Piwik\Intl\Data\Provider\RegionDataProvider instead. + * @see \Piwik\Intl\Data\Provider\RegionDataProvider::getCountryList() */ public static function getCountriesList($includeInternalCodes = false) { - require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/Countries.php'; - - $countriesList = $GLOBALS['Piwik_CountryList']; - $extras = $GLOBALS['Piwik_CountryList_Extras']; - - if ($includeInternalCodes) { - return array_merge($countriesList, $extras); - } - return $countriesList; + /** @var RegionDataProvider $dataProvider */ + $dataProvider = StaticContainer::get('Piwik\Intl\Data\Provider\RegionDataProvider'); + return $dataProvider->getCountryList($includeInternalCodes); } /** @@ -711,13 +781,15 @@ class Common * @return array Array of two letter ISO codes mapped with their associated language names (in English). E.g. * `array('en' => 'English', 'ja' => 'Japanese')`. * @api + * + * @deprecated Use Piwik\Intl\Data\Provider\LanguageDataProvider instead. + * @see \Piwik\Intl\Data\Provider\LanguageDataProvider::getLanguageList() */ public static function getLanguagesList() { - require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/Languages.php'; - - $languagesList = $GLOBALS['Piwik_LanguageList']; - return $languagesList; + /** @var LanguageDataProvider $dataProvider */ + $dataProvider = StaticContainer::get('Piwik\Intl\Data\Provider\LanguageDataProvider'); + return $dataProvider->getLanguageList(); } /** @@ -728,70 +800,15 @@ class Common * @return array Array of two letter ISO language codes mapped with two letter ISO country codes: * `array('fr' => 'fr') // French => France` * @api + * + * @deprecated Use Piwik\Intl\Data\Provider\LanguageDataProvider instead. + * @see \Piwik\Intl\Data\Provider\LanguageDataProvider::getLanguageToCountryList() */ public static function getLanguageToCountryList() { - require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/LanguageToCountry.php'; - - $languagesList = $GLOBALS['Piwik_LanguageToCountry']; - return $languagesList; - } - - /** - * Returns list of search engines by URL - * - * @see core/DataFiles/SearchEngines.php - * - * @return array Array of ( URL => array( searchEngineName, keywordParameter, path, charset ) ) - */ - public static function getSearchEngineUrls() - { - require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/SearchEngines.php'; - - $searchEngines = $GLOBALS['Piwik_SearchEngines']; - - Piwik::postEvent('Referrer.addSearchEngineUrls', array(&$searchEngines)); - - return $searchEngines; - } - - /** - * Returns list of search engines by name - * - * @see core/DataFiles/SearchEngines.php - * - * @return array Array of ( searchEngineName => URL ) - */ - public static function getSearchEngineNames() - { - $searchEngines = self::getSearchEngineUrls(); - - $nameToUrl = array(); - foreach ($searchEngines as $url => $info) { - if (!isset($nameToUrl[$info[0]])) { - $nameToUrl[$info[0]] = $url; - } - } - - return $nameToUrl; - } - - /** - * Returns list of social networks by URL - * - * @see core/DataFiles/Socials.php - * - * @return array Array of ( URL => Social Network Name ) - */ - public static function getSocialUrls() - { - require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/Socials.php'; - - $socialUrls = $GLOBALS['Piwik_socialUrl']; - - Piwik::postEvent('Referrer.addSocialUrls', array(&$socialUrls)); - - return $socialUrls; + /** @var LanguageDataProvider $dataProvider */ + $dataProvider = StaticContainer::get('Piwik\Intl\Data\Provider\LanguageDataProvider'); + return $dataProvider->getLanguageToCountryList(); } /** @@ -840,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 { @@ -872,11 +889,15 @@ class Common */ public static function getCountry($lang, $enableLanguageToCountryGuess, $ip) { - if (empty($lang) || strlen($lang) < 2 || $lang == 'xx') { - return 'xx'; + if (empty($lang) || strlen($lang) < 2 || $lang == self::LANGUAGE_CODE_INVALID) { + return self::LANGUAGE_CODE_INVALID; } - $validCountries = self::getCountriesList(); + /** @var RegionDataProvider $dataProvider */ + $dataProvider = StaticContainer::get('Piwik\Intl\Data\Provider\RegionDataProvider'); + + $validCountries = $dataProvider->getCountryList(); + return self::extractCountryCodeFromBrowserLanguage($lang, $validCountries, $enableLanguageToCountryGuess); } @@ -890,7 +911,10 @@ class Common */ public static function extractCountryCodeFromBrowserLanguage($browserLanguage, $validCountries, $enableLanguageToCountryGuess) { - $langToCountry = self::getLanguageToCountryList(); + /** @var LanguageDataProvider $dataProvider */ + $dataProvider = StaticContainer::get('Piwik\Intl\Data\Provider\LanguageDataProvider'); + + $langToCountry = $dataProvider->getLanguageToCountryList(); if ($enableLanguageToCountryGuess) { if (preg_match('/^([a-z]{2,3})(?:,|;|$)/', $browserLanguage, $matches)) { @@ -909,51 +933,90 @@ class Common } } } - return 'xx'; + return self::LANGUAGE_CODE_INVALID; } /** - * Returns the visitor language based only on the Browser 'accepted language' information + * Returns the language and region string, based only on the Browser 'accepted language' information. + * * The language tag is defined by ISO 639-1 * * @param string $browserLanguage Browser's accepted langauge header * @param array $validLanguages array of valid language codes - * @return string 2 letter ISO 639 code + * @return string 2 letter ISO 639 code 'es' (Spanish) */ - public static function extractLanguageCodeFromBrowserLanguage($browserLanguage, $validLanguages) + public static function extractLanguageCodeFromBrowserLanguage($browserLanguage, $validLanguages = array()) { - // assumes language preference is sorted; - // does not handle language-script-region tags or language range (*) - if (!empty($validLanguages) && preg_match_all('/(?:^|,)([a-z]{2,3})([-][a-z]{2})?/', $browserLanguage, $matches, PREG_SET_ORDER)) { - foreach ($matches as $parts) { - if (count($parts) == 3) { - // match locale (language and location) - if (in_array($parts[1] . $parts[2], $validLanguages)) { - return $parts[1] . $parts[2]; - } + $validLanguages = self::checkValidLanguagesIsSet($validLanguages); + $languageRegionCode = self::extractLanguageAndRegionCodeFromBrowserLanguage($browserLanguage, $validLanguages); + + if (strlen($languageRegionCode) == 2) { + $languageCode = $languageRegionCode; + } else { + $languageCode = substr($languageRegionCode, 0, 2); + } + if (in_array($languageCode, $validLanguages)) { + return $languageCode; + } + return self::LANGUAGE_CODE_INVALID; + } + + /** + * Returns the language and region string, based only on the Browser 'accepted language' information. + * * The language tag is defined by ISO 639-1 + * * The region tag is defined by ISO 3166-1 + * + * @param string $browserLanguage Browser's accepted langauge header + * @param array $validLanguages array of valid language codes. Note that if the array includes "fr" then it will consider all regional variants of this language valid, such as "fr-ca" etc. + * @return string 2 letter ISO 639 code 'es' (Spanish) or if found, includes the region as well: 'es-ar' + */ + public static function extractLanguageAndRegionCodeFromBrowserLanguage($browserLanguage, $validLanguages = array()) + { + $validLanguages = self::checkValidLanguagesIsSet($validLanguages); + + if (!preg_match_all('/(?:^|,)([a-z]{2,3})([-][a-z]{2})?/', $browserLanguage, $matches, PREG_SET_ORDER)) { + return self::LANGUAGE_CODE_INVALID; + } + foreach ($matches as $parts) { + $langIso639 = $parts[1]; + if (empty($langIso639)) { + continue; + } + + // If a region tag is found eg. "fr-ca" + if (count($parts) == 3) { + $regionIso3166 = $parts[2]; // eg. "-ca" + + if (in_array($langIso639 . $regionIso3166, $validLanguages)) { + return $langIso639 . $regionIso3166; } - // match language only (where no region provided) - if (in_array($parts[1], $validLanguages)) { - return $parts[1]; + + if (in_array($langIso639, $validLanguages)) { + return $langIso639 . $regionIso3166; } } + // eg. "fr" or "es" + if (in_array($langIso639, $validLanguages)) { + return $langIso639; + } } - return 'xx'; + return self::LANGUAGE_CODE_INVALID; } /** * Returns the continent of a given country * - * @param string $country 2 letters isocode + * @param string $country 2 letters iso code * * @return string Continent (3 letters code : afr, asi, eur, amn, ams, oce) */ public static function getContinent($country) { - $countryList = self::getCountriesList(); - if (isset($countryList[$country])) { - return $countryList[$country]; - } - return 'unk'; + /** @var RegionDataProvider $dataProvider */ + $dataProvider = StaticContainer::get('Piwik\Intl\Data\Provider\RegionDataProvider'); + + $countryList = $dataProvider->getCountryList(); + + return isset($countryList[$country]) ? $countryList[$country] : 'unk'; } /* @@ -995,10 +1058,10 @@ class Common /** * Returns a string with a comma separated list of placeholders for use in an SQL query. Used mainly * to fill the `IN (...)` part of a query. - * + * * @param array|string $fields The names of the mysql table fields to bind, e.g. * `array(fieldName1, fieldName2, fieldName3)`. - * + * * _Note: The content of the array isn't important, just its length._ * @return string The placeholder string, e.g. `"?, ?, ?"`. * @api @@ -1015,6 +1078,22 @@ class Common return '?' . str_repeat(',?', $count - 1); } + /** + * Force the separator for decimal point to be a dot. See https://github.com/piwik/piwik/issues/6435 + * If for instance a German locale is used it would be a comma otherwise. + * + * @param float|string $value + * @return string + */ + public static function forceDotAsSeparatorForDecimalPoint($value) + { + if (null === $value || false === $value) { + return $value; + } + + return str_replace(',', '.', $value); + } + /** * Sets outgoing header. * @@ -1024,23 +1103,62 @@ class Common public static function sendHeader($header, $replace = true) { // don't send header in CLI mode - if(Common::isPhpCliMode()) { - return; - } - if (isset($GLOBALS['PIWIK_TRACKER_LOCAL_TRACKING']) && $GLOBALS['PIWIK_TRACKER_LOCAL_TRACKING']) { - @header($header, $replace); - } else { + if (!Common::isPhpCliMode() and !headers_sent()) { header($header, $replace); } } + /** + * Sends the given response code if supported. + * + * @param int $code Eg 204 + * + * @throws Exception + */ + public static function sendResponseCode($code) + { + $messages = array( + 200 => 'Ok', + 204 => 'No Response', + 301 => 'Moved Permanently', + 302 => 'Found', + 304 => 'Not Modified', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 403 => 'Forbidden', + 404 => 'Not Found', + 500 => 'Internal Server Error', + 503 => 'Service Unavailable', + ); + + if (!array_key_exists($code, $messages)) { + throw new Exception('Response code not supported: ' . $code); + } + + if (strpos(PHP_SAPI, '-fcgi') === false) { + $key = 'HTTP/1.1'; + + if (array_key_exists('SERVER_PROTOCOL', $_SERVER) + && strlen($_SERVER['SERVER_PROTOCOL']) < 15 + && strlen($_SERVER['SERVER_PROTOCOL']) > 1) { + $key = $_SERVER['SERVER_PROTOCOL']; + } + } else { + // FastCGI + $key = 'Status:'; + } + + $message = $messages[$code]; + Common::sendHeader($key . ' ' . $code . ' ' . $message); + } + /** * Returns the ID of the current LocationProvider (see UserCountry plugin code) from * the Tracker cache. */ public static function getCurrentLocationProviderId() { - $cache = Cache::getCacheGeneral(); + $cache = TrackerCache::getCacheGeneral(); return empty($cache['currentLocationProviderId']) ? DefaultProvider::ID : $cache['currentLocationProviderId']; @@ -1049,11 +1167,11 @@ class Common /** * Marks an orphaned object for garbage collection. * - * For more information: {@link http://dev.piwik.org/trac/ticket/374} - * @param $var The object to destroy. + * For more information: {@link https://github.com/piwik/piwik/issues/374} + * @param mixed $var The object to destroy. * @api */ - static public function destroy(&$var) + public static function destroy(&$var) { if (is_object($var) && method_exists($var, '__destruct')) { $var->__destruct(); @@ -1062,27 +1180,62 @@ class Common $var = null; } - static public function printDebug($info = '') + /** + * @todo This method is weird, it's debugging statements but seem to only work for the tracker, maybe it + * should be moved elsewhere + */ + public static function printDebug($info = '') { if (isset($GLOBALS['PIWIK_TRACKER_DEBUG']) && $GLOBALS['PIWIK_TRACKER_DEBUG']) { + if (!headers_sent()) { + // prevent XSS in tracker debug output + header('Content-type: text/plain'); + } if (is_object($info)) { $info = var_export($info, true); } - Log::getInstance()->setLogLevel(Log::DEBUG); + $logger = StaticContainer::get('Psr\Log\LoggerInterface'); if (is_array($info) || is_object($info)) { $info = Common::sanitizeInputValues($info); $out = var_export($info, true); foreach (explode("\n", $out) as $line) { - Log::debug($line); + $logger->debug($line); } } else { foreach (explode("\n", $info) as $line) { - Log::debug(htmlspecialchars($line, ENT_QUOTES)); + $logger->debug($line); } } } } + + /** + * Returns true if the request is an AJAX request. + * + * @return bool + */ + public static function isXmlHttpRequest() + { + return isset($_SERVER['HTTP_X_REQUESTED_WITH']) + && (strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'); + } + + /** + * @param $validLanguages + * @return array + */ + protected static function checkValidLanguagesIsSet($validLanguages) + { + /** @var LanguageDataProvider $dataProvider */ + $dataProvider = StaticContainer::get('Piwik\Intl\Data\Provider\LanguageDataProvider'); + + if (empty($validLanguages)) { + $validLanguages = array_keys($dataProvider->getLanguageList()); + return $validLanguages; + } + return $validLanguages; + } } diff --git a/www/analytics/core/Composer/ScriptHandler.php b/www/analytics/core/Composer/ScriptHandler.php new file mode 100644 index 00000000..0595473f --- /dev/null +++ b/www/analytics/core/Composer/ScriptHandler.php @@ -0,0 +1,47 @@ +optionName = $optionName; + $this->logger = $logger ?: StaticContainer::get('Psr\Log\LoggerInterface'); + } + + /** + * Queries the option table and returns all items in this list. + * + * @return array + */ + public function getAll() + { + $result = $this->getListOptionValue(); + + foreach ($result as $key => $item) { + // remove non-array items (unexpected state, though can happen when upgrading from an old Piwik) + if (is_array($item)) { + $this->logger->info("Found array item in DistributedList option value '{name}': {data}", array( + 'name' => $this->optionName, + 'data' => var_export($result, true) + )); + + unset($result[$key]); + } + } + + return $result; + } + + /** + * Sets the contents of the list in the option table. + * + * @param string[] $items + */ + public function setAll($items) + { + foreach ($items as $key => &$item) { + if (is_array($item)) { + throw new \InvalidArgumentException("Array item encountered in DistributedList::setAll() [ key = $key ]."); + } else { + $item = (string)$item; + } + } + + Option::set($this->optionName, serialize($items)); + } + + /** + * Adds one or more items to the list in the option table. + * + * @param string|array $item + */ + public function add($item) + { + $allItems = $this->getAll(); + if (is_array($item)) { + $allItems = array_merge($allItems, $item); + } else { + $allItems[] = $item; + } + + $this->setAll($allItems); + } + + /** + * Removes one or more items by value from the list in the option table. + * + * Does not preserve array keys. + * + * @param string|array $items + */ + public function remove($items) + { + if (!is_array($items)) { + $items = array($items); + } + + $allItems = $this->getAll(); + + foreach ($items as $item) { + $existingIndex = array_search($item, $allItems); + if ($existingIndex === false) { + return; + } + + unset($allItems[$existingIndex]); + } + + $this->setAll(array_values($allItems)); + } + + /** + * Removes one or more items by index from the list in the option table. + * + * Does not preserve array keys. + * + * @param int[]|int $indices + */ + public function removeByIndex($indices) + { + if (!is_array($indices)) { + $indices = array($indices); + } + + $indices = array_unique($indices); + + $allItems = $this->getAll(); + foreach ($indices as $index) { + unset($allItems[$index]); + } + + $this->setAll(array_values($allItems)); + } + + protected function getListOptionValue() + { + Option::clearCachedOption($this->optionName); + $array = Option::get($this->optionName); + + $result = array(); + if ($array + && ($array = unserialize($array)) + && count($array) + ) { + $result = $array; + } + return $result; + } +} diff --git a/www/analytics/core/Config.php b/www/analytics/core/Config.php index 6fe19d2e..adc3ba94 100644 --- a/www/analytics/core/Config.php +++ b/www/analytics/core/Config.php @@ -1,6 +1,6 @@ General['minimum_memory_limit'] = 256; * Config::getInstance()->forceSave(); - * + * * **Setting an entire section:** - * + * * Config::getInstance()->MySection = array('myoption' => 1); * Config::getInstance()->forceSave(); - * - * @method static \Piwik\Config getInstance() */ -class Config extends Singleton +class Config { const DEFAULT_LOCAL_CONFIG_PATH = '/config/config.ini.php'; const DEFAULT_COMMON_CONFIG_PATH = '/config/common.config.ini.php'; const DEFAULT_GLOBAL_CONFIG_PATH = '/config/global.ini.php'; - /** - * Contains configuration files values - * - * @var array - */ - protected $initialized = false; - protected $configGlobal = array(); - protected $configLocal = array(); - protected $configCommon = array(); - protected $configCache = array(); - protected $pathGlobal = null; - protected $pathCommon = null; - protected $pathLocal = null; - /** * @var boolean */ - protected $isTest = false; + protected $doNotWriteConfigInTests = false; /** - * Constructor + * @var GlobalSettingsProvider */ - public function __construct($pathGlobal = null, $pathLocal = null, $pathCommon = null) + protected $settings; + + /** + * @return Config + */ + public static function getInstance() { - $this->pathGlobal = $pathGlobal ?: self::getGlobalConfigPath(); - $this->pathCommon = $pathCommon ?: self::getCommonConfigPath(); - $this->pathLocal = $pathLocal ?: self::getLocalConfigPath(); + return StaticContainer::get('Piwik\Config'); + } + + public function __construct(GlobalSettingsProvider $settings) + { + $this->settings = $settings; } /** @@ -80,7 +74,7 @@ class Config extends Singleton */ public function getLocalPath() { - return $this->pathLocal; + return $this->settings->getPathLocal(); } /** @@ -90,7 +84,7 @@ class Config extends Singleton */ public function getGlobalPath() { - return $this->pathGlobal; + return $this->settings->getPathGlobal(); } /** @@ -100,72 +94,7 @@ class Config extends Singleton */ public function getCommonPath() { - return $this->pathCommon; - } - - /** - * Enable test environment - * - * @param string $pathLocal - * @param string $pathGlobal - * @param string $pathCommon - */ - public function setTestEnvironment($pathLocal = null, $pathGlobal = null, $pathCommon = null, $allowSaving = false) - { - if (!$allowSaving) { - $this->isTest = true; - } - - $this->clear(); - - $this->pathLocal = $pathLocal ?: Config::getLocalConfigPath(); - $this->pathGlobal = $pathGlobal ?: Config::getGlobalConfigPath(); - $this->pathCommon = $pathCommon ?: Config::getCommonConfigPath(); - - $this->init(); - - // this proxy will not record any data in the production database. - // this provides security for Piwik installs and tests were setup. - if (isset($this->configGlobal['database_tests']) - || isset($this->configLocal['database_tests']) - ) { - $this->__get('database_tests'); - $this->configCache['database'] = $this->configCache['database_tests']; - } - - // Ensure local mods do not affect tests - if (empty($pathGlobal)) { - $this->configCache['log'] = $this->configGlobal['log']; - $this->configCache['Debug'] = $this->configGlobal['Debug']; - $this->configCache['mail'] = $this->configGlobal['mail']; - $this->configCache['General'] = $this->configGlobal['General']; - $this->configCache['Segments'] = $this->configGlobal['Segments']; - $this->configCache['Tracker'] = $this->configGlobal['Tracker']; - $this->configCache['Deletelogs'] = $this->configGlobal['Deletelogs']; - $this->configCache['Deletereports'] = $this->configGlobal['Deletereports']; - } - - // for unit tests, we set that no plugin is installed. This will force - // the test initialization to create the plugins tables, execute ALTER queries, etc. - $this->configCache['PluginsInstalled'] = array('PluginsInstalled' => array()); - - // DevicesDetection plugin is not yet enabled by default - if (isset($configGlobal['Plugins'])) { - $this->configCache['Plugins'] = $this->configGlobal['Plugins']; - $this->configCache['Plugins']['Plugins'][] = 'DevicesDetection'; - } - if (isset($configGlobal['Plugins_Tracker'])) { - $this->configCache['Plugins_Tracker'] = $this->configGlobal['Plugins_Tracker']; - $this->configCache['Plugins_Tracker']['Plugins_Tracker'][] = 'DevicesDetection'; - } - - // to avoid weird session error in travis - if (empty($pathGlobal)) { - $configArray = &$this->configCache; - } else { - $configArray = &$this->configLocal; - } - $configArray['General']['session_save_handler'] = 'dbtables'; + return $this->settings->getPathCommon(); } /** @@ -173,7 +102,7 @@ class Config extends Singleton * * @return string */ - protected static function getGlobalConfigPath() + public static function getGlobalConfigPath() { return PIWIK_USER_PATH . self::DEFAULT_GLOBAL_CONFIG_PATH; } @@ -204,6 +133,8 @@ class Config extends Singleton private static function getLocalConfigInfoForHostname($hostname) { + // Remove any port number to get actual hostname + $hostname = Url::getHostSanitized($hostname); $perHostFilename = $hostname . '.config.ini.php'; $pathDomainConfig = PIWIK_USER_PATH . '/config/' . $perHostFilename; @@ -225,11 +156,25 @@ class Config extends Singleton return array( 'action_url_category_delimiter' => $general['action_url_category_delimiter'], 'autocomplete_min_sites' => $general['autocomplete_min_sites'], - 'datatable_export_range_as_day' => $general['datatable_export_range_as_day'] + 'datatable_export_range_as_day' => $general['datatable_export_range_as_day'], + 'datatable_row_limits' => $this->getDatatableRowLimits(), + 'are_ads_enabled' => $general['piwik_pro_ads_enabled'] ); } - protected static function getByDomainConfigPath() + /** + * @param $general + * @return mixed + */ + private function getDatatableRowLimits() + { + $limits = $this->General['datatable_row_limits']; + $limits = explode(",", $limits); + $limits = array_map('trim', $limits); + return $limits; + } + + public static function getByDomainConfigPath() { $host = self::getHostname(); $hostConfig = self::getLocalConfigInfoForHostname($host); @@ -242,9 +187,19 @@ class Config extends Singleton return false; } - protected static function getHostname() + /** + * Returns the hostname of the current request (without port number) + * + * @return string + */ + public static function getHostname() { - $host = Url::getHost($checkIfTrusted = false); // Check trusted requires config file which is not ready yet + // Check trusted requires config file which is not ready yet + $host = Url::getHost($checkIfTrusted = false); + + // Remove any port number to get actual hostname + $host = Url::getHostSanitized($host); + return $host; } @@ -259,19 +214,26 @@ class Config extends Singleton * @param string $hostname eg piwik.example.com * @return string * @throws \Exception In case the domain contains not allowed characters + * @internal */ public function forceUsageOfLocalHostnameConfig($hostname) { - $hostConfig = static::getLocalConfigInfoForHostname($hostname); + $hostConfig = self::getLocalConfigInfoForHostname($hostname); - if (!Filesystem::isValidFilename($hostConfig['file'])) { - throw new Exception('Hostname is not valid'); + $filename = $hostConfig['file']; + if (!Filesystem::isValidFilename($filename)) { + throw new Exception('Piwik domain is not a valid looking hostname (' . $filename . ').'); } - $this->pathLocal = $hostConfig['path']; - $this->configLocal = array(); - $this->initialized = false; - return $this->pathLocal; + $pathLocal = $hostConfig['path']; + + try { + $this->reload($pathLocal); + } catch (Exception $ex) { + // pass (not required for local file to exist at this point) + } + + return $pathLocal; } /** @@ -281,99 +243,57 @@ class Config extends Singleton */ public function isFileWritable() { - return is_writable($this->pathLocal); + return is_writable($this->settings->getPathLocal()); } /** * Clear in-memory configuration so it can be reloaded + * @deprecated since v2.12.0 */ public function clear() { - $this->configGlobal = array(); - $this->configLocal = array(); - $this->configCache = array(); - $this->initialized = false; + $this->reload(); } /** * Read configuration from files into memory * * @throws Exception if local config file is not readable; exits for other errors + * @deprecated since v2.12.0 */ public function init() { - $this->initialized = true; - $reportError = SettingsServer::isTrackerApiRequest(); - - // read defaults from global.ini.php - if (!is_readable($this->pathGlobal) && $reportError) { - Piwik_ExitWithMessage(Piwik::translate('General_ExceptionConfigurationFileNotFound', array($this->pathGlobal))); - } - - $this->configGlobal = _parse_ini_file($this->pathGlobal, true); - - if (empty($this->configGlobal) && $reportError) { - Piwik_ExitWithMessage(Piwik::translate('General_ExceptionUnreadableFileDisabledMethod', array($this->pathGlobal, "parse_ini_file()"))); - } - - $this->configCommon = _parse_ini_file($this->pathCommon, true); - - // Check config.ini.php last - $this->checkLocalConfigFound(); - - $this->configLocal = _parse_ini_file($this->pathLocal, true); - if (empty($this->configLocal) && $reportError) { - Piwik_ExitWithMessage(Piwik::translate('General_ExceptionUnreadableFileDisabledMethod', array($this->pathLocal, "parse_ini_file()"))); - } + $this->reload(); } + /** + * Reloads config data from disk. + * + * @throws \Exception if the global config file is not found and this is a tracker request, or + * if the local config file is not found and this is NOT a tracker request. + */ + protected function reload($pathLocal = null, $pathGlobal = null, $pathCommon = null) + { + $this->settings->reload($pathGlobal, $pathLocal, $pathCommon); + } + + /** + * @deprecated + */ public function existsLocalConfig() { - return is_readable($this->pathLocal); + return is_readable($this->getLocalPath()); } - public function checkLocalConfigFound() + public function deleteLocalConfig() { - if (!$this->existsLocalConfig()) { - throw new Exception(Piwik::translate('General_ExceptionConfigurationFileNotFound', array($this->pathLocal))); + $configLocal = $this->getLocalPath(); + + if(file_exists($configLocal)){ + @unlink($configLocal); } } - /** - * Decode HTML entities - * - * @param mixed $values - * @return mixed - */ - protected function decodeValues($values) - { - if (is_array($values)) { - foreach ($values as &$value) { - $value = $this->decodeValues($value); - } - return $values; - } - return html_entity_decode($values, ENT_COMPAT, 'UTF-8'); - } - - /** - * Encode HTML entities - * - * @param mixed $values - * @return mixed - */ - protected function encodeValues($values) - { - if (is_array($values)) { - foreach ($values as &$value) { - $value = $this->encodeValues($value); - } - } else { - $values = htmlentities($values, ENT_COMPAT, 'UTF-8'); - } - return $values; - } - /** * Returns a configuration value or section by name. * @@ -385,85 +305,32 @@ class Config extends Singleton */ public function &__get($name) { - if (!$this->initialized) { - $this->init(); - - // must be called here, not in init(), since setTestEnvironment() calls init(). (this avoids - // infinite recursion) - Piwik::postTestEvent('Config.createConfigSingleton', - array($this, &$this->configCache, &$this->configLocal)); - } - - // check cache for merged section - if (isset($this->configCache[$name])) { - $tmp =& $this->configCache[$name]; - return $tmp; - } - - $section = $this->getFromGlobalConfig($name); - $sectionCommon = $this->getFromCommonConfig($name); - if(empty($section) && !empty($sectionCommon)) { - $section = $sectionCommon; - } elseif(!empty($section) && !empty($sectionCommon)) { - $section = $this->array_merge_recursive_distinct($section, $sectionCommon); - } - - if (isset($this->configLocal[$name])) { - // local settings override the global defaults - $section = $section - ? array_merge($section, $this->configLocal[$name]) - : $this->configLocal[$name]; - } - - if ($section === null && $name = 'superuser') { - $user = $this->getConfigSuperUserForBackwardCompatibility(); - return $user; - } else if ($section === null) { - throw new Exception("Error while trying to read a specific config file entry '$name' from your configuration files.If you just completed a Piwik upgrade, please check that the file config/global.ini.php was overwritten by the latest Piwik version."); - } - - // cache merged section for later - $this->configCache[$name] = $this->decodeValues($section); - $tmp =& $this->configCache[$name]; - - return $tmp; + $section =& $this->settings->getIniFileChain()->get($name); + return $section; } /** - * @deprecated since version 2.0.4 + * @api */ - public function getConfigSuperUserForBackwardCompatibility() - { - try { - $db = Db::get(); - $user = $db->fetchRow("SELECT login, email, password - FROM " . Common::prefixTable("user") . " - WHERE superuser_access = 1 - ORDER BY date_registered ASC LIMIT 1"); - - if (!empty($user)) { - $user['bridge'] = 1; - return $user; - } - } catch (Exception $e) {} - - return array(); - } - public function getFromGlobalConfig($name) { - if (isset($this->configGlobal[$name])) { - return $this->configGlobal[$name]; - } - return null; + return $this->settings->getIniFileChain()->getFrom($this->getGlobalPath(), $name); } + /** + * @api + */ public function getFromCommonConfig($name) { - if (isset($this->configCommon[$name])) { - return $this->configCommon[$name]; - } - return null; + return $this->settings->getIniFileChain()->getFrom($this->getCommonPath(), $name); + } + + /** + * @api + */ + public function getFromLocalConfig($name) + { + return $this->settings->getIniFileChain()->getFrom($this->getLocalPath(), $name); } /** @@ -475,154 +342,24 @@ class Config extends Singleton */ public function __set($name, $value) { - $this->configCache[$name] = $value; - } - - /** - * Comparison function - * - * @param mixed $elem1 - * @param mixed $elem2 - * @return int; - */ - public static function compareElements($elem1, $elem2) - { - if (is_array($elem1)) { - if (is_array($elem2)) { - return strcmp(serialize($elem1), serialize($elem2)); - } - - return 1; - } - - if (is_array($elem2)) { - return -1; - } - - if ((string)$elem1 === (string)$elem2) { - return 0; - } - - return ((string)$elem1 > (string)$elem2) ? 1 : -1; - } - - /** - * Compare arrays and return difference, such that: - * - * $modified = array_merge($original, $difference); - * - * @param array $original original array - * @param array $modified modified array - * @return array differences between original and modified - */ - public function array_unmerge($original, $modified) - { - // return key/value pairs for keys in $modified but not in $original - // return key/value pairs for keys in both $modified and $original, but values differ - // ignore keys that are in $original but not in $modified - - return array_udiff_assoc($modified, $original, array(__CLASS__, 'compareElements')); + $this->settings->getIniFileChain()->set($name, $value); } /** * Dump config * - * @param array $configLocal - * @param array $configGlobal - * @param array $configCommon - * @param array $configCache - * @return string + * @return string|null + * @throws \Exception */ - public function dumpConfig($configLocal, $configGlobal, $configCommon, $configCache) + public function dumpConfig() { - $dirty = false; + $chain = $this->settings->getIniFileChain(); - $output = "; DO NOT REMOVE THIS LINE\n"; - $output .= "; file automatically generated or modified by Piwik; you can manually override the default values in global.ini.php by redefining them in this file.\n"; - - if (!$configCache) { - return false; - } - - // If there is a common.config.ini.php, this will ensure config.ini.php does not duplicate its values - if(!empty($configCommon)) { - $configGlobal = $this->array_merge_recursive_distinct($configGlobal, $configCommon); - } - - if ($configLocal) { - foreach ($configLocal as $name => $section) { - if (!isset($configCache[$name])) { - $configCache[$name] = $this->decodeValues($section); - } - } - } - - $sectionNames = array_unique(array_merge(array_keys($configGlobal), array_keys($configCache))); - - foreach ($sectionNames as $section) { - if (!isset($configCache[$section])) { - continue; - } - - // Only merge if the section exists in global.ini.php (in case a section only lives in config.ini.php) - - // get local and cached config - $local = isset($configLocal[$section]) ? $configLocal[$section] : array(); - $config = $configCache[$section]; - - // remove default values from both (they should not get written to local) - if (isset($configGlobal[$section])) { - $config = $this->array_unmerge($configGlobal[$section], $configCache[$section]); - $local = $this->array_unmerge($configGlobal[$section], $local); - } - - // if either local/config have non-default values and the other doesn't, - // OR both have values, but different values, we must write to config.ini.php - if (empty($local) xor empty($config) - || (!empty($local) - && !empty($config) - && self::compareElements($config, $configLocal[$section])) - ) { - $dirty = true; - } - - // no point in writing empty sections, so skip if the cached section is empty - if (empty($config)) { - continue; - } - - $output .= "[$section]\n"; - - foreach ($config as $name => $value) { - $value = $this->encodeValues($value); - - if (is_numeric($name)) { - $name = $section; - $value = array($value); - } - - if (is_array($value)) { - foreach ($value as $currentValue) { - $output .= $name . "[] = \"$currentValue\"\n"; - } - } else { - if (!is_numeric($value)) { - $value = "\"$value\""; - } - $output .= $name . ' = ' . $value . "\n"; - } - } - - $output .= "\n"; - } - - if ($dirty) { - return $output; - } - return false; + $header = "; DO NOT REMOVE THIS LINE\n"; + $header .= "; file automatically generated or modified by Piwik; you can manually override the default values in global.ini.php by redefining them in this file.\n"; + return $chain->dumpChanges($header); } - /** * Write user configuration file * @@ -635,22 +372,24 @@ class Config extends Singleton * * @throws \Exception if config file not writable */ - protected function writeConfig($configLocal, $configGlobal, $configCommon, $configCache, $pathLocal, $clear = true) + protected function writeConfig($clear = true) { - if ($this->isTest) { + if ($this->doNotWriteConfigInTests) { return; } - $output = $this->dumpConfig($configLocal, $configGlobal, $configCommon, $configCache); - if ($output !== false) { - $success = @file_put_contents($pathLocal, $output); - if (!$success) { + $output = $this->dumpConfig(); + if ($output !== null + && $output !== false + ) { + $success = @file_put_contents($this->getLocalPath(), $output); + if ($success === false) { throw $this->getConfigNotWritableException(); } } if ($clear) { - $this->clear(); + $this->reload(); } } @@ -662,7 +401,7 @@ class Config extends Singleton */ public function forceSave() { - $this->writeConfig($this->configLocal, $this->configGlobal, $this->configCommon, $this->configCache, $this->pathLocal); + $this->writeConfig(); } /** @@ -670,45 +409,7 @@ class Config extends Singleton */ public function getConfigNotWritableException() { - $path = "config/" . basename($this->pathLocal); + $path = "config/" . basename($this->getLocalPath()); return new Exception(Piwik::translate('General_ConfigFileIsNotWritable', array("(" . $path . ")", ""))); } - - /** - * array_merge_recursive does indeed merge arrays, but it converts values with duplicate - * keys to arrays rather than overwriting the value in the first array with the duplicate - * value in the second array, as array_merge does. I.e., with array_merge_recursive, - * this happens (documented behavior): - * - * array_merge_recursive(array('key' => 'org value'), array('key' => 'new value')); - * => array('key' => array('org value', 'new value')); - * - * array_merge_recursive_distinct does not change the datatypes of the values in the arrays. - * Matching keys' values in the second array overwrite those in the first array, as is the - * case with array_merge, i.e.: - * - * array_merge_recursive_distinct(array('key' => 'org value'), array('key' => 'new value')); - * => array('key' => array('new value')); - * - * Parameters are passed by reference, though only for performance reasons. They're not - * altered by this function. - * - * @param array $array1 - * @param array $array2 - * @return array - * @author Daniel - * @author Gabriel Sobrinho - */ - function array_merge_recursive_distinct ( array &$array1, array &$array2 ) - { - $merged = $array1; - foreach ( $array2 as $key => &$value ) { - if ( is_array ( $value ) && isset ( $merged [$key] ) && is_array ( $merged [$key] ) ) { - $merged [$key] = $this->array_merge_recursive_distinct ( $merged [$key], $value ); - } else { - $merged [$key] = $value; - } - } - return $merged; - } } diff --git a/www/analytics/core/Config/ConfigNotFoundException.php b/www/analytics/core/Config/ConfigNotFoundException.php new file mode 100644 index 00000000..5860367b --- /dev/null +++ b/www/analytics/core/Config/ConfigNotFoundException.php @@ -0,0 +1,16 @@ +reload($defaultSettingsFiles, $userSettingsFile); + } + + /** + * Return setting section by reference. + * + * @param string $name + * @return mixed + */ + public function &get($name) + { + if (!isset($this->mergedSettings[$name])) { + $this->mergedSettings[$name] = array(); + } + + $result =& $this->mergedSettings[$name]; + return $result; + } + + /** + * Return setting section from a specific file, rather than the current merged settings. + * + * @param string $file The path of the file. Should be the path used in construction or reload(). + * @param string $name The name of the section to access. + */ + public function getFrom($file, $name) + { + return @$this->settingsChain[$file][$name]; + } + + /** + * Sets a setting value. + * + * @param string $name + * @param mixed $value + */ + public function set($name, $value) + { + $this->mergedSettings[$name] = $value; + } + + /** + * Returns all settings. Changes made to the array result will be reflected in the + * IniFileChain instance. + * + * @return array + */ + public function &getAll() + { + return $this->mergedSettings; + } + + /** + * Dumps the current in-memory setting values to a string in INI format and returns it. + * + * @param string $header The header of the output INI file. + * @return string The dumped INI contents. + */ + public function dump($header = '') + { + return $this->dumpSettings($this->mergedSettings, $header); + } + + /** + * Writes the difference of the in-memory setting values and the on-disk user settings file setting + * values to a string in INI format, and returns it. + * + * If a config section is identical to the default settings section (as computed by merging + * all default setting files), it is not written to the user settings file. + * + * @param string $header The header of the INI output. + * @return string The dumped INI contents. + */ + public function dumpChanges($header = '') + { + $userSettingsFile = $this->getUserSettingsFile(); + + $defaultSettings = $this->getMergedDefaultSettings(); + $existingMutableSettings = $this->settingsChain[$userSettingsFile]; + + $dirty = false; + + $configToWrite = array(); + foreach ($this->mergedSettings as $sectionName => $changedSection) { + if(isset($existingMutableSettings[$sectionName])){ + $existingMutableSection = $existingMutableSettings[$sectionName]; + } else{ + $existingMutableSection = array(); + } + + // remove default values from both (they should not get written to local) + if (isset($defaultSettings[$sectionName])) { + $changedSection = $this->arrayUnmerge($defaultSettings[$sectionName], $changedSection); + $existingMutableSection = $this->arrayUnmerge($defaultSettings[$sectionName], $existingMutableSection); + } + + // if either local/config have non-default values and the other doesn't, + // OR both have values, but different values, we must write to config.ini.php + if (empty($changedSection) xor empty($existingMutableSection) + || (!empty($changedSection) + && !empty($existingMutableSection) + && self::compareElements($changedSection, $existingMutableSection)) + ) { + $dirty = true; + } + + $configToWrite[$sectionName] = $changedSection; + } + + if ($dirty) { + // sort config sections by how early they appear in the file chain + $self = $this; + uksort($configToWrite, function ($sectionNameLhs, $sectionNameRhs) use ($self) { + $lhsIndex = $self->findIndexOfFirstFileWithSection($sectionNameLhs); + $rhsIndex = $self->findIndexOfFirstFileWithSection($sectionNameRhs); + + if ($lhsIndex == $rhsIndex) { + $lhsIndexInFile = $self->getIndexOfSectionInFile($lhsIndex, $sectionNameLhs); + $rhsIndexInFile = $self->getIndexOfSectionInFile($rhsIndex, $sectionNameRhs); + + if ($lhsIndexInFile == $rhsIndexInFile) { + return 0; + } elseif ($lhsIndexInFile < $rhsIndexInFile) { + return -1; + } else { + return 1; + } + } elseif ($lhsIndex < $rhsIndex) { + return -1; + } else { + return 1; + } + }); + + return $this->dumpSettings($configToWrite, $header); + } else { + return null; + } + } + + /** + * Reloads settings from disk. + */ + public function reload($defaultSettingsFiles = array(), $userSettingsFile = null) + { + if (!empty($defaultSettingsFiles) + || !empty($userSettingsFile) + ) { + $this->resetSettingsChain($defaultSettingsFiles, $userSettingsFile); + } + + $reader = new IniReader(); + foreach ($this->settingsChain as $file => $ignore) { + if (is_readable($file)) { + try { + $contents = $reader->readFile($file); + $this->settingsChain[$file] = $this->decodeValues($contents); + } catch (IniReadingException $ex) { + throw new IniReadingException('Unable to read INI file {' . $file . '}: ' . $ex->getMessage() . "\n Your host may have disabled parse_ini_file()."); + } + + $this->decodeValues($this->settingsChain[$file]); + } + } + + $merged = $this->mergeFileSettings(); + // remove reference to $this->settingsChain... otherwise dump() or compareElements() will never notice a difference + // on PHP 7+ as they would be always equal + $this->mergedSettings = $this->copy($merged); + } + + private function copy($merged) + { + $copy = array(); + foreach ($merged as $index => $value) { + if (is_array($value)) { + $copy[$index] = $this->copy($value); + } else { + $copy[$index] = $value; + } + } + return $copy; + } + + private function resetSettingsChain($defaultSettingsFiles, $userSettingsFile) + { + $this->settingsChain = array(); + + if (!empty($defaultSettingsFiles)) { + foreach ($defaultSettingsFiles as $file) { + $this->settingsChain[$file] = null; + } + } + + if (!empty($userSettingsFile)) { + $this->settingsChain[$userSettingsFile] = null; + } + } + + protected function mergeFileSettings() + { + $mergedSettings = $this->getMergedDefaultSettings(); + + $userSettings = end($this->settingsChain) ?: array(); + foreach ($userSettings as $sectionName => $section) { + if (!isset($mergedSettings[$sectionName])) { + $mergedSettings[$sectionName] = $section; + } else { + // the last user settings file completely overwrites INI sections. the other files in the chain + // can add to array options + $mergedSettings[$sectionName] = array_merge($mergedSettings[$sectionName], $section); + } + } + + return $mergedSettings; + } + + protected function getMergedDefaultSettings() + { + $userSettingsFile = $this->getUserSettingsFile(); + + $mergedSettings = array(); + foreach ($this->settingsChain as $file => $settings) { + if ($file == $userSettingsFile + || empty($settings) + ) { + continue; + } + + foreach ($settings as $sectionName => $section) { + if (!isset($mergedSettings[$sectionName])) { + $mergedSettings[$sectionName] = $section; + } else { + $mergedSettings[$sectionName] = $this->array_merge_recursive_distinct($mergedSettings[$sectionName], $section); + } + } + } + return $mergedSettings; + } + + protected function getUserSettingsFile() + { + // the user settings file is the last key in $settingsChain + end($this->settingsChain); + return key($this->settingsChain); + } + + /** + * Comparison function + * + * @param mixed $elem1 + * @param mixed $elem2 + * @return int; + */ + public static function compareElements($elem1, $elem2) + { + if (is_array($elem1)) { + if (is_array($elem2)) { + return strcmp(serialize($elem1), serialize($elem2)); + } + + return 1; + } + + if (is_array($elem2)) { + return -1; + } + + if ((string)$elem1 === (string)$elem2) { + return 0; + } + + return ((string)$elem1 > (string)$elem2) ? 1 : -1; + } + + /** + * Compare arrays and return difference, such that: + * + * $modified = array_merge($original, $difference); + * + * @param array $original original array + * @param array $modified modified array + * @return array differences between original and modified + */ + public function arrayUnmerge($original, $modified) + { + // return key/value pairs for keys in $modified but not in $original + // return key/value pairs for keys in both $modified and $original, but values differ + // ignore keys that are in $original but not in $modified + + return array_udiff_assoc($modified, $original, array(__CLASS__, 'compareElements')); + } + + /** + * array_merge_recursive does indeed merge arrays, but it converts values with duplicate + * keys to arrays rather than overwriting the value in the first array with the duplicate + * value in the second array, as array_merge does. I.e., with array_merge_recursive, + * this happens (documented behavior): + * + * array_merge_recursive(array('key' => 'org value'), array('key' => 'new value')); + * => array('key' => array('org value', 'new value')); + * + * array_merge_recursive_distinct does not change the datatypes of the values in the arrays. + * Matching keys' values in the second array overwrite those in the first array, as is the + * case with array_merge, i.e.: + * + * array_merge_recursive_distinct(array('key' => 'org value'), array('key' => 'new value')); + * => array('key' => array('new value')); + * + * Parameters are passed by reference, though only for performance reasons. They're not + * altered by this function. + * + * @param array $array1 + * @param array $array2 + * @return array + * @author Daniel + * @author Gabriel Sobrinho + */ + private function array_merge_recursive_distinct(array &$array1, array &$array2) + { + $merged = $array1; + foreach ($array2 as $key => &$value) { + if (is_array($value) && isset($merged [$key]) && is_array($merged [$key])) { + $merged [$key] = $this->array_merge_recursive_distinct($merged [$key], $value); + } else { + $merged [$key] = $value; + } + } + return $merged; + } + + /** + * public for use in closure. + */ + public function findIndexOfFirstFileWithSection($sectionName) + { + $count = 0; + foreach ($this->settingsChain as $file => $settings) { + if (isset($settings[$sectionName])) { + break; + } + + ++$count; + } + return $count; + } + + /** + * public for use in closure. + */ + public function getIndexOfSectionInFile($fileIndex, $sectionName) + { + reset($this->settingsChain); + for ($i = 0; $i != $fileIndex; ++$i) { + next($this->settingsChain); + } + + $settingsData = current($this->settingsChain); + if (empty($settingsData)) { + return -1; + } + + $settingsDataSectionNames = array_keys($settingsData); + + return array_search($sectionName, $settingsDataSectionNames); + } + + /** + * Encode HTML entities + * + * @param mixed $values + * @return mixed + */ + protected function encodeValues(&$values) + { + if (is_array($values)) { + foreach ($values as &$value) { + $value = $this->encodeValues($value); + } + } elseif (is_float($values)) { + $values = Common::forceDotAsSeparatorForDecimalPoint($values); + } elseif (is_string($values)) { + $values = htmlentities($values, ENT_COMPAT, 'UTF-8'); + $values = str_replace('$', '$', $values); + } + return $values; + } + + /** + * Decode HTML entities + * + * @param mixed $values + * @return mixed + */ + protected function decodeValues(&$values) + { + if (is_array($values)) { + foreach ($values as &$value) { + $value = $this->decodeValues($value); + } + return $values; + } elseif (is_string($values)) { + return html_entity_decode($values, ENT_COMPAT, 'UTF-8'); + } + return $values; + } + + private function dumpSettings($values, $header) + { + $values = $this->encodeValues($values); + + $writer = new IniWriter(); + return $writer->writeToString($values, $header); + } +} diff --git a/www/analytics/core/Console.php b/www/analytics/core/Console.php index cdeb6a08..6b0c5929 100644 --- a/www/analytics/core/Console.php +++ b/www/analytics/core/Console.php @@ -1,6 +1,6 @@ setServerArgsIfPhpCgi(); + parent::__construct(); + $this->environment = $environment; + $option = new InputOption('piwik-domain', null, InputOption::VALUE_OPTIONAL, @@ -28,42 +41,60 @@ class Console extends Application ); $this->getDefinition()->addOption($option); - } - /** - * @deprecated - */ - public function init() - { - // TODO: remove + $option = new InputOption('xhprof', + null, + InputOption::VALUE_NONE, + 'Enable profiling with XHProf' + ); + + $this->getDefinition()->addOption($option); } public function doRun(InputInterface $input, OutputInterface $output) { - $this->initPiwikHost($input); - $this->initConfig($output); - try { - self::initPlugins(); - } catch(\Exception $e) { - // Piwik not installed yet, no config file? + if ($input->hasParameterOption('--xhprof')) { + Profiler::setupProfilerXHProf(true, true); } - Translate::reloadLanguage('en'); + $this->initPiwikHost($input); + $this->initEnvironment($output); + $this->initLoggerOutput($output); + + try { + self::initPlugins(); + } catch (ConfigNotFoundException $e) { + // Piwik not installed yet, no config file? + Log::warning($e->getMessage()); + } $commands = $this->getAvailableCommands(); foreach ($commands as $command) { - - if (!class_exists($command)) { - Log::warning(sprintf('Cannot add command %s, class does not exist', $command)); - } elseif (!is_subclass_of($command, 'Piwik\Plugin\ConsoleCommand')) { - Log::warning(sprintf('Cannot add command %s, class does not extend Piwik\Plugin\ConsoleCommand', $command)); - } else { - $this->add(new $command); - } + $this->addCommandIfExists($command); } - return parent::doRun($input, $output); + $self = $this; + return Access::doAsSuperUser(function () use ($input, $output, $self) { + return call_user_func(array($self, 'Symfony\Component\Console\Application::doRun'), $input, $output); + }); + } + + private function addCommandIfExists($command) + { + if (!class_exists($command)) { + Log::warning(sprintf('Cannot add command %s, class does not exist', $command)); + } elseif (!is_subclass_of($command, 'Piwik\Plugin\ConsoleCommand')) { + Log::warning(sprintf('Cannot add command %s, class does not extend Piwik\Plugin\ConsoleCommand', $command)); + } else { + /** @var Command $commandInstance */ + $commandInstance = new $command; + + // do not add the command if it already exists; this way we can add the command ourselves in tests + if (!$this->has($commandInstance->getName())) { + $this->add($commandInstance); + } + } } /** @@ -74,11 +105,9 @@ class Console extends Application private function getAvailableCommands() { $commands = $this->getDefaultPiwikCommands(); + $detected = PluginManager::getInstance()->findMultipleComponents('Commands', 'Piwik\\Plugin\\ConsoleCommand'); - $pluginNames = PluginManager::getInstance()->getLoadedPluginsName(); - foreach ($pluginNames as $pluginName) { - $commands = array_merge($commands, $this->findCommandsInPlugin($pluginName)); - } + $commands = array_merge($commands, $detected); /** * Triggered to filter / restrict console commands. Plugins that want to restrict commands @@ -103,52 +132,76 @@ class Console extends Application return $commands; } - private function findCommandsInPlugin($pluginName) + private function setServerArgsIfPhpCgi() { - $commands = array(); + if (Common::isPhpCgiType()) { + $_SERVER['argv'] = array(); + foreach ($_GET as $name => $value) { + $argument = $name; + if (!empty($value)) { + $argument .= '=' . $value; + } - $files = Filesystem::globr(PIWIK_INCLUDE_PATH . '/plugins/' . $pluginName .'/Commands', '*.php'); - - foreach ($files as $file) { - $klassName = sprintf('Piwik\\Plugins\\%s\\Commands\\%s', $pluginName, basename($file, '.php')); - - if (!class_exists($klassName) || !is_subclass_of($klassName, 'Piwik\\Plugin\\ConsoleCommand')) { - continue; + $_SERVER['argv'][] = $argument; } - $klass = new \ReflectionClass($klassName); - - if ($klass->isAbstract()) { - continue; + if (!defined('STDIN')) { + define('STDIN', fopen('php://stdin', 'r')); } - - $commands[] = $klassName; } + } - return $commands; + public static function isSupported() + { + return Common::isPhpCliMode() && !Common::isPhpCgiType(); } protected function initPiwikHost(InputInterface $input) { $piwikHostname = $input->getParameterOption('--piwik-domain'); + + if (empty($piwikHostname)) { + $piwikHostname = $input->getParameterOption('--url'); + } + $piwikHostname = UrlHelper::getHostFromUrl($piwikHostname); Url::setHost($piwikHostname); } - protected function initConfig(OutputInterface $output) + protected function initEnvironment(OutputInterface $output) { - $config = Config::getInstance(); try { - $config->checkLocalConfigFound(); + if ($this->environment === null) { + $this->environment = new Environment('cli'); + $this->environment->init(); + } + + $config = Config::getInstance(); return $config; } catch (\Exception $e) { $output->writeln($e->getMessage() . "\n"); } } + /** + * Register the console output into the logger. + * + * Ideally, this should be done automatically with events: + * @see http://symfony.com/fr/doc/current/components/console/events.html + * @see Symfony\Bridge\Monolog\Handler\ConsoleHandler::onCommand() + * But it would require to install Symfony's Event Dispatcher. + */ + private function initLoggerOutput(OutputInterface $output) + { + /** @var ConsoleHandler $consoleLogHandler */ + $consoleLogHandler = StaticContainer::get('Symfony\Bridge\Monolog\Handler\ConsoleHandler'); + $consoleLogHandler->setOutput($output); + } + public static function initPlugins() { Plugin\Manager::getInstance()->loadActivatedPlugins(); + Plugin\Manager::getInstance()->loadPluginTranslations(); } private function getDefaultPiwikCommands() @@ -156,11 +209,12 @@ class Console extends Application $commands = array( 'Piwik\CliMulti\RequestCommand' ); - + if (class_exists('Piwik\Plugins\EnterpriseAdmin\EnterpriseAdmin')) { $extra = new \Piwik\Plugins\EnterpriseAdmin\EnterpriseAdmin(); $extra->addConsoleCommands($commands); } + return $commands; } } diff --git a/www/analytics/core/Container/ContainerDoesNotExistException.php b/www/analytics/core/Container/ContainerDoesNotExistException.php new file mode 100644 index 00000000..4d1d33ed --- /dev/null +++ b/www/analytics/core/Container/ContainerDoesNotExistException.php @@ -0,0 +1,18 @@ +pluginList = $pluginList; + $this->settings = $settings; + $this->environments = $environments; + $this->definitions = $definitions; + } + + /** + * @link http://php-di.org/doc/container-configuration.html + * @throws \Exception + * @return Container + */ + public function create() + { + $builder = new ContainerBuilder(); + + $builder->useAnnotations(false); + $builder->setDefinitionCache(new ArrayCache()); + + // INI config + $builder->addDefinitions(new IniConfigDefinitionSource($this->settings)); + + // Global config + $builder->addDefinitions(PIWIK_USER_PATH . '/config/global.php'); + + // Plugin configs + $this->addPluginConfigs($builder); + + // Development config + if ($this->isDevelopmentModeEnabled()) { + $this->addEnvironmentConfig($builder, 'dev'); + } + + // Environment config + foreach ($this->environments as $environment) { + $this->addEnvironmentConfig($builder, $environment); + } + + // User config + if (file_exists(PIWIK_USER_PATH . '/config/config.php')) { + $builder->addDefinitions(PIWIK_USER_PATH . '/config/config.php'); + } + + if (!empty($this->definitions)) { + foreach ($this->definitions as $definitionArray) { + $builder->addDefinitions($definitionArray); + } + } + + $container = $builder->build(); + $container->set('Piwik\Application\Kernel\PluginList', $this->pluginList); + $container->set('Piwik\Application\Kernel\GlobalSettingsProvider', $this->settings); + + return $container; + } + + private function addEnvironmentConfig(ContainerBuilder $builder, $environment) + { + if (!$environment) { + return; + } + + $file = sprintf('%s/config/environment/%s.php', PIWIK_USER_PATH, $environment); + + if (file_exists($file)) { + $builder->addDefinitions($file); + } + + // add plugin environment configs + $plugins = $this->pluginList->getActivatedPlugins(); + foreach ($plugins as $plugin) { + $baseDir = Manager::getPluginsDirectory() . $plugin; + + $environmentFile = $baseDir . '/config/' . $environment . '.php'; + if (file_exists($environmentFile)) { + $builder->addDefinitions($environmentFile); + } + } + } + + private function addPluginConfigs(ContainerBuilder $builder) + { + $plugins = $this->pluginList->getActivatedPlugins(); + + foreach ($plugins as $plugin) { + $baseDir = Manager::getPluginsDirectory() . $plugin; + + $file = $baseDir . '/config/config.php'; + if (file_exists($file)) { + $builder->addDefinitions($file); + } + } + } + + private function isDevelopmentModeEnabled() + { + $section = $this->settings->getSection('Development'); + return (bool) @$section['enabled']; // TODO: code redundancy w/ Development. hopefully ok for now. + } +} diff --git a/www/analytics/core/Container/IniConfigDefinitionSource.php b/www/analytics/core/Container/IniConfigDefinitionSource.php new file mode 100644 index 00000000..4b56d1aa --- /dev/null +++ b/www/analytics/core/Container/IniConfigDefinitionSource.php @@ -0,0 +1,95 @@ +get('ini.General.maintenance_mode'); + */ +class IniConfigDefinitionSource implements DefinitionSource +{ + /** + * @var GlobalSettingsProvider + */ + private $config; + + /** + * @var string + */ + private $prefix; + + /** + * @param GlobalSettingsProvider $config + * @param string $prefix Prefix for the container entries. + */ + public function __construct(GlobalSettingsProvider $config, $prefix = 'ini.') + { + $this->config = $config; + $this->prefix = $prefix; + } + + /** + * {@inheritdoc} + */ + public function getDefinition($name) + { + if (strpos($name, $this->prefix) !== 0) { + return null; + } + + list($sectionName, $configKey) = $this->parseEntryName($name); + + $section = $this->getSection($sectionName); + + if ($configKey === null) { + return new ValueDefinition($name, $section); + } + + if (! array_key_exists($configKey, $section)) { + return null; + } + + return new ValueDefinition($name, $section[$configKey]); + } + + private function parseEntryName($name) + { + $parts = explode('.', $name, 3); + + array_shift($parts); + + if (! isset($parts[1])) { + $parts[1] = null; + } + + return $parts; + } + + private function getSection($sectionName) + { + $section = $this->config->getSection($sectionName); + + if (!is_array($section)) { + throw new DefinitionException(sprintf( + 'IniFileChain did not return an array for the config section %s', + $section + )); + } + + return $section; + } +} diff --git a/www/analytics/core/Container/StaticContainer.php b/www/analytics/core/Container/StaticContainer.php new file mode 100644 index 00000000..68246716 --- /dev/null +++ b/www/analytics/core/Container/StaticContainer.php @@ -0,0 +1,87 @@ +get($name); + } + + public static function getDefinitions() + { + return self::$definitions; + } +} diff --git a/www/analytics/core/Cookie.php b/www/analytics/core/Cookie.php index 08db3580..24f8a3f6 100644 --- a/www/analytics/core/Cookie.php +++ b/www/analytics/core/Cookie.php @@ -1,6 +1,6 @@ extractSignedContent($_COOKIE[$this->name]); + if ($cookieStr === false) { return; } @@ -255,6 +260,7 @@ class Cookie protected function generateContentString() { $cookieStr = ''; + foreach ($this->value as $name => $value) { if (!is_numeric($value)) { $value = base64_encode(safe_serialize($value)); @@ -335,6 +341,7 @@ class Cookie $this->value[$name] = $value; return; } + $this->value[$this->keyStore][$name] = $value; } @@ -347,14 +354,19 @@ class Cookie public function get($name) { $name = self::escapeValue($name); - if ($this->keyStore === false) { - return isset($this->value[$name]) - ? self::escapeValue($this->value[$name]) - : false; + if (false === $this->keyStore) { + if (isset($this->value[$name])) { + return self::escapeValue($this->value[$name]); + } + + return false; } - return isset($this->value[$this->keyStore][$name]) - ? self::escapeValue($this->value[$this->keyStore][$name]) - : false; + + if (isset($this->value[$this->keyStore][$name])) { + return self::escapeValue($this->value[$this->keyStore][$name]); + } + + return false; } /** @@ -364,8 +376,10 @@ class Cookie */ public function __toString() { - $str = 'COOKIE ' . $this->name . ', rows count: ' . count($this->value) . ', cookie size = ' . strlen($this->generateContentString()) . " bytes\n"; + $str = 'COOKIE ' . $this->name . ', rows count: ' . count($this->value) . ', cookie size = ' . strlen($this->generateContentString()) . " bytes, "; + $str .= 'path: ' . $this->path. ', expire: ' . $this->expire . "\n"; $str .= var_export($this->value, $return = true); + return $str; } diff --git a/www/analytics/core/CronArchive.php b/www/analytics/core/CronArchive.php index 8d5ff009..28ddf49c 100644 --- a/www/analytics/core/CronArchive.php +++ b/www/analytics/core/CronArchive.php @@ -1,6 +1,6 @@ logger = $logger ?: StaticContainer::get('Psr\Log\LoggerInterface'); + $this->formatter = new Formatter(); + + $processNewSegmentsFrom = $processNewSegmentsFrom ?: StaticContainer::get('ini.General.process_new_segments_from'); + $this->segmentArchivingRequestUrlProvider = new SegmentArchivingRequestUrlProvider($processNewSegmentsFrom); + + $this->invalidator = StaticContainer::get('Piwik\Archive\ArchiveInvalidator'); } /** @@ -100,81 +282,67 @@ class CronArchive */ public function main() { - $this->init(); - $this->run(); - $this->runScheduledTasks(); - $this->end(); + $self = $this; + Access::doAsSuperUser(function () use ($self) { + $self->init(); + $self->run(); + $self->runScheduledTasks(); + $self->end(); + }); } public function init() { + /** + * This event is triggered during initializing archiving. + * + * @param CronArchive $this + */ + Piwik::postEvent('CronArchive.init.start', array($this)); + + SettingsServer::setMaxExecutionTime(0); + + $this->archivingStartingTime = time(); + // Note: the order of methods call matters here. - $this->initLog(); - $this->initPiwikHost(); - $this->initCore(); - $this->initTokenAuth(); - $this->initCheckCli(); $this->initStateFromParameters(); - Piwik::setUserHasSuperUserAccess(true); $this->logInitInfo(); - $this->checkPiwikUrlIsValid(); $this->logArchiveTimeoutInfo(); // record archiving start time Option::set(self::OPTION_ARCHIVING_STARTED_TS, time()); - $this->segments = $this->initSegmentsToArchive(); + $this->segments = $this->initSegmentsToArchive(); $this->allWebsites = APISitesManager::getInstance()->getAllSitesId(); + if (!empty($this->shouldArchiveOnlySpecificPeriods)) { + $this->logger->info("- Will only process the following periods: " . implode(", ", $this->shouldArchiveOnlySpecificPeriods) . " (--force-periods)"); + } + + $this->invalidateArchivedReportsForSitesThatNeedToBeArchivedAgain(); + $websitesIds = $this->initWebsiteIds(); $this->filterWebsiteIds($websitesIds); - if (!empty($this->shouldArchiveSpecifiedSites) - || !empty($this->shouldArchiveAllSites) - || !SharedSiteIds::isSupported()) { - $this->websites = new FixedSiteIds($websitesIds); - } else { - $this->websites = new SharedSiteIds($websitesIds); - if ($this->websites->getInitialSiteIds() != $websitesIds) { - $this->log('Will ignore websites and help finish a previous started queue instead. IDs: ' . implode(',', $this->websites->getInitialSiteIds())); - } + $this->websites = $this->createSitesToArchiveQueue($websitesIds); + + if ($this->websites->getInitialSiteIds() != $websitesIds) { + $this->logger->info('Will ignore websites and help finish a previous started queue instead. IDs: ' . implode(', ', $this->websites->getInitialSiteIds())); } - if ($this->shouldStartProfiler) { - \Piwik\Profiler::setupProfilerXHProf($mainRun = true); - $this->log("XHProf profiling is enabled."); - } + $this->logForcedSegmentInfo(); /** * This event is triggered after a CronArchive instance is initialized. * * @param array $websiteIds The list of website IDs this CronArchive instance is processing. - * This will be the enitre list of IDs regardless of whether some have + * This will be the entire list of IDs regardless of whether some have * already been processed. */ Piwik::postEvent('CronArchive.init.finish', array($this->websites->getInitialSiteIds())); } - public function runScheduledTasksInTrackerMode() - { - $this->initPiwikHost(); - $this->initLog(); - $this->initCore(); - $this->initTokenAuth(); - $this->logInitInfo(); - $this->checkPiwikUrlIsValid(); - $this->runScheduledTasks(); - } - - // TODO: replace w/ $this-> - private $websitesWithVisitsSinceLastRun = 0; - private $skippedPeriodsArchivesWebsite = 0; - private $skippedDayArchivesWebsites = 0; - private $skipped = 0; - private $processed = 0; - private $archivedPeriodsArchivesWebsite = 0; - /** * Main function, runs archiving on all websites with new activity */ @@ -183,37 +351,66 @@ class CronArchive $timer = new Timer; $this->logSection("START"); - $this->log("Starting Piwik reports archiving..."); + $this->logger->info("Starting Piwik reports archiving..."); do { - $idsite = $this->websites->getNextSiteId(); + $idSite = $this->websites->getNextSiteId(); - if (null === $idsite) { + if (null === $idSite) { break; } flush(); $requestsBefore = $this->requests; - if ($idsite <= 0) { + if ($idSite <= 0) { continue; } - $skipWebsiteForced = in_array($idsite, $this->shouldSkipSpecifiedSites); - if($skipWebsiteForced) { - $this->log("Skipped website id $idsite, found in --skip-idsites "); + $skipWebsiteForced = in_array($idSite, $this->shouldSkipSpecifiedSites); + if ($skipWebsiteForced) { + $this->logger->info("Skipped website id $idSite, found in --skip-idsites "); $this->skipped++; continue; } + $shouldCheckIfArchivingIsNeeded = !$this->shouldArchiveSpecifiedSites && !$this->shouldArchiveAllSites && !$this->dateLastForced; + $hasWebsiteDayFinishedSinceLastRun = in_array($idSite, $this->websiteDayHasFinishedSinceLastRun); + $isOldReportInvalidatedForWebsite = $this->isOldReportInvalidatedForWebsite($idSite); + + if ($shouldCheckIfArchivingIsNeeded) { + // if not specific sites and not all websites should be archived, we check whether we actually have + // to process the archives for this website (only if there were visits since midnight) + if (!$hasWebsiteDayFinishedSinceLastRun && !$isOldReportInvalidatedForWebsite) { + + if ($this->isWebsiteUsingTheTracker($idSite)) { + + if(!$this->hadWebsiteTrafficSinceMidnightInTimezone($idSite)) { + $this->logger->info("Skipped website id $idSite as archiving is not needed"); + + $this->skippedDayNoRecentData++; + $this->skipped++; + continue; + } + } else { + $this->logger->info("- website id $idSite is not using the tracker"); + } + + } elseif ($hasWebsiteDayFinishedSinceLastRun) { + $this->logger->info("Day has finished for website id $idSite since last run"); + } elseif ($isOldReportInvalidatedForWebsite) { + $this->logger->info("Old report was invalidated for website id $idSite"); + } + } + /** * This event is triggered before the cron archiving process starts archiving data for a single * site. * * @param int $idSite The ID of the site we're archiving data for. */ - Piwik::postEvent('CronArchive.archiveSingleSite.start', array($idsite)); + Piwik::postEvent('CronArchive.archiveSingleSite.start', array($idSite)); - $completed = $this->archiveSingleSite($idsite, $requestsBefore); + $completed = $this->archiveSingleSite($idSite, $requestsBefore); /** * This event is triggered immediately after the cron archiving process starts archiving data for a single @@ -221,36 +418,46 @@ class CronArchive * * @param int $idSite The ID of the site we're archiving data for. */ - Piwik::postEvent('CronArchive.archiveSingleSite.finish', array($idsite, $completed)); - } while (!empty($idsite)); + Piwik::postEvent('CronArchive.archiveSingleSite.finish', array($idSite, $completed)); + } while (!empty($idSite)); - $this->log("Done archiving!"); + $this->logger->info("Done archiving!"); $this->logSection("SUMMARY"); - $this->log("Total visits for today across archived websites: " . $this->visitsToday); + $this->logger->info("Total visits for today across archived websites: " . $this->visitsToday); $totalWebsites = count($this->allWebsites); $this->skipped = $totalWebsites - $this->websitesWithVisitsSinceLastRun; - $this->log("Archived today's reports for {$this->websitesWithVisitsSinceLastRun} websites"); - $this->log("Archived week/month/year for {$this->archivedPeriodsArchivesWebsite} websites"); - $this->log("Skipped {$this->skipped} websites: no new visit since the last script execution"); - $this->log("Skipped {$this->skippedDayArchivesWebsites} websites day archiving: existing daily reports are less than {$this->todayArchiveTimeToLive} seconds old"); - $this->log("Skipped {$this->skippedPeriodsArchivesWebsite} websites week/month/year archiving: existing periods reports are less than {$this->processPeriodsMaximumEverySeconds} seconds old"); - $this->log("Total API requests: {$this->requests}"); + $this->logger->info("Archived today's reports for {$this->websitesWithVisitsSinceLastRun} websites"); + $this->logger->info("Archived week/month/year for {$this->archivedPeriodsArchivesWebsite} websites"); + $this->logger->info("Skipped {$this->skipped} websites"); + $this->logger->info("- {$this->skippedDayNoRecentData} skipped because no new visit since the last script execution"); + $this->logger->info("- {$this->skippedDayArchivesWebsites} skipped because existing daily reports are less than {$this->todayArchiveTimeToLive} seconds old"); + $this->logger->info("- {$this->skippedPeriodsArchivesWebsite} skipped because existing week/month/year periods reports are less than {$this->processPeriodsMaximumEverySeconds} seconds old"); + + if($this->skippedPeriodsNoDataInPeriod) { + $this->logger->info("- {$this->skippedPeriodsNoDataInPeriod} skipped periods archiving because no visit in recent days"); + } + + if($this->skippedDayOnApiError) { + $this->logger->info("- {$this->skippedDayOnApiError} skipped because got an error while querying reporting API"); + } + $this->logger->info("Total API requests: {$this->requests}"); //DONE: done/total, visits, wtoday, wperiods, reqs, time, errors[count]: first eg. $percent = $this->websites->getNumSites() == 0 ? "" : " " . round($this->processed * 100 / $this->websites->getNumSites(), 0) . "%"; - $this->log("done: " . + $this->logger->info("done: " . $this->processed . "/" . $this->websites->getNumSites() . "" . $percent . ", " . $this->visitsToday . " vtoday, $this->websitesWithVisitsSinceLastRun wtoday, {$this->archivedPeriodsArchivesWebsite} wperiods, " . $this->requests . " req, " . round($timer->getTimeMs()) . " ms, " . (empty($this->errors) - ? "no error" - : (count($this->errors) . " errors. eg. '" . reset($this->errors) . "'")) + ? self::NO_ERROR + : (count($this->errors) . " errors.")) ); - $this->log($timer->__toString()); + + $this->logger->info($timer->__toString()); } /** @@ -258,67 +465,90 @@ class CronArchive */ public function end() { - // How to test the error handling code? - // - Generate some hits since last archive.php run - // - Start the script, in the middle, shutdown apache, then restore - // Some errors should be logged and script should successfully finish and then report the errors and trigger a PHP error - if (!empty($this->errors)) { - $this->logSection("SUMMARY OF ERRORS"); + /** + * This event is triggered after archiving. + * + * @param CronArchive $this + */ + Piwik::postEvent('CronArchive.end', array($this)); - foreach ($this->errors as $error) { - $this->log("Error: " . $error); - } - $summary = count($this->errors) . " total errors during this script execution, please investigate and try and fix these errors"; - $this->log($summary); - - $summary .= '. First error was: ' . reset($this->errors); - $this->logFatalError($summary); - } else { + if (empty($this->errors)) { // No error -> Logs the successful script execution until completion Option::set(self::OPTION_ARCHIVING_FINISHED_TS, time()); + return; } + + $this->logSection("SUMMARY OF ERRORS"); + foreach ($this->errors as $error) { + // do not logError since errors are already in stderr + $this->logger->info("Error: " . $error); + } + + $summary = count($this->errors) . " total errors during this script execution, please investigate and try and fix these errors."; + $this->logFatalError($summary); } - public function logFatalError($m, $backtrace = true) + public function logFatalError($m) { - throw new CronArchiveFatalException($m, $backtrace ? $this->output : false); + $this->logError($m); + + throw new Exception($m); } - public function logFatalExceptionAndExit($ex, $backtrace = true) + /** + * @param int[] $idSegments + */ + public function setSegmentsToForceFromSegmentIds($idSegments) { - $wrapped = new CronArchiveFatalException($ex->getMessage(), $backtrace ? $this->output : false); - $wrapped->logAndExit($this); + /** @var SegmentEditorModel $segmentEditorModel */ + $segmentEditorModel = StaticContainer::get('Piwik\Plugins\SegmentEditor\Model'); + $segments = $segmentEditorModel->getAllSegmentsAndIgnoreVisibility(); + + $segments = array_filter($segments, function ($segment) use ($idSegments) { + return in_array($segment['idsegment'], $idSegments); + }); + + $segments = array_map(function ($segment) { + return $segment['definition']; + }, $segments); + + $this->segmentsToForce = $segments; } public function runScheduledTasks() { $this->logSection("SCHEDULED TASKS"); - if($this->getParameterFromCli('--disable-scheduled-tasks')) { - $this->log("Scheduled tasks are disabled with --disable-scheduled-tasks"); + + if ($this->disableScheduledTasks) { + $this->logger->info("Scheduled tasks are disabled with --disable-scheduled-tasks"); return; } - $this->log("Starting Scheduled tasks... "); - $tasksOutput = $this->request("?module=API&method=CoreAdminHome.runScheduledTasks&format=csv&convertToUnicode=0&token_auth=" . $this->token_auth); - if ($tasksOutput == \Piwik\DataTable\Renderer\Csv::NO_DATA_AVAILABLE) { - $tasksOutput = " No task to run"; - } - $this->log($tasksOutput); - $this->log("done"); + // TODO: this is a HACK to get the purgeOutdatedArchives task to work when run below. without + // it, the task will not run because we no longer run the tasks through CliMulti. + // harder to implement alternatives include: + // - moving CronArchive logic to DI and setting a flag in the class when the whole process + // runs + // - setting a new DI environment for core:archive which CoreAdminHome can use to conditionally + // enable/disable the task + $_GET['trigger'] = 'archivephp'; + CoreAdminHomeAPI::getInstance()->runScheduledTasks(); + $this->logSection(""); } - private function archiveSingleSite($idsite, $requestsBefore) + private function archiveSingleSite($idSite, $requestsBefore) { $timerWebsite = new Timer; $lastTimestampWebsiteProcessedPeriods = $lastTimestampWebsiteProcessedDay = false; - if ($this->archiveAndRespectTTL) { - Option::clearCachedOption($this->lastRunKey($idsite, "periods")); - $lastTimestampWebsiteProcessedPeriods = Option::get($this->lastRunKey($idsite, "periods")); - Option::clearCachedOption($this->lastRunKey($idsite, "day")); - $lastTimestampWebsiteProcessedDay = Option::get($this->lastRunKey($idsite, "day")); + if ($this->archiveAndRespectTTL) { + Option::clearCachedOption($this->lastRunKey($idSite, "periods")); + $lastTimestampWebsiteProcessedPeriods = $this->getPeriodLastProcessedTimestamp($idSite); + + Option::clearCachedOption($this->lastRunKey($idSite, "day")); + $lastTimestampWebsiteProcessedDay = $this->getDayLastProcessedTimestamp($idSite); } $this->updateIdSitesInvalidatedOldReports(); @@ -331,6 +561,7 @@ class CronArchive if ($this->processPeriodsMaximumEverySeconds > 10 * 60) { $secondsSinceLastExecution += 5 * 60; } + $shouldArchivePeriods = $secondsSinceLastExecution > $this->processPeriodsMaximumEverySeconds; if (empty($lastTimestampWebsiteProcessedPeriods)) { // 2) OR always if script never executed for this website before @@ -339,21 +570,21 @@ class CronArchive // (*) If the website is archived because it is a new day in its timezone // We make sure all periods are archived, even if there is 0 visit today - $dayHasEndedMustReprocess = in_array($idsite, $this->websiteDayHasFinishedSinceLastRun); + $dayHasEndedMustReprocess = in_array($idSite, $this->websiteDayHasFinishedSinceLastRun); if ($dayHasEndedMustReprocess) { $shouldArchivePeriods = true; } // (*) If there was some old reports invalidated for this website // we make sure all these old reports are triggered at least once - $websiteIsOldDataInvalidate = in_array($idsite, $this->idSitesInvalidatedOldReports); + $websiteInvalidatedShouldReprocess = $this->isOldReportInvalidatedForWebsite($idSite); - if ($websiteIsOldDataInvalidate) { + if ($websiteInvalidatedShouldReprocess) { $shouldArchivePeriods = true; } - $websiteIdIsForced = in_array($idsite, $this->shouldArchiveSpecifiedSites); - if($websiteIdIsForced) { + $websiteIdIsForced = in_array($idSite, $this->shouldArchiveSpecifiedSites); + if ($websiteIdIsForced) { $shouldArchivePeriods = true; } @@ -366,12 +597,12 @@ class CronArchive $skipDayArchive = $existingArchiveIsValid; // Invalidate old website forces the archiving for this site - $skipDayArchive = $skipDayArchive && !$websiteIsOldDataInvalidate; + $skipDayArchive = $skipDayArchive && !$websiteInvalidatedShouldReprocess; // Also reprocess when day has ended since last run if ($dayHasEndedMustReprocess // it might have reprocessed for that day by another cron - && !$this->hasBeenProcessedSinceMidnight($idsite, $lastTimestampWebsiteProcessedDay) + && !$this->hasBeenProcessedSinceMidnight($idSite, $lastTimestampWebsiteProcessedDay) && !$existingArchiveIsValid) { $skipDayArchive = false; } @@ -380,106 +611,61 @@ class CronArchive $skipDayArchive = false; } - if ($skipDayArchive) { - $this->log("Skipped website id $idsite, already done " - . \Piwik\MetricsFormatter::getPrettyTimeFromSeconds($elapsedSinceLastArchiving, true, $isHtml = false) + if ($skipDayArchive) { + $this->logger->info("Skipped website id $idSite, already done " + . $this->formatter->getPrettyTimeFromSeconds($elapsedSinceLastArchiving, true) . " ago, " . $timerWebsite->__toString()); $this->skippedDayArchivesWebsites++; $this->skipped++; return false; } - // Fake that the request is already done, so that other archive.php - // running do not grab the same website from the queue - Option::set($this->lastRunKey($idsite, "day"), time()); - - // Remove this website from the list of websites to be invalidated - // since it's now just about to being re-processed, makes sure another running cron archiving process - // does not archive the same idsite - if ($websiteIsOldDataInvalidate) { - $this->setSiteIsArchived($idsite); + /** + * Trigger archiving for days + */ + try { + $shouldProceed = $this->processArchiveDays($idSite, $lastTimestampWebsiteProcessedDay, $shouldArchivePeriods, $timerWebsite); + } catch (UnexpectedWebsiteFoundException $e) { + // this website was deleted in the meantime + $shouldProceed = false; + $this->logger->info("Skipped website id $idSite, got: UnexpectedWebsiteFoundException, " . $timerWebsite->__toString()); } - // when some data was purged from this website - // we make sure we query all previous days/weeks/months - $processDaysSince = $lastTimestampWebsiteProcessedDay; - if($websiteIsOldDataInvalidate - // when --force-all-websites option, - // also forces to archive last52 days to be safe - || $this->shouldArchiveAllSites) { - $processDaysSince = false; - } - - - $timer = new Timer; - $dateLast = $this->getApiDateLastParameter($idsite, "day", $processDaysSince); - $url = $this->getVisitsRequestUrl($idsite, "day", $dateLast); - $content = $this->request($url); - $response = @unserialize($content); - $visitsToday = $this->getVisitsLastPeriodFromApiResponse($response); - $visitsLastDays = $this->getVisitsFromApiResponse($response); - - if (empty($content) - || !is_array($response) - || count($response) == 0 - ) { - // cancel the succesful run flag - Option::set($this->lastRunKey($idsite, "day"), 0); - - $this->log("WARNING: Empty or invalid response '$content' for website id $idsite, " . $timerWebsite->__toString() . ", skipping"); - $this->skipped++; + if (!$shouldProceed) { return false; } - $this->requests++; - $this->processed++; - - // If there is no visit today and we don't need to process this website, we can skip remaining archives - if ($visitsToday == 0 - && !$shouldArchivePeriods - ) { - $this->log("Skipped website id $idsite, no visit today, " . $timerWebsite->__toString()); - $this->skipped++; - return false; - } - - if ($visitsLastDays == 0 - && !$shouldArchivePeriods - && $this->shouldArchiveAllSites - ) { - $this->log("Skipped website id $idsite, no visits in the last " . $dateLast . " days, " . $timerWebsite->__toString()); - $this->skipped++; - return false; - } - - - $this->visitsToday += $visitsToday; - $this->websitesWithVisitsSinceLastRun++; - $this->archiveVisitsAndSegments($idsite, "day", $lastTimestampWebsiteProcessedDay); - $this->logArchivedWebsite($idsite, "day", $dateLast, $visitsLastDays, $visitsToday, $timer); - if (!$shouldArchivePeriods) { - $this->log("Skipped website id $idsite periods processing, already done " - . \Piwik\MetricsFormatter::getPrettyTimeFromSeconds($elapsedSinceLastArchiving, true, $isHtml = false) + $this->logger->info("Skipped website id $idSite periods processing, already done " + . $this->formatter->getPrettyTimeFromSeconds($elapsedSinceLastArchiving, true) . " ago, " . $timerWebsite->__toString()); - $this->skippedDayArchivesWebsites++; + $this->skippedPeriodsArchivesWebsite++; $this->skipped++; return false; } - $success = true; - foreach (array('week', 'month', 'year') as $period) { - $success = $this->archiveVisitsAndSegments($idsite, $period, $lastTimestampWebsiteProcessedPeriods) - && $success; - } + /** + * Trigger archiving for non-day periods + */ + $success = $this->processArchiveForPeriods($idSite, $lastTimestampWebsiteProcessedPeriods); + // Record succesful run of this website's periods archiving if ($success) { - Option::set($this->lastRunKey($idsite, "periods"), time()); + Option::set($this->lastRunKey($idSite, "periods"), time()); } + + if (!$success) { + // cancel marking the site as reprocessed + if ($websiteInvalidatedShouldReprocess) { + $store = new SitesToReprocessDistributedList(); + $store->add($idSite); + } + } + $this->archivedPeriodsArchivesWebsite++; $requestsWebsite = $this->requests - $requestsBefore; - Log::info("Archived website id = $idsite, " + $this->logger->info("Archived website id = $idSite, " . $requestsWebsite . " API requests, " . $timerWebsite->__toString() . " [" . $this->websites->getNumProcessedWebsites() . "/" @@ -490,78 +676,206 @@ class CronArchive } /** - * Checks the config file is found. - * - * @param $piwikUrl - * @throws Exception + * @param $idSite + * @param $lastTimestampWebsiteProcessedPeriods + * @return bool */ - protected function initConfigObject($piwikUrl) + private function processArchiveForPeriods($idSite, $lastTimestampWebsiteProcessedPeriods) { - // HOST is required for the Config object - $parsed = parse_url($piwikUrl); - Url::setHost($parsed['host']); + $success = true; - Config::getInstance()->clear(); + foreach (array('week', 'month', 'year') as $period) { + if (!$this->shouldProcessPeriod($period)) { + // if any period was skipped, we do not mark the Periods archiving as successful + $success = false; + continue; + } - try { - Config::getInstance()->checkLocalConfigFound(); - } catch (Exception $e) { - throw new Exception("The configuration file for Piwik could not be found. " . - "Please check that config/config.ini.php is readable by the user " . - get_current_user()); + $timer = new Timer(); + + $date = $this->getApiDateParameter($idSite, $period, $lastTimestampWebsiteProcessedPeriods); + $periodArchiveWasSuccessful = $this->archiveReportsFor($idSite, $period, $date, $archiveSegments = true, $timer); + $success = $periodArchiveWasSuccessful && $success; } + + if ($this->shouldProcessPeriod('range')) { + // period=range + $customDateRangesToPreProcessForSite = $this->getCustomDateRangeToPreProcess($idSite); + foreach ($customDateRangesToPreProcessForSite as $dateRange) { + $timer = new Timer(); + $archiveSegments = false; // do not pre-process segments for period=range #7611 + $periodArchiveWasSuccessful = $this->archiveReportsFor($idSite, 'range', $dateRange, $archiveSegments, $timer); + $success = $periodArchiveWasSuccessful && $success; + } + } + + return $success; } /** - * Returns base URL to process reports for the $idsite on a given $period + * Returns base URL to process reports for the $idSite on a given $period */ - private function getVisitsRequestUrl($idsite, $period, $dateLast) + private function getVisitsRequestUrl($idSite, $period, $date, $segment = false) { - return "?module=API&method=API.get&idSite=$idsite&period=$period&date=last" . $dateLast . "&format=php&token_auth=" . $this->token_auth; + $request = "?module=API&method=API.get&idSite=$idSite&period=$period&date=" . $date . "&format=php"; + if ($segment) { + $request .= '&segment=' . urlencode($segment); + ; + } + return $request; } private function initSegmentsToArchive() { $segments = \Piwik\SettingsPiwik::getKnownSegmentsToArchive(); + if (empty($segments)) { return array(); } - $this->log("- Will pre-process " . count($segments) . " Segments for each website and each period: " . implode(", ", $segments)); - return $segments; - } - private function getSegmentsForSite($idsite) - { - $segmentsAllSites = $this->segments; - $segmentsThisSite = \Piwik\SettingsPiwik::getKnownSegmentsToArchiveForSite($idsite); - if (!empty($segmentsThisSite)) { - $this->log("Will pre-process the following " . count($segmentsThisSite) . " Segments for this website (id = $idsite): " . implode(", ", $segmentsThisSite)); - } - $segments = array_unique(array_merge($segmentsAllSites, $segmentsThisSite)); + $this->logger->info("- Will pre-process " . count($segments) . " Segments for each website and each period: " . implode(", ", $segments)); return $segments; } /** - * Will trigger API requests for the specified Website $idsite, + * @param $idSite + * @param $lastTimestampWebsiteProcessedDay + * @param $shouldArchivePeriods + * @param $timerWebsite + * @return bool + */ + protected function processArchiveDays($idSite, $lastTimestampWebsiteProcessedDay, $shouldArchivePeriods, Timer $timerWebsite) + { + if (!$this->shouldProcessPeriod("day")) { + // skip day archiving and proceed to period processing + return true; + } + + $timer = new Timer(); + + // Fake that the request is already done, so that other core:archive commands + // running do not grab the same website from the queue + Option::set($this->lastRunKey($idSite, "day"), time()); + + // Remove this website from the list of websites to be invalidated + // since it's now just about to being re-processed, makes sure another running cron archiving process + // does not archive the same idSite + $websiteInvalidatedShouldReprocess = $this->isOldReportInvalidatedForWebsite($idSite); + if ($websiteInvalidatedShouldReprocess) { + $store = new SitesToReprocessDistributedList(); + $store->remove($idSite); + } + + // when some data was purged from this website + // we make sure we query all previous days/weeks/months + $processDaysSince = $lastTimestampWebsiteProcessedDay; + if ($websiteInvalidatedShouldReprocess + // when --force-all-websites option, + // also forces to archive last52 days to be safe + || $this->shouldArchiveAllSites) { + $processDaysSince = false; + } + + $date = $this->getApiDateParameter($idSite, "day", $processDaysSince); + $url = $this->getVisitsRequestUrl($idSite, "day", $date); + + $this->logArchiveWebsite($idSite, "day", $date); + + $content = $this->request($url); + $daysResponse = @unserialize($content); + + if (empty($content) + || !is_array($daysResponse) + || count($daysResponse) == 0 + ) { + // cancel the succesful run flag + Option::set($this->lastRunKey($idSite, "day"), 0); + + // cancel marking the site as reprocessed + if ($websiteInvalidatedShouldReprocess) { + $store = new SitesToReprocessDistributedList(); + $store->add($idSite); + } + + $this->logError("Empty or invalid response '$content' for website id $idSite, " . $timerWebsite->__toString() . ", skipping"); + $this->skippedDayOnApiError++; + $this->skipped++; + return false; + } + + $visitsToday = $this->getVisitsLastPeriodFromApiResponse($daysResponse); + $visitsLastDays = $this->getVisitsFromApiResponse($daysResponse); + + $this->requests++; + $this->processed++; + + // If there is no visit today and we don't need to process this website, we can skip remaining archives + if ( + 0 == $visitsToday + && !$shouldArchivePeriods + ) { + $this->logger->info("Skipped website id $idSite, no visit today, " . $timerWebsite->__toString()); + $this->skippedDayNoRecentData++; + $this->skipped++; + return false; + } + + if (0 == $visitsLastDays + && !$shouldArchivePeriods + && $this->shouldArchiveAllSites + ) { + $humanReadableDate = $this->formatReadableDateRange($date); + $this->logger->info("Skipped website id $idSite, no visits in the $humanReadableDate days, " . $timerWebsite->__toString()); + $this->skippedPeriodsNoDataInPeriod++; + $this->skipped++; + return false; + } + + $this->visitsToday += $visitsToday; + $this->websitesWithVisitsSinceLastRun++; + + $this->archiveReportsFor($idSite, "day", $this->getApiDateParameter($idSite, "day", $processDaysSince), $archiveSegments = true, $timer); + + return true; + } + + private function getSegmentsForSite($idSite, $period) + { + $segmentsAllSites = $this->segments; + $segmentsThisSite = SettingsPiwik::getKnownSegmentsToArchiveForSite($idSite); + $segments = array_unique(array_merge($segmentsAllSites, $segmentsThisSite)); + return $segments; + } + + private function formatReadableDateRange($date) + { + if (0 === strpos($date, 'last')) { + $readable = 'last ' . str_replace('last', '', $date); + } elseif (0 === strpos($date, 'previous')) { + $readable = 'previous ' . str_replace('previous', '', $date); + } else { + $readable = 'last ' . $date; + } + + return $readable; + } + + /** + * Will trigger API requests for the specified Website $idSite, * for the specified $period, for all segments that are pre-processed for this website. * Requests are triggered using cURL multi handle * - * @param $idsite int - * @param $period - * @param $lastTimestampWebsiteProcessed + * @param $idSite int + * @param $period string + * @param $date string + * @param $archiveSegments bool Whether to pre-process all custom segments + * @param Timer $periodTimer * @return bool True on success, false if some request failed */ - private function archiveVisitsAndSegments($idsite, $period, $lastTimestampWebsiteProcessed) + private function archiveReportsFor($idSite, $period, $date, $archiveSegments, Timer $periodTimer) { - $timer = new Timer(); - - $url = $this->piwikUrl; - - $dateLast = $this->getApiDateLastParameter($idsite, $period, $lastTimestampWebsiteProcessed); - $url .= $this->getVisitsRequestUrl($idsite, $period, $dateLast); - - - $url .= self::APPEND_TO_API_REQUEST; + $url = $this->getVisitsRequestUrl($idSite, $period, $date, $segment = false); + $url = $this->makeRequestUrl($url); $visitsInLastPeriods = $visitsLastPeriod = 0; $success = true; @@ -572,17 +886,23 @@ class CronArchive // already processed above for "day" if ($period != "day") { $urls[] = $url; - $this->requests++; + $this->logArchiveWebsite($idSite, $period, $date); } - foreach ($this->getSegmentsForSite($idsite) as $segment) { - $urlWithSegment = $url . '&segment=' . urlencode($segment); - $urls[] = $urlWithSegment; - $this->requests++; + $segmentRequestsCount = 0; + if ($archiveSegments) { + $urlsWithSegment = $this->getUrlsWithSegment($idSite, $period, $date); + $urls = array_merge($urls, $urlsWithSegment); + $segmentRequestsCount = count($urlsWithSegment); + + // in case several segment URLs for period=range had the date= rewritten to the same value, we only call API once + $urls = array_unique($urls); } - $cliMulti = new CliMulti(); - $cliMulti->setAcceptInvalidSSLCertificate($this->acceptInvalidSSLCertificate); + $this->requests += count($urls); + + $cliMulti = $this->makeCliMulti(); + $cliMulti->setConcurrentProcessesLimit($this->getConcurrentRequestsPerWebsite()); $response = $cliMulti->request($urls); foreach ($urls as $index => $url) { @@ -590,21 +910,23 @@ class CronArchive $success = $success && $this->checkResponse($content, $url); if ($noSegmentUrl === $url && $success) { - $stats = @unserialize($content); if (!is_array($stats)) { $this->logError("Error unserializing the following response from $url: " . $content); } + if ($period == 'range') { + // range returns one dataset (the sum of data between the two dates), + // whereas other periods return lastN which is N datasets in an array. Here we make our period=range dataset look like others: + $stats = array($stats); + } + $visitsInLastPeriods = $this->getVisitsFromApiResponse($stats); $visitsLastPeriod = $this->getVisitsLastPeriodFromApiResponse($stats); } } - // we have already logged the daily archive above - if($period != "day") { - $this->logArchivedWebsite($idsite, $period, $dateLast, $visitsInLastPeriods, $visitsLastPeriod, $timer); - } + $this->logArchivedWebsite($idSite, $period, $date, $segmentRequestsCount, $visitsInLastPeriods, $visitsLastPeriod, $periodTimer); return $success; } @@ -614,45 +936,48 @@ class CronArchive */ private function logSection($title = "") { - $this->log("---------------------------"); - if(!empty($title)) { - $this->log($title); + $this->logger->info("---------------------------"); + if (!empty($title)) { + $this->logger->info($title); } } - private function log($m) + public function logError($m) { - $this->output .= $m . "\n"; - try { - Log::info($m); - } catch(Exception $e) { - print($m . "\n"); + if (!defined('PIWIK_ARCHIVE_NO_TRUNCATE')) { + $m = substr($m, 0, self::TRUNCATE_ERROR_MESSAGE_SUMMARY); } + $m = str_replace(array("\n", "\t"), " ", $m); + $this->errors[] = $m; + $this->logger->error($m); + } + + private function logNetworkError($url, $response) + { + $message = "Got invalid response from API request: $url. "; + if (empty($response)) { + $message .= "The response was empty. This usually means a server error. This solution to this error is generally to increase the value of 'memory_limit' in your php.ini file. Please check your Web server Error Log file for more details."; + } else { + $message .= "Response was '$response'"; + } + + $this->logError($message); + return false; } /** - * Issues a request to $url + * Issues a request to $url eg. "?module=API&method=API.getDefaultMetricTranslations&format=original&serialize=1" + * */ private function request($url) { - $url = $this->piwikUrl . $url . self::APPEND_TO_API_REQUEST; - - if($this->shouldStartProfiler) { - $url .= "&xhprof=2"; - } - - if ($this->testmode) { - $url .= "&testmode=1"; - } + $url = $this->makeRequestUrl($url); try { - - $cliMulti = new CliMulti(); - $cliMulti->setAcceptInvalidSSLCertificate($this->acceptInvalidSSLCertificate); + $cliMulti = $this->makeCliMulti(); $responses = $cliMulti->request(array($url)); $response = !empty($responses) ? array_shift($responses) : null; - } catch (Exception $e) { return $this->logNetworkError($url, $e->getMessage()); } @@ -672,99 +997,6 @@ class CronArchive return true; } - public function logError($m) - { - if (!defined('PIWIK_ARCHIVE_NO_TRUNCATE')) { - $m = substr($m, 0, self::TRUNCATE_ERROR_MESSAGE_SUMMARY); - } - - $this->errors[] = $m; - $this->log("ERROR: $m"); - } - - private function logNetworkError($url, $response) - { - $message = "Got invalid response from API request: $url. "; - if (empty($response)) { - $message .= "The response was empty. This usually means a server error. This solution to this error is generally to increase the value of 'memory_limit' in your php.ini file. Please check your Web server Error Log file for more details."; - } else { - $message .= "Response was '$response'"; - } - $this->logError($message); - return false; - } - - /** - * Configures Piwik\Log so messages are written in output - */ - private function initLog() - { - $config = Config::getInstance(); - /** - * access a property that is not overriden by TestingEnvironment before accessing log as the - * log section is used in TestingEnvironment. Otherwise access to magic __get('log') fails in - * TestingEnvironment as it tries to acccess it already here with __get('log'). - * $config->log ==> __get('log') ==> Config.createConfigInstance ==> nested __get('log') ==> returns null - */ - $initConfigToPreventErrorWhenAccessingLog = $config->mail; - - $log = $config->log; - $log['log_only_when_debug_parameter'] = 0; - $log[\Piwik\Log::LOG_WRITERS_CONFIG_OPTION] = array("screen"); - - if (!empty($_GET['forcelogtoscreen'])) { - Log::getInstance()->addLogWriter('screen'); - } - - // Make sure we log at least INFO (if logger is set to DEBUG then keep it) - $logLevel = @$log[\Piwik\Log::LOG_LEVEL_CONFIG_OPTION]; - if ($logLevel != 'VERBOSE' - && $logLevel != 'DEBUG' - ) { - $log[\Piwik\Log::LOG_LEVEL_CONFIG_OPTION] = 'INFO'; - Log::getInstance()->setLogLevel(Log::INFO); - } - - $config->log = $log; - } - - /** - * Script does run on http:// ONLY if the SU token is specified - */ - private function initCheckCli() - { - if (Common::isPhpCliMode()) { - return; - } - $token_auth = Common::getRequestVar('token_auth', '', 'string'); - if ($token_auth != $this->token_auth - || strlen($token_auth) != 32 - ) { - die('You must specify the Super User token_auth as a parameter to this script, eg. ?token_auth=XYZ if you wish to run this script through the browser.
- However it is recommended to run it via cron in the command line, since it can take a long time to run.
- In a shell, execute for example the following to trigger archiving on the local Piwik server:
- $ /path/to/php /path/to/piwik/misc/cron/archive.php --url=http://your-website.org/path/to/piwik/'); - } - } - - /** - * Init Piwik, connect DB, create log & config objects, etc. - */ - private function initCore() - { - try { - FrontController::getInstance()->init(); - $this->isCoreInited = true; - } catch (Exception $e) { - throw new CronArchiveFatalException("ERROR: During Piwik init, Message: " . $e->getMessage()); - } - } - - public function isCoreInited() - { - return $this->isCoreInited; - } - /** * Initializes the various parameters to the script, based on input parameters. * @@ -772,179 +1004,112 @@ class CronArchive private function initStateFromParameters() { $this->todayArchiveTimeToLive = Rules::getTodayArchiveTimeToLive(); - $this->acceptInvalidSSLCertificate = $this->getParameterFromCli("accept-invalid-ssl-certificate"); $this->processPeriodsMaximumEverySeconds = $this->getDelayBetweenPeriodsArchives(); - $this->shouldArchiveAllSites = (bool) $this->getParameterFromCli("force-all-websites"); - $this->shouldStartProfiler = (bool) $this->getParameterFromCli("xhprof"); - $restrictToIdSites = $this->getParameterFromCli("force-idsites", true); - $skipIdSites = $this->getParameterFromCli("skip-idsites", true); - $this->shouldArchiveSpecifiedSites = \Piwik\Site::getIdSitesFromIdSitesString($restrictToIdSites); - $this->shouldSkipSpecifiedSites = \Piwik\Site::getIdSitesFromIdSitesString($skipIdSites); - $this->lastSuccessRunTimestamp = Option::get(self::OPTION_ARCHIVING_FINISHED_TS); + $this->lastSuccessRunTimestamp = $this->getLastSuccessRunTimestamp(); $this->shouldArchiveOnlySitesWithTrafficSince = $this->isShouldArchiveAllSitesWithTrafficSince(); + $this->shouldArchiveOnlySpecificPeriods = $this->getPeriodsToProcess(); - if($this->shouldArchiveOnlySitesWithTrafficSince === false) { - // force-all-periods is not set here - if (empty($this->lastSuccessRunTimestamp)) { - // First time we run the script - $this->shouldArchiveOnlySitesWithTrafficSince = self::ARCHIVE_SITES_WITH_TRAFFIC_SINCE; - } else { - // there was a previous successful run - $this->shouldArchiveOnlySitesWithTrafficSince = time() - $this->lastSuccessRunTimestamp; - } - } else { + if ($this->shouldArchiveOnlySitesWithTrafficSince !== false) { // force-all-periods is set here $this->archiveAndRespectTTL = false; - - if($this->shouldArchiveOnlySitesWithTrafficSince === true) { - // force-all-periods without value - $this->shouldArchiveOnlySitesWithTrafficSince = self::ARCHIVE_SITES_WITH_TRAFFIC_SINCE; - } } } + private function getSecondsSinceLastArchive() + { + $wasNotCustomTimeRequested = $this->shouldArchiveOnlySitesWithTrafficSince === false; + + if ($wasNotCustomTimeRequested && !empty($this->lastSuccessRunTimestamp)) { + // there was a previous successful run + + return time() - $this->lastSuccessRunTimestamp; + + } elseif (is_numeric($this->shouldArchiveOnlySitesWithTrafficSince)) { + // $shouldArchiveAllPeriodsSince was specified + $secondsSinceStart = time() - $this->archivingStartingTime; + return $this->shouldArchiveOnlySitesWithTrafficSince + $secondsSinceStart; + } + + // force-all-periods without value + return self::ARCHIVE_SITES_WITH_TRAFFIC_SINCE; + } + public function filterWebsiteIds(&$websiteIds) { // Keep only the websites that do exist $websiteIds = array_intersect($websiteIds, $this->allWebsites); /** - * Triggered by the **archive.php** cron script so plugins can modify the list of + * Triggered by the **core:archive** console command so plugins can modify the list of * websites that the archiving process will be launched for. - * + * * Plugins can use this hook to add websites to archive, remove websites to archive, or change * the order in which websites will be archived. - * + * * @param array $websiteIds The list of website IDs to launch the archiving process for. */ Piwik::postEvent('CronArchive.filterWebsiteIds', array(&$websiteIds)); } + /** + * @internal + */ + public function setApiToInvalidateArchivedReport($api) + { + $this->apiToInvalidateArchivedReport = $api; + } + + private function getApiToInvalidateArchivedReport() + { + if ($this->apiToInvalidateArchivedReport) { + return $this->apiToInvalidateArchivedReport; + } + + return CoreAdminHomeAPI::getInstance(); + } + + public function invalidateArchivedReportsForSitesThatNeedToBeArchivedAgain() + { + $sitesPerDays = $this->invalidator->getRememberedArchivedReportsThatShouldBeInvalidated(); + + foreach ($sitesPerDays as $date => $siteIds) { + $listSiteIds = implode(',', $siteIds); + + try { + $this->logger->info('Will invalidate archived reports for ' . $date . ' for following websites ids: ' . $listSiteIds); + $this->getApiToInvalidateArchivedReport()->invalidateArchivedReports($siteIds, $date); + } catch (Exception $e) { + $this->logger->info('Failed to invalidate archived reports: ' . $e->getMessage()); + } + } + } + /** * Returns the list of sites to loop over and archive. * @return array */ public function initWebsiteIds() { - if(count($this->shouldArchiveSpecifiedSites) > 0) { - $this->log("- Will process " . count($this->shouldArchiveSpecifiedSites) . " websites (--force-idsites)"); + if (count($this->shouldArchiveSpecifiedSites) > 0) { + $this->logger->info("- Will process " . count($this->shouldArchiveSpecifiedSites) . " websites (--force-idsites)"); return $this->shouldArchiveSpecifiedSites; } + + $this->findWebsiteIdsInTimezoneWithNewDay($this->allWebsites); + $this->findInvalidatedSitesToReprocess(); + if ($this->shouldArchiveAllSites) { - $this->log("- Will process all " . count($this->allWebsites) . " websites"); - return $this->allWebsites; + $this->logger->info("- Will process all " . count($this->allWebsites) . " websites"); } - $websiteIds = array_merge( - $this->addWebsiteIdsWithVisitsSinceLastRun(), - $this->getWebsiteIdsToInvalidate() - ); - $websiteIds = array_merge($websiteIds, $this->addWebsiteIdsInTimezoneWithNewDay($websiteIds)); - return array_unique($websiteIds); - } - - private function initTokenAuth() - { - $superUser = Db::get()->fetchRow("SELECT login, token_auth - FROM " . Common::prefixTable("user") . " - WHERE superuser_access = 1 - ORDER BY date_registered ASC"); - $this->token_auth = $superUser['token_auth']; - } - - private function initPiwikHost() - { - // If archive.php run as a web cron, we use the current hostname+path - if (!Common::isPhpCliMode()) { - if (!empty(self::$url)) { - $piwikUrl = self::$url; - } else { - // example.org/piwik/ - $piwikUrl = SettingsPiwik::getPiwikUrl(); - } - } else { - // If archive.php run as CLI/shell we require the piwik url to be set - $piwikUrl = $this->getParameterFromCli("url", true); - - if (!$piwikUrl) { - $this->logFatalErrorUrlExpected(); - } - - if(!\Piwik\UrlHelper::isLookLikeUrl($piwikUrl)) { - // try adding http:// in case it's missing - $piwikUrl = "http://" . $piwikUrl; - } - if(!\Piwik\UrlHelper::isLookLikeUrl($piwikUrl)) { - $this->logFatalErrorUrlExpected(); - } - - // ensure there is a trailing slash - if ($piwikUrl[strlen($piwikUrl) - 1] != '/' && !Common::stringEndsWith($piwikUrl, 'index.php')) { - $piwikUrl .= '/'; - } - } - - $this->initConfigObject($piwikUrl); - - if (Config::getInstance()->General['force_ssl'] == 1) { - $piwikUrl = str_replace('http://', 'https://', $piwikUrl); - } - - if (!Common::stringEndsWith($piwikUrl, 'index.php')) { - $piwikUrl .= 'index.php'; - } - - $this->piwikUrl = $piwikUrl; - } - - /** - * Returns if the requested parameter is defined in the command line arguments. - * If $valuePossible is true, then a value is possibly set for this parameter, - * ie. --force-timeout-for-periods=3600 would return 3600 - * - * @param $parameter - * @param bool $valuePossible - * @return true or the value (int,string) if set, false otherwise - */ - public static function getParameterFromCli($parameter, $valuePossible = false) - { - if (!Common::isPhpCliMode()) { - return false; - } - if($parameter == 'url' && self::$url) { - return self::$url; - } - $parameters = array( - "--$parameter", - "-$parameter", - $parameter - ); - if(empty($_SERVER['argv'])) { - return false; - } - foreach ($parameters as $parameter) { - foreach ($_SERVER['argv'] as $arg) { - if (strpos($arg, $parameter) === 0) { - if ($valuePossible) { - $parameterFound = $arg; - if (($posEqual = strpos($parameterFound, '=')) !== false) { - $return = substr($parameterFound, $posEqual + 1); - if ($return !== false) { - return $return; - } - } - } - return true; - } - } - } - return false; + return $this->allWebsites; } private function updateIdSitesInvalidatedOldReports() { - $this->idSitesInvalidatedOldReports = APICoreAdminHome::getWebsiteIdsToInvalidate(); + $store = new SitesToReprocessDistributedList(); + $this->idSitesInvalidatedOldReports = $store->getAll(); } /** @@ -954,13 +1119,13 @@ class CronArchive * * @return array */ - private function getWebsiteIdsToInvalidate() + private function findInvalidatedSitesToReprocess() { $this->updateIdSitesInvalidatedOldReports(); if (count($this->idSitesInvalidatedOldReports) > 0) { $ids = ", IDs: " . implode(", ", $this->idSitesInvalidatedOldReports); - $this->log("- Will process " . count($this->idSitesInvalidatedOldReports) + $this->logger->info("- Will process " . count($this->idSitesInvalidatedOldReports) . " other websites because some old data reports have been invalidated (eg. using the Log Import script) " . $ids); } @@ -969,20 +1134,37 @@ class CronArchive } /** - * Returns all sites that had visits since specified time + * Detects whether a site had visits since midnight in the websites timezone * - * @return string + * @return bool */ - private function addWebsiteIdsWithVisitsSinceLastRun() + private function hadWebsiteTrafficSinceMidnightInTimezone($idSite) { - $sitesIdWithVisits = APISitesManager::getInstance()->getSitesIdWithVisits(time() - $this->shouldArchiveOnlySitesWithTrafficSince); - $websiteIds = !empty($sitesIdWithVisits) ? ", IDs: " . implode(", ", $sitesIdWithVisits) : ""; - $prettySeconds = \Piwik\MetricsFormatter::getPrettyTimeFromSeconds( $this->shouldArchiveOnlySitesWithTrafficSince, true, false); - $this->log("- Will process " . count($sitesIdWithVisits) . " websites with new visits since " - . $prettySeconds - . " " - . $websiteIds); - return $sitesIdWithVisits; + $timezone = Site::getTimezoneFor($idSite); + + $nowInTimezone = Date::factory('now', $timezone); + $midnightInTimezone = $nowInTimezone->setTime('00:00:00'); + + $secondsSinceMidnight = $nowInTimezone->getTimestamp() - $midnightInTimezone->getTimestamp(); + + $secondsSinceLastArchive = $this->getSecondsSinceLastArchive(); + if ($secondsSinceLastArchive < $secondsSinceMidnight) { + $secondsSinceMidnight = $secondsSinceLastArchive; + } + + $from = Date::now()->subSeconds($secondsSinceMidnight)->getDatetime(); + $to = Date::now()->addHour(1)->getDatetime(); + + $dao = new RawLogDao(); + $hasVisits = $dao->hasSiteVisitsBetweenTimeframe($from, $to, $idSite); + + if ($hasVisits) { + $this->logger->info("- tracking data found for website id $idSite (between $from and $to)"); + } else { + $this->logger->info("- no new tracking data for website id $idSite (between $from and $to)"); + } + + return $hasVisits; } /** @@ -993,7 +1175,7 @@ class CronArchive */ private function getTimezonesHavingNewDay() { - $timestamp = time() - $this->shouldArchiveOnlySitesWithTrafficSince; + $timestamp = $this->lastSuccessRunTimestamp; $uniqueTimezones = APISitesManager::getInstance()->getUniqueSiteTimezones(); $timezoneToProcess = array(); foreach ($uniqueTimezones as &$timezone) { @@ -1007,13 +1189,13 @@ class CronArchive return $timezoneToProcess; } - private function hasBeenProcessedSinceMidnight($idsite, $lastTimestampWebsiteProcessedDay) + private function hasBeenProcessedSinceMidnight($idSite, $lastTimestampWebsiteProcessedDay) { if (false === $lastTimestampWebsiteProcessedDay) { return true; } - $timezone = Site::getTimezoneFor($idsite); + $timezone = Site::getTimezoneFor($idSite); $dateInTimezone = Date::factory('now', $timezone); $midnightInTimezone = $dateInTimezone->setTime('00:00:00'); @@ -1030,40 +1212,27 @@ class CronArchive * @param $websiteIds * @return array Website IDs */ - private function addWebsiteIdsInTimezoneWithNewDay($websiteIds) + private function findWebsiteIdsInTimezoneWithNewDay($websiteIds) { $timezones = $this->getTimezonesHavingNewDay(); $websiteDayHasFinishedSinceLastRun = APISitesManager::getInstance()->getSitesIdFromTimezones($timezones); $websiteDayHasFinishedSinceLastRun = array_diff($websiteDayHasFinishedSinceLastRun, $websiteIds); $this->websiteDayHasFinishedSinceLastRun = $websiteDayHasFinishedSinceLastRun; + if (count($websiteDayHasFinishedSinceLastRun) > 0) { $ids = !empty($websiteDayHasFinishedSinceLastRun) ? ", IDs: " . implode(", ", $websiteDayHasFinishedSinceLastRun) : ""; - $this->log("- Will process " . count($websiteDayHasFinishedSinceLastRun) + $this->logger->info("- Will process " . count($websiteDayHasFinishedSinceLastRun) . " other websites because the last time they were archived was on a different day (in the website's timezone) " . $ids); } - return $websiteDayHasFinishedSinceLastRun; - } - /** - * Test that the specified piwik URL is a valid Piwik endpoint. - */ - private function checkPiwikUrlIsValid() - { - $response = $this->request("?module=API&method=API.getDefaultMetricTranslations&format=original&serialize=1"); - $responseUnserialized = @unserialize($response); - if ($response === false - || !is_array($responseUnserialized) - ) { - $this->logFatalError("The Piwik URL {$this->piwikUrl} does not seem to be pointing to a Piwik server. Response was '$response'."); - } + return $websiteDayHasFinishedSinceLastRun; } private function logInitInfo() { $this->logSection("INIT"); - $this->log("Piwik is installed at: {$this->piwikUrl}"); - $this->log("Running Piwik " . Version::VERSION . " as Super User"); + $this->logger->info("Running Piwik " . Version::VERSION . " as Super User"); } private function logArchiveTimeoutInfo() @@ -1072,18 +1241,19 @@ class CronArchive // Recommend to disable browser archiving when using this script if (Rules::isBrowserTriggerEnabled()) { - $this->log("- If you execute this script at least once per hour (or more often) in a crontab, you may disable 'Browser trigger archiving' in Piwik UI > Settings > General Settings. "); - $this->log(" See the doc at: http://piwik.org/docs/setup-auto-archiving/"); + $this->logger->info("- If you execute this script at least once per hour (or more often) in a crontab, you may disable 'Browser trigger archiving' in Piwik UI > Settings > General Settings."); + $this->logger->info(" See the doc at: http://piwik.org/docs/setup-auto-archiving/"); } - $this->log("- Reports for today will be processed at most every " . $this->todayArchiveTimeToLive + $this->logger->info("- Reports for today will be processed at most every " . $this->todayArchiveTimeToLive . " seconds. You can change this value in Piwik UI > Settings > General Settings."); - $this->log("- Reports for the current week/month/year will be refreshed at most every " + $this->logger->info("- Reports for the current week/month/year will be refreshed at most every " . $this->processPeriodsMaximumEverySeconds . " seconds."); // Try and not request older data we know is already archived if ($this->lastSuccessRunTimestamp !== false) { $dateLast = time() - $this->lastSuccessRunTimestamp; - $this->log("- Archiving was last executed without error " . \Piwik\MetricsFormatter::getPrettyTimeFromSeconds($dateLast, true, $isHtml = false) . " ago"); + $this->logger->info("- Archiving was last executed without error " + . $this->formatter->getPrettyTimeFromSeconds($dateLast, true) . " ago"); } } @@ -1095,88 +1265,199 @@ class CronArchive */ private function getDelayBetweenPeriodsArchives() { - $forceTimeoutPeriod = $this->getParameterFromCli("force-timeout-for-periods", $valuePossible = true); - if (empty($forceTimeoutPeriod) || $forceTimeoutPeriod === true) { + if (empty($this->forceTimeoutPeriod)) { return self::SECONDS_DELAY_BETWEEN_PERIOD_ARCHIVES; } // Ensure the cache for periods is at least as high as cache for today - if ($forceTimeoutPeriod > $this->todayArchiveTimeToLive) { - return $forceTimeoutPeriod; + if ($this->forceTimeoutPeriod > $this->todayArchiveTimeToLive) { + return $this->forceTimeoutPeriod; } - $this->log("WARNING: Automatically increasing --force-timeout-for-periods from $forceTimeoutPeriod to " + $this->logger->info("WARNING: Automatically increasing --force-timeout-for-periods from {$this->forceTimeoutPeriod} to " . $this->todayArchiveTimeToLive . " to match the cache timeout for Today's report specified in Piwik UI > Settings > General Settings"); + return $this->todayArchiveTimeToLive; } private function isShouldArchiveAllSitesWithTrafficSince() { - $shouldArchiveAllPeriodsSince = $this->getParameterFromCli("force-all-periods", $valuePossible = true); - if(empty($shouldArchiveAllPeriodsSince)) { + if (empty($this->shouldArchiveAllPeriodsSince)) { return false; } - if ( is_numeric($shouldArchiveAllPeriodsSince) - && $shouldArchiveAllPeriodsSince > 1 + + if (is_numeric($this->shouldArchiveAllPeriodsSince) + && $this->shouldArchiveAllPeriodsSince > 1 ) { - return (int)$shouldArchiveAllPeriodsSince; + return (int)$this->shouldArchiveAllPeriodsSince; } + return true; } - /** - * @param $idsite - */ - protected function setSiteIsArchived($idsite) - { - $websiteIdsInvalidated = APICoreAdminHome::getWebsiteIdsToInvalidate(); - if (count($websiteIdsInvalidated)) { - $found = array_search($idsite, $websiteIdsInvalidated); - if ($found !== false) { - unset($websiteIdsInvalidated[$found]); - Option::set(APICoreAdminHome::OPTION_INVALIDATED_IDSITES, serialize($websiteIdsInvalidated)); - } - } - } - - private function logFatalErrorUrlExpected() - { - $this->logFatalError("archive.php expects the argument 'url' to be set to your Piwik URL, for example: --url=http://example.org/piwik/ " - . "\n--help for more information", $backtrace = false); - } - private function getVisitsLastPeriodFromApiResponse($stats) { - if(empty($stats)) { + if (empty($stats)) { return 0; } + $today = end($stats); + + if (empty($today['nb_visits'])) { + return 0; + } + return $today['nb_visits']; } private function getVisitsFromApiResponse($stats) { - if(empty($stats)) { + if (empty($stats)) { return 0; } + $visits = 0; - foreach($stats as $metrics) { - if(empty($metrics['nb_visits'])) { + foreach ($stats as $metrics) { + if (empty($metrics['nb_visits'])) { continue; } $visits += $metrics['nb_visits']; } + return $visits; } /** - * @param $idsite + * @param $idSite * @param $period * @param $lastTimestampWebsiteProcessed * @return float|int|true */ - private function getApiDateLastParameter($idsite, $period, $lastTimestampWebsiteProcessed = false) + private function getApiDateParameter($idSite, $period, $lastTimestampWebsiteProcessed = false) + { + $dateRangeForced = $this->getDateRangeToProcess(); + + if (!empty($dateRangeForced)) { + return $dateRangeForced; + } + + return $this->getDateLastN($idSite, $period, $lastTimestampWebsiteProcessed); + } + + /** + * @param $idSite + * @param $period + * @param $date + * @param $segmentsCount + * @param $visitsInLastPeriods + * @param $visitsToday + * @param $timer + */ + private function logArchivedWebsite($idSite, $period, $date, $segmentsCount, $visitsInLastPeriods, $visitsToday, Timer $timer) + { + if (strpos($date, 'last') === 0 || strpos($date, 'previous') === 0) { + $humanReadable = $this->formatReadableDateRange($date); + $visitsInLastPeriods = (int)$visitsInLastPeriods . " visits in $humanReadable " . $period . "s, "; + $thisPeriod = $period == "day" ? "today" : "this " . $period; + $visitsInLastPeriod = (int)$visitsToday . " visits " . $thisPeriod . ", "; + } else { + $visitsInLastPeriods = (int)$visitsInLastPeriods . " visits in " . $period . "s included in: $date, "; + $visitsInLastPeriod = ''; + } + + $this->logger->info("Archived website id = $idSite, period = $period, $segmentsCount segments, " + . $visitsInLastPeriods + . $visitsInLastPeriod + . $timer->__toString()); + } + + private function getDateRangeToProcess() + { + if (empty($this->restrictToDateRange)) { + return false; + } + + if (strpos($this->restrictToDateRange, ',') === false) { + throw new Exception("--force-date-range expects a date range ie. YYYY-MM-DD,YYYY-MM-DD"); + } + + return $this->restrictToDateRange; + } + + /** + * @return array + */ + private function getPeriodsToProcess() + { + $this->restrictToPeriods = array_intersect($this->restrictToPeriods, $this->getDefaultPeriodsToProcess()); + $this->restrictToPeriods = array_intersect($this->restrictToPeriods, PeriodFactory::getPeriodsEnabledForAPI()); + + return $this->restrictToPeriods; + } + + /** + * @return array + */ + private function getDefaultPeriodsToProcess() + { + return array('day', 'week', 'month', 'year', 'range'); + } + + /** + * @param $idSite + * @return bool + */ + private function isOldReportInvalidatedForWebsite($idSite) + { + return in_array($idSite, $this->idSitesInvalidatedOldReports); + } + + private function isWebsiteUsingTheTracker($idSite) + { + if (!isset($this->idSitesNotUsingTracker)) { + // we want to trigger event only once + $this->idSitesNotUsingTracker = array(); + + /** + * This event is triggered when detecting whether there are sites that do not use the tracker. + * + * By default we only archive a site when there was actually any visit since the last archiving. + * However, some plugins do import data from another source instead of using the tracker and therefore + * will never have any visits for this site. To make sure we still archive data for such a site when + * archiving for this site is requested, you can listen to this event and add the idSite to the list of + * sites that do not use the tracker. + * + * @param bool $idSitesNotUsingTracker The list of idSites that rather import data instead of using the tracker + */ + Piwik::postEvent('CronArchive.getIdSitesNotUsingTracker', array(&$this->idSitesNotUsingTracker)); + + if (!empty($this->idSitesNotUsingTracker)) { + $this->logger->info("- The following websites do not use the tracker: " . implode(',', $this->idSitesNotUsingTracker)); + } + } + + $isUsingTracker = !in_array($idSite, $this->idSitesNotUsingTracker); + + return $isUsingTracker; + } + + private function shouldProcessPeriod($period) + { + if (empty($this->shouldArchiveOnlySpecificPeriods)) { + return true; + } + + return in_array($period, $this->shouldArchiveOnlySpecificPeriods); + } + + /** + * @param $idSite + * @param $period + * @param $lastTimestampWebsiteProcessed + * @return string + */ + private function getDateLastN($idSite, $period, $lastTimestampWebsiteProcessed) { $dateLastMax = self::DEFAULT_DATE_LAST; if ($period == 'year') { @@ -1185,7 +1466,8 @@ class CronArchive $dateLastMax = self::DEFAULT_DATE_LAST_WEEKS; } if (empty($lastTimestampWebsiteProcessed)) { - $lastTimestampWebsiteProcessed = strtotime(\Piwik\Site::getCreationDateFor($idsite)); + $creationDateFor = \Piwik\Site::getCreationDateFor($idSite); + $lastTimestampWebsiteProcessed = strtotime($creationDateFor); } // Enforcing last2 at minimum to work around timing issues and ensure we make most archives available @@ -1194,54 +1476,254 @@ class CronArchive $dateLast = $dateLastMax; } - $dateLastForced = $this->getParameterFromCli('--force-date-last-n', true); - if (!empty($dateLastForced)) { - $dateLast = $dateLastForced; - return $dateLast; + if (!empty($this->dateLastForced)) { + $dateLast = $this->dateLastForced; } - return $dateLast; + + return "last" . $dateLast; } /** - * @param $idsite - * @param $period - * @param $dateLast - * @param $visitsInLastPeriods - * @param $visitsToday - * @param Timer $timerWebsite - * @param $timer + * @return int */ - private function logArchivedWebsite($idsite, $period, $dateLast, $visitsInLastPeriods, $visitsToday, Timer $timer) + private function getConcurrentRequestsPerWebsite() { - $thisPeriod = $period == "day" ? "today" : "this " . $period; - $this->log("Archived website id = $idsite, period = $period, " - . (int)$visitsInLastPeriods . " visits in last " . $dateLast . " " . $period . "s, " - . (int)$visitsToday . " visits " . $thisPeriod . ", " - . $timer->__toString()); - } -} - -class CronArchiveFatalException extends Exception -{ - private $fullOutput = null; - - public function __construct($message, $fullOutput = null) - { - parent::__construct($message); - - $this->fullOutput = $fullOutput; - } - - public function logAndExit(CronArchive$cronArchiver) - { - if ($cronArchiver->isCoreInited()) { - $cronArchiver->logError($this->getMessage()); + if (false !== $this->concurrentRequestsPerWebsite) { + return $this->concurrentRequestsPerWebsite; } - $fe = fopen('php://stderr', 'w'); - fwrite($fe, "Error in the last Piwik archive.php run: \n" . $this->getMessage() . "\n" - . (!empty($this->fullOutput) ? "\n\n Here is the full errors output:\n\n" . $this->fullOutput : '')); - - exit(1); + return self::MAX_CONCURRENT_API_REQUESTS; } -} \ No newline at end of file + + /** + * @param $idSite + * @return false|string + */ + private function getPeriodLastProcessedTimestamp($idSite) + { + $timestamp = Option::get($this->lastRunKey($idSite, "periods")); + return $this->sanitiseTimestamp($timestamp); + } + + /** + * @param $idSite + * @return false|string + */ + private function getDayLastProcessedTimestamp($idSite) + { + $timestamp = Option::get($this->lastRunKey($idSite, "day")); + return $this->sanitiseTimestamp($timestamp); + } + + /** + * @return false|string + */ + private function getLastSuccessRunTimestamp() + { + $timestamp = Option::get(self::OPTION_ARCHIVING_FINISHED_TS); + return $this->sanitiseTimestamp($timestamp); + } + + private function sanitiseTimestamp($timestamp) + { + $now = time(); + return ($timestamp < $now) ? $timestamp : $now; + } + + /** + * @param $idSite + * @return array of date strings + */ + private function getCustomDateRangeToPreProcess($idSite) + { + static $cache = null; + if (is_null($cache)) { + $cache = $this->loadCustomDateRangeToPreProcess(); + } + if (empty($cache[$idSite])) { + return array(); + } + $dates = array_unique($cache[$idSite]); + return $dates; + } + + /** + * @return array + */ + private function loadCustomDateRangeToPreProcess() + { + $customDateRangesToProcessForSites = array(); + + // For all users who have selected this website to load by default, + // we load the default period/date that will be loaded for this user + // and make sure it's pre-archived + $allUsersPreferences = APIUsersManager::getInstance()->getAllUsersPreferences(array( + APIUsersManager::PREFERENCE_DEFAULT_REPORT_DATE, + APIUsersManager::PREFERENCE_DEFAULT_REPORT + )); + + foreach ($allUsersPreferences as $userLogin => $userPreferences) { + if (!isset($userPreferences[APIUsersManager::PREFERENCE_DEFAULT_REPORT_DATE])) { + continue; + } + + $defaultDate = $userPreferences[APIUsersManager::PREFERENCE_DEFAULT_REPORT_DATE]; + $preference = new UserPreferences(); + $period = $preference->getDefaultPeriod($defaultDate); + if ($period != 'range') { + continue; + } + + if (isset($userPreferences[APIUsersManager::PREFERENCE_DEFAULT_REPORT]) + && is_numeric($userPreferences[APIUsersManager::PREFERENCE_DEFAULT_REPORT])) { + // If user selected one particular website ID + $idSites = array($userPreferences[APIUsersManager::PREFERENCE_DEFAULT_REPORT]); + } else { + // If user selected "All websites" or some other random value, we pre-process all websites that he has access to + $idSites = APISitesManager::getInstance()->getSitesIdWithAtLeastViewAccess($userLogin); + } + + foreach ($idSites as $idSite) { + $customDateRangesToProcessForSites[$idSite][] = $defaultDate; + } + } + + return $customDateRangesToProcessForSites; + } + + /** + * @param $url + * @return string + */ + private function makeRequestUrl($url) + { + $url = $url . self::APPEND_TO_API_REQUEST; + + if ($this->shouldStartProfiler) { + $url .= "&xhprof=2"; + } + + if ($this->testmode) { + $url .= "&testmode=1"; + } + + return $url; + } + + /** + * @param $idSite + * @param $period + * @param $date + * @return Request[] + */ + private function getUrlsWithSegment($idSite, $period, $date) + { + $urlsWithSegment = array(); + $segmentsForSite = $this->getSegmentsForSite($idSite, $period); + + $segments = array(); + foreach ($segmentsForSite as $segment) { + if ($this->shouldSkipSegmentArchiving($segment)) { + $this->logger->info("- skipping segment archiving for '{segment}'.", array('segment' => $segment)); + + continue; + } + + $segments[] = $segment; + } + + $segmentCount = count($segments); + $processedSegmentCount = 0; + foreach ($segments as $segment) { + $dateParamForSegment = $this->segmentArchivingRequestUrlProvider->getUrlParameterDateString($idSite, $period, $date, $segment); + + $urlWithSegment = $this->getVisitsRequestUrl($idSite, $period, $dateParamForSegment, $segment); + $urlWithSegment = $this->makeRequestUrl($urlWithSegment); + + $request = new Request($urlWithSegment); + $logger = $this->logger; + $request->before(function () use ($logger, $segment, $segmentCount, &$processedSegmentCount) { + $processedSegmentCount++; + $logger->info(sprintf( + '- pre-processing segment %d/%d %s', + $processedSegmentCount, + $segmentCount, + $segment + )); + }); + + $urlsWithSegment[] = $request; + } + + return $urlsWithSegment; + } + + private function createSitesToArchiveQueue($websitesIds) + { + // use synchronous, single process queue if --force-idsites is used or sharing site IDs isn't supported + if (!SharedSiteIds::isSupported() || !empty($this->shouldArchiveSpecifiedSites)) { + return new FixedSiteIds($websitesIds); + } + + // use separate shared queue if --force-all-websites is used + if (!empty($this->shouldArchiveAllSites)) { + return new SharedSiteIds($websitesIds, SharedSiteIds::OPTION_ALL_WEBSITES); + } + + return new SharedSiteIds($websitesIds); + } + + /** + * @param $idSite + * @param $period + */ + private function logArchiveWebsite($idSite, $period, $date) + { + $this->logger->info(sprintf( + "Will pre-process for website id = %s, period = %s, date = %s", + $idSite, + $period, + $date + )); + $this->logger->info('- pre-processing all visits'); + } + + private function shouldSkipSegmentArchiving($segment) + { + if ($this->disableSegmentsArchiving) { + return true; + } + + return !empty($this->segmentsToForce) && !in_array($segment, $this->segmentsToForce); + } + + private function logForcedSegmentInfo() + { + if (empty($this->segmentsToForce)) { + return; + } + + $this->logger->info("- Limiting segment archiving to following segments:"); + foreach ($this->segmentsToForce as $segmentDefinition) { + $this->logger->info(" * " . $segmentDefinition); + } + } + + /** + * @return CliMulti + */ + private function makeCliMulti() + { + $cliMulti = StaticContainer::get('Piwik\CliMulti'); + $cliMulti->setUrlToPiwik($this->urlToPiwik); + $cliMulti->setPhpCliConfigurationOptions($this->phpCliConfigurationOptions); + $cliMulti->setAcceptInvalidSSLCertificate($this->acceptInvalidSSLCertificate); + $cliMulti->runAsSuperUser(); + return $cliMulti; + } + + public function setUrlToPiwik($url) + { + $this->urlToPiwik = $url; + } +} diff --git a/www/analytics/core/CronArchive/FixedSiteIds.php b/www/analytics/core/CronArchive/FixedSiteIds.php index 73db0b9d..685d7ebe 100644 --- a/www/analytics/core/CronArchive/FixedSiteIds.php +++ b/www/analytics/core/CronArchive/FixedSiteIds.php @@ -1,6 +1,6 @@ processNewSegmentsFrom = $processNewSegmentsFrom; + $this->segmentEditorModel = $segmentEditorModel ?: new Model(); + $this->segmentListCache = $segmentListCache ?: new Transient(); + $this->now = $now ?: Date::factory('now'); + $this->logger = $logger ?: StaticContainer::get('Psr\Log\LoggerInterface'); + } + + public function getUrlParameterDateString($idSite, $period, $date, $segment) + { + $oldestDateToProcessForNewSegment = $this->getOldestDateToProcessForNewSegment($idSite, $segment); + if (empty($oldestDateToProcessForNewSegment)) { + return $date; + } + + // if the start date for the archiving request is before the minimum date allowed for processing this segment, + // use the minimum allowed date as the start date + $periodObj = PeriodFactory::build($period, $date); + if ($periodObj->getDateStart()->getTimestamp() < $oldestDateToProcessForNewSegment->getTimestamp()) { + $this->logger->debug("Start date of archiving request period ({start}) is older than configured oldest date to process for the segment.", array( + 'start' => $periodObj->getDateStart() + )); + + $endDate = $periodObj->getDateEnd(); + + // if the creation time of a segment is older than the end date of the archiving request range, we cannot + // blindly rewrite the date string, since the resulting range would be incorrect. instead we make the + // start date equal to the end date, so less archiving occurs, and no fatal error occurs. + if ($oldestDateToProcessForNewSegment->getTimestamp() > $endDate->getTimestamp()) { + $this->logger->debug("Oldest date to process is greater than end date of archiving request period ({end}), so setting oldest date to end date.", array( + 'end' => $endDate + )); + + $oldestDateToProcessForNewSegment = $endDate; + } + + $date = $oldestDateToProcessForNewSegment->toString().','.$endDate; + + $this->logger->debug("Archiving request date range changed to {date} w/ period {period}.", array('date' => $date, 'period' => $period)); + } + + return $date; + } + + private function getOldestDateToProcessForNewSegment($idSite, $segment) + { + /** + * @var Date $segmentCreatedTime + * @var Date $segmentLastEditedTime + */ + list($segmentCreatedTime, $segmentLastEditedTime) = $this->getCreatedTimeOfSegment($idSite, $segment); + + if ($this->processNewSegmentsFrom == self::CREATION_TIME) { + $this->logger->debug("process_new_segments_from set to segment_creation_time, oldest date to process is {time}", array('time' => $segmentCreatedTime)); + + return $segmentCreatedTime; + } elseif ($this->processNewSegmentsFrom == self::LAST_EDIT_TIME) { + $this->logger->debug("process_new_segments_from set to segment_last_edit_time, segment last edit time is {time}", + array('time' => $segmentLastEditedTime)); + + if ($segmentLastEditedTime === null + || $segmentLastEditedTime->getTimestamp() < $segmentCreatedTime->getTimestamp() + ) { + $this->logger->debug("segment last edit time is older than created time, using created time instead"); + + $segmentLastEditedTime = $segmentCreatedTime; + } + + return $segmentLastEditedTime; + } elseif (preg_match("/^last([0-9]+)$/", $this->processNewSegmentsFrom, $matches)) { + $lastN = $matches[1]; + + list($lastDate, $lastPeriod) = Range::getDateXPeriodsAgo($lastN, $segmentCreatedTime, 'day'); + $result = Date::factory($lastDate); + + $this->logger->debug("process_new_segments_from set to last{N}, oldest date to process is {time}", array('N' => $lastN, 'time' => $result)); + + return $result; + } else { + $this->logger->debug("process_new_segments_from set to beginning_of_time or cannot recognize value"); + + return null; + } + } + + private function getCreatedTimeOfSegment($idSite, $segmentDefinition) + { + $segments = $this->getAllSegments(); + + /** @var Date $latestEditTime */ + $latestEditTime = null; + $earliestCreatedTime = $this->now; + foreach ($segments as $segment) { + if (empty($segment['ts_created']) + || empty($segment['definition']) + || !isset($segment['enable_only_idsite']) + ) { + continue; + } + + if ($this->isSegmentForSite($segment, $idSite) + && $segment['definition'] == $segmentDefinition + ) { + // check for an earlier ts_created timestamp + $createdTime = Date::factory($segment['ts_created']); + if ($createdTime->getTimestamp() < $earliestCreatedTime->getTimestamp()) { + $earliestCreatedTime = $createdTime; + } + + // if there is no ts_last_edit timestamp, initialize it to ts_created + if (empty($segment['ts_last_edit'])) { + $segment['ts_last_edit'] = $segment['ts_created']; + } + + // check for a later ts_last_edit timestamp + $lastEditTime = Date::factory($segment['ts_last_edit']); + if ($latestEditTime === null + || $latestEditTime->getTimestamp() < $lastEditTime->getTimestamp() + ) { + $latestEditTime = $lastEditTime; + } + } + } + + $this->logger->debug( + "Earliest created time of segment '{segment}' w/ idSite = {idSite} is found to be {createdTime}. Latest " . + "edit time is found to be {latestEditTime}.", + array( + 'segment' => $segmentDefinition, + 'idSite' => $idSite, + 'createdTime' => $earliestCreatedTime, + 'latestEditTime' => $latestEditTime, + ) + ); + + return array($earliestCreatedTime, $latestEditTime); + } + + private function getAllSegments() + { + if (!$this->segmentListCache->contains('all')) { + $segments = $this->segmentEditorModel->getAllSegmentsAndIgnoreVisibility(); + + $this->segmentListCache->save('all', $segments); + } + + return $this->segmentListCache->fetch('all'); + } + + private function isSegmentForSite($segment, $idSite) + { + return $segment['enable_only_idsite'] == 0 + || $segment['enable_only_idsite'] == $idSite; + } +} diff --git a/www/analytics/core/CronArchive/SharedSiteIds.php b/www/analytics/core/CronArchive/SharedSiteIds.php index c36b449b..f77f3d3f 100644 --- a/www/analytics/core/CronArchive/SharedSiteIds.php +++ b/www/analytics/core/CronArchive/SharedSiteIds.php @@ -1,6 +1,6 @@ optionName = $optionName; + if (empty($websiteIds)) { $websiteIds = array(); } @@ -86,16 +96,16 @@ class SharedSiteIds public function setSiteIdsToArchive($siteIds) { if (!empty($siteIds)) { - Option::set('SharedSiteIdsToArchive', implode(',', $siteIds)); + Option::set($this->optionName, implode(',', $siteIds)); } else { - Option::delete('SharedSiteIdsToArchive'); + Option::delete($this->optionName); } } public function getAllSiteIdsToArchive() { - Option::clearCachedOption('SharedSiteIdsToArchive'); - $siteIdsToArchive = Option::get('SharedSiteIdsToArchive'); + Option::clearCachedOption($this->optionName); + $siteIdsToArchive = Option::get($this->optionName); if (empty($siteIdsToArchive)) { return array(); @@ -171,6 +181,4 @@ class SharedSiteIds { return Process::isSupported(); } - } - diff --git a/www/analytics/core/CronArchive/SitesToReprocessDistributedList.php b/www/analytics/core/CronArchive/SitesToReprocessDistributedList.php new file mode 100644 index 00000000..ad58cc3a --- /dev/null +++ b/www/analytics/core/CronArchive/SitesToReprocessDistributedList.php @@ -0,0 +1,40 @@ +getPeriod()->getDateStart(); - $bindSQL = array($params->getSite()->getId(), - $dateStart->toString('Y-m-d'), - $params->getPeriod()->getDateEnd()->toString('Y-m-d'), - $params->getPeriod()->getId(), - ); + return new Model(); + } - $timeStampWhere = ''; + public static function getArchiveIdAndVisits(ArchiveProcessor\Parameters $params, $minDatetimeArchiveProcessedUTC) + { + $idSite = $params->getSite()->getId(); + $period = $params->getPeriod()->getId(); + $dateStart = $params->getPeriod()->getDateStart(); + $dateStartIso = $dateStart->toString('Y-m-d'); + $dateEndIso = $params->getPeriod()->getDateEnd()->toString('Y-m-d'); + + $numericTable = ArchiveTableCreator::getNumericTable($dateStart); + + $minDatetimeIsoArchiveProcessedUTC = null; if ($minDatetimeArchiveProcessedUTC) { - $timeStampWhere = " AND ts_archived >= ? "; - $bindSQL[] = Date::factory($minDatetimeArchiveProcessedUTC)->getDatetime(); + $minDatetimeIsoArchiveProcessedUTC = Date::factory($minDatetimeArchiveProcessedUTC)->getDatetime(); } $requestedPlugin = $params->getRequestedPlugin(); - $segment = $params->getSegment(); - $isSkipAggregationOfSubTables = $params->isSkipAggregationOfSubTables(); - + $segment = $params->getSegment(); $plugins = array("VisitsSummary", $requestedPlugin); - $sqlWhereArchiveName = self::getNameCondition($plugins, $segment, $isSkipAggregationOfSubTables); - $sqlQuery = " SELECT idarchive, value, name, date1 as startDate - FROM " . ArchiveTableCreator::getNumericTable($dateStart) . "`` - WHERE idsite = ? - AND date1 = ? - AND date2 = ? - AND period = ? - AND ( ($sqlWhereArchiveName) - OR name = '" . self::NB_VISITS_RECORD_LOOKED_UP . "' - OR name = '" . self::NB_VISITS_CONVERTED_RECORD_LOOKED_UP . "') - $timeStampWhere - ORDER BY idarchive DESC"; - $results = Db::fetchAll($sqlQuery, $bindSQL); + $doneFlags = Rules::getDoneFlags($plugins, $segment); + $doneFlagValues = Rules::getSelectableDoneFlagValues(); + + $results = self::getModel()->getArchiveIdAndVisits($numericTable, $idSite, $period, $dateStartIso, $dateEndIso, $minDatetimeIsoArchiveProcessedUTC, $doneFlags, $doneFlagValues); + if (empty($results)) { return false; } - $idArchive = self::getMostRecentIdArchiveFromResults($segment, $requestedPlugin, $isSkipAggregationOfSubTables, $results); - $idArchiveVisitsSummary = self::getMostRecentIdArchiveFromResults($segment, "VisitsSummary", $isSkipAggregationOfSubTables, $results); + $idArchive = self::getMostRecentIdArchiveFromResults($segment, $requestedPlugin, $results); + + $idArchiveVisitsSummary = self::getMostRecentIdArchiveFromResults($segment, "VisitsSummary", $results); list($visits, $visitsConverted) = self::getVisitsMetricsFromResults($idArchive, $idArchiveVisitsSummary, $results); - if ($visits === false - && $idArchive === false - ) { + if (false === $visits && false === $idArchive) { return false; } @@ -98,9 +90,11 @@ class ArchiveSelector { $visits = $visitsConverted = false; $archiveWithVisitsMetricsWasFound = ($idArchiveVisitsSummary !== false); + if ($archiveWithVisitsMetricsWasFound) { $visits = $visitsConverted = 0; } + foreach ($results as $result) { if (in_array($result['idarchive'], array($idArchive, $idArchiveVisitsSummary))) { $value = (int)$result['value']; @@ -116,13 +110,15 @@ class ArchiveSelector } } } + return array($visits, $visitsConverted); } - protected static function getMostRecentIdArchiveFromResults(Segment $segment, $requestedPlugin, $isSkipAggregationOfSubTables, $results) + protected static function getMostRecentIdArchiveFromResults(Segment $segment, $requestedPlugin, $results) { $idArchive = false; - $namesRequestedPlugin = Rules::getDoneFlags(array($requestedPlugin), $segment, $isSkipAggregationOfSubTables); + $namesRequestedPlugin = Rules::getDoneFlags(array($requestedPlugin), $segment); + foreach ($results as $result) { if ($idArchive === false && in_array($result['name'], $namesRequestedPlugin) @@ -131,6 +127,7 @@ class ArchiveSelector break; } } + return $idArchive; } @@ -141,21 +138,29 @@ class ArchiveSelector * @param array $periods * @param Segment $segment * @param array $plugins List of plugin names for which data is being requested. - * @param bool $isSkipAggregationOfSubTables Whether we are selecting an archive that may be partial (no sub-tables) * @return array Archive IDs are grouped by archive name and period range, ie, * array( * 'VisitsSummary.done' => array( * '2010-01-01' => array(1,2,3) * ) * ) + * @throws */ - static public function getArchiveIds($siteIds, $periods, $segment, $plugins, $isSkipAggregationOfSubTables = false) + public static function getArchiveIds($siteIds, $periods, $segment, $plugins) { + if (empty($siteIds)) { + throw new \Exception("Website IDs could not be read from the request, ie. idSite="); + } + + foreach ($siteIds as $index => $siteId) { + $siteIds[$index] = (int) $siteId; + } + $getArchiveIdsSql = "SELECT idsite, name, date1, date2, MAX(idarchive) as idarchive FROM %s - WHERE %s - AND " . self::getNameCondition($plugins, $segment, $isSkipAggregationOfSubTables) . " - AND idsite IN (" . implode(',', $siteIds) . ") + WHERE idsite IN (" . implode(',', $siteIds) . ") + AND " . self::getNameCondition($plugins, $segment) . " + AND %s GROUP BY idsite, date1, date2"; $monthToPeriods = array(); @@ -197,14 +202,14 @@ class ArchiveSelector $sql = sprintf($getArchiveIdsSql, $table, $dateCondition); + $archiveIds = Db::fetchAll($sql, $bind); + // get the archive IDs - foreach (Db::fetchAll($sql, $bind) as $row) { - $archiveName = $row['name']; - + foreach ($archiveIds as $row) { //FIXMEA duplicate with Archive.php - $dateStr = $row['date1'] . "," . $row['date2']; + $dateStr = $row['date1'] . ',' . $row['date2']; - $result[$archiveName][$dateStr][] = $row['idarchive']; + $result[$row['name']][$dateStr][] = $row['idarchive']; } } @@ -215,29 +220,52 @@ class ArchiveSelector * Queries and returns archive data using a set of archive IDs. * * @param array $archiveIds The IDs of the archives to get data from. - * @param array $recordNames The names of the data to retrieve (ie, nb_visits, nb_actions, etc.) + * @param array $recordNames The names of the data to retrieve (ie, nb_visits, nb_actions, etc.). + * Note: You CANNOT pass multiple recordnames if $loadAllSubtables=true. * @param string $archiveDataType The archive data type (either, 'blob' or 'numeric'). - * @param bool $loadAllSubtables Whether to pre-load all subtables + * @param int|null|string $idSubtable null if the root blob should be loaded, an integer if a subtable should be + * loaded and 'all' if all subtables should be loaded. * @throws Exception * @return array */ - static public function getArchiveData($archiveIds, $recordNames, $archiveDataType, $loadAllSubtables) + public static function getArchiveData($archiveIds, $recordNames, $archiveDataType, $idSubtable) { + $chunk = new Chunk(); + // create the SQL to select archive data - $inNames = Common::getSqlStringFieldsArray($recordNames); + $loadAllSubtables = $idSubtable == Archive::ID_SUBTABLE_LOAD_ALL_SUBTABLES; if ($loadAllSubtables) { $name = reset($recordNames); // select blobs w/ name like "$name_[0-9]+" w/o using RLIKE - $nameEnd = strlen($name) + 2; - $whereNameIs = "(name = ? - OR (name LIKE ? - AND SUBSTRING(name, $nameEnd, 1) >= '0' - AND SUBSTRING(name, $nameEnd, 1) <= '9') )"; + $nameEnd = strlen($name) + 1; + $nameEndAppendix = $nameEnd + 1; + $appendix = $chunk->getAppendix(); + $lenAppendix = strlen($appendix); + + $checkForChunkBlob = "SUBSTRING(name, $nameEnd, $lenAppendix) = '$appendix'"; + $checkForSubtableId = "(SUBSTRING(name, $nameEndAppendix, 1) >= '0' + AND SUBSTRING(name, $nameEndAppendix, 1) <= '9')"; + + $whereNameIs = "(name = ? OR (name LIKE ? AND ( $checkForChunkBlob OR $checkForSubtableId ) ))"; $bind = array($name, $name . '%'); } else { + if ($idSubtable === null) { + // select root table or specific record names + $bind = array_values($recordNames); + } else { + // select a subtable id + $bind = array(); + foreach ($recordNames as $recordName) { + // to be backwards compatibe we need to look for the exact idSubtable blob and for the chunk + // that stores the subtables (a chunk stores many blobs in one blob) + $bind[] = $chunk->getRecordNameForTableId($recordName, $idSubtable); + $bind[] = self::appendIdSubtable($recordName, $idSubtable); + } + } + + $inNames = Common::getSqlStringFieldsArray($bind); $whereNameIs = "name IN ($inNames)"; - $bind = array_values($recordNames); } $getValuesSql = "SELECT value, name, idsite, date1, date2, ts_archived @@ -251,110 +279,91 @@ class ArchiveSelector if (empty($ids)) { throw new Exception("Unexpected: id archive not found for period '$period' '"); } + // $period = "2009-01-04,2009-01-04", $date = Date::factory(substr($period, 0, 10)); - if ($archiveDataType == 'numeric') { + + $isNumeric = $archiveDataType == 'numeric'; + if ($isNumeric) { $table = ArchiveTableCreator::getNumericTable($date); } else { $table = ArchiveTableCreator::getBlobTable($date); } - $sql = sprintf($getValuesSql, $table, implode(',', $ids)); + + $sql = sprintf($getValuesSql, $table, implode(',', $ids)); $dataRows = Db::fetchAll($sql, $bind); + foreach ($dataRows as $row) { - $rows[] = $row; + if ($isNumeric) { + $rows[] = $row; + } else { + $row['value'] = self::uncompress($row['value']); + + if ($chunk->isRecordNameAChunk($row['name'])) { + self::moveChunkRowToRows($rows, $row, $chunk, $loadAllSubtables, $idSubtable); + } else { + $rows[] = $row; + } + } } } return $rows; } + private static function moveChunkRowToRows(&$rows, $row, Chunk $chunk, $loadAllSubtables, $idSubtable) + { + // $blobs = array([subtableID] = [blob of subtableId]) + $blobs = unserialize($row['value']); + + if (!is_array($blobs)) { + return; + } + + // $rawName = eg 'PluginName_ArchiveName' + $rawName = $chunk->getRecordNameWithoutChunkAppendix($row['name']); + + if ($loadAllSubtables) { + foreach ($blobs as $subtableId => $blob) { + $row['value'] = $blob; + $row['name'] = self::appendIdSubtable($rawName, $subtableId); + $rows[] = $row; + } + } elseif (array_key_exists($idSubtable, $blobs)) { + $row['value'] = $blobs[$idSubtable]; + $row['name'] = self::appendIdSubtable($rawName, $idSubtable); + $rows[] = $row; + } + } + + public static function appendIdSubtable($recordName, $id) + { + return $recordName . "_" . $id; + } + + private static function uncompress($data) + { + return @gzuncompress($data); + } + /** * Returns the SQL condition used to find successfully completed archives that * this instance is querying for. * * @param array $plugins * @param Segment $segment - * @param bool $isSkipAggregationOfSubTables * @return string */ - static private function getNameCondition(array $plugins, Segment $segment, $isSkipAggregationOfSubTables) + private static function getNameCondition(array $plugins, Segment $segment) { // the flags used to tell how the archiving process for a specific archive was completed, // if it was completed - $doneFlags = Rules::getDoneFlags($plugins, $segment, $isSkipAggregationOfSubTables); - + $doneFlags = Rules::getDoneFlags($plugins, $segment); $allDoneFlags = "'" . implode("','", $doneFlags) . "'"; + $possibleValues = Rules::getSelectableDoneFlagValues(); + // create the SQL to find archives that are DONE - return "((name IN ($allDoneFlags)) AND " . - " (value = '" . ArchiveWriter::DONE_OK . "' OR " . - " value = '" . ArchiveWriter::DONE_OK_TEMPORARY . "'))"; - } - - static public function purgeOutdatedArchives(Date $dateStart) - { - $purgeArchivesOlderThan = Rules::shouldPurgeOutdatedArchives($dateStart); - if (!$purgeArchivesOlderThan) { - return; - } - - $idArchivesToDelete = self::getTemporaryArchiveIdsOlderThan($dateStart, $purgeArchivesOlderThan); - if (!empty($idArchivesToDelete)) { - self::deleteArchiveIds($dateStart, $idArchivesToDelete); - } - self::deleteArchivesWithPeriodRange($dateStart); - - Log::debug("Purging temporary archives: done [ purged archives older than %s in %s ] [Deleted IDs: %s]", - $purgeArchivesOlderThan, $dateStart->toString("Y-m"), implode(',', $idArchivesToDelete)); - } - - /* - * Deleting "Custom Date Range" reports after 1 day, since they can be re-processed and would take up un-necessary space - */ - protected static function deleteArchivesWithPeriodRange(Date $date) - { - $query = "DELETE FROM %s WHERE period = ? AND ts_archived < ?"; - - $yesterday = Date::factory('yesterday')->getDateTime(); - $bind = array(Piwik::$idPeriods['range'], $yesterday); - $numericTable = ArchiveTableCreator::getNumericTable($date); - Db::query(sprintf($query, $numericTable), $bind); - Log::debug("Purging Custom Range archives: done [ purged archives older than %s from %s / blob ]", $yesterday, $numericTable); - try { - Db::query(sprintf($query, ArchiveTableCreator::getBlobTable($date)), $bind); - } catch (Exception $e) { - // Individual blob tables could be missing - } - } - - protected static function deleteArchiveIds(Date $date, $idArchivesToDelete) - { - $query = "DELETE FROM %s WHERE idarchive IN (" . implode(',', $idArchivesToDelete) . ")"; - - Db::query(sprintf($query, ArchiveTableCreator::getNumericTable($date))); - try { - Db::query(sprintf($query, ArchiveTableCreator::getBlobTable($date))); - } catch (Exception $e) { - // Individual blob tables could be missing - } - } - - protected static function getTemporaryArchiveIdsOlderThan(Date $date, $purgeArchivesOlderThan) - { - $query = "SELECT idarchive - FROM " . ArchiveTableCreator::getNumericTable($date) . " - WHERE name LIKE 'done%' - AND (( value = " . ArchiveWriter::DONE_OK_TEMPORARY . " - AND ts_archived < ?) - OR value = " . ArchiveWriter::DONE_ERROR . ")"; - - $result = Db::fetchAll($query, array($purgeArchivesOlderThan)); - $idArchivesToDelete = array(); - if (!empty($result)) { - foreach ($result as $row) { - $idArchivesToDelete[] = $row['idarchive']; - } - } - return $idArchivesToDelete; + return "((name IN ($allDoneFlags)) AND (value IN (" . implode(',', $possibleValues) . ")))"; } } diff --git a/www/analytics/core/DataAccess/ArchiveTableCreator.php b/www/analytics/core/DataAccess/ArchiveTableCreator.php index 6f8d0b90..ad95863d 100644 --- a/www/analytics/core/DataAccess/ArchiveTableCreator.php +++ b/www/analytics/core/DataAccess/ArchiveTableCreator.php @@ -1,6 +1,6 @@ toString('Y_m'); + $tableName = $tableNamePrefix . "_" . self::getTableMonthFromDate($date); $tableName = Common::prefixTable($tableName); + self::createArchiveTablesIfAbsent($tableName, $tableNamePrefix); + return $tableName; } - static protected function createArchiveTablesIfAbsent($tableName, $tableNamePrefix) + protected static function createArchiveTablesIfAbsent($tableName, $tableNamePrefix) { if (is_null(self::$tablesAlreadyInstalled)) { self::refreshTableList(); } if (!in_array($tableName, self::$tablesAlreadyInstalled)) { - $db = Db::get(); - $sql = DbHelper::getTableCreateSql($tableNamePrefix); - - // replace table name template by real name - $tableNamePrefix = Common::prefixTable($tableNamePrefix); - $sql = str_replace($tableNamePrefix, $tableName, $sql); - try { - $db->query($sql); - } catch (Exception $e) { - // accept mysql error 1050: table already exists, throw otherwise - if (!$db->isErrNo($e, '1050')) { - throw $e; - } - } + self::getModel()->createArchiveTable($tableName, $tableNamePrefix); self::$tablesAlreadyInstalled[] = $tableName; } } - static public function clear() + private static function getModel() + { + return new Model(); + } + + public static function clear() { self::$tablesAlreadyInstalled = null; } - static public function refreshTableList($forceReload = false) + public static function refreshTableList($forceReload = false) { self::$tablesAlreadyInstalled = DbHelper::getTablesInstalled($forceReload); } @@ -80,40 +71,53 @@ class ArchiveTableCreator /** * Returns all table names archive_* * + * @param string $type The type of table to return. Either `self::NUMERIC_TABLE` or `self::BLOB_TABLE`. * @return array */ - static public function getTablesArchivesInstalled() + public static function getTablesArchivesInstalled($type = null) { if (is_null(self::$tablesAlreadyInstalled)) { self::refreshTableList(); } + if (empty($type)) { + $tableMatchRegex = '/archive_(numeric|blob)_/'; + } else { + $tableMatchRegex = '/archive_' . preg_quote($type) . '_/'; + } + $archiveTables = array(); foreach (self::$tablesAlreadyInstalled as $table) { - if (strpos($table, 'archive_numeric_') !== false - || strpos($table, 'archive_blob_') !== false - ) { + if (preg_match($tableMatchRegex, $table)) { $archiveTables[] = $table; } } return $archiveTables; } - static public function getDateFromTableName($tableName) + public static function getDateFromTableName($tableName) { $tableName = Common::unprefixTable($tableName); - $date = str_replace(array('archive_numeric_', 'archive_blob_'), '', $tableName); + $date = str_replace(array('archive_numeric_', 'archive_blob_'), '', $tableName); + return $date; } - static public function getTypeFromTableName($tableName) + public static function getTableMonthFromDate(Date $date) + { + return $date->toString('Y_m'); + } + + public static function getTypeFromTableName($tableName) { if (strpos($tableName, 'archive_numeric_') !== false) { return self::NUMERIC_TABLE; } + if (strpos($tableName, 'archive_blob_') !== false) { return self::BLOB_TABLE; } + return false; } } diff --git a/www/analytics/core/DataAccess/ArchiveTableDao.php b/www/analytics/core/DataAccess/ArchiveTableDao.php new file mode 100644 index 00000000..889b170b --- /dev/null +++ b/www/analytics/core/DataAccess/ArchiveTableDao.php @@ -0,0 +1,89 @@ + '-', + 'count_invalidated_archives' => '-', + 'count_temporary_archives' => '-', + 'count_error_archives' => '-', + 'count_segment_archives' => '-', + 'count_numeric_rows' => '-', + ); + + $tableDate = str_replace("`", "", $tableDate); // for sanity + + $numericTable = Common::prefixTable("archive_numeric_$tableDate"); + $blobTable = Common::prefixTable("archive_blob_$tableDate"); + + // query numeric table + $sql = "SELECT CONCAT_WS('.', idsite, date1, date2, period) AS label, + SUM(CASE WHEN name LIKE 'done%' THEN 1 ELSE 0 END) AS count_archives, + SUM(CASE WHEN name LIKE 'done%' AND value = ? THEN 1 ELSE 0 END) AS count_invalidated_archives, + SUM(CASE WHEN name LIKE 'done%' AND value = ? THEN 1 ELSE 0 END) AS count_temporary_archives, + SUM(CASE WHEN name LIKE 'done%' AND value = ? THEN 1 ELSE 0 END) AS count_error_archives, + SUM(CASE WHEN name LIKE 'done%' AND CHAR_LENGTH(name) > 32 THEN 1 ELSE 0 END) AS count_segment_archives, + SUM(CASE WHEN name NOT LIKE 'done%' THEN 1 ELSE 0 END) AS count_numeric_rows, + 0 AS count_blob_rows + FROM `$numericTable` + GROUP BY idsite, date1, date2, period"; + + $rows = Db::fetchAll($sql, array(ArchiveWriter::DONE_INVALIDATED, ArchiveWriter::DONE_OK_TEMPORARY, + ArchiveWriter::DONE_ERROR)); + + // index result + $result = array(); + foreach ($rows as $row) { + $result[$row['label']] = $row; + } + + // query blob table & manually merge results (no FULL OUTER JOIN in mysql) + $sql = "SELECT CONCAT_WS('.', idsite, date1, date2, period) AS label, + COUNT(*) AS count_blob_rows + FROM `$blobTable` + GROUP BY idsite, date1, date1, period"; + + foreach (Db::fetchAll($sql) as $blobStatsRow) { + $label = $blobStatsRow['label']; + if (isset($result[$label])) { + $result[$label] = array_merge($result[$label], $blobStatsRow); + } else { + $result[$label] = $blobStatsRow + $numericQueryEmptyRow; + } + } + + return $result; + } +} \ No newline at end of file diff --git a/www/analytics/core/DataAccess/ArchiveWriter.php b/www/analytics/core/DataAccess/ArchiveWriter.php index 8c54dd52..3473a33c 100644 --- a/www/analytics/core/DataAccess/ArchiveWriter.php +++ b/www/analytics/core/DataAccess/ArchiveWriter.php @@ -1,6 +1,6 @@ idArchive = false; - $this->idSite = $params->getSite()->getId(); - $this->segment = $params->getSegment(); - $this->period = $params->getPeriod(); + $this->idSite = $params->getSite()->getId(); + $this->segment = $params->getSegment(); + $this->period = $params->getPeriod(); + $idSites = array($this->idSite); - $this->doneFlag = Rules::getDoneStringFlagFor($idSites, $this->segment, $this->period->getLabel(), $params->getRequestedPlugin(), $params->isSkipAggregationOfSubTables()); + $this->doneFlag = Rules::getDoneStringFlagFor($idSites, $this->segment, $this->period->getLabel(), $params->getRequestedPlugin()); $this->isArchiveTemporary = $isArchiveTemporary; $this->dateStart = $this->period->getDateStart(); @@ -74,25 +77,32 @@ class ArchiveWriter /** * @param string $name - * @param string[] $values + * @param string|string[] $values A blob string or an array of blob strings. If an array + * is used, the first element in the array will be inserted + * with the `$name` name. The others will be splitted into chunks. All subtables + * within one chunk will be serialized as an array where the index is the + * subtableId. */ public function insertBlobRecord($name, $values) { if (is_array($values)) { $clean = array(); - foreach ($values as $id => $value) { - // for the parent Table we keep the name - // for example for the Table of searchEngines we keep the name 'referrer_search_engine' - // but for the child table of 'Google' which has the ID = 9 the name would be 'referrer_search_engine_9' - $newName = $name; - if ($id != 0) { - //FIXMEA: refactor - $newName = $name . '_' . $id; - } - $value = $this->compress($value); - $clean[] = array($newName, $value); + if (isset($values[0])) { + // we always store the root table in a single blob for fast access + $clean[] = array($name, $this->compress($values[0])); + unset($values[0]); } + + if (!empty($values)) { + // we move all subtables into chunks + $chunk = new Chunk(); + $chunks = $chunk->moveArchiveBlobsIntoChunks($name, $values); + foreach ($chunks as $index => $subtables) { + $clean[] = array($index, $this->compress(serialize($subtables))); + } + } + $this->insertBulkRecords($clean); return; } @@ -106,6 +116,7 @@ class ArchiveWriter if ($this->idArchive === false) { throw new Exception("Must call allocateNewArchiveId() first"); } + return $this->idArchive; } @@ -117,108 +128,49 @@ class ArchiveWriter public function finalizeArchive() { - $this->deletePreviousArchiveStatus(); + $numericTable = $this->getTableNumeric(); + $idArchive = $this->getIdArchive(); + + $this->getModel()->deletePreviousArchiveStatus($numericTable, $idArchive, $this->doneFlag); + $this->logArchiveStatusAsFinal(); } - static protected function compress($data) + protected function compress($data) { if (Db::get()->hasBlobDataType()) { return gzcompress($data); } + return $data; } - protected function getArchiveLockName() - { - $numericTable = $this->getTableNumeric(); - $dbLockName = "allocateNewArchiveId.$numericTable"; - return $dbLockName; - } - - protected function acquireArchiveTableLock() - { - $dbLockName = $this->getArchiveLockName(); - if (Db::getDbLock($dbLockName, $maxRetries = 30) === false) { - throw new Exception("allocateNewArchiveId: Cannot get named lock $dbLockName."); - } - } - - protected function releaseArchiveTableLock() - { - $dbLockName = $this->getArchiveLockName(); - Db::releaseDbLock($dbLockName); - } - protected function allocateNewArchiveId() { - $this->idArchive = $this->insertNewArchiveId(); + $numericTable = $this->getTableNumeric(); + + $this->idArchive = $this->getModel()->allocateNewArchiveId($numericTable); return $this->idArchive; } - /** - * Locks the archive table to generate a new archive ID. - * - * We lock to make sure that - * if several archiving processes are running at the same time (for different websites and/or periods) - * then they will each use a unique archive ID. - * - * @return int - */ - protected function insertNewArchiveId() + private function getModel() { - $numericTable = $this->getTableNumeric(); - $idSite = $this->idSite; - - $this->acquireArchiveTableLock(); - - $locked = self::PREFIX_SQL_LOCK . Common::generateUniqId(); - $date = date("Y-m-d H:i:s"); - $insertSql = "INSERT INTO $numericTable " - . " SELECT IFNULL( MAX(idarchive), 0 ) + 1, - '" . $locked . "', - " . (int)$idSite . ", - '" . $date . "', - '" . $date . "', - 0, - '" . $date . "', - 0 " - . " FROM $numericTable as tb1"; - Db::get()->exec($insertSql); - - $this->releaseArchiveTableLock(); - - $selectIdSql = "SELECT idarchive FROM $numericTable WHERE name = ? LIMIT 1"; - $id = Db::get()->fetchOne($selectIdSql, $locked); - return $id; + return new Model(); } protected function logArchiveStatusAsIncomplete() { - $statusWhileProcessing = self::DONE_ERROR; - $this->insertRecord($this->doneFlag, $statusWhileProcessing); - } - - protected function deletePreviousArchiveStatus() - { - // without advisory lock here, the DELETE would acquire Exclusive Lock - $this->acquireArchiveTableLock(); - - Db::query("DELETE FROM " . $this->getTableNumeric() . " - WHERE idarchive = ? AND (name = '" . $this->doneFlag - . "' OR name LIKE '" . self::PREFIX_SQL_LOCK . "%')", - array($this->getIdArchive()) - ); - - $this->releaseArchiveTableLock(); + $this->insertRecord($this->doneFlag, self::DONE_ERROR); } protected function logArchiveStatusAsFinal() { $status = self::DONE_OK; + if ($this->isArchiveTemporary) { $status = self::DONE_OK_TEMPORARY; } + $this->insertRecord($this->doneFlag, $status); } @@ -231,27 +183,37 @@ class ArchiveWriter foreach ($records as $record) { $this->insertRecord($record[0], $record[1]); } + return true; } + $bindSql = $this->getInsertRecordBind(); - $values = array(); + $values = array(); $valueSeen = false; foreach ($records as $record) { // don't record zero - if (empty($record[1])) continue; + if (empty($record[1])) { + continue; + } - $bind = $bindSql; - $bind[] = $record[0]; // name - $bind[] = $record[1]; // value + $bind = $bindSql; + $bind[] = $record[0]; // name + $bind[] = $record[1]; // value $values[] = $bind; $valueSeen = $record[1]; } - if (empty($values)) return true; + + if (empty($values)) { + return true; + } $tableName = $this->getTableNameToInsert($valueSeen); - BatchInsert::tableInsertBatch($tableName, $this->getInsertFields(), $values); + $fields = $this->getInsertFields(); + + BatchInsert::tableInsertBatch($tableName, $fields, $values, $throwException = false, $charset = 'latin1'); + return true; } @@ -270,26 +232,22 @@ class ArchiveWriter } $tableName = $this->getTableNameToInsert($value); + $fields = $this->getInsertFields(); + $record = $this->getInsertRecordBind(); + + $this->getModel()->insertRecord($tableName, $fields, $record, $name, $value); - // duplicate idarchives are Ignored, see http://dev.piwik.org/trac/ticket/987 - $query = "INSERT IGNORE INTO " . $tableName . " - (" . implode(", ", $this->getInsertFields()) . ") - VALUES (?,?,?,?,?,?,?,?)"; - $bindSql = $this->getInsertRecordBind(); - $bindSql[] = $name; - $bindSql[] = $value; - Db::query($query, $bindSql); return true; } protected function getInsertRecordBind() { return array($this->getIdArchive(), - $this->idSite, - $this->dateStart->toString('Y-m-d'), - $this->period->getDateEnd()->toString('Y-m-d'), - $this->period->getId(), - date("Y-m-d H:i:s")); + $this->idSite, + $this->dateStart->toString('Y-m-d'), + $this->period->getDateEnd()->toString('Y-m-d'), + $this->period->getId(), + date("Y-m-d H:i:s")); } protected function getTableNameToInsert($value) @@ -297,6 +255,7 @@ class ArchiveWriter if (is_numeric($value)) { return $this->getTableNumeric(); } + return ArchiveTableCreator::getBlobTable($this->dateStart); } diff --git a/www/analytics/core/DataAccess/LogAggregator.php b/www/analytics/core/DataAccess/LogAggregator.php index f3a3a740..ceeba0b1 100644 --- a/www/analytics/core/DataAccess/LogAggregator.php +++ b/www/analytics/core/DataAccess/LogAggregator.php @@ -1,6 +1,6 @@ getLogAggregator(); - * + * * // get metrics for every used browser language of all visits by returning visitors * $query = $logAggregator->queryVisitsByDimension( * $dimensions = array('log_visit.location_browser_lang'), * $where = 'log_visit.visitor_returning = 1', - * + * * // also count visits for each browser language that are not located in the US * $additionalSelects = array('sum(case when log_visit.location_country <> 'us' then 1 else 0 end) as nonus'), - * + * * // we're only interested in visits, unique visitors & actions, so don't waste time calculating anything else * $metrics = array(Metrics::INDEX_NB_UNIQ_VISITORS, Metrics::INDEX_NB_VISITS, Metrics::INDEX_NB_ACTIONS), * ); * if ($query === false) { * return; * } - * + * * while ($row = $query->fetch()) { * $uniqueVisitors = $row[Metrics::INDEX_NB_UNIQ_VISITORS]; * $visits = $row[Metrics::INDEX_NB_VISITS]; @@ -89,8 +87,8 @@ use Piwik\Tracker\GoalManager; * $country = $row['location_country']; * $numEcommerceSales = $row[Metrics::INDEX_GOAL_NB_CONVERSIONS]; * $numVisitsWithEcommerceSales = $row[Metrics::INDEX_GOAL_NB_VISITS_CONVERTED]; - * $avgTaxForCountry = $country['avg_tax']; - * $maxShippingForCountry = $country['max_shipping']; + * $avgTaxForCountry = $row['avg_tax']; + * $maxShippingForCountry = $row['max_shipping']; * * // ... do something with aggregated data ... * } @@ -131,15 +129,20 @@ class LogAggregator /** @var \Piwik\Date */ protected $dateEnd; - /** @var \Piwik\Site */ - protected $site; + /** @var int[] */ + protected $sites; /** @var \Piwik\Segment */ protected $segment; + /** + * @var string + */ + private $queryOriginHint = ''; + /** * Constructor. - * + * * @param \Piwik\ArchiveProcessor\Parameters $params */ public function __construct(Parameters $params) @@ -147,30 +150,44 @@ class LogAggregator $this->dateStart = $params->getDateStart(); $this->dateEnd = $params->getDateEnd(); $this->segment = $params->getSegment(); - $this->site = $params->getSite(); + $this->sites = $params->getIdSites(); + } + + public function setQueryOriginHint($nameOfOrigiin) + { + $this->queryOriginHint = $nameOfOrigiin; } public function generateQuery($select, $from, $where, $groupBy, $orderBy) { - $bind = $this->getBindDatetimeSite(); + $bind = $this->getGeneralQueryBindParams(); $query = $this->segment->getSelectQuery($select, $from, $where, $bind, $orderBy, $groupBy); + + $select = 'SELECT'; + if ($this->queryOriginHint && is_array($query) && 0 === strpos(trim($query['sql']), $select)) { + $query['sql'] = trim($query['sql']); + $query['sql'] = 'SELECT /* ' . $this->queryOriginHint . ' */' . substr($query['sql'], strlen($select)); + } + return $query; } protected function getVisitsMetricFields() { return array( - Metrics::INDEX_NB_UNIQ_VISITORS => "count(distinct " . self::LOG_VISIT_TABLE . ".idvisitor)", - Metrics::INDEX_NB_VISITS => "count(*)", - Metrics::INDEX_NB_ACTIONS => "sum(" . self::LOG_VISIT_TABLE . ".visit_total_actions)", - Metrics::INDEX_MAX_ACTIONS => "max(" . self::LOG_VISIT_TABLE . ".visit_total_actions)", - Metrics::INDEX_SUM_VISIT_LENGTH => "sum(" . self::LOG_VISIT_TABLE . ".visit_total_time)", - Metrics::INDEX_BOUNCE_COUNT => "sum(case " . self::LOG_VISIT_TABLE . ".visit_total_actions when 1 then 1 when 0 then 1 else 0 end)", - Metrics::INDEX_NB_VISITS_CONVERTED => "sum(case " . self::LOG_VISIT_TABLE . ".visit_goal_converted when 1 then 1 else 0 end)", + Metrics::INDEX_NB_UNIQ_VISITORS => "count(distinct " . self::LOG_VISIT_TABLE . ".idvisitor)", + Metrics::INDEX_NB_UNIQ_FINGERPRINTS => "count(distinct " . self::LOG_VISIT_TABLE . ".config_id)", + Metrics::INDEX_NB_VISITS => "count(*)", + Metrics::INDEX_NB_ACTIONS => "sum(" . self::LOG_VISIT_TABLE . ".visit_total_actions)", + Metrics::INDEX_MAX_ACTIONS => "max(" . self::LOG_VISIT_TABLE . ".visit_total_actions)", + Metrics::INDEX_SUM_VISIT_LENGTH => "sum(" . self::LOG_VISIT_TABLE . ".visit_total_time)", + Metrics::INDEX_BOUNCE_COUNT => "sum(case " . self::LOG_VISIT_TABLE . ".visit_total_actions when 1 then 1 when 0 then 1 else 0 end)", + Metrics::INDEX_NB_VISITS_CONVERTED => "sum(case " . self::LOG_VISIT_TABLE . ".visit_goal_converted when 1 then 1 else 0 end)", + Metrics::INDEX_NB_USERS => "count(distinct " . self::LOG_VISIT_TABLE . ".user_id)", ); } - static public function getConversionsMetricFields() + public static function getConversionsMetricFields() { return array( Metrics::INDEX_GOAL_NB_CONVERSIONS => "count(*)", @@ -184,12 +201,12 @@ class LogAggregator ); } - static private function getSqlConversionRevenueSum($field) + private static function getSqlConversionRevenueSum($field) { return self::getSqlRevenue('SUM(' . self::LOG_CONVERSION_TABLE . '.' . $field . ')'); } - static public function getSqlRevenue($field) + public static function getSqlRevenue($field) { return "ROUND(" . $field . "," . GoalManager::REVENUE_PRECISION . ")"; } @@ -271,7 +288,7 @@ class LogAggregator * clause. These can be aggregate expressions, eg, `SUM(somecol)`. * @param bool|array $metrics The set of metrics to calculate and return. If false, the query will select * all of them. The following values can be used: - * + * * - {@link Piwik\Metrics::INDEX_NB_UNIQ_VISITORS} * - {@link Piwik\Metrics::INDEX_NB_VISITS} * - {@link Piwik\Metrics::INDEX_NB_ACTIONS} @@ -293,52 +310,61 @@ class LogAggregator $tableName = self::LOG_VISIT_TABLE; $availableMetrics = $this->getVisitsMetricFields(); - $select = $this->getSelectStatement($dimensions, $tableName, $additionalSelects, $availableMetrics, $metrics); - $from = array($tableName); - $where = $this->getWhereStatement($tableName, self::VISIT_DATETIME_FIELD, $where); + $select = $this->getSelectStatement($dimensions, $tableName, $additionalSelects, $availableMetrics, $metrics); + $from = array($tableName); + $where = $this->getWhereStatement($tableName, self::VISIT_DATETIME_FIELD, $where); $groupBy = $this->getGroupByStatement($dimensions, $tableName); $orderBy = false; if ($rankingQuery) { $orderBy = '`' . Metrics::INDEX_NB_VISITS . '` DESC'; } + $query = $this->generateQuery($select, $from, $where, $groupBy, $orderBy); if ($rankingQuery) { unset($availableMetrics[Metrics::INDEX_MAX_ACTIONS]); $sumColumns = array_keys($availableMetrics); + if ($metrics) { $sumColumns = array_intersect($sumColumns, $metrics); } + $rankingQuery->addColumn($sumColumns, 'sum'); if ($this->isMetricRequested(Metrics::INDEX_MAX_ACTIONS, $metrics)) { $rankingQuery->addColumn(Metrics::INDEX_MAX_ACTIONS, 'max'); } + return $rankingQuery->execute($query['sql'], $query['bind']); } + return $this->getDb()->query($query['sql'], $query['bind']); } protected function getSelectsMetrics($metricsAvailable, $metricsRequested = false) { $selects = array(); + foreach ($metricsAvailable as $metricId => $statement) { if ($this->isMetricRequested($metricId, $metricsRequested)) { - $aliasAs = $this->getSelectAliasAs($metricId); + $aliasAs = $this->getSelectAliasAs($metricId); $selects[] = $statement . $aliasAs; } } + return $selects; } protected function getSelectStatement($dimensions, $tableName, $additionalSelects, array $availableMetrics, $requestedMetrics = false) { $dimensionsToSelect = $this->getDimensionsToSelect($dimensions, $additionalSelects); + $selects = array_merge( $this->getSelectDimensions($dimensionsToSelect, $tableName), $this->getSelectsMetrics($availableMetrics, $requestedMetrics), !empty($additionalSelects) ? $additionalSelects : array() ); + $select = implode(self::FIELDS_SEPARATOR, $selects); return $select; } @@ -355,6 +381,7 @@ class LogAggregator if (empty($additionalSelects)) { return $dimensions; } + $dimensionsToSelect = array(); foreach ($dimensions as $selectAs => $dimension) { $asAlias = $this->getSelectAliasAs($dimension); @@ -364,6 +391,7 @@ class LogAggregator } } } + $dimensionsToSelect = array_unique($dimensionsToSelect); return $dimensionsToSelect; } @@ -382,6 +410,7 @@ class LogAggregator { foreach ($dimensions as $selectAs => &$field) { $selectAsString = $field; + if (!is_numeric($selectAs)) { $selectAsString = $selectAs; } else { @@ -390,16 +419,18 @@ class LogAggregator $selectAsString = $appendSelectAs = false; } } + $isKnownField = !in_array($field, array('referrer_data')); - if ($selectAsString == $field - && $isKnownField - ) { + + if ($selectAsString == $field && $isKnownField) { $field = $this->prefixColumn($field, $tableName); } + if ($appendSelectAs && $selectAsString) { $field = $this->prefixColumn($field, $tableName) . $this->getSelectAliasAs($selectAsString); } } + return $dimensions; } @@ -422,7 +453,7 @@ class LogAggregator protected function isFieldFunctionOrComplexExpression($field) { return strpos($field, "(") !== false - || strpos($field, "CASE") !== false; + || strpos($field, "CASE") !== false; } protected function getSelectAliasAs($metricId) @@ -432,32 +463,50 @@ class LogAggregator protected function isMetricRequested($metricId, $metricsRequested) { - return $metricsRequested === false - || in_array($metricId, $metricsRequested); + // do not process INDEX_NB_UNIQ_FINGERPRINTS unless specifically asked for + if ($metricsRequested === false) { + if ($metricId == Metrics::INDEX_NB_UNIQ_FINGERPRINTS) { + return false; + } + return true; + } + return in_array($metricId, $metricsRequested); } protected function getWhereStatement($tableName, $datetimeField, $extraWhere = false) { $where = "$tableName.$datetimeField >= ? AND $tableName.$datetimeField <= ? - AND $tableName.idsite = ?"; + AND $tableName.idsite IN (". Common::getSqlStringFieldsArray($this->sites) . ")"; + if (!empty($extraWhere)) { $extraWhere = sprintf($extraWhere, $tableName, $tableName); - $where .= ' AND ' . $extraWhere; + $where .= ' AND ' . $extraWhere; } + return $where; } protected function getGroupByStatement($dimensions, $tableName) { $dimensions = $this->getSelectDimensions($dimensions, $tableName, $appendSelectAs = false); - $groupBy = implode(", ", $dimensions); + $groupBy = implode(", ", $dimensions); + return $groupBy; } - protected function getBindDatetimeSite() + /** + * Returns general bind parameters for all log aggregation queries. This includes the datetime + * start of entities, datetime end of entities and IDs of all sites. + * + * @return array + */ + protected function getGeneralQueryBindParams() { - return array($this->dateStart->getDateStartUTC(), $this->dateEnd->getDateEndUTC(), $this->site->getId()); + $bind = array($this->dateStart->getDateStartUTC(), $this->dateEnd->getDateEndUTC()); + $bind = array_merge($bind, $this->sites); + + return $bind; } /** @@ -487,7 +536,7 @@ class LogAggregator * * @param string $dimension One or more **log\_conversion\_item** columns to group aggregated data by. * Eg, `'idaction_sku'` or `'idaction_sku, idaction_category'`. - * @return Zend_Db_Statement A statement object that can be used to iterate through the query's + * @return \Zend_Db_Statement A statement object that can be used to iterate through the query's * result set. See [above](#queryEcommerceItems-result-set) to learn more * about what this query selects. * @api @@ -547,7 +596,7 @@ class LogAggregator array( 'log_conversion_item.server_time >= ?', 'log_conversion_item.server_time <= ?', - 'log_conversion_item.idsite = ?', + 'log_conversion_item.idsite IN (' . Common::getSqlStringFieldsArray($this->sites) . ')', 'log_conversion_item.deleted = 0' ) ), @@ -593,7 +642,7 @@ class LogAggregator * clause. These can be aggregate expressions, eg, `SUM(somecol)`. * @param bool|array $metrics The set of metrics to calculate and return. If `false`, the query will select * all of them. The following values can be used: - * + * * - {@link Piwik\Metrics::INDEX_NB_UNIQ_VISITORS} * - {@link Piwik\Metrics::INDEX_NB_VISITS} * - {@link Piwik\Metrics::INDEX_NB_ACTIONS} @@ -604,7 +653,7 @@ class LogAggregator * log_action should be joined on. The table alias used for each join * is `"log_action$i"` where `$i` is the index of the column in this * array. - * + * * If a string is used for this parameter, the table alias is not * suffixed (since there is only one column). * @return mixed A Zend_Db_Statement if `$rankingQuery` isn't supplied, otherwise the result of @@ -617,9 +666,9 @@ class LogAggregator $tableName = self::LOG_ACTIONS_TABLE; $availableMetrics = $this->getActionsMetricFields(); - $select = $this->getSelectStatement($dimensions, $tableName, $additionalSelects, $availableMetrics, $metrics); - $from = array($tableName); - $where = $this->getWhereStatement($tableName, self::ACTION_DATETIME_FIELD, $where); + $select = $this->getSelectStatement($dimensions, $tableName, $additionalSelects, $availableMetrics, $metrics); + $from = array($tableName); + $where = $this->getWhereStatement($tableName, self::ACTION_DATETIME_FIELD, $where); $groupBy = $this->getGroupByStatement($dimensions, $tableName); $orderBy = false; @@ -631,12 +680,14 @@ class LogAggregator foreach ($joinLogActionOnColumn as $i => $joinColumn) { $tableAlias = 'log_action' . ($multiJoin ? $i + 1 : ''); + if (strpos($joinColumn, ' ') === false) { $joinOn = $tableAlias . '.idaction = ' . $tableName . '.' . $joinColumn; } else { - // more complex join column like IF(...) + // more complex join column like if (...) $joinOn = $tableAlias . '.idaction = ' . $joinColumn; } + $from[] = array( 'table' => 'log_action', 'tableAlias' => $tableAlias, @@ -656,7 +707,9 @@ class LogAggregator if ($metrics) { $sumColumns = array_intersect($sumColumns, $metrics); } + $rankingQuery->addColumn($sumColumns, 'sum'); + return $rankingQuery->execute($query['sql'], $query['bind']); } @@ -665,7 +718,7 @@ class LogAggregator protected function getActionsMetricFields() { - return $availableMetrics = array( + return array( Metrics::INDEX_NB_VISITS => "count(distinct " . self::LOG_ACTIONS_TABLE . ".idvisit)", Metrics::INDEX_NB_UNIQ_VISITORS => "count(distinct " . self::LOG_ACTIONS_TABLE . ".idvisitor)", Metrics::INDEX_NB_ACTIONS => "count(*)", @@ -691,32 +744,32 @@ class LogAggregator * - **{@link Piwik\Metrics::INDEX_GOAL_ECOMMERCE_REVENUE_SUBTOTAL}**: The total cost of all ecommerce items sold * within these conversions. This value does not * include tax, shipping or any applied discount. - * + * * _This metric is only applicable to the special * **ecommerce** goal (where `idGoal == 'ecommerceOrder'`)._ * - **{@link Piwik\Metrics::INDEX_GOAL_ECOMMERCE_REVENUE_TAX}**: The total tax applied to every transaction in these * conversions. - * + * * _This metric is only applicable to the special * **ecommerce** goal (where `idGoal == 'ecommerceOrder'`)._ * - **{@link Piwik\Metrics::INDEX_GOAL_ECOMMERCE_REVENUE_SHIPPING}**: The total shipping cost for every transaction * in these conversions. - * + * * _This metric is only applicable to the special * **ecommerce** goal (where `idGoal == 'ecommerceOrder'`)._ * - **{@link Piwik\Metrics::INDEX_GOAL_ECOMMERCE_REVENUE_DISCOUNT}**: The total discount applied to every transaction * in these conversions. - * + * * _This metric is only applicable to the special * **ecommerce** goal (where `idGoal == 'ecommerceOrder'`)._ * - **{@link Piwik\Metrics::INDEX_GOAL_ECOMMERCE_ITEMS}**: The total number of ecommerce items sold in each transaction * in these conversions. - * + * * _This metric is only applicable to the special * **ecommerce** goal (where `idGoal == 'ecommerceOrder'`)._ - * + * * Additional data can be selected through the `$additionalSelects` parameter. - * + * * _Note: This method will only query the **log_conversion** table. Other tables cannot be joined * using this method._ * @@ -726,21 +779,22 @@ class LogAggregator * @param bool|string $where An optional SQL expression used in the SQL's **WHERE** clause. * @param array $additionalSelects Additional SELECT fields that are not included in the group by * clause. These can be aggregate expressions, eg, `SUM(somecol)`. - * @return Zend_Db_Statement + * @return \Zend_Db_Statement */ public function queryConversionsByDimension($dimensions = array(), $where = false, $additionalSelects = array()) { $dimensions = array_merge(array(self::IDGOAL_FIELD), $dimensions); + $tableName = self::LOG_CONVERSION_TABLE; $availableMetrics = $this->getConversionsMetricFields(); - $tableName = self::LOG_CONVERSION_TABLE; $select = $this->getSelectStatement($dimensions, $tableName, $additionalSelects, $availableMetrics); - $from = array($tableName); - $where = $this->getWhereStatement($tableName, self::CONVERSION_DATETIME_FIELD, $where); + $from = array($tableName); + $where = $this->getWhereStatement($tableName, self::CONVERSION_DATETIME_FIELD, $where); $groupBy = $this->getGroupByStatement($dimensions, $tableName); $orderBy = false; - $query = $this->generateQuery($select, $from, $where, $groupBy, $orderBy); + $query = $this->generateQuery($select, $from, $where, $groupBy, $orderBy); + return $this->getDb()->query($query['sql'], $query['bind']); } @@ -750,9 +804,9 @@ class LogAggregator * * **Note:** The result of this function is meant for use in the `$additionalSelects` parameter * in one of the query... methods (for example {@link queryVisitsByDimension()}). - * + * * **Example** - * + * * // summarize one column * $visitTotalActionsRanges = array( * array(1, 1), @@ -760,7 +814,7 @@ class LogAggregator * array(10) * ); * $selects = LogAggregator::getSelectsFromRangedColumn('visit_total_actions', $visitTotalActionsRanges, 'log_visit', 'vta'); - * + * * // summarize another column in the same request * $visitCountVisitsRanges = array( * array(1, 1), @@ -771,17 +825,17 @@ class LogAggregator * $selects, * LogAggregator::getSelectsFromRangedColumn('visitor_count_visits', $visitCountVisitsRanges, 'log_visit', 'vcv') * ); - * + * * // perform the query * $logAggregator = // get the LogAggregator somehow * $query = $logAggregator->queryVisitsByDimension($dimensions = array(), $where = false, $selects); * $tableSummary = $query->fetch(); - * + * * $numberOfVisitsWithOneAction = $tableSummary['vta0']; * $numberOfVisitsBetweenTwoAnd10 = $tableSummary['vta1']; - * + * * $numberOfVisitsWithVisitCountOfOne = $tableSummary['vcv0']; - * + * * @param string $column The name of a column in `$table` that will be summarized. * @param array $ranges The array of ranges over which the data in the table * will be summarized. For example, @@ -817,14 +871,16 @@ class LogAggregator { $selects = array(); $extraCondition = ''; + if ($restrictToReturningVisitors) { // extra condition for the SQL SELECT that makes sure only returning visits are counted // when creating the 'days since last visit' report $extraCondition = 'and log_visit.visitor_returning = 1'; - $extraSelect = "sum(case when log_visit.visitor_returning = 0 then 1 else 0 end) " - . " as `" . $selectColumnPrefix . 'General_NewVisits' . "`"; + $extraSelect = "sum(case when log_visit.visitor_returning = 0 then 1 else 0 end) " + . " as `" . $selectColumnPrefix . 'General_NewVisits' . "`"; $selects[] = $extraSelect; } + foreach ($ranges as $gap) { if (count($gap) == 2) { $lowerBound = $gap[0]; @@ -833,12 +889,11 @@ class LogAggregator $selectAs = "$selectColumnPrefix$lowerBound-$upperBound"; $selects[] = "sum(case when $table.$column between $lowerBound and $upperBound $extraCondition" . - " then 1 else 0 end) as `$selectAs`"; + " then 1 else 0 end) as `$selectAs`"; } else { $lowerBound = $gap[0]; - $selectAs = $selectColumnPrefix . ($lowerBound + 1) . urlencode('+'); - + $selectAs = $selectColumnPrefix . ($lowerBound + 1) . urlencode('+'); $selects[] = "sum(case when $table.$column > $lowerBound $extraCondition then 1 else 0 end) as `$selectAs`"; } } @@ -859,9 +914,10 @@ class LogAggregator * value is used. * @return array */ - static public function makeArrayOneColumn($row, $columnName, $lookForThisPrefix = false) + public static function makeArrayOneColumn($row, $columnName, $lookForThisPrefix = false) { $cleanRow = array(); + foreach ($row as $label => $count) { if (empty($lookForThisPrefix) || strpos($label, $lookForThisPrefix) === 0 @@ -870,6 +926,7 @@ class LogAggregator $cleanRow[$cleanLabel] = array($columnName => $count); } } + return $cleanRow; } diff --git a/www/analytics/core/DataAccess/LogQueryBuilder.php b/www/analytics/core/DataAccess/LogQueryBuilder.php new file mode 100644 index 00000000..b0f2b73a --- /dev/null +++ b/www/analytics/core/DataAccess/LogQueryBuilder.php @@ -0,0 +1,391 @@ +isEmpty()) { + $segmentExpression->parseSubExpressionsIntoSqlExpressions($from); + $segmentSql = $segmentExpression->getSql(); + $where = $this->getWhereMatchBoth($where, $segmentSql['where']); + $bind = array_merge($bind, $segmentSql['bind']); + } + + $joins = $this->generateJoinsString($from); + $joinWithSubSelect = $joins['joinWithSubSelect']; + $from = $joins['sql']; + + // hack for https://github.com/piwik/piwik/issues/9194#issuecomment-164321612 + $useSpecialConversionGroupBy = (!empty($segmentSql) + && strpos($groupBy, 'log_conversion.idgoal') !== false + && $fromInitially == array('log_conversion') + && strpos($from, 'log_link_visit_action') !== false); + + if ($useSpecialConversionGroupBy) { + $innerGroupBy = "CONCAT(log_conversion.idvisit, '_' , log_conversion.idgoal, '_', log_conversion.buster)"; + $sql = $this->buildWrappedSelectQuery($select, $from, $where, $groupBy, $orderBy, $limit, $innerGroupBy); + } elseif ($joinWithSubSelect) { + $sql = $this->buildWrappedSelectQuery($select, $from, $where, $groupBy, $orderBy, $limit); + } else { + $sql = $this->buildSelectQuery($select, $from, $where, $groupBy, $orderBy, $limit); + } + return array( + 'sql' => $sql, + 'bind' => $bind + ); + } + + private function hasJoinedTableAlreadyManually($tableToFind, $joinToFind, $tables) + { + foreach ($tables as $index => $table) { + if (is_array($table) + && !empty($table['table']) + && $table['table'] === $tableToFind + && (!isset($table['tableAlias']) || $table['tableAlias'] === $tableToFind) + && isset($table['joinOn']) && $table['joinOn'] === $joinToFind) { + return true; + } + } + + return false; + } + + private function findIndexOfManuallyAddedTable($tableToFind, $tables) + { + foreach ($tables as $index => $table) { + if (is_array($table) + && !empty($table['table']) + && $table['table'] === $tableToFind + && (!isset($table['tableAlias']) || $table['tableAlias'] === $tableToFind)) { + return $index; + } + } + } + + private function hasTableAddedManually($tableToFind, $tables) + { + $table = $this->findIndexOfManuallyAddedTable($tableToFind, $tables); + + return isset($table); + } + + /** + * Generate the join sql based on the needed tables + * @param array $tables tables to join + * @throws Exception if tables can't be joined + * @return array + */ + private function generateJoinsString(&$tables) + { + $knownTables = array("log_action", "log_visit", "log_link_visit_action", "log_conversion", "log_conversion_item"); + $visitsAvailable = $linkVisitActionsTableAvailable = $conversionsAvailable = $conversionItemAvailable = $actionsTableAvailable = false; + $defaultLogActionJoin = "log_link_visit_action.idaction_url = log_action.idaction"; + + $joinWithSubSelect = false; + $sql = ''; + + // make sure the tables are joined in the right order + // base table first, then action before conversion + // this way, conversions can be left joined on idvisit + $actionIndex = array_search("log_link_visit_action", $tables); + $conversionIndex = array_search("log_conversion", $tables); + if ($actionIndex > 0 && $conversionIndex > 0 && $actionIndex > $conversionIndex) { + $tables[$actionIndex] = "log_conversion"; + $tables[$conversionIndex] = "log_link_visit_action"; + } + // same as above: action before visit + $actionIndex = array_search("log_link_visit_action", $tables); + $visitIndex = array_search("log_visit", $tables); + if ($actionIndex > 0 && $visitIndex > 0 && $actionIndex > $visitIndex) { + $tables[$actionIndex] = "log_visit"; + $tables[$visitIndex] = "log_link_visit_action"; + } + + // we need to add log_link_visit_action dynamically to join eg visit with action + $linkVisitAction = array_search("log_link_visit_action", $tables); + $actionIndex = array_search("log_action", $tables); + if ($linkVisitAction === false && $actionIndex > 0) { + $tables[] = "log_link_visit_action"; + } + + if ($actionIndex > 0 + && $this->hasTableAddedManually('log_action', $tables) + && !$this->hasJoinedTableAlreadyManually('log_action', $defaultLogActionJoin, $tables)) { + // we cannot join the same table with same alias twice, therefore we need to combine the join via AND + $tableIndex = $this->findIndexOfManuallyAddedTable('log_action', $tables); + $defaultLogActionJoin = '(' . $tables[$tableIndex]['joinOn'] . ' AND ' . $defaultLogActionJoin . ')'; + unset($tables[$tableIndex]); + } + + $linkVisitAction = array_search("log_link_visit_action", $tables); + $actionIndex = array_search("log_action", $tables); + if ($linkVisitAction > 0 && $actionIndex > 0 && $linkVisitAction > $actionIndex) { + $tables[$actionIndex] = "log_link_visit_action"; + $tables[$linkVisitAction] = "log_action"; + } + + foreach ($tables as $i => $table) { + if (is_array($table)) { + // join condition provided + $alias = isset($table['tableAlias']) ? $table['tableAlias'] : $table['table']; + $sql .= " + LEFT JOIN " . Common::prefixTable($table['table']) . " AS " . $alias + . " ON " . $table['joinOn']; + continue; + } + + if (!in_array($table, $knownTables)) { + throw new Exception("Table '$table' can't be used for segmentation"); + } + + $tableSql = Common::prefixTable($table) . " AS $table"; + + if ($i == 0) { + // first table + $sql .= $tableSql; + } else { + + if ($linkVisitActionsTableAvailable && $table === 'log_action') { + $join = $defaultLogActionJoin; + + if ($this->hasJoinedTableAlreadyManually($table, $join, $tables)) { + $actionsTableAvailable = true; + continue; + } + + } elseif ($linkVisitActionsTableAvailable && $table == "log_conversion") { + // have actions, need conversions => join on idvisit + $join = "log_conversion.idvisit = log_link_visit_action.idvisit"; + } elseif ($linkVisitActionsTableAvailable && $table == "log_visit") { + // have actions, need visits => join on idvisit + $join = "log_visit.idvisit = log_link_visit_action.idvisit"; + + if ($this->hasJoinedTableAlreadyManually($table, $join, $tables)) { + $visitsAvailable = true; + continue; + } + + } elseif ($visitsAvailable && $table == "log_link_visit_action") { + // have visits, need actions => we have to use a more complex join + // we don't hande this here, we just return joinWithSubSelect=true in this case + $joinWithSubSelect = true; + $join = "log_link_visit_action.idvisit = log_visit.idvisit"; + + if ($this->hasJoinedTableAlreadyManually($table, $join, $tables)) { + $linkVisitActionsTableAvailable = true; + continue; + } + + } elseif ($conversionsAvailable && $table == "log_link_visit_action") { + // have conversions, need actions => join on idvisit + $join = "log_conversion.idvisit = log_link_visit_action.idvisit"; + } elseif (($visitsAvailable && $table == "log_conversion") + || ($conversionsAvailable && $table == "log_visit") + ) { + // have visits, need conversion (or vice versa) => join on idvisit + // notice that joining conversions on visits has lower priority than joining it on actions + $join = "log_conversion.idvisit = log_visit.idvisit"; + + // if conversions are joined on visits, we need a complex join + if ($table == "log_conversion") { + $joinWithSubSelect = true; + } + } elseif ($conversionItemAvailable && $table === 'log_visit') { + $join = "log_conversion_item.idvisit = log_visit.idvisit"; + } elseif ($conversionItemAvailable && $table === 'log_link_visit_action') { + $join = "log_conversion_item.idvisit = log_link_visit_action.idvisit"; + } elseif ($conversionItemAvailable && $table === 'log_conversion') { + $join = "log_conversion_item.idvisit = log_conversion.idvisit"; + } else { + throw new Exception("Table '$table' can't be joined for segmentation"); + } + + // the join sql the default way + $sql .= " + LEFT JOIN $tableSql ON $join"; + } + + // remember which tables are available + $visitsAvailable = ($visitsAvailable || $table == "log_visit"); + $linkVisitActionsTableAvailable = ($linkVisitActionsTableAvailable || $table == "log_link_visit_action"); + $actionsTableAvailable = ($actionsTableAvailable || $table == "log_action"); + $conversionsAvailable = ($conversionsAvailable || $table == "log_conversion"); + $conversionItemAvailable = ($conversionItemAvailable || $table == "log_conversion_item"); + } + + $return = array( + 'sql' => $sql, + 'joinWithSubSelect' => $joinWithSubSelect + ); + return $return; + } + + + /** + * Build a select query where actions have to be joined on visits (or conversions) + * In this case, the query gets wrapped in another query so that grouping by visit is possible + * @param string $select + * @param string $from + * @param string $where + * @param string $groupBy + * @param string $orderBy + * @param string $limit + * @param null|string $innerGroupBy If given, this inner group by will be used. If not, we try to detect one + * @throws Exception + * @return string + */ + private function buildWrappedSelectQuery($select, $from, $where, $groupBy, $orderBy, $limit, $innerGroupBy = null) + { + $matchTables = "(log_visit|log_conversion_item|log_conversion|log_action)"; + preg_match_all("/". $matchTables ."\.[a-z0-9_\*]+/", $select, $matches); + $neededFields = array_unique($matches[0]); + + if (count($neededFields) == 0) { + throw new Exception("No needed fields found in select expression. " + . "Please use a table prefix."); + } + + preg_match_all("/". $matchTables . "/", $from, $matchesFrom); + + $innerSelect = implode(", \n", $neededFields); + $innerFrom = $from; + $innerWhere = $where; + + $innerLimit = $limit; + + if (!isset($innerGroupBy) && in_array('log_visit', $matchesFrom[1])) { + $innerGroupBy = "log_visit.idvisit"; + } elseif (!isset($innerGroupBy)) { + throw new Exception('Cannot use subselect for join as no group by rule is specified'); + } + + $innerOrderBy = "NULL"; + if ($innerLimit && $orderBy) { + // only When LIMITing we can apply to the inner query the same ORDER BY as the parent query + $innerOrderBy = $orderBy; + } + if ($innerLimit) { + // When LIMITing, no need to GROUP BY (GROUPing by is done before the LIMIT which is super slow when large amount of rows is matched) + $innerGroupBy = false; + } + + $innerQuery = $this->buildSelectQuery($innerSelect, $innerFrom, $innerWhere, $innerGroupBy, $innerOrderBy, $innerLimit); + + $select = preg_replace('/'.$matchTables.'\./', 'log_inner.', $select); + $from = " + ( + $innerQuery + ) AS log_inner"; + $where = false; + $orderBy = preg_replace('/'.$matchTables.'\./', 'log_inner.', $orderBy); + $groupBy = preg_replace('/'.$matchTables.'\./', 'log_inner.', $groupBy); + $query = $this->buildSelectQuery($select, $from, $where, $groupBy, $orderBy, $limit); + return $query; + } + + + /** + * Build select query the normal way + * + * @param string $select fieldlist to be selected + * @param string $from tablelist to select from + * @param string $where where clause + * @param string $groupBy group by clause + * @param string $orderBy order by clause + * @param string|int $limit limit by clause eg '5' for Limit 5 Offset 0 or '10, 5' for Limit 5 Offset 10 + * @return string + */ + private function buildSelectQuery($select, $from, $where, $groupBy, $orderBy, $limit) + { + $sql = " + SELECT + $select + FROM + $from"; + + if ($where) { + $sql .= " + WHERE + $where"; + } + + if ($groupBy) { + $sql .= " + GROUP BY + $groupBy"; + } + + if ($orderBy) { + $sql .= " + ORDER BY + $orderBy"; + } + + $sql = $this->appendLimitClauseToQuery($sql, $limit); + + return $sql; + } + + private function appendLimitClauseToQuery($sql, $limit) + { + $limitParts = explode(',', (string) $limit); + $isLimitWithOffset = 2 === count($limitParts); + + if ($isLimitWithOffset) { + // $limit = "10, 5". We would not have to do this but we do to prevent possible injections. + $offset = trim($limitParts[0]); + $limit = trim($limitParts[1]); + $sql .= sprintf(' LIMIT %d, %d', $offset, $limit); + } else { + // $limit = "5" + $limit = (int)$limit; + if ($limit >= 1) { + $sql .= " LIMIT $limit"; + } + } + + return $sql; + } + + /** + * @param $where + * @param $segmentWhere + * @return string + * @throws + */ + protected function getWhereMatchBoth($where, $segmentWhere) + { + if (empty($segmentWhere) && empty($where)) { + throw new \Exception("Segment where clause should be non empty."); + } + if (empty($segmentWhere)) { + return $where; + } + if (empty($where)) { + return $segmentWhere; + } + return "( $where ) + AND + ($segmentWhere)"; + } +} diff --git a/www/analytics/core/DataAccess/Model.php b/www/analytics/core/DataAccess/Model.php new file mode 100644 index 00000000..ba04962f --- /dev/null +++ b/www/analytics/core/DataAccess/Model.php @@ -0,0 +1,358 @@ +logger = $logger ?: StaticContainer::get('Psr\Log\LoggerInterface'); + } + + /** + * Returns the archives IDs that have already been invalidated and have been since re-processed. + * + * These archives { archive name (includes segment hash) , idsite, date, period } will be deleted. + * + * @param string $archiveTable + * @param array $idSites + * @return array + * @throws Exception + */ + public function getInvalidatedArchiveIdsSafeToDelete($archiveTable, array $idSites) + { + try { + Db::get()->query('SET SESSION group_concat_max_len=' . (128 * 1024)); + } catch (\Exception $ex) { + $this->logger->info("Could not set group_concat_max_len MySQL session variable."); + } + + $idSites = array_map(function ($v) { return (int)$v; }, $idSites); + + $sql = "SELECT idsite, date1, date2, period, name, + GROUP_CONCAT(idarchive, '.', value ORDER BY ts_archived DESC) as archives + FROM `$archiveTable` + WHERE name LIKE 'done%' + AND value IN (" . ArchiveWriter::DONE_INVALIDATED . ',' + . ArchiveWriter::DONE_OK . ',' + . ArchiveWriter::DONE_OK_TEMPORARY . ") + AND idsite IN (" . implode(',', $idSites) . ") + GROUP BY idsite, date1, date2, period, name"; + + $archiveIds = array(); + + $rows = Db::fetchAll($sql); + foreach ($rows as $row) { + $duplicateArchives = explode(',', $row['archives']); + + $firstArchive = array_shift($duplicateArchives); + list($firstArchiveId, $firstArchiveValue) = explode('.', $firstArchive); + + // if the first archive (ie, the newest) is an 'ok' or 'ok temporary' archive, then + // all invalidated archives after it can be deleted + if ($firstArchiveValue == ArchiveWriter::DONE_OK + || $firstArchiveValue == ArchiveWriter::DONE_OK_TEMPORARY + ) { + foreach ($duplicateArchives as $pair) { + if (strpos($pair, '.') === false) { + $this->logger->info("GROUP_CONCAT cut off the query result, you may have to purge archives again."); + break; + } + + list($idarchive, $value) = explode('.', $pair); + if ($value == ArchiveWriter::DONE_INVALIDATED) { + $archiveIds[] = $idarchive; + } + } + } + } + + return $archiveIds; + } + + /** + * @param string $archiveTable Prefixed table name + * @param int[] $idSites + * @param string[][] $datesByPeriodType + * @param Segment $segment + * @return \Zend_Db_Statement + * @throws Exception + */ + public function updateArchiveAsInvalidated($archiveTable, $idSites, $datesByPeriodType, Segment $segment = null) + { + $idSites = array_map('intval', $idSites); + + $bind = array(); + + $periodConditions = array(); + foreach ($datesByPeriodType as $periodType => $dates) { + $dateConditions = array(); + + foreach ($dates as $date) { + $dateConditions[] = "(date1 <= ? AND ? <= date2)"; + $bind[] = $date; + $bind[] = $date; + } + + $dateConditionsSql = implode(" OR ", $dateConditions); + if (empty($periodType) + || $periodType == Period\Day::PERIOD_ID + ) { + // invalidate all periods if no period supplied or period is day + $periodConditions[] = "($dateConditionsSql)"; + } else if ($periodType == Period\Range::PERIOD_ID) { + $periodConditions[] = "(period = " . Period\Range::PERIOD_ID . " AND ($dateConditionsSql))"; + } else { + // for non-day periods, invalidate greater periods, but not range periods + $periodConditions[] = "(period >= " . (int)$periodType . " AND period < " . Period\Range::PERIOD_ID . " AND ($dateConditionsSql))"; + } + } + + if ($segment) { + $nameCondition = "name LIKE '" . Rules::getDoneFlagArchiveContainsAllPlugins($segment) . "%'"; + } else { + $nameCondition = "name LIKE 'done%'"; + } + + $sql = "UPDATE $archiveTable SET value = " . ArchiveWriter::DONE_INVALIDATED + . " WHERE $nameCondition + AND idsite IN (" . implode(", ", $idSites) . ") + AND (" . implode(" OR ", $periodConditions) . ")"; + + return Db::query($sql, $bind); + } + + + public function getTemporaryArchivesOlderThan($archiveTable, $purgeArchivesOlderThan) + { + $query = "SELECT idarchive FROM " . $archiveTable . " + WHERE name LIKE 'done%' + AND (( value = " . ArchiveWriter::DONE_OK_TEMPORARY . " + AND ts_archived < ?) + OR value = " . ArchiveWriter::DONE_ERROR . ")"; + + return Db::fetchAll($query, array($purgeArchivesOlderThan)); + } + + public function deleteArchivesWithPeriod($numericTable, $blobTable, $period, $date) + { + $query = "DELETE FROM %s WHERE period = ? AND ts_archived < ?"; + $bind = array($period, $date); + + $queryObj = Db::query(sprintf($query, $numericTable), $bind); + $deletedRows = $queryObj->rowCount(); + + try { + $queryObj = Db::query(sprintf($query, $blobTable), $bind); + $deletedRows += $queryObj->rowCount(); + } catch (Exception $e) { + // Individual blob tables could be missing + $this->logger->debug("Unable to delete archives by period from {blobTable}.", array( + 'blobTable' => $blobTable, + 'exception' => $e, + )); + } + + return $deletedRows; + } + + public function deleteArchiveIds($numericTable, $blobTable, $idsToDelete) + { + $idsToDelete = array_values($idsToDelete); + $query = "DELETE FROM %s WHERE idarchive IN (" . Common::getSqlStringFieldsArray($idsToDelete) . ")"; + + $queryObj = Db::query(sprintf($query, $numericTable), $idsToDelete); + $deletedRows = $queryObj->rowCount(); + + try { + $queryObj = Db::query(sprintf($query, $blobTable), $idsToDelete); + $deletedRows += $queryObj->rowCount(); + } catch (Exception $e) { + // Individual blob tables could be missing + $this->logger->debug("Unable to delete archive IDs from {blobTable}.", array( + 'blobTable' => $blobTable, + 'exception' => $e, + )); + } + + return $deletedRows; + } + + public function getArchiveIdAndVisits($numericTable, $idSite, $period, $dateStartIso, $dateEndIso, $minDatetimeIsoArchiveProcessedUTC, $doneFlags, $doneFlagValues) + { + $bindSQL = array($idSite, + $dateStartIso, + $dateEndIso, + $period, + ); + + $timeStampWhere = ''; + if ($minDatetimeIsoArchiveProcessedUTC) { + $timeStampWhere = " AND ts_archived >= ? "; + $bindSQL[] = $minDatetimeIsoArchiveProcessedUTC; + } + + $sqlWhereArchiveName = self::getNameCondition($doneFlags, $doneFlagValues); + + $sqlQuery = "SELECT idarchive, value, name, date1 as startDate FROM $numericTable + WHERE idsite = ? + AND date1 = ? + AND date2 = ? + AND period = ? + AND ( ($sqlWhereArchiveName) + OR name = '" . ArchiveSelector::NB_VISITS_RECORD_LOOKED_UP . "' + OR name = '" . ArchiveSelector::NB_VISITS_CONVERTED_RECORD_LOOKED_UP . "') + $timeStampWhere + ORDER BY idarchive DESC"; + $results = Db::fetchAll($sqlQuery, $bindSQL); + + return $results; + } + + public function createArchiveTable($tableName, $tableNamePrefix) + { + $db = Db::get(); + $sql = DbHelper::getTableCreateSql($tableNamePrefix); + + // replace table name template by real name + $tableNamePrefix = Common::prefixTable($tableNamePrefix); + $sql = str_replace($tableNamePrefix, $tableName, $sql); + + try { + $db->query($sql); + } catch (Exception $e) { + // accept mysql error 1050: table already exists, throw otherwise + if (!$db->isErrNo($e, '1050')) { + throw $e; + } + } + + try { + if (ArchiveTableCreator::NUMERIC_TABLE === ArchiveTableCreator::getTypeFromTableName($tableName)) { + $sequence = new Sequence($tableName); + $sequence->create(); + } + } catch (Exception $e) { + } + } + + public function allocateNewArchiveId($numericTable) + { + $sequence = new Sequence($numericTable); + + try { + $idarchive = $sequence->getNextId(); + } catch (Exception $e) { + // edge case: sequence was not found, create it now + $sequence->create(); + + $idarchive = $sequence->getNextId(); + } + + return $idarchive; + } + + public function deletePreviousArchiveStatus($numericTable, $archiveId, $doneFlag) + { + $tableWithoutLeadingPrefix = $numericTable; + $lenNumericTableWithoutPrefix = strlen('archive_numeric_MM_YYYY'); + + if (strlen($numericTable) >= $lenNumericTableWithoutPrefix) { + $tableWithoutLeadingPrefix = substr($numericTable, strlen($numericTable) - $lenNumericTableWithoutPrefix); + // we need to make sure lock name is less than 64 characters see https://github.com/piwik/piwik/issues/9131 + } + $dbLockName = "rmPrevArchiveStatus.$tableWithoutLeadingPrefix.$archiveId"; + + // without advisory lock here, the DELETE would acquire Exclusive Lock + $this->acquireArchiveTableLock($dbLockName); + + Db::query("DELETE FROM $numericTable WHERE idarchive = ? AND (name = '" . $doneFlag . "')", + array($archiveId) + ); + + $this->releaseArchiveTableLock($dbLockName); + } + + public function insertRecord($tableName, $fields, $record, $name, $value) + { + // duplicate idarchives are Ignored, see https://github.com/piwik/piwik/issues/987 + $query = "INSERT IGNORE INTO " . $tableName . " (" . implode(", ", $fields) . ") + VALUES (?,?,?,?,?,?,?,?)"; + + $bindSql = $record; + $bindSql[] = $name; + $bindSql[] = $value; + + Db::query($query, $bindSql); + + return true; + } + + /** + * Returns the site IDs for invalidated archives in an archive table. + * + * @param string $numericTable The numeric table to search through. + * @return int[] + */ + public function getSitesWithInvalidatedArchive($numericTable) + { + $rows = Db::fetchAll("SELECT DISTINCT idsite FROM `$numericTable` WHERE name LIKE 'done%' AND value = " . ArchiveWriter::DONE_INVALIDATED); + + $result = array(); + foreach ($rows as $row) { + $result[] = $row['idsite']; + } + return $result; + } + + /** + * Returns the SQL condition used to find successfully completed archives that + * this instance is querying for. + */ + private static function getNameCondition($doneFlags, $possibleValues) + { + $allDoneFlags = "'" . implode("','", $doneFlags) . "'"; + + // create the SQL to find archives that are DONE + return "((name IN ($allDoneFlags)) AND (value IN (" . implode(',', $possibleValues) . ")))"; + } + + protected function acquireArchiveTableLock($dbLockName) + { + if (Db::getDbLock($dbLockName, $maxRetries = 30) === false) { + throw new Exception("Cannot get named lock $dbLockName."); + } + } + + protected function releaseArchiveTableLock($dbLockName) + { + Db::releaseDbLock($dbLockName); + } +} diff --git a/www/analytics/core/DataAccess/RawLogDao.php b/www/analytics/core/DataAccess/RawLogDao.php new file mode 100644 index 00000000..7310267b --- /dev/null +++ b/www/analytics/core/DataAccess/RawLogDao.php @@ -0,0 +1,402 @@ +dimensionMetadataProvider = $provider ?: StaticContainer::get('Piwik\Plugin\Dimension\DimensionMetadataProvider'); + } + + /** + * @param array $values + * @param string $idVisit + */ + public function updateVisits(array $values, $idVisit) + { + $sql = "UPDATE " . Common::prefixTable('log_visit') + . " SET " . $this->getColumnSetExpressions(array_keys($values)) + . " WHERE idvisit = ?"; + + $this->update($sql, $values, $idVisit); + } + + /** + * @param array $values + * @param string $idVisit + */ + public function updateConversions(array $values, $idVisit) + { + $sql = "UPDATE " . Common::prefixTable('log_conversion') + . " SET " . $this->getColumnSetExpressions(array_keys($values)) + . " WHERE idvisit = ?"; + + $this->update($sql, $values, $idVisit); + } + + /** + * @param string $from + * @param string $to + * @return int + */ + public function countVisitsWithDatesLimit($from, $to) + { + $sql = "SELECT COUNT(*) AS num_rows" + . " FROM " . Common::prefixTable('log_visit') + . " WHERE visit_last_action_time >= ? AND visit_last_action_time < ?"; + + $bind = array($from, $to); + + return (int) Db::fetchOne($sql, $bind); + } + + /** + * Iterates over logs in a log table in chunks. Parameters to this function are as backend agnostic + * as possible w/o dramatically increasing code complexity. + * + * @param string $logTable The log table name. Unprefixed, eg, `log_visit`. + * @param array[] $conditions An array describing the conditions logs must match in the query. Translates to + * the WHERE part of a SELECT statement. Each element must contain three elements: + * + * * the column name + * * the operator (ie, '=', '<>', '<', etc.) + * * the operand (ie, a value) + * + * The elements are AND-ed together. + * + * Example: + * + * ``` + * array( + * array('visit_first_action_time', '>=', ...), + * array('visit_first_action_time', '<', ...) + * ) + * ``` + * @param int $iterationStep The number of rows to query at a time. + * @param callable $callback The callback that processes each chunk of rows. + */ + public function forAllLogs($logTable, $fields, $conditions, $iterationStep, $callback) + { + $idField = $this->getIdFieldForLogTable($logTable); + list($query, $bind) = $this->createLogIterationQuery($logTable, $idField, $fields, $conditions, $iterationStep); + + $lastId = 0; + do { + $rows = Db::fetchAll($query, array_merge(array($lastId), $bind)); + if (!empty($rows)) { + $lastId = $rows[count($rows) - 1][$idField]; + + $callback($rows); + } + } while (count($rows) == $iterationStep); + } + + /** + * Deletes visits with the supplied IDs from log_visit. This method does not cascade, so rows in other tables w/ + * the same visit ID will still exist. + * + * @param int[] $idVisits + * @return int The number of deleted rows. + */ + public function deleteVisits($idVisits) + { + $sql = "DELETE FROM `" . Common::prefixTable('log_visit') . "` WHERE idvisit IN " + . $this->getInFieldExpressionWithInts($idVisits); + + $statement = Db::query($sql); + return $statement->rowCount(); + } + + /** + * Deletes visit actions for the supplied visit IDs from log_link_visit_action. + * + * @param int[] $visitIds + * @return int The number of deleted rows. + */ + public function deleteVisitActionsForVisits($visitIds) + { + $sql = "DELETE FROM `" . Common::prefixTable('log_link_visit_action') . "` WHERE idvisit IN " + . $this->getInFieldExpressionWithInts($visitIds); + + $statement = Db::query($sql); + return $statement->rowCount(); + } + + /** + * Deletes conversions for the supplied visit IDs from log_conversion. This method does not cascade, so + * conversion items will not be deleted. + * + * @param int[] $visitIds + * @return int The number of deleted rows. + */ + public function deleteConversions($visitIds) + { + $sql = "DELETE FROM `" . Common::prefixTable('log_conversion') . "` WHERE idvisit IN " + . $this->getInFieldExpressionWithInts($visitIds); + + $statement = Db::query($sql); + return $statement->rowCount(); + } + + /** + * Deletes conversion items for the supplied visit IDs from log_conversion_item. + * + * @param int[] $visitIds + * @return int The number of deleted rows. + */ + public function deleteConversionItems($visitIds) + { + $sql = "DELETE FROM `" . Common::prefixTable('log_conversion_item') . "` WHERE idvisit IN " + . $this->getInFieldExpressionWithInts($visitIds); + + $statement = Db::query($sql); + return $statement->rowCount(); + } + + /** + * Deletes all unused entries from the log_action table. This method uses a temporary table to store used + * actions, and then deletes rows from log_action that are not in this temporary table. + * + * Table locking is required to avoid concurrency issues. + * + * @throws \Exception If table locking permission is not granted to the current MySQL user. + */ + public function deleteUnusedLogActions() + { + if (!Db::isLockPrivilegeGranted()) { + throw new \Exception("RawLogDao.deleteUnusedLogActions() requires table locking permission in order to complete without error."); + } + + // get current max ID in log tables w/ idaction references. + $maxIds = $this->getMaxIdsInLogTables(); + + $this->createTempTableForStoringUsedActions(); + + // do large insert (inserting everything before maxIds) w/o locking tables... + $this->insertActionsToKeep($maxIds, $deleteOlderThanMax = true); + + // ... then do small insert w/ locked tables to minimize the amount of time tables are locked. + $this->lockLogTables(); + $this->insertActionsToKeep($maxIds, $deleteOlderThanMax = false); + + // delete before unlocking tables so there's no chance a new log row that references an + // unused action will be inserted. + $this->deleteUnusedActions(); + Db::unlockAllTables(); + } + + + /** + * Returns the list of the website IDs that received some visits between the specified timestamp. + * + * @param string $fromDateTime + * @param string $toDateTime + * @return bool true if there are visits for this site between the given timeframe, false if not + */ + public function hasSiteVisitsBetweenTimeframe($fromDateTime, $toDateTime, $idSite) + { + $sites = Db::fetchOne("SELECT 1 + FROM " . Common::prefixTable('log_visit') . " + WHERE idsite = ? + AND visit_last_action_time > ? + AND visit_last_action_time < ? + LIMIT 1", array($idSite, $fromDateTime, $toDateTime)); + + return (bool) $sites; + } + + /** + * @param array $columnsToSet + * @return string + */ + protected function getColumnSetExpressions(array $columnsToSet) + { + $columnsToSet = array_map( + function ($column) { + return $column . ' = ?'; + }, + $columnsToSet + ); + + return implode(', ', $columnsToSet); + } + + /** + * @param array $values + * @param $idVisit + * @param $sql + * @return \Zend_Db_Statement + * @throws \Exception + */ + protected function update($sql, array $values, $idVisit) + { + return Db::query($sql, array_merge(array_values($values), array($idVisit))); + } + + private function getIdFieldForLogTable($logTable) + { + switch ($logTable) { + case 'log_visit': + return 'idvisit'; + case 'log_link_visit_action': + return 'idlink_va'; + case 'log_conversion': + return 'idvisit'; + case 'log_conversion_item': + return 'idvisit'; + case 'log_action': + return 'idaction'; + default: + throw new \InvalidArgumentException("Unknown log table '$logTable'."); + } + } + + // TODO: instead of creating a log query like this, we should re-use segments. to do this, however, there must be a 1-1 + // mapping for dimensions => segments, and each dimension should automatically have a segment. + private function createLogIterationQuery($logTable, $idField, $fields, $conditions, $iterationStep) + { + $bind = array(); + + $sql = "SELECT " . implode(', ', $fields) . " FROM `" . Common::prefixTable($logTable) . "` WHERE $idField > ?"; + + foreach ($conditions as $condition) { + list($column, $operator, $value) = $condition; + + if (is_array($value)) { + $sql .= " AND $column IN (" . Common::getSqlStringFieldsArray($value) . ")"; + + $bind = array_merge($bind, $value); + } else { + $sql .= " AND $column $operator ?"; + + $bind[] = $value; + } + } + + $sql .= " ORDER BY $idField ASC LIMIT " . (int)$iterationStep; + + return array($sql, $bind); + } + + private function getInFieldExpressionWithInts($idVisits) + { + $sql = "("; + + $isFirst = true; + foreach ($idVisits as $idVisit) { + if ($isFirst) { + $isFirst = false; + } else { + $sql .= ', '; + } + + $sql .= (int)$idVisit; + } + + $sql .= ")"; + + return $sql; + } + + + private function getMaxIdsInLogTables() + { + $tables = array('log_conversion', 'log_link_visit_action', 'log_visit', 'log_conversion_item'); + $idColumns = $this->getTableIdColumns(); + + $result = array(); + foreach ($tables as $table) { + $idCol = $idColumns[$table]; + $result[$table] = Db::fetchOne("SELECT MAX($idCol) FROM " . Common::prefixTable($table)); + } + + return $result; + } + + private function createTempTableForStoringUsedActions() + { + $sql = "CREATE TEMPORARY TABLE " . Common::prefixTable(self::DELETE_UNUSED_ACTIONS_TEMP_TABLE_NAME) . " ( + idaction INT(11), + PRIMARY KEY (idaction) + )"; + Db::query($sql); + } + + // protected for testing purposes + protected function insertActionsToKeep($maxIds, $olderThan = true, $insertIntoTempIterationStep = 100000) + { + $tempTableName = Common::prefixTable(self::DELETE_UNUSED_ACTIONS_TEMP_TABLE_NAME); + + $idColumns = $this->getTableIdColumns(); + foreach ($this->dimensionMetadataProvider->getActionReferenceColumnsByTable() as $table => $columns) { + $idCol = $idColumns[$table]; + + foreach ($columns as $col) { + $select = "SELECT $col FROM " . Common::prefixTable($table) . " WHERE $idCol >= ? AND $idCol < ?"; + $sql = "INSERT IGNORE INTO $tempTableName $select"; + + if ($olderThan) { + $start = 0; + $finish = $maxIds[$table]; + } else { + $start = $maxIds[$table]; + $finish = Db::fetchOne("SELECT MAX($idCol) FROM " . Common::prefixTable($table)); + } + + Db::segmentedQuery($sql, $start, $finish, $insertIntoTempIterationStep); + } + } + } + + private function lockLogTables() + { + Db::lockTables( + $readLocks = Common::prefixTables('log_conversion', 'log_link_visit_action', 'log_visit', 'log_conversion_item'), + $writeLocks = Common::prefixTables('log_action') + ); + } + + private function deleteUnusedActions() + { + list($logActionTable, $tempTableName) = Common::prefixTables("log_action", self::DELETE_UNUSED_ACTIONS_TEMP_TABLE_NAME); + + $deleteSql = "DELETE LOW_PRIORITY QUICK IGNORE $logActionTable + FROM $logActionTable + LEFT JOIN $tempTableName tmp ON tmp.idaction = $logActionTable.idaction + WHERE tmp.idaction IS NULL"; + + Db::query($deleteSql); + } + + private function getTableIdColumns() + { + return array( + 'log_link_visit_action' => 'idlink_va', + 'log_conversion' => 'idvisit', + 'log_visit' => 'idvisit', + 'log_conversion_item' => 'idvisit' + ); + } +} \ No newline at end of file diff --git a/www/analytics/core/DataAccess/TableMetadata.php b/www/analytics/core/DataAccess/TableMetadata.php new file mode 100644 index 00000000..a07d3780 --- /dev/null +++ b/www/analytics/core/DataAccess/TableMetadata.php @@ -0,0 +1,56 @@ +getColumns($table); + + $columns = array_filter($columns, function ($columnName) { + return strpos($columnName, 'idaction') !== false; + }); + + return array_values($columns); + } +} diff --git a/www/analytics/core/DataArray.php b/www/analytics/core/DataArray.php index 72b31fe3..4f375730 100644 --- a/www/analytics/core/DataArray.php +++ b/www/analytics/core/DataArray.php @@ -1,6 +1,6 @@ data[$label])) { - $this->data[$label] = self::makeEmptyRow(); + $this->data[$label] = static::makeEmptyRow(); } $this->doSumVisitsMetrics($row, $this->data[$label]); } @@ -57,11 +57,12 @@ class DataArray * * @return array */ - static public function makeEmptyRow() + public static function makeEmptyRow() { return array(Metrics::INDEX_NB_UNIQ_VISITORS => 0, Metrics::INDEX_NB_VISITS => 0, Metrics::INDEX_NB_ACTIONS => 0, + Metrics::INDEX_NB_USERS => 0, Metrics::INDEX_MAX_ACTIONS => 0, Metrics::INDEX_SUM_VISIT_LENGTH => 0, Metrics::INDEX_BOUNCE_COUNT => 0, @@ -79,7 +80,7 @@ class DataArray * * @return void */ - protected function doSumVisitsMetrics($newRowToAdd, &$oldRowToUpdate, $onlyMetricsAvailableInActionsTable = false) + protected function doSumVisitsMetrics($newRowToAdd, &$oldRowToUpdate) { // Pre 1.2 format: string indexed rows are returned from the DB // Left here for Backward compatibility with plugins doing custom SQL queries using these metrics as string @@ -87,9 +88,7 @@ class DataArray $oldRowToUpdate[Metrics::INDEX_NB_VISITS] += $newRowToAdd['nb_visits']; $oldRowToUpdate[Metrics::INDEX_NB_ACTIONS] += $newRowToAdd['nb_actions']; $oldRowToUpdate[Metrics::INDEX_NB_UNIQ_VISITORS] += $newRowToAdd['nb_uniq_visitors']; - if ($onlyMetricsAvailableInActionsTable) { - return; - } + $oldRowToUpdate[Metrics::INDEX_NB_USERS] += $newRowToAdd['nb_users']; $oldRowToUpdate[Metrics::INDEX_MAX_ACTIONS] = (float)max($newRowToAdd['max_actions'], $oldRowToUpdate[Metrics::INDEX_MAX_ACTIONS]); $oldRowToUpdate[Metrics::INDEX_SUM_VISIT_LENGTH] += $newRowToAdd['sum_visit_length']; $oldRowToUpdate[Metrics::INDEX_BOUNCE_COUNT] += $newRowToAdd['bounce_count']; @@ -97,36 +96,81 @@ class DataArray return; } - $oldRowToUpdate[Metrics::INDEX_NB_VISITS] += $newRowToAdd[Metrics::INDEX_NB_VISITS]; - $oldRowToUpdate[Metrics::INDEX_NB_ACTIONS] += $newRowToAdd[Metrics::INDEX_NB_ACTIONS]; - $oldRowToUpdate[Metrics::INDEX_NB_UNIQ_VISITORS] += $newRowToAdd[Metrics::INDEX_NB_UNIQ_VISITORS]; - if ($onlyMetricsAvailableInActionsTable) { + // Edge case fail safe + if (!isset($oldRowToUpdate[Metrics::INDEX_NB_VISITS])) { return; } + $oldRowToUpdate[Metrics::INDEX_NB_VISITS] += $newRowToAdd[Metrics::INDEX_NB_VISITS]; + $oldRowToUpdate[Metrics::INDEX_NB_ACTIONS] += $newRowToAdd[Metrics::INDEX_NB_ACTIONS]; + $oldRowToUpdate[Metrics::INDEX_NB_UNIQ_VISITORS] += $newRowToAdd[Metrics::INDEX_NB_UNIQ_VISITORS]; + // In case the existing Row had no action metrics (eg. Custom Variable XYZ with "visit" scope) // but the new Row has action metrics (eg. same Custom Variable XYZ this time with a "page" scope) - if(!isset($oldRowToUpdate[Metrics::INDEX_MAX_ACTIONS])) { - $toZero = array(Metrics::INDEX_MAX_ACTIONS, + if (!isset($oldRowToUpdate[Metrics::INDEX_MAX_ACTIONS])) { + $toZero = array( + Metrics::INDEX_NB_USERS, + Metrics::INDEX_MAX_ACTIONS, Metrics::INDEX_SUM_VISIT_LENGTH, Metrics::INDEX_BOUNCE_COUNT, - Metrics::INDEX_NB_VISITS_CONVERTED); - foreach($toZero as $metric) { + Metrics::INDEX_NB_VISITS_CONVERTED + ); + foreach ($toZero as $metric) { $oldRowToUpdate[$metric] = 0; } } + $oldRowToUpdate[Metrics::INDEX_NB_USERS] += $newRowToAdd[Metrics::INDEX_NB_USERS]; $oldRowToUpdate[Metrics::INDEX_MAX_ACTIONS] = (float)max($newRowToAdd[Metrics::INDEX_MAX_ACTIONS], $oldRowToUpdate[Metrics::INDEX_MAX_ACTIONS]); $oldRowToUpdate[Metrics::INDEX_SUM_VISIT_LENGTH] += $newRowToAdd[Metrics::INDEX_SUM_VISIT_LENGTH]; $oldRowToUpdate[Metrics::INDEX_BOUNCE_COUNT] += $newRowToAdd[Metrics::INDEX_BOUNCE_COUNT]; $oldRowToUpdate[Metrics::INDEX_NB_VISITS_CONVERTED] += $newRowToAdd[Metrics::INDEX_NB_VISITS_CONVERTED]; } + /** + * Adds the given row $newRowToAdd to the existing $oldRowToUpdate passed by reference + * The rows are php arrays Name => value + * + * @param array $newRowToAdd + * @param array $oldRowToUpdate + * @param bool $onlyMetricsAvailableInActionsTable + * + * @return void + */ + protected function doSumActionsMetrics($newRowToAdd, &$oldRowToUpdate) + { + // Pre 1.2 format: string indexed rows are returned from the DB + // Left here for Backward compatibility with plugins doing custom SQL queries using these metrics as string + if (!isset($newRowToAdd[Metrics::INDEX_NB_VISITS])) { + $oldRowToUpdate[Metrics::INDEX_NB_VISITS] += $newRowToAdd['nb_visits']; + $oldRowToUpdate[Metrics::INDEX_NB_ACTIONS] += $newRowToAdd['nb_actions']; + $oldRowToUpdate[Metrics::INDEX_NB_UNIQ_VISITORS] += $newRowToAdd['nb_uniq_visitors']; + return; + } + + // Edge case fail safe + if (!isset($oldRowToUpdate[Metrics::INDEX_NB_VISITS])) { + return; + } + + $oldRowToUpdate[Metrics::INDEX_NB_VISITS] += $newRowToAdd[Metrics::INDEX_NB_VISITS]; + if (array_key_exists(Metrics::INDEX_NB_ACTIONS, $newRowToAdd)) { + $oldRowToUpdate[Metrics::INDEX_NB_ACTIONS] += $newRowToAdd[Metrics::INDEX_NB_ACTIONS]; + } + if (array_key_exists(Metrics::INDEX_PAGE_NB_HITS, $newRowToAdd)) { + if (!array_key_exists(Metrics::INDEX_PAGE_NB_HITS, $oldRowToUpdate)) { + $oldRowToUpdate[Metrics::INDEX_PAGE_NB_HITS] = 0; + } + $oldRowToUpdate[Metrics::INDEX_PAGE_NB_HITS] += $newRowToAdd[Metrics::INDEX_PAGE_NB_HITS]; + } + $oldRowToUpdate[Metrics::INDEX_NB_UNIQ_VISITORS] += $newRowToAdd[Metrics::INDEX_NB_UNIQ_VISITORS]; + } + public function sumMetricsGoals($label, $row) { $idGoal = $row['idgoal']; if (!isset($this->data[$label][Metrics::INDEX_GOALS][$idGoal])) { - $this->data[$label][Metrics::INDEX_GOALS][$idGoal] = self::makeEmptyGoalRow($idGoal); + $this->data[$label][Metrics::INDEX_GOALS][$idGoal] = static::makeEmptyGoalRow($idGoal); } $this->doSumGoalsMetrics($row, $this->data[$label][Metrics::INDEX_GOALS][$idGoal]); } @@ -190,12 +234,13 @@ class DataArray public function sumMetricsActions($label, $row) { if (!isset($this->data[$label])) { - $this->data[$label] = self::makeEmptyActionRow(); + $this->data[$label] = static::makeEmptyActionRow(); } - $this->doSumVisitsMetrics($row, $this->data[$label], $onlyMetricsAvailableInActionsTable = true); + + $this->doSumActionsMetrics($row, $this->data[$label]); } - static protected function makeEmptyActionRow() + protected static function makeEmptyActionRow() { return array( Metrics::INDEX_NB_UNIQ_VISITORS => 0, @@ -207,12 +252,12 @@ class DataArray public function sumMetricsEvents($label, $row) { if (!isset($this->data[$label])) { - $this->data[$label] = self::makeEmptyEventRow(); + $this->data[$label] = static::makeEmptyEventRow(); } $this->doSumEventsMetrics($row, $this->data[$label], $onlyMetricsAvailableInActionsTable = true); } - static protected function makeEmptyEventRow() + protected static function makeEmptyEventRow() { return array( Metrics::INDEX_NB_UNIQ_VISITORS => 0, @@ -220,7 +265,7 @@ class DataArray Metrics::INDEX_EVENT_NB_HITS => 0, Metrics::INDEX_EVENT_NB_HITS_WITH_VALUE => 0, Metrics::INDEX_EVENT_SUM_EVENT_VALUE => 0, - Metrics::INDEX_EVENT_MIN_EVENT_VALUE => 0, + Metrics::INDEX_EVENT_MIN_EVENT_VALUE => false, Metrics::INDEX_EVENT_MAX_EVENT_VALUE => 0, ); } @@ -239,16 +284,16 @@ class DataArray $oldRowToUpdate[Metrics::INDEX_EVENT_NB_HITS] += $newRowToAdd[Metrics::INDEX_EVENT_NB_HITS]; $oldRowToUpdate[Metrics::INDEX_EVENT_NB_HITS_WITH_VALUE] += $newRowToAdd[Metrics::INDEX_EVENT_NB_HITS_WITH_VALUE]; - $newRowToAdd[Metrics::INDEX_EVENT_SUM_EVENT_VALUE] = round($newRowToAdd[Metrics::INDEX_EVENT_SUM_EVENT_VALUE], self::EVENT_VALUE_PRECISION); + $newRowToAdd[Metrics::INDEX_EVENT_SUM_EVENT_VALUE] = round($newRowToAdd[Metrics::INDEX_EVENT_SUM_EVENT_VALUE], static::EVENT_VALUE_PRECISION); $oldRowToUpdate[Metrics::INDEX_EVENT_SUM_EVENT_VALUE] += $newRowToAdd[Metrics::INDEX_EVENT_SUM_EVENT_VALUE]; - $oldRowToUpdate[Metrics::INDEX_EVENT_MAX_EVENT_VALUE] = round(max($newRowToAdd[Metrics::INDEX_EVENT_MAX_EVENT_VALUE], $oldRowToUpdate[Metrics::INDEX_EVENT_MAX_EVENT_VALUE]), self::EVENT_VALUE_PRECISION); + $oldRowToUpdate[Metrics::INDEX_EVENT_MAX_EVENT_VALUE] = round(max($newRowToAdd[Metrics::INDEX_EVENT_MAX_EVENT_VALUE], $oldRowToUpdate[Metrics::INDEX_EVENT_MAX_EVENT_VALUE]), static::EVENT_VALUE_PRECISION); // Update minimum only if it is set - if($newRowToAdd[Metrics::INDEX_EVENT_MIN_EVENT_VALUE] !== false) { - if($oldRowToUpdate[Metrics::INDEX_EVENT_MIN_EVENT_VALUE] === false) { - $oldRowToUpdate[Metrics::INDEX_EVENT_MIN_EVENT_VALUE] = round($newRowToAdd[Metrics::INDEX_EVENT_MIN_EVENT_VALUE], self::EVENT_VALUE_PRECISION); + if ($newRowToAdd[Metrics::INDEX_EVENT_MIN_EVENT_VALUE] !== false) { + if ($oldRowToUpdate[Metrics::INDEX_EVENT_MIN_EVENT_VALUE] === false) { + $oldRowToUpdate[Metrics::INDEX_EVENT_MIN_EVENT_VALUE] = round($newRowToAdd[Metrics::INDEX_EVENT_MIN_EVENT_VALUE], static::EVENT_VALUE_PRECISION); } else { - $oldRowToUpdate[Metrics::INDEX_EVENT_MIN_EVENT_VALUE] = round(min($newRowToAdd[Metrics::INDEX_EVENT_MIN_EVENT_VALUE], $oldRowToUpdate[Metrics::INDEX_EVENT_MIN_EVENT_VALUE]), self::EVENT_VALUE_PRECISION); + $oldRowToUpdate[Metrics::INDEX_EVENT_MIN_EVENT_VALUE] = round(min($newRowToAdd[Metrics::INDEX_EVENT_MIN_EVENT_VALUE], $oldRowToUpdate[Metrics::INDEX_EVENT_MIN_EVENT_VALUE]), static::EVENT_VALUE_PRECISION); } } } @@ -279,7 +324,7 @@ class DataArray public function sumMetricsVisitsPivot($parentLabel, $label, $row) { if (!isset($this->dataTwoLevels[$parentLabel][$label])) { - $this->dataTwoLevels[$parentLabel][$label] = self::makeEmptyRow(); + $this->dataTwoLevels[$parentLabel][$label] = static::makeEmptyRow(); } $this->doSumVisitsMetrics($row, $this->dataTwoLevels[$parentLabel][$label]); } @@ -288,7 +333,7 @@ class DataArray { $idGoal = $row['idgoal']; if (!isset($this->dataTwoLevels[$parentLabel][$label][Metrics::INDEX_GOALS][$idGoal])) { - $this->dataTwoLevels[$parentLabel][$label][Metrics::INDEX_GOALS][$idGoal] = self::makeEmptyGoalRow($idGoal); + $this->dataTwoLevels[$parentLabel][$label][Metrics::INDEX_GOALS][$idGoal] = static::makeEmptyGoalRow($idGoal); } $this->doSumGoalsMetrics($row, $this->dataTwoLevels[$parentLabel][$label][Metrics::INDEX_GOALS][$idGoal]); } @@ -298,7 +343,7 @@ class DataArray if (!isset($this->dataTwoLevels[$parentLabel][$label])) { $this->dataTwoLevels[$parentLabel][$label] = $this->makeEmptyActionRow(); } - $this->doSumVisitsMetrics($row, $this->dataTwoLevels[$parentLabel][$label], $onlyMetricsAvailableInActionsTable = true); + $this->doSumActionsMetrics($row, $this->dataTwoLevels[$parentLabel][$label]); } public function sumMetricsEventsPivot($parentLabel, $label, $row) @@ -332,7 +377,7 @@ class DataArray */ protected function enrichWithConversions(&$data) { - foreach ($data as $label => &$values) { + foreach ($data as &$values) { if (!isset($values[Metrics::INDEX_GOALS])) { continue; } @@ -357,7 +402,7 @@ class DataArray // if there are no "visit" column, we force one to prevent future complications // eg. This helps the setDefaultColumnsToDisplay() call - if(!isset($values[Metrics::INDEX_NB_VISITS])) { + if (!isset($values[Metrics::INDEX_NB_VISITS])) { $values[Metrics::INDEX_NB_VISITS] = 0; } } @@ -369,9 +414,9 @@ class DataArray * @param $row * @return bool */ - static public function isRowActions($row) + public static function isRowActions($row) { - return (count($row) == count(self::makeEmptyActionRow())) && isset($row[Metrics::INDEX_NB_ACTIONS]); + return (count($row) == count(static::makeEmptyActionRow())) && isset($row[Metrics::INDEX_NB_ACTIONS]); } /** diff --git a/www/analytics/core/DataFiles/Countries.php b/www/analytics/core/DataFiles/Countries.php deleted file mode 100644 index 38a60827..00000000 --- a/www/analytics/core/DataFiles/Countries.php +++ /dev/null @@ -1,326 +0,0 @@ - 'eur', - 'ae' => 'asi', - 'af' => 'asi', - 'ag' => 'amc', - 'ai' => 'amc', - 'al' => 'eur', - 'am' => 'asi', - 'ao' => 'afr', - 'aq' => 'ant', - 'ar' => 'ams', - 'as' => 'oce', - 'at' => 'eur', - 'au' => 'oce', - 'aw' => 'amc', - 'ax' => 'eur', - 'az' => 'asi', - 'ba' => 'eur', - 'bb' => 'amc', - 'bd' => 'asi', - 'be' => 'eur', - 'bf' => 'afr', - 'bg' => 'eur', - 'bh' => 'asi', - 'bi' => 'afr', - 'bj' => 'afr', - 'bl' => 'amc', - 'bm' => 'amc', - 'bn' => 'asi', - 'bo' => 'ams', - 'bq' => 'amc', - 'br' => 'ams', - 'bs' => 'amc', - 'bt' => 'asi', - 'bv' => 'ant', - 'bw' => 'afr', - 'by' => 'eur', - 'bz' => 'amc', - 'ca' => 'amn', - 'cc' => 'asi', - 'cd' => 'afr', - 'cf' => 'afr', - 'cg' => 'afr', - 'ch' => 'eur', - 'ci' => 'afr', - 'ck' => 'oce', - 'cl' => 'ams', - 'cm' => 'afr', - 'cn' => 'asi', - 'co' => 'ams', - 'cr' => 'amc', - 'cu' => 'amc', - 'cv' => 'afr', - 'cw' => 'amc', - 'cx' => 'asi', - 'cy' => 'eur', - 'cz' => 'eur', - 'de' => 'eur', - 'dj' => 'afr', - 'dk' => 'eur', - 'dm' => 'amc', - 'do' => 'amc', - 'dz' => 'afr', - 'ec' => 'ams', - 'ee' => 'eur', - 'eg' => 'afr', - 'eh' => 'afr', - 'er' => 'afr', - 'es' => 'eur', - 'et' => 'afr', - 'fi' => 'eur', - 'fj' => 'oce', - 'fk' => 'ams', - 'fm' => 'oce', - 'fo' => 'eur', - 'fr' => 'eur', - 'ga' => 'afr', - 'gb' => 'eur', - 'gd' => 'amc', - 'ge' => 'asi', - 'gf' => 'ams', - 'gg' => 'eur', - 'gh' => 'afr', - 'gi' => 'eur', - 'gl' => 'amn', - 'gm' => 'afr', - 'gn' => 'afr', - 'gp' => 'amc', - 'gq' => 'afr', - 'gr' => 'eur', - 'gs' => 'ant', - 'gt' => 'amc', - 'gu' => 'oce', - 'gw' => 'afr', - 'gy' => 'ams', - 'hk' => 'asi', - 'hm' => 'ant', - 'hn' => 'amc', - 'hr' => 'eur', - 'ht' => 'amc', - 'hu' => 'eur', - 'id' => 'asi', - 'ie' => 'eur', - 'il' => 'asi', - 'im' => 'eur', - 'in' => 'asi', - 'io' => 'asi', - 'iq' => 'asi', - 'ir' => 'asi', - 'is' => 'eur', - 'it' => 'eur', - 'je' => 'eur', - 'jm' => 'amc', - 'jo' => 'asi', - 'jp' => 'asi', - 'ke' => 'afr', - 'kg' => 'asi', - 'kh' => 'asi', - 'ki' => 'oce', - 'km' => 'afr', - 'kn' => 'amc', - 'kp' => 'asi', - 'kr' => 'asi', - 'kw' => 'asi', - 'ky' => 'amc', - 'kz' => 'asi', - 'la' => 'asi', - 'lb' => 'asi', - 'lc' => 'amc', - 'li' => 'eur', - 'lk' => 'asi', - 'lr' => 'afr', - 'ls' => 'afr', - 'lt' => 'eur', - 'lu' => 'eur', - 'lv' => 'eur', - 'ly' => 'afr', - 'ma' => 'afr', - 'mc' => 'eur', - 'md' => 'eur', - 'me' => 'eur', - 'mf' => 'amc', - 'mg' => 'afr', - 'mh' => 'oce', - 'mk' => 'eur', - 'ml' => 'afr', - 'mm' => 'asi', - 'mn' => 'asi', - 'mo' => 'asi', - 'mp' => 'oce', - 'mq' => 'amc', - 'mr' => 'afr', - 'ms' => 'amc', - 'mt' => 'eur', - 'mu' => 'afr', - 'mv' => 'asi', - 'mw' => 'afr', - 'mx' => 'amn', - 'my' => 'asi', - 'mz' => 'afr', - 'na' => 'afr', - 'nc' => 'oce', - 'ne' => 'afr', - 'nf' => 'oce', - 'ng' => 'afr', - 'ni' => 'amc', - 'nl' => 'eur', - 'no' => 'eur', - 'np' => 'asi', - 'nr' => 'oce', - 'nu' => 'oce', - 'nz' => 'oce', - 'om' => 'asi', - 'pa' => 'amc', - 'pe' => 'ams', - 'pf' => 'oce', - 'pg' => 'oce', - 'ph' => 'asi', - 'pk' => 'asi', - 'pl' => 'eur', - 'pm' => 'amn', - 'pn' => 'oce', - 'pr' => 'amc', - 'ps' => 'asi', - 'pt' => 'eur', - 'pw' => 'oce', - 'py' => 'ams', - 'qa' => 'asi', - 're' => 'afr', - 'ro' => 'eur', - 'rs' => 'eur', - 'ru' => 'eur', - 'rw' => 'afr', - 'sa' => 'asi', - 'sb' => 'oce', - 'sc' => 'afr', - 'sd' => 'afr', - 'se' => 'eur', - 'sg' => 'asi', - 'sh' => 'afr', - 'si' => 'eur', - 'sj' => 'eur', - 'sk' => 'eur', - 'sl' => 'afr', - 'sm' => 'eur', - 'sn' => 'afr', - 'so' => 'afr', - 'sr' => 'ams', - 'ss' => 'afr', - 'st' => 'afr', - 'sv' => 'amc', - 'sx' => 'amc', - 'sy' => 'asi', - 'sz' => 'afr', - 'tc' => 'amc', - 'td' => 'afr', - 'tf' => 'ant', - 'tg' => 'afr', - 'th' => 'asi', - 'ti' => 'asi', - 'tj' => 'asi', - 'tk' => 'oce', - 'tl' => 'asi', - 'tm' => 'asi', - 'tn' => 'afr', - 'to' => 'oce', - 'tr' => 'eur', - 'tt' => 'amc', - 'tv' => 'oce', - 'tw' => 'asi', - 'tz' => 'afr', - 'ua' => 'eur', - 'ug' => 'afr', - 'um' => 'oce', - 'us' => 'amn', - 'uy' => 'ams', - 'uz' => 'asi', - 'va' => 'eur', - 'vc' => 'amc', - 've' => 'ams', - 'vg' => 'amc', - 'vi' => 'amc', - 'vn' => 'asi', - 'vu' => 'oce', - 'wf' => 'oce', - 'ws' => 'oce', - 'ye' => 'asi', - 'yt' => 'afr', - 'za' => 'afr', - 'zm' => 'afr', - 'zw' => 'afr', - ); - - // codes for internal use - $GLOBALS['Piwik_CountryList_Extras'] = array( - // unknown - 'xx' => 'unk', - - // exceptionally reserved - 'ac' => 'afr', // .ac TLD - 'cp' => 'amc', - 'dg' => 'asi', - 'ea' => 'afr', - 'eu' => 'eur', // .eu TLD - 'fx' => 'eur', - 'ic' => 'afr', - 'su' => 'eur', // .su TLD - 'ta' => 'afr', - 'uk' => 'eur', // .uk TLD - - // transitionally reserved - 'an' => 'amc', // former Netherlands Antilles - 'bu' => 'asi', - 'cs' => 'eur', // former Serbia and Montenegro - 'nt' => 'asi', - 'sf' => 'eur', - 'tp' => 'oce', // .tp TLD - 'yu' => 'eur', // .yu TLD - 'zr' => 'afr', - - // MaxMind GeoIP specific - 'a1' => 'unk', - 'a2' => 'unk', - 'ap' => 'asi', - 'o1' => 'unk', - - // Catalonia (Spain) - 'cat' => 'eur', - ); -} - -if (!isset($GLOBALS['Piwik_ContinentList'])) { - // Primary reference: ISO 3166-1 alpha-2 - $GLOBALS['Piwik_ContinentList'] = array( - 'unk', // unknown - 'amn', // North America - 'amc', // Central America - 'ams', // South America - 'eur', // Europe - 'afr', // Africa - 'asi', // Asia - 'oce', // Oceania - 'ant', // Antarctica - ); -} diff --git a/www/analytics/core/DataFiles/Currencies.php b/www/analytics/core/DataFiles/Currencies.php deleted file mode 100644 index ccf10f61..00000000 --- a/www/analytics/core/DataFiles/Currencies.php +++ /dev/null @@ -1,186 +0,0 @@ - array('currency symbol', 'description'), - - // Top 5 by global trading volume - 'USD' => array('$', 'US dollar'), - 'EUR' => array('€', 'Euro'), - 'JPY' => array('¥', 'Japanese yen'), - 'GBP' => array('£', 'British pound'), - 'CHF' => array('Fr', 'Swiss franc'), - - 'AFN' => array('؋', 'Afghan afghani'), - 'ALL' => array('L', 'Albanian lek'), - 'DZD' => array('د.ج', 'Algerian dinar'), - 'AOA' => array('Kz', 'Angolan kwanza'), - 'ARS' => array('$', 'Argentine peso'), - 'AMD' => array('դր.', 'Armenian dram'), - 'AWG' => array('ƒ', 'Aruban florin'), - 'AUD' => array('$', 'Australian dollar'), - 'AZN' => array('m', 'Azerbaijani manat'), - 'BSD' => array('$', 'Bahamian dollar'), - 'BHD' => array('.د.ب', 'Bahraini dinar'), - 'BDT' => array('৳', 'Bangladeshi taka'), - 'BBD' => array('$', 'Barbadian dollar'), - 'BYR' => array('Br', 'Belarusian ruble'), - 'BZD' => array('$', 'Belize dollar'), - 'BMD' => array('$', 'Bermudian dollar'), - 'BTC' => array('BTC', 'Bitcoin'), - 'BTN' => array('Nu.', 'Bhutanese ngultrum'), - 'BOB' => array('Bs.', 'Bolivian boliviano'), - 'BAM' => array('KM', 'Bosnia Herzegovina mark'), - 'BWP' => array('P', 'Botswana pula'), - 'BRL' => array('R$', 'Brazilian real'), -// 'GBP' => array('£', 'British pound'), - 'BND' => array('$', 'Brunei dollar'), - 'BGN' => array('лв', 'Bulgarian lev'), - 'BIF' => array('Fr', 'Burundian franc'), - 'KHR' => array('៛', 'Cambodian riel'), - 'CAD' => array('$', 'Canadian dollar'), - 'CVE' => array('$', 'Cape Verdean escudo'), - 'KYD' => array('$', 'Cayman Islands dollar'), - 'XAF' => array('Fr', 'Central African CFA franc'), - 'CLP' => array('$', 'Chilean peso'), - 'CNY' => array('元', 'Chinese yuan'), - 'COP' => array('$', 'Colombian peso'), - 'KMF' => array('Fr', 'Comorian franc'), - 'CDF' => array('Fr', 'Congolese franc'), - 'CRC' => array('₡', 'Costa Rican colón'), - 'HRK' => array('kn', 'Croatian kuna'), - 'XPF' => array('F', 'CFP franc'), - 'CUC' => array('$', 'Cuban convertible peso'), - 'CUP' => array('$', 'Cuban peso'), - 'CMG' => array('ƒ', 'Curaçao and Sint Maarten guilder'), - 'CZK' => array('Kč', 'Czech koruna'), - 'DKK' => array('kr', 'Danish krone'), - 'DJF' => array('Fr', 'Djiboutian franc'), - 'DOP' => array('$', 'Dominican peso'), - 'XCD' => array('$', 'East Caribbean dollar'), - 'EGP' => array('ج.م', 'Egyptian pound'), - 'ERN' => array('Nfk', 'Eritrean nakfa'), - 'ETB' => array('Br', 'Ethiopian birr'), -// 'EUR' => array('€', 'Euro'), - 'FKP' => array('£', 'Falkland Islands pound'), - 'FJD' => array('$', 'Fijian dollar'), - 'GMD' => array('D', 'Gambian dalasi'), - 'GEL' => array('ლ', 'Georgian lari'), - 'GHS' => array('₵', 'Ghanaian cedi'), - 'GIP' => array('£', 'Gibraltar pound'), - 'GTQ' => array('Q', 'Guatemalan quetzal'), - 'GNF' => array('Fr', 'Guinean franc'), - 'GYD' => array('$', 'Guyanese dollar'), - 'HTG' => array('G', 'Haitian gourde'), - 'HNL' => array('L', 'Honduran lempira'), - 'HKD' => array('$', 'Hong Kong dollar'), - 'HUF' => array('Ft', 'Hungarian forint'), - 'ISK' => array('kr', 'Icelandic króna'), - 'INR' => array('‎₹', 'Indian rupee'), - 'IDR' => array('Rp', 'Indonesian rupiah'), - 'IRR' => array('﷼', 'Iranian rial'), - 'IQD' => array('ع.د', 'Iraqi dinar'), - 'ILS' => array('₪', 'Israeli new shekel'), - 'JMD' => array('$', 'Jamaican dollar'), -// 'JPY' => array('¥', 'Japanese yen'), - 'JOD' => array('د.ا', 'Jordanian dinar'), - 'KZT' => array('₸', 'Kazakhstani tenge'), - 'KES' => array('Sh', 'Kenyan shilling'), - 'KWD' => array('د.ك', 'Kuwaiti dinar'), - 'KGS' => array('лв', 'Kyrgyzstani som'), - 'LAK' => array('₭', 'Lao kip'), - 'LBP' => array('ل.ل', 'Lebanese pound'), - 'LSL' => array('L', 'Lesotho loti'), - 'LRD' => array('$', 'Liberian dollar'), - 'LYD' => array('ل.د', 'Libyan dinar'), - 'LTL' => array('Lt', 'Lithuanian litas'), - 'MOP' => array('P', 'Macanese pataca'), - 'MKD' => array('ден', 'Macedonian denar'), - 'MGA' => array('Ar', 'Malagasy ariary'), - 'MWK' => array('MK', 'Malawian kwacha'), - 'MYR' => array('RM', 'Malaysian ringgit'), - 'MVR' => array('ރ.', 'Maldivian rufiyaa'), - 'MRO' => array('UM', 'Mauritanian ouguiya'), - 'MUR' => array('₨', 'Mauritian rupee'), - 'MXN' => array('$', 'Mexican peso'), - 'MDL' => array('L', 'Moldovan leu'), - 'MNT' => array('₮', 'Mongolian tögrög'), - 'MAD' => array('د.م.', 'Moroccan dirham'), - 'MZN' => array('MTn', 'Mozambican metical'), - 'MMK' => array('K', 'Myanma kyat'), - 'NAD' => array('$', 'Namibian dollar'), - 'NPR' => array('₨', 'Nepalese rupee'), - 'ANG' => array('ƒ', 'Netherlands Antillean guilder'), - 'TWD' => array('$', 'New Taiwan dollar'), - 'NZD' => array('$', 'New Zealand dollar'), - 'NIO' => array('C$', 'Nicaraguan córdoba'), - 'NGN' => array('₦', 'Nigerian naira'), - 'KPW' => array('₩', 'North Korean won'), - 'NOK' => array('kr', 'Norwegian krone'), - 'OMR' => array('ر.ع.', 'Omani rial'), - 'PKR' => array('₨', 'Pakistani rupee'), - 'PAB' => array('B/.', 'Panamanian balboa'), - 'PGK' => array('K', 'Papua New Guinean kina'), - 'PYG' => array('₲', 'Paraguayan guaraní'), - 'PEN' => array('S/.', 'Peruvian nuevo sol'), - 'PHP' => array('₱', 'Philippine peso'), - 'PLN' => array('zł', 'Polish złoty'), - 'QAR' => array('ر.ق', 'Qatari riyal'), - 'RON' => array('L', 'Romanian leu'), - 'RUB' => array('руб.', 'Russian ruble'), - 'RWF' => array('Fr', 'Rwandan franc'), - 'SHP' => array('£', 'Saint Helena pound'), - 'SVC' => array('₡', 'Salvadoran colón'), - 'WST' => array('T', 'Samoan tala'), - 'STD' => array('Db', 'São Tomé and Príncipe dobra'), - 'SAR' => array('ر.س', 'Saudi riyal'), - 'RSD' => array('дин. or din.', 'Serbian dinar'), - 'SCR' => array('₨', 'Seychellois rupee'), - 'SLL' => array('Le', 'Sierra Leonean leone'), - 'SGD' => array('$', 'Singapore dollar'), - 'SBD' => array('$', 'Solomon Islands dollar'), - 'SOS' => array('Sh', 'Somali shilling'), - 'ZAR' => array('R', 'South African rand'), - 'KRW' => array('₩', 'South Korean won'), - 'LKR' => array('Rs', 'Sri Lankan rupee'), - 'SDG' => array('جنيه سوداني', 'Sudanese pound'), - 'SRD' => array('$', 'Surinamese dollar'), - 'SZL' => array('L', 'Swazi lilangeni'), - 'SEK' => array('kr', 'Swedish krona'), -// 'CHF' => array('Fr', 'Swiss franc'), - 'SYP' => array('ل.س', 'Syrian pound'), - 'TJS' => array('ЅМ', 'Tajikistani somoni'), - 'TZS' => array('Sh', 'Tanzanian shilling'), - 'THB' => array('฿', 'Thai baht'), - 'TOP' => array('T$', 'Tongan paʻanga'), - 'TTD' => array('$', 'Trinidad and Tobago dollar'), - 'TND' => array('د.ت', 'Tunisian dinar'), - 'TRY' => array('TL', 'Turkish lira'), - 'TMM' => array('m', 'Turkmenistani manat'), - 'UGX' => array('Sh', 'Ugandan shilling'), - 'UAH' => array('₴', 'Ukrainian hryvnia'), - 'AED' => array('د.إ', 'United Arab Emirates dirham'), -// 'USD' => array('$', 'United States dollar'), - 'UYU' => array('$', 'Uruguayan peso'), - 'UZS' => array('лв', 'Uzbekistani som'), - 'VUV' => array('Vt', 'Vanuatu vatu'), - 'VEF' => array('Bs F', 'Venezuelan bolívar'), - 'VND' => array('₫', 'Vietnamese đồng'), - 'XOF' => array('Fr', 'West African CFA franc'), - 'YER' => array('﷼', 'Yemeni rial'), - 'ZMW' => array('ZK', 'Zambian kwacha'), - 'ZWL' => array('$', 'Zimbabwean dollar'), - ); -} diff --git a/www/analytics/core/DataFiles/LanguageToCountry.php b/www/analytics/core/DataFiles/LanguageToCountry.php deleted file mode 100644 index 7e3ffafd..00000000 --- a/www/analytics/core/DataFiles/LanguageToCountry.php +++ /dev/null @@ -1,63 +0,0 @@ - 'bg', // Bulgarian => Bulgaria - 'ca' => 'es', // Catalan => Spain - 'cs' => 'cz', // Czech => Czech Republic - 'da' => 'dk', // Danish => Denmark - 'de' => 'de', // German => Germany - 'el' => 'gr', // Greek => Greece - 'es' => 'es', // Spanish => Spain - 'et' => 'ee', // Estonian => Estonia - 'fa' => 'ir', // Farsi => Iran - 'fi' => 'fi', // Finnish => Finland - 'fr' => 'fr', // French => France - 'he' => 'il', // Hebrew => Israel - 'hr' => 'hr', // Croatian => Croatia - 'hu' => 'hu', // Hungarian => Hungary - 'id' => 'id', // Indonesian => Indonesia - 'is' => 'is', // Icelandic => Iceland - 'it' => 'it', // Italian => Italy - 'ja' => 'jp', // Japanese => Japan - 'ko' => 'kr', // Korean => South Korea - 'lt' => 'lt', // Lithuanian => Lithuania - 'lv' => 'lv', // Latvian => Latvia - 'mk' => 'mk', // Macedonian => Macedonia - 'ms' => 'my', // Malay => Malaysia - 'nb' => 'no', // Bokmål => Norway - 'nl' => 'nl', // Dutch => Netherlands - 'nn' => 'no', // Nynorsk => Norway - 'no' => 'no', // Norwegian => Norway - 'pl' => 'pl', // Polish => Poland - 'pt' => 'pt', // Portugese => Portugal - 'ro' => 'ro', // Romanian => Romania - 'ru' => 'ru', // Russian => Russia - 'sk' => 'sk', // Slovak => Slovakia - 'sl' => 'si', // Slovene => Slovenia - 'sq' => 'al', // Albanian => Albania - 'sr' => 'rs', // Serbian => Serbia - 'sv' => 'se', // Swedish => Sweden - 'th' => 'th', // Thai => Thailand - 'bo' => 'ti', // Tibetan => Tibet - 'tr' => 'tr', // Turkish => Turkey - 'uk' => 'ua', // Ukrainian => Ukraine - ); -} diff --git a/www/analytics/core/DataFiles/Languages.php b/www/analytics/core/DataFiles/Languages.php deleted file mode 100644 index 47720149..00000000 --- a/www/analytics/core/DataFiles/Languages.php +++ /dev/null @@ -1,203 +0,0 @@ - array('Afar'), - 'ab' => array('Abkhazian'), - 'ae' => array('Avestan'), - 'af' => array('Afrikaans'), - 'ak' => array('Akan'), - 'am' => array('Amharic'), - 'an' => array('Aragonese'), - 'ar' => array('Arabic'), - 'as' => array('Assamese'), - 'av' => array('Avaric'), - 'ay' => array('Aymara'), - 'az' => array('Azerbaijani'), - 'ba' => array('Bashkir'), - 'be' => array('Belarusian'), - 'bg' => array('Bulgarian'), - 'bh' => array('Bihari'), // 'Bihari languages' - 'bi' => array('Bislama'), - 'bm' => array('Bambara'), - 'bn' => array('Bengali'), - 'bo' => array('Tibetan'), - 'br' => array('Breton'), - 'bs' => array('Bosnian'), - 'ca' => array('Catalan', 'Valencian'), - 'ce' => array('Chechen'), - 'ch' => array('Chamorro'), - 'co' => array('Corsican'), - 'cr' => array('Cree'), - 'cs' => array('Czech'), - 'cu' => array('Church Slavic', 'Old Slavonic', 'Church Slavonic', 'Old Bulgarian', 'Old Church Slavonic'), - 'cv' => array('Chuvash'), - 'cy' => array('Welsh'), - 'da' => array('Danish'), - 'de' => array('German'), - 'dv' => array('Divehi', 'Dhivehi', 'Maldivian'), - 'dz' => array('Dzongkha'), - 'ee' => array('Ewe'), - 'el' => array('Greek', 'Modern Greek', 'Hellenic'), // Greek, Modern (1453-) - 'en' => array('English'), - 'eo' => array('Esperanto'), - 'es' => array('Spanish', 'Castilian'), - 'et' => array('Estonian'), - 'eu' => array('Basque'), - 'fa' => array('Persian'), - 'ff' => array('Fulah'), - 'fi' => array('Finnish'), - 'fj' => array('Fijian'), - 'fo' => array('Faroese'), - 'fr' => array('French'), - 'fy' => array('Western Frisian'), - 'ga' => array('Irish'), - 'gd' => array('Gaelic', 'Scottish Gaelic'), - 'gl' => array('Galician'), - 'gn' => array('Guarani'), - 'gu' => array('Gujarati'), - 'gv' => array('Manx'), - 'ha' => array('Hausa'), - 'he' => array('Hebrew'), - 'hi' => array('Hindi'), - 'ho' => array('Hiri Motu'), - 'hr' => array('Croatian'), - 'ht' => array('Haitian', 'Haitian Creole'), - 'hu' => array('Hungarian'), - 'hy' => array('Armenian'), - 'hz' => array('Herero'), - 'ia' => array('Interlingua'), // 'Interlingua (International Auxiliary Language Association)' - 'id' => array('Indonesian'), - 'ie' => array('Interlingue', 'Occidental'), - 'ig' => array('Igbo'), - 'ii' => array('Sichuan Yi', 'Nuosu'), - 'ik' => array('Inupiaq'), - 'io' => array('Ido'), - 'is' => array('Icelandic'), - 'it' => array('Italian'), - 'iu' => array('Inuktitut'), - 'ja' => array('Japanese'), - 'jv' => array('Javanese'), - 'ka' => array('Georgian'), - 'kg' => array('Kongo'), - 'ki' => array('Kikuyu', 'Gikuyu'), - 'kj' => array('Kuanyama', 'Kwanyama'), - 'kk' => array('Kazakh'), - 'kl' => array('Kalaallisut', 'Greenlandic'), - 'km' => array('Central Khmer'), - 'kn' => array('Kannada'), - 'ko' => array('Korean'), - 'kr' => array('Kanuri'), - 'ks' => array('Kashmiri'), - 'ku' => array('Kurdish'), - 'kv' => array('Komi'), - 'kw' => array('Cornish'), - 'ky' => array('Kirghiz', 'Kyrgyz'), - 'la' => array('Latin'), - 'lb' => array('Luxembourgish', 'Letzeburgesch'), - 'lg' => array('Ganda'), - 'li' => array('Limburgan', 'Limburger', 'Limburgish'), - 'ln' => array('Lingala'), - 'lo' => array('Lao'), - 'lt' => array('Lithuanian'), - 'lu' => array('Luba-Katanga'), - 'lv' => array('Latvian'), - 'mg' => array('Malagasy'), - 'mh' => array('Marshallese'), - 'mi' => array('Maori'), - 'mk' => array('Macedonian'), - 'ml' => array('Malayalam'), - 'mn' => array('Mongolian'), -// 'mo' => array('Moldavian'), // deprecated - 'mr' => array('Marathi'), - 'ms' => array('Malay'), - 'mt' => array('Maltese'), - 'my' => array('Burmese'), - 'na' => array('Nauru'), - 'nb' => array('Norwegian Bokmål'), - 'nd' => array('North Ndebele'), - 'ne' => array('Nepali'), - 'ng' => array('Ndonga'), - 'nl' => array('Dutch', 'Flemish'), - 'nn' => array('Norwegian Nynorsk'), - 'no' => array('Norwegian'), - 'nr' => array('South Ndebele'), - 'nv' => array('Navajo', 'Navaho'), - 'ny' => array('Chichewa', 'Chewa', 'Nyanja'), - 'oc' => array('Occitan', 'Provençal'), // Occitan (post 1500) - 'oj' => array('Ojibwa'), - 'om' => array('Oromo'), - 'or' => array('Oriya'), - 'os' => array('Ossetian', 'Ossetic'), - 'pa' => array('Panjabi', 'Punjabi'), - 'pi' => array('Pali'), - 'pl' => array('Polish'), - 'ps' => array('Pushto', 'Pashto'), - 'pt' => array('Portuguese'), - 'qu' => array('Quechua'), - 'rm' => array('Romansh'), - 'rn' => array('Rundi'), - 'ro' => array('Romanian', 'Moldavian', 'Moldovan'), - 'ru' => array('Russian'), - 'rw' => array('Kinyarwanda'), - 'sa' => array('Sanskrit'), - 'sc' => array('Sardinian'), - 'sd' => array('Sindhi'), - 'se' => array('Northern Sami'), - 'sg' => array('Sango'), -// 'sh' => array('Serbo-Croatian'), // deprecated - 'si' => array('Sinhala', 'Sinhalese'), - 'sk' => array('Slovak'), - 'sl' => array('Slovenian'), - 'sm' => array('Samoan'), - 'sn' => array('Shona'), - 'so' => array('Somali'), - 'sq' => array('Albanian'), - 'sr' => array('Serbian'), - 'ss' => array('Swati'), - 'st' => array('Southern Soth'), - 'su' => array('Sundanese'), - 'sv' => array('Swedish'), - 'sw' => array('Swahili'), - 'ta' => array('Tamil'), - 'te' => array('Telugu'), - 'tg' => array('Tajik'), - 'th' => array('Thai'), - 'ti' => array('Tigrinya'), - 'tk' => array('Turkmen'), - 'tl' => array('Tagalog'), - 'tn' => array('Tswana'), - 'to' => array('Tonga'), // Tonga (Tonga Islands) - 'tr' => array('Turkish'), - 'ts' => array('Tsonga'), - 'tt' => array('Tatar'), - 'tw' => array('Twi'), - 'ty' => array('Tahitian'), - 'ug' => array('Uighur', 'Uyghur'), - 'uk' => array('Ukrainian'), - 'ur' => array('Urdu'), - 'uz' => array('Uzbek'), - 've' => array('Venda'), - 'vi' => array('Vietnamese'), - 'vo' => array('Volapük'), - 'wa' => array('Walloon'), - 'wo' => array('Wolof'), - 'xh' => array('Xhosa'), - 'yi' => array('Yiddish'), - 'yo' => array('Yoruba'), - 'za' => array('Zhuang', 'Chuang'), - 'zh' => array('Chinese'), - 'zu' => array('Zulu'), - ); -} diff --git a/www/analytics/core/DataFiles/Providers.php b/www/analytics/core/DataFiles/Providers.php index ff26661c..d26d85b6 100644 --- a/www/analytics/core/DataFiles/Providers.php +++ b/www/analytics/core/DataFiles/Providers.php @@ -1,6 +1,6 @@ array( SearchEngineName, KeywordParameter, [path containing the keyword], [charset used by the search engine]) - * - * The main search engine URL has to be at the top of the list for the given - * search Engine. This serves as the master record so additional URLs - * don't have to duplicate all the information, but can override when needed. - * - * The URL, "example.com", will match "example.com", "m.example.com", - * "www.example.com", and "search.example.com". - * - * For region-specific search engines, the URL, "{}.example.com" will match - * any ISO3166-1 alpha2 country code against "{}". Similarly, "example.{}" - * will match against valid country TLDs, but should be used sparingly to - * avoid false positives. - * - * The charset should be an encoding supported by mbstring. If unspecified, - * we'll assume it's UTF-8. - * Reference: http://www.php.net/manual/en/mbstring.encodings.php - * - * You can add new search engines icons by adding the icon in the - * plugins/Referrers/images/searchEngines directory using the format - * 'mainSearchEngineUrl.png'. Example: www.google.com.png - * - * To help Piwik link directly the search engine result page for the keyword, - * specify the third entry in the array using the macro {k} that will - * automatically be replaced by the keyword. - * - * A simple example is: - * 'www.google.com' => array('Google', 'q', 'search?q={k}'), - * - * A more complicated example, with an array of possible variable names, and a custom charset: - * 'www.baidu.com' => array('Baidu', array('wd', 'word', 'kw'), 's?wd={k}', 'gb2312'), - * - * Another example using a regular expression to parse the path for keywords: - * 'infospace.com' => array('InfoSpace', array('/dir1\/(pattern)\/dir2/'), '/dir1/{k}/dir2/stuff/'), - */ -if (!isset($GLOBALS['Piwik_SearchEngines'])) { - $GLOBALS['Piwik_SearchEngines'] = array( - // 1 - '1.cz' => array('1.cz', array('/s\/([^\/]+)/', 'q'), 's/{k}', 'iso-8859-2'), - - // 123people - 'www.123people.com' => array('123people', array('/s\/([^\/]+)/', 'search_term'), 's/{k}'), - '123people.{}' => array('123people'), - - // 360search - 'so.360.cn' => array('360search', 'q', 's?q={k}', array('UTF-8', 'gb2312')), - 'www.so.com' => array('360search', 'q', 's?q={k}', array('UTF-8', 'gb2312')), - - // Abacho - 'www.abacho.de' => array('Abacho', 'q', 'suche?q={k}'), - 'www.abacho.com' => array('Abacho'), - 'www.abacho.co.uk' => array('Abacho'), - 'www.se.abacho.com' => array('Abacho'), - 'www.tr.abacho.com' => array('Abacho'), - 'www.abacho.at' => array('Abacho'), - 'www.abacho.fr' => array('Abacho'), - 'www.abacho.es' => array('Abacho'), - 'www.abacho.ch' => array('Abacho'), - 'www.abacho.it' => array('Abacho'), - - // ABCsøk - 'abcsok.no' => array('ABCsøk', 'q', '?q={k}'), - 'verden.abcsok.no' => array('ABCsøk'), - - // Acoon - 'www.acoon.de' => array('Acoon', 'begriff', 'cgi-bin/search.exe?begriff={k}'), - - // Alexa - 'alexa.com' => array('Alexa', 'q', 'search?q={k}'), - 'search.toolbars.alexa.com' => array('Alexa'), - - // Alice Adsl - 'rechercher.aliceadsl.fr' => array('Alice Adsl', 'qs', 'google.pl?qs={k}'), - - // Allesklar - 'www.allesklar.de' => array('Allesklar', 'words', '?words={k}'), - 'www.allesklar.at' => array('Allesklar'), - 'www.allesklar.ch' => array('Allesklar'), - - // AllTheWeb - 'www.alltheweb.com' => array('AllTheWeb', 'q', 'search?q={k}'), - - // all.by - 'all.by' => array('All.by', 'query', 'cgi-bin/search.cgi?mode=by&query={k}'), - - // Altavista - 'www.altavista.com' => array('AltaVista', 'q', 'web/results?q={k}'), - 'search.altavista.com' => array('AltaVista'), - 'listings.altavista.com' => array('AltaVista'), - 'altavista.de' => array('AltaVista'), - 'altavista.fr' => array('AltaVista'), - '{}.altavista.com' => array('AltaVista'), - 'be-nl.altavista.com' => array('AltaVista'), - 'be-fr.altavista.com' => array('AltaVista'), - - // Apollo Latvia - 'apollo.lv/portal/search/' => array('Apollo lv', 'q', '?cof=FORID%3A11&q={k}&search_where=www'), - - // APOLLO7 - 'apollo7.de' => array('Apollo7', 'query', 'a7db/index.php?query={k}&de_sharelook=true&de_bing=true&de_witch=true&de_google=true&de_yahoo=true&de_lycos=true'), - - // AOL - 'search.aol.com' => array('AOL', array('query', 'q'), 'aol/search?q={k}'), - 'search.aol.it' => array('AOL'), - 'aolsearch.aol.com' => array('AOL'), - 'www.aolrecherche.aol.fr' => array('AOL'), - 'www.aolrecherches.aol.fr' => array('AOL'), - 'www.aolimages.aol.fr' => array('AOL'), - 'aim.search.aol.com' => array('AOL'), - 'www.recherche.aol.fr' => array('AOL'), - 'recherche.aol.fr' => array('AOL'), - 'find.web.aol.com' => array('AOL'), - 'recherche.aol.ca' => array('AOL'), - 'aolsearch.aol.co.uk' => array('AOL'), - 'search.aol.co.uk' => array('AOL'), - 'aolrecherche.aol.fr' => array('AOL'), - 'sucheaol.aol.de' => array('AOL'), - 'suche.aol.de' => array('AOL'), - 'o2suche.aol.de' => array('AOL'), - 'suche.aolsvc.de' => array('AOL'), - 'aolbusqueda.aol.com.mx' => array('AOL'), - 'alicesuche.aol.de' => array('AOL'), - 'alicesuchet.aol.de' => array('AOL'), - 'suchet2.aol.de' => array('AOL'), - 'search.hp.my.aol.com.au' => array('AOL'), - 'search.hp.my.aol.de' => array('AOL'), - 'search.hp.my.aol.it' => array('AOL'), - 'search-intl.netscape.com' => array('AOL'), - 'de.aolsearch.com' => array('AOL', 'q', 'search?q={k}'), - - // Aport - 'sm.aport.ru' => array('Aport', 'r', 'search?r={k}'), - - // arama - 'arama.com' => array('Arama', 'q', 'search.php3?q={k}'), - - // Arcor - 'www.arcor.de' => array('Arcor', 'Keywords', 'content/searchresult.jsp?Keywords={k}'), - - // Arianna (Libero.it) - 'arianna.libero.it' => array('Arianna', 'query', 'search/abin/integrata.cgi?query={k}'), - 'www.arianna.com' => array('Arianna'), - - // Ask (IAC Search & Media) - 'ask.com' => array('Ask', array('ask', 'q', 'searchfor'), 'web?q={k}'), - 'web.ask.com' => array('Ask'), - 'int.ask.com' => array('Ask'), - 'mws.ask.com' => array('Ask'), - 'images.ask.com' => array('Ask'), - 'images.{}.ask.com' => array('Ask'), - 'ask.reference.com' => array('Ask'), - 'www.askkids.com' => array('Ask'), - 'iwon.ask.com' => array('Ask'), - 'www.ask.co.uk' => array('Ask'), - '{}.ask.com' => array('Ask'), - 'www.qbyrd.com' => array('Ask'), - '{}.qbyrd.com' => array('Ask'), - 'www.search-results.com' => array('Ask'), - 'int.search-results.com' => array('Ask'), - '{}.search-results.com' => array('Ask'), - 'search.ask.com' => array('Ask'), - '{}.search.ask.com' => array('Ask'), - 'avira-int.ask.com' => array('Ask'), - 'searchqu.com' => array('Ask'), - - // Atlas - 'searchatlas.centrum.cz' => array('Atlas', 'q', '?q={k}'), - - // Austronaut - 'www2.austronaut.at' => array('Austronaut', 'q'), - 'www1.austronaut.at' => array('Austronaut'), - - // Babylon (Enhanced by Google) - 'search.babylon.com' => array('Babylon', array('q', '/\/web\/(.*)/'), '?q={k}'), - 'searchassist.babylon.com' => array('Babylon'), - - // Baidu - 'www.baidu.com' => array('Baidu', array('wd', 'word', 'kw'), 's?wd={k}', array('UTF-8', 'gb2312')), - 'www1.baidu.com' => array('Baidu'), - 'zhidao.baidu.com' => array('Baidu'), - 'tieba.baidu.com' => array('Baidu'), - 'news.baidu.com' => array('Baidu'), - 'web.gougou.com' => array('Baidu', 'search', 'search?search={k}'), // uses baidu search - - // Biglobe - 'cgi.search.biglobe.ne.jp' => array('Biglobe', 'q', 'cgi-bin/search-st?q={k}'), - - // Bing - 'bing.com' => array('Bing', array('q', 'Q'), 'search?q={k}'), - '{}.bing.com' => array('Bing'), - 'msnbc.msn.com' => array('Bing'), - 'dizionario.it.msn.com' => array('Bing'), - 'enciclopedia.it.msn.com' => array('Bing'), - - // Bing Cache - 'cc.bingj.com' => array('Bing'), - - // Bing Images - 'bing.com/images/search' => array('Bing Images', array('q', 'Q'), '?q={k}'), - '{}.bing.com/images/search' => array('Bing Images'), - - // blekko - 'blekko.com' => array('blekko', array('q', '/\/ws\/(.*)/'), 'ws/{k}'), - - // Blogdigger - 'www.blogdigger.com' => array('Blogdigger', 'q'), - - // Blogpulse - 'www.blogpulse.com' => array('Blogpulse', 'query', 'search?query={k}'), - - // Bluewin - 'search.bluewin.ch' => array('Bluewin', array('searchTerm', 'q'), 'v2/index.php?q={k}'), - - // canoe.ca - 'web.canoe.ca' => array('Canoe.ca', 'q', 'search?q={k}'), - - // Centrum - 'search.centrum.cz' => array('Centrum', 'q', '?q={k}'), - 'morfeo.centrum.cz' => array('Centrum'), - - // Charter - 'www.charter.net' => array('Charter', 'q', 'search/index.php?q={k}'), - - // Claro Search - 'claro-search.com' => array('Claro Search', 'q', '?q={k}'), - - // Clix (Enhanced by Google) - 'pesquisa.clix.pt' => array('Clix', 'question', 'resultado.html?in=Mundial&question={k}'), - - // Conduit - 'search.conduit.com' => array('Conduit.com', 'q', 'Results.aspx?q={k}'), - 'images.search.conduit.com' => array('Conduit.com'), - - // Comcast - 'search.comcast.net' => array('Comcast', 'q', '?q={k}'), - - // Crawler - 'www.crawler.com' => array('Crawler', 'q', 'search/results1.aspx?q={k}'), - - // Compuserve - 'websearch.cs.com' => array('Compuserve.com (Enhanced by Google)', 'query', 'cs/search?query={k}'), - - // Cuil - 'www.cuil.com' => array('Cuil', 'q', 'search?q={k}'), - - // Daemon search - 'daemon-search.com' => array('Daemon search', 'q', 'explore/web?q={k}'), - 'my.daemon-search.com' => array('Daemon search'), - - // DasOertliche - 'www.dasoertliche.de' => array('DasOertliche', 'kw'), - - // DasTelefonbuch - 'www1.dastelefonbuch.de' => array('DasTelefonbuch', 'kw'), - - // Daum - 'search.daum.net' => array('Daum', 'q', 'search?q={k}'), - - // Delfi Latvia - 'smart.delfi.lv' => array('Delfi lv', 'q', 'find?q={k}'), - - // Delfi - 'otsing.delfi.ee' => array('Delfi EE', 'q', 'find?q={k}'), - - // Digg - 'digg.com' => array('Digg', 's', 'search?s={k}'), - - // dir.com - 'fr.dir.com' => array('dir.com', 'req'), - - // dmoz - 'dmoz.org' => array('dmoz', 'search'), - 'editors.dmoz.org' => array('dmoz'), - - // DuckDuckGo - 'duckduckgo.com' => array('DuckDuckGo', 'q', '?q={k}'), - - // earthlink - 'search.earthlink.net' => array('Earthlink', 'q', 'search?q={k}'), - - // Ecosia (powered by Bing) - 'ecosia.org' => array('Ecosia', 'q', 'search.php?q={k}'), - - // Eniro - 'www.eniro.se' => array('Eniro', array('q', 'search_word'), 'query?q={k}'), - - // Eurip - 'www.eurip.com' => array('Eurip', 'q', 'search/?q={k}'), - - // Euroseek - 'www.euroseek.com' => array('Euroseek', 'string', 'system/search.cgi?string={k}'), - - // Everyclick - 'www.everyclick.com' => array('Everyclick', 'keyword'), - - // Excite - 'search.excite.it' => array('Excite', 'q', 'web/?q={k}'), - 'search.excite.fr' => array('Excite'), - 'search.excite.de' => array('Excite'), - 'search.excite.co.uk' => array('Excite'), - 'search.excite.es' => array('Excite'), - 'search.excite.nl' => array('Excite'), - 'msxml.excite.com' => array('Excite', '/\/[^\/]+\/ws\/results\/[^\/]+\/([^\/]+)/'), - 'www.excite.co.jp' => array('Excite', 'search', 'search.gw?search={k}', 'SHIFT_JIS'), - - // Exalead - 'www.exalead.fr' => array('Exalead', 'q', 'search/results?q={k}'), - 'www.exalead.com' => array('Exalead'), - - // eo - 'eo.st' => array('eo', 'x_query', 'cgi-bin/eolost.cgi?x_query={k}'), - - // Facebook - 'www.facebook.com' => array('Facebook', 'q', 'search/?q={k}'), - - // Fast Browser Search - 'www.fastbrowsersearch.com' => array('Fast Browser Search', 'q', 'results/results.aspx?q={k}'), - - // Francite - 'recherche.francite.com' => array('Francite', 'name'), - - // Fireball - 'www.fireball.de' => array('Fireball', 'q', 'ajax.asp?q={k}'), - - // Firstfind - 'www.firstsfind.com' => array('Firstsfind', 'qry'), - - // Fixsuche - 'www.fixsuche.de' => array('Fixsuche', 'q'), - - // Flix - 'www.flix.de' => array('Flix.de', 'keyword'), - - // Forestle - 'forestle.org' => array('Forestle', 'q', 'search.php?q={k}'), - '{}.forestle.org' => array('Forestle'), - 'forestle.mobi' => array('Forestle'), - - // Free - 'search.free.fr' => array('Free', 'q'), - 'search1-2.free.fr' => array('Free'), - 'search1-1.free.fr' => array('Free'), - - // Freecause - 'search.freecause.com' => array('FreeCause', 'p', '?p={k}'), - - // Freenet - 'suche.freenet.de' => array('Freenet', array('query', 'Keywords'), 'suche/?query={k}'), - - // FriendFeed - 'friendfeed.com' => array('FriendFeed', 'q', 'search?q={k}'), - - // GAIS - 'gais.cs.ccu.edu.tw' => array('GAIS', 'q', 'search.php?q={k}'), - - // Geona - 'geona.net' => array('Geona', 'q', 'search?q={k}'), - - // Gigablast - 'www.gigablast.com' => array('Gigablast', 'q', 'search?q={k}'), - 'dir.gigablast.com' => array('Gigablast (Directory)', 'q'), - - // Gnadenmeer - 'www.gnadenmeer.de' => array('Gnadenmeer', 'keyword'), - - // Gomeo - 'www.gomeo.com' => array('Gomeo', array('Keywords', '/\/search\/([^\/]+)/'), '/search/{k}'), - - // goo - 'search.goo.ne.jp' => array('goo', 'MT', 'web.jsp?MT={k}'), - 'ocnsearch.goo.ne.jp' => array('goo'), - - // Google - 'google.com' => array('Google', 'q', 'search?q={k}'), - 'google.{}' => array('Google'), - 'www2.google.com' => array('Google'), - 'ipv6.google.com' => array('Google'), - 'go.google.com' => array('Google'), - - // Google vs typo squatters - 'wwwgoogle.com' => array('Google'), - 'wwwgoogle.{}' => array('Google'), - 'gogole.com' => array('Google'), - 'gogole.{}' => array('Google'), - 'gppgle.com' => array('Google'), - 'gppgle.{}' => array('Google'), - 'googel.com' => array('Google'), - 'googel.{}' => array('Google'), - - // Powered by Google - 'search.avg.com' => array('Google'), - 'isearch.avg.com' => array('Google'), - 'www.cnn.com' => array('Google', 'query'), - 'darkoogle.com' => array('Google'), - 'search.darkoogle.com' => array('Google'), - 'search.foxtab.com' => array('Google'), - 'www.gooofullsearch.com' => array('Google', 'Keywords'), - 'search.hiyo.com' => array('Google'), - 'search.incredimail.com' => array('Google'), - 'search1.incredimail.com' => array('Google'), - 'search2.incredimail.com' => array('Google'), - 'search3.incredimail.com' => array('Google'), - 'search4.incredimail.com' => array('Google'), - 'search.sweetim.com' => array('Google'), - 'www.fastweb.it' => array('Google'), - 'search.juno.com' => array('Google', 'query'), - 'find.tdc.dk' => array('Google'), - 'it.luna.tv' => array('Google'), - 'searchresults.verizon.com' => array('Google'), - 'search.walla.co.il' => array('Google'), - 'search.alot.com' => array('Google'), - 'suche.gmx.net' => array('Google', 'q', 'web?q={k}'), - 'search.incredibar.com' => array('Google', 'q', 'search.php?q={k}'), - 'www.delta-search.com' => array('Google', 'q'), - 'search.1und1.de' => array('Google', 'q', 'web?q={k}'), - 'search.zonealarm.com' => array('Google'), - 'start.lenovo.com' => array('Google', 'q', 'search/index.php?q={k}'), - 'wow.com' => array('Google'), - '{}.wow.com' => array('Google'), - 'search.leonardo.it' => array('Google'), - 'www.optuszoo.com.au' => array('Google'), - 'search.smt.docomo.ne.jp' => array('Google', 'MT'), - 'image.search.smt.docomo.ne.jp' => array('Google', 'MT'), - - - // Google Earth - // - 2010-09-13: are these redirects now? - 'www.googleearth.de' => array('Google'), - 'www.googleearth.fr' => array('Google'), - - // Google Cache - 'webcache.googleusercontent.com' => array('Google', '/\/search\?q=cache:[A-Za-z0-9]+:[^+]+([^&]+)/', 'search?q={k}'), - - // Google SSL - 'encrypted.google.com' => array('Google SSL', 'q', 'search?q={k}'), - - // Google Blogsearch - 'blogsearch.google.com' => array('Google Blogsearch', 'q', 'blogsearch?q={k}'), - 'blogsearch.google.{}' => array('Google Blogsearch'), - - // Google Custom Search - 'google.com/cse' => array('Google Custom Search', array('q', 'query')), - 'google.{}/cse' => array('Google Custom Search'), - 'google.com/custom' => array('Google Custom Search'), - 'google.{}/custom' => array('Google Custom Search'), - - // Google Translation - 'translate.google.com' => array('Google Translations', 'q'), - - // Google Images - 'images.google.com' => array('Google Images', 'q', 'images?q={k}'), - 'images.google.{}' => array('Google Images'), - - // Google Maps - 'maps.google.com' => array('Google Maps', 'q', 'maps?q={k}'), - 'maps.google.{}' => array('Google Maps'), - - // Google News - 'news.google.com' => array('Google News', 'q'), - 'news.google.{}' => array('Google News'), - - // Google Shopping - 'google.com/products' => array('Google Shopping', 'q', '?q={k}&tbm=shop'), - 'google.{}/products' => array('Google Shopping'), - - // Google syndicated search - 'googlesyndicatedsearch.com' => array('Google syndicated search', 'q'), - - // Google Video - 'video.google.com' => array('Google Video', 'q', 'search?q={k}&tbm=vid'), - - // Google Scholar - 'scholar.google.com' => array('Google Scholar', 'q', 'scholar?q={k}'), - 'scholar.google.{}' => array('Google Scholar'), - - // Google Wireless Transcoder - // - does not appear to execute JavaScript -// 'google.com/gwt/n' => array('Google Wireless Transcoder'), - - // Goyellow.de - 'www.goyellow.de' => array('GoYellow.de', 'MDN'), - - // Gule Sider - 'www.gulesider.no' => array('Gule Sider', 'q'), - - // HighBeam - 'www.highbeam.com' => array('HighBeam', 'q', 'Search.aspx?q={k}'), - - // Hit-Parade - 'req.hit-parade.com' => array('Hit-Parade', 'p7', 'general/recherche.asp?p7={k}'), - 'class.hit-parade.com' => array('Hit-Parade'), - 'www.hit-parade.com' => array('Hit-Parade'), - - // Holmes.ge - 'holmes.ge' => array('Holmes', 'q', 'search.htm?q={k}'), - - // Hooseek.com - 'www.hooseek.com' => array('Hooseek', 'recherche', 'web?recherche={k}'), - - // Hotbot - 'www.hotbot.com' => array('Hotbot', 'query'), - - // Icerocket - 'blogs.icerocket.com' => array('Icerocket', 'q', 'search?q={k}'), - - // ICQ - 'www.icq.com' => array('ICQ', 'q', 'search/results.php?q={k}'), - 'search.icq.com' => array('ICQ'), - - // Ilse - 'www.ilse.nl' => array('Ilse NL', 'search_for', '?search_for={k}'), - - // iMesh - 'search.imesh.com' => array('iMesh', array('q', 'si'), 'web?q={k}'), - - // Inbox.com - 'www2.inbox.com' => array('Inbox', 'q', 'search/results1.aspx?q={k}'), - - // InfoSpace (and related web properties) - 'infospace.com' => array('InfoSpace', 'q', '/search/web?q={k}'), - 'dogpile.com' => array('InfoSpace'), - 'tattoodle.com' => array('InfoSpace'), - 'metacrawler.com' => array('InfoSpace'), - 'webfetch.com' => array('InfoSpace'), - 'webcrawler.com' => array('InfoSpace'), - 'search.kiwee.com' => array('InfoSpace'), - - // old infospace system - 'wsdsold.infospace.com' => array('InfoSpace', '/\/[^\/]+\/ws\/results\/[^\/]+\/([^\/]+)/', 'pemonitorhosted/ws/results/Web/{k}/1/417/TopNavigation/Source/'), - - // Powered by InfoSpace - 'isearch.babylon.com' => array('InfoSpace', 'q'), - 'start.facemoods.com' => array('InfoSpace', 's'), - 'start.funmoods.com' => array('InfoSpace', 'q'), - 'search.magentic.com' => array('InfoSpace', 'q'), - 'search.searchcompletion.com' => array('InfoSpace', 'q'), - 'www.searchmobileonline.com' => array('InfoSpace', 'q'), - 'isearch.glarysoft.com' => array('InfoSpace', 'q'), - 'search.chatzum.com' => array('InfoSpace', 'q'), - 'home.speedbit.com' => array('InfoSpace', 'q'), - 'search.b1.org' => array('InfoSpace', 'q'), - 'searchya.com' => array('InfoSpace', 'q'), - 'search.handycafe.com' => array('InfoSpace', 'q'), - 'search.v9.com' => array('InfoSpace', 'q'), - - /* - * Other InfoSpace powered metasearches are handled in Common::extractSearchEngineInformationFromUrl() - * - * This includes sites such as: - * - search.nation.com - * - ws.copernic.com - * - result.iminent.com - */ - - // Interia - 'www.google.interia.pl' => array('Interia', 'q', 'szukaj?q={k}'), - - // I-play - 'start.iplay.com' => array('I-play', 'q', 'searchresults.aspx?q={k}'), - - // Ixquick - 'ixquick.com' => array('Ixquick', 'query'), - 'www.eu.ixquick.com' => array('Ixquick'), - 'ixquick.de' => array('Ixquick'), - 'www.ixquick.de' => array('Ixquick'), - 'us.ixquick.com' => array('Ixquick'), - 's1.us.ixquick.com' => array('Ixquick'), - 's2.us.ixquick.com' => array('Ixquick'), - 's3.us.ixquick.com' => array('Ixquick'), - 's4.us.ixquick.com' => array('Ixquick'), - 's5.us.ixquick.com' => array('Ixquick'), - 'eu.ixquick.com' => array('Ixquick'), - 's8-eu.ixquick.com' => array('Ixquick'), - 's1-eu.ixquick.de' => array('Ixquick'), - - // Jyxo - 'jyxo.1188.cz' => array('Jyxo', 'q', 's?q={k}'), - - // Jungle Spider - 'www.jungle-spider.de' => array('Jungle Spider', 'q'), - - // Jungle key - 'junglekey.com' => array('Jungle Key', 'query', 'search.php?query={k}&type=web&lang=en'), - 'junglekey.fr' => array('Jungle Key'), - - // Kataweb - 'www.kataweb.it' => array('Kataweb', 'q'), - - // Kvasir - 'www.kvasir.no' => array('Kvasir', 'q', 'alle?q={k}'), - - // Latne - 'www.latne.lv' => array('Latne', 'q', 'siets.php?q={k}'), - - // La Toile Du Québec via Google - 'www.toile.com' => array('La Toile Du Québec (Google)', 'q', 'search?q={k}'), - 'web.toile.com' => array('La Toile Du Québec (Google)'), - - // Looksmart - 'www.looksmart.com' => array('Looksmart', 'key'), - - // Lo.st (Enhanced by Google) - 'lo.st' => array('Lo.st', 'x_query', 'cgi-bin/eolost.cgi?x_query={k}'), - - // Lycos - 'search.lycos.com' => array('Lycos', 'query', '?query={k}'), - 'lycos.{}' => array('Lycos'), - - // maailm.com - 'www.maailm.com' => array('maailm.com', 'tekst'), - - // Mail.ru - 'go.mail.ru' => array('Mailru', 'q', 'search?rch=e&q={k}', array('UTF-8', 'windows-1251')), - - // Mamma - 'www.mamma.com' => array('Mamma', 'query', 'result.php?q={k}'), - 'mamma75.mamma.com' => array('Mamma'), - - // Meta - 'meta.ua' => array('Meta.ua', 'q', 'search.asp?q={k}'), - - // MetaCrawler.de - 's1.metacrawler.de' => array('MetaCrawler DE', 'qry', '?qry={k}'), - 's2.metacrawler.de' => array('MetaCrawler DE'), - 's3.metacrawler.de' => array('MetaCrawler DE'), - - // Metager - 'meta.rrzn.uni-hannover.de' => array('Metager', 'eingabe', 'meta/cgi-bin/meta.ger1?eingabe={k}'), - 'www.metager.de' => array('Metager'), - - // Metager2 - 'metager2.de' => array('Metager2', 'q', 'search/index.php?q={k}'), - - // Meinestadt - 'www.meinestadt.de' => array('Meinestadt.de', 'words'), - - // Mister Wong - 'www.mister-wong.com' => array('Mister Wong', 'keywords', 'search/?keywords={k}'), - 'www.mister-wong.de' => array('Mister Wong'), - - // Monstercrawler - 'www.monstercrawler.com' => array('Monstercrawler', 'qry'), - - // Mozbot - 'www.mozbot.fr' => array('mozbot', 'q', 'results.php?q={k}'), - 'www.mozbot.co.uk' => array('mozbot'), - 'www.mozbot.com' => array('mozbot'), - - // El Mundo - 'ariadna.elmundo.es' => array('El Mundo', 'q'), - - // MySpace - 'searchservice.myspace.com' => array('MySpace', 'qry', 'index.cfm?fuseaction=sitesearch.results&type=Web&qry={k}'), - - // MySearch / MyWay / MyWebSearch (default: powered by Ask.com) - 'www.mysearch.com' => array('MyWebSearch', array('searchfor', 'searchFor'), 'search/Ajmain.jhtml?searchfor={k}'), - 'ms114.mysearch.com' => array('MyWebSearch'), - 'ms146.mysearch.com' => array('MyWebSearch'), - 'kf.mysearch.myway.com' => array('MyWebSearch'), - 'ki.mysearch.myway.com' => array('MyWebSearch'), - 'search.myway.com' => array('MyWebSearch'), - 'search.mywebsearch.com' => array('MyWebSearch'), - - - // Najdi - 'www.najdi.si' => array('Najdi.si', 'q', 'search.jsp?q={k}'), - - // Nate - 'search.nate.com' => array('Nate', 'q', 'search/all.html?q={k}', 'EUC-KR'), - - // Naver - 'search.naver.com' => array('Naver', 'query', 'search.naver?query={k}'), - - // Needtofind - 'ko.search.need2find.com' => array('Needtofind', 'searchfor', 'search/AJmain.jhtml?searchfor={k}'), - - // Neti - 'www.neti.ee' => array('Neti', 'query', 'cgi-bin/otsing?query={k}', 'iso-8859-1'), - - // Nifty - 'search.nifty.com' => array('Nifty', 'q', 'websearch/search?q={k}'), - - // Nigma - 'nigma.ru' => array('Nigma', 's', 'index.php?s={k}'), - - // Onet - 'szukaj.onet.pl' => array('Onet.pl', 'qt', 'query.html?qt={k}'), - - // Online.no - 'online.no' => array('Online.no', 'q', 'google/index.jsp?q={k}'), - - // Opplysningen 1881 - 'www.1881.no' => array('Opplysningen 1881', 'Query', 'Multi/?Query={k}'), - - // Orange - 'busca.orange.es' => array('Orange', 'q', 'search?q={k}'), - 'lemoteur.ke.voila.fr' => array('Orange', 'kw', '?kw={k}'), - - // Paperball - 'www.paperball.de' => array('Paperball', 'q', 'suche/s/?q={k}'), - - // PeoplePC - 'search.peoplepc.com' => array('PeoplePC', 'q', 'search?q={k}'), - - // Picsearch - 'www.picsearch.com' => array('Picsearch', 'q', 'index.cgi?q={k}'), - - // Plazoo - 'www.plazoo.com' => array('Plazoo', 'q'), - - // PlusNetwork - 'plusnetwork.com' => array('PlusNetwork', 'q', '?q={k}'), - - // Poisk.Ru - 'poisk.ru' => array('Poisk.Ru', 'text', 'cgi-bin/poisk?text={k}', 'windows-1251'), - - // qip - 'search.qip.ru' => array('qip.ru', 'query', 'search?query={k}'), - - // Qualigo - 'www.qualigo.at' => array('Qualigo', 'q'), - 'www.qualigo.ch' => array('Qualigo'), - 'www.qualigo.de' => array('Qualigo'), - 'www.qualigo.nl' => array('Qualigo'), - - // Rakuten - 'websearch.rakuten.co.jp' => array('Rakuten', 'qt', 'WebIS?qt={k}'), - - // Rambler - 'nova.rambler.ru' => array('Rambler', array('query', 'words'), 'search?query={k}'), - - // RPMFind - 'rpmfind.net' => array('rpmfind', 'query', 'linux/rpm2html/search.php?query={k}'), - 'fr2.rpmfind.net' => array('rpmfind'), - - // Road Runner Search - 'search.rr.com' => array('Road Runner', 'q', '?q={k}'), - - // Sapo - 'pesquisa.sapo.pt' => array('Sapo', 'q', '?q={k}'), - - // scour.com - 'scour.com' => array('Scour.com', '/search\/[^\/]+\/(.*)/', 'search/web/{k}'), - - // Search.com - 'www.search.com' => array('Search.com', 'q', 'search?q={k}'), - - // Search.ch - 'www.search.ch' => array('Search.ch', 'q', '?q={k}'), - - // Searchalot - 'searchalot.com' => array('Searchalot', 'q', '?q={k}'), - - // SearchCanvas - 'www.searchcanvas.com' => array('SearchCanvas', 'q', 'web?q={k}'), - - // Searchy - 'www.searchy.co.uk' => array('Searchy', 'q', 'index.html?q={k}'), - - // Setooz - // 2010-09-13: the mismatches are because subdomains are language codes - // (not country codes) - 'bg.setooz.com' => array('Setooz', 'query', 'search?query={k}'), - 'da.setooz.com' => array('Setooz'), - 'el.setooz.com' => array('Setooz'), - 'fa.setooz.com' => array('Setooz'), - 'ur.setooz.com' => array('Setooz'), - '{}.setooz.com' => array('Setooz'), - - // Seznam - 'search.seznam.cz' => array('Seznam', 'q', '?q={k}'), - - // Sharelook - 'www.sharelook.fr' => array('Sharelook', 'keyword'), - - // Skynet - 'www.skynet.be' => array('Skynet', 'q', 'services/recherche/google?q={k}'), - - // SmartAdressbar - 'search.smartaddressbar.com' => array('SmartAddressbar', 's', '?s={k}'), - - // Snap.do - 'search.snap.do' => array('Snap.do', 'q', '?q={k}'), - - // Sogou - 'www.sogou.com' => array('Sogou', 'query', 'web?query={k}', 'gb2312'), - - // Softonic - 'search.softonic.com' => array('Softonic', 'q', 'default/default?q={k}'), - - // soso.com - 'www.soso.com' => array('Soso', 'w', 'q?w={k}', 'gb2312'), - - // Startpagina - 'startgoogle.startpagina.nl' => array('Startpagina (Google)', 'q', '?q={k}'), - - // Startsiden - 'www.startsiden.no' => array('Startsiden', 'q', 'sok/index.html?q={k}'), - - // suche.info - 'suche.info' => array('Suche.info', 'Keywords', 'suche.php?Keywords={k}'), - - // Suchmaschine.com - 'www.suchmaschine.com' => array('Suchmaschine.com', 'suchstr', 'cgi-bin/wo.cgi?suchstr={k}'), - - // Suchnase - 'www.suchnase.de' => array('Suchnase', 'q'), - - // Surf Canyon - 'surfcanyon.com' => array('Surf Canyon', 'q'), - - // talimba - 'www.talimba.com' => array('talimba', 'search', 'index.php?page=search/web&search={k}'), - - // TalkTalk - 'www.talktalk.co.uk' => array('TalkTalk', 'query', 'search/results.html?query={k}'), - - // Technorati - 'technorati.com' => array('Technorati', 'q', 'search?return=sites&authority=all&q={k}'), - - // Teoma - 'www.teoma.com' => array('Teoma', 'q', 'web?q={k}'), - - // Terra -- referrer does not contain search phrase (keywords) - 'buscador.terra.es' => array('Terra', 'query', 'Default.aspx?source=Search&query={k}'), - 'buscador.terra.cl' => array('Terra'), - 'buscador.terra.com.br' => array('Terra'), - - // Tiscali - 'search.tiscali.it' => array('Tiscali', array('q', 'key'), '?q={k}'), - 'search-dyn.tiscali.it' => array('Tiscali'), - 'hledani.tiscali.cz' => array('Tiscali', 'query'), - - // Tixuma - 'www.tixuma.de' => array('Tixuma', 'sc', 'index.php?mp=search&stp=&sc={k}&tg=0'), - - // T-Online - 'suche.t-online.de' => array('T-Online', 'q', 'fast-cgi/tsc?mandant=toi&context=internet-tab&q={k}'), - 'brisbane.t-online.de' => array('T-Online'), - 'navigationshilfe.t-online.de' => array('T-Online', 'q', 'dtag/dns/results?mode=search_top&q={k}'), - - // Toolbarhome - 'www.toolbarhome.com' => array('Toolbarhome', 'q', 'search.aspx?q={k}'), - - 'vshare.toolbarhome.com' => array('Toolbarhome'), - - // Trouvez.com - 'www.trouvez.com' => array('Trouvez.com', 'query'), - - // TrovaRapido - 'www.trovarapido.com' => array('TrovaRapido', 'q', 'result.php?q={k}'), - - // Trusted-Search - 'www.trusted-search.com' => array('Trusted Search', 'w', 'search?w={k}'), - - // Twingly - 'www.twingly.com' => array('Twingly', 'q', 'search?q={k}'), - - // uol.com.br - 'busca.uol.com.br' => array('uol.com.br', 'q', '/web/?q={k}'), - - // URL.ORGanzier - 'www.url.org' => array('URL.ORGanzier', 'q', '?l=de&q={k}'), - - // Vinden - 'www.vinden.nl' => array('Vinden', 'q', '?q={k}'), - - // Vindex - 'www.vindex.nl' => array('Vindex', 'search_for', '/web?search_for={k}'), - 'search.vindex.nl' => array('Vindex'), - - // Virgilio - 'ricerca.virgilio.it' => array('Virgilio', 'qs', 'ricerca?qs={k}'), - 'ricercaimmagini.virgilio.it' => array('Virgilio'), - 'ricercavideo.virgilio.it' => array('Virgilio'), - 'ricercanews.virgilio.it' => array('Virgilio'), - 'mobile.virgilio.it' => array('Virgilio', 'qrs'), - - // Voila - 'search.ke.voila.fr' => array('Voila', 'rdata', 'S/voila?rdata={k}'), - 'www.lemoteur.fr' => array('Voila'), // uses voila search - - // Volny - 'web.volny.cz' => array('Volny', 'search', 'fulltext/?search={k}', 'windows-1250'), - - // Walhello - 'www.walhello.info' => array('Walhello', 'key', 'search?key={k}'), - 'www.walhello.com' => array('Walhello'), - 'www.walhello.de' => array('Walhello'), - 'www.walhello.nl' => array('Walhello'), - - // Web.de - 'suche.web.de' => array('Web.de', array('su', 'q'), 'search/web/?su={k}'), - - // Web.nl - 'www.web.nl' => array('Web.nl', 'zoekwoord'), - - // Weborama - 'www.weborama.fr' => array('weborama', 'QUERY'), - - // WebSearch - 'www.websearch.com' => array('WebSearch', array('qkw', 'q'), 'search/results2.aspx?q={k}'), - - // Wedoo - // 2011-02-15 - keyword no longer appears to be in Referrer URL; candidate for removal? - 'fr.wedoo.com' => array('Wedoo', 'keyword'), - 'en.wedoo.com' => array('Wedoo'), - 'es.wedoo.com' => array('Wedoo'), - - // Winamp (Enhanced by Google) - 'search.winamp.com' => array('Winamp', 'q', 'search/search?q={k}'), - - // Witch - 'www.witch.de' => array('Witch', 'search', 'search-result.php?cn=0&search={k}'), - - // Wirtualna Polska - 'szukaj.wp.pl' => array('Wirtualna Polska', 'szukaj', 'http://szukaj.wp.pl/szukaj.html?szukaj={k}'), - - // WWW - 'search.www.ee' => array('www värav', 'query'), - - // X-recherche - 'www.x-recherche.com' => array('X-Recherche', 'MOTS', 'cgi-bin/websearch?MOTS={k}'), - - // Yahoo - 'search.yahoo.com' => array('Yahoo!', array('p', 'q'), 'search?p={k}'), -// '*.search.yahoo.com' => array('Yahoo!'), // see built-in helper in Common.php - 'yahoo.com' => array('Yahoo!'), - 'yahoo.{}' => array('Yahoo!'), - '{}.yahoo.com' => array('Yahoo!'), - 'cade.yahoo.com' => array('Yahoo!'), - 'espanol.yahoo.com' => array('Yahoo!'), - 'qc.yahoo.com' => array('Yahoo!'), - 'one.cn.yahoo.com' => array('Yahoo!'), - 'video.search.yahoo.co.jp' => array('Yahoo!'), - 'image.search.yahoo.co.jp' => array('Yahoo!'), - - // Powered by Yahoo APIs - 'www.cercato.it' => array('Yahoo!', 'q'), - 'search.offerbox.com' => array('Yahoo!', 'q'), - 'www.benefind.de' => array('Yahoo!', 'q'), - - // Powered by Yahoo! Search Marketing (Overture) - 'ys.mirostart.com' => array('Yahoo!', 'q'), - - // Yahoo! Directory - 'search.yahoo.com/search/dir' => array('Yahoo! Directory', 'p', '?p={k}'), -// '{}.dir.yahoo.com' => array('Yahoo! Directory'), - - // Yahoo! Images - 'images.search.yahoo.com' => array('Yahoo! Images', 'p', 'search/images?p={k}'), -// '*.images.search.yahoo.com'=> array('Yahoo! Images'), // see built-in helper in Common.php - '{}.images.yahoo.com' => array('Yahoo! Images'), - 'cade.images.yahoo.com' => array('Yahoo! Images'), - 'espanol.images.yahoo.com' => array('Yahoo! Images'), - 'qc.images.yahoo.com' => array('Yahoo! Images'), - - // Yam - 'search.yam.com' => array('Yam', 'k', 'Search/Web/?SearchType=web&k={k}'), - - // Yandex - 'yandex.ru' => array('Yandex', 'text', 'yandsearch?text={k}'), - 'yandex.com' => array('Yandex'), - 'yandex.{}' => array('Yandex'), - - // Yandex Images - 'images.yandex.ru' => array('Yandex Images', 'text', 'yandsearch?text={k}'), - 'images.yandex.com' => array('Yandex Images'), - 'images.yandex.{}' => array('Yandex Images'), - - // Yasni - 'www.yasni.de' => array('Yasni', 'query'), - 'www.yasni.com' => array('Yasni'), - 'www.yasni.co.uk' => array('Yasni'), - 'www.yasni.ch' => array('Yasni'), - 'www.yasni.at' => array('Yasni'), - - // Yatedo - 'www.yatedo.com' => array('Yatedo', 'q', 'search/profil?q={k}'), - 'www.yatedo.fr' => array('Yatedo'), - - // Yellowmap - 'yellowmap.de' => array('Yellowmap', ' '), - - // Yippy - 'search.yippy.com' => array('Yippy', 'query', 'search?query={k}'), - - // YouGoo - 'www.yougoo.fr' => array('YouGoo', 'q', '?cx=search&q={k}'), - - // Zapmeta - 'www.zapmeta.com' => array('Zapmeta', array('q', 'query'), '?q={k}'), - 'www.zapmeta.nl' => array('Zapmeta'), - 'www.zapmeta.de' => array('Zapmeta'), - 'uk.zapmeta.com' => array('Zapmeta'), - - // Zoek - 'www3.zoek.nl' => array('Zoek', 'q'), - - // Zhongsou - 'p.zhongsou.com' => array('Zhongsou', 'w', 'p?w={k}'), - - // Zoeken - 'www.zoeken.nl' => array('Zoeken', 'q', '?q={k}'), - - // Zoohoo - 'zoohoo.cz' => array('Zoohoo', 'q', '?q={k}', 'windows-1250'), - - // Zoznam - 'www.zoznam.sk' => array('Zoznam', 's', 'hladaj.fcgi?s={k}&co=svet'), - ); -} diff --git a/www/analytics/core/DataFiles/Socials.php b/www/analytics/core/DataFiles/Socials.php deleted file mode 100755 index ebe28da7..00000000 --- a/www/analytics/core/DataFiles/Socials.php +++ /dev/null @@ -1,226 +0,0 @@ - 'Facebook', - 'fb.me' => 'Facebook', - - // Ozone - 'qzone.qq.com' => 'Qzone', - - // Haboo - 'habbo.com' => 'Haboo', - - // Twitter - 'twitter.com' => 'Twitter', - 't.co' => 'Twitter', - - // Renren - 'renren.com' => 'Renren', - - // Windows Live Spaces - 'login.live.com' => 'Windows Live Spaces', - - // LinkedIn - 'linkedin.com' => 'LinkedIn', - - // Bebo - 'bebo.com' => 'Bebo', - - // Vkontakte - 'vk.com' => 'Vkontakte', - 'vkontakte.ru' => 'Vkontakte', - - // Tagged - 'login.tagged.com' => 'Tagged', - - // Orkut - 'orkut.com' => 'Orkut', - - // Myspace - 'myspace.com' => 'Myspace', - - // Frinedster - 'friendster.com' => 'Friendster', - - // Badoo - 'badoo.com' => 'Badoo', - - // hi5 - 'hi5.com' => 'hi5', - - // Netlog - 'netlog.com' => 'Netlog', - - // Flixster - 'flixster.com' => 'Flixster', - - // MyLife - 'mylife.ru' => 'MyLife', - - // Classmates.com - 'classmates.com' => 'Classmates.com', - - // Github - 'github.com' => 'Github', - - // Google+ - 'url.google.com' => 'Google%2B', - - // douban - 'douban.com' => 'douban', - - // Odnoklassniki - 'odnoklassniki.ru' => 'Odnoklassniki', - - // Viadeo - 'viadeo.com' => 'Viadeo', - - // Flickr - 'flickr.com' => 'Flickr', - - // WeeWorld - 'weeworld.com' => 'WeeWorld', - - // Last.fm - 'last.fm' => 'Last.fm', - 'lastfm.ru' => 'Last.fm', - 'lastfm.de' => 'Last.fm', - 'lastfm.es' => 'Last.fm', - 'lastfm.fr' => 'Last.fm', - 'lastfm.it' => 'Last.fm', - 'lastfm.jp' => 'Last.fm', - 'lastfm.pl' => 'Last.fm', - 'lastfm.com.br' => 'Last.fm', - 'lastfm.se' => 'Last.fm', - 'lastfm.com.tr' => 'Last.fm', - - // MyHeritage - 'myheritage.com' => 'MyHeritage', - - // Xanga - 'xanga.com' => 'Xanga', - - // Mixi - 'mixi.jp' => 'Mixi', - - // Cyworld - 'global.cyworld.com' => 'Cyworld', - - // Gaia Online - 'gaiaonline.com' => 'Gaia Online', - - // Skyrock - 'skyrock.com' => 'Skyrock', - - // BlackPlanet - 'blackplanet.com' => 'BlackPlanet', - - // myYearbook - 'myyearbook.com' => 'myYearbook', - - // Fotolog - 'fotolog.com' => 'Fotolog', - - // Friends Reunited - 'friendsreunited.com' => 'Friends Reunited', - - // LiveJournal - 'livejournal.ru' => 'LiveJournal', - 'livejournal.com' => 'LiveJournal', - - // StudiVZ/MeinVZ - 'studivz.net' => 'StudiVZ', - 'meinvz.net' => 'MeinVZ', - - // StackOverflow - 'stackoverflow.com' => 'StackOverflow', - - // Sonico.com - 'sonico.com' => 'Sonico.com', - - // Pinterest - 'pinterest.com' => 'Pinterest', - - // Plaxo - 'plaxo.com' => 'Plaxo', - - // Geni.com - 'geni.com' => 'Geni.com', - - // Tuenti - 'tuenti.com' => 'Tuenti', - - // XING - 'xing.com' => 'XING', - - // Taringa! - 'taringa.net' => 'Taringa!', - - // Nasza-klasa.pl - 'nk.pl' => 'Nasza-klasa.pl', - - // StumbleUpon - 'stumbleupon.com' => 'StumbleUpon', - - // Sourceforge - 'sourceforge.net' => 'SourceForge', - - // Hyves - 'hyves.nl' => 'Hyves', - - // WAYN - 'wayn.com' => 'WAYN', - - // Buzznet - 'buzznet.com' => 'Buzznet', - - // Multiply - 'multiply.com' => 'Multiply', - - // Foursquare - 'foursquare.com' => 'Foursquare', - - // vkrugudruzei.ru - 'vkrugudruzei.ru' => 'vkrugudruzei.ru', - - // my.mail.ru - 'my.mail.ru' => 'my.mail.ru', - - //MoiKrug.ru - 'moikrug.ru' => 'moikrug.ru', - - // Reddit - 'reddit.com' => 'reddit', - - // HackerNews - 'news.ycombinator.com' => 'Hacker News', - - // Identi.ca - 'identi.ca' => 'identi.ca', - - // Weibo - 'weibo.com' => 'Weibo', - 't.cn' => 'Weibo', - - // YouTube - 'youtube.com' => 'YouTube', - 'youtu.be' => 'YouTube', - - // Vimeo - 'vimeo.com' => 'Vimeo', - - //tumblr - 'tumblr.com' => 'tumblr', - ); -} diff --git a/www/analytics/core/DataFiles/cacert.pem b/www/analytics/core/DataFiles/cacert.pem new file mode 100644 index 00000000..27078e45 --- /dev/null +++ b/www/analytics/core/DataFiles/cacert.pem @@ -0,0 +1,3981 @@ +## +## Bundle of CA Root Certificates +## +## Certificate data from Mozilla as of: Wed Oct 28 04:12:04 2015 +## +## This is a bundle of X.509 certificates of public Certificate Authorities +## (CA). These were automatically extracted from Mozilla's root certificates +## file (certdata.txt). This file can be found in the mozilla source tree: +## http://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt +## +## It contains the certificates in PEM format and therefore +## can be directly used with curl / libcurl / php_curl, or with +## an Apache+mod_ssl webserver for SSL client authentication. +## Just configure this file as the SSLCACertificateFile. +## +## Conversion done with mk-ca-bundle.pl version 1.25. +## SHA1: 6d7d2f0a4fae587e7431be191a081ac1257d300a +## + +Let’s Encrypt Authority X1 +========================== +-----BEGIN CERTIFICATE----- +MIIEqDCCA5CgAwIBAgIRAJgT9HUT5XULQ+dDHpceRL0wDQYJKoZIhvcNAQELBQAw +PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD +Ew5EU1QgUm9vdCBDQSBYMzAeFw0xNTEwMTkyMjMzMzZaFw0yMDEwMTkyMjMzMzZa +MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD +ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAJzTDPBa5S5Ht3JdN4OzaGMw6tc1Jhkl4b2+NfFwki+3uEtB +BaupnjUIWOyxKsRohwuj43Xk5vOnYnG6eYFgH9eRmp/z0HhncchpDpWRz/7mmelg +PEjMfspNdxIknUcbWuu57B43ABycrHunBerOSuu9QeU2mLnL/W08lmjfIypCkAyG +dGfIf6WauFJhFBM/ZemCh8vb+g5W9oaJ84U/l4avsNwa72sNlRZ9xCugZbKZBDZ1 +gGusSvMbkEl4L6KWTyogJSkExnTA0DHNjzE4lRa6qDO4Q/GxH8Mwf6J5MRM9LTb4 +4/zyM2q5OTHFr8SNDR1kFjOq+oQpttQLwNh9w5MCAwEAAaOCAZIwggGOMBIGA1Ud +EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMH8GCCsGAQUFBwEBBHMwcTAy +BggrBgEFBQcwAYYmaHR0cDovL2lzcmcudHJ1c3RpZC5vY3NwLmlkZW50cnVzdC5j +b20wOwYIKwYBBQUHMAKGL2h0dHA6Ly9hcHBzLmlkZW50cnVzdC5jb20vcm9vdHMv +ZHN0cm9vdGNheDMucDdjMB8GA1UdIwQYMBaAFMSnsaR7LHH62+FLkHX/xBVghYkQ +MFQGA1UdIARNMEswCAYGZ4EMAQIBMD8GCysGAQQBgt8TAQEBMDAwLgYIKwYBBQUH +AgEWImh0dHA6Ly9jcHMucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcwPAYDVR0fBDUw +MzAxoC+gLYYraHR0cDovL2NybC5pZGVudHJ1c3QuY29tL0RTVFJPT1RDQVgzQ1JM +LmNybDATBgNVHR4EDDAKoQgwBoIELm1pbDAdBgNVHQ4EFgQUqEpqYwR93brm0Tm3 +pkVl7/Oo7KEwDQYJKoZIhvcNAQELBQADggEBANHIIkus7+MJiZZQsY14cCoBG1hd +v0J20/FyWo5ppnfjL78S2k4s2GLRJ7iD9ZDKErndvbNFGcsW+9kKK/TnY21hp4Dd +ITv8S9ZYQ7oaoqs7HwhEMY9sibED4aXw09xrJZTC9zK1uIfW6t5dHQjuOWv+HHoW +ZnupyxpsEUlEaFb+/SCI4KCSBdAsYxAcsHYI5xxEI4LutHp6s3OT2FuO90WfdsIk +6q78OMSdn875bNjdBYAqxUp2/LEIHfDBkLoQz0hFJmwAbYahqKaLn73PAAm1X2kj +f1w8DdnkabOLGeOVcj9LQ+s67vBykx4anTjURkbqZslUEUsn2k5xeua2zUk= +-----END CERTIFICATE----- + +Equifax Secure CA +================= +-----BEGIN CERTIFICATE----- +MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJVUzEQMA4GA1UE +ChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoT +B0VxdWlmYXgxLTArBgNVBAsTJEVxdWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCB +nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPR +fM6fBeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+AcJkVV5MW +8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kCAwEAAaOCAQkwggEFMHAG +A1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UE +CxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoG +A1UdEAQTMBGBDzIwMTgwODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvS +spXXR9gjIBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQFMAMB +Af8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GBAFjOKer89961 +zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y7qj/WsjTVbJmcVfewCHrPSqnI0kB +BIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee95 +70+sB3c4 +-----END CERTIFICATE----- + +GlobalSign Root CA +================== +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx +GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds +b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD +VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa +DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc +THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb +Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP +c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX +gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF +AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj +Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG +j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH +hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC +X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- + +GlobalSign Root CA - R2 +======================= +-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UECxMXR2xv +YmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh +bFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT +aWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln +bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6 +ErPLv4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8eoLrvozp +s6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklqtTleiDTsvHgMCJiEbKjN +S7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzdC9XZzPnqJworc5HGnRusyMvo4KD0L5CL +TfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pazq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6C +ygPCm48CAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQUm+IHV2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5nbG9i +YWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG3lm0mi3f3BmGLjAN +BgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4GsJ0/WwbgcQ3izDJr86iw8bmEbTUsp +9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu +01yiPqFbQfXf5WRDLenVOavSot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG7 +9G+dwfCMNYxdAfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 +TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority - G3 +============================================================ +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv +cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy +dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAMu6nFL8eB8aHm8bN3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1 +EUGO+i2tKmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGukxUc +cLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBmCC+Vk7+qRy+oRpfw +EuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJXwzw3sJ2zq/3avL6QaaiMxTJ5Xpj +055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWuimi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA +ERSWwauSCPc/L8my/uRan2Te2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5f +j267Cz3qWhMeDGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC +/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565pF4ErWjfJXir0 +xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGtTxzhT5yvDwyd93gN2PQ1VoDa +t20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== +-----END CERTIFICATE----- + +Verisign Class 4 Public Primary Certification Authority - G3 +============================================================ +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv +cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy +dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAK3LpRFpxlmr8Y+1GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaS +tBO3IFsJ+mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0GbdU6LM +8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLmNxdLMEYH5IBtptiW +Lugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XYufTsgsbSPZUd5cBPhMnZo0QoBmrX +Razwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA +j/ola09b5KROJ1WrIhVZPMq1CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXtt +mhwwjIDLk5Mqg6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm +fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c2NU8Qh0XwRJd +RTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/bLvSHgCwIe34QWKCudiyxLtG +UPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg== +-----END CERTIFICATE----- + +Entrust.net Premium 2048 Secure Server CA +========================================= +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u +ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp +bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV +BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx +NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3 +d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl +MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u +ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL +Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr +hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW +nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi +VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ +KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy +T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT +J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e +nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE= +-----END CERTIFICATE----- + +Baltimore CyberTrust Root +========================= +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE +ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li +ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC +SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs +dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME +uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB +UnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C +G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9 +XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr +l3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI +VDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB +BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRh +cL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5 +hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa +Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H +RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- + +AddTrust Low-Value Services Root +================================ +-----BEGIN CERTIFICATE----- +MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRU +cnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMwMTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQsw +CQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBO +ZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ulCDtbKRY6 +54eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6ntGO0/7Gcrjyvd7ZWxbWr +oulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyldI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1 +Zmne3yzxbrww2ywkEtvrNTVokMsAsJchPXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJui +GMx1I4S+6+JNM3GOGvDC+Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8w +HQYDVR0OBBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8EBTAD +AQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBlMQswCQYDVQQGEwJT +RTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEw +HwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxt +ZBsfzQ3duQH6lmM0MkhHma6X7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0Ph +iVYrqW9yTkkz43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY +eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJlpz/+0WatC7xr +mYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOAWiFeIc9TVPC6b4nbqKqVz4vj +ccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk= +-----END CERTIFICATE----- + +AddTrust External Root +====================== +-----BEGIN CERTIFICATE----- +MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYD +VQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEw +NDgzOFowbzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRU +cnVzdCBFeHRlcm5hbCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0Eg +Um9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvtH7xsD821 ++iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9uMq/NzgtHj6RQa1wVsfw +Tz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzXmk6vBbOmcZSccbNQYArHE504B4YCqOmo +aSYYkKtMsE8jqzpPhNjfzp/haW+710LXa0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy +2xSoRcRdKn23tNbE7qzNE0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv7 +7+ldU9U0WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYDVR0P +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0Jvf6xCZU7wO94CTL +VBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRk +VHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENB +IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZl +j7DYd7usQWxHYINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 +6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvCNr4TDea9Y355 +e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEXc4g/VhsxOBi0cQ+azcgOno4u +G+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5amnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= +-----END CERTIFICATE----- + +AddTrust Public Services Root +============================= +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSAwHgYDVQQDExdBZGRU +cnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAxMDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJ +BgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5l +dHdvcmsxIDAeBgNVBAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV6tsfSlbu +nyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nXGCwwfQ56HmIexkvA/X1i +d9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnPdzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSG +Aa2Il+tmzV7R/9x98oTaunet3IAIx6eH1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAw +HM+A+WD+eeSI8t0A65RF62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0G +A1UdDgQWBBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDELMAkGA1UEBhMCU0Ux +FDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29yazEgMB4G +A1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4 +JNojVhaTdt02KLmuG7jD8WS6IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL ++YPoRNWyQSW/iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao +GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh4SINhwBk/ox9 +Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQmXiLsks3/QppEIW1cxeMiHV9H +EufOX1362KqxMy3ZdvJOOjMMK7MtkAY= +-----END CERTIFICATE----- + +AddTrust Qualified Certificates Root +==================================== +-----BEGIN CERTIFICATE----- +MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSMwIQYDVQQDExpBZGRU +cnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcx +CzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQ +IE5ldHdvcmsxIzAhBgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwqxBb/4Oxx +64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G87B4pfYOQnrjfxvM0PC3 +KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i2O+tCBGaKZnhqkRFmhJePp1tUvznoD1o +L/BLcHwTOK28FSXx1s6rosAx1i+f4P8UWfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GR +wVY18BTcZTYJbqukB8c10cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HU +MIHRMB0GA1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6FrpGkwZzELMAkGA1UE +BhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29y +azEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlmaWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQAD +ggEBABmrder4i2VhlRO6aQTvhsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxG +GuoYQ992zPlmhpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X +dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3P6CxB9bpT9ze +RXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9YiQBCYz95OdBEsIJuQRno3eDB +iFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5noxqE= +-----END CERTIFICATE----- + +Entrust Root Certification Authority +==================================== +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw +b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG +A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0 +MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu +MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu +Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v +dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz +A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww +Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68 +j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN +rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1 +MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH +hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM +Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa +v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS +W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0 +tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- + +RSA Security 2048 v3 +==================== +-----BEGIN CERTIFICATE----- +MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6MRkwFwYDVQQK +ExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJpdHkgMjA0OCBWMzAeFw0wMTAy +MjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAXBgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAb +BgNVBAsTFFJTQSBTZWN1cml0eSAyMDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAt49VcdKA3XtpeafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7 +Jylg/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGlwSMiuLgb +WhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnhAMFRD0xS+ARaqn1y07iH +KrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP ++Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpuAWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/ +MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4E +FgQUB8NRMKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYcHnmY +v/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/Zb5gEydxiKRz44Rj +0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+f00/FGj1EVDVwfSQpQgdMWD/YIwj +VAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVOrSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395 +nzIlQnQFgCi/vcEkllgVsRch6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kA +pKnXwiJPZ9d37CAFYd4= +-----END CERTIFICATE----- + +GeoTrust Global CA +================== +-----BEGIN CERTIFICATE----- +MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQK +Ew1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0EwHhcNMDIwNTIxMDQw +MDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j +LjEbMBkGA1UEAxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjo +BbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDviS2Aelet +8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU1XupGc1V3sjs0l44U+Vc +T4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagU +vTLrGAMoUgRx5aszPeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVk +DBF9qn1luMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKInZ57Q +zxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfStQWVYrmm3ok9Nns4 +d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcFPseKUgzbFbS9bZvlxrFUaKnjaZC2 +mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Unhw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6p +XE0zX5IJL4hmXXeXxx12E6nV5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvm +Mw== +-----END CERTIFICATE----- + +GeoTrust Global CA 2 +==================== +-----BEGIN CERTIFICATE----- +MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN +R2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwHhcNMDQwMzA0MDUw +MDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j +LjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDvPE1APRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/ +NTL8Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hLTytCOb1k +LUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL5mkWRxHCJ1kDs6ZgwiFA +Vvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7S4wMcoKK+xfNAGw6EzywhIdLFnopsk/b +HdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNH +K266ZUapEBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6tdEPx7 +srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv/NgdRN3ggX+d6Yvh +ZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywNA0ZF66D0f0hExghAzN4bcLUprbqL +OzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkC +x1YAzUm5s2x7UwQa4qjJqhIFI8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqF +H4z1Ir+rzoPz4iIprn2DQKi6bA== +-----END CERTIFICATE----- + +GeoTrust Universal CA +===================== +-----BEGIN CERTIFICATE----- +MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN +R2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVyc2FsIENBMB4XDTA0MDMwNDA1 +MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu +Yy4xHjAcBgNVBAMTFUdlb1RydXN0IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAKYVVaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9t +JPi8cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTTQjOgNB0e +RXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFhF7em6fgemdtzbvQKoiFs +7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2vc7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d +8Lsrlh/eezJS/R27tQahsiFepdaVaH/wmZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7V +qnJNk22CDtucvc+081xdVHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3Cga +Rr0BHdCXteGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZf9hB +Z3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfReBi9Fi1jUIxaS5BZu +KGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+nhutxx9z3SxPGWX9f5NAEC7S8O08 +ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0 +XG0D08DYj3rWMB8GA1UdIwQYMBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIB +hjANBgkqhkiG9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc +aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fXIwjhmF7DWgh2 +qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzynANXH/KttgCJwpQzgXQQpAvvL +oJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0zuzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsK +xr2EoyNB3tZ3b4XUhRxQ4K5RirqNPnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxF +KyDuSN/n3QmOGKjaQI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2 +DFKWkoRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9ER/frslK +xfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQtDF4JbAiXfKM9fJP/P6EU +p8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/SfuvmbJxPgWp6ZKy7PtXny3YuxadIwVyQD8vI +P/rmMuGNG2+k5o7Y+SlIis5z/iw= +-----END CERTIFICATE----- + +GeoTrust Universal CA 2 +======================= +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN +R2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwHhcNMDQwMzA0 +MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3Qg +SW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0 +DE81WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUGFF+3Qs17 +j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdqXbboW0W63MOhBW9Wjo8Q +JqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxLse4YuU6W3Nx2/zu+z18DwPw76L5GG//a +QMJS9/7jOvdqdzXQ2o3rXhhqMcceujwbKNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2 +WP0+GfPtDCapkzj4T8FdIgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP +20gaXT73y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRthAAn +ZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgocQIgfksILAAX/8sgC +SqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4Lt1ZrtmhN79UNdxzMk+MBB4zsslG +8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2 ++/CfXGJx7Tz0RzgQKzAfBgNVHSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8E +BAMCAYYwDQYJKoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z +dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQL1EuxBRa3ugZ +4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgrFg5fNuH8KrUwJM/gYwx7WBr+ +mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSoag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpq +A1Ihn0CoZ1Dy81of398j9tx4TuaYT1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpg +Y+RdM4kX2TGq2tbzGDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiP +pm8m1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJVOCiNUW7d +FGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH6aLcr34YEoP9VhdBLtUp +gn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwXQMAJKOSLakhT2+zNVVXxxvjpoixMptEm +X36vWkzaH6byHCx+rgIW0lbQL1dTR+iS +-----END CERTIFICATE----- + +Visa eCommerce Root +=================== +-----BEGIN CERTIFICATE----- +MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQG +EwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2Ug +QXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2 +WhcNMjIwNjI0MDAxNjEyWjBrMQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMm +VmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv +bW1lcmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h2mCxlCfL +F9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4ElpF7sDPwsRROEW+1QK8b +RaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdVZqW1LS7YgFmypw23RuwhY/81q6UCzyr0 +TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI +/k4+oKsGGelT84ATB+0tvz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzs +GHxBvfaLdXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG +MB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUFAAOCAQEAX/FBfXxc +CLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcRzCSs00Rsca4BIGsDoo8Ytyk6feUW +YFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pz +zkWKsKZJ/0x9nXGIxHYdkFsd7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBu +YQa7FkKMcPcw++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt +398znM/jra6O1I7mT1GvFpLgXPYHDw== +-----END CERTIFICATE----- + +Certum Root CA +============== +-----BEGIN CERTIFICATE----- +MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQK +ExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQTAeFw0wMjA2MTExMDQ2Mzla +Fw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8u +by4xEjAQBgNVBAMTCUNlcnR1bSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6x +wS7TT3zNJc4YPk/EjG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdL +kKWoePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GIULdtlkIJ +89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapuOb7kky/ZR6By6/qmW6/K +Uz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUgAKpoC6EahQGcxEZjgoi2IrHu/qpGWX7P +NSzVttpd90gzFFS269lvzs2I1qsb2pY7HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkq +hkiG9w0BAQUFAAOCAQEAuI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+ +GXYkHAQaTOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTgxSvg +GrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1qCjqTE5s7FCMTY5w/ +0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5xO/fIR/RpbxXyEV6DHpx8Uq79AtoS +qFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs6GAqm4VKQPNriiTsBhYscw== +-----END CERTIFICATE----- + +Comodo AAA Services root +======================== +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS +R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg +TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw +MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl +c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG +C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M/vg4aijJRPn2jymJBGhCfHdr/jzDUs +i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW +Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH +Ypy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK +Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f +BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl +cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz +LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm +7l3sAg9g1o1QGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z +8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C +12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- + +Comodo Secure Services root +=========================== +-----BEGIN CERTIFICATE----- +MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS +R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg +TGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAw +MDAwMFoXDTI4MTIzMTIzNTk1OVowfjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFu +Y2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAi +BgNVBAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPMcm3ye5drswfxdySRXyWP +9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3SHpR7LZQdqnXXs5jLrLxkU0C8j6ysNstc +rbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rC +oznl2yY4rYsK7hljxxwk3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3V +p6ea5EQz6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNVHQ4E +FgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w +gYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL1NlY3VyZUNlcnRpZmlj +YXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRwOi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlm +aWNhdGVTZXJ2aWNlcy5jcmwwDQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm +4J4oqF7Tt/Q05qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj +Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtIgKvcnDe4IRRL +DXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJaD61JlfutuC23bkpgHl9j6Pw +pCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDlizeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1H +RR3B7Hzs/Sk= +-----END CERTIFICATE----- + +Comodo Trusted Services root +============================ +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS +R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg +TGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEw +MDAwMDBaFw0yODEyMzEyMzU5NTlaMH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1h +bmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUw +IwYDVQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWWfnJSoBVC21ndZHoa0Lh7 +3TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMtTGo87IvDktJTdyR0nAducPy9C1t2ul/y +/9c3S0pgePfw+spwtOpZqqPOSC+pw7ILfhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6 +juljatEPmsbS9Is6FARW1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsS +ivnkBbA7kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0GA1Ud +DgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21vZG9jYS5jb20vVHJ1c3RlZENlcnRp +ZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRodHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENl +cnRpZmljYXRlU2VydmljZXMuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8Ntw +uleGFTQQuS9/HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32 +pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxISjBc/lDb+XbDA +BHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+xqFx7D+gIIxmOom0jtTYsU0l +R+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/AtyjcndBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O +9y5Xt5hwXsjEeLBi +-----END CERTIFICATE----- + +QuoVadis Root CA +================ +-----BEGIN CERTIFICATE----- +MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJCTTEZMBcGA1UE +ChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 +eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAz +MTkxODMzMzNaFw0yMTAzMTcxODMzMzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRp +cyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQD +EyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Ypli4kVEAkOPcahdxYTMuk +J0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2DrOpm2RgbaIr1VxqYuvXtdj182d6UajtL +F8HVj71lODqV0D1VNk7feVcxKh7YWWVJWCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeL +YzcS19Dsw3sgQUSj7cugF+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWen +AScOospUxbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCCAk4w +PQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVvdmFkaXNvZmZzaG9y +ZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREwggENMIIBCQYJKwYBBAG+WAABMIH7 +MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNlIG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmlj +YXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJs +ZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh +Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYIKwYBBQUHAgEW +Fmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3TKbkGGew5Oanwl4Rqy+/fMIGu +BgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rqy+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkw +FwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6 +tlCLMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSkfnIYj9lo +fFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf87C9TqnN7Az10buYWnuul +LsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1RcHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2x +gI4JVrmcGmD+XcHXetwReNDWXcG31a0ymQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi +5upZIof4l/UO/erMkqQWxFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi +5nrQNiOKSnQ2+Q== +-----END CERTIFICATE----- + +QuoVadis Root CA 2 +================== +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx +ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6 +XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk +lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB +lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy +lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt +66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn +wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh +D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy +BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie +J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud +DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU +a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv +Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3 +UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm +VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK ++JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW +IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1 +WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X +f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II +4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8 +VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- + +QuoVadis Root CA 3 +================== +-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx +OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg +DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij +KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K +DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv +BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp +p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8 +nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX +MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM +Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz +uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT +BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj +YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB +BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD +VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4 +ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE +AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV +qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s +hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z +POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2 +Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp +8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC +bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu +g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p +vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr +qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE----- + +Security Communication Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP +U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw +HhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP +U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw +8yl89f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJDKaVv0uM +DPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9Ms+k2Y7CI9eNqPPYJayX +5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/NQV3Is00qVUarH9oe4kA92819uZKAnDfd +DJZkndwi92SL32HeFZRSFaB9UslLqCHJxrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2 +JChzAgMBAAGjPzA9MB0GA1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vGkl3g +0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfrUj94nK9NrvjVT8+a +mCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5Bw+SUEmK3TGXX8npN6o7WWWXlDLJ +s58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ +6rBK+1YWc26sTfcioU+tHXotRSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAi +FL39vmwLAw== +-----END CERTIFICATE----- + +Sonera Class 2 Root CA +====================== +-----BEGIN CERTIFICATE----- +MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEPMA0GA1UEChMG +U29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAxMDQwNjA3Mjk0MFoXDTIxMDQw +NjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNVBAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJh +IENsYXNzMiBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3 +/Ei9vX+ALTU74W+oZ6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybT +dXnt5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s3TmVToMG +f+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2EjvOr7nQKV0ba5cTppCD8P +tOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu8nYybieDwnPz3BjotJPqdURrBGAgcVeH +nfO+oJAjPYok4doh28MCAwEAAaMzMDEwDwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITT +XjwwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt +0jSv9zilzqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/3DEI +cbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvDFNr450kkkdAdavph +Oe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6Tk6ezAyNlNzZRZxe7EJQY670XcSx +EtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLH +llpwrN9M +-----END CERTIFICATE----- + +Staat der Nederlanden Root CA +============================= +-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJOTDEeMBwGA1UE +ChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFhdCBkZXIgTmVkZXJsYW5kZW4g +Um9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEyMTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4w +HAYDVQQKExVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxh +bmRlbiBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFt +vsznExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw719tV2U02P +jLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MOhXeiD+EwR+4A5zN9RGca +C1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+UtFE5A3+y3qcym7RHjm+0Sq7lr7HcsBth +vJly3uSJt3omXdozSVtSnA71iq3DuD3oBmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn6 +22r+I/q85Ej0ZytqERAhSQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRV +HSAAMDwwOgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMvcm9v +dC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA7Jbg0zTBLL9s+DAN +BgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k/rvuFbQvBgwp8qiSpGEN/KtcCFtR +EytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzmeafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbw +MVcoEoJz6TMvplW0C5GUR5z6u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3y +nGQI0DvDKcWy7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR +iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw== +-----END CERTIFICATE----- + +UTN DATACorp SGC Root CA +======================== +-----BEGIN CERTIFICATE----- +MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCBkzELMAkGA1UE +BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl +IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZ +BgNVBAMTElVUTiAtIERBVEFDb3JwIFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBa +MIGTMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4w +HAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRy +dXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ys +raP6LnD43m77VkIVni5c7yPeIbkFdicZD0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlo +wHDyUwDAXlCCpVZvNvlK4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA +9P4yPykqlXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulWbfXv +33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQABo4GrMIGoMAsGA1Ud +DwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRTMtGzz3/64PGgXYVOktKeRR20TzA9 +BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dD +LmNybDAqBgNVHSUEIzAhBggrBgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3 +DQEBBQUAA4IBAQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft +Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyjj98C5OBxOvG0 +I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVHKWss5nbZqSl9Mt3JNjy9rjXx +EZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwP +DPafepE39peC4N1xaf92P2BNPM/3mfnGV/TJVTl4uix5yaaIK/QI +-----END CERTIFICATE----- + +UTN USERFirst Hardware Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCBlzELMAkGA1UE +BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl +IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAd +BgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgx +OTIyWjCBlzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0 +eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVz +ZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlI +wrthdBKWHTxqctU8EGc6Oe0rE81m65UJM6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFd +tqdt++BxF2uiiPsA3/4aMXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8 +i4fDidNdoI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqIDsjf +Pe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9KsyoUhbAgMBAAGjgbkw +gbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKFyXyYbKJhDlV0HN9WF +lp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNF +UkZpcnN0LUhhcmR3YXJlLmNybDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUF +BwMGBggrBgEFBQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM +//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28GpgoiskliCE7/yMgUsogW +XecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gECJChicsZUN/KHAG8HQQZexB2 +lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kn +iCrVWFCVH/A7HFe7fRQ5YiuayZSSKqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67 +nfhmqA== +-----END CERTIFICATE----- + +Camerfirma Chambers of Commerce Root +==================================== +-----BEGIN CERTIFICATE----- +MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe +QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i +ZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAx +NjEzNDNaFw0zNzA5MzAxNjEzNDRaMH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZp +cm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3Jn +MSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0BAQEFAAOC +AQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtbunXF/KGIJPov7coISjlU +xFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0dBmpAPrMMhe5cG3nCYsS4No41XQEMIwRH +NaqbYE6gZj3LJgqcQKH0XZi/caulAGgq7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jW +DA+wWFjbw2Y3npuRVDM30pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFV +d9oKDMyXroDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIGA1Ud +EwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5jaGFtYmVyc2lnbi5v +cmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p26EpW1eLTXYGduHRooowDgYDVR0P +AQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hh +bWJlcnNpZ24ub3JnMCcGA1UdEgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYD +VR0gBFEwTzBNBgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz +aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEBAAxBl8IahsAi +fJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZdp0AJPaxJRUXcLo0waLIJuvvD +L8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wN +UPf6s+xCX6ndbcj0dc97wXImsQEcXCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/n +ADydb47kMgkdTXg0eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1 +erfutGWaIZDgqtCYvDi1czyL+Nw= +-----END CERTIFICATE----- + +Camerfirma Global Chambersign Root +================================== +-----BEGIN CERTIFICATE----- +MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe +QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i +ZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYx +NDE4WhcNMzcwOTMwMTYxNDE4WjB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJt +YSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEg +MB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAw +ggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0Mi+ITaFgCPS3CU6gSS9J +1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/sQJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8O +by4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpVeAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl +6DJWk0aJqCWKZQbua795B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c +8lCrEqWhz0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0TAQH/ +BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1iZXJzaWduLm9yZy9j +aGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4wTcbOX60Qq+UDpfqpFDAOBgNVHQ8B +Af8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAHMCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBj +aGFtYmVyc2lnbi5vcmcwKgYDVR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9y +ZzBbBgNVHSAEVDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh +bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0BAQUFAAOCAQEA +PDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUMbKGKfKX0j//U2K0X1S0E0T9Y +gOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXiryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJ +PJ7oKXqJ1/6v/2j1pReQvayZzKWGVwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4 +IBHNfTIzSJRUTN3cecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREes +t2d/AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A== +-----END CERTIFICATE----- + +NetLock Notary (Class A) Root +============================= +-----BEGIN CERTIFICATE----- +MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQI +EwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6 +dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9j +ayBLb3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oX +DTE5MDIxOTIzMTQ0N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQH +EwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYD +VQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFz +cyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSM +D7tM9DceqQWC2ObhbHDqeLVu0ThEDaiDzl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZ +z+qMkjvN9wfcZnSX9EUi3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC +/tmwqcm8WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LYOph7 +tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2EsiNCubMvJIH5+hCoR6 +4sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCCApswDgYDVR0PAQH/BAQDAgAGMBIG +A1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaC +Ak1GSUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pv +bGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu +IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2Vn +LWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0 +ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFz +IGxlaXJhc2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBh +IGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVu +b3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBh +bmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sg +Q1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFp +bCBhdCBjcHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5 +ayZrU3/b39/zcT0mwBQOxmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjP +ytoUMaFP0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQQeJB +CWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxkf1qbFFgBJ34TUMdr +KuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK8CtmdWOMovsEPoMOmzbwGOQmIMOM +8CgHrTwXZoi1/baI +-----END CERTIFICATE----- + +XRamp Global CA Root +==================== +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE +BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj +dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx +HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg +U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu +IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx +foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FE +zG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs +AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvry +xS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap +oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC +AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc +/Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n +nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz +8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE----- + +Go Daddy Class 2 CA +=================== +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY +VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG +A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g +RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD +ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv +2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32 +qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j +YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY +vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O +BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o +atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu +MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim +PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt +I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI +Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b +vZ8= +-----END CERTIFICATE----- + +Starfield Class 2 CA +==================== +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc +U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo +MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG +A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG +SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY +bitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ +JRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm +epsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN +F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF +MIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f +hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo +bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g +QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs +afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLM +PUxA2IGvd56Deruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD +KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3 +QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- + +StartCom Certification Authority +================================ +-----BEGIN CERTIFICATE----- +MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN +U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu +ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0 +NjM2WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk +LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg +U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y +o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/ +Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d +eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt +2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z +6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ +osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/ +untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc +UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT +37uMdBNSSwIDAQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE +FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9jZXJ0LnN0YXJ0 +Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3JsLnN0YXJ0Y29tLm9yZy9zZnNj +YS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFMBgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUH +AgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRw +Oi8vY2VydC5zdGFydGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYg +U3RhcnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlhYmlsaXR5 +LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2YgdGhlIFN0YXJ0Q29tIENl +cnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFpbGFibGUgYXQgaHR0cDovL2NlcnQuc3Rh +cnRjb20ub3JnL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilT +dGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOC +AgEAFmyZ9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8jhvh +3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUWFjgKXlf2Ysd6AgXm +vB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJzewT4F+irsfMuXGRuczE6Eri8sxHk +fY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3 +fsNrarnDy0RLrHiQi+fHLB5LEUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZ +EoalHmdkrQYuL6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq +yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuCO3NJo2pXh5Tl +1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6Vum0ABj6y6koQOdjQK/W/7HW/ +lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkyShNOsF/5oirpt9P/FlUQqmMGqz9IgcgA38coro +g14= +-----END CERTIFICATE----- + +Taiwan GRCA +=========== +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/MQswCQYDVQQG +EwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4X +DTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1owPzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dv +dmVybm1lbnQgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qN +w8XRIePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1qgQdW8or5 +BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKyyhwOeYHWtXBiCAEuTk8O +1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAtsF/tnyMKtsc2AtJfcdgEWFelq16TheEfO +htX7MfP6Mb40qij7cEwdScevLJ1tZqa2jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wov +J5pGfaENda1UhhXcSTvxls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7 +Q3hub/FCVGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHKYS1t +B6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoHEgKXTiCQ8P8NHuJB +O9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThNXo+EHWbNxWCWtFJaBYmOlXqYwZE8 +lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1UdDgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNV +HRMEBTADAQH/MDkGBGcqBwAEMTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg2 +09yewDL7MTqKUWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ +TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyfqzvS/3WXy6Tj +Zwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaKZEk9GhiHkASfQlK3T8v+R0F2 +Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFEJPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlU +D7gsL0u8qV1bYH+Mh6XgUmMqvtg7hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6Qz +DxARvBMB1uUO07+1EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+Hbk +Z6MmnD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WXudpVBrkk +7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44VbnzssQwmSNOXfJIoRIM3BKQ +CZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDeLMDDav7v3Aun+kbfYNucpllQdSNpc5Oy ++fwC00fmcc4QAu4njIT/rEUNE1yDMuAlpYYsfPQS +-----END CERTIFICATE----- + +Swisscom Root CA 1 +================== +-----BEGIN CERTIFICATE----- +MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQG +EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy +dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4 +MTgyMjA2MjBaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln +aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIIC +IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9m2BtRsiM +MW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdihFvkcxC7mlSpnzNApbjyF +NDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/TilftKaNXXsLmREDA/7n29uj/x2lzZAe +AR81sH8A25Bvxn570e56eqeqDFdvpG3FEzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkC +b6dJtDZd0KTeByy2dbcokdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn +7uHbHaBuHYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNFvJbN +cA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo19AOeCMgkckkKmUp +WyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjCL3UcPX7ape8eYIVpQtPM+GP+HkM5 +haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJWbjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNY +MUJDLXT5xp6mig/p/r+D5kNXJLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw +HQYDVR0hBBYwFDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j +BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzcK6FptWfUjNP9 +MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzfky9NfEBWMXrrpA9gzXrzvsMn +jgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7IkVh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQ +MbFamIp1TpBcahQq4FJHgmDmHtqBsfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4H +VtA4oJVwIHaM190e3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtl +vrsRls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ipmXeascCl +OS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HHb6D0jqTsNFFbjCYDcKF3 +1QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksfrK/7DZBaZmBwXarNeNQk7shBoJMBkpxq +nvy5JMWzFYJ+vq6VK+uxwNrjAWALXmmshFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCy +x/yP2FS1k2Kdzs9Z+z0YzirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMW +NY6E0F/6MBr1mmz0DlP5OlvRHA== +-----END CERTIFICATE----- + +DigiCert Assured ID Root CA +=========================== +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx +MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO +9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy +UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW +/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy +oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf +GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF +66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq +hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc +EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn +SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i +8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- + +DigiCert Global Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw +MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn +TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5 +BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H +4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y +7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB +o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm +8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF +BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr +EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt +tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886 +UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + +DigiCert High Assurance EV Root CA +================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw +KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw +MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ +MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu +Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t +Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS +OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3 +MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ +NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe +h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY +JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ +V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp +myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK +mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K +-----END CERTIFICATE----- + +Certplus Class 2 Primary CA +=========================== +-----BEGIN CERTIFICATE----- +MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAwPTELMAkGA1UE +BhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFzcyAyIFByaW1hcnkgQ0EwHhcN +OTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2Vy +dHBsdXMxGzAZBgNVBAMTEkNsYXNzIDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBANxQltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR +5aiRVhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyLkcAbmXuZ +Vg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCdEgETjdyAYveVqUSISnFO +YFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yasH7WLO7dDWWuwJKZtkIvEcupdM5i3y95e +e++U8Rs+yskhwcWYAqqi9lt3m/V+llU0HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRME +CDAGAQH/AgEKMAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJ +YIZIAYb4QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMuY29t +L0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/AN9WM2K191EBkOvD +P9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8yfFC82x/xXp8HVGIutIKPidd3i1R +TtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMRFcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+ +7UCmnYR0ObncHoUW2ikbhiMAybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW +//1IMwrh3KWBkJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7 +l7+ijrRU +-----END CERTIFICATE----- + +DST Root CA X3 +============== +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/MSQwIgYDVQQK +ExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4X +DTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVowPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1 +cmUgVHJ1c3QgQ28uMRcwFQYDVQQDEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmT +rE4Orz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEqOLl5CjH9 +UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9bxiqKqy69cK3FCxolkHRy +xXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40d +utolucbY38EVAjqr2m7xPi71XAicPNaDaeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQ +MA0GCSqGSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69ikug +dB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXrAvHRAosZy5Q6XkjE +GB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZzR8srzJmwN0jP41ZL9c8PDHIyh8bw +RLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubS +fZGL+T0yjWW06XyxV3bqxbYoOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE----- + +DST ACES CA X6 +============== +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBbMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QxETAPBgNVBAsTCERTVCBBQ0VT +MRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0wMzExMjAyMTE5NThaFw0xNzExMjAyMTE5NTha +MFsxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UE +CxMIRFNUIEFDRVMxFzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPuktKe1jzI +DZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7gLFViYsx+tC3dr5BPTCa +pCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZHfAjIgrrep4c9oW24MFbCswKBXy314pow +GCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4aahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPy +MjwmR/onJALJfh1biEITajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1Ud +EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rkc3Qu +Y29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjtodHRwOi8vd3d3LnRy +dXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMtaW5kZXguaHRtbDAdBgNVHQ4EFgQU +CXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZIhvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V2 +5FYrnJmQ6AgwbN99Pe7lv7UkQIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6t +Fr8hlxCBPeP/h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq +nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpRrscL9yuwNwXs +vFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf29w4LTJxoeHtxMcfrHuBnQfO3 +oKfN5XozNmr6mis= +-----END CERTIFICATE----- + +TURKTRUST Certificate Services Provider Root 2 +============================================== +-----BEGIN CERTIFICATE----- +MIIEPDCCAySgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP +MA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg +QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwHhcN +MDUxMTA3MTAwNzU3WhcNMTUwOTE2MTAwNzU3WjCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBFbGVr +dHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEPMA0G +A1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmls +acWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpNn7DkUNMwxmYCMjHWHtPFoylzkkBH3MOrHUTpvqe +LCDe2JAOCtFp0if7qnefJ1Il4std2NiDUBd9irWCPwSOtNXwSadktx4uXyCcUHVPr+G1QRT0mJKI +x+XlZEdhR3n9wFHxwZnn3M5q+6+1ATDcRhzviuyV79z/rxAc653YsKpqhRgNF8k+v/Gb0AmJQv2g +QrSdiVFVKc8bcLyEVK3BEx+Y9C52YItdP5qtygy/p1Zbj3e41Z55SZI/4PGXJHpsmxcPbe9TmJEr +5A++WXkHeLuXlfSfadRYhwqp48y2WBmfJiGxxFmNskF1wK1pzpwACPI2/z7woQ8arBT9pmAPAgMB +AAGjQzBBMB0GA1UdDgQWBBTZN7NOBf3Zz58SFq62iS/rJTqIHDAPBgNVHQ8BAf8EBQMDBwYAMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHJglrfJ3NgpXiOFX7KzLXb7iNcX/ntt +Rbj2hWyfIvwqECLsqrkw9qtY1jkQMZkpAL2JZkH7dN6RwRgLn7Vhy506vvWolKMiVW4XSf/SKfE4 +Jl3vpao6+XF75tpYHdN0wgH6PmlYX63LaL4ULptswLbcoCb6dxriJNoaN+BnrdFzgw2lGh1uEpJ+ +hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotHuFEJjOp9zYhys2AzsfAKRO8P +9Qk3iCQOLGsgOqL6EfJANZxEaGM7rDNvY7wsu/LSy3Z9fYjYHcgFHW68lKlmjHdxx/qR+i9Rnuk5 +UrbnBEI= +-----END CERTIFICATE----- + +SwissSign Gold CA - G2 +====================== +-----BEGIN CERTIFICATE----- +MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw +EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN +MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp +c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq +t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C +jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg +vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF +ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR +AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend +jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO +peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR +7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi +GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64 +OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov +L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm +5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr +44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf +Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m +Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp +mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk +vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf +KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br +NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj +viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ +-----END CERTIFICATE----- + +SwissSign Silver CA - G2 +======================== +-----BEGIN CERTIFICATE----- +MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gxFTAT +BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMB4X +DTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3 +aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644 +N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7brYT7QbNHm ++/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieFnbAVlDLaYQ1HTWBCrpJH +6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH6ATK72oxh9TAtvmUcXtnZLi2kUpCe2Uu +MGoM9ZDulebyzYLs2aFK7PayS+VFheZteJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5h +qAaEuSh6XzjZG6k4sIN/c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5 +FZGkECwJMoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBs +ROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmc +celM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb65i/4z3GcRm25xBWNOHkDRUjvxF3X +CO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRB +tjpbO8tFnb0cwpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 +cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P +4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F +kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L +3XWgwF15kIwb4FDm3jH+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx +/uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFa +DGi8aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2Xem1ZqSqP +e97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQRdAtq/gsD/KNVV4n+Ssuu +WxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJ +DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub +DgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u +-----END CERTIFICATE----- + +GeoTrust Primary Certification Authority +======================================== +-----BEGIN CERTIFICATE----- +MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQG +EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMoR2VvVHJ1c3QgUHJpbWFyeSBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgx +CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQ +cmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9AWbK7hWN +b6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjAZIVcFU2Ix7e64HXprQU9 +nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE07e9GceBrAqg1cmuXm2bgyxx5X9gaBGge +RwLmnWDiNpcB3841kt++Z8dtd1k7j53WkBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGt +tm/81w7a4DSwDRp35+MImO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJKoZI +hvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ16CePbJC/kRYkRj5K +Ts4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl4b7UVXGYNTq+k+qurUKykG/g/CFN +NWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6KoKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHa +Floxt/m0cYASSJlyc1pZU8FjUjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG +1riR/aYNKxoUAT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= +-----END CERTIFICATE----- + +thawte Primary Root CA +====================== +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCBqTELMAkGA1UE +BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2 +aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3 +MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwg +SW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMv +KGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMT +FnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCs +oPD7gFnUnMekz52hWXMJEEUMDSxuaPFsW0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ +1CRfBsDMRJSUjQJib+ta3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGc +q/gcfomk6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6Sk/K +aAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94JNqR32HuHUETVPm4p +afs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD +VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XPr87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUF +AAOCAQEAeRHAS7ORtvzw6WfUDW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeE +uzLlQRHAd9mzYJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX +xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2/qxAeeWsEG89 +jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/LHbTY5xZ3Y+m4Q6gLkH3LpVH +z7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7jVaMaA== +-----END CERTIFICATE----- + +VeriSign Class 3 Public Primary Certification Authority - G5 +============================================================ +-----BEGIN CERTIFICATE----- +MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCByjELMAkGA1UE +BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO +ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk +IHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCB +yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln +biBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBh +dXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmlt +YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKz +j/i5Vbext0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhD +Y2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/ +Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNHiDxpg8v+R70r +fk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/ +BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2Uv +Z2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy +aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKvMzEzMA0GCSqG +SIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzEp6B4Eq1iDkVwZMXnl2YtmAl+ +X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKE +KQsTb47bDN0lAtukixlE0kF6BWlKWE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiC +Km0oHw0LxOXnGiYZ4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vE +ZV8NhnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq +-----END CERTIFICATE----- + +SecureTrust CA +============== +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy +dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe +BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX +OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t +DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH +GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b +01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH +ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj +aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ +KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu +SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf +mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ +nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- + +Secure Global CA +================ +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH +bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg +MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg +Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx +YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ +bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g +8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV +HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi +0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn +oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA +MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+ +OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn +CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5 +3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- + +COMODO Certification Authority +============================== +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb +MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD +T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH ++7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww +xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV +4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA +1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI +rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k +b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC +AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP +OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc +IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN ++8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ== +-----END CERTIFICATE----- + +Network Solutions Certificate Authority +======================================= +-----BEGIN CERTIFICATE----- +MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQG +EwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydOZXR3b3Jr +IFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMx +MjM1OTU5WjBiMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu +MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwzc7MEL7xx +jOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPPOCwGJgl6cvf6UDL4wpPT +aaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rlmGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXT +crA/vGp97Eh/jcOrqnErU2lBUzS1sLnFBgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc +/Qzpf14Dl847ABSHJ3A4qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMB +AAGjgZcwgZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwubmV0c29sc3NsLmNv +bS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3JpdHkuY3JsMA0GCSqGSIb3DQEBBQUA +A4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc86fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q +4LqILPxFzBiwmZVRDuwduIj/h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/ +GGUsyfJj4akH/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv +wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHNpGxlaKFJdlxD +ydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey +-----END CERTIFICATE----- + +WellsSecure Public Root Certificate Authority +============================================= +-----BEGIN CERTIFICATE----- +MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoM +F1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYw +NAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcN +MDcxMjEzMTcwNzU0WhcNMjIxMjE0MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dl +bGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYD +VQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+rWxxTkqxtnt3CxC5FlAM1 +iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjUDk/41itMpBb570OYj7OeUt9tkTmPOL13 +i0Nj67eT/DBMHAGTthP796EfvyXhdDcsHqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8 +bJVhHlfXBIEyg1J55oNjz7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiB +K0HmOFafSZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/SlwxlAgMB +AAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqGKGh0dHA6Ly9jcmwu +cGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0PAQH/BAQDAgHGMB0GA1UdDgQWBBQm +lRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0jBIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGB +i6SBiDCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRww +GgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg +Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEBALkVsUSRzCPI +K0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd/ZDJPHV3V3p9+N701NX3leZ0 +bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pBA4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSlj +qHyita04pO2t/caaH/+Xc/77szWnk4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+es +E2fDbbFwRnzVlhE9iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJ +tylv2G0xffX8oRAHh84vWdw+WNs= +-----END CERTIFICATE----- + +COMODO ECC Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix +GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X +4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni +wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG +FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA +U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +IGC/A +===== +-----BEGIN CERTIFICATE----- +MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYTAkZSMQ8wDQYD +VQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVE +Q1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZy +MB4XDTAyMTIxMzE0MjkyM1oXDTIwMTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQI +EwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NT +STEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaIs9z4iPf930Pfeo2aSVz2 +TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCW +So7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYy +HF2fYPepraX/z9E0+X1bF8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNd +frGoRpAxVs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGdPDPQ +tQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNVHSAEDjAMMAoGCCqB +egF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAxNjAfBgNVHSMEGDAWgBSjBS8YYFDC +iQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUFAAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RK +q89toB9RlPhJy3Q2FLwV3duJL92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3Q +MZsyK10XZZOYYLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg +Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2aNjSaTFR+FwNI +lQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R0982gaEbeC9xs/FZTEYYKKuF +0mBWWg== +-----END CERTIFICATE----- + +Security Communication EV RootCA1 +================================= +-----BEGIN CERTIFICATE----- +MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDElMCMGA1UEChMc +U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMhU2VjdXJpdHkgQ29tbXVuaWNh +dGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIzMloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UE +BhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNl +Y3VyaXR5IENvbW11bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSERMqm4miO +/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gOzXppFodEtZDkBp2uoQSX +WHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4z +ZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDFMxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4 +bepJz11sS6/vmsJWXMY1VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK +9U2vP9eCOKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG +SIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HWtWS3irO4G8za+6xm +iEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZq51ihPZRwSzJIxXYKLerJRO1RuGG +Av8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDbEJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnW +mHyojf6GPgcWkuF75x3sM3Z+Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEW +T1MKZPlO9L9OVL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490 +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GA CA +=============================== +-----BEGIN CERTIFICATE----- +MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCBijELMAkGA1UE +BhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHlyaWdodCAoYykgMjAwNTEiMCAG +A1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBH +bG9iYWwgUm9vdCBHQSBDQTAeFw0wNTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYD +VQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIw +IAYDVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5 +IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy0+zAJs9 +Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxRVVuuk+g3/ytr6dTqvirdqFEr12bDYVxg +Asj1znJ7O7jyTmUIms2kahnBAbtzptf2w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbD +d50kc3vkDIzh2TbhmYsFmQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ +/yxViJGg4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t94B3R +LoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ +KoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOxSPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vIm +MMkQyh2I+3QZH4VFvbBsUfk2ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4 ++vg1YFkCExh8vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa +hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZiFj4A4xylNoEY +okxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ/L7fCg0= +-----END CERTIFICATE----- + +Microsec e-Szigno Root CA +========================= +-----BEGIN CERTIFICATE----- +MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAwcjELMAkGA1UE +BhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNyb3NlYyBMdGQuMRQwEgYDVQQL +EwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9zZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0 +MDYxMjI4NDRaFw0xNzA0MDYxMjI4NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVz +dDEWMBQGA1UEChMNTWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMT +GU1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2uuO/TEdyB5s87lozWbxXG +d36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/N +oqdNAoI/gqyFxuEPkEeZlApxcpMqyabAvjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjc +QR/Ji3HWVBTji1R4P770Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJ +PqW+jqpx62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcBAQRb +MFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3AwLQYIKwYBBQUHMAKG +IWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAPBgNVHRMBAf8EBTADAQH/MIIBcwYD +VR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIBAQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3 +LmUtc3ppZ25vLmh1L1NaU1ovMIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0A +dAB2AOEAbgB5ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn +AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABTAHoAbwBsAGcA +4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABhACAAcwB6AGUAcgBpAG4AdAAg +AGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABoAHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMA +egBpAGcAbgBvAC4AaAB1AC8AUwBaAFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6 +Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NO +PU1pY3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxPPU1pY3Jv +c2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDtiaW5h +cnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuBEGluZm9AZS1zemlnbm8uaHWkdzB1MSMw +IQYDVQQDDBpNaWNyb3NlYyBlLVN6aWduw7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhT +WjEWMBQGA1UEChMNTWljcm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhV +MIGsBgNVHSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJIVTER +MA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDASBgNVBAsTC2UtU3pp +Z25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBSb290IENBghEAzLjnv04pGv2i3Gal +HCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMT +nGZjWS7KXHAM/IO8VbH0jgdsZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FE +aGAHQzAxQmHl7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a +86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfRhUZLphK3dehK +yVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/MPMMNz7UwiiAc7EBt51alhQB +S6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU= +-----END CERTIFICATE----- + +Certigna +======== +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw +EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3 +MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI +Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q +XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH +GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p +ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg +DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf +Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ +tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ +BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J +SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA +hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+ +ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu +PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY +1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- + +Deutsche Telekom Root CA 2 +========================== +-----BEGIN CERTIFICATE----- +MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMT +RGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEG +A1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENBIDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5 +MjM1OTAwWjBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0G +A1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBS +b290IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEUha88EOQ5 +bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhCQN/Po7qCWWqSG6wcmtoI +KyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1MjwrrFDa1sPeg5TKqAyZMg4ISFZbavva4VhY +AUlfckE8FQYBjl2tqriTtM2e66foai1SNNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aK +Se5TBY8ZTNXeWHmb0mocQqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTV +jlsB9WoHtxa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAPBgNV +HRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAlGRZrTlk5ynr +E/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756AbrsptJh6sTtU6zkXR34ajgv8HzFZMQSy +zhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpaIzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8 +rZ7/gFnkm0W09juwzTkZmDLl6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4G +dyd1Lx+4ivn+xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU +Cm26OWMohpLzGITY+9HPBVZkVw== +-----END CERTIFICATE----- + +Cybertrust Global Root +====================== +-----BEGIN CERTIFICATE----- +MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYGA1UEChMPQ3li +ZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBSb290MB4XDTA2MTIxNTA4 +MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQD +ExZDeWJlcnRydXN0IEdsb2JhbCBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA ++Mi8vRRQZhP/8NN57CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW +0ozSJ8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2yHLtgwEZL +AfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iPt3sMpTjr3kfb1V05/Iin +89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNzFtApD0mpSPCzqrdsxacwOUBdrsTiXSZT +8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAYXSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2 +MDSgMqAwhi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3JsMB8G +A1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUAA4IBAQBW7wojoFRO +lZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMjWqd8BfP9IjsO0QbE2zZMcwSO5bAi +5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUxXOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2 +hO0j9n0Hq0V+09+zv+mKts2oomcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+T +X3EJIrduPuocA06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW +WL1WMRJOEcgh4LMRkWXbtKaIOM5V +-----END CERTIFICATE----- + +ePKI Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG +EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg +Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx +MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq +MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs +IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi +lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv +qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX +12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O +WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+ +ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao +lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/ +vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi +Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi +MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0 +1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq +KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV +xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP +NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r +GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE +xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx +gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy +sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD +BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- + +T\xc3\x9c\x42\xC4\xB0TAK UEKAE K\xC3\xB6k Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 - S\xC3\xBCr\xC3\xBCm 3 +============================================================================================================================= +-----BEGIN CERTIFICATE----- +MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRSMRgwFgYDVQQH +DA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJpbGltc2VsIHZlIFRla25vbG9q +aWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSwVEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ry +b25payB2ZSBLcmlwdG9sb2ppIEFyYcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNV +BAsMGkthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUg +S8O2ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAeFw0wNzA4 +MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIxGDAWBgNVBAcMD0dlYnpl +IC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmlsaW1zZWwgdmUgVGVrbm9sb2ppayBBcmHF +n3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBUQUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZl +IEtyaXB0b2xvamkgQXJhxZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2Ft +dSBTZXJ0aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7ZrIFNl +cnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4hgb46ezzb8R1Sf1n68yJMlaCQvEhO +Eav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yKO7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1 +xnnRFDDtG1hba+818qEhTsXOfJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR +6Oqeyjh1jmKwlZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL +hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQIDAQABo0IwQDAd +BgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmPNOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4 +N5EY3ATIZJkrGG2AA1nJrvhY0D7twyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLT +y9LQQfMmNkqblWwM7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYh +LBOhgLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5noN+J1q2M +dqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUsyZyQ2uypQjyttgI= +-----END CERTIFICATE----- + +Buypass Class 2 CA 1 +==================== +-----BEGIN CERTIFICATE----- +MIIDUzCCAjugAwIBAgIBATANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMiBDQSAxMB4XDTA2 +MTAxMzEwMjUwOVoXDTE2MTAxMzEwMjUwOVowSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh +c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDIgQ0EgMTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAIs8B0XY9t/mx8q6jUPFR42wWsE425KEHK8T1A9vNkYgxC7M +cXA0ojTTNy7Y3Tp3L8DrKehc0rWpkTSHIln+zNvnma+WwajHQN2lFYxuyHyXA8vmIPLXl18xoS83 +0r7uvqmtqEyeIWZDO6i88wmjONVZJMHCR3axiFyCO7srpgTXjAePzdVBHfCuuCkslFJgNJQ72uA4 +0Z0zPhX0kzLFANq1KWYOOngPIVJfAuWSeyXTkh4vFZ2B5J2O6O+JzhRMVB0cgRJNcKi+EAUXfh/R +uFdV7c27UsKwHnjCTTZoy1YmwVLBvXb3WNVyfh9EdrsAiR0WnVE1703CVu9r4Iw7DekCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUP42aWYv8e3uco684sDntkHGA1sgwDgYDVR0P +AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAVGn4TirnoB6NLJzKyQJHyIdFkhb5jatLPgcIV +1Xp+DCmsNx4cfHZSldq1fyOhKXdlyTKdqC5Wq2B2zha0jX94wNWZUYN/Xtm+DKhQ7SLHrQVMdvvt +7h5HZPb3J31cKA9FxVxiXqaakZG3Uxcu3K1gnZZkOb1naLKuBctN518fV4bVIJwo+28TOPX2EZL2 +fZleHwzoq0QkKXJAPTZSr4xYkHPB7GEseaHsh7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5w +wDX3OaJdZtB7WZ+oRxKaJyOkLY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho +-----END CERTIFICATE----- + +EBG Elektronik Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 +========================================================================== +-----BEGIN CERTIFICATE----- +MIIF5zCCA8+gAwIBAgIITK9zQhyOdAIwDQYJKoZIhvcNAQEFBQAwgYAxODA2BgNVBAMML0VCRyBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMTcwNQYDVQQKDC5FQkcg +QmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXptZXRsZXJpIEEuxZ4uMQswCQYDVQQGEwJUUjAe +Fw0wNjA4MTcwMDIxMDlaFw0xNjA4MTQwMDMxMDlaMIGAMTgwNgYDVQQDDC9FQkcgRWxla3Ryb25p +ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTE3MDUGA1UECgwuRUJHIEJpbGnFn2lt +IFRla25vbG9qaWxlcmkgdmUgSGl6bWV0bGVyaSBBLsWeLjELMAkGA1UEBhMCVFIwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQDuoIRh0DpqZhAy2DE4f6en5f2h4fuXd7hxlugTlkaDT7by +X3JWbhNgpQGR4lvFzVcfd2NR/y8927k/qqk153nQ9dAktiHq6yOU/im/+4mRDGSaBUorzAzu8T2b +gmmkTPiab+ci2hC6X5L8GCcKqKpE+i4stPtGmggDg3KriORqcsnlZR9uKg+ds+g75AxuetpX/dfr +eYteIAbTdgtsApWjluTLdlHRKJ2hGvxEok3MenaoDT2/F08iiFD9rrbskFBKW5+VQarKD7JK/oCZ +TqNGFav4c0JqwmZ2sQomFd2TkuzbqV9UIlKRcF0T6kjsbgNs2d1s/OsNA/+mgxKb8amTD8UmTDGy +Y5lhcucqZJnSuOl14nypqZoaqsNW2xCaPINStnuWt6yHd6i58mcLlEOzrz5z+kI2sSXFCjEmN1Zn +uqMLfdb3ic1nobc6HmZP9qBVFCVMLDMNpkGMvQQxahByCp0OLna9XvNRiYuoP1Vzv9s6xiQFlpJI +qkuNKgPlV5EQ9GooFW5Hd4RcUXSfGenmHmMWOeMRFeNYGkS9y8RsZteEBt8w9DeiQyJ50hBs37vm +ExH8nYQKE3vwO9D8owrXieqWfo1IhR5kX9tUoqzVegJ5a9KK8GfaZXINFHDk6Y54jzJ0fFfy1tb0 +Nokb+Clsi7n2l9GkLqq+CxnCRelwXQIDAJ3Zo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB +/wQEAwIBBjAdBgNVHQ4EFgQU587GT/wWZ5b6SqMHwQSny2re2kcwHwYDVR0jBBgwFoAU587GT/wW +Z5b6SqMHwQSny2re2kcwDQYJKoZIhvcNAQEFBQADggIBAJuYml2+8ygjdsZs93/mQJ7ANtyVDR2t +FcU22NU57/IeIl6zgrRdu0waypIN30ckHrMk2pGI6YNw3ZPX6bqz3xZaPt7gyPvT/Wwp+BVGoGgm +zJNSroIBk5DKd8pNSe/iWtkqvTDOTLKBtjDOWU/aWR1qeqRFsIImgYZ29fUQALjuswnoT4cCB64k +XPBfrAowzIpAoHMEwfuJJPaaHFy3PApnNgUIMbOv2AFoKuB4j3TeuFGkjGwgPaL7s9QJ/XvCgKqT +bCmYIai7FvOpEl90tYeY8pUm3zTvilORiF0alKM/fCL414i6poyWqD1SNGKfAB5UVUJnxk1Gj7sU +RT0KlhaOEKGXmdXTMIXM3rRyt7yKPBgpaP3ccQfuJDlq+u2lrDgv+R4QDgZxGhBM/nV+/x5XOULK +1+EVoVZVWRvRo68R2E7DpSvvkL/A7IITW43WciyTTo9qKd+FPNMN4KIYEsxVL0e3p5sC/kH2iExt +2qkBR4NkJ2IQgtYSe14DHzSpyZH+r11thie3I6p1GMog57AP14kOpmciY/SDQSsGS7tY1dHXt7kQ +Y9iJSrSq3RZj9W6+YKH47ejWkE8axsWgKdOnIaj1Wjz3x0miIZpKlVIglnKaZsv30oZDfCK+lvm9 +AahH3eU7QPl1K5srRmSGjR70j/sHd9DqSaIcjVIUpgqT +-----END CERTIFICATE----- + +certSIGN ROOT CA +================ +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD +VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa +Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE +CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I +JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH +rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2 +ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD +0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943 +AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B +Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB +AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8 +SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0 +x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt +vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz +TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD +-----END CERTIFICATE----- + +CNNIC ROOT +========== +-----BEGIN CERTIFICATE----- +MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJDTjEOMAwGA1UE +ChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2MDcwOTE0WhcNMjcwNDE2MDcw +OTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1Qw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzD +o+/hn7E7SIX1mlwhIhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tiz +VHa6dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZOV/kbZKKT +VrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrCGHn2emU1z5DrvTOTn1Or +czvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gNv7Sg2Ca+I19zN38m5pIEo3/PIKe38zrK +y5nLAgMBAAGjczBxMBEGCWCGSAGG+EIBAQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscC +wQ7vptU7ETAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991S +lgrHAsEO76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnKOOK5 +Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvHugDnuL8BV8F3RTIM +O/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7HgviyJA/qIYM/PmLXoXLT1tLYhFHxUV8 +BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fLbuXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2 +G8kS1sHNzYDzAgE8yGnLRUhj2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5m +mxE= +-----END CERTIFICATE----- + +ApplicationCA - Japanese Government +=================================== +-----BEGIN CERTIFICATE----- +MIIDoDCCAoigAwIBAgIBMTANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJKUDEcMBoGA1UEChMT +SmFwYW5lc2UgR292ZXJubWVudDEWMBQGA1UECxMNQXBwbGljYXRpb25DQTAeFw0wNzEyMTIxNTAw +MDBaFw0xNzEyMTIxNTAwMDBaMEMxCzAJBgNVBAYTAkpQMRwwGgYDVQQKExNKYXBhbmVzZSBHb3Zl +cm5tZW50MRYwFAYDVQQLEw1BcHBsaWNhdGlvbkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAp23gdE6Hj6UG3mii24aZS2QNcfAKBZuOquHMLtJqO8F6tJdhjYq+xpqcBrSGUeQ3DnR4 +fl+Kf5Sk10cI/VBaVuRorChzoHvpfxiSQE8tnfWuREhzNgaeZCw7NCPbXCbkcXmP1G55IrmTwcrN +wVbtiGrXoDkhBFcsovW8R0FPXjQilbUfKW1eSvNNcr5BViCH/OlQR9cwFO5cjFW6WY2H/CPek9AE +jP3vbb3QesmlOmpyM8ZKDQUXKi17safY1vC+9D/qDihtQWEjdnjDuGWk81quzMKq2edY3rZ+nYVu +nyoKb58DKTCXKB28t89UKU5RMfkntigm/qJj5kEW8DOYRwIDAQABo4GeMIGbMB0GA1UdDgQWBBRU +WssmP3HMlEYNllPqa0jQk/5CdTAOBgNVHQ8BAf8EBAMCAQYwWQYDVR0RBFIwUKROMEwxCzAJBgNV +BAYTAkpQMRgwFgYDVQQKDA/ml6XmnKzlm73mlL/lupwxIzAhBgNVBAsMGuOCouODl+ODquOCseOD +vOOCt+ODp+ODs0NBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADlqRHZ3ODrs +o2dGD/mLBqj7apAxzn7s2tGJfHrrLgy9mTLnsCTWw//1sogJhyzjVOGjprIIC8CFqMjSnHH2HZ9g +/DgzE+Ge3Atf2hZQKXsvcJEPmbo0NI2VdMV+eKlmXb3KIXdCEKxmJj3ekav9FfBv7WxfEPjzFvYD +io+nEhEMy/0/ecGc/WLuo89UDNErXxc+4z6/wCs+CZv+iKZ+tJIX/COUgb1up8WMwusRRdv4QcmW +dupwX3kSa+SjB1oF7ydJzyGfikwJcGapJsErEU4z0g781mzSDjJkaP+tBXhfAx2o45CsJOAPQKdL +rosot4LKGAfmt1t06SAZf7IbiVQ= +-----END CERTIFICATE----- + +GeoTrust Primary Certification Authority - G3 +============================================= +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UE +BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA4IEdlb1RydXN0 +IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFy +eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIz +NTk1OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAo +YykgMjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMT +LUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5j +K/BGvESyiaHAKAxJcCGVn2TAppMSAmUmhsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdE +c5IiaacDiGydY8hS2pgn5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3C +IShwiP/WJmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exALDmKu +dlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZChuOl1UcCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMR5yo6hTgMdHNxr +2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IBAQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9 +cr5HqQ6XErhK8WTTOd8lNNTBzU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbE +Ap7aDHdlDkQNkv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD +AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUHSJsMC8tJP33s +t/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2Gspki4cErx5z481+oghLrGREt +-----END CERTIFICATE----- + +thawte Primary Root CA - G2 +=========================== +-----BEGIN CERTIFICATE----- +MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDELMAkGA1UEBhMC +VVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMpIDIwMDcgdGhhd3RlLCBJbmMu +IC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3Qg +Q0EgLSBHMjAeFw0wNzExMDUwMDAwMDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEV +MBMGA1UEChMMdGhhd3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBG +b3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAt +IEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/BebfowJPDQfGAFG6DAJS +LSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6papu+7qzcMBniKI11KOasf2twu8x+qi5 +8/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU +mtgAMADna3+FGO6Lts6KDPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUN +G4k8VIZ3KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41oxXZ3K +rr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== +-----END CERTIFICATE----- + +thawte Primary Root CA - G3 +=========================== +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCBrjELMAkGA1UE +BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2 +aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0w +ODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh +d3RlLCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9uMTgwNgYD +VQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIG +A1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAsr8nLPvb2FvdeHsbnndmgcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2At +P0LMqmsywCPLLEHd5N/8YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC ++BsUa0Lfb1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS99irY +7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2SzhkGcuYMXDhpxwTW +vGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUkOQIDAQABo0IwQDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJ +KoZIhvcNAQELBQADggEBABpA2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweK +A3rD6z8KLFIWoCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu +t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7cKUGRIjxpp7sC +8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fMm7v/OeZWYdMKp8RcTGB7BXcm +er/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZuMdRAGmI0Nj81Aa6sY6A= +-----END CERTIFICATE----- + +GeoTrust Primary Certification Authority - G2 +============================================= +-----BEGIN CERTIFICATE----- +MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA3IEdlb1RydXN0IElu +Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1 +OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg +MjAwNyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMTLUdl +b1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjB2MBAGByqGSM49AgEG +BSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcLSo17VDs6bl8VAsBQps8lL33KSLjHUGMc +KiEIfJo22Av+0SbFWDEwKCXzXV2juLaltJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYD +VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+ +EVXVMAoGCCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGTqQ7m +ndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBuczrD6ogRLQy7rQkgu2 +npaqBA+K +-----END CERTIFICATE----- + +VeriSign Universal Root Certification Authority +=============================================== +-----BEGIN CERTIFICATE----- +MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCBvTELMAkGA1UE +BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO +ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk +IHVzZSBvbmx5MTgwNgYDVQQDEy9WZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9u +IEF1dGhvcml0eTAeFw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJV +UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv +cmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj +1mCOkdeQmIN65lgZOIzF9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGP +MiJhgsWHH26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+HLL72 +9fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN/BMReYTtXlT2NJ8I +AfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPTrJ9VAMf2CGqUuV/c4DPxhGD5WycR +tPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0G +CCsGAQUFBwEMBGEwX6FdoFswWTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2O +a8PPgGrUSBgsexkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud +DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4sAPmLGd75JR3 +Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+seQxIcaBlVZaDrHC1LGmWazx +Y8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTx +P/jgdFcrGJ2BtMQo2pSXpXDrrB2+BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+P +wGZsY6rp2aQW9IHRlRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4 +mJO37M2CYfE45k+XmCpajQ== +-----END CERTIFICATE----- + +VeriSign Class 3 Public Primary Certification Authority - G4 +============================================================ +-----BEGIN CERTIFICATE----- +MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjELMAkGA1UEBhMC +VVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3 +b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVz +ZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjEL +MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBU +cnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRo +b3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5 +IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8 +Utpkmw4tXNherJI9/gHmGUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGz +rl0Bp3vefLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEw +HzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24u +Y29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMWkf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMD +A2gAMGUCMGYhDBgmYFo4e1ZC4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIx +AJw9SDkjOVgaFRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== +-----END CERTIFICATE----- + +NetLock Arany (Class Gold) Főtanúsítvány +============================================ +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G +A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610 +dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB +cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx +MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO +ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6 +c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu +0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw +/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk +H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw +fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1 +neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW +qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta +YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna +NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu +dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +Staat der Nederlanden Root CA - G2 +================================== +-----BEGIN CERTIFICATE----- +MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwGA1UE +CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJsYW5kZW4g +Um9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oXDTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMC +TkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5l +ZGVybGFuZGVuIFJvb3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ +5291qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8SpuOUfiUtn +vWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPUZ5uW6M7XxgpT0GtJlvOj +CwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvEpMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiil +e7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCR +OME4HYYEhLoaJXhena/MUGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpI +CT0ugpTNGmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy5V65 +48r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv6q012iDTiIJh8BIi +trzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEKeN5KzlW/HdXZt1bv8Hb/C3m1r737 +qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMB +AAGjgZcwgZQwDwYDVR0TAQH/BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcC +ARYxaHR0cDovL3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqGSIb3DQEBCwUA +A4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLySCZa59sCrI2AGeYwRTlHSeYAz ++51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwj +f/ST7ZwaUb7dRUG/kSS0H4zpX897IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaN +kqbG9AclVMwWVxJKgnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfk +CpYL+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxLvJxxcypF +URmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkmbEgeqmiSBeGCc1qb3Adb +CG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvkN1trSt8sV4pAWja63XVECDdCcAz+3F4h +oKOKwJCcaNpQ5kUQR3i2TtJlycM33+FCY7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoV +IPVVYpbtbZNQvOSqeK3Zywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm +66+KAQ== +-----END CERTIFICATE----- + +CA Disig +======== +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMK +QnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwHhcNMDYw +MzIyMDEzOTM0WhcNMTYwMzIyMDEzOTM0WjBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMKQnJhdGlz +bGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCS9jHBfYj9mQGp2HvycXXxMcbzdWb6UShGhJd4NLxs/LxFWYgm +GErENx+hSkS943EE9UQX4j/8SFhvXJ56CbpRNyIjZkMhsDxkovhqFQ4/61HhVKndBpnXmjxUizkD +Pw/Fzsbrg3ICqB9x8y34dQjbYkzo+s7552oftms1grrijxaSfQUMbEYDXcDtab86wYqg6I7ZuUUo +hwjstMoVvoLdtUSLLa2GDGhibYVW8qwUYzrG0ZmsNHhWS8+2rT+MitcE5eN4TPWGqvWP+j1scaMt +ymfraHtuM6kMgiioTGohQBUgDCZbg8KpFhXAJIJdKxatymP2dACw30PEEGBWZ2NFAgMBAAGjgf8w +gfwwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUjbJJaJ1yCCW5wCf1UJNWSEZx+Y8wDgYDVR0P +AQH/BAQDAgEGMDYGA1UdEQQvMC2BE2Nhb3BlcmF0b3JAZGlzaWcuc2uGFmh0dHA6Ly93d3cuZGlz +aWcuc2svY2EwZgYDVR0fBF8wXTAtoCugKYYnaHR0cDovL3d3dy5kaXNpZy5zay9jYS9jcmwvY2Ff +ZGlzaWcuY3JsMCygKqAohiZodHRwOi8vY2EuZGlzaWcuc2svY2EvY3JsL2NhX2Rpc2lnLmNybDAa +BgNVHSAEEzARMA8GDSuBHpGT5goAAAABAQEwDQYJKoZIhvcNAQEFBQADggEBAF00dGFMrzvY/59t +WDYcPQuBDRIrRhCA/ec8J9B6yKm2fnQwM6M6int0wHl5QpNt/7EpFIKrIYwvF/k/Ji/1WcbvgAa3 +mkkp7M5+cTxqEEHA9tOasnxakZzArFvITV734VP/Q3f8nktnbNfzg9Gg4H8l37iYC5oyOGwwoPP/ +CBUz91BKez6jPiCp3C9WgArtQVCwyfTssuMmRAAOb54GvCKWU3BlxFAKRmukLyeBEicTXxChds6K +ezfqwzlhA5WYOudsiCUI/HloDYd9Yvi0X/vF2Ey9WLw/Q1vUHgFNPGO+I++MzVpQuGhU+QqZMxEA +4Z7CRneC9VkGjCFMhwnN5ag= +-----END CERTIFICATE----- + +Juur-SK +======= +-----BEGIN CERTIFICATE----- +MIIE5jCCA86gAwIBAgIEO45L/DANBgkqhkiG9w0BAQUFADBdMRgwFgYJKoZIhvcNAQkBFglwa2lA +c2suZWUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKExlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMRAw +DgYDVQQDEwdKdXVyLVNLMB4XDTAxMDgzMDE0MjMwMVoXDTE2MDgyNjE0MjMwMVowXTEYMBYGCSqG +SIb3DQEJARYJcGtpQHNrLmVlMQswCQYDVQQGEwJFRTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVy +aW1pc2tlc2t1czEQMA4GA1UEAxMHSnV1ci1TSzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAIFxNj4zB9bjMI0TfncyRsvPGbJgMUaXhvSYRqTCZUXP00B841oiqBB4M8yIsdOBSvZiF3tf +TQou0M+LI+5PAk676w7KvRhj6IAcjeEcjT3g/1tf6mTll+g/mX8MCgkzABpTpyHhOEvWgxutr2TC ++Rx6jGZITWYfGAriPrsfB2WThbkasLnE+w0R9vXW+RvHLCu3GFH+4Hv2qEivbDtPL+/40UceJlfw +UR0zlv/vWT3aTdEVNMfqPxZIe5EcgEMPPbgFPtGzlc3Yyg/CQ2fbt5PgIoIuvvVoKIO5wTtpeyDa +Tpxt4brNj3pssAki14sL2xzVWiZbDcDq5WDQn/413z8CAwEAAaOCAawwggGoMA8GA1UdEwEB/wQF +MAMBAf8wggEWBgNVHSAEggENMIIBCTCCAQUGCisGAQQBzh8BAQEwgfYwgdAGCCsGAQUFBwICMIHD +HoHAAFMAZQBlACAAcwBlAHIAdABpAGYAaQBrAGEAYQB0ACAAbwBuACAAdgDkAGwAagBhAHMAdABh +AHQAdQBkACAAQQBTAC0AaQBzACAAUwBlAHIAdABpAGYAaQB0AHMAZQBlAHIAaQBtAGkAcwBrAGUA +cwBrAHUAcwAgAGEAbABhAG0ALQBTAEsAIABzAGUAcgB0AGkAZgBpAGsAYQBhAHQAaQBkAGUAIABr +AGkAbgBuAGkAdABhAG0AaQBzAGUAawBzMCEGCCsGAQUFBwIBFhVodHRwOi8vd3d3LnNrLmVlL2Nw +cy8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL3d3dy5zay5lZS9qdXVyL2NybC8wHQYDVR0OBBYE +FASqekej5ImvGs8KQKcYP2/v6X2+MB8GA1UdIwQYMBaAFASqekej5ImvGs8KQKcYP2/v6X2+MA4G +A1UdDwEB/wQEAwIB5jANBgkqhkiG9w0BAQUFAAOCAQEAe8EYlFOiCfP+JmeaUOTDBS8rNXiRTHyo +ERF5TElZrMj3hWVcRrs7EKACr81Ptcw2Kuxd/u+gkcm2k298gFTsxwhwDY77guwqYHhpNjbRxZyL +abVAyJRld/JXIWY7zoVAtjNjGr95HvxcHdMdkxuLDF2FvZkwMhgJkVLpfKG6/2SSmuz+Ne6ML678 +IIbsSt4beDI3poHSna9aEhbKmVv8b20OxaAehsmR0FyYgl9jDIpaq9iVpszLita/ZEuOyoqysOkh +Mp6qqIWYNIE5ITuoOlIyPfZrN4YGWhWY3PARZv40ILcD9EEQfTmEeZZyY7aWAuVrua0ZTbvGRNs2 +yyqcjg== +-----END CERTIFICATE----- + +Hongkong Post Root CA 1 +======================= +-----BEGIN CERTIFICATE----- +MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoT +DUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMB4XDTAzMDUx +NTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25n +IFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1 +ApzQjVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEnPzlTCeqr +auh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjhZY4bXSNmO7ilMlHIhqqh +qZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9nnV0ttgCXjqQesBCNnLsak3c78QA3xMY +V18meMjWCnl3v/evt3a5pQuEF10Q6m/hq5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNV +HRMBAf8ECDAGAQH/AgEDMA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7i +h9legYsCmEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI37pio +l7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clBoiMBdDhViw+5Lmei +IAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJsEhTkYY2sEJCehFC78JZvRZ+K88ps +T/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpOfMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilT +c4afU9hDDl3WY4JxHYB0yvbiAmvZWg== +-----END CERTIFICATE----- + +SecureSign RootCA11 +=================== +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UEChMi +SmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJlU2lnbiBS +b290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSsw +KQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1 +cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvL +TJszi1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8h9uuywGO +wvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOVMdrAG/LuYpmGYz+/3ZMq +g6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rP +O7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitA +bpSACW22s293bzUIUPsCh8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZX +t94wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKCh +OBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4r +bnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQ +Oh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01 +y8hSyn+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061 +lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A/I= +-----END CERTIFICATE----- + +ACEDICOM Root +============= +-----BEGIN CERTIFICATE----- +MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UEAwwNQUNFRElD +T00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMB4XDTA4 +MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEWMBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoG +A1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHk +WLn709gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7XBZXehuD +YAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5PGrjm6gSSrj0RuVFCPYew +MYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAKt0SdE3QrwqXrIhWYENiLxQSfHY9g5QYb +m8+5eaA9oiM/Qj9r+hwDezCNzmzAv+YbX79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbk +HQl/Sog4P75n/TSW9R28MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTT +xKJxqvQUfecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI2Sf2 +3EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyHK9caUPgn6C9D4zq9 +2Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEaeZAwUswdbxcJzbPEHXEUkFDWug/Fq +TYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz +4SsrSbbXc6GqlPUB53NlTKxQMA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU +9QHnc2VMrFAwRAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv +bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWImfQwng4/F9tqg +aHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3gvoFNTPhNahXwOf9jU8/kzJP +eGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKeI6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1Pwk +zQSulgUV1qzOMPPKC8W64iLgpq0i5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1 +ThCojz2GuHURwCRiipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oI +KiMnMCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZo5NjEFIq +nxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6zqylfDJKZ0DcMDQj3dcE +I2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacNGHk0vFQYXlPKNFHtRQrmjseCNj6nOGOp +MCwXEGCSn1WHElkQwg9naRHMTh5+Spqtr0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3o +tkYNbn5XOmeUwssfnHdKZ05phkOTOPu220+DkdRgfks+KzgHVZhepA== +-----END CERTIFICATE----- + +Microsec e-Szigno Root CA 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER +MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv +c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE +BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt +U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA +fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG +0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA +pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm +1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC +AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf +QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE +FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o +lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX +I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02 +yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi +LXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +GlobalSign Root CA - R3 +======================= +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv +YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh +bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT +aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln +bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt +iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ +0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3 +rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl +OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2 +xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7 +lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8 +EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E +bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18 +YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r +kpeDMdmztcpHWD9f +-----END CERTIFICATE----- + +Autoridad de Certificacion Firmaprofesional CIF A62634068 +========================================================= +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UEBhMCRVMxQjBA +BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 +MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEyMzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIw +QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB +NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD +Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P +B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY +7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH +ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI +plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX +MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX +LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK +bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU +vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1Ud +EwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNH +DhpkLzCBpgYDVR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp +cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBvACAAZABlACAA +bABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBlAGwAbwBuAGEAIAAwADgAMAAx +ADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx +51tkljYyGOylMnfX40S2wBEqgLk9am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qk +R71kMrv2JYSiJ0L1ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaP +T481PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS3a/DTg4f +Jl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5kSeTy36LssUzAKh3ntLFl +osS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF3dvd6qJ2gHN99ZwExEWN57kci57q13XR +crHedUTnQn3iV2t93Jm8PYMo6oCTjcVMZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoR +saS8I8nkvof/uZS2+F0gStRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTD +KCOM/iczQ0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQBjLMi +6Et8Vcad+qMUu2WFbm5PEn4KPJ2V +-----END CERTIFICATE----- + +Izenpe.com +========== +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG +EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz +MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu +QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ +03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK +ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU ++zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC +PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT +OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK +F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK +0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+ +0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB +leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID +AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+ +SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG +NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l +Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga +kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q +hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs +g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5 +aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5 +nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC +ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo +Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z +WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE----- + +Chambers of Commerce Root - 2008 +================================ +-----BEGIN CERTIFICATE----- +MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYDVQQGEwJFVTFD +MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv +bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu +QS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEy +Mjk1MFoXDTM4MDczMTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNl +ZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQF +EwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJl +cnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW928sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKA +XuFixrYp4YFs8r/lfTJqVKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorj +h40G072QDuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR5gN/ +ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfLZEFHcpOrUMPrCXZk +NNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05aSd+pZgvMPMZ4fKecHePOjlO+Bd5g +D2vlGts/4+EhySnB8esHnFIbAURRPHsl18TlUlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331 +lubKgdaX8ZSD6e2wsWsSaR6s+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ +0wlf2eOKNcx5Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj +ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAxhduub+84Mxh2 +EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNVHQ4EFgQU+SSsD7K1+HnA+mCI +G8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJ +BgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNh +bWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENh +bWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDiC +CQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUH +AgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAJASryI1 +wqM58C7e6bXpeHxIvj99RZJe6dqxGfwWPJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH +3qLPaYRgM+gQDROpI9CF5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbU +RWpGqOt1glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaHFoI6 +M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2pSB7+R5KBWIBpih1 +YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MDxvbxrN8y8NmBGuScvfaAFPDRLLmF +9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QGtjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcK +zBIKinmwPQN/aUv0NCB9szTqjktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvG +nrDQWzilm1DefhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg +OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZd0jQ +-----END CERTIFICATE----- + +Global Chambersign Root - 2008 +============================== +-----BEGIN CERTIFICATE----- +MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYDVQQGEwJFVTFD +MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv +bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu +QS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMx +NDBaFw0zODA3MzExMjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUg +Y3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJ +QTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD +aGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDf +VtPkOpt2RbQT2//BthmLN0EYlVJH6xedKYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXf +XjaOcNFccUMd2drvXNL7G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0 +ZJJ0YPP2zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4ddPB +/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyGHoiMvvKRhI9lNNgA +TH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2Id3UwD2ln58fQ1DJu7xsepeY7s2M +H/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3VyJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfe +Ox2YItaswTXbo6Al/3K1dh3ebeksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSF +HTynyQbehP9r6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh +wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsogzCtLkykPAgMB +AAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQWBBS5CcqcHtvTbDprru1U8VuT +BjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDprru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UE +BhMCRVUxQzBBBgNVBAcTOk1hZHJpZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJm +aXJtYS5jb20vYWRkcmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJm +aXJtYSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiCCQDJzdPp +1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0 +dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAICIf3DekijZBZRG +/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZUohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6 +ReAJ3spED8IXDneRRXozX1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/s +dZ7LoR/xfxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVza2Mg +9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yydYhz2rXzdpjEetrHH +foUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMdSqlapskD7+3056huirRXhOukP9Du +qqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9OAP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETr +P3iZ8ntxPjzxmKfFGBI/5rsoM0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVq +c5iJWzouE4gev8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z +09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B +-----END CERTIFICATE----- + +Go Daddy Root Certificate Authority - G2 +======================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu +MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G +A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq +9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD ++qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd +fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl +NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9 +BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac +vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r +5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV +N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1 +-----END CERTIFICATE----- + +Starfield Root Certificate Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0 +eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw +DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg +VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB +dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv +W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs +bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk +N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf +ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU +JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol +TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx +4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw +F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ +c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +Starfield Services Root Certificate Authority - G2 +================================================== +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl +IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV +BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT +dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg +Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2 +h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa +hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP +LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB +rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG +SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP +E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy +xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza +YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6 +-----END CERTIFICATE----- + +AffirmTrust Commercial +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw +MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb +DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV +C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6 +BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww +MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV +HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG +hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi +qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv +0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh +sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= +-----END CERTIFICATE----- + +AffirmTrust Networking +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw +MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE +Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI +dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24 +/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb +h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV +HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu +UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6 +12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23 +WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9 +/ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= +-----END CERTIFICATE----- + +AffirmTrust Premium +=================== +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy +OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy +dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn +BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV +5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs ++7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd +GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R +p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI +S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04 +6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5 +/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo ++Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv +MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg +Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC +6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S +L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK ++4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV +BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg +IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60 +g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb +zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw== +-----END CERTIFICATE----- + +AffirmTrust Premium ECC +======================= +-----BEGIN CERTIFICATE----- +MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV +BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx +MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U +cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ +N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW +BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK +BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X +57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM +eQ== +-----END CERTIFICATE----- + +Certum Trusted Network CA +========================= +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK +ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy +MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU +ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC +l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J +J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4 +fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0 +cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw +DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj +jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1 +mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj +Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +Certinomis - Autorité Racine +============================= +-----BEGIN CERTIFICATE----- +MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjETMBEGA1UEChMK +Q2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAkBgNVBAMMHUNlcnRpbm9taXMg +LSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkG +A1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYw +JAYDVQQDDB1DZXJ0aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jYF1AMnmHa +wE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N8y4oH3DfVS9O7cdxbwly +Lu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWerP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw +2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K/OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92N +jMD2AR5vpTESOH2VwnHu7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9q +c1pkIuVC28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6lSTC +lrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1Enn1So2+WLhl+HPNb +xxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB0iSVL1N6aaLwD4ZFjliCK0wi1F6g +530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql095gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna +4NH4+ej9Uji29YnfAgMBAAGjWzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBQNjLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ +KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9sov3/4gbIOZ/x +WqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZMOH8oMDX/nyNTt7buFHAAQCva +R6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40 +nJ+U8/aGH88bc62UeYdocMMzpXDn2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1B +CxMjidPJC+iKunqjo3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjv +JL1vnxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG5ERQL1TE +qkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWqpdEdnV1j6CTmNhTih60b +WfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZbdsLLO7XSAPCjDuGtbkD326C00EauFddE +wk01+dIL8hf2rGbVJLJP0RyZwG71fet0BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/ +vgt2Fl43N+bYdJeimUV5 +-----END CERTIFICATE----- + +Root CA Generalitat Valenciana +============================== +-----BEGIN CERTIFICATE----- +MIIGizCCBXOgAwIBAgIEO0XlaDANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJFUzEfMB0GA1UE +ChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290 +IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwHhcNMDEwNzA2MTYyMjQ3WhcNMjEwNzAxMTUyMjQ3 +WjBoMQswCQYDVQQGEwJFUzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UE +CxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGKqtXETcvIorKA3Qdyu0togu8M1JAJke+WmmmO3I2 +F0zo37i7L3bhQEZ0ZQKQUgi0/6iMweDHiVYQOTPvaLRfX9ptI6GJXiKjSgbwJ/BXufjpTjJ3Cj9B +ZPPrZe52/lSqfR0grvPXdMIKX/UIKFIIzFVd0g/bmoGlu6GzwZTNVOAydTGRGmKy3nXiz0+J2ZGQ +D0EbtFpKd71ng+CT516nDOeB0/RSrFOyA8dEJvt55cs0YFAQexvba9dHq198aMpunUEDEO5rmXte +JajCq+TA81yc477OMUxkHl6AovWDfgzWyoxVjr7gvkkHD6MkQXpYHYTqWBLI4bft75PelAgxAgMB +AAGjggM7MIIDNzAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnBraS5n +dmEuZXMwEgYDVR0TAQH/BAgwBgEB/wIBAjCCAjQGA1UdIASCAiswggInMIICIwYKKwYBBAG/VQIB +ADCCAhMwggHoBggrBgEFBQcCAjCCAdoeggHWAEEAdQB0AG8AcgBpAGQAYQBkACAAZABlACAAQwBl +AHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAFIAYQDtAHoAIABkAGUAIABsAGEAIABHAGUAbgBlAHIA +YQBsAGkAdABhAHQAIABWAGEAbABlAG4AYwBpAGEAbgBhAC4ADQAKAEwAYQAgAEQAZQBjAGwAYQBy +AGEAYwBpAPMAbgAgAGQAZQAgAFAAcgDhAGMAdABpAGMAYQBzACAAZABlACAAQwBlAHIAdABpAGYA +aQBjAGEAYwBpAPMAbgAgAHEAdQBlACAAcgBpAGcAZQAgAGUAbAAgAGYAdQBuAGMAaQBvAG4AYQBt +AGkAZQBuAHQAbwAgAGQAZQAgAGwAYQAgAHAAcgBlAHMAZQBuAHQAZQAgAEEAdQB0AG8AcgBpAGQA +YQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAHMAZQAgAGUAbgBjAHUAZQBu +AHQAcgBhACAAZQBuACAAbABhACAAZABpAHIAZQBjAGMAaQDzAG4AIAB3AGUAYgAgAGgAdAB0AHAA +OgAvAC8AdwB3AHcALgBwAGsAaQAuAGcAdgBhAC4AZQBzAC8AYwBwAHMwJQYIKwYBBQUHAgEWGWh0 +dHA6Ly93d3cucGtpLmd2YS5lcy9jcHMwHQYDVR0OBBYEFHs100DSHHgZZu90ECjcPk+yeAT8MIGV +BgNVHSMEgY0wgYqAFHs100DSHHgZZu90ECjcPk+yeAT8oWykajBoMQswCQYDVQQGEwJFUzEfMB0G +A1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5S +b290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmGCBDtF5WgwDQYJKoZIhvcNAQEFBQADggEBACRh +TvW1yEICKrNcda3FbcrnlD+laJWIwVTAEGmiEi8YPyVQqHxK6sYJ2fR1xkDar1CdPaUWu20xxsdz +Ckj+IHLtb8zog2EWRpABlUt9jppSCS/2bxzkoXHPjCpaF3ODR00PNvsETUlR4hTJZGH71BTg9J63 +NI8KJr2XXPR5OkowGcytT6CYirQxlyric21+eLj4iIlPsSKRZEv1UN4D2+XFducTZnV+ZfsBn5OH +iJ35Rld8TWCvmHMTI6QgkYH60GFmuH3Rr9ZvHmw96RH9qfmCIoaZM3Fa6hlXPZHNqcCjbgcTpsnt ++GijnsNacgmHKNHEc8RzGF9QdRYxn7fofMM= +-----END CERTIFICATE----- + +A-Trust-nQual-03 +================ +-----BEGIN CERTIFICATE----- +MIIDzzCCAregAwIBAgIDAWweMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYDVQQGEwJBVDFIMEYGA1UE +Cgw/QS1UcnVzdCBHZXMuIGYuIFNpY2hlcmhlaXRzc3lzdGVtZSBpbSBlbGVrdHIuIERhdGVudmVy +a2VociBHbWJIMRkwFwYDVQQLDBBBLVRydXN0LW5RdWFsLTAzMRkwFwYDVQQDDBBBLVRydXN0LW5R +dWFsLTAzMB4XDTA1MDgxNzIyMDAwMFoXDTE1MDgxNzIyMDAwMFowgY0xCzAJBgNVBAYTAkFUMUgw +RgYDVQQKDD9BLVRydXN0IEdlcy4gZi4gU2ljaGVyaGVpdHNzeXN0ZW1lIGltIGVsZWt0ci4gRGF0 +ZW52ZXJrZWhyIEdtYkgxGTAXBgNVBAsMEEEtVHJ1c3QtblF1YWwtMDMxGTAXBgNVBAMMEEEtVHJ1 +c3QtblF1YWwtMDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtPWFuA/OQO8BBC4SA +zewqo51ru27CQoT3URThoKgtUaNR8t4j8DRE/5TrzAUjlUC5B3ilJfYKvUWG6Nm9wASOhURh73+n +yfrBJcyFLGM/BWBzSQXgYHiVEEvc+RFZznF/QJuKqiTfC0Li21a8StKlDJu3Qz7dg9MmEALP6iPE +SU7l0+m0iKsMrmKS1GWH2WrX9IWf5DMiJaXlyDO6w8dB3F/GaswADm0yqLaHNgBid5seHzTLkDx4 +iHQF63n1k3Flyp3HaxgtPVxO59X4PzF9j4fsCiIvI+n+u33J4PTs63zEsMMtYrWacdaxaujs2e3V +cuy+VwHOBVWf3tFgiBCzAgMBAAGjNjA0MA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0OBAoECERqlWdV +eRFPMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAVdRU0VlIXLOThaq/Yy/kgM40 +ozRiPvbY7meIMQQDbwvUB/tOdQ/TLtPAF8fGKOwGDREkDg6lXb+MshOWcdzUzg4NCmgybLlBMRmr +sQd7TZjTXLDR8KdCoLXEjq/+8T/0709GAHbrAvv5ndJAlseIOrifEXnzgGWovR/TeIGgUUw3tKZd +JXDRZslo+S4RFGjxVJgIrCaSD96JntT6s3kr0qN51OyLrIdTaEJMUVF0HhsnLuP1Hyl0Te2v9+GS +mYHovjrHF1D2t8b8m7CKa9aIA5GPBnc6hQLdmNVDeD/GMBWsm2vLV7eJUYs66MmEDNuxUCAKGkq6 +ahq97BvIxYSazQ== +-----END CERTIFICATE----- + +TWCA Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ +VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG +EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB +IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx +QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC +oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP +4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r +y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG +9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC +mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW +QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY +T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny +Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +Security Communication RootCA2 +============================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc +U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh +dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC +SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy +aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++ ++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R +3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV +spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K +EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8 +QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB +CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj +u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk +3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q +tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29 +mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +EC-ACC +====== +-----BEGIN CERTIFICATE----- +MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB8zELMAkGA1UE +BhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2VydGlmaWNhY2lvIChOSUYgUS0w +ODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYD +VQQLEyxWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UE +CxMsSmVyYXJxdWlhIEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMT +BkVDLUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQGEwJFUzE7 +MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYt +SSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZl +Z2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJh +cnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R85iK +w5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm4CgPukLjbo73FCeT +ae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaVHMf5NLWUhdWZXqBIoH7nF2W4onW4 +HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNdQlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0a +E9jD2z3Il3rucO2n5nzbcc8tlGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw +0JDnJwIDAQABo4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4opvpXY0wfwYD +VR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBodHRwczovL3d3dy5jYXRjZXJ0 +Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5l +dC92ZXJhcnJlbCAwDQYJKoZIhvcNAQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJ +lF7W2u++AVtd0x7Y/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNa +Al6kSBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhyRp/7SNVe +l+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOSAgu+TGbrIP65y7WZf+a2 +E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xlnJ2lYJU6Un/10asIbvPuW/mIPX64b24D +5EI= +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions RootCA 2011 +======================================================= +-----BEGIN CERTIFICATE----- +MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1IxRDBCBgNVBAoT +O0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9y +aXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z +IFJvb3RDQSAyMDExMB4XDTExMTIwNjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYT +AkdSMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z +IENlcnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNo +IEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPzdYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI +1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJfel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa +71HFK9+WXesyHgLacEnsbgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u +8yBRQlqD75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSPFEDH +3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNVHRMBAf8EBTADAQH/ +MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp5dgTBCPuQSUwRwYDVR0eBEAwPqA8 +MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQub3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQu +b3JnMA0GCSqGSIb3DQEBBQUAA4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVt +XdMiKahsog2p6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8 +TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7dIsXRSZMFpGD +/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8AcysNnq/onN694/BtZqhFLKPM58N +7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXIl7WdmplNsDz4SgCbZN2fOUvRJ9e4 +-----END CERTIFICATE----- + +Actalis Authentication Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM +BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE +AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky +MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz +IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ +wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa +by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6 +zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f +YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2 +oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l +EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7 +hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8 +EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5 +jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY +iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI +WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0 +JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx +K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+ +Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC +4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo +2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz +lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem +OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9 +vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +Trustis FPS Root CA +=================== +-----BEGIN CERTIFICATE----- +MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQG +EwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQLExNUcnVzdGlzIEZQUyBSb290 +IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTExMzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNV +BAoTD1RydXN0aXMgTGltaXRlZDEcMBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQ +RUN+AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihHiTHcDnlk +H5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjjvSkCqPoc4Vu5g6hBSLwa +cY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zt +o3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlBOrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEA +AaNTMFEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAd +BgNVHQ4EFgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01GX2c +GE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmWzaD+vkAMXBJV+JOC +yinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP41BIy+Q7DsdwyhEQsb8tGD+pmQQ9P +8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZEf1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHV +l/9D7S3B2l0pKoU/rGXuhg8FjZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYl +iB6XzCGcKQENZetX2fNXlrtIzYE= +-----END CERTIFICATE----- + +StartCom Certification Authority +================================ +-----BEGIN CERTIFICATE----- +MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN +U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu +ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0 +NjM3WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk +LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg +U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y +o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/ +Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d +eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt +2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z +6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ +osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/ +untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc +UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT +37uMdBNSSwIDAQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFulF2mHMMo0aEPQ +Qa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCCATgwLgYIKwYBBQUHAgEWImh0 +dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cu +c3RhcnRzc2wuY29tL2ludGVybWVkaWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENv +bW1lcmNpYWwgKFN0YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0 +aGUgc2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93d3cuc3RhcnRzc2wuY29t +L3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBG +cmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5 +fPGFf59Jb2vKXfuM/gTFwWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWm +N3PH/UvSTa0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst0OcN +Org+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNcpRJvkrKTlMeIFw6T +tn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKlCcWw0bdT82AUuoVpaiF8H3VhFyAX +e2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVFP0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA +2MFrLH9ZXF2RsXAiV+uKa0hK1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBs +HvUwyKMQ5bLmKhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE +JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ8dCAWZvLMdib +D4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnmfyWl8kgAwKQB2j8= +-----END CERTIFICATE----- + +StartCom Certification Authority G2 +=================================== +-----BEGIN CERTIFICATE----- +MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMN +U3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +RzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UE +ChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8O +o1XJJZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsDvfOpL9HG +4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnooD/Uefyf3lLE3PbfHkffi +Aez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/Q0kGi4xDuFby2X8hQxfqp0iVAXV16iul +Q5XqFYSdCI0mblWbq9zSOdIxHWDirMxWRST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbs +O+wmETRIjfaAKxojAuuKHDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8H +vKTlXcxNnw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM0D4L +nMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/iUUjXuG+v+E5+M5iS +FGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9Ha90OrInwMEePnWjFqmveiJdnxMa +z6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHgTuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJ +KoZIhvcNAQELBQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K +2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfXUfEpY9Z1zRbk +J4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl6/2o1PXWT6RbdejF0mCy2wl+ +JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG +/+gyRr61M3Z3qAFdlsHB1b6uJcDJHgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTc +nIhT76IxW1hPkWLIwpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/Xld +blhYXzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5lIxKVCCIc +l85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoohdVddLHRDiBYmxOlsGOm +7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulrso8uBtjRkcfGEvRM/TAXw8HaOFvjqerm +obp573PYtlNXLfbQ4ddI +-----END CERTIFICATE----- + +Buypass Class 2 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X +DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1 +g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn +9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b +/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU +CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff +awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI +zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn +Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX +Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs +M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI +osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S +aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd +DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD +LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0 +oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC +wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS +CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN +rJgWVqA= +-----END CERTIFICATE----- + +Buypass Class 3 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X +DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH +sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR +5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh +7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ +ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH +2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV +/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ +RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA +Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq +j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G +uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG +Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8 +ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2 +KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz +6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug +UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe +eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi +Cp/HuZc= +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 3 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx +MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK +9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU +NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF +iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W +0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr +AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb +fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT +ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h +P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw== +-----END CERTIFICATE----- + +EE Certification Centre Root CA +=============================== +-----BEGIN CERTIFICATE----- +MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG +EwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEoMCYGA1UEAwwfRUUgQ2Vy +dGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIw +MTAxMDMwMTAxMDMwWhgPMjAzMDEyMTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlB +UyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRy +ZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUyeuuOF0+W2Ap7kaJjbMeM +TC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvObntl8jixwKIy72KyaOBhU8E2lf/slLo2 +rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIwWFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw +93X2PaRka9ZP585ArQ/dMtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtN +P2MbRMNE1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/zQas8fElyalL1BSZ +MEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEF +BQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEFBQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+Rj +xY6hUFaTlrg4wCQiZrxTFGGVv9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqM +lIpPnTX/dqQGE5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u +uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIWiAYLtqZLICjU +3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/vGVCJYMzpJJUPwssd8m92kMfM +dcGWxZ0= +-----END CERTIFICATE----- + +TURKTRUST Certificate Services Provider Root 2007 +================================================= +-----BEGIN CERTIFICATE----- +MIIEPTCCAyWgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvzE/MD0GA1UEAww2VMOcUktUUlVTVCBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP +MA0GA1UEBwwGQW5rYXJhMV4wXAYDVQQKDFVUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg +QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgQXJhbMSxayAyMDA3MB4X +DTA3MTIyNTE4MzcxOVoXDTE3MTIyMjE4MzcxOVowgb8xPzA9BgNVBAMMNlTDnFJLVFJVU1QgRWxl +a3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTELMAkGA1UEBhMCVFIxDzAN +BgNVBAcMBkFua2FyYTFeMFwGA1UECgxVVMOcUktUUlVTVCBCaWxnaSDEsGxldGnFn2ltIHZlIEJp +bGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7Fni4gKGMpIEFyYWzEsWsgMjAwNzCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKu3PgqMyKVYFeaK7yc9SrToJdPNM8Ig3BnuiD9N +YvDdE3ePYakqtdTyuTFYKTsvP2qcb3N2Je40IIDu6rfwxArNK4aUyeNgsURSsloptJGXg9i3phQv +KUmi8wUG+7RP2qFsmmaf8EMJyupyj+sA1zU511YXRxcw9L6/P8JorzZAwan0qafoEGsIiveGHtya +KhUG9qPw9ODHFNRRf8+0222vR5YXm3dx2KdxnSQM9pQ/hTEST7ruToK4uT6PIzdezKKqdfcYbwnT +rqdUKDT74eA7YH2gvnmJhsifLfkKS8RQouf9eRbHegsYz85M733WB2+Y8a+xwXrXgTW4qhe04MsC +AwEAAaNCMEAwHQYDVR0OBBYEFCnFkKslrxHkYb+j/4hhkeYO/pyBMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAQDdr4Ouwo0RSVgrESLFF6QSU2TJ/s +Px+EnWVUXKgWAkD6bho3hO9ynYYKVZ1WKKxmLNA6VpM0ByWtCLCPyA8JWcqdmBzlVPi5RX9ql2+I +aE1KBiY3iAIOtsbWcpnOa3faYjGkVh+uX4132l32iPwa2Z61gfAyuOOI0JzzaqC5mxRZNTZPz/OO +Xl0XrRWV2N2y1RVuAE6zS89mlOTgzbUF2mNXi+WzqtvALhyQRNsaXRik7r4EW5nVcV9VZWRi1aKb +BFmGyGJ353yCRWo9F7/snXUMrqNvWtMvmDb08PUZqxFdyKbjKlhqQgnDvZImZjINXQhVdP+MmNAK +poRq0Tl9 +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe +Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE +LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD +ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA +BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv +KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z +p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC +AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ +4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y +eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw +MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G +PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw +OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm +2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV +dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph +X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 EV 2009 +================================= +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS +egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh +zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T +7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60 +sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35 +11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv +cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v +ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El +MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp +b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh +c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+ +PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX +ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA +NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv +w9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +PSCProcert +========== +-----BEGIN CERTIFICATE----- +MIIJhjCCB26gAwIBAgIBCzANBgkqhkiG9w0BAQsFADCCAR4xPjA8BgNVBAMTNUF1dG9yaWRhZCBk +ZSBDZXJ0aWZpY2FjaW9uIFJhaXogZGVsIEVzdGFkbyBWZW5lem9sYW5vMQswCQYDVQQGEwJWRTEQ +MA4GA1UEBxMHQ2FyYWNhczEZMBcGA1UECBMQRGlzdHJpdG8gQ2FwaXRhbDE2MDQGA1UEChMtU2lz +dGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMUMwQQYDVQQLEzpTdXBl +cmludGVuZGVuY2lhIGRlIFNlcnZpY2lvcyBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMSUw +IwYJKoZIhvcNAQkBFhZhY3JhaXpAc3VzY2VydGUuZ29iLnZlMB4XDTEwMTIyODE2NTEwMFoXDTIw +MTIyNTIzNTk1OVowgdExJjAkBgkqhkiG9w0BCQEWF2NvbnRhY3RvQHByb2NlcnQubmV0LnZlMQ8w +DQYDVQQHEwZDaGFjYW8xEDAOBgNVBAgTB01pcmFuZGExKjAoBgNVBAsTIVByb3ZlZWRvciBkZSBD +ZXJ0aWZpY2Fkb3MgUFJPQ0VSVDE2MDQGA1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZp +Y2FjaW9uIEVsZWN0cm9uaWNhMQswCQYDVQQGEwJWRTETMBEGA1UEAxMKUFNDUHJvY2VydDCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANW39KOUM6FGqVVhSQ2oh3NekS1wwQYalNo97BVC +wfWMrmoX8Yqt/ICV6oNEolt6Vc5Pp6XVurgfoCfAUFM+jbnADrgV3NZs+J74BCXfgI8Qhd19L3uA +3VcAZCP4bsm+lU/hdezgfl6VzbHvvnpC2Mks0+saGiKLt38GieU89RLAu9MLmV+QfI4tL3czkkoh +RqipCKzx9hEC2ZUWno0vluYC3XXCFCpa1sl9JcLB/KpnheLsvtF8PPqv1W7/U0HU9TI4seJfxPmO +EO8GqQKJ/+MMbpfg353bIdD0PghpbNjU5Db4g7ayNo+c7zo3Fn2/omnXO1ty0K+qP1xmk6wKImG2 +0qCZyFSTXai20b1dCl53lKItwIKOvMoDKjSuc/HUtQy9vmebVOvh+qBa7Dh+PsHMosdEMXXqP+UH +0quhJZb25uSgXTcYOWEAM11G1ADEtMo88aKjPvM6/2kwLkDd9p+cJsmWN63nOaK/6mnbVSKVUyqU +td+tFjiBdWbjxywbk5yqjKPK2Ww8F22c3HxT4CAnQzb5EuE8XL1mv6JpIzi4mWCZDlZTOpx+FIyw +Bm/xhnaQr/2v/pDGj59/i5IjnOcVdo/Vi5QTcmn7K2FjiO/mpF7moxdqWEfLcU8UC17IAggmosvp +r2uKGcfLFFb14dq12fy/czja+eevbqQ34gcnAgMBAAGjggMXMIIDEzASBgNVHRMBAf8ECDAGAQH/ +AgEBMDcGA1UdEgQwMC6CD3N1c2NlcnRlLmdvYi52ZaAbBgVghl4CAqASDBBSSUYtRy0yMDAwNDAz +Ni0wMB0GA1UdDgQWBBRBDxk4qpl/Qguk1yeYVKIXTC1RVDCCAVAGA1UdIwSCAUcwggFDgBStuyId +xuDSAaj9dlBSk+2YwU2u06GCASakggEiMIIBHjE+MDwGA1UEAxM1QXV0b3JpZGFkIGRlIENlcnRp +ZmljYWNpb24gUmFpeiBkZWwgRXN0YWRvIFZlbmV6b2xhbm8xCzAJBgNVBAYTAlZFMRAwDgYDVQQH +EwdDYXJhY2FzMRkwFwYDVQQIExBEaXN0cml0byBDYXBpdGFsMTYwNAYDVQQKEy1TaXN0ZW1hIE5h +Y2lvbmFsIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExQzBBBgNVBAsTOlN1cGVyaW50ZW5k +ZW5jaWEgZGUgU2VydmljaW9zIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExJTAjBgkqhkiG +9w0BCQEWFmFjcmFpekBzdXNjZXJ0ZS5nb2IudmWCAQowDgYDVR0PAQH/BAQDAgEGME0GA1UdEQRG +MESCDnByb2NlcnQubmV0LnZloBUGBWCGXgIBoAwMClBTQy0wMDAwMDKgGwYFYIZeAgKgEgwQUklG +LUotMzE2MzUzNzMtNzB2BgNVHR8EbzBtMEagRKBChkBodHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52 +ZS9sY3IvQ0VSVElGSUNBRE8tUkFJWi1TSEEzODRDUkxERVIuY3JsMCOgIaAfhh1sZGFwOi8vYWNy +YWl6LnN1c2NlcnRlLmdvYi52ZTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUHMAGGG2h0dHA6Ly9v +Y3NwLnN1c2NlcnRlLmdvYi52ZTBBBgNVHSAEOjA4MDYGBmCGXgMBAjAsMCoGCCsGAQUFBwIBFh5o +dHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9kcGMwDQYJKoZIhvcNAQELBQADggIBACtZ6yKZu4Sq +T96QxtGGcSOeSwORR3C7wJJg7ODU523G0+1ng3dS1fLld6c2suNUvtm7CpsR72H0xpkzmfWvADmN +g7+mvTV+LFwxNG9s2/NkAZiqlCxB3RWGymspThbASfzXg0gTB1GEMVKIu4YXx2sviiCtxQuPcD4q +uxtxj7mkoP3YldmvWb8lK5jpY5MvYB7Eqvh39YtsL+1+LrVPQA3uvFd359m21D+VJzog1eWuq2w1 +n8GhHVnchIHuTQfiSLaeS5UtQbHh6N5+LwUeaO6/u5BlOsju6rEYNxxik6SgMexxbJHmpHmJWhSn +FFAFTKQAVzAswbVhltw+HoSvOULP5dAssSS830DD7X9jSr3hTxJkhpXzsOfIt+FTvZLm8wyWuevo +5pLtp4EJFAv8lXrPj9Y0TzYS3F7RNHXGRoAvlQSMx4bEqCaJqD8Zm4G7UaRKhqsLEQ+xrmNTbSjq +3TNWOByyrYDT13K9mmyZY+gAu0F2BbdbmRiKw7gSXFbPVgx96OLP7bx0R/vu0xdOIk9W/1DzLuY5 +poLWccret9W6aAjtmcz9opLLabid+Qqkpj5PkygqYWwHJgD/ll9ohri4zspV4KuxPX+Y1zMOWj3Y +eMLEYC/HYvBhkdI4sPaeVdtAgAUSM84dkpvRabP/v/GSCmE1P93+hvS84Bpxs2Km +-----END CERTIFICATE----- + +China Internet Network Information Center EV Certificates Root +============================================================== +-----BEGIN CERTIFICATE----- +MIID9zCCAt+gAwIBAgIESJ8AATANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMCQ04xMjAwBgNV +BAoMKUNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyMUcwRQYDVQQDDD5D +aGluYSBJbnRlcm5ldCBOZXR3b3JrIEluZm9ybWF0aW9uIENlbnRlciBFViBDZXJ0aWZpY2F0ZXMg +Um9vdDAeFw0xMDA4MzEwNzExMjVaFw0zMDA4MzEwNzExMjVaMIGKMQswCQYDVQQGEwJDTjEyMDAG +A1UECgwpQ2hpbmEgSW50ZXJuZXQgTmV0d29yayBJbmZvcm1hdGlvbiBDZW50ZXIxRzBFBgNVBAMM +PkNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyIEVWIENlcnRpZmljYXRl +cyBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm35z7r07eKpkQ0H1UN+U8i6y +jUqORlTSIRLIOTJCBumD1Z9S7eVnAztUwYyZmczpwA//DdmEEbK40ctb3B75aDFk4Zv6dOtouSCV +98YPjUesWgbdYavi7NifFy2cyjw1l1VxzUOFsUcW9SxTgHbP0wBkvUCZ3czY28Sf1hNfQYOL+Q2H +klY0bBoQCxfVWhyXWIQ8hBouXJE0bhlffxdpxWXvayHG1VA6v2G5BY3vbzQ6sm8UY78WO5upKv23 +KzhmBsUs4qpnHkWnjQRmQvaPK++IIGmPMowUc9orhpFjIpryp9vOiYurXccUwVswah+xt54ugQEC +7c+WXmPbqOY4twIDAQABo2MwYTAfBgNVHSMEGDAWgBR8cks5x8DbYqVPm6oYNJKiyoOCWTAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUfHJLOcfA22KlT5uqGDSSosqD +glkwDQYJKoZIhvcNAQEFBQADggEBACrDx0M3j92tpLIM7twUbY8opJhJywyA6vPtI2Z1fcXTIWd5 +0XPFtQO3WKwMVC/GVhMPMdoG52U7HW8228gd+f2ABsqjPWYWqJ1MFn3AlUa1UeTiH9fqBk1jjZaM +7+czV0I664zBechNdn3e9rG3geCg+aF4RhcaVpjwTj2rHO3sOdwHSPdj/gauwqRcalsyiMXHM4Ws +ZkJHwlgkmeHlPuV1LI5D1l08eB6olYIpUNHRFrrvwb562bTYzB5MRuF3sTGrvSrIzo9uoV1/A3U0 +5K2JRVRevq4opbs/eHnrc7MKDf2+yfdWrPa37S+bISnHOLaVxATywy39FCqQmbkHzJ8= +-----END CERTIFICATE----- + +Swisscom Root CA 2 +================== +-----BEGIN CERTIFICATE----- +MIIF2TCCA8GgAwIBAgIQHp4o6Ejy5e/DfEoeWhhntjANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQG +EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy +dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMjAeFw0xMTA2MjQwODM4MTRaFw0zMTA2 +MjUwNzM4MTRaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln +aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAyMIIC +IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlUJOhJ1R5tMJ6HJaI2nbeHCOFvErjw0DzpPM +LgAIe6szjPTpQOYXTKueuEcUMncy3SgM3hhLX3af+Dk7/E6J2HzFZ++r0rk0X2s682Q2zsKwzxNo +ysjL67XiPS4h3+os1OD5cJZM/2pYmLcX5BtS5X4HAB1f2uY+lQS3aYg5oUFgJWFLlTloYhyxCwWJ +wDaCFCE/rtuh/bxvHGCGtlOUSbkrRsVPACu/obvLP+DHVxxX6NZp+MEkUp2IVd3Chy50I9AU/SpH +Wrumnf2U5NGKpV+GY3aFy6//SSj8gO1MedK75MDvAe5QQQg1I3ArqRa0jG6F6bYRzzHdUyYb3y1a +SgJA/MTAtukxGggo5WDDH8SQjhBiYEQN7Aq+VRhxLKX0srwVYv8c474d2h5Xszx+zYIdkeNL6yxS +NLCK/RJOlrDrcH+eOfdmQrGrrFLadkBXeyq96G4DsguAhYidDMfCd7Camlf0uPoTXGiTOmekl9Ab +mbeGMktg2M7v0Ax/lZ9vh0+Hio5fCHyqW/xavqGRn1V9TrALacywlKinh/LTSlDcX3KwFnUey7QY +Ypqwpzmqm59m2I2mbJYV4+by+PGDYmy7Velhk6M99bFXi08jsJvllGov34zflVEpYKELKeRcVVi3 +qPyZ7iVNTA6z00yPhOgpD/0QVAKFyPnlw4vP5w8CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw +HQYDVR0hBBYwFDASBgdghXQBUwIBBgdghXQBUwIBMBIGA1UdEwEB/wQIMAYBAf8CAQcwHQYDVR0O +BBYEFE0mICKJS9PVpAqhb97iEoHF8TwuMB8GA1UdIwQYMBaAFE0mICKJS9PVpAqhb97iEoHF8Twu +MA0GCSqGSIb3DQEBCwUAA4ICAQAyCrKkG8t9voJXiblqf/P0wS4RfbgZPnm3qKhyN2abGu2sEzsO +v2LwnN+ee6FTSA5BesogpxcbtnjsQJHzQq0Qw1zv/2BZf82Fo4s9SBwlAjxnffUy6S8w5X2lejjQ +82YqZh6NM4OKb3xuqFp1mrjX2lhIREeoTPpMSQpKwhI3qEAMw8jh0FcNlzKVxzqfl9NX+Ave5XLz +o9v/tdhZsnPdTSpxsrpJ9csc1fV5yJmz/MFMdOO0vSk3FQQoHt5FRnDsr7p4DooqzgB53MBfGWcs +a0vvaGgLQ+OswWIJ76bdZWGgr4RVSJFSHMYlkSrQwSIjYVmvRRGFHQEkNI/Ps/8XciATwoCqISxx +OQ7Qj1zB09GOInJGTB2Wrk9xseEFKZZZ9LuedT3PDTcNYtsmjGOpI99nBjx8Oto0QuFmtEYE3saW +mA9LSHokMnWRn6z3aOkquVVlzl1h0ydw2Df+n7mvoC5Wt6NlUe07qxS/TFED6F+KBZvuim6c779o ++sjaC+NCydAXFJy3SuCvkychVSa1ZC+N8f+mQAWFBVzKBxlcCxMoTFh/wqXvRdpg065lYZ1Tg3TC +rvJcwhbtkj6EPnNgiLx29CzP0H1907he0ZESEOnN3col49XtmS++dYFLJPlFRpTJKSFTnCZFqhMX +5OfNeOI5wSsSnqaeG8XmDtkx2Q== +-----END CERTIFICATE----- + +Swisscom Root EV CA 2 +===================== +-----BEGIN CERTIFICATE----- +MIIF4DCCA8igAwIBAgIRAPL6ZOJ0Y9ON/RAdBB92ylgwDQYJKoZIhvcNAQELBQAwZzELMAkGA1UE +BhMCY2gxETAPBgNVBAoTCFN3aXNzY29tMSUwIwYDVQQLExxEaWdpdGFsIENlcnRpZmljYXRlIFNl +cnZpY2VzMR4wHAYDVQQDExVTd2lzc2NvbSBSb290IEVWIENBIDIwHhcNMTEwNjI0MDk0NTA4WhcN +MzEwNjI1MDg0NTA4WjBnMQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsT +HERpZ2l0YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxHjAcBgNVBAMTFVN3aXNzY29tIFJvb3QgRVYg +Q0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMT3HS9X6lds93BdY7BxUglgRCgz +o3pOCvrY6myLURYaVa5UJsTMRQdBTxB5f3HSek4/OE6zAMaVylvNwSqD1ycfMQ4jFrclyxy0uYAy +Xhqdk/HoPGAsp15XGVhRXrwsVgu42O+LgrQ8uMIkqBPHoCE2G3pXKSinLr9xJZDzRINpUKTk4Rti +GZQJo/PDvO/0vezbE53PnUgJUmfANykRHvvSEaeFGHR55E+FFOtSN+KxRdjMDUN/rhPSays/p8Li +qG12W0OfvrSdsyaGOx9/5fLoZigWJdBLlzin5M8J0TbDC77aO0RYjb7xnglrPvMyxyuHxuxenPaH +Za0zKcQvidm5y8kDnftslFGXEBuGCxobP/YCfnvUxVFkKJ3106yDgYjTdLRZncHrYTNaRdHLOdAG +alNgHa/2+2m8atwBz735j9m9W8E6X47aD0upm50qKGsaCnw8qyIL5XctcfaCNYGu+HuB5ur+rPQa +m3Rc6I8k9l2dRsQs0h4rIWqDJ2dVSqTjyDKXZpBy2uPUZC5f46Fq9mDU5zXNysRojddxyNMkM3Ox +bPlq4SjbX8Y96L5V5jcb7STZDxmPX2MYWFCBUWVv8p9+agTnNCRxunZLWB4ZvRVgRaoMEkABnRDi +xzgHcgplwLa7JSnaFp6LNYth7eVxV4O1PHGf40+/fh6Bn0GXAgMBAAGjgYYwgYMwDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdIQQWMBQwEgYHYIV0AVMCAgYHYIV0AVMCAjASBgNVHRMBAf8ECDAGAQH/AgED +MB0GA1UdDgQWBBRF2aWBbj2ITY1x0kbBbkUe88SAnTAfBgNVHSMEGDAWgBRF2aWBbj2ITY1x0kbB +bkUe88SAnTANBgkqhkiG9w0BAQsFAAOCAgEAlDpzBp9SSzBc1P6xXCX5145v9Ydkn+0UjrgEjihL +j6p7jjm02Vj2e6E1CqGdivdj5eu9OYLU43otb98TPLr+flaYC/NUn81ETm484T4VvwYmneTwkLbU +wp4wLh/vx3rEUMfqe9pQy3omywC0Wqu1kx+AiYQElY2NfwmTv9SoqORjbdlk5LgpWgi/UOGED1V7 +XwgiG/W9mR4U9s70WBCCswo9GcG/W6uqmdjyMb3lOGbcWAXH7WMaLgqXfIeTK7KK4/HsGOV1timH +59yLGn602MnTihdsfSlEvoqq9X46Lmgxk7lq2prg2+kupYTNHAq4Sgj5nPFhJpiTt3tm7JFe3VE/ +23MPrQRYCd0EApUKPtN236YQHoA96M2kZNEzx5LH4k5E4wnJTsJdhw4Snr8PyQUQ3nqjsTzyP6Wq +J3mtMX0f/fwZacXduT98zca0wjAefm6S139hdlqP65VNvBFuIXxZN5nQBrz5Bm0yFqXZaajh3DyA +HmBR3NdUIR7KYndP+tiPsys6DXhyyWhBWkdKwqPrGtcKqzwyVcgKEZzfdNbwQBUdyLmPtTbFr/gi +uMod89a2GQ+fYWVq6nTIfI/DT11lgh/ZDYnadXL77/FHZxOzyNEZiCcmmpl5fx7kLD977vHeTYuW +l8PVP3wbI+2ksx0WckNLIOFZfsLorSa/ovc= +-----END CERTIFICATE----- + +CA Disig Root R1 +================ +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAMMDmu5QkG4oMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNVBAYTAlNLMRMw +EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp +ZyBSb290IFIxMB4XDTEyMDcxOTA5MDY1NloXDTQyMDcxOTA5MDY1NlowUjELMAkGA1UEBhMCU0sx +EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp +c2lnIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqw3j33Jijp1pedxiy +3QRkD2P9m5YJgNXoqqXinCaUOuiZc4yd39ffg/N4T0Dhf9Kn0uXKE5Pn7cZ3Xza1lK/oOI7bm+V8 +u8yN63Vz4STN5qctGS7Y1oprFOsIYgrY3LMATcMjfF9DCCMyEtztDK3AfQ+lekLZWnDZv6fXARz2 +m6uOt0qGeKAeVjGu74IKgEH3G8muqzIm1Cxr7X1r5OJeIgpFy4QxTaz+29FHuvlglzmxZcfe+5nk +CiKxLU3lSCZpq+Kq8/v8kiky6bM+TR8noc2OuRf7JT7JbvN32g0S9l3HuzYQ1VTW8+DiR0jm3hTa +YVKvJrT1cU/J19IG32PK/yHoWQbgCNWEFVP3Q+V8xaCJmGtzxmjOZd69fwX3se72V6FglcXM6pM6 +vpmumwKjrckWtc7dXpl4fho5frLABaTAgqWjR56M6ly2vGfb5ipN0gTco65F97yLnByn1tUD3AjL +LhbKXEAz6GfDLuemROoRRRw1ZS0eRWEkG4IupZ0zXWX4Qfkuy5Q/H6MMMSRE7cderVC6xkGbrPAX +ZcD4XW9boAo0PO7X6oifmPmvTiT6l7Jkdtqr9O3jw2Dv1fkCyC2fg69naQanMVXVz0tv/wQFx1is +XxYb5dKj6zHbHzMVTdDypVP1y+E9Tmgt2BLdqvLmTZtJ5cUoobqwWsagtQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUiQq0OJMa5qvum5EY+fU8PjXQ +04IwDQYJKoZIhvcNAQEFBQADggIBADKL9p1Kyb4U5YysOMo6CdQbzoaz3evUuii+Eq5FLAR0rBNR +xVgYZk2C2tXck8An4b58n1KeElb21Zyp9HWc+jcSjxyT7Ff+Bw+r1RL3D65hXlaASfX8MPWbTx9B +LxyE04nH4toCdu0Jz2zBuByDHBb6lM19oMgY0sidbvW9adRtPTXoHqJPYNcHKfyyo6SdbhWSVhlM +CrDpfNIZTUJG7L399ldb3Zh+pE3McgODWF3vkzpBemOqfDqo9ayk0d2iLbYq/J8BjuIQscTK5Gfb +VSUZP/3oNn6z4eGBrxEWi1CXYBmCAMBrTXO40RMHPuq2MU/wQppt4hF05ZSsjYSVPCGvxdpHyN85 +YmLLW1AL14FABZyb7bq2ix4Eb5YgOe2kfSnbSM6C3NQCjR0EMVrHS/BsYVLXtFHCgWzN4funodKS +ds+xDzdYpPJScWc/DIh4gInByLUfkmO+p3qKViwaqKactV2zY9ATIKHrkWzQjX2v3wvkF7mGnjix +lAxYjOBVqjtjbZqJYLhkKpLGN/R+Q0O3c+gB53+XD9fyexn9GtePyfqFa3qdnom2piiZk4hA9z7N +UaPK6u95RyG1/jLix8NRb76AdPCkwzryT+lf3xkK8jsTQ6wxpLPn6/wY1gGp8yqPNg7rtLG8t0zJ +a7+h89n07eLw4+1knj0vllJPgFOL +-----END CERTIFICATE----- + +CA Disig Root R2 +================ +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw +EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp +ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx +EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp +c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC +w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia +xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7 +A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S +GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV +g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa +5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE +koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A +Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i +Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u +Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV +sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je +dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8 +1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx +mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01 +utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0 +sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg +UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV +7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +ACCVRAIZ1 +========= +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB +SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1 +MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH +UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM +jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0 +RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD +aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ +0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG +WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7 +8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR +5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J +9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK +Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw +Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu +Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM +Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA +QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh +AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA +YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj +AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA +IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk +aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0 +dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2 +MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI +hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E +R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN +YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49 +nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ +TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3 +sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg +Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd +3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p +EfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +TWCA Global Root CA +=================== +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT +CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD +QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK +EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C +nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV +r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR +Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV +tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W +KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99 +sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p +yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn +kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI +zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g +cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M +8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg +/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg +lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP +A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m +i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8 +EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3 +zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0= +-----END CERTIFICATE----- + +TeliaSonera Root CA v1 +====================== +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE +CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4 +MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW +VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+ +6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA +3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k +B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn +Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH +oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3 +F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ +oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7 +gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc +TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB +AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW +DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm +zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW +pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV +G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc +c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT +JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2 +qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6 +Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems +WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +E-Tugra Certification Authority +=============================== +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNVBAYTAlRSMQ8w +DQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamls +ZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN +ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMw +NTEyMDk0OFoXDTIzMDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmEx +QDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxl +cmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQD +DB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEA4vU/kwVRHoViVF56C/UYB4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vd +hQd2h8y/L5VMzH2nPbxHD5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5K +CKpbknSFQ9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEoq1+g +ElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3Dk14opz8n8Y4e0ypQ +BaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcHfC425lAcP9tDJMW/hkd5s3kc91r0 +E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsutdEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gz +rt48Ue7LE3wBf4QOXVGUnhMMti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAq +jqFGOjGY5RH8zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn +rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUXU8u3Zg5mTPj5 +dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6Jyr+zE7S6E5UMA8GA1UdEwEB +/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEG +MA0GCSqGSIb3DQEBCwUAA4ICAQAFNzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAK +kEh47U6YA5n+KGCRHTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jO +XKqYGwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c77NCR807 +VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3+GbHeJAAFS6LrVE1Uweo +a2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WKvJUawSg5TB9D0pH0clmKuVb8P7Sd2nCc +dlqMQ1DujjByTd//SffGqWfZbawCEeI6FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEV +KV0jq9BgoRJP3vQXzTLlyb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gT +Dx4JnW2PAJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpDy4Q0 +8ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8dNL/+I5c30jn6PQ0G +C7TbO6Orb1wdtn7os4I07QZcJA== +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 2 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx +MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ +SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F +vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970 +2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV +WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy +YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4 +r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf +vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR +3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg== +-----END CERTIFICATE----- + +Atos TrustedRoot 2011 +===================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU +cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4 +MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG +A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV +hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr +54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+ +DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320 +HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR +z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R +l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ +bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h +k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh +TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9 +61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G +3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +QuoVadis Root CA 1 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakE +PBtVwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWerNrwU8lm +PNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF34168Xfuw6cwI2H44g4hWf6 +Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh4Pw5qlPafX7PGglTvF0FBM+hSo+LdoIN +ofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXpUhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/l +g6AnhF4EwfWQvTA9xO+oabw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV +7qJZjqlc3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/GKubX +9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSthfbZxbGL0eUQMk1f +iyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KOTk0k+17kBL5yG6YnLUlamXrXXAkg +t3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOtzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZI +hvcNAQELBQADggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2cDMT/uFPpiN3 +GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUNqXsCHKnQO18LwIE6PWThv6ct +Tr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP ++V04ikkwj+3x6xn0dxoxGE1nVGwvb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh +3jRJjehZrJ3ydlo28hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fa +wx/kNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNjZgKAvQU6 +O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhpq1467HxpvMc7hU6eFbm0 +FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFtnh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOV +hMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +QuoVadis Root CA 2 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFh +ZiFfqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMWn4rjyduY +NM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ymc5GQYaYDFCDy54ejiK2t +oIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+o +MiwMzAkd056OXbxMmO7FGmh77FOm6RQ1o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+l +V0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZo +L1NesNKqIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQ +sSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43ehvNURG3YBZwjgQQvD +6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxh +lRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALGcC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZI +hvcNAQELBQADggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RCroijQ1h5fq7K +pVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9 +x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgz +dWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6X +U/IyAgkwo1jwDQHVcsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+Nw +mNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNgKCLjsZWD +zYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeMHVOyToV7BjjHLPj4sHKN +JeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4WSr2Rz0ZiC3oheGe7IUIarFsNMkd7Egr +O3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +QuoVadis Root CA 3 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286 +IxSR/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNuFoM7pmRL +Mon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXRU7Ox7sWTaYI+FrUoRqHe +6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+cra1AdHkrAj80//ogaX3T7mH1urPnMNA3 +I4ZyYUUpSFlob3emLoG+B01vr87ERRORFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3U +VDmrJqMz6nWB2i3ND0/kA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f7 +5li59wzweyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634RylsSqi +Md5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBpVzgeAVuNVejH38DM +dyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0QA4XN8f+MFrXBsj6IbGB/kE+V9/Yt +rQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZI +hvcNAQELBQADggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnIFUBhynLWcKzS +t/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5WvvoxXqA/4Ti2Tk08HS6IT7SdEQ +TXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFgu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9Du +DcpmvJRPpq3t/O5jrFc/ZSXPsoaP0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGib +Ih6BJpsQBJFxwAYf3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmD +hPbl8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+DhcI00iX +0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HNPlopNLk9hM6xZdRZkZFW +dSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ywaZWWDYWGWVjUTR939+J399roD1B0y2 +PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +DigiCert Assured ID Root G2 +=========================== +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgw +MTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSAn61UQbVH +35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4HteccbiJVMWWXvdMX0h5i89vq +bFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9HpEgjAALAcKxHad3A2m67OeYfcgnDmCXRw +VWmvo2ifv922ebPynXApVfSr/5Vh88lAbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OP +YLfykqGxvYmJHzDNw6YuYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+Rn +lTGNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTO +w0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPIQW5pJ6d1Ee88hjZv +0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I0jJmwYrA8y8678Dj1JGG0VDjA9tz +d29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4GnilmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAW +hsI6yLETcDbYz+70CjTVW0z9B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0M +jomZmWzwPDCvON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +DigiCert Assured ID Root G3 +=========================== +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD +VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJfZn4f5dwb +RXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17QRSAPWXYQ1qAk8C3eNvJs +KTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgF +UaFNN6KDec6NHSrkhDAKBggqhkjOPQQDAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5Fy +YZ5eEJJZVrmDxxDnOOlYJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy +1vUhZscv6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +DigiCert Global Root G2 +======================= +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUx +MjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJ +kTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO +3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauV +BJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyM +UNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQAB +o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu +5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsr +F9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0U +WTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBH +QRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/ +iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +DigiCert Global Root G3 +======================= +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD +VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw +MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k +aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O +YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp +Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y +3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34 +VOKa5Vt8sycX +-----END CERTIFICATE----- + +DigiCert Trusted Root G4 +======================== +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEw +HwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEp +pz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9o +k3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7Fsa +vOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY +QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6 +MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtm +mnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7 +f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFH +dL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8 +oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYY +ZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdNOj6PWTkiU0Tr +yF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy +7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iah +ixTXTBmyUEFxPT9NcCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN +5r5N0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie4u1Ki7wb +/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mIr/OSmbaz5mEP0oUA51Aa +5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tK +G48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP +82Z+ +-----END CERTIFICATE----- + +WoSign +====== +-----BEGIN CERTIFICATE----- +MIIFdjCCA16gAwIBAgIQXmjWEXGUY1BWAGjzPsnFkTANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQG +EwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxKjAoBgNVBAMTIUNlcnRpZmljYXRpb24g +QXV0aG9yaXR5IG9mIFdvU2lnbjAeFw0wOTA4MDgwMTAwMDFaFw0zOTA4MDgwMTAwMDFaMFUxCzAJ +BgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRlZDEqMCgGA1UEAxMhQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkgb2YgV29TaWduMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +vcqNrLiRFVaXe2tcesLea9mhsMMQI/qnobLMMfo+2aYpbxY94Gv4uEBf2zmoAHqLoE1UfcIiePyO +CbiohdfMlZdLdNiefvAA5A6JrkkoRBoQmTIPJYhTpA2zDxIIFgsDcSccf+Hb0v1naMQFXQoOXXDX +2JegvFNBmpGN9J42Znp+VsGQX+axaCA2pIwkLCxHC1l2ZjC1vt7tj/id07sBMOby8w7gLJKA84X5 +KIq0VC6a7fd2/BVoFutKbOsuEo/Uz/4Mx1wdC34FMr5esAkqQtXJTpCzWQ27en7N1QhatH/YHGkR ++ScPewavVIMYe+HdVHpRaG53/Ma/UkpmRqGyZxq7o093oL5d//xWC0Nyd5DKnvnyOfUNqfTq1+ez +EC8wQjchzDBwyYaYD8xYTYO7feUapTeNtqwylwA6Y3EkHp43xP901DfA4v6IRmAR3Qg/UDaruHqk +lWJqbrDKaiFaafPz+x1wOZXzp26mgYmhiMU7ccqjUu6Du/2gd/Tkb+dC221KmYo0SLwX3OSACCK2 +8jHAPwQ+658geda4BmRkAjHXqc1S+4RFaQkAKtxVi8QGRkvASh0JWzko/amrzgD5LkhLJuYwTKVY +yrREgk/nkR4zw7CT/xH8gdLKH3Ep3XZPkiWvHYG3Dy+MwwbMLyejSuQOmbp8HkUff6oZRZb9/D0C +AwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOFmzw7R +8bNLtwYgFP6HEtX2/vs+MA0GCSqGSIb3DQEBBQUAA4ICAQCoy3JAsnbBfnv8rWTjMnvMPLZdRtP1 +LOJwXcgu2AZ9mNELIaCJWSQBnfmvCX0KI4I01fx8cpm5o9dU9OpScA7F9dY74ToJMuYhOZO9sxXq +T2r09Ys/L3yNWC7F4TmgPsc9SnOeQHrAK2GpZ8nzJLmzbVUsWh2eJXLOC62qx1ViC777Y7NhRCOj +y+EaDveaBk3e1CNOIZZbOVtXHS9dCF4Jef98l7VNg64N1uajeeAz0JmWAjCnPv/So0M/BVoG6kQC +2nz4SNAzqfkHx5Xh9T71XXG68pWpdIhhWeO/yloTunK0jF02h+mmxTwTv97QRCbut+wucPrXnbes +5cVAWubXbHssw1abR80LzvobtCHXt2a49CUwi1wNuepnsvRtrtWhnk/Yn+knArAdBtaP4/tIEp9/ +EaEQPkxROpaw0RPxx9gmrjrKkcRpnd8BKWRRb2jaFOwIQZeQjdCygPLPwj2/kWjFgGcexGATVdVh +mVd8upUPYUk6ynW8yQqTP2cOEvIo4jEbwFcW3wh8GcF+Dx+FHgo2fFt+J7x6v+Db9NpSvd4MVHAx +kUOVyLzwPt0JfjBkUO1/AaQzZ01oT74V77D2AhGiGxMlOtzCWfHjXEa7ZywCRuoeSKbmW9m1vFGi +kpbbqsY3Iqb+zCB0oy2pLmvLwIIRIbWTee5Ehr7XHuQe+w== +-----END CERTIFICATE----- + +WoSign China +============ +-----BEGIN CERTIFICATE----- +MIIFWDCCA0CgAwIBAgIQUHBrzdgT/BtOOzNy0hFIjTANBgkqhkiG9w0BAQsFADBGMQswCQYDVQQG +EwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxGzAZBgNVBAMMEkNBIOayg+mAmuagueiv +geS5pjAeFw0wOTA4MDgwMTAwMDFaFw0zOTA4MDgwMTAwMDFaMEYxCzAJBgNVBAYTAkNOMRowGAYD +VQQKExFXb1NpZ24gQ0EgTGltaXRlZDEbMBkGA1UEAwwSQ0Eg5rKD6YCa5qC56K+B5LmmMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0EkhHiX8h8EqwqzbdoYGTufQdDTc7WU1/FDWiD+k +8H/rD195L4mx/bxjWDeTmzj4t1up+thxx7S8gJeNbEvxUNUqKaqoGXqW5pWOdO2XCld19AXbbQs5 +uQF/qvbW2mzmBeCkTVL829B0txGMe41P/4eDrv8FAxNXUDf+jJZSEExfv5RxadmWPgxDT74wwJ85 +dE8GRV2j1lY5aAfMh09Qd5Nx2UQIsYo06Yms25tO4dnkUkWMLhQfkWsZHWgpLFbE4h4TV2TwYeO5 +Ed+w4VegG63XX9Gv2ystP9Bojg/qnw+LNVgbExz03jWhCl3W6t8Sb8D7aQdGctyB9gQjF+BNdeFy +b7Ao65vh4YOhn0pdr8yb+gIgthhid5E7o9Vlrdx8kHccREGkSovrlXLp9glk3Kgtn3R46MGiCWOc +76DbT52VqyBPt7D3h1ymoOQ3OMdc4zUPLK2jgKLsLl3Az+2LBcLmc272idX10kaO6m1jGx6KyX2m ++Jzr5dVjhU1zZmkR/sgO9MHHZklTfuQZa/HpelmjbX7FF+Ynxu8b22/8DU0GAbQOXDBGVWCvOGU6 +yke6rCzMRh+yRpY/8+0mBe53oWprfi1tWFxK1I5nuPHa1UaKJ/kR8slC/k7e3x9cxKSGhxYzoacX +GKUN5AXlK8IrC6KVkLn9YDxOiT7nnO4fuwECAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFOBNv9ybQV0T6GTwp+kVpOGBwboxMA0GCSqGSIb3DQEBCwUA +A4ICAQBqinA4WbbaixjIvirTthnVZil6Xc1bL3McJk6jfW+rtylNpumlEYOnOXOvEESS5iVdT2H6 +yAa+Tkvv/vMx/sZ8cApBWNromUuWyXi8mHwCKe0JgOYKOoICKuLJL8hWGSbueBwj/feTZU7n85iY +r83d2Z5AiDEoOqsuC7CsDCT6eiaY8xJhEPRdF/d+4niXVOKM6Cm6jBAyvd0zaziGfjk9DgNyp115 +j0WKWa5bIW4xRtVZjc8VX90xJc/bYNaBRHIpAlf2ltTW/+op2znFuCyKGo3Oy+dCMYYFaA6eFN0A +kLppRQjbbpCBhqcqBT/mhDn4t/lXX0ykeVoQDF7Va/81XwVRHmyjdanPUIPTfPRm94KNPQx96N97 +qA4bLJyuQHCH2u2nFoJavjVsIE4iYdm8UXrNemHcSxH5/mc0zy4EZmFcV5cjjPOGG0jfKq+nwf/Y +jj4Du9gqsPoUJbJRa4ZDhS4HIxaAjUz7tGM7zMN07RujHv41D198HRaG9Q7DlfEvr10lO1Hm13ZB +ONFLAzkopR6RctR9q5czxNM+4Gm2KHmgCY0c0f9BckgG/Jou5yD5m6Leie2uPAmvylezkolwQOQv +T8Jwg0DXJCxr5wkf09XHwQj02w47HAcLQxGEIYbpgNR12KvxAmLBsX5VYc8T1yaw15zLKYs4SgsO +kI26oQ== +-----END CERTIFICATE----- + +COMODO RSA Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCBhTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMTE5MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR6FSS0gpWsawNJN3Fz0Rn +dJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8Xpz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZ +FGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+ +5eNu/Nio5JIk2kNrYrhV/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pG +x8cgoLEfZd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z+pUX +2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7wqP/0uK3pN/u6uPQL +OvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZahSL0896+1DSJMwBGB7FY79tOi4lu3 +sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVICu9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+C +GCe01a60y1Dma/RMhnEw6abfFobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5 +WdYgGq/yapiqcrxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvlwFTPoCWOAvn9sKIN9SCYPBMt +rFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+ +nq6PK7o9mfjYcwlYRm6mnPTXJ9OV2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSg +tZx8jb8uk2IntznaFxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwW +sRqZCuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiKboHGhfKp +pC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmckejkk9u+UJueBPSZI9FoJA +zMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yLS0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHq +ZJx64SIDqZxubw5lT2yHh17zbqD5daWbQOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk52 +7RH89elWsn2/x20Kk4yl0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7I +LaZRfyHBNVOFBkpdn627G190 +-----END CERTIFICATE----- + +USERTrust RSA Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCBiDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK +ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK +ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCAEmUXNg7D2wiz +0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2j +Y0K2dvKpOyuR+OJv0OwWIJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFn +RghRy4YUVD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O ++T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT79uq +/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6c0Plfg6lZrEpfDKE +Y1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJM +lXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8 +yexDJtC/QV9AqURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+ +eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPFUp/L+M+ZBn8b2kMVn54CVVeW +FPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KOVWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ +7l8wXEskEVX/JJpuXior7gtNn3/3ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQ +Eg9zKC7F4iRO/Fjs8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM +8WcRiQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYzeSf7dNXGi +FSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZXHlKYC6SQK5MNyosycdi +yA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9c +J2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRBVXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGw +sAvgnEzDHNb842m1R0aBL6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gx +Q+6IHdfGjjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +USERTrust ECC Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqfloI+d61SRvU8Za2EurxtW2 +0eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6Ez +nPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNV +HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBB +HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu +9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R4 +=========================== +-----BEGIN CERTIFICATE----- +MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprl +OQcJFspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAwDgYDVR0P +AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61FuOJAf/sKbvu+M8k8o4TV +MAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGXkPoUVy0D7O48027KqGx2vKLeuwIgJ6iF +JzWbVsaj8kfSt24bAgAXqmemFZHe+pTsewv4n4Q= +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R5 +=========================== +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6 +SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmS +h5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYIKoZIzj0EAwMDaAAwZQIxAOVpEslu28Yx +uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7 +yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +Staat der Nederlanden Root CA - G3 +================================== +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwGA1UE +CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJsYW5kZW4g +Um9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloXDTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMC +TkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5l +ZGVybGFuZGVuIFJvb3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4y +olQPcPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WWIkYFsO2t +x1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqXxz8ecAgwoNzFs21v0IJy +EavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFyKJLZWyNtZrVtB0LrpjPOktvA9mxjeM3K +Tj215VKb8b475lRgsGYeCasH/lSJEULR9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUur +mkVLoR9BvUhTFXFkC4az5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU5 +1nus6+N86U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7Ngzp +07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHPbMk7ccHViLVlvMDo +FxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXtBznaqB16nzaeErAMZRKQFWDZJkBE +41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTtXUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleu +yjWcLhL75LpdINyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD +U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwpLiniyMMB8jPq +KqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8Ipf3YF3qKS9Ysr1YvY2WTxB1 +v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixpgZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA +8KCWAg8zxXHzniN9lLf9OtMJgwYh/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b +8KKaa8MFSu1BYBQw0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0r +mj1AfsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq4BZ+Extq +1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR1VmiiXTTn74eS9fGbbeI +JG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/QFH1T/U67cjF68IeHRaVesd+QnGTbksV +tzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM94B7IWcnMFk= +-----END CERTIFICATE----- + +Staat der Nederlanden EV Root CA +================================ +-----BEGIN CERTIFICATE----- +MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJOTDEeMBwGA1UE +CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFhdCBkZXIgTmVkZXJsYW5kZW4g +RVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0yMjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5M +MR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRl +cmxhbmRlbiBFViBSb290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkk +SzrSM4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nCUiY4iKTW +O0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3dZ//BYY1jTw+bbRcwJu+r +0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46prfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8 +Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13lpJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gV +XJrm0w912fxBmJc+qiXbj5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr +08C+eKxCKFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS/ZbV +0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0XcgOPvZuM5l5Tnrmd +74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH1vI4gnPah1vlPNOePqc7nvQDs/nx +fRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrPpx9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwa +ivsnuL8wbqg7MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI +eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u2dfOWBfoqSmu +c0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHSv4ilf0X8rLiltTMMgsT7B/Zq +5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTCwPTxGfARKbalGAKb12NMcIxHowNDXLldRqAN +b/9Zjr7dn3LDWyvfjFvO5QxGbJKyCqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tN +f1zuacpzEPuKqf2evTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi +5Dp6Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIaGl6I6lD4 +WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeLeG9QgkRQP2YGiqtDhFZK +DyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGy +eUN51q1veieQA6TqJIc/2b3Z6fJfUEkc7uzXLg== +-----END CERTIFICATE----- + +IdenTrust Commercial Root CA 1 +============================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBS +b290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQwMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzES +MBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENB +IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ld +hNlT3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU+ehcCuz/ +mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gpS0l4PJNgiCL8mdo2yMKi +1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1bVoE/c40yiTcdCMbXTMTEl3EASX2MN0C +XZ/g1Ue9tOsbobtJSdifWwLziuQkkORiT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl +3ZBWzvurpWCdxJ35UrCLvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzy +NeVJSQjKVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZKdHzV +WYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHTc+XvvqDtMwt0viAg +xGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hvl7yTmvmcEpB4eoCHFddydJxVdHix +uuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5NiGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZI +hvcNAQELBQADggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwtLRvM7Kqas6pg +ghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93nAbowacYXVKV7cndJZ5t+qnt +ozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmV +YjzlVYA211QC//G5Xc7UI2/YRYRKW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUX +feu+h1sXIFRRk0pTAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/ro +kTLql1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG4iZZRHUe +2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZmUlO+KWA2yUPHGNiiskz +Z2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7R +cGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +IdenTrust Public Sector Root CA 1 +================================= +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3Rv +ciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcNMzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJV +UzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBS +b290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTy +P4o7ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGyRBb06tD6 +Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlSbdsHyo+1W/CD80/HLaXI +rcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF/YTLNiCBWS2ab21ISGHKTN9T0a9SvESf +qy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoS +mJxZZoY+rfGwyj4GD3vwEUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFn +ol57plzy9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9VGxyh +LrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ2fjXctscvG29ZV/v +iDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsVWaFHVCkugyhfHMKiq3IXAAaOReyL +4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gDW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8B +Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMw +DQYJKoZIhvcNAQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHVDRDtfULAj+7A +mgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9TaDKQGXSc3z1i9kKlT/YPyNt +GtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8GlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFt +m6/n6J91eEyrRjuazr8FGF1NFTwWmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMx +NRF4eKLg6TCMf4DfWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4 +Mhn5+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJtshquDDI +ajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhAGaQdp/lLQzfcaFpPz+vC +ZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ +3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +Entrust Root Certification Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVy +bXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ug +b25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIw +HhcNMDkwNzA3MTcyNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoT +DUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMx +OTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25s +eTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP +/vaCeb9zYQYKpSfYs1/TRU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXz +HHfV1IWNcCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hWwcKU +s/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1U1+cPvQXLOZprE4y +TGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0jaWvYkxN4FisZDQSA/i2jZRjJKRx +AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ6 +0B7vfec7aVHUbI2fkBJmqzANBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5Z +iXMRrEPR9RP/jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v1fN2D807iDgi +nWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4RnAuknZoh8/CbCzB428Hch0P+ +vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmHVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xO +e4pIb4tF9g== +-----END CERTIFICATE----- + +Entrust Root Certification Authority - EC1 +========================================== +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkGA1UEBhMCVVMx +FjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVn +YWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXpl +ZCB1c2Ugb25seTEzMDEGA1UEAxMqRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +IC0gRUMxMB4XDTEyMTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYw +FAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2Fs +LXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQg +dXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt +IEVDMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHy +AsWfoPZb1YsGGYZPUxBtByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef +9eNi1KlHBz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVCR98crlOZF7ZvHH3h +vxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nXhTcGtXsI/esni0qU+eH6p44mCOh8 +kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +CFCA EV ROOT +============ +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEwMC4GA1UE +CgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNB +IEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkxMjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEw +MC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQD +DAxDRkNBIEVWIFJPT1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnV +BU03sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpLTIpTUnrD +7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5/ZOkVIBMUtRSqy5J35DN +uF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp7hZZLDRJGqgG16iI0gNyejLi6mhNbiyW +ZXvKWfry4t3uMCz7zEasxGPrb382KzRzEpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7 +xzbh72fROdOXW3NiGUgthxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9f +py25IGvPa931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqotaK8K +gWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNgTnYGmE69g60dWIol +hdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfVPKPtl8MeNPo4+QgO48BdK4PRVmrJ +tqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hvcWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAf +BgNVHSMEGDAWgBTj/i39KNALtbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB +/wQEAwIBBjAdBgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObTej/tUxPQ4i9q +ecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdLjOztUmCypAbqTuv0axn96/Ua +4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBSESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sG +E5uPhnEFtC+NiWYzKXZUmhH4J/qyP5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfX +BDrDMlI1Dlb4pd19xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjn +aH9dCi77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN5mydLIhy +PDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe/v5WOaHIz16eGWRGENoX +kbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3C +ekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H5 +========================================================= +-----BEGIN CERTIFICATE----- +MIIEJzCCAw+gAwIBAgIHAI4X/iQggTANBgkqhkiG9w0BAQsFADCBsTELMAkGA1UEBhMCVFIxDzAN +BgNVBAcMBkFua2FyYTFNMEsGA1UECgxEVMOcUktUUlVTVCBCaWxnaSDEsGxldGnFn2ltIHZlIEJp +bGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7Fni4xQjBABgNVBAMMOVTDnFJLVFJVU1Qg +RWxla3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSBINTAeFw0xMzA0MzAw +ODA3MDFaFw0yMzA0MjgwODA3MDFaMIGxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMU0w +SwYDVQQKDERUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnE +n2kgSGl6bWV0bGVyaSBBLsWeLjFCMEAGA1UEAww5VMOcUktUUlVTVCBFbGVrdHJvbmlrIFNlcnRp +ZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIEg1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEApCUZ4WWe60ghUEoI5RHwWrom/4NZzkQqL/7hzmAD/I0Dpe3/a6i6zDQGn1k19uwsu537 +jVJp45wnEFPzpALFp/kRGml1bsMdi9GYjZOHp3GXDSHHmflS0yxjXVW86B8BSLlg/kJK9siArs1m +ep5Fimh34khon6La8eHBEJ/rPCmBp+EyCNSgBbGM+42WAA4+Jd9ThiI7/PS98wl+d+yG6w8z5UNP +9FR1bSmZLmZaQ9/LXMrI5Tjxfjs1nQ/0xVqhzPMggCTTV+wVunUlm+hkS7M0hO8EuPbJbKoCPrZV +4jI3X/xml1/N1p7HIL9Nxqw/dV8c7TKcfGkAaZHjIxhT6QIDAQABo0IwQDAdBgNVHQ4EFgQUVpkH +HtOsDGlktAxQR95DLL4gwPswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI +hvcNAQELBQADggEBAJ5FdnsXSDLyOIspve6WSk6BGLFRRyDN0GSxDsnZAdkJzsiZ3GglE9Rc8qPo +BP5yCccLqh0lVX6Wmle3usURehnmp349hQ71+S4pL+f5bFgWV1Al9j4uPqrtd3GqqpmWRgqujuwq +URawXs3qZwQcWDD1YIq9pr1N5Za0/EKJAWv2cMhQOQwt1WbZyNKzMrcbGW3LM/nfpeYVhDfwwvJl +lpKQd/Ct9JDpEXjXk4nAPQu6KfTomZ1yju2dL+6SfaHx/126M2CFYv4HAqGEVka+lgqaE9chTLd8 +B59OTj+RdPsnnRHM3eaxynFNExc5JsUpISuTKWqW+qtB4Uu2NQvAmxU= +-----END CERTIFICATE----- + +TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H6 +========================================================= +-----BEGIN CERTIFICATE----- +MIIEJjCCAw6gAwIBAgIGfaHyZeyKMA0GCSqGSIb3DQEBCwUAMIGxMQswCQYDVQQGEwJUUjEPMA0G +A1UEBwwGQW5rYXJhMU0wSwYDVQQKDERUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmls +acWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjFCMEAGA1UEAww5VMOcUktUUlVTVCBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIEg2MB4XDTEzMTIxODA5 +MDQxMFoXDTIzMTIxNjA5MDQxMFowgbExCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExTTBL +BgNVBAoMRFTDnFJLVFJVU1QgQmlsZ2kgxLBsZXRpxZ9pbSB2ZSBCaWxpxZ9pbSBHw7x2ZW5sacSf +aSBIaXptZXRsZXJpIEEuxZ4uMUIwQAYDVQQDDDlUw5xSS1RSVVNUIEVsZWt0cm9uaWsgU2VydGlm +aWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLEgSDYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCdsGjW6L0UlqMACprx9MfMkU1xeHe59yEmFXNRFpQJRwXiM/VomjX/3EsvMsew7eKC5W/a +2uqsxgbPJQ1BgfbBOCK9+bGlprMBvD9QFyv26WZV1DOzXPhDIHiTVRZwGTLmiddk671IUP320EED +wnS3/faAz1vFq6TWlRKb55cTMgPp1KtDWxbtMyJkKbbSk60vbNg9tvYdDjTu0n2pVQ8g9P0pu5Fb +HH3GQjhtQiht1AH7zYiXSX6484P4tZgvsycLSF5W506jM7NE1qXyGJTtHB6plVxiSvgNZ1GpryHV ++DKdeboaX+UEVU0TRv/yz3THGmNtwx8XEsMeED5gCLMxAgMBAAGjQjBAMB0GA1UdDgQWBBTdVRcT +9qzoSCHK77Wv0QAy7Z6MtTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG +9w0BAQsFAAOCAQEAb1gNl0OqFlQ+v6nfkkU/hQu7VtMMUszIv3ZnXuaqs6fvuay0EBQNdH49ba3R +fdCaqaXKGDsCQC4qnFAUi/5XfldcEQlLNkVS9z2sFP1E34uXI9TDwe7UU5X+LEr+DXCqu4svLcsy +o4LyVN/Y8t3XSHLuSqMplsNEzm61kod2pLv0kmzOLBQJZo6NrRa1xxsJYTvjIKIDgI6tflEATseW +hvtDmHd9KMeP2Cpu54Rvl0EpABZeTeIT6lnAY2c6RPuY/ATTMHKm9ocJV612ph1jmv3XZch4gyt1 +O6VbuA1df74jrlZVlFjvH4GMKrLN5ptjnhi85WsGtAuYSyher4hYyw== +-----END CERTIFICATE----- + +Certinomis - Root CA +==================== +-----BEGIN CERTIFICATE----- +MIIFkjCCA3qgAwIBAgIBATANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJGUjETMBEGA1UEChMK +Q2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxHTAbBgNVBAMTFENlcnRpbm9taXMg +LSBSb290IENBMB4XDTEzMTAyMTA5MTcxOFoXDTMzMTAyMTA5MTcxOFowWjELMAkGA1UEBhMCRlIx +EzARBgNVBAoTCkNlcnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMR0wGwYDVQQDExRD +ZXJ0aW5vbWlzIC0gUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANTMCQos +P5L2fxSeC5yaah1AMGT9qt8OHgZbn1CF6s2Nq0Nn3rD6foCWnoR4kkjW4znuzuRZWJflLieY6pOo +d5tK8O90gC3rMB+12ceAnGInkYjwSond3IjmFPnVAy//ldu9n+ws+hQVWZUKxkd8aRi5pwP5ynap +z8dvtF4F/u7BUrJ1Mofs7SlmO/NKFoL21prbcpjp3vDFTKWrteoB4owuZH9kb/2jJZOLyKIOSY00 +8B/sWEUuNKqEUL3nskoTuLAPrjhdsKkb5nPJWqHZZkCqqU2mNAKthH6yI8H7KsZn9DS2sJVqM09x +RLWtwHkziOC/7aOgFLScCbAK42C++PhmiM1b8XcF4LVzbsF9Ri6OSyemzTUK/eVNfaoqoynHWmgE +6OXWk6RiwsXm9E/G+Z8ajYJJGYrKWUM66A0ywfRMEwNvbqY/kXPLynNvEiCL7sCCeN5LLsJJwx3t +FvYk9CcbXFcx3FXuqB5vbKziRcxXV4p1VxngtViZSTYxPDMBbRZKzbgqg4SGm/lg0h9tkQPTYKbV +PZrdd5A9NaSfD171UkRpucC63M9933zZxKyGIjK8e2uR73r4F2iw4lNVYC2vPsKD2NkJK/DAZNuH +i5HMkesE/Xa0lZrmFAYb1TQdvtj/dBxThZngWVJKYe2InmtJiUZ+IFrZ50rlau7SZRFDAgMBAAGj +YzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTvkUz1pcMw6C8I +6tNxIqSSaHh02TAfBgNVHSMEGDAWgBTvkUz1pcMw6C8I6tNxIqSSaHh02TANBgkqhkiG9w0BAQsF +AAOCAgEAfj1U2iJdGlg+O1QnurrMyOMaauo++RLrVl89UM7g6kgmJs95Vn6RHJk/0KGRHCwPT5iV +WVO90CLYiF2cN/z7ZMF4jIuaYAnq1fohX9B0ZedQxb8uuQsLrbWwF6YSjNRieOpWauwK0kDDPAUw +Pk2Ut59KA9N9J0u2/kTO+hkzGm2kQtHdzMjI1xZSg081lLMSVX3l4kLr5JyTCcBMWwerx20RoFAX +lCOotQqSD7J6wWAsOMwaplv/8gzjqh8c3LigkyfeY+N/IZ865Z764BNqdeuWXGKRlI5nU7aJ+BIJ +y29SWwNyhlCVCNSNh4YVH5Uk2KRvms6knZtt0rJ2BobGVgjF6wnaNsIbW0G+YSrjcOa4pvi2WsS9 +Iff/ql+hbHY5ZtbqTFXhADObE5hjyW/QASAJN1LnDE8+zbz1X5YnpyACleAu6AdBBR8Vbtaw5Bng +DwKTACdyxYvRVB9dSsNAl35VpnzBMwQUAR1JIGkLGZOdblgi90AMRgwjY/M50n92Uaf0yKHxDHYi +I0ZSKS3io0EHVmmY0gUJvGnHWmHNj4FgFU2A3ZDifcRQ8ow7bkrHxuaAKzyBvBGAFhAn1/DNP3nM +cyrDflOR1m749fPH0FFNjkulW+YZFzvWgQncItzujrnEj1PhZ7szuIgVRs/taTX/dQ1G885x4cVr +hkIGuUE= +-----END CERTIFICATE----- diff --git a/www/analytics/core/DataTable.php b/www/analytics/core/DataTable.php index b70ef4bd..221af531 100644 --- a/www/analytics/core/DataTable.php +++ b/www/analytics/core/DataTable.php @@ -1,6 +1,6 @@ * ### The Basics - * + * * DataTables consist of rows and each row consists of columns. A column value can be * a numeric, a string or an array. - * + * * Every row has an ID. The ID is either the index of the row or {@link ID_SUMMARY_ROW}. - * + * * DataTables are hierarchical data structures. Each row can also contain an additional * nested sub-DataTable (commonly referred to as a 'subtable'). - * + * * Both DataTables and DataTable rows can hold **metadata**. _DataTable metadata_ is information * regarding all the data, such as the site or period that the data is for. _Row metadata_ * is information regarding that row, such as a browser logo or website URL. - * + * * Finally, all DataTables contain a special _summary_ row. This row, if it exists, is * always at the end of the DataTable. - * + * * ### Populating DataTables - * + * * Data can be added to DataTables in three different ways. You can either: - * + * * 1. create rows one by one and add them through {@link addRow()} then truncate if desired, * 2. create an array of DataTable\Row instances or an array of arrays and add them using * {@link addRowsFromArray()} or {@link addRowsFromSimpleArray()} * then truncate if desired, * 3. or set the maximum number of allowed rows (with {@link setMaximumAllowedRows()}) * and add rows one by one. - * + * * If you want to eventually truncate your data (standard practice for all Piwik plugins), * the third method is the most memory efficient. It is, unfortunately, not always possible * to use since it requires that the data be sorted before adding. - * + * * ### Manipulating DataTables - * + * * There are two ways to manipulate a DataTable. You can either: - * + * * 1. manually iterate through each row and manipulate the data, * 2. or you can use predefined filters. - * + * * A filter is a class that has a 'filter' method which will manipulate a DataTable in * some way. There are several predefined Filters that allow you to do common things, * such as, - * + * * - add a new column to each row, * - add new metadata to each row, * - modify an existing column value for each row, * - sort an entire DataTable, * - and more. - * + * * Using these filters instead of writing your own code will increase code clarity and * reduce code redundancy. Additionally, filters have the advantage that they can be * applied to DataTable\Map instances. So you can visit every DataTable in a {@link DataTable\Map} * without having to write a recursive visiting function. - * + * * All predefined filters exist in the **Piwik\DataTable\BaseFilter** namespace. - * + * * _Note: For convenience, [anonymous functions](http://www.php.net/manual/en/functions.anonymous.php) * can be used as DataTable filters._ - * + * * ### Applying Filters - * + * * Filters can be applied now (via {@link filter()}), or they can be applied later (via * {@link queueFilter()}). - * + * * Filters that sort rows or manipulate the number of rows should be applied right away. * Non-essential, presentation filters should be queued. - * + * * ### Learn more - * + * * - See **{@link ArchiveProcessor}** to learn how DataTables are persisted. - * + * * ### Examples - * + * * **Populating a DataTable** - * + * * // adding one row at a time * $dataTable = new DataTable(); * $dataTable->addRow(new Row(array( @@ -115,7 +114,7 @@ require_once PIWIK_INCLUDE_PATH . '/core/Common.php'; * Row::COLUMNS => array('label' => 'thing2', 'nb_visits' => 2, 'nb_actions' => 2), * Row::METADATA => array('url' => 'http://thing2.com') * ))); - * + * * // using an array of rows * $dataTable = new DataTable(); * $dataTable->addRowsFromArray(array( @@ -128,32 +127,32 @@ require_once PIWIK_INCLUDE_PATH . '/core/Common.php'; * Row::METADATA => array('url' => 'http://thing2.com') * ) * )); - * + * * // using a "simple" array * $dataTable->addRowsFromSimpleArray(array( * array('label' => 'thing1', 'nb_visits' => 1, 'nb_actions' => 1), * array('label' => 'thing2', 'nb_visits' => 2, 'nb_actions' => 2) * )); - * + * * **Getting & setting metadata** - * + * * $dataTable = \Piwik\Plugins\Referrers\API::getInstance()->getSearchEngines($idSite = 1, $period = 'day', $date = '2007-07-24'); * $oldPeriod = $dataTable->metadata['period']; - * $dataTable->metadata['period'] = Period::factory('week', Date::factory('2013-10-18')); - * + * $dataTable->metadata['period'] = Period\Factory::build('week', Date::factory('2013-10-18')); + * * **Serializing & unserializing** - * + * * $maxRowsInTable = Config::getInstance()->General['datatable_archiving_maximum_rows_standard'];j - * + * * $dataTable = // ... build by aggregating visits ... * $serializedData = $dataTable->getSerialized($maxRowsInTable, $maxRowsInSubtable = $maxRowsInTable, * $columnToSortBy = Metrics::INDEX_NB_VISITS); - * + * * $serializedDataTable = $serializedData[0]; * $serailizedSubTable = $serializedData[$idSubtable]; - * + * * **Filtering for an API method** - * + * * public function getMyReport($idSite, $period, $date, $segment = false, $expanded = false) * { * $dataTable = Archive::getDataTableFromArchive('MyPlugin_MyReport', $idSite, $period, $date, $segment, $expanded); @@ -162,16 +161,16 @@ require_once PIWIK_INCLUDE_PATH . '/core/Common.php'; * $dataTable->queueFilter('ColumnCallbackAddMetadata', array('label', 'url', __NAMESPACE__ . '\getUrlFromLabelForMyReport')); * return $dataTable; * } - * + * * * @api */ -class DataTable implements DataTableInterface +class DataTable implements DataTableInterface, \IteratorAggregate, \ArrayAccess { const MAX_DEPTH_DEFAULT = 15; /** Name for metadata that describes when a report was archived. */ - const ARCHIVED_DATE_METADATA_NAME = 'archived_date'; + const ARCHIVED_DATE_METADATA_NAME = 'ts_archived'; /** Name for metadata that describes which columns are empty and should not be shown. */ const EMPTY_COLUMNS_METADATA_NAME = 'empty_column'; @@ -182,14 +181,14 @@ class DataTable implements DataTableInterface /** * Name for metadata that describes how individual columns should be aggregated when {@link addDataTable()} * or {@link Piwik\DataTable\Row::sumRow()} is called. - * + * * This metadata value must be an array that maps column names with valid operations. Valid aggregation operations are: - * + * * - `'skip'`: do nothing * - `'max'`: does `max($column1, $column2)` * - `'min'`: does `min($column1, $column2)` * - `'sum'`: does `$column1 + $column2` - * + * * See {@link addDataTable()} and {@link DataTable\Row::sumRow()} for more information. */ const COLUMN_AGGREGATION_OPS_METADATA_NAME = 'column_aggregation_ops'; @@ -200,6 +199,13 @@ class DataTable implements DataTableInterface /** The original label of the Summary Row. */ const LABEL_SUMMARY_ROW = -1; + /** + * Name for metadata that contains extra {@link Piwik\Plugin\ProcessedMetric}s for a DataTable. + * These metrics will be added in addition to the ones specified in the table's associated + * {@link Piwik\Plugin\Report} class. + */ + const EXTRA_PROCESSED_METRICS_METADATA_NAME = 'extra_processed_metrics'; + /** * Maximum nesting level. */ @@ -258,6 +264,13 @@ class DataTable implements DataTableInterface */ protected $queuedFilters = array(); + /** + * List of disabled filter names eg 'Limit' or 'Sort' + * + * @var array + */ + protected $disabledFilters = array(); + /** * We keep track of the number of rows before applying the LIMIT filter that deletes some rows * @@ -291,7 +304,7 @@ class DataTable implements DataTableInterface /** * Table metadata. Read [this](#class-desc-the-basics) to learn more. - * + * * Any data that describes the data held in the table's rows should go here. * * @var array @@ -326,15 +339,44 @@ class DataTable implements DataTableInterface && isset($this->rows) ) { $depth++; - foreach ($this->getRows() as $row) { + foreach ($this->rows as $row) { Common::destroy($row); } + if (isset($this->summaryRow)) { + Common::destroy($this->summaryRow); + } unset($this->rows); - Manager::getInstance()->setTableDeleted($this->getId()); + Manager::getInstance()->setTableDeleted($this->currentId); $depth--; } } + /** + * Clone. Called when cloning the datatable. We need to make sure to create a new datatableId. + * If we do not increase tableId it can result in segmentation faults when destructing a datatable. + */ + public function __clone() + { + // registers this instance to the manager + $this->currentId = Manager::getInstance()->addTable($this); + } + + public function setLabelsHaveChanged() + { + $this->indexNotUpToDate = true; + } + + /** + * @ignore + * does not update the summary row! + */ + public function setRows($rows) + { + unset($this->rows); + $this->rows = $rows; + $this->indexNotUpToDate = true; + } + /** * Sorts the DataTable rows using the supplied callback function. * @@ -344,16 +386,16 @@ class DataTable implements DataTableInterface */ public function sort($functionCallback, $columnSortedBy) { - $this->indexNotUpToDate = true; - $this->tableSortedBy = $columnSortedBy; + $this->setTableSortedBy($columnSortedBy); + usort($this->rows, $functionCallback); - if ($this->enableRecursiveSort === true) { - foreach ($this->getRows() as $row) { - if (($idSubtable = $row->getIdSubDataTable()) !== null) { - $table = Manager::getInstance()->getTable($idSubtable); - $table->enableRecursiveSort(); - $table->sort($functionCallback, $columnSortedBy); + if ($this->isSortRecursiveEnabled()) { + foreach ($this->getRowsWithoutSummaryRow() as $row) { + $subTable = $row->getSubtable(); + if ($subTable) { + $subTable->enableRecursiveSort(); + $subTable->sort($functionCallback, $columnSortedBy); } } } @@ -380,6 +422,23 @@ class DataTable implements DataTableInterface $this->enableRecursiveSort = true; } + /** + * @ignore + */ + public function isSortRecursiveEnabled() + { + return $this->enableRecursiveSort === true; + } + + /** + * @ignore + */ + public function setTableSortedBy($column) + { + $this->indexNotUpToDate = true; + $this->tableSortedBy = $column; + } + /** * Enables recursive filtering. If this method is called then the {@link filter()} method * will apply filters to every subtable in addition to this instance. @@ -389,9 +448,17 @@ class DataTable implements DataTableInterface $this->enableRecursiveFilters = true; } + /** + * @ignore + */ + public function disableRecursiveFilters() + { + $this->enableRecursiveFilters = false; + } + /** * Applies a filter to this datatable. - * + * * If {@link enableRecursiveFilters()} was called, the filter will be applied * to all subtables as well. * @@ -410,6 +477,10 @@ class DataTable implements DataTableInterface return; } + if (in_array($className, $this->disabledFilters)) { + return; + } + if (!class_exists($className, true)) { $className = 'Piwik\DataTable\Filter\\' . $className; } @@ -426,10 +497,50 @@ class DataTable implements DataTableInterface $filter->filter($this); } + /** + * Applies a filter to all subtables but not to this datatable. + * + * @param string|Closure $className Class name, eg. `"Sort"` or "Piwik\DataTable\Filters\Sort"`. If no + * namespace is supplied, `Piwik\DataTable\BaseFilter` is assumed. This parameter + * can also be a closure that takes a DataTable as its first parameter. + * @param array $parameters Array of extra parameters to pass to the filter. + */ + public function filterSubtables($className, $parameters = array()) + { + foreach ($this->getRowsWithoutSummaryRow() as $row) { + $subtable = $row->getSubtable(); + if ($subtable) { + $subtable->filter($className, $parameters); + $subtable->filterSubtables($className, $parameters); + } + } + } + + /** + * Adds a filter and a list of parameters to the list of queued filters of all subtables. These filters will be + * executed when {@link applyQueuedFilters()} is called. + * + * Filters that prettify the column values or don't need the full set of rows should be queued. This + * way they will be run after the table is truncated which will result in better performance. + * + * @param string|Closure $className The class name of the filter, eg. `'Limit'`. + * @param array $parameters The parameters to give to the filter, eg. `array($offset, $limit)` for the Limit filter. + */ + public function queueFilterSubtables($className, $parameters = array()) + { + foreach ($this->getRowsWithoutSummaryRow() as $row) { + $subtable = $row->getSubtable(); + if ($subtable) { + $subtable->queueFilter($className, $parameters); + $subtable->queueFilterSubtables($className, $parameters); + } + } + } + /** * Adds a filter and a list of parameters to the list of queued filters. These filters will be * executed when {@link applyQueuedFilters()} is called. - * + * * Filters that prettify the column values or don't need the full set of rows should be queued. This * way they will be run after the table is truncated which will result in better performance. * @@ -444,6 +555,23 @@ class DataTable implements DataTableInterface $this->queuedFilters[] = array('className' => $className, 'parameters' => $parameters); } + /** + * Disable a specific filter to run on this DataTable in case you have already applied this filter or if you will + * handle this filter manually by using a custom filter. Be aware if you disable a given filter, that filter won't + * be ever executed. Even if another filter calls this filter on the DataTable. + * + * @param string $className eg 'Limit' or 'Sort'. Passing a `Closure` or an `array($class, $methodName)` is not + * supported yet. We check for exact match. So if you disable 'Limit' and + * call `->filter('Limit')` this filter won't be executed. If you call + * `->filter('Piwik\DataTable\Filter\Limit')` that filter will be executed. See it as a + * feature. + * @ignore + */ + public function disableFilter($className) + { + $this->disabledFilters[] = $className; + } + /** * Applies all filters that were previously queued to the table. See {@link queueFilter()} * for more information. @@ -453,54 +581,59 @@ class DataTable implements DataTableInterface foreach ($this->queuedFilters as $filter) { $this->filter($filter['className'], $filter['parameters']); } - $this->queuedFilters = array(); + $this->clearQueuedFilters(); } /** * Sums a DataTable to this one. - * + * * This method will sum rows that have the same label. If a row is found in `$tableToSum` whose * label is not found in `$this`, the row will be added to `$this`. - * + * * If the subtables for this table are loaded, they will be summed as well. - * + * * Rows are summed together by summing individual columns. By default columns are summed by * adding one column value to another. Some columns cannot be aggregated this way. In these * cases, the {@link COLUMN_AGGREGATION_OPS_METADATA_NAME} * metadata can be used to specify a different type of operation. - * + * * @param \Piwik\DataTable $tableToSum + * @throws Exception */ - public function addDataTable(DataTable $tableToSum, $doAggregateSubTables = true) + public function addDataTable(DataTable $tableToSum) { - if($tableToSum instanceof Simple) { - if($tableToSum->getRowsCount() > 1) { + if ($tableToSum instanceof Simple) { + if ($tableToSum->getRowsCount() > 1) { throw new Exception("Did not expect a Simple table with more than one row in addDataTable()"); } $row = $tableToSum->getFirstRow(); $this->aggregateRowFromSimpleTable($row); } else { - foreach ($tableToSum->getRows() as $row) { - $this->aggregateRowWithLabel($row, $doAggregateSubTables); + $columnAggregationOps = $this->getMetadata(self::COLUMN_AGGREGATION_OPS_METADATA_NAME); + foreach ($tableToSum->getRowsWithoutSummaryRow() as $row) { + $this->aggregateRowWithLabel($row, $columnAggregationOps); + } + // we do not use getRows() as this method might get called 100k times when aggregating many datatables and + // this takes a lot of time. + $row = $tableToSum->getRowFromId(DataTable::ID_SUMMARY_ROW); + if ($row) { + $this->aggregateRowWithLabel($row, $columnAggregationOps); } } } /** * Returns the Row whose `'label'` column is equal to `$label`. - * + * * This method executes in constant time except for the first call which caches row * label => row ID mappings. - * + * * @param string $label `'label'` column value to look for. * @return Row|false The row if found, `false` if otherwise. */ public function getRowFromLabel($label) { $rowId = $this->getRowIdFromLabel($label); - if ($rowId instanceof Row) { - return $rowId; - } if (is_int($rowId) && isset($this->rows[$rowId])) { return $this->rows[$rowId]; } @@ -509,6 +642,9 @@ class DataTable implements DataTableInterface ) { return $this->summaryRow; } + if ($rowId instanceof Row) { + return $rowId; + } return false; } @@ -517,13 +653,12 @@ class DataTable implements DataTableInterface * * This method executes in constant time except for the first call which caches row * label => row ID mappings. - * + * * @param string $label `'label'` column value to look for. * @return int The row ID. */ public function getRowIdFromLabel($label) { - $this->rebuildIndexContinuously = true; if ($this->indexNotUpToDate) { $this->rebuildIndex(); } @@ -534,7 +669,8 @@ class DataTable implements DataTableInterface return self::ID_SUMMARY_ROW; } - $label = (string)$label; + $label = (string) $label; + if (!isset($this->rowsIndexByLabel[$label])) { return false; } @@ -559,15 +695,26 @@ class DataTable implements DataTableInterface /** * Rebuilds the index used to lookup a row by label + * @internal */ - private function rebuildIndex() + public function rebuildIndex() { - foreach ($this->getRows() as $id => $row) { + $this->rebuildIndexContinuously = true; + + foreach ($this->rows as $id => $row) { $label = $row->getColumn('label'); if ($label !== false) { $this->rowsIndexByLabel[$label] = $id; } } + + if ($this->summaryRow) { + $label = $this->summaryRow->getColumn('label'); + if ($label !== false) { + $this->rowsIndexByLabel[$label] = DataTable::ID_SUMMARY_ROW; + } + } + $this->indexNotUpToDate = false; } @@ -592,7 +739,7 @@ class DataTable implements DataTableInterface /** * Returns the row that has a subtable with ID matching `$idSubtable`. - * + * * @param int $idSubTable The subtable ID. * @return Row|false The row or false if not found */ @@ -609,7 +756,7 @@ class DataTable implements DataTableInterface /** * Adds a row to this table. - * + * * If {@link setMaximumAllowedRows()} was called and the current row count is * at the maximum, the new row will be summed to the summary row. If there is no summary row, * this row is set as the summary row. @@ -624,8 +771,9 @@ class DataTable implements DataTableInterface if ($this->maximumAllowedRows > 0 && $this->getRowsCount() >= $this->maximumAllowedRows - 1 ) { - if ($this->summaryRow === null) // create the summary row if necessary - { + if ($this->summaryRow === null) { + // create the summary row if necessary + $columns = array('label' => self::LABEL_SUMMARY_ROW) + $row->getColumns(); $this->addSummaryRow(new Row(array(Row::COLUMNS => $columns))); } else { @@ -649,7 +797,7 @@ class DataTable implements DataTableInterface /** * Sets the summary row. - * + * * _Note: A DataTable can have only one summary row._ * * @param Row $row @@ -684,7 +832,7 @@ class DataTable implements DataTableInterface /** * Adds a new row from an array. - * + * * You can add row metadata with this method. * * @param array $row eg. `array(Row::COLUMNS => array('visits' => 13, 'test' => 'toto'), @@ -697,7 +845,7 @@ class DataTable implements DataTableInterface /** * Adds a new row a from an array of column values. - * + * * Row metadata cannot be added with this method. * * @param array $row eg. `array('name' => 'google analytics', 'license' => 'commercial')` @@ -721,6 +869,14 @@ class DataTable implements DataTableInterface } } + /** + * @ignore + */ + public function getRowsWithoutSummaryRow() + { + return $this->rows; + } + /** * Returns an array containing all column values for the requested column. * @@ -739,7 +895,7 @@ class DataTable implements DataTableInterface /** * Returns an array containing all column values of columns whose name starts with `$name`. * - * @param $namePrefix The column name prefix. + * @param string $namePrefix The column name prefix. * @return array The array of column values. */ public function getColumnsStartingWith($namePrefix) @@ -759,10 +915,10 @@ class DataTable implements DataTableInterface /** * Returns the names of every column this DataTable contains. This method will return the * columns of the first row with data and will assume they occur in every other row as well. - * + * *_ Note: If column names still use their in-database INDEX values (@see Metrics), they * will be converted to their string name in the array result._ - * + * * @return array Array of string column names. */ public function getColumns() @@ -788,7 +944,7 @@ class DataTable implements DataTableInterface /** * Returns an array containing the requested metadata value of each row. - * + * * @param string $name The metadata column to return. * @return array */ @@ -803,7 +959,7 @@ class DataTable implements DataTableInterface /** * Returns the number of rows in the table including the summary row. - * + * * @return int */ public function getRowsCount() @@ -860,8 +1016,8 @@ class DataTable implements DataTableInterface { $totalCount = 0; foreach ($this->rows as $row) { - if (($idSubTable = $row->getIdSubDataTable()) !== null) { - $subTable = Manager::getInstance()->getTable($idSubTable); + $subTable = $row->getSubtable(); + if ($subTable) { $count = $subTable->getRowsCountRecursive(); $totalCount += $count; } @@ -893,15 +1049,14 @@ class DataTable implements DataTableInterface * @param string $oldName Old column name. * @param string $newName New column name. */ - public function renameColumn($oldName, $newName, $doRenameColumnsOfSubTables = true) + public function renameColumn($oldName, $newName) { - foreach ($this->getRows() as $row) { + foreach ($this->rows as $row) { $row->renameColumn($oldName, $newName); - if($doRenameColumnsOfSubTables) { - if (($idSubDataTable = $row->getIdSubDataTable()) !== null) { - Manager::getInstance()->getTable($idSubDataTable)->renameColumn($oldName, $newName); - } + $subTable = $row->getSubtable(); + if ($subTable) { + $subTable->renameColumn($oldName, $newName); } } if (!is_null($this->summaryRow)) { @@ -917,12 +1072,13 @@ class DataTable implements DataTableInterface */ public function deleteColumns($names, $deleteRecursiveInSubtables = false) { - foreach ($this->getRows() as $row) { + foreach ($this->rows as $row) { foreach ($names as $name) { $row->deleteColumn($name); } - if (($idSubDataTable = $row->getIdSubDataTable()) !== null) { - Manager::getInstance()->getTable($idSubDataTable)->deleteColumns($names, $deleteRecursiveInSubtables); + $subTable = $row->getSubtable(); + if ($subTable) { + $subTable->deleteColumns($names, $deleteRecursiveInSubtables); } } if (!is_null($this->summaryRow)) { @@ -977,12 +1133,12 @@ class DataTable implements DataTableInterface } if (is_null($limit)) { - $spliced = array_splice($this->rows, $offset); + array_splice($this->rows, $offset); } else { - $spliced = array_splice($this->rows, $offset, $limit); + array_splice($this->rows, $offset, $limit); } - $countDeleted = count($spliced); - return $countDeleted; + + return $count - $this->getRowsCount(); } /** @@ -1000,7 +1156,7 @@ class DataTable implements DataTableInterface /** * Returns a string representation of this DataTable for convenient viewing. - * + * * _Note: This uses the **html** DataTable renderer._ * * @return string @@ -1019,16 +1175,13 @@ class DataTable implements DataTableInterface * each row has a label that exists in the other table, and if each row * is equal to the row in the other table with the same label. The order * of rows is not important. - * + * * @param \Piwik\DataTable $table1 * @param \Piwik\DataTable $table2 * @return bool */ public static function isEqual(DataTable $table1, DataTable $table2) { - $rows1 = $table1->getRows(); - $rows2 = $table2->getRows(); - $table1->rebuildIndex(); $table2->rebuildIndex(); @@ -1036,6 +1189,8 @@ class DataTable implements DataTableInterface return false; } + $rows1 = $table1->getRows(); + foreach ($rows1 as $row1) { $row2 = $table2->getRowFromLabel($row1->getColumn('label')); if ($row2 === false @@ -1050,10 +1205,10 @@ class DataTable implements DataTableInterface /** * Serializes an entire DataTable hierarchy and returns the array of serialized DataTables. - * + * * The first element in the returned array will be the serialized representation of this DataTable. * Every subsequent element will be a serialized subtable. - * + * * This DataTable and subtables can optionally be truncated before being serialized. In most * cases where DataTables can become quite large, they should be truncated before being persisted * in an archive. @@ -1065,7 +1220,7 @@ class DataTable implements DataTableInterface * @param int $maximumRowsInSubDataTable If not null, defines the maximum number of rows allowed in serialized subtables. * @param string $columnToSortByBeforeTruncation The column to sort by before truncating, eg, `Metrics::INDEX_NB_VISITS`. * @return array The array of serialized DataTables: - * + * * array( * // this DataTable (the root) * 0 => 'eghuighahgaueytae78yaet7yaetae', @@ -1075,7 +1230,7 @@ class DataTable implements DataTableInterface * * // another subtable * 2 => 'gqegJHUIGHEQjkgneqjgnqeugUGEQHGUHQE', - * + * * // etc. * ); */ @@ -1084,9 +1239,12 @@ class DataTable implements DataTableInterface $columnToSortByBeforeTruncation = null) { static $depth = 0; + // make sure subtableIds are consecutive from 1 to N + static $subtableId = 0; if ($depth > self::$maximumDepthLevelAllowed) { $depth = 0; + $subtableId = 0; throw new Exception("Maximum recursion level of " . self::$maximumDepthLevelAllowed . " reached. Maybe you have set a DataTable\Row with an associated DataTable belonging already to one of its parent tables?"); } if (!is_null($maximumRowsInDataTable)) { @@ -1098,75 +1256,121 @@ class DataTable implements DataTableInterface ); } + $consecutiveSubtableIds = array(); + $forcedId = $subtableId; + // For each row, get the serialized row // If it is associated to a sub table, get the serialized table recursively ; // but returns all serialized tables and subtable in an array of 1 dimension $aSerializedDataTable = array(); - foreach ($this->rows as $row) { - if (($idSubTable = $row->getIdSubDataTable()) !== null) { - $subTable = null; - try { - $subTable = Manager::getInstance()->getTable($idSubTable); - } catch(TableNotFoundException $e) { - // This occurs is an unknown & random data issue. Catch Exception and remove subtable from the row. - $row->removeSubtable(); - // Go to next row - continue; - } - + foreach ($this->rows as $id => $row) { + $subTable = $row->getSubtable(); + if ($subTable) { + $consecutiveSubtableIds[$id] = ++$subtableId; $depth++; $aSerializedDataTable = $aSerializedDataTable + $subTable->getSerialized($maximumRowsInSubDataTable, $maximumRowsInSubDataTable, $columnToSortByBeforeTruncation); $depth--; + } else { + $row->removeSubtable(); } } - // we load the current Id of the DataTable - $forcedId = $this->getId(); // if the datatable is the parent we force the Id at 0 (this is part of the specification) if ($depth == 0) { $forcedId = 0; + $subtableId = 0; } // we then serialize the rows and store them in the serialized dataTable - $addToRows = array(self::ID_SUMMARY_ROW => $this->summaryRow); - - $aSerializedDataTable[$forcedId] = serialize($this->rows + $addToRows); - foreach ($this->rows as &$row) { - $row->cleanPostSerialize(); + $rows = array(); + foreach ($this->rows as $id => $row) { + if (array_key_exists($id, $consecutiveSubtableIds)) { + $backup = $row->subtableId; + $row->subtableId = $consecutiveSubtableIds[$id]; + $rows[$id] = $row->export(); + $row->subtableId = $backup; + } else { + $rows[$id] = $row->export(); + } } + if (isset($this->summaryRow)) { + $rows[self::ID_SUMMARY_ROW] = $this->summaryRow->export(); + } + + $aSerializedDataTable[$forcedId] = serialize($rows); + unset($rows); + return $aSerializedDataTable; } + private static $previousRowClasses = array('O:39:"Piwik\DataTable\Row\DataTableSummaryRow"', 'O:19:"Piwik\DataTable\Row"', 'O:36:"Piwik_DataTable_Row_DataTableSummary"', 'O:19:"Piwik_DataTable_Row"'); + private static $rowClassToUseForUnserialize = 'O:29:"Piwik_DataTable_SerializedRow"'; + + /** + * It is faster to unserialize existing serialized Row instances to "Piwik_DataTable_SerializedRow" and access the + * `$row->c` property than implementing a "__wakeup" method in the Row instance to map the "$row->c" to $row->columns + * etc. We're talking here about 15% faster reports aggregation in some cases. To be concrete: We have a test where + * Archiving a year takes 1700 seconds with "__wakeup" and 1400 seconds with this method. Yes, it takes 300 seconds + * to wake up millions of rows. We should be able to remove this code here end 2015 and use the "__wakeup" way by then. + * Why? By then most new archives will have only arrays serialized anyway and therefore this mapping is rather an overhead. + * + * @param string $serialized + * @return array + * @throws Exception In case the unserialize fails + */ + private function unserializeRows($serialized) + { + $serialized = str_replace(self::$previousRowClasses, self::$rowClassToUseForUnserialize, $serialized); + $rows = unserialize($serialized); + + if ($rows === false) { + throw new Exception("The unserialization has failed!"); + } + + return $rows; + } + /** * Adds a set of rows from a serialized DataTable string. * * See {@link serialize()}. - * + * * _Note: This function will successfully load DataTables serialized by Piwik 1.X._ - * - * @param string $stringSerialized A string with the format of a string in the array returned by + * + * @param string $serialized A string with the format of a string in the array returned by * {@link serialize()}. - * @throws Exception if `$stringSerialized` is invalid. + * @throws Exception if `$serialized` is invalid. */ - public function addRowsFromSerializedArray($stringSerialized) + public function addRowsFromSerializedArray($serialized) { - require_once PIWIK_INCLUDE_PATH . "/core/DataTable/Bridges.php"; - - $serialized = unserialize($stringSerialized); - if ($serialized === false) { - throw new Exception("The unserialization has failed!"); + $rows = $this->unserializeRows($serialized); + + if (array_key_exists(self::ID_SUMMARY_ROW, $rows)) { + if (is_array($rows[self::ID_SUMMARY_ROW])) { + $this->summaryRow = new Row($rows[self::ID_SUMMARY_ROW]); + } elseif (isset($rows[self::ID_SUMMARY_ROW]->c)) { + $this->summaryRow = new Row($rows[self::ID_SUMMARY_ROW]->c); // Pre Piwik 2.13 + } + unset($rows[self::ID_SUMMARY_ROW]); + } + + foreach ($rows as $id => $row) { + if (isset($row->c)) { + $this->addRow(new Row($row->c)); // Pre Piwik 2.13 + } else { + $this->addRow(new Row($row)); + } } - $this->addRowsFromArray($serialized); } /** * Adds multiple rows from an array. - * + * * You can add row metadata with this method. * * @param array $array Array with the following structure - * + * * array( * // row1 * array( @@ -1183,6 +1387,7 @@ class DataTable implements DataTableInterface if (is_array($row)) { $row = new Row($row); } + if ($id == self::ID_SUMMARY_ROW) { $this->summaryRow = $row; } else { @@ -1193,11 +1398,11 @@ class DataTable implements DataTableInterface /** * Adds multiple rows from an array containing arrays of column values. - * + * * Row metadata cannot be added with this method. * * @param array $array Array with the following structure: - * + * * array( * array( col1_name => valueA, col2_name => valueC, ...), * array( col1_name => valueB, col2_name => valueD, ...), @@ -1210,11 +1415,10 @@ class DataTable implements DataTableInterface return; } - // we define an exception we may throw if at one point we notice that we cannot handle the data structure - $e = new Exception(" Data structure returned is not convertible in the requested format." . + $exceptionText = " Data structure returned is not convertible in the requested format." . " Try to call this method with the parameters '&format=original&serialize=1'" . "; you will get the original php data structure serialized." . - " The data structure looks like this: \n \$data = " . var_export($array, true) . "; "); + " The data structure looks like this: \n \$data = %s; "; // first pass to see if the array has the structure // array(col1_name => val1, col2_name => val2, etc.) @@ -1255,12 +1459,13 @@ class DataTable implements DataTableInterface // it cannot be lost during the conversion. Because we are not able to handle properly // this key, we throw an explicit exception. if (is_string($key)) { - throw $e; + // we define an exception we may throw if at one point we notice that we cannot handle the data structure + throw new Exception(sprintf($exceptionText, var_export($array, true))); } // if any of the sub elements of row is an array we cannot handle this data structure... foreach ($row as $subRow) { if (is_array($subRow)) { - throw $e; + throw new Exception(sprintf($exceptionText, var_export($array, true))); } } $row = new Row(array(Row::COLUMNS => $row)); @@ -1274,28 +1479,28 @@ class DataTable implements DataTableInterface /** * Rewrites the input `$array` - * + * * array ( * LABEL => array(col1 => X, col2 => Y), * LABEL2 => array(col1 => X, col2 => Y), * ) - * + * * to a DataTable with rows that look like: - * + * * array ( * array( Row::COLUMNS => array('label' => LABEL, col1 => X, col2 => Y)), * array( Row::COLUMNS => array('label' => LABEL2, col1 => X, col2 => Y)), * ) * - * Will also convert arrays like: - * + * Will also convert arrays like: + * * array ( * LABEL => X, * LABEL2 => Y, * ) - * + * * to: - * + * * array ( * array( Row::COLUMNS => array('label' => LABEL, 'value' => X)), * array( Row::COLUMNS => array('label' => LABEL2, 'value' => Y)), @@ -1329,11 +1534,11 @@ class DataTable implements DataTableInterface /** * Sets the maximum depth level to at least a certain value. If the current value is * greater than `$atLeastLevel`, the maximum nesting level is not changed. - * + * * The maximum depth level determines the maximum number of subtable levels in the * DataTable tree. For example, if it is set to `2`, this DataTable is allowed to * have subtables, but the subtables are not. - * + * * @param int $atLeastLevel */ public static function setMaximumDepthLevelAllowedAtLeast($atLeastLevel) @@ -1381,7 +1586,7 @@ class DataTable implements DataTableInterface /** * Sets several metadata values by name. - * + * * @param array $values Array mapping metadata names with metadata values. */ public function setMetadataValues($values) @@ -1393,7 +1598,7 @@ class DataTable implements DataTableInterface /** * Sets metadata, erasing existing values. - * + * * @param array $values Array mapping metadata names with metadata values. */ public function setAllTableMetadata($metadata) @@ -1417,14 +1622,14 @@ class DataTable implements DataTableInterface * Traverses a DataTable tree using an array of labels and returns the row * it finds or `false` if it cannot find one. The number of path segments that * were successfully walked is also returned. - * + * * If `$missingRowColumns` is supplied, the specified path is created. When * a subtable is encountered w/o the required label, a new row is created * with the label, and a new subtable is added to the row. * * Read [http://en.wikipedia.org/wiki/Tree_(data_structure)#Traversal_methods](http://en.wikipedia.org/wiki/Tree_(data_structure)#Traversal_methods) * for more information about tree walking. - * + * * @param array $path The path to walk. An array of label values. The first element * refers to a row in this DataTable, the second in a subtable of * the first row, the third a subtable of the second row, etc. @@ -1452,15 +1657,17 @@ class DataTable implements DataTableInterface // if there is no table to advance to, and we're not adding missing rows, return false if ($missingRowColumns === false) { return array(false, $i); - } else // if we're adding missing rows, add a new row - { + } else { + // if we're adding missing rows, add a new row + $row = new DataTableSummaryRow(); $row->setColumns(array('label' => $segment) + $missingRowColumns); $next = $table->addRow($row); - if ($next !== $row) // if the row wasn't added, the table is full - { + if ($next !== $row) { + // if the row wasn't added, the table is full + // Summary row, has no metadata $next->deleteMetadata(); return array($next, $i); @@ -1474,8 +1681,9 @@ class DataTable implements DataTableInterface // missing rows, return false if ($missingRowColumns === false) { return array(false, $i); - } else if ($i != $pathLength - 1) // create subtable if missing, but only if not on the last segment - { + } elseif ($i != $pathLength - 1) { + // create subtable if missing, but only if not on the last segment + $table = new DataTable(); $table->setMaximumAllowedRows($maxSubtableRows); $table->metadata[self::COLUMN_AGGREGATION_OPS_METADATA_NAME] @@ -1495,7 +1703,7 @@ class DataTable implements DataTableInterface * * @param string|bool $labelColumn If supplied the label of the parent row will be added to * a new column in each subtable row. - * + * * If set to, `'label'` each subtable row's label will be prepended * w/ the parent row's label. So `'child_label'` becomes * `'parent_label - child_label'`. @@ -1506,7 +1714,7 @@ class DataTable implements DataTableInterface public function mergeSubtables($labelColumn = false, $useMetadataColumn = false) { $result = new DataTable(); - foreach ($this->getRows() as $row) { + foreach ($this->getRowsWithoutSummaryRow() as $row) { $subtable = $row->getSubtable(); if ($subtable !== false) { $parentLabel = $row->getColumn('label'); @@ -1551,9 +1759,9 @@ class DataTable implements DataTableInterface /** * Returns a new DataTable created with data from a 'simple' array. - * + * * See {@link addRowsFromSimpleArray()}. - * + * * @param array $array * @return \Piwik\DataTable */ @@ -1566,7 +1774,7 @@ class DataTable implements DataTableInterface /** * Creates a new DataTable instance from a serialized DataTable string. - * + * * See {@link getSerialized()} and {@link addRowsFromSerializedArray()} * for more information on DataTable serialization. * @@ -1586,9 +1794,10 @@ class DataTable implements DataTableInterface * $row must have a column "label". The $row will be summed to this table's row with the same label. * * @param $row + * @params null|array $columnAggregationOps * @throws \Exception */ - protected function aggregateRowWithLabel(Row $row, $doAggregateSubTables = true) + protected function aggregateRowWithLabel(Row $row, $columnAggregationOps) { $labelToLookFor = $row->getColumn('label'); if ($labelToLookFor === false) { @@ -1602,19 +1811,16 @@ class DataTable implements DataTableInterface $this->addRow($row); } } else { - $rowFound->sumRow($row, $copyMeta = true, $this->getMetadata(self::COLUMN_AGGREGATION_OPS_METADATA_NAME)); + $rowFound->sumRow($row, $copyMeta = true, $columnAggregationOps); - if($doAggregateSubTables) { - // if the row to add has a subtable whereas the current row doesn't - // we simply add it (cloning the subtable) - // if the row has the subtable already - // then we have to recursively sum the subtables - if (($idSubTable = $row->getIdSubDataTable()) !== null) { - $subTable = Manager::getInstance()->getTable($idSubTable); - $subTable->metadata[self::COLUMN_AGGREGATION_OPS_METADATA_NAME] - = $this->getMetadata(self::COLUMN_AGGREGATION_OPS_METADATA_NAME); - $rowFound->sumSubtable($subTable); - } + // if the row to add has a subtable whereas the current row doesn't + // we simply add it (cloning the subtable) + // if the row has the subtable already + // then we have to recursively sum the subtables + $subTable = $row->getSubtable(); + if ($subTable) { + $subTable->metadata[self::COLUMN_AGGREGATION_OPS_METADATA_NAME] = $columnAggregationOps; + $rowFound->sumSubtable($subTable); } } } @@ -1626,7 +1832,6 @@ class DataTable implements DataTableInterface { if ($row === false) { return; - } $thisRow = $this->getFirstRow(); if ($thisRow === false) { @@ -1635,4 +1840,42 @@ class DataTable implements DataTableInterface } $thisRow->sumRow($row, $copyMeta = true, $this->getMetadata(self::COLUMN_AGGREGATION_OPS_METADATA_NAME)); } -} \ No newline at end of file + + /** + * Unsets all queued filters. + */ + public function clearQueuedFilters() + { + $this->queuedFilters = array(); + } + + /** + * @return \ArrayIterator|Row[] + */ + public function getIterator() + { + return new \ArrayIterator($this->getRows()); + } + + public function offsetExists($offset) + { + $row = $this->getRowFromId($offset); + + return false !== $row; + } + + public function offsetGet($offset) + { + return $this->getRowFromId($offset); + } + + public function offsetSet($offset, $value) + { + $this->rows[$offset] = $value; + } + + public function offsetUnset($offset) + { + $this->deleteRow($offset); + } +} diff --git a/www/analytics/core/DataTable/BaseFilter.php b/www/analytics/core/DataTable/BaseFilter.php index 22c926e5..ce423112 100644 --- a/www/analytics/core/DataTable/BaseFilter.php +++ b/www/analytics/core/DataTable/BaseFilter.php @@ -1,6 +1,6 @@ enableRecursive) { return; } - if ($row->isSubtableLoaded()) { - $subTable = Manager::getInstance()->getTable($row->getIdSubDataTable()); + $subTable = $row->getSubtable(); + if ($subTable) { $this->filter($subTable); } } diff --git a/www/analytics/core/DataTable/Bridges.php b/www/analytics/core/DataTable/Bridges.php index cf924e03..ccc25736 100644 --- a/www/analytics/core/DataTable/Bridges.php +++ b/www/analytics/core/DataTable/Bridges.php @@ -1,6 +1,6 @@ filter('AddColumnsProcessedMetrics'); - * + * * @api */ class AddColumnsProcessedMetrics extends BaseFilter @@ -43,7 +46,7 @@ class AddColumnsProcessedMetrics extends BaseFilter /** * Constructor. - * + * * @param DataTable $table The table to eventually filter. * @param bool $deleteRowsWithNoVisit Whether to delete rows with no visits or not. */ @@ -61,89 +64,32 @@ class AddColumnsProcessedMetrics extends BaseFilter */ public function filter($table) { - $rowsIdToDelete = array(); + if ($this->deleteRowsWithNoVisit) { + $this->deleteRowsWithNoVisit($table); + } + + $extraProcessedMetrics = $table->getMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME); + + $extraProcessedMetrics[] = new ConversionRate(); + $extraProcessedMetrics[] = new ActionsPerVisit(); + $extraProcessedMetrics[] = new AverageTimeOnSite(); + $extraProcessedMetrics[] = new BounceRate(); + + $table->setMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME, $extraProcessedMetrics); + } + + private function deleteRowsWithNoVisit(DataTable $table) + { foreach ($table->getRows() as $key => $row) { - $nbVisits = $this->getColumn($row, Metrics::INDEX_NB_VISITS); - $nbActions = $this->getColumn($row, Metrics::INDEX_NB_ACTIONS); + $nbVisits = Metric::getMetric($row, 'nb_visits'); + $nbActions = Metric::getMetric($row, 'nb_actions'); + if ($nbVisits == 0 && $nbActions == 0 - && $this->deleteRowsWithNoVisit ) { - // case of keyword/website/campaign with a conversion for this day, - // but no visit, we don't show it - $rowsIdToDelete[] = $key; - continue; + // case of keyword/website/campaign with a conversion for this day, but no visit, we don't show it + $table->deleteRow($key); } - - $nbVisitsConverted = (int)$this->getColumn($row, Metrics::INDEX_NB_VISITS_CONVERTED); - if ($nbVisitsConverted > 0) { - $conversionRate = round(100 * $nbVisitsConverted / $nbVisits, $this->roundPrecision); - try { - $row->addColumn('conversion_rate', $conversionRate . "%"); - } catch (\Exception $e) { - // conversion_rate can be defined upstream apparently? FIXME - } - } - - if ($nbVisits == 0) { - $actionsPerVisit = $averageTimeOnSite = $bounceRate = $this->invalidDivision; - } else { - // nb_actions / nb_visits => Actions/visit - // sum_visit_length / nb_visits => Avg. Time on Site - // bounce_count / nb_visits => Bounce Rate - $actionsPerVisit = round($nbActions / $nbVisits, $this->roundPrecision); - $visitLength = $this->getColumn($row, Metrics::INDEX_SUM_VISIT_LENGTH); - $averageTimeOnSite = round($visitLength / $nbVisits, $rounding = 0); - $bounceRate = round(100 * $this->getColumn($row, Metrics::INDEX_BOUNCE_COUNT) / $nbVisits, $this->roundPrecision); - } - try { - $row->addColumn('nb_actions_per_visit', $actionsPerVisit); - $row->addColumn('avg_time_on_site', $averageTimeOnSite); - // It could be useful for API users to have raw sum length value. - //$row->addMetadata('sum_visit_length', $visitLength); - } catch (\Exception $e) { - } - - try { - $row->addColumn('bounce_rate', $bounceRate . "%"); - } catch (\Exception $e) { - } - - $this->filterSubTable($row); } - $table->deleteRows($rowsIdToDelete); - } - - /** - * Returns column from a given row. - * Will work with 2 types of datatable - * - raw datatables coming from the archive DB, which columns are int indexed - * - datatables processed resulting of API calls, which columns have human readable english names - * - * @param Row|array $row - * @param int $columnIdRaw see consts in Archive:: - * @param bool|array $mappingIdToName - * @return mixed Value of column, false if not found - */ - protected function getColumn($row, $columnIdRaw, $mappingIdToName = false) - { - if (empty($mappingIdToName)) { - $mappingIdToName = Metrics::$mappingFromIdToName; - } - $columnIdReadable = $mappingIdToName[$columnIdRaw]; - if ($row instanceof Row) { - $raw = $row->getColumn($columnIdRaw); - if ($raw !== false) { - return $raw; - } - return $row->getColumn($columnIdReadable); - } - if (isset($row[$columnIdRaw])) { - return $row[$columnIdRaw]; - } - if (isset($row[$columnIdReadable])) { - return $row[$columnIdReadable]; - } - return false; } } diff --git a/www/analytics/core/DataTable/Filter/AddColumnsProcessedMetricsGoal.php b/www/analytics/core/DataTable/Filter/AddColumnsProcessedMetricsGoal.php index 05a16631..7bdb7c79 100644 --- a/www/analytics/core/DataTable/Filter/AddColumnsProcessedMetricsGoal.php +++ b/www/analytics/core/DataTable/Filter/AddColumnsProcessedMetricsGoal.php @@ -1,6 +1,6 @@ filter('AddColumnsProcessedMetricsGoal', * array($enable = true, $idGoal = Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER)); - * + * * @api */ class AddColumnsProcessedMetricsGoal extends AddColumnsProcessedMetrics @@ -68,7 +75,7 @@ class AddColumnsProcessedMetricsGoal extends AddColumnsProcessedMetrics /** * Constructor. - * + * * @param DataTable $table The table that will eventually filtered. * @param bool $enable Always set to true. * @param string $processOnlyIdGoal Defines what metrics to add (don't process metrics when you don't display them). @@ -76,13 +83,14 @@ class AddColumnsProcessedMetricsGoal extends AddColumnsProcessedMetrics * If self::GOALS_OVERVIEW, only the main goal metrics will be added. * If an int > 0, then will process only metrics for this specific Goal. */ - public function __construct($table, $enable = true, $processOnlyIdGoal) + public function __construct($table, $enable = true, $processOnlyIdGoal, $goalsToProcess = null) { $this->processOnlyIdGoal = $processOnlyIdGoal; $this->isEcommerce = $this->processOnlyIdGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER || $this->processOnlyIdGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART; parent::__construct($table); // Ensure that all rows with no visit but conversions will be displayed $this->deleteRowsWithNoVisit = false; + $this->goalsToProcess = $goalsToProcess; } /** @@ -95,132 +103,63 @@ class AddColumnsProcessedMetricsGoal extends AddColumnsProcessedMetrics { // Add standard processed metrics parent::filter($table); - $roundingPrecision = GoalManager::REVENUE_PRECISION; - $expectedColumns = array(); - foreach ($table->getRows() as $key => $row) { - $currentColumns = $row->getColumns(); - $newColumns = array(); - // visits could be undefined when there is a conversion but no visit - $nbVisits = (int)$this->getColumn($row, Metrics::INDEX_NB_VISITS); - $conversions = (int)$this->getColumn($row, Metrics::INDEX_NB_CONVERSIONS); - $goals = $this->getColumn($currentColumns, Metrics::INDEX_GOALS); - if ($goals) { - $revenue = 0; - foreach ($goals as $goalId => $goalMetrics) { - if ($goalId == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART) { - continue; - } - if ($goalId >= GoalManager::IDGOAL_ORDER - || $goalId == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER - ) { - $revenue += (int)$this->getColumn($goalMetrics, Metrics::INDEX_GOAL_REVENUE, Metrics::$mappingFromIdToNameGoal); - } - } + $goals = $this->getGoalsInTable($table); + if (!empty($this->goalsToProcess)) { + $goals = array_unique(array_merge($goals, $this->goalsToProcess)); + sort($goals); + } - if ($revenue == 0) { - $revenue = (int)$this->getColumn($currentColumns, Metrics::INDEX_REVENUE); - } - if (!isset($currentColumns['revenue_per_visit'])) { - // If no visit for this metric, but some conversions, we still want to display some kind of "revenue per visit" - // even though it will actually be in this edge case "Revenue per conversion" - $revenuePerVisit = $this->invalidDivision; - if ($nbVisits > 0 - || $conversions > 0 - ) { - $revenuePerVisit = round($revenue / ($nbVisits == 0 ? $conversions : $nbVisits), $roundingPrecision); - } - $newColumns['revenue_per_visit'] = $revenuePerVisit; - } - if ($this->processOnlyIdGoal == self::GOALS_MINIMAL_REPORT) { - $row->addColumns($newColumns); + $idSite = DataTableFactory::getSiteIdFromMetadata($table); + + $extraProcessedMetrics = $table->getMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME); + + $extraProcessedMetrics[] = new RevenuePerVisit(); + if ($this->processOnlyIdGoal != self::GOALS_MINIMAL_REPORT) { + foreach ($goals as $idGoal) { + if (($this->processOnlyIdGoal > self::GOALS_FULL_TABLE + || $this->isEcommerce) + && $this->processOnlyIdGoal != $idGoal + ) { continue; } - // Display per goal metrics - // - conversion rate - // - conversions - // - revenue per visit - foreach ($goals as $goalId => $goalMetrics) { - $goalId = str_replace("idgoal=", "", $goalId); - if (($this->processOnlyIdGoal > self::GOALS_FULL_TABLE - || $this->isEcommerce) - && $this->processOnlyIdGoal != $goalId - ) { - continue; - } - $conversions = (int)$this->getColumn($goalMetrics, Metrics::INDEX_GOAL_NB_CONVERSIONS, Metrics::$mappingFromIdToNameGoal); - // Goal Conversion rate - $name = 'goal_' . $goalId . '_conversion_rate'; - if ($nbVisits == 0) { - $value = $this->invalidDivision; - } else { - $value = round(100 * $conversions / $nbVisits, $roundingPrecision); - } - $newColumns[$name] = $value . "%"; - $expectedColumns[$name] = true; + $extraProcessedMetrics[] = new ConversionRate($idSite, $idGoal); // PerGoal\ConversionRate - // When the table is displayed by clicking on the flag icon, we only display the columns - // Visits, Conversions, Per goal conversion rate, Revenue - if ($this->processOnlyIdGoal == self::GOALS_OVERVIEW) { - continue; - } - - // Goal Conversions - $name = 'goal_' . $goalId . '_nb_conversions'; - $newColumns[$name] = $conversions; - $expectedColumns[$name] = true; - - // Goal Revenue per visit - $name = 'goal_' . $goalId . '_revenue_per_visit'; - // See comment above for $revenuePerVisit - $goalRevenue = (float)$this->getColumn($goalMetrics, Metrics::INDEX_GOAL_REVENUE, Metrics::$mappingFromIdToNameGoal); - $revenuePerVisit = round($goalRevenue / ($nbVisits == 0 ? $conversions : $nbVisits), $roundingPrecision); - $newColumns[$name] = $revenuePerVisit; - $expectedColumns[$name] = true; - - // Total revenue - $name = 'goal_' . $goalId . '_revenue'; - $newColumns[$name] = $goalRevenue; - $expectedColumns[$name] = true; - - if ($this->isEcommerce) { - - // AOV Average Order Value - $name = 'goal_' . $goalId . '_avg_order_revenue'; - $newColumns[$name] = $goalRevenue / $conversions; - $expectedColumns[$name] = true; - - // Items qty - $name = 'goal_' . $goalId . '_items'; - $newColumns[$name] = $this->getColumn($goalMetrics, Metrics::INDEX_GOAL_ECOMMERCE_ITEMS, Metrics::$mappingFromIdToNameGoal); - $expectedColumns[$name] = true; - } + // When the table is displayed by clicking on the flag icon, we only display the columns + // Visits, Conversions, Per goal conversion rate, Revenue + if ($this->processOnlyIdGoal == self::GOALS_OVERVIEW) { + continue; } - } - // conversion_rate can be defined upstream apparently? FIXME - try { - $row->addColumns($newColumns); - } catch (Exception $e) { - } - } - $expectedColumns['revenue_per_visit'] = true; + $extraProcessedMetrics[] = new Conversions($idSite, $idGoal); // PerGoal\Conversions or GoalSpecific\ + $extraProcessedMetrics[] = new GoalSpecificRevenuePerVisit($idSite, $idGoal); // PerGoal\Revenue + $extraProcessedMetrics[] = new Revenue($idSite, $idGoal); // PerGoal\Revenue - // make sure all goals values are set, 0 by default - // if no value then sorting would put at the end - $expectedColumns = array_keys($expectedColumns); - $rows = $table->getRows(); - foreach ($rows as &$row) { - foreach ($expectedColumns as $name) { - if (false === $row->getColumn($name)) { - $value = 0; - if (strpos($name, 'conversion_rate') !== false) { - $value = '0%'; - } - $row->addColumn($name, $value); + if ($this->isEcommerce) { + $extraProcessedMetrics[] = new AverageOrderRevenue($idSite, $idGoal); + $extraProcessedMetrics[] = new ItemsCount($idSite, $idGoal); } } } + + $table->setMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME, $extraProcessedMetrics); + } + + private function getGoalsInTable(DataTable $table) + { + $result = array(); + foreach ($table->getRows() as $row) { + $goals = Metric::getMetric($row, 'goals'); + if (!$goals) { + continue; + } + + foreach ($goals as $goalId => $goalMetrics) { + $goalId = str_replace("idgoal=", "", $goalId); + $result[] = $goalId; + } + } + return array_unique($result); } } diff --git a/www/analytics/core/DataTable/Filter/AddSegmentByLabel.php b/www/analytics/core/DataTable/Filter/AddSegmentByLabel.php new file mode 100644 index 00000000..1eacd4f7 --- /dev/null +++ b/www/analytics/core/DataTable/Filter/AddSegmentByLabel.php @@ -0,0 +1,99 @@ +filter('AddSegmentByLabel', array('segmentName')); + * $dataTable->filter('AddSegmentByLabel', array(array('segmentName1', 'segment2'), ';'); + * + * @api + */ +class AddSegmentByLabel extends BaseFilter +{ + private $segments; + private $delimiter; + + /** + * Generates a segment filter based on the label column and the given segment names + * + * @param DataTable $table + * @param string|array $segmentOrSegments Either one segment or an array of segments. + * If more than one segment is given a delimter has to be defined. + * @param string $delimiter The delimiter by which the label should be splitted. + */ + public function __construct($table, $segmentOrSegments, $delimiter = '') + { + parent::__construct($table); + + if (!is_array($segmentOrSegments)) { + $segmentOrSegments = array($segmentOrSegments); + } + + $this->segments = $segmentOrSegments; + $this->delimiter = $delimiter; + } + + /** + * See {@link AddSegmentByLabel}. + * + * @param DataTable $table + */ + public function filter($table) + { + if (empty($this->segments)) { + $msg = 'AddSegmentByLabel is called without having any segments defined'; + Development::error($msg); + return; + } + + if (count($this->segments) === 1) { + $segment = reset($this->segments); + + foreach ($table->getRowsWithoutSummaryRow() as $key => $row) { + $label = $row->getColumn('label'); + + if (!empty($label)) { + $row->setMetadata('segment', $segment . '==' . urlencode($label)); + } + } + } elseif (!empty($this->delimiter)) { + $numSegments = count($this->segments); + $conditionAnd = ';'; + + foreach ($table->getRowsWithoutSummaryRow() as $key => $row) { + $label = $row->getColumn('label'); + if (!empty($label)) { + $parts = explode($this->delimiter, $label); + + if (count($parts) === $numSegments) { + $filter = array(); + foreach ($this->segments as $index => $segment) { + if (!empty($segment)) { + $filter[] = $segment . '==' . urlencode($parts[$index]); + } + } + $row->setMetadata('segment', implode($conditionAnd, $filter)); + } + } + } + } else { + $names = implode(', ', $this->segments); + $msg = 'Multiple segments are given but no delimiter defined. Segments: ' . $names; + Development::error($msg); + } + } +} diff --git a/www/analytics/core/DataTable/Filter/AddSegmentByLabelMapping.php b/www/analytics/core/DataTable/Filter/AddSegmentByLabelMapping.php new file mode 100644 index 00000000..29fbdced --- /dev/null +++ b/www/analytics/core/DataTable/Filter/AddSegmentByLabelMapping.php @@ -0,0 +1,63 @@ +filter('AddSegmentByLabelMapping', array('segmentName', array('1' => 'smartphone, '2' => 'desktop'))); + * + * @api + */ +class AddSegmentByLabelMapping extends BaseFilter +{ + private $segment; + private $mapping; + + /** + * @param DataTable $table + * @param string $segment + * @param array $mapping + */ + public function __construct($table, $segment, $mapping) + { + parent::__construct($table); + + $this->segment = $segment; + $this->mapping = $mapping; + } + + /** + * See {@link AddSegmentByLabelMapping}. + * + * @param DataTable $table + */ + public function filter($table) + { + if (empty($this->segment) || empty($this->mapping)) { + return; + } + + foreach ($table->getRows() as $row) { + $label = $row->getColumn('label'); + + if (!empty($this->mapping[$label])) { + $label = $this->mapping[$label]; + $row->setMetadata('segment', $this->segment . '==' . urlencode($label)); + } + } + } +} diff --git a/www/analytics/core/DataTable/Filter/AddSegmentBySegmentValue.php b/www/analytics/core/DataTable/Filter/AddSegmentBySegmentValue.php new file mode 100644 index 00000000..7417a48c --- /dev/null +++ b/www/analytics/core/DataTable/Filter/AddSegmentBySegmentValue.php @@ -0,0 +1,78 @@ +filter('AddSegmentBySegmentValue', array($reportInstance)); + * + * @api + */ +class AddSegmentBySegmentValue extends BaseFilter +{ + /** + * @var \Piwik\Plugin\Report + */ + private $report; + + /** + * @param DataTable $table + * @param $report + */ + public function __construct($table, $report) + { + parent::__construct($table); + $this->report = $report; + } + + /** + * See {@link AddSegmentBySegmentValue}. + * + * @param DataTable $table + * @return int The number of deleted rows. + */ + public function filter($table) + { + if (empty($this->report) || !$table->getRowsCount()) { + return; + } + + $dimension = $this->report->getDimension(); + + if (empty($dimension)) { + return; + } + + $segments = $dimension->getSegments(); + + if (empty($segments)) { + return; + } + + /** @var \Piwik\Plugin\Segment $segment */ + $segment = reset($segments); + $segmentName = $segment->getSegment(); + + foreach ($table->getRows() as $row) { + $value = $row->getMetadata('segmentValue'); + $filter = $row->getMetadata('segment'); + + if ($value !== false && $filter === false) { + $row->setMetadata('segment', sprintf('%s==%s', $segmentName, urlencode($value))); + } + } + } +} diff --git a/www/analytics/core/DataTable/Filter/AddSegmentValue.php b/www/analytics/core/DataTable/Filter/AddSegmentValue.php new file mode 100644 index 00000000..886db60d --- /dev/null +++ b/www/analytics/core/DataTable/Filter/AddSegmentValue.php @@ -0,0 +1,32 @@ +filter('AddSegmentValue', array()); + * $dataTable->filter('AddSegmentValue', array(function ($label) { + * $transformedValue = urldecode($transformedValue); + * return $transformedValue; + * }); + * + * @api + */ +class AddSegmentValue extends ColumnCallbackAddMetadata +{ + public function __construct($table, $callback = null) + { + parent::__construct($table, 'label', 'segmentValue', $callback, null, false); + } +} diff --git a/www/analytics/core/DataTable/Filter/AddSummaryRow.php b/www/analytics/core/DataTable/Filter/AddSummaryRow.php index 20de397a..9cb4b3e2 100644 --- a/www/analytics/core/DataTable/Filter/AddSummaryRow.php +++ b/www/analytics/core/DataTable/Filter/AddSummaryRow.php @@ -1,6 +1,6 @@ filter('AddSummaryRow'); - * + * * // use a human readable label for the summary row (instead of '-1') * $dataTable->filter('AddSummaryRow', array($labelSummaryRow = Piwik::translate('General_Total'))); - * + * * @api */ class AddSummaryRow extends BaseFilter diff --git a/www/analytics/core/DataTable/Filter/BeautifyRangeLabels.php b/www/analytics/core/DataTable/Filter/BeautifyRangeLabels.php index dac3aad2..bf5e8d65 100644 --- a/www/analytics/core/DataTable/Filter/BeautifyRangeLabels.php +++ b/www/analytics/core/DataTable/Filter/BeautifyRangeLabels.php @@ -1,6 +1,6 @@ queueFilter('BeautifyRangeLabels', array("1 visit", "%s visits")); - * + * * @api */ class BeautifyRangeLabels extends ColumnCallbackReplace @@ -65,7 +65,7 @@ class BeautifyRangeLabels extends ColumnCallbackReplace parent::__construct($table, 'label', array($this, 'beautify'), array()); $this->labelSingular = $labelSingular; - $this->labelPlural = $labelPlural; + $this->labelPlural = $labelPlural; } /** diff --git a/www/analytics/core/DataTable/Filter/BeautifyTimeRangeLabels.php b/www/analytics/core/DataTable/Filter/BeautifyTimeRangeLabels.php index fc4416fb..7cc21699 100644 --- a/www/analytics/core/DataTable/Filter/BeautifyTimeRangeLabels.php +++ b/www/analytics/core/DataTable/Filter/BeautifyTimeRangeLabels.php @@ -1,6 +1,6 @@ filter('BeautifyTimeRangeLabels', array("%1$s-%2$s min", "1 min", "%s min")); - * + * * @api */ class BeautifyTimeRangeLabels extends BeautifyRangeLabels @@ -70,7 +70,7 @@ class BeautifyTimeRangeLabels extends BeautifyRangeLabels { if ($lowerBound < 60) { return sprintf($this->labelSecondsPlural, $lowerBound, $lowerBound); - } else if ($lowerBound == 60) { + } elseif ($lowerBound == 60) { return $this->labelSingular; } else { return sprintf($this->labelPlural, ceil($lowerBound / 60)); diff --git a/www/analytics/core/DataTable/Filter/CalculateEvolutionFilter.php b/www/analytics/core/DataTable/Filter/CalculateEvolutionFilter.php index 24c86121..73274c53 100755 --- a/www/analytics/core/DataTable/Filter/CalculateEvolutionFilter.php +++ b/www/analytics/core/DataTable/Filter/CalculateEvolutionFilter.php @@ -1,6 +1,6 @@ getPastRowFromCurrent($row); - if (!$pastRow) return 0; + if (!$pastRow) { + return 0; + } return $pastRow->getColumn($this->columnNameUsedAsDivisor); } @@ -121,6 +126,9 @@ class CalculateEvolutionFilter extends ColumnCallbackAddColumnPercentage { $value = self::getPercentageValue($value, $divisor, $this->quotientPrecision); $value = self::appendPercentSign($value); + + $value = Common::forceDotAsSeparatorForDecimalPoint($value); + return $value; } @@ -150,9 +158,10 @@ class CalculateEvolutionFilter extends ColumnCallbackAddColumnPercentage { $number = self::getPercentageValue($currentValue - $pastValue, $pastValue, $quotientPrecision); if ($appendPercentSign) { - $number = self::appendPercentSign($number); + return NumberFormatter::getInstance()->formatPercent($number, $quotientPrecision); } - return $number; + + return NumberFormatter::getInstance()->format($number, $quotientPrecision); } public static function appendPercentSign($number) @@ -165,6 +174,7 @@ class CalculateEvolutionFilter extends ColumnCallbackAddColumnPercentage if ($number > 0) { $number = '+' . $number; } + return $number; } diff --git a/www/analytics/core/DataTable/Filter/ColumnCallbackAddColumn.php b/www/analytics/core/DataTable/Filter/ColumnCallbackAddColumn.php index 56ad449f..1d81f189 100755 --- a/www/analytics/core/DataTable/Filter/ColumnCallbackAddColumn.php +++ b/www/analytics/core/DataTable/Filter/ColumnCallbackAddColumn.php @@ -1,6 +1,6 @@ filter('ColumnCallbackAddColumn', array(array('nb_visits', 'sum_time_spent'), 'avg_time_on_site', $callback)); * * @api @@ -79,17 +80,33 @@ class ColumnCallbackAddColumn extends BaseFilter */ public function filter($table) { - foreach ($table->getRows() as $row) { + $columns = $this->columns; + $functionParams = $this->functionParameters; + $functionToApply = $this->functionToApply; + + $extraProcessedMetrics = $table->getMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME); + + if (empty($extraProcessedMetrics)) { + $extraProcessedMetrics = array(); + } + + $metric = new CallableProcessedMetric($this->columnToAdd, function (DataTable\Row $row) use ($columns, $functionParams, $functionToApply) { + $columnValues = array(); - foreach ($this->columns as $column) { + foreach ($columns as $column) { $columnValues[] = $row->getColumn($column); } - $parameters = array_merge($columnValues, $this->functionParameters); - $value = call_user_func_array($this->functionToApply, $parameters); + $parameters = array_merge($columnValues, $functionParams); - $row->setColumn($this->columnToAdd, $value); + return call_user_func_array($functionToApply, $parameters); + }, $columns); + $extraProcessedMetrics[] = $metric; + $table->setMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME, $extraProcessedMetrics); + + foreach ($table->getRows() as $row) { + $row->setColumn($this->columnToAdd, $metric->compute($row)); $this->filterSubTable($row); } } diff --git a/www/analytics/core/DataTable/Filter/ColumnCallbackAddColumnPercentage.php b/www/analytics/core/DataTable/Filter/ColumnCallbackAddColumnPercentage.php index 78841bcb..056c1294 100644 --- a/www/analytics/core/DataTable/Filter/ColumnCallbackAddColumnPercentage.php +++ b/www/analytics/core/DataTable/Filter/ColumnCallbackAddColumnPercentage.php @@ -1,6 +1,6 @@ queueFilter('ColumnCallbackAddColumnPercentage', array('nb_visits', 'nb_visits_percentage', $nbVisits, 1)); * diff --git a/www/analytics/core/DataTable/Filter/ColumnCallbackAddColumnQuotient.php b/www/analytics/core/DataTable/Filter/ColumnCallbackAddColumnQuotient.php index 10d53024..57451675 100644 --- a/www/analytics/core/DataTable/Filter/ColumnCallbackAddColumnQuotient.php +++ b/www/analytics/core/DataTable/Filter/ColumnCallbackAddColumnQuotient.php @@ -1,6 +1,6 @@ queueFilter('ColumnCallbackAddColumnQuotient', array('bounce_rate', 'bounce_count', 'nb_visits', $precision = 2)); - * + * * @api */ class ColumnCallbackAddColumnQuotient extends BaseFilter @@ -38,7 +38,7 @@ class ColumnCallbackAddColumnQuotient extends BaseFilter /** * Constructor. - * + * * @param DataTable $table The DataTable that will eventually be filtered. * @param string $columnNameToAdd The name of the column to add the quotient value to. * @param string $columnValueToRead The name of the column that holds the dividend. @@ -75,7 +75,7 @@ class ColumnCallbackAddColumnQuotient extends BaseFilter */ public function filter($table) { - foreach ($table->getRows() as $key => $row) { + foreach ($table->getRows() as $row) { $value = $this->getDividend($row); if ($value === false && $this->shouldSkipRows) { continue; @@ -109,6 +109,7 @@ class ColumnCallbackAddColumnQuotient extends BaseFilter if ($divisor > 0 && $value > 0) { $quotient = round($value / $divisor, $this->quotientPrecision); } + return $quotient; } @@ -135,7 +136,7 @@ class ColumnCallbackAddColumnQuotient extends BaseFilter { if (!is_null($this->totalValueUsedAsDivisor)) { return $this->totalValueUsedAsDivisor; - } else if ($this->getDivisorFromSummaryRow) { + } elseif ($this->getDivisorFromSummaryRow) { $summaryRow = $this->table->getRowFromId(DataTable::ID_SUMMARY_ROW); return $summaryRow->getColumn($this->columnNameUsedAsDivisor); } else { diff --git a/www/analytics/core/DataTable/Filter/ColumnCallbackAddMetadata.php b/www/analytics/core/DataTable/Filter/ColumnCallbackAddMetadata.php index 34b36c4d..d302e641 100644 --- a/www/analytics/core/DataTable/Filter/ColumnCallbackAddMetadata.php +++ b/www/analytics/core/DataTable/Filter/ColumnCallbackAddMetadata.php @@ -1,6 +1,6 @@ filter('ColumnCallbackAddMetadata', array('label', 'logo', 'Piwik\Plugins\MyPlugin\getLogoFromLabel')); - * + * * @api */ class ColumnCallbackAddMetadata extends BaseFilter @@ -31,7 +31,7 @@ class ColumnCallbackAddMetadata extends BaseFilter /** * Constructor. - * + * * @param DataTable $table The DataTable instance that will be filtered. * @param string|array $columnsToRead The columns to read from each row and pass on to the callback. * @param string $metadataToAdd The name of the metadata field that will be added to each row. @@ -48,12 +48,12 @@ class ColumnCallbackAddMetadata extends BaseFilter if (!is_array($columnsToRead)) { $columnsToRead = array($columnsToRead); } - $this->columnsToRead = $columnsToRead; - $this->functionToApply = $functionToApply; + $this->columnsToRead = $columnsToRead; + $this->functionToApply = $functionToApply; $this->functionParameters = $functionParameters; - $this->metadataToAdd = $metadataToAdd; - $this->applyToSummaryRow = $applyToSummaryRow; + $this->metadataToAdd = $metadataToAdd; + $this->applyToSummaryRow = $applyToSummaryRow; } /** @@ -63,11 +63,13 @@ class ColumnCallbackAddMetadata extends BaseFilter */ public function filter($table) { - foreach ($table->getRows() as $key => $row) { - if (!$this->applyToSummaryRow && $key == DataTable::ID_SUMMARY_ROW) { - continue; - } + if ($this->applyToSummaryRow) { + $rows = $table->getRows(); + } else { + $rows = $table->getRowsWithoutSummaryRow(); + } + foreach ($rows as $key => $row) { $parameters = array(); foreach ($this->columnsToRead as $columnsToRead) { $parameters[] = $row->getColumn($columnsToRead); diff --git a/www/analytics/core/DataTable/Filter/ColumnCallbackDeleteMetadata.php b/www/analytics/core/DataTable/Filter/ColumnCallbackDeleteMetadata.php new file mode 100644 index 00000000..bb8618a6 --- /dev/null +++ b/www/analytics/core/DataTable/Filter/ColumnCallbackDeleteMetadata.php @@ -0,0 +1,55 @@ +filter('ColumnCallbackDeleteMetadata', array('segmentValue')); + * + * @api + */ +class ColumnCallbackDeleteMetadata extends BaseFilter +{ + private $metadataToRemove; + + /** + * Constructor. + * + * @param DataTable $table The DataTable instance that will be filtered. + * @param string $metadataToRemove The name of the metadata field that will be removed from each row. + */ + public function __construct($table, $metadataToRemove) + { + parent::__construct($table); + + $this->metadataToRemove = $metadataToRemove; + } + + /** + * See {@link ColumnCallbackDeleteMetadata}. + * + * @param DataTable $table + */ + public function filter($table) + { + $this->enableRecursive(true); + + foreach ($table->getRows() as $row) { + $row->deleteMetadata($this->metadataToRemove); + + $this->filterSubTable($row); + } + } +} diff --git a/www/analytics/core/DataTable/Filter/ColumnCallbackDeleteRow.php b/www/analytics/core/DataTable/Filter/ColumnCallbackDeleteRow.php index a51e1df8..414bf71e 100644 --- a/www/analytics/core/DataTable/Filter/ColumnCallbackDeleteRow.php +++ b/www/analytics/core/DataTable/Filter/ColumnCallbackDeleteRow.php @@ -1,6 +1,6 @@ filter('ColumnCallbackDeleteRow', array('label', function ($label) use ($labelsToRemove) { * return in_array($label, $labelsToRemove); * })); - * + * * @api */ class ColumnCallbackDeleteRow extends BaseFilter { - private $columnToFilter; private $function; private $functionParams; /** * Constructor. - * + * * @param DataTable $table The DataTable that will be filtered eventually. * @param array|string $columnsToFilter The column or array of columns that should be * passed to the callback. diff --git a/www/analytics/core/DataTable/Filter/ColumnCallbackReplace.php b/www/analytics/core/DataTable/Filter/ColumnCallbackReplace.php index 0b16aedb..b850b7a6 100644 --- a/www/analytics/core/DataTable/Filter/ColumnCallbackReplace.php +++ b/www/analytics/core/DataTable/Filter/ColumnCallbackReplace.php @@ -1,6 +1,6 @@ $truncateLength) { * return substr(0, $truncateLength); @@ -25,10 +25,11 @@ use Piwik\DataTable\Row; * return $value; * } * }; - * + * * // label, url and truncate_length are columns in $dataTable * $dataTable->filter('ColumnCallbackReplace', array('label', 'url'), $truncateString, null, array('truncate_length')); - * + * + * @api */ class ColumnCallbackReplace extends BaseFilter { @@ -39,7 +40,7 @@ class ColumnCallbackReplace extends BaseFilter /** * Constructor. - * + * * @param DataTable $table The DataTable to filter. * @param array|string $columnsToFilter The columns whose values should be passed to the callback * and then replaced with the callback's result. @@ -54,14 +55,14 @@ class ColumnCallbackReplace extends BaseFilter $extraColumnParameters = array()) { parent::__construct($table); - $this->functionToApply = $functionToApply; + $this->functionToApply = $functionToApply; $this->functionParameters = $functionParameters; if (!is_array($columnsToFilter)) { $columnsToFilter = array($columnsToFilter); } - $this->columnsToFilter = $columnsToFilter; + $this->columnsToFilter = $columnsToFilter; $this->extraColumnParameters = $extraColumnParameters; } @@ -72,13 +73,14 @@ class ColumnCallbackReplace extends BaseFilter */ public function filter($table) { - foreach ($table->getRows() as $key => $row) { + foreach ($table->getRows() as $row) { $extraColumnParameters = array(); foreach ($this->extraColumnParameters as $columnName) { $extraColumnParameters[] = $row->getColumn($columnName); } foreach ($this->columnsToFilter as $column) { + // when a value is not defined, we set it to zero by default (rather than displaying '-') $value = $this->getElementToReplace($row, $column); if ($value === false) { @@ -86,14 +88,21 @@ class ColumnCallbackReplace extends BaseFilter } $parameters = array_merge(array($value), $extraColumnParameters); + if (!is_null($this->functionParameters)) { $parameters = array_merge($parameters, $this->functionParameters); } + $newValue = call_user_func_array($this->functionToApply, $parameters); $this->setElementToReplace($row, $column, $newValue); $this->filterSubTable($row); } } + + if (in_array('label', $this->columnsToFilter)) { + // we need to force rebuilding the index + $table->setLabelsHaveChanged(); + } } /** diff --git a/www/analytics/core/DataTable/Filter/ColumnDelete.php b/www/analytics/core/DataTable/Filter/ColumnDelete.php index 1b724109..a0fba5a9 100644 --- a/www/analytics/core/DataTable/Filter/ColumnDelete.php +++ b/www/analytics/core/DataTable/Filter/ColumnDelete.php @@ -1,6 +1,6 @@ filter('ColumnDelete', array($columnsToRemove)); - * + * * $columnsToKeep = array('nb_visits'); * $dataTable->filter('ColumnDelete', array(array(), $columnsToKeep)); - * + * * @api */ class ColumnDelete extends BaseFilter @@ -91,6 +91,7 @@ class ColumnDelete extends BaseFilter * See {@link ColumnDelete}. * * @param DataTable $table + * @return DataTable */ public function filter($table) { @@ -100,15 +101,16 @@ class ColumnDelete extends BaseFilter // remove columns specified in $this->columnsToRemove if (!empty($this->columnsToRemove)) { - foreach ($table->getRows() as $row) { + foreach ($table as $index => $row) { foreach ($this->columnsToRemove as $column) { if ($this->deleteIfZeroOnly) { - $value = $row->getColumn($column); + $value = $row[$column]; if ($value === false || !empty($value)) { continue; } } - $row->deleteColumn($column); + + unset($table[$index][$column]); } } @@ -117,9 +119,8 @@ class ColumnDelete extends BaseFilter // remove columns not specified in $columnsToKeep if (!empty($this->columnsToKeep)) { - foreach ($table->getRows() as $row) { - foreach ($row->getColumns() as $name => $value) { - + foreach ($table as $index => $row) { + foreach ($row as $name => $value) { $keep = false; // @see self::APPEND_TO_COLUMN_NAME_TO_KEEP foreach ($this->columnsToKeep as $nameKeep => $true) { @@ -132,7 +133,7 @@ class ColumnDelete extends BaseFilter && $name != 'label' // label cannot be removed via whitelisting && !isset($this->columnsToKeep[$name]) ) { - $row->deleteColumn($name); + unset($table[$index][$name]); } } } @@ -141,10 +142,12 @@ class ColumnDelete extends BaseFilter } // recurse - if ($recurse) { - foreach ($table->getRows() as $row) { + if ($recurse && !is_array($table)) { + foreach ($table as $row) { $this->filterSubTable($row); } } + + return $table; } } diff --git a/www/analytics/core/DataTable/Filter/ExcludeLowPopulation.php b/www/analytics/core/DataTable/Filter/ExcludeLowPopulation.php index 6e6fbf33..e6a423ef 100644 --- a/www/analytics/core/DataTable/Filter/ExcludeLowPopulation.php +++ b/www/analytics/core/DataTable/Filter/ExcludeLowPopulation.php @@ -1,6 +1,6 @@ filter('ExcludeLowPopulation', array('nb_visits', 3)); - * + * * // remove all countries from UserCountry.getCountry whose percent of total visits is less than 5% * $dataTable = // ... get a DataTable whose queued filters have been run ... * $dataTable->filter('ExcludeLowPopulation', array('nb_visits', false, 0.05)); - * + * * // remove all countries from UserCountry.getCountry whose bounce rate is less than 10% * $dataTable = // ... get a DataTable that has a numerical bounce_rate column ... * $dataTable->filter('ExcludeLowPopulation', array('bounce_rate', 0.10)); @@ -50,7 +51,7 @@ class ExcludeLowPopulation extends BaseFilter * @param string $columnToFilter The name of the column whose value will determine whether * a row is deleted or not. * @param number|false $minimumValue The minimum column value. Rows with column values < - * this number will be deleted. If false, + * this number will be deleted. If false, * `$minimumPercentageThreshold` is used. * @param bool|float $minimumPercentageThreshold If supplied, column values must be a greater * percentage of the sum of all column values than @@ -59,7 +60,13 @@ class ExcludeLowPopulation extends BaseFilter public function __construct($table, $columnToFilter, $minimumValue, $minimumPercentageThreshold = false) { parent::__construct($table); - $this->columnToFilter = $columnToFilter; + + $row = $table->getFirstRow(); + if ($row === false) { + return; + } + + $this->columnToFilter = $this->selectColumnToExclude($columnToFilter, $row); if ($minimumValue == 0) { if ($minimumPercentageThreshold === false) { @@ -80,6 +87,9 @@ class ExcludeLowPopulation extends BaseFilter */ public function filter($table) { + if(empty($this->columnToFilter)) { + return; + } $minimumValue = $this->minimumValue; $isValueLowPopulation = function ($value) use ($minimumValue) { return $value < $minimumValue; @@ -87,4 +97,29 @@ class ExcludeLowPopulation extends BaseFilter $table->filter('ColumnCallbackDeleteRow', array($this->columnToFilter, $isValueLowPopulation)); } + + /** + * Sets the column to be used for Excluding low population + * + * @param DataTable\Row $row + * @return int + */ + private function selectColumnToExclude($columnToFilter, $row) + { + if ($row->hasColumn($columnToFilter)) { + return $columnToFilter; + } + + // filter_excludelowpop=nb_visits but the column name is still Metrics::INDEX_NB_VISITS in the table + $columnIdToName = Metrics::getMappingFromNameToId(); + if (isset($columnIdToName[$columnToFilter])) { + $column = $columnIdToName[$columnToFilter]; + + if ($row->hasColumn($column)) { + return $column; + } + } + + return $columnToFilter; + } } diff --git a/www/analytics/core/DataTable/Filter/GroupBy.php b/www/analytics/core/DataTable/Filter/GroupBy.php index 093aee0a..b00c6614 100755 --- a/www/analytics/core/DataTable/Filter/GroupBy.php +++ b/www/analytics/core/DataTable/Filter/GroupBy.php @@ -1,6 +1,6 @@ filter('GroupBy', array('label', function ($labelUrl) { * return parse_url($labelUrl, PHP_URL_HOST); * })); - * + * * @api */ class GroupBy extends BaseFilter @@ -51,17 +52,17 @@ class GroupBy extends BaseFilter * @param DataTable $table The DataTable to filter. * @param string $groupByColumn The column name to reduce. * @param callable $reduceFunction The reduce function. This must alter the `$groupByColumn` - * columng in some way. + * columng in some way. If not set then the filter will group by the raw column value. * @param array $parameters deprecated - use an [anonymous function](http://php.net/manual/en/functions.anonymous.php) * instead. */ - public function __construct($table, $groupByColumn, $reduceFunction, $parameters = array()) + public function __construct($table, $groupByColumn, $reduceFunction = null, $parameters = array()) { parent::__construct($table); - $this->groupByColumn = $groupByColumn; + $this->groupByColumn = $groupByColumn; $this->reduceFunction = $reduceFunction; - $this->parameters = $parameters; + $this->parameters = $parameters; } /** @@ -71,19 +72,19 @@ class GroupBy extends BaseFilter */ public function filter($table) { + /** @var Row[] $groupByRows */ $groupByRows = array(); $nonGroupByRowIds = array(); - foreach ($table->getRows() as $rowId => $row) { - // skip the summary row - if ($rowId == DataTable::ID_SUMMARY_ROW) { - continue; - } + foreach ($table->getRowsWithoutSummaryRow() as $rowId => $row) { + $groupByColumnValue = $row->getColumn($this->groupByColumn); + $groupByValue = $groupByColumnValue; // reduce the group by column of this row - $groupByColumnValue = $row->getColumn($this->groupByColumn); - $parameters = array_merge(array($groupByColumnValue), $this->parameters); - $groupByValue = call_user_func_array($this->reduceFunction, $parameters); + if ($this->reduceFunction) { + $parameters = array_merge(array($groupByColumnValue), $this->parameters); + $groupByValue = call_user_func_array($this->reduceFunction, $parameters); + } if (!isset($groupByRows[$groupByValue])) { // if we haven't encountered this group by value before, we mark this row as a @@ -98,6 +99,10 @@ class GroupBy extends BaseFilter } } + if ($this->groupByColumn === 'label') { + $table->setLabelsHaveChanged(); + } + // delete the unneeded rows. $table->deleteRows($nonGroupByRowIds); } diff --git a/www/analytics/core/DataTable/Filter/Limit.php b/www/analytics/core/DataTable/Filter/Limit.php index d1174293..415be296 100644 --- a/www/analytics/core/DataTable/Filter/Limit.php +++ b/www/analytics/core/DataTable/Filter/Limit.php @@ -1,6 +1,6 @@ 15 * $dataTable->filter('Limit', array(5, 10)); * @@ -34,9 +34,9 @@ class Limit extends BaseFilter public function __construct($table, $offset, $limit = -1, $keepSummaryRow = false) { parent::__construct($table); - $this->offset = $offset; - $this->limit = $limit; + $this->offset = $offset; + $this->limit = $limit; $this->keepSummaryRow = $keepSummaryRow; } diff --git a/www/analytics/core/DataTable/Filter/MetadataCallbackAddMetadata.php b/www/analytics/core/DataTable/Filter/MetadataCallbackAddMetadata.php index 767ecc4f..414f9391 100644 --- a/www/analytics/core/DataTable/Filter/MetadataCallbackAddMetadata.php +++ b/www/analytics/core/DataTable/Filter/MetadataCallbackAddMetadata.php @@ -1,6 +1,6 @@ filter('MetadataCallbackAddMetadata', array('url', 'logo', 'Piwik\Plugins\MyPlugin\getLogoFromUrl')); * @@ -31,7 +31,7 @@ class MetadataCallbackAddMetadata extends BaseFilter /** * Constructor. - * + * * @param DataTable $table The DataTable that will eventually be filtered. * @param string|array $metadataToRead The metadata to read from each row and pass to the callback. * @param string $metadataToAdd The name of the metadata to add. @@ -57,16 +57,18 @@ class MetadataCallbackAddMetadata extends BaseFilter /** * See {@link MetadataCallbackAddMetadata}. - * + * * @param DataTable $table */ public function filter($table) { - foreach ($table->getRows() as $key => $row) { - if (!$this->applyToSummaryRow && $key == DataTable::ID_SUMMARY_ROW) { - continue; - } + if ($this->applyToSummaryRow) { + $rows = $table->getRows(); + } else { + $rows = $table->getRowsWithoutSummaryRow(); + } + foreach ($rows as $key => $row) { $params = array(); foreach ($this->metadataToRead as $name) { $params[] = $row->getMetadata($name); diff --git a/www/analytics/core/DataTable/Filter/MetadataCallbackReplace.php b/www/analytics/core/DataTable/Filter/MetadataCallbackReplace.php index 2d472c9c..cb04cdb1 100644 --- a/www/analytics/core/DataTable/Filter/MetadataCallbackReplace.php +++ b/www/analytics/core/DataTable/Filter/MetadataCallbackReplace.php @@ -1,6 +1,6 @@ filter('MetadataCallbackReplace', array('url', function ($url) { * return $url . '#index'; * })); @@ -27,7 +27,7 @@ class MetadataCallbackReplace extends ColumnCallbackReplace { /** * Constructor. - * + * * @param DataTable $table The DataTable that will eventually be filtered. * @param array|string $metadataToFilter The metadata whose values should be passed to the callback * and then replaced with the callback's result. diff --git a/www/analytics/core/DataTable/Filter/Pattern.php b/www/analytics/core/DataTable/Filter/Pattern.php index 30a7907b..1327b4f4 100644 --- a/www/analytics/core/DataTable/Filter/Pattern.php +++ b/www/analytics/core/DataTable/Filter/Pattern.php @@ -1,6 +1,6 @@ filter('Pattern', array('label', '^piwik')); * @@ -23,6 +23,9 @@ use Piwik\DataTable\BaseFilter; */ class Pattern extends BaseFilter { + /** + * @var string|array + */ private $columnToFilter; private $patternToSearch; private $patternToSearchQuoted; @@ -30,7 +33,7 @@ class Pattern extends BaseFilter /** * Constructor. - * + * * @param DataTable $table The table to eventually filter. * @param string $columnToFilter The column to match with the `$patternToSearch` pattern. * @param string $patternToSearch The regex pattern to use. @@ -53,7 +56,7 @@ class Pattern extends BaseFilter * @return string * @ignore */ - static public function getPatternQuoted($pattern) + public static function getPatternQuoted($pattern) { return '/' . str_replace('/', '\/', $pattern) . '/'; } @@ -67,14 +70,14 @@ class Pattern extends BaseFilter * @return int * @ignore */ - static public function match($patternQuoted, $string, $invertedMatch = false) + public static function match($patternQuoted, $string, $invertedMatch = false) { return preg_match($patternQuoted . "i", $string) == 1 ^ $invertedMatch; } /** * See {@link Pattern}. - * + * * @param DataTable $table */ public function filter($table) @@ -93,4 +96,30 @@ class Pattern extends BaseFilter } } } + + /** + * See {@link Pattern}. + * + * @param array $array + * @return array + */ + public function filterArray($array) + { + $newArray = array(); + + foreach ($array as $key => $row) { + foreach ($this->columnToFilter as $column) { + if (!array_key_exists($column, $row)) { + continue; + } + + if (self::match($this->patternToSearchQuoted, $row[$column], $this->invertedMatch)) { + $newArray[$key] = $row; + continue 2; + } + } + } + + return $newArray; + } } diff --git a/www/analytics/core/DataTable/Filter/PatternRecursive.php b/www/analytics/core/DataTable/Filter/PatternRecursive.php index ed4c137f..62a8b26b 100644 --- a/www/analytics/core/DataTable/Filter/PatternRecursive.php +++ b/www/analytics/core/DataTable/Filter/PatternRecursive.php @@ -1,6 +1,6 @@ filter('PatternRecursive', array('label', 'index')); * @@ -32,7 +30,7 @@ class PatternRecursive extends BaseFilter /** * Constructor. - * + * * @param DataTable $table The table to eventually filter. * @param string $columnToFilter The column to match with the `$patternToSearch` pattern. * @param string $patternToSearch The regex pattern to use. @@ -48,7 +46,7 @@ class PatternRecursive extends BaseFilter /** * See {@link PatternRecursive}. - * + * * @param DataTable $table * @return int The number of deleted rows. */ @@ -62,18 +60,15 @@ class PatternRecursive extends BaseFilter // AND 2 - the label is not found in the children $patternNotFoundInChildren = false; - try { - $idSubTable = $row->getIdSubDataTable(); - $subTable = Manager::getInstance()->getTable($idSubTable); - + $subTable = $row->getSubtable(); + if (!$subTable) { + $patternNotFoundInChildren = true; + } else { // we delete the row if we couldn't find the pattern in any row in the // children hierarchy if ($this->filter($subTable) == 0) { $patternNotFoundInChildren = true; } - } catch (Exception $e) { - // there is no subtable loaded for example - $patternNotFoundInChildren = true; } if ($patternNotFoundInChildren diff --git a/www/analytics/core/DataTable/Filter/PivotByDimension.php b/www/analytics/core/DataTable/Filter/PivotByDimension.php new file mode 100644 index 00000000..b7f5ca1c --- /dev/null +++ b/www/analytics/core/DataTable/Filter/PivotByDimension.php @@ -0,0 +1,550 @@ +pivotColumn = $pivotColumn; + $this->pivotByColumnLimit = $pivotByColumnLimit ?: self::getDefaultColumnLimit(); + $this->isFetchingBySegmentEnabled = $isFetchingBySegmentEnabled; + + $namesToId = Metrics::getMappingFromNameToId(); + $this->metricIndexValue = isset($namesToId[$this->pivotColumn]) ? $namesToId[$this->pivotColumn] : null; + + $this->setPivotByDimension($pivotByDimension); + $this->setThisReportMetadata($report); + + $this->checkSupportedPivot(); + } + + /** + * Pivots to table. + * + * @param DataTable $table The table to manipulate. + */ + public function filter($table) + { + // set of all column names in the pivoted table mapped with the sum of all column + // values. used later in truncating and ordering the pivoted table's columns. + $columnSet = array(); + + // if no pivot column was set, use the first one found in the row + if (empty($this->pivotColumn)) { + $this->pivotColumn = $this->getNameOfFirstNonLabelColumnInTable($table); + } + + Log::debug("PivotByDimension::%s: pivoting table with pivot column = %s", __FUNCTION__, $this->pivotColumn); + + foreach ($table->getRows() as $row) { + $row->setColumns(array('label' => $row->getColumn('label'))); + + $associatedTable = $this->getIntersectedTable($table, $row); + if (!empty($associatedTable)) { + foreach ($associatedTable->getRows() as $columnRow) { + $pivotTableColumn = $columnRow->getColumn('label'); + + $columnValue = $this->getColumnValue($columnRow, $this->pivotColumn); + + if (isset($columnSet[$pivotTableColumn])) { + $columnSet[$pivotTableColumn] += $columnValue; + } else { + $columnSet[$pivotTableColumn] = $columnValue; + } + + $row->setColumn($pivotTableColumn, $columnValue); + } + + Common::destroy($associatedTable); + unset($associatedTable); + } + } + + Log::debug("PivotByDimension::%s: pivoted columns set: %s", __FUNCTION__, $columnSet); + + $others = Piwik::translate('General_Others'); + $defaultRow = $this->getPivotTableDefaultRowFromColumnSummary($columnSet, $others); + + Log::debug("PivotByDimension::%s: un-prepended default row: %s", __FUNCTION__, $defaultRow); + + // post process pivoted datatable + foreach ($table->getRows() as $row) { + // remove subtables from rows + $row->removeSubtable(); + $row->deleteMetadata('idsubdatatable_in_db'); + + // use default row to ensure column ordering and add missing columns/aggregate cut-off columns + $orderedColumns = $defaultRow; + foreach ($row->getColumns() as $name => $value) { + if (isset($orderedColumns[$name])) { + $orderedColumns[$name] = $value; + } else { + $orderedColumns[$others] += $value; + } + } + $row->setColumns($orderedColumns); + } + + $table->clearQueuedFilters(); // TODO: shouldn't clear queued filters, but we can't wait for them to be run + // since generic filters are run before them. remove after refactoring + // processed metrics. + + // prepend numerals to columns in a queued filter (this way, disable_queued_filters can be used + // to get machine readable data from the API if needed) + $prependedColumnNames = $this->getOrderedColumnsWithPrependedNumerals($defaultRow, $others); + + Log::debug("PivotByDimension::%s: prepended column name mapping: %s", __FUNCTION__, $prependedColumnNames); + + $table->queueFilter(function (DataTable $table) use ($prependedColumnNames) { + foreach ($table->getRows() as $row) { + $row->setColumns(array_combine($prependedColumnNames, $row->getColumns())); + } + }); + } + + /** + * An intersected table is a table that describes visits by a certain dimension for the visits + * represented by a row in another table. This method fetches intersected tables either via + * subtable or by using a segment. Read the class docs for more info. + */ + private function getIntersectedTable(DataTable $table, Row $row) + { + if ($this->isPivotDimensionSubtable()) { + return $this->loadSubtable($table, $row); + } + + if ($this->isFetchingBySegmentEnabled) { + $segmentValue = $row->getColumn('label'); + return $this->fetchIntersectedWithThisBySegment($table, $segmentValue); + } + + // should never occur, unless checkSupportedPivot() fails to catch an unsupported pivot + throw new Exception("Unexpected error, cannot fetch intersected table."); + } + + private function isPivotDimensionSubtable() + { + return self::areDimensionsEqualAndNotNull($this->subtableDimension, $this->pivotByDimension); + } + + private function loadSubtable(DataTable $table, Row $row) + { + $idSubtable = $row->getIdSubDataTable(); + if ($idSubtable === null) { + return null; + } + + $subtable = $row->getSubtable(); + if (!$subtable) { + $subtable = $this->thisReport->fetchSubtable($idSubtable, $this->getRequestParamOverride($table)); + } + + if (!$subtable) { // sanity check + throw new Exception("Unexpected error: could not load subtable '$idSubtable'."); + } + + return $subtable; + } + + private function fetchIntersectedWithThisBySegment(DataTable $table, $segmentValue) + { + $segmentStr = $this->thisReportDimensionSegment->getSegment() . "==" . urlencode($segmentValue); + + // TODO: segment + report API method query params should be stored in DataTable metadata so we don't have to access it here + $originalSegment = Common::getRequestVar('segment', false); + if (!empty($originalSegment)) { + $segmentStr = $originalSegment . ';' . $segmentStr; + } + + Log::debug("PivotByDimension: Fetching intersected with segment '%s'", $segmentStr); + + $params = array('segment' => $segmentStr) + $this->getRequestParamOverride($table); + return $this->pivotDimensionReport->fetch($params); + } + + private function setPivotByDimension($pivotByDimension) + { + $this->pivotByDimension = Dimension::factory($pivotByDimension); + if (empty($this->pivotByDimension)) { + throw new Exception("Invalid dimension '$pivotByDimension'."); + } + + $this->pivotDimensionReport = Report::getForDimension($this->pivotByDimension); + } + + private function setThisReportMetadata($report) + { + list($module, $method) = explode('.', $report); + + $this->thisReport = Report::factory($module, $method); + if (empty($this->thisReport)) { + throw new Exception("Unable to find report '$report'."); + } + + $this->subtableDimension = $this->thisReport->getSubtableDimension(); + + $thisReportDimension = $this->thisReport->getDimension(); + if ($thisReportDimension !== null) { + $segments = $thisReportDimension->getSegments(); + $this->thisReportDimensionSegment = reset($segments); + } + } + + private function checkSupportedPivot() + { + $reportId = $this->thisReport->getModule() . '.' . $this->thisReport->getName(); + + if (!$this->isFetchingBySegmentEnabled) { + // if fetching by segment is disabled, then there must be a subtable for the current report and + // subtable's dimension must be the pivot dimension + + if (empty($this->subtableDimension)) { + throw new Exception("Unsupported pivot: report '$reportId' has no subtable dimension."); + } + + if (!$this->isPivotDimensionSubtable()) { + throw new Exception("Unsupported pivot: the subtable dimension for '$reportId' does not match the " + . "requested pivotBy dimension. [subtable dimension = {$this->subtableDimension->getId()}, " + . "pivot by dimension = {$this->pivotByDimension->getId()}]"); + } + } else { + $canFetchBySubtable = !empty($this->subtableDimension) + && $this->subtableDimension->getId() === $this->pivotByDimension->getId(); + if ($canFetchBySubtable) { + return; + } + + // if fetching by segment is enabled, and we cannot fetch by subtable, then there has to be a report + // for the pivot dimension (so we can fetch the report), and there has to be a segment for this report's + // dimension (so we can use it when fetching) + + if (empty($this->pivotDimensionReport)) { + throw new Exception("Unsupported pivot: No report for pivot dimension '{$this->pivotByDimension->getId()}'" + . " (report required for fetching intersected tables by segment)."); + } + + if (empty($this->thisReportDimensionSegment)) { + throw new Exception("Unsupported pivot: No segment for dimension of report '$reportId'." + . " (segment required for fetching intersected tables by segment)."); + } + } + } + + /** + * @param $columnRow + * @param $pivotColumn + * @return false|mixed + */ + private function getColumnValue(Row $columnRow, $pivotColumn) + { + $value = $columnRow->getColumn($pivotColumn); + if (empty($value) + && !empty($this->metricIndexValue) + ) { + $value = $columnRow->getColumn($this->metricIndexValue); + } + return $value; + } + + private function getNameOfFirstNonLabelColumnInTable(DataTable $table) + { + foreach ($table->getRows() as $row) { + foreach ($row->getColumns() as $columnName => $ignore) { + if ($columnName != 'label') { + return $columnName; + } + } + } + } + + private function getRequestParamOverride(DataTable $table) + { + $params = array( + 'pivotBy' => '', + 'column' => '', + 'flat' => 0, + 'totals' => 0, + 'disable_queued_filters' => 1, + 'disable_generic_filters' => 1, + 'showColumns' => '', + 'hideColumns' => '' + ); + + /** @var Site $site */ + $site = $table->getMetadata('site'); + if (!empty($site)) { + $params['idSite'] = $site->getId(); + } + + /** @var Period $period */ + $period = $table->getMetadata('period'); + if (!empty($period)) { + $params['period'] = $period->getLabel(); + + if ($params['period'] == 'range') { + $params['date'] = $period->getRangeString(); + } else { + $params['date'] = $period->getDateStart()->toString(); + } + } + + return $params; + } + + private function getPivotTableDefaultRowFromColumnSummary($columnSet, $othersRowLabel) + { + // sort columns by sum (to ensure deterministic ordering) + arsort($columnSet); + + // limit columns if necessary (adding aggregate Others column at end) + if ($this->pivotByColumnLimit > 0 + && count($columnSet) > $this->pivotByColumnLimit + ) { + $columnSet = array_slice($columnSet, 0, $this->pivotByColumnLimit - 1, $preserveKeys = true); + $columnSet[$othersRowLabel] = 0; + } + + // remove column sums from array so it can be used as a default row + $columnSet = array_map(function () { return false; }, $columnSet); + + // make sure label column is first + $columnSet = array('label' => false) + $columnSet; + + return $columnSet; + } + + private function getOrderedColumnsWithPrependedNumerals($defaultRow, $othersRowLabel) + { + $flags = ENT_COMPAT; + if (defined('ENT_HTML401')) { + $flags |= ENT_HTML401; // part of default flags for 5.4, but not 5.3 + } + + // must use decoded character otherwise sort later will fail + // (sort column will be set to decoded but columns will have  ) + $nbsp = html_entity_decode(' ', $flags, 'utf-8'); + + $result = array(); + + $currentIndex = 1; + foreach ($defaultRow as $columnName => $ignore) { + if ($columnName === $othersRowLabel + || $columnName === 'label' + ) { + $result[] = $columnName; + } else { + $modifiedColumnName = $currentIndex . '.' . $nbsp . $columnName; + $result[] = $modifiedColumnName; + + ++$currentIndex; + } + } + + return $result; + } + + /** + * Returns true if pivoting by subtable is supported for a report. Will return true if the report + * has a subtable dimension and if the subtable dimension is different than the report's dimension. + * + * @param Report $report + * @return bool + */ + public static function isPivotingReportBySubtableSupported(Report $report) + { + return self::areDimensionsNotEqualAndNotNull($report->getSubtableDimension(), $report->getDimension()); + } + + /** + * Returns true if fetching intersected tables by segment is enabled in the INI config, false if otherwise. + * + * @return bool + */ + public static function isSegmentFetchingEnabledInConfig() + { + return Config::getInstance()->General['pivot_by_filter_enable_fetch_by_segment']; + } + + /** + * Returns the default maximum number of columns to allow in a pivot table from the INI config. + * Uses the **pivot_by_filter_default_column_limit** INI config option. + * + * @return int + */ + public static function getDefaultColumnLimit() + { + return Config::getInstance()->General['pivot_by_filter_default_column_limit']; + } + + /** + * @param Dimension|null $lhs + * @param Dimension|null $rhs + * @return bool + */ + private static function areDimensionsEqualAndNotNull($lhs, $rhs) + { + return !empty($lhs) && !empty($rhs) && $lhs->getId() == $rhs->getId(); + } + + /** + * @param Dimension|null $lhs + * @param Dimension|null $rhs + * @return bool + */ + private static function areDimensionsNotEqualAndNotNull($lhs, $rhs) + { + return !empty($lhs) && !empty($rhs) && $lhs->getId() != $rhs->getId(); + } +} diff --git a/www/analytics/core/DataTable/Filter/PrependSegment.php b/www/analytics/core/DataTable/Filter/PrependSegment.php new file mode 100644 index 00000000..34d14e6f --- /dev/null +++ b/www/analytics/core/DataTable/Filter/PrependSegment.php @@ -0,0 +1,34 @@ +filter('PrependSegment', array('segmentName==segmentValue;')); + * + * @api + */ +class PrependSegment extends PrependValueToMetadata +{ + /** + * @param DataTable $table + * @param string $prependSegment The segment to prepend if a segment is already defined. Make sure to include + * A condition, eg the segment should end with ';' or ',' + */ + public function __construct($table, $prependSegment = '') + { + parent::__construct($table, 'segment', $prependSegment); + } +} diff --git a/www/analytics/core/DataTable/Filter/PrependValueToMetadata.php b/www/analytics/core/DataTable/Filter/PrependValueToMetadata.php new file mode 100644 index 00000000..3e2e0ceb --- /dev/null +++ b/www/analytics/core/DataTable/Filter/PrependValueToMetadata.php @@ -0,0 +1,65 @@ +filter('PrependValueToMetadata', array('segment', 'segmentName==segmentValue')); + * + * @api + */ +class PrependValueToMetadata extends BaseFilter +{ + private $metadataColumn; + private $valueToPrepend; + + /** + * @param DataTable $table + * @param string $metadataName The name of the metadata that should be prepended + * @param string $valueToPrepend The value to prepend if the metadata entry exists + */ + public function __construct($table, $metadataName, $valueToPrepend) + { + parent::__construct($table); + + $this->metadataColumn = $metadataName; + $this->valueToPrepend = $valueToPrepend; + } + + /** + * See {@link PrependValueToMetadata}. + * + * @param DataTable $table + */ + public function filter($table) + { + if (empty($this->metadataColumn) || empty($this->valueToPrepend)) { + return; + } + + $metadataColumn = $this->metadataColumn; + $valueToPrepend = $this->valueToPrepend; + + $table->filter(function (DataTable $dataTable) use ($metadataColumn, $valueToPrepend) { + foreach ($dataTable->getRows() as $row) { + $filter = $row->getMetadata($metadataColumn); + if ($filter !== false) { + $row->setMetadata($metadataColumn, $valueToPrepend . $filter); + } + } + }); + } +} diff --git a/www/analytics/core/DataTable/Filter/RangeCheck.php b/www/analytics/core/DataTable/Filter/RangeCheck.php index d8229a4d..211638d3 100644 --- a/www/analytics/core/DataTable/Filter/RangeCheck.php +++ b/www/analytics/core/DataTable/Filter/RangeCheck.php @@ -1,6 +1,6 @@ columnToFilter = $columnToFilter; - if ($minimumValue < $maximumValue) { + if ((float) $minimumValue < (float) $maximumValue) { self::$minimumValue = $minimumValue; self::$maximumValue = $maximumValue; } @@ -47,10 +47,23 @@ class RangeCheck extends BaseFilter { foreach ($table->getRows() as $row) { $value = $row->getColumn($this->columnToFilter); + + if ($value === false) { + $value = $row->getMetadata($this->columnToFilter); + if ($value !== false) { + if ($value < (float) self::$minimumValue) { + $row->setMetadata($this->columnToFilter, self::$minimumValue); + } elseif ($value > (float) self::$maximumValue) { + $row->setMetadata($this->columnToFilter, self::$maximumValue); + } + } + continue; + } + if ($value !== false) { - if ($value < self::$minimumValue) { + if ($value < (float) self::$minimumValue) { $row->setColumn($this->columnToFilter, self::$minimumValue); - } elseif ($value > self::$maximumValue) { + } elseif ($value > (float) self::$maximumValue) { $row->setColumn($this->columnToFilter, self::$maximumValue); } } diff --git a/www/analytics/core/DataTable/Filter/ReplaceColumnNames.php b/www/analytics/core/DataTable/Filter/ReplaceColumnNames.php index fd842ad0..cbf4bbb3 100644 --- a/www/analytics/core/DataTable/Filter/ReplaceColumnNames.php +++ b/www/analytics/core/DataTable/Filter/ReplaceColumnNames.php @@ -1,6 +1,6 @@ queueFilter('ReplaceColumnNames'); * return $dataTable; * } - * + * * @api */ class ReplaceColumnNames extends BaseFilter @@ -43,14 +43,14 @@ class ReplaceColumnNames extends BaseFilter /** * Constructor. - * + * * @param DataTable $table The table that will be eventually filtered. * @param array|null $mappingToApply The name mapping to apply. Must map old column names * with new ones, eg, - * + * * array('OLD_COLUMN_NAME' => 'NEW_COLUMN NAME', * 'OLD_COLUMN_NAME2' => 'NEW_COLUMN NAME2') - * + * * If null, {@link Piwik\Metrics::$mappingFromIdToName} is used. */ public function __construct($table, $mappingToApply = null) @@ -81,9 +81,8 @@ class ReplaceColumnNames extends BaseFilter */ protected function filterTable($table) { - foreach ($table->getRows() as $key => $row) { - $oldColumns = $row->getColumns(); - $newColumns = $this->getRenamedColumns($oldColumns); + foreach ($table->getRows() as $row) { + $newColumns = $this->getRenamedColumns($row->getColumns()); $row->setColumns($newColumns); $this->filterSubTable($row); } diff --git a/www/analytics/core/DataTable/Filter/ReplaceSummaryRowLabel.php b/www/analytics/core/DataTable/Filter/ReplaceSummaryRowLabel.php index b89a306a..3256fe96 100644 --- a/www/analytics/core/DataTable/Filter/ReplaceSummaryRowLabel.php +++ b/www/analytics/core/DataTable/Filter/ReplaceSummaryRowLabel.php @@ -1,6 +1,6 @@ queueFilter('ReplaceSummaryRowLabel', array(Piwik::translate('General_Others'))); - * + * * @api */ class ReplaceSummaryRowLabel extends BaseFilter { /** * Constructor. - * + * * @param DataTable $table The table that will eventually be filtered. * @param string|null $newLabel The new label for summary row. If null, defaults to * `Piwik::translate('General_Others')`. @@ -53,20 +52,20 @@ class ReplaceSummaryRowLabel extends BaseFilter */ public function filter($table) { - $rows = $table->getRows(); - foreach ($rows as $id => $row) { - if ($row->getColumn('label') == DataTable::LABEL_SUMMARY_ROW - || $id == DataTable::ID_SUMMARY_ROW - ) { + $row = $table->getRowFromId(DataTable::ID_SUMMARY_ROW); + if ($row) { + $row->setColumn('label', $this->newLabel); + } else { + $row = $table->getRowFromLabel(DataTable::LABEL_SUMMARY_ROW); + if ($row) { $row->setColumn('label', $this->newLabel); - break; } } // recurse - foreach ($rows as $row) { - if ($row->isSubtableLoaded()) { - $subTable = Manager::getInstance()->getTable($row->getIdSubDataTable()); + foreach ($table->getRowsWithoutSummaryRow() as $row) { + $subTable = $row->getSubtable(); + if ($subTable) { $this->filter($subTable); } } diff --git a/www/analytics/core/DataTable/Filter/SafeDecodeLabel.php b/www/analytics/core/DataTable/Filter/SafeDecodeLabel.php index eb44c8d7..f2629618 100644 --- a/www/analytics/core/DataTable/Filter/SafeDecodeLabel.php +++ b/www/analytics/core/DataTable/Filter/SafeDecodeLabel.php @@ -1,6 +1,6 @@ enableRecursiveSort(); } + $this->columnToSort = $columnToSort; - $this->naturalSort = $naturalSort; + $this->naturalSort = $naturalSort; $this->setOrder($order); } @@ -55,67 +61,56 @@ class Sort extends BaseFilter { if ($order == 'asc') { $this->order = 'asc'; - $this->sign = 1; + $this->sign = 1; } else { $this->order = 'desc'; - $this->sign = -1; + $this->sign = -1; } } /** * Sorting method used for sorting numbers * - * @param number $a - * @param number $b + * @param array $rowA array[0 => value of column to sort, 1 => label] + * @param array $rowB array[0 => value of column to sort, 1 => label] * @return int */ - public function numberSort($a, $b) + public function numberSort($rowA, $rowB) { - return !isset($a->c[Row::COLUMNS][$this->columnToSort]) - && !isset($b->c[Row::COLUMNS][$this->columnToSort]) + if (isset($rowA[0]) && isset($rowB[0])) { + if ($rowA[0] != $rowB[0] || !isset($rowA[1])) { + return $this->sign * ($rowA[0] < $rowB[0] ? -1 : 1); + } else { + return -1 * $this->sign * strnatcasecmp($rowA[1], $rowB[1]); + } + } elseif (!isset($rowB[0]) && !isset($rowA[0])) { + return -1 * $this->sign * strnatcasecmp($rowA[1], $rowB[1]); + } elseif (!isset($rowA[0])) { + return 1; + } - ? 0 - : ( - !isset($a->c[Row::COLUMNS][$this->columnToSort]) - ? 1 - : ( - !isset($b->c[Row::COLUMNS][$this->columnToSort]) - ? -1 - : (($a->c[Row::COLUMNS][$this->columnToSort] != $b->c[Row::COLUMNS][$this->columnToSort] - || !isset($a->c[Row::COLUMNS]['label'])) - ? ($this->sign * ( - $a->c[Row::COLUMNS][$this->columnToSort] - < $b->c[Row::COLUMNS][$this->columnToSort] - ? -1 - : 1) - ) - : -1 * $this->sign * strnatcasecmp( - $a->c[Row::COLUMNS]['label'], - $b->c[Row::COLUMNS]['label']) - ) - ) - ); + return -1; } /** * Sorting method used for sorting values natural * - * @param mixed $a - * @param mixed $b + * @param mixed $valA + * @param mixed $valB * @return int */ - function naturalSort($a, $b) + public function naturalSort($valA, $valB) { - return !isset($a->c[Row::COLUMNS][$this->columnToSort]) - && !isset($b->c[Row::COLUMNS][$this->columnToSort]) + return !isset($valA) + && !isset($valB) ? 0 - : (!isset($a->c[Row::COLUMNS][$this->columnToSort]) + : (!isset($valA) ? 1 - : (!isset($b->c[Row::COLUMNS][$this->columnToSort]) + : (!isset($valB) ? -1 : $this->sign * strnatcasecmp( - $a->c[Row::COLUMNS][$this->columnToSort], - $b->c[Row::COLUMNS][$this->columnToSort] + $valA, + $valB ) ) ); @@ -124,27 +119,38 @@ class Sort extends BaseFilter /** * Sorting method used for sorting values * - * @param mixed $a - * @param mixed $b + * @param mixed $valA + * @param mixed $valB * @return int */ - function sortString($a, $b) + public function sortString($valA, $valB) { - return !isset($a->c[Row::COLUMNS][$this->columnToSort]) - && !isset($b->c[Row::COLUMNS][$this->columnToSort]) + return !isset($valA) + && !isset($valB) ? 0 - : (!isset($a->c[Row::COLUMNS][$this->columnToSort]) + : (!isset($valA) ? 1 - : (!isset($b->c[Row::COLUMNS][$this->columnToSort]) + : (!isset($valB) ? -1 : $this->sign * - strcasecmp($a->c[Row::COLUMNS][$this->columnToSort], - $b->c[Row::COLUMNS][$this->columnToSort] + strcasecmp($valA, + $valB ) ) ); } + protected function getColumnValue(Row $row) + { + $value = $row->getColumn($this->columnToSort); + + if ($value === false || is_array($value)) { + return null; + } + + return $value; + } + /** * Sets the column to be used for sorting * @@ -153,18 +159,18 @@ class Sort extends BaseFilter */ protected function selectColumnToSort($row) { - $value = $row->getColumn($this->columnToSort); - if ($value !== false) { + $value = $row->hasColumn($this->columnToSort); + if ($value) { return $this->columnToSort; } - $columnIdToName = Metrics::getMappingFromIdToName(); + $columnIdToName = Metrics::getMappingFromNameToId(); // sorting by "nb_visits" but the index is Metrics::INDEX_NB_VISITS in the table if (isset($columnIdToName[$this->columnToSort])) { $column = $columnIdToName[$this->columnToSort]; - $value = $row->getColumn($column); + $value = $row->hasColumn($column); - if ($value !== false) { + if ($value) { return $column; } } @@ -172,8 +178,8 @@ class Sort extends BaseFilter // eg. was previously sorted by revenue_per_visit, but this table // doesn't have this column; defaults with nb_visits $column = Metrics::INDEX_NB_VISITS; - $value = $row->getColumn($column); - if ($value !== false) { + $value = $row->hasColumn($column); + if ($value) { return $column; } @@ -193,21 +199,25 @@ class Sort extends BaseFilter if ($table instanceof Simple) { return; } + if (empty($this->columnToSort)) { return; } - $rows = $table->getRows(); - if (count($rows) == 0) { + + if (!$table->getRowsCount()) { return; } - $row = current($rows); + + $row = $table->getFirstRow(); if ($row === false) { return; } + $this->columnToSort = $this->selectColumnToSort($row); - $value = $row->getColumn($this->columnToSort); - if (is_numeric($value)) { + $value = $this->getFirstValueFromDataTable($table); + + if (is_numeric($value) && $this->columnToSort !== 'label') { $methodToUse = "numberSort"; } else { if ($this->naturalSort) { @@ -216,6 +226,65 @@ class Sort extends BaseFilter $methodToUse = "sortString"; } } - $table->sort(array($this, $methodToUse), $this->columnToSort); + + $this->sort($table, $methodToUse); + } + + private function getFirstValueFromDataTable($table) + { + foreach ($table->getRowsWithoutSummaryRow() as $row) { + $value = $this->getColumnValue($row); + if (!is_null($value)) { + return $value; + } + } + } + + /** + * Sorts the DataTable rows using the supplied callback function. + * + * @param string $functionCallback A comparison callback compatible with {@link usort}. + * @param string $columnSortedBy The column name `$functionCallback` sorts by. This is stored + * so we can determine how the DataTable was sorted in the future. + */ + private function sort(DataTable $table, $functionCallback) + { + $table->setTableSortedBy($this->columnToSort); + + $rows = $table->getRowsWithoutSummaryRow(); + + // get column value and label only once for performance tweak + $values = array(); + if ($functionCallback === 'numberSort') { + foreach ($rows as $key => $row) { + $values[$key] = array($this->getColumnValue($row), $row->getColumn('label')); + } + } else { + foreach ($rows as $key => $row) { + $values[$key] = $this->getColumnValue($row); + } + } + + uasort($values, array($this, $functionCallback)); + + $sortedRows = array(); + foreach ($values as $key => $value) { + $sortedRows[] = $rows[$key]; + } + + $table->setRows($sortedRows); + + unset($rows); + unset($sortedRows); + + if ($table->isSortRecursiveEnabled()) { + foreach ($table->getRowsWithoutSummaryRow() as $row) { + $subTable = $row->getSubtable(); + if ($subTable) { + $subTable->enableRecursiveSort(); + $this->sort($subTable, $functionCallback); + } + } + } } } diff --git a/www/analytics/core/DataTable/Filter/Truncate.php b/www/analytics/core/DataTable/Filter/Truncate.php index fa538965..ec95811c 100644 --- a/www/analytics/core/DataTable/Filter/Truncate.php +++ b/www/analytics/core/DataTable/Filter/Truncate.php @@ -1,6 +1,6 @@ filter('Truncate', array($truncateAfter = 500)); - * + * * **Using a custom summary row label** - * + * * $dataTable->filter('Truncate', array($truncateAfter = 500, $summaryRowLabel = Piwik::translate('General_Total'))); - * + * * @api */ class Truncate extends BaseFilter { /** * Constructor. - * + * * @param DataTable $table The table that will be filtered eventually. * @param int $truncateAfter The row index to truncate at. All rows passed this index will * be removed. @@ -69,11 +69,15 @@ class Truncate extends BaseFilter */ public function filter($table) { + if ($this->truncateAfter < 0) { + return; + } + $this->addSummaryRow($table); $table->queueFilter('ReplaceSummaryRowLabel', array($this->labelSummaryRow)); if ($this->filterRecursive) { - foreach ($table->getRows() as $row) { + foreach ($table->getRowsWithoutSummaryRow() as $row) { if ($row->isSubtableLoaded()) { $this->filter($row->getSubtable()); } @@ -81,17 +85,23 @@ class Truncate extends BaseFilter } } + /** + * @param DataTable $table + */ private function addSummaryRow($table) { - $table->filter('Sort', array($this->columnToSortByBeforeTruncating, 'desc')); - if ($table->getRowsCount() <= $this->truncateAfter + 1) { return; } - $rows = $table->getRows(); - $count = $table->getRowsCount(); + $table->filter('Sort', array($this->columnToSortByBeforeTruncating, 'desc', $naturalSort = true, $recursiveSort = false)); + + $rows = array_values($table->getRows()); + $count = $table->getRowsCount(); $newRow = new Row(array(Row::COLUMNS => array('label' => DataTable::LABEL_SUMMARY_ROW))); + + $aggregationOps = $table->getMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME); + for ($i = $this->truncateAfter; $i < $count; $i++) { if (!isset($rows[$i])) { // case when the last row is a summary row, it is not indexed by $cout but by DataTable::ID_SUMMARY_ROW @@ -99,10 +109,10 @@ class Truncate extends BaseFilter //FIXME: I'm not sure why it could return false, but it was reported in: http://forum.piwik.org/read.php?2,89324,page=1#msg-89442 if ($summaryRow) { - $newRow->sumRow($summaryRow, $enableCopyMetadata = false, $table->getMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME)); + $newRow->sumRow($summaryRow, $enableCopyMetadata = false, $aggregationOps); } } else { - $newRow->sumRow($rows[$i], $enableCopyMetadata = false, $table->getMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME)); + $newRow->sumRow($rows[$i], $enableCopyMetadata = false, $aggregationOps); } } diff --git a/www/analytics/core/DataTable/Manager.php b/www/analytics/core/DataTable/Manager.php index b9703c14..228f13da 100644 --- a/www/analytics/core/DataTable/Manager.php +++ b/www/analytics/core/DataTable/Manager.php @@ -1,6 +1,6 @@ tables[$this->nextTableId] = $table; $this->nextTableId++; - return $this->nextTableId - 1; + $this[$this->nextTableId] = $table; + return $this->nextTableId; } /** @@ -60,10 +61,11 @@ class Manager extends Singleton */ public function getTable($idTable) { - if (!isset($this->tables[$idTable])) { - throw new TableNotFoundException(sprintf("This report has been reprocessed since your last click. To see this error less often, please increase the timeout value in seconds in Settings > General Settings. (error: id %s not found).", $idTable)); + if (!isset($this[$idTable])) { + throw new TableNotFoundException(sprintf("Error: table id %s not found in memory. (If this error is causing you problems in production, please report it in Piwik issue tracker.)", $idTable)); } - return $this->tables[$idTable]; + + return $this[$idTable]; } /** @@ -73,7 +75,7 @@ class Manager extends Singleton */ public function getMostRecentTableId() { - return $this->nextTableId - 1; + return $this->nextTableId; } /** @@ -81,14 +83,15 @@ class Manager extends Singleton */ public function deleteAll($deleteWhenIdTableGreaterThan = 0) { - foreach ($this->tables as $id => $table) { + foreach ($this as $id => $table) { if ($id > $deleteWhenIdTableGreaterThan) { $this->deleteTable($id); } } + if ($deleteWhenIdTableGreaterThan == 0) { - $this->tables = array(); - $this->nextTableId = 1; + $this->exchangeArray(array()); + $this->nextTableId = 0; } } @@ -100,8 +103,8 @@ class Manager extends Singleton */ public function deleteTable($id) { - if (isset($this->tables[$id])) { - Common::destroy($this->tables[$id]); + if (isset($this[$id])) { + Common::destroy($this[$id]); $this->setTableDeleted($id); } } @@ -131,7 +134,7 @@ class Manager extends Singleton */ public function setTableDeleted($id) { - $this->tables[$id] = null; + $this[$id] = null; } /** @@ -140,7 +143,7 @@ class Manager extends Singleton public function dumpAllTables() { echo "
Manager->dumpAllTables()
"; - foreach ($this->tables as $id => $table) { + foreach ($this as $id => $table) { if (!($table instanceof DataTable)) { echo "Error table $id is not instance of datatable
"; var_export($table); diff --git a/www/analytics/core/DataTable/Map.php b/www/analytics/core/DataTable/Map.php index 7985afb1..8f9d259f 100644 --- a/www/analytics/core/DataTable/Map.php +++ b/www/analytics/core/DataTable/Map.php @@ -1,6 +1,6 @@ getDataTables() as $id => $table) { + foreach ($this->getDataTables() as $table) { $table->filter($className, $parameters); } } + /** + * Apply a filter to all subtables contained by this instance. + * + * @param string|Closure $className Name of filter class or a Closure. + * @param array $parameters Parameters to pass to the filter. + */ + public function filterSubtables($className, $parameters = array()) + { + foreach ($this->getDataTables() as $table) { + $table->filterSubtables($className, $parameters); + } + } + + /** + * Apply a queued filter to all subtables contained by this instance. + * + * @param string|Closure $className Name of filter class or a Closure. + * @param array $parameters Parameters to pass to the filter. + */ + public function queueFilterSubtables($className, $parameters = array()) + { + foreach ($this->getDataTables() as $table) { + $table->queueFilterSubtables($className, $parameters); + } + } + /** * Returns the array of DataTables contained by this class. * @@ -142,7 +169,7 @@ class Map implements DataTableInterface /** * Returns the last element in the Map's array. - * + * * @return DataTable|Map|false */ public function getLastRow() @@ -161,6 +188,22 @@ class Map implements DataTableInterface $this->array[$label] = $table; } + public function getRowFromIdSubDataTable($idSubtable) + { + $dataTables = $this->getDataTables(); + + // find first datatable containing data + foreach ($dataTables as $subTable) { + $subTableRow = $subTable->getRowFromIdSubDataTable($idSubtable); + + if (!empty($subTableRow)) { + return $subTableRow; + } + } + + return null; + } + /** * Returns a string output of this DataTable\Map (applying the default renderer to every {@link DataTable} * of this DataTable\Map). @@ -184,11 +227,31 @@ class Map implements DataTableInterface } } + /** + * @ignore + */ + public function disableRecursiveFilters() + { + foreach ($this->getDataTables() as $table) { + $table->disableRecursiveFilters(); + } + } + + /** + * @ignore + */ + public function enableRecursiveFilters() + { + foreach ($this->getDataTables() as $table) { + $table->enableRecursiveFilters(); + } + } + /** * Renames the given column in each contained {@link DataTable}. * * See {@link DataTable::renameColumn()}. - * + * * @param string $oldName * @param string $newName */ @@ -203,7 +266,7 @@ class Map implements DataTableInterface * Deletes the specified columns in each contained {@link DataTable}. * * See {@link DataTable::deleteColumns()}. - * + * * @param array $columns The columns to delete. * @param bool $deleteRecursiveInSubtables This param is currently not used. */ @@ -216,7 +279,7 @@ class Map implements DataTableInterface /** * Deletes a table from the array of DataTables. - * + * * @param string $id The label associated with {@link DataTable}. */ public function deleteRow($id) @@ -246,12 +309,14 @@ class Map implements DataTableInterface public function getColumn($name) { $values = array(); + foreach ($this->getDataTables() as $table) { $moreValues = $table->getColumn($name); foreach ($moreValues as &$value) { $values[] = $value; } } + return $values; } @@ -263,19 +328,19 @@ class Map implements DataTableInterface * The result of this function is determined by the type of DataTable * this instance holds. If this DataTable\Map instance holds an array * of DataTables, this function will transform it from: - * + * * Label 0: * DataTable(row1) * Label 1: * DataTable(row2) - * + * * to: - * + * * DataTable(row1[label = 'Label 0'], row2[label = 'Label 1']) * * If this instance holds an array of DataTable\Maps, this function will * transform it from: - * + * * Outer Label 0: // the outer DataTable\Map * Inner Label 0: // one of the inner DataTable\Maps * DataTable(row1) @@ -286,9 +351,9 @@ class Map implements DataTableInterface * DataTable(row3) * Inner Label 1: * DataTable(row4) - * + * * to: - * + * * Inner Label 0: * DataTable(row1[label = 'Outer Label 0'], row3[label = 'Outer Label 1']) * Inner Label 1: @@ -366,11 +431,11 @@ class Map implements DataTableInterface /** * Sums a DataTable to all the tables in this array. - * + * * _Note: Will only add `$tableToSum` if the childTable has some rows._ * * See {@link Piwik\DataTable::addDataTable()}. - * + * * @param DataTable $tableToSum */ public function addDataTable(DataTable $tableToSum) diff --git a/www/analytics/core/DataTable/Renderer.php b/www/analytics/core/DataTable/Renderer.php index 7820f3e5..e8796aea 100644 --- a/www/analytics/core/DataTable/Renderer.php +++ b/www/analytics/core/DataTable/Renderer.php @@ -1,6 +1,6 @@ setTable($dataTable); * echo $render; */ -abstract class Renderer +abstract class Renderer extends BaseFactory { protected $table; @@ -100,7 +101,7 @@ abstract class Renderer */ protected function renderHeader() { - @header('Content-Type: text/plain; charset=utf-8'); + Common::sendHeader('Content-Type: text/plain; charset=utf-8'); } /** @@ -110,22 +111,6 @@ abstract class Renderer */ abstract public function render(); - /** - * Computes the exception output and returns the string/binary - * - * @return string - */ - abstract public function renderException(); - - protected function getExceptionMessage() - { - $message = $this->exception->getMessage(); - if (\Piwik_ShouldPrintBackTraceWithMessage()) { - $message .= "\n" . $this->exception->getTraceAsString(); - } - return self::renderHtmlEntities($message); - } - /** * @see render() * @return string @@ -144,32 +129,17 @@ abstract class Renderer public function setTable($table) { if (!is_array($table) - && !($table instanceof DataTable) - && !($table instanceof DataTable\Map) + && !($table instanceof DataTableInterface) ) { - throw new Exception("DataTable renderers renderer accepts only DataTable and Map instances, and arrays."); + throw new Exception("DataTable renderers renderer accepts only DataTable, Simple and Map instances, and arrays."); } $this->table = $table; } - /** - * Set the Exception to be rendered - * - * @param Exception $exception to be rendered - * @throws Exception - */ - public function setException($exception) - { - if (!($exception instanceof Exception)) { - throw new Exception("The exception renderer accepts only an Exception object."); - } - $this->exception = $exception; - } - /** * @var array */ - static protected $availableRenderers = array('xml', + protected static $availableRenderers = array('xml', 'json', 'csv', 'tsv', @@ -182,41 +152,25 @@ abstract class Renderer * * @return array */ - static public function getRenderers() + public static function getRenderers() { 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 - */ - static public 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; } - /** - * Returns $rawData after all applicable characters have been converted to HTML entities. - * - * @param String $rawData data to be converted - * @return String - */ - static protected function renderHtmlEntities($rawData) + protected static function getInvalidClassIdExceptionMessage($id) { - return self::formatValueXml($rawData); + $availableRenderers = implode(', ', self::getRenderers()); + $klassName = self::getClassNameFromClassId($id); + + return Piwik::translate('General_ExceptionInvalidRendererFormat', array($klassName, $availableRenderers)); } /** @@ -236,12 +190,14 @@ abstract class Renderer $value = @mb_convert_encoding($value, 'UTF-8', 'UTF-8'); } $value = htmlspecialchars($value, ENT_COMPAT, 'UTF-8'); + $htmlentities = array(" ", "¡", "¢", "£", "¤", "¥", "¦", "§", "¨", "©", "ª", "«", "¬", "­", "®", "¯", "°", "±", "²", "³", "´", "µ", "¶", "·", "¸", "¹", "º", "»", "¼", "½", "¾", "¿", "À", "Á", "Â", "Ã", "Ä", "Å", "Æ", "Ç", "È", "É", "Ê", "Ë", "Ì", "Í", "Î", "Ï", "Ð", "Ñ", "Ò", "Ó", "Ô", "Õ", "Ö", "×", "Ø", "Ù", "Ú", "Û", "Ü", "Ý", "Þ", "ß", "à", "á", "â", "ã", "ä", "å", "æ", "ç", "è", "é", "ê", "ë", "ì", "í", "î", "ï", "ð", "ñ", "ò", "ó", "ô", "õ", "ö", "÷", "ø", "ù", "ú", "û", "ü", "ý", "þ", "ÿ", "€"); - $xmlentities = array("¢", "£", "¤", "¥", "¦", "§", "¨", "©", "ª", "«", "¬", "­", "®", "¯", "°", "±", "²", "³", "´", "µ", "¶", "·", "¸", "¹", "º", "»", "¼", "½", "¾", "¿", "À", "Á", "Â", "Ã", "Ä", "Å", "Æ", "Ç", "È", "É", "Ê", "Ë", "Ì", "Í", "Î", "Ï", "Ð", "Ñ", "Ò", "Ó", "Ô", "Õ", "Ö", "×", "Ø", "Ù", "Ú", "Û", "Ü", "Ý", "Þ", "ß", "à", "á", "â", "ã", "ä", "å", "æ", "ç", "è", "é", "ê", "ë", "ì", "í", "î", "ï", "ð", "ñ", "ò", "ó", "ô", "õ", "ö", "÷", "ø", "ù", "ú", "û", "ü", "ý", "þ", "ÿ", "€"); - $value = str_replace($htmlentities, $xmlentities, $value); + $xmlentities = array("¢", "£", "¤", "¥", "¦", "§", "¨", "©", "ª", "«", "¬", "­", "®", "¯", "°", "±", "²", "³", "´", "µ", "¶", "·", "¸", "¹", "º", "»", "¼", "½", "¾", "¿", "À", "Á", "Â", "Ã", "Ä", "Å", "Æ", "Ç", "È", "É", "Ê", "Ë", "Ì", "Í", "Î", "Ï", "Ð", "Ñ", "Ò", "Ó", "Ô", "Õ", "Ö", "×", "Ø", "Ù", "Ú", "Û", "Ü", "Ý", "Þ", "ß", "à", "á", "â", "ã", "ä", "å", "æ", "ç", "è", "é", "ê", "ë", "ì", "í", "î", "ï", "ð", "ñ", "ò", "ó", "ô", "õ", "ö", "÷", "ø", "ù", "ú", "û", "ü", "ý", "þ", "ÿ", "€"); + $value = str_replace($htmlentities, $xmlentities, $value); } elseif ($value === false) { $value = 0; } + return $value; } diff --git a/www/analytics/core/DataTable/Renderer/Console.php b/www/analytics/core/DataTable/Renderer/Console.php index 7959c644..bd16e93c 100644 --- a/www/analytics/core/DataTable/Renderer/Console.php +++ b/www/analytics/core/DataTable/Renderer/Console.php @@ -1,6 +1,6 @@ renderHeader(); return $this->renderTable($this->table); } - /** - * Computes the exception output and returns the string/binary - * - * @return string - */ - public function renderException() - { - $this->renderHeader(); - $exceptionMessage = $this->getExceptionMessage(); - return 'Error: ' . $exceptionMessage; - } - /** * Sets the prefix to be used * @@ -85,8 +71,9 @@ class Console extends Renderer */ protected function renderTable($table, $prefix = "") { - if (is_array($table)) // convert array to DataTable - { + if (is_array($table)) { + // convert array to DataTable + $table = DataTable::makeFromSimpleArray($table); } @@ -110,8 +97,11 @@ class Console extends Renderer $dataTableMapBreak = true; break; } - if (is_string($value)) $value = "'$value'"; - elseif (is_array($value)) $value = var_export($value, true); + if (is_string($value)) { + $value = "'$value'"; + } elseif (is_array($value)) { + $value = var_export($value, true); + } $columns[] = "'$column' => $value"; } @@ -122,8 +112,11 @@ class Console extends Renderer $metadata = array(); foreach ($row->getMetadata() as $name => $value) { - if (is_string($value)) $value = "'$value'"; - elseif (is_array($value)) $value = var_export($value, true); + if (is_string($value)) { + $value = "'$value'"; + } elseif (is_array($value)) { + $value = var_export($value, true); + } $metadata[] = "'$name' => $value"; } $metadata = implode(", ", $metadata); @@ -133,14 +126,10 @@ class Console extends Renderer . $row->getIdSubDataTable() . "]
\n"; if (!is_null($row->getIdSubDataTable())) { - if ($row->isSubtableLoaded()) { + $subTable = $row->getSubtable(); + if ($subTable) { $depth++; - $output .= $this->renderTable( - Manager::getInstance()->getTable( - $row->getIdSubDataTable() - ), - $prefix . '      ' - ); + $output .= $this->renderTable($subTable, $prefix . '      '); $depth--; } else { $output .= "-- Sub DataTable not loaded
\n"; @@ -155,7 +144,7 @@ class Console extends Renderer foreach ($metadata as $id => $metadataIn) { $output .= "
"; $output .= $prefix . " $id
"; - if(is_array($metadataIn)) { + if (is_array($metadataIn)) { foreach ($metadataIn as $name => $value) { $output .= $prefix . $prefix . "$name => $value"; } diff --git a/www/analytics/core/DataTable/Renderer/Csv.php b/www/analytics/core/DataTable/Renderer/Csv.php index cc9030d4..c3fb08b1 100644 --- a/www/analytics/core/DataTable/Renderer/Csv.php +++ b/www/analytics/core/DataTable/Renderer/Csv.php @@ -1,6 +1,6 @@ renderHeader(); - if ($this->convertToUnicode - && function_exists('mb_convert_encoding') - ) { - $str = chr(255) . chr(254) . mb_convert_encoding($str, 'UTF-16LE', 'UTF-8'); - } + $str = $this->convertToUnicode($str); return $str; } - /** - * Computes the exception output and returns the string/binary - * - * @return string - */ - function renderException() - { - @header('Content-Type: text/html; charset=utf-8'); - $exceptionMessage = $this->getExceptionMessage(); - return 'Error: ' . $exceptionMessage; - } - /** * Enables / Disables unicode converting * @@ -133,8 +119,9 @@ class Csv extends Renderer */ protected function renderTable($table, &$allColumns = array()) { - if (is_array($table)) // convert array to DataTable - { + if (is_array($table)) { + // convert array to DataTable + $table = DataTable::makeFromSimpleArray($table); } @@ -205,42 +192,7 @@ class Csv extends Renderer } } - $csv = array(); - foreach ($table->getRows() as $row) { - $csvRow = $this->flattenColumnArray($row->getColumns()); - - if ($this->exportMetadata) { - $metadata = $row->getMetadata(); - foreach ($metadata as $name => $value) { - if ($name == 'idsubdatatable_in_db') { - continue; - } - //if a metadata and a column have the same name make sure they dont overwrite - if ($this->translateColumnNames) { - $name = Piwik::translate('General_Metadata') . ': ' . $name; - } else { - $name = 'metadata_' . $name; - } - - $csvRow[$name] = $value; - } - } - - foreach ($csvRow as $name => $value) { - $allColumns[$name] = true; - } - - if ($this->exportIdSubtable) { - $idsubdatatable = $row->getIdSubDataTable(); - if ($idsubdatatable !== false - && $this->hideIdSubDatatable === false - ) { - $csvRow['idsubdatatable'] = $idsubdatatable; - } - } - - $csv[] = $csvRow; - } + $csv = $this->makeArrayFromDataTable($table, $allColumns); // now we make sure that all the rows in the CSV array have all the columns foreach ($csv as &$row) { @@ -251,31 +203,7 @@ class Csv extends Renderer } } - $str = ''; - - // specific case, we have only one column and this column wasn't named properly (indexed by a number) - // we don't print anything in the CSV file => an empty line - if (sizeof($allColumns) == 1 - && reset($allColumns) - && !is_string(key($allColumns)) - ) { - $str .= ''; - } else { - // render row names - $str .= $this->getHeaderLine(array_keys($allColumns)) . $this->lineEnd; - } - - // we render the CSV - foreach ($csv as $theRow) { - $rowStr = ''; - foreach ($allColumns as $columnName => $true) { - $rowStr .= $this->formatValue($theRow[$columnName]) . $this->separator; - } - // remove the last separator - $rowStr = substr_replace($rowStr, "", -strlen($this->separator)); - $str .= $rowStr . $this->lineEnd; - } - $str = substr($str, 0, -strlen($this->lineEnd)); + $str = $this->buildCsvString($allColumns, $csv); return $str; } @@ -287,9 +215,20 @@ class Csv extends Renderer */ private function getHeaderLine($columnMetrics) { + foreach ($columnMetrics as $index => $value) { + if (in_array($value, $this->unsupportedColumns)) { + unset($columnMetrics[$index]); + } + } + if ($this->translateColumnNames) { $columnMetrics = $this->translateColumnNames($columnMetrics); } + + foreach ($columnMetrics as &$value) { + $value = $this->formatValue($value); + } + return implode($this->separator, $columnMetrics); } @@ -334,14 +273,15 @@ class Csv extends Renderer $period = Common::getRequestVar('period', false); $date = Common::getRequestVar('date', false); - if ($period || $date) // in test cases, there are no request params set - { + if ($period || $date) { + // in test cases, there are no request params set + if ($period == 'range') { $period = new Range($period, $date); - } else if (strpos($date, ',') !== false) { + } elseif (strpos($date, ',') !== false) { $period = new Range('range', $date); } else { - $period = Period::factory($period, Date::factory($date)); + $period = Period\Factory::build($period, Date::factory($date)); } $prettyDate = $period->getLocalizedLongString(); @@ -353,8 +293,7 @@ class Csv extends Renderer } // silent fail otherwise unit tests fail - @header('Content-Type: application/vnd.ms-excel'); - @header('Content-Disposition: attachment; filename="' . $fileName . '"'); + Common::sendHeader('Content-Disposition: attachment; filename="' . $fileName . '"', true); ProxyHttp::overrideCacheControlHeaders(); } @@ -400,4 +339,119 @@ class Csv extends Renderer return $name; } } + + /** + * @param $allColumns + * @param $csv + * @return array + */ + private function buildCsvString($allColumns, $csv) + { + $str = ''; + + // specific case, we have only one column and this column wasn't named properly (indexed by a number) + // we don't print anything in the CSV file => an empty line + if (sizeof($allColumns) == 1 + && reset($allColumns) + && !is_string(key($allColumns)) + ) { + $str .= ''; + } else { + // render row names + $str .= $this->getHeaderLine(array_keys($allColumns)) . $this->lineEnd; + } + + // we render the CSV + foreach ($csv as $theRow) { + $rowStr = ''; + foreach ($allColumns as $columnName => $true) { + $rowStr .= $this->formatValue($theRow[$columnName]) . $this->separator; + } + // remove the last separator + $rowStr = substr_replace($rowStr, "", -strlen($this->separator)); + $str .= $rowStr . $this->lineEnd; + } + $str = substr($str, 0, -strlen($this->lineEnd)); + return $str; + } + + /** + * @param $table + * @param $allColumns + * @return array of csv data + */ + private function makeArrayFromDataTable($table, &$allColumns) + { + $csv = array(); + foreach ($table->getRows() as $row) { + $csvRow = $this->flattenColumnArray($row->getColumns()); + + if ($this->exportMetadata) { + $metadata = $row->getMetadata(); + foreach ($metadata as $name => $value) { + if ($name == 'idsubdatatable_in_db') { + continue; + } + //if a metadata and a column have the same name make sure they dont overwrite + if ($this->translateColumnNames) { + $name = Piwik::translate('General_Metadata') . ': ' . $name; + } else { + $name = 'metadata_' . $name; + } + + if (is_array($value)) { + if (!in_array($name, $this->unsupportedColumns)) { + $this->unsupportedColumns[] = $name; + } + } else { + $csvRow[$name] = $value; + } + + } + } + + foreach ($csvRow as $name => $value) { + if (in_array($name, $this->unsupportedColumns)) { + unset($allColumns[$name]); + } else { + $allColumns[$name] = true; + } + } + + if ($this->exportIdSubtable) { + $idsubdatatable = $row->getIdSubDataTable(); + if ($idsubdatatable !== false + && $this->hideIdSubDatatable === false + ) { + $csvRow['idsubdatatable'] = $idsubdatatable; + } + } + + $csv[] = $csvRow; + } + + if (!empty($this->unsupportedColumns)) { + foreach ($this->unsupportedColumns as $unsupportedColumn) { + foreach ($csv as $index => $row) { + unset($row[$index][$unsupportedColumn]); + } + } + } + + return $csv; + } + + /** + * @param $str + * @return string + */ + private function convertToUnicode($str) + { + if ($this->convertToUnicode + && function_exists('mb_convert_encoding') + ) { + $str = chr(255) . chr(254) . mb_convert_encoding($str, 'UTF-16LE', 'UTF-8'); + } + return $str; + } } diff --git a/www/analytics/core/DataTable/Renderer/Html.php b/www/analytics/core/DataTable/Renderer/Html.php index ca4cd653..f27dc517 100644 --- a/www/analytics/core/DataTable/Renderer/Html.php +++ b/www/analytics/core/DataTable/Renderer/Html.php @@ -1,6 +1,6 @@ tableId = str_replace('.', '_', $id); } - /** - * Output HTTP Content-Type header - */ - protected function renderHeader() - { - @header('Content-Type: text/html; charset=utf-8'); - } - /** * Computes the dataTable output and returns the string/binary * * @return string */ - function render() + public function render() { - $this->renderHeader(); $this->tableStructure = array(); $this->allColumns = array(); $this->i = 0; @@ -57,18 +48,6 @@ class Html extends Renderer return $this->renderTable($this->table); } - /** - * Computes the exception output and returns the string/binary - * - * @return string - */ - function renderException() - { - $this->renderHeader(); - $exceptionMessage = $this->getExceptionMessage(); - return nl2br($exceptionMessage); - } - /** * Computes the output for the given data table * @@ -77,8 +56,9 @@ class Html extends Renderer */ protected function renderTable($table) { - if (is_array($table)) // convert array to DataTable - { + if (is_array($table)) { + // convert array to DataTable + $table = DataTable::makeFromSimpleArray($table); } @@ -88,8 +68,9 @@ class Html extends Renderer $this->buildTableStructure($subtable, '_' . $table->getKeyName(), $date); } } - } else // Simple - { + } else { + // Simple + if ($table->getRowsCount()) { $this->buildTableStructure($table); } @@ -134,7 +115,9 @@ class Html extends Renderer $metadata = array(); foreach ($row->getMetadata() as $name => $value) { - if (is_string($value)) $value = "'$value'"; + if (is_string($value)) { + $value = "'$value'"; + } $metadata[] = "'$name' => $value"; } diff --git a/www/analytics/core/DataTable/Renderer/Json.php b/www/analytics/core/DataTable/Renderer/Json.php index 664f14f4..36079aad 100644 --- a/www/analytics/core/DataTable/Renderer/Json.php +++ b/www/analytics/core/DataTable/Renderer/Json.php @@ -1,6 +1,6 @@ renderHeader(); return $this->renderTable($this->table); } - /** - * Computes the exception output and returns the string/binary - * - * @return string - */ - function renderException() - { - $this->renderHeader(); - - $exceptionMessage = $this->getExceptionMessage(); - $exceptionMessage = str_replace(array("\r\n", "\n"), "", $exceptionMessage); - - $result = json_encode(array('result' => 'error', 'message' => $exceptionMessage)); - - return $this->jsonpWrap($result); - } - /** * Computes the output for the given data table * @@ -73,7 +54,6 @@ class Json extends Renderer } } } - } else { $array = $this->convertDataTableToArray($table); } @@ -90,40 +70,15 @@ class Json extends Renderer }; array_walk_recursive($array, $callback); - $str = json_encode($array); - - return $this->jsonpWrap($str); - } - - /** - * @param $str - * @return string - */ - protected function jsonpWrap($str) - { - if (($jsonCallback = Common::getRequestVar('callback', false)) === false) - $jsonCallback = Common::getRequestVar('jsoncallback', false); - if ($jsonCallback !== false) { - if (preg_match('/^[0-9a-zA-Z_.]*$/D', $jsonCallback) > 0) { - $str = $jsonCallback . "(" . $str . ")"; - } - } + // silence "Warning: json_encode(): Invalid UTF-8 sequence in argument" + $str = @json_encode($array); return $str; } - /** - * Sends the http header for json file - */ - protected function renderHeader() - { - self::sendHeaderJSON(); - ProxyHttp::overrideCacheControlHeaders(); - } - public static function sendHeaderJSON() { - @header('Content-Type: application/json; charset=utf-8'); + Common::sendHeader('Content-Type: application/json; charset=utf-8'); } private function convertDataTableToArray($table) diff --git a/www/analytics/core/DataTable/Renderer/Php.php b/www/analytics/core/DataTable/Renderer/Php.php index 80e49bc2..9a121bc6 100644 --- a/www/analytics/core/DataTable/Renderer/Php.php +++ b/www/analytics/core/DataTable/Renderer/Php.php @@ -1,6 +1,6 @@ renderHeader(); - if (is_null($dataTable)) { $dataTable = $this->table; } @@ -87,26 +84,6 @@ class Php extends Renderer return $toReturn; } - /** - * Computes the exception output and returns the string/binary - * - * @return string - */ - public function renderException() - { - $this->renderHeader(); - - $exceptionMessage = $this->getExceptionMessage(); - - $return = array('result' => 'error', 'message' => $exceptionMessage); - - if ($this->serialize) { - $return = serialize($return); - } - - return $return; - } - /** * Produces a flat php array from the DataTable, putting "columns" and "metadata" on the same level. * @@ -133,7 +110,7 @@ class Php extends Renderer if (self::shouldWrapArrayBeforeRendering($flatArray)) { $flatArray = array($flatArray); } - } else if ($dataTable instanceof DataTable\Map) { + } elseif ($dataTable instanceof DataTable\Map) { $flatArray = array(); foreach ($dataTable->getDataTables() as $keyName => $table) { $serializeSave = $this->serialize; @@ -141,7 +118,7 @@ class Php extends Renderer $flatArray[$keyName] = $this->flatRender($table); $this->serialize = $serializeSave; } - } else if ($dataTable instanceof Simple) { + } elseif ($dataTable instanceof Simple) { $flatArray = $this->renderSimpleTable($dataTable); // if we return only one numeric value then we print out the result in a simple tag @@ -228,10 +205,11 @@ class Php extends Renderer $newRow['issummaryrow'] = true; } + $subTable = $row->getSubtable(); if ($this->isRenderSubtables() - && $row->isSubtableLoaded() + && $subTable ) { - $subTable = $this->renderTable(Manager::getInstance()->getTable($row->getIdSubDataTable())); + $subTable = $this->renderTable($subTable); $newRow['subtable'] = $subTable; if ($this->hideIdSubDatatable === false && isset($newRow['metadata']['idsubdatatable_in_db']) diff --git a/www/analytics/core/DataTable/Renderer/Rss.php b/www/analytics/core/DataTable/Renderer/Rss.php index 2c07e00b..fd18b443 100644 --- a/www/analytics/core/DataTable/Renderer/Rss.php +++ b/www/analytics/core/DataTable/Renderer/Rss.php @@ -1,6 +1,6 @@ renderHeader(); return $this->renderTable($this->table); } - /** - * Computes the exception output and returns the string/binary - * - * @return string - */ - function renderException() - { - header('Content-type: text/plain'); - $exceptionMessage = $this->getExceptionMessage(); - return 'Error: ' . $exceptionMessage; - } - /** * Computes the output for the given data table * @@ -101,14 +87,6 @@ class Rss extends Renderer return $header . $out . $footer; } - /** - * Sends the xml file http header - */ - protected function renderHeader() - { - @header('Content-Type: text/xml; charset=utf-8'); - } - /** * Returns the RSS file footer * @@ -185,7 +163,6 @@ class Rss extends Renderer } } $html .= "\n"; - $colspan = count($allColumns); foreach ($tableStructure as $row) { $html .= "\n\n"; diff --git a/www/analytics/core/DataTable/Renderer/Tsv.php b/www/analytics/core/DataTable/Renderer/Tsv.php index 949874da..af45a62d 100644 --- a/www/analytics/core/DataTable/Renderer/Tsv.php +++ b/www/analytics/core/DataTable/Renderer/Tsv.php @@ -1,6 +1,6 @@ setSeparator("\t"); @@ -32,7 +31,7 @@ class Tsv extends Csv * * @return string */ - function render() + public function render() { return parent::render(); } diff --git a/www/analytics/core/DataTable/Renderer/Xml.php b/www/analytics/core/DataTable/Renderer/Xml.php index 15f4f52e..b01f5596 100644 --- a/www/analytics/core/DataTable/Renderer/Xml.php +++ b/www/analytics/core/DataTable/Renderer/Xml.php @@ -1,6 +1,6 @@ renderHeader(); return '' . "\n" . $this->renderTable($this->table); } - /** - * Computes the exception output and returns the string/binary - * - * @return string - */ - function renderException() - { - $this->renderHeader(); - - $exceptionMessage = $this->getExceptionMessage(); - - $return = '' . "\n" . - "\n" . - "\t\n" . - ""; - - return $return; - } - /** * Converts the given data table to an array * @@ -174,17 +154,16 @@ class Xml extends Renderer foreach ($array as $key => $value) { // based on the type of array & the key, determine how this node will look if ($isAssociativeArray) { - $keyIsInvalidXmlElement = is_numeric($key) || is_numeric($key[0]); - if ($keyIsInvalidXmlElement) { - $prefix = ""; - $suffix = ""; - $emptyNode = ""; - } else if (strpos($key, '=') !== false) { + if (strpos($key, '=') !== false) { list($keyAttributeName, $key) = explode('=', $key, 2); $prefix = ""; $suffix = ""; $emptyNode = ""; + } elseif (!self::isValidXmlTagName($key)) { + $prefix = ""; + $suffix = ""; + $emptyNode = ""; } else { $prefix = "<$key>"; $suffix = ""; @@ -201,7 +180,7 @@ class Xml extends Renderer $result .= $prefixLines . $prefix . "\n"; $result .= $this->renderArray($value, $prefixLines . "\t"); $result .= $prefixLines . $suffix . "\n"; - } else if ($value instanceof DataTable + } elseif ($value instanceof DataTable || $value instanceof Map ) { if ($value->getRowsCount() == 0) { @@ -210,7 +189,7 @@ class Xml extends Renderer $result .= $prefixLines . $prefix . "\n"; if ($value instanceof Map) { $result .= $this->renderDataTableMap($value, $this->getArrayFromDataTable($value), $prefixLines); - } else if ($value instanceof Simple) { + } elseif ($value instanceof Simple) { $result .= $this->renderDataTableSimple($this->getArrayFromDataTable($value), $prefixLines); } else { $result .= $this->renderDataTable($this->getArrayFromDataTable($value), $prefixLines); @@ -358,6 +337,8 @@ class Xml extends Renderer */ protected function renderDataTable($array, $prefixLine = "") { + $columnsHaveInvalidChars = $this->areTableLabelsInvalidXmlTagNames(reset($array)); + $out = ''; foreach ($array as $rowId => $row) { if (!is_array($row)) { @@ -370,10 +351,9 @@ class Xml extends Renderer continue; } - // Handing case idgoal=7, creating a new array for that one $rowAttribute = ''; - if (($equalFound = strstr($rowId, '=')) !== false) { + if (strstr($rowId, '=') !== false) { $rowAttribute = explode('=', $rowId); $rowAttribute = " " . $rowAttribute[0] . "='" . $rowAttribute[1] . "'"; } @@ -394,10 +374,13 @@ class Xml extends Renderer } else { $value = self::formatValueXml($value); } + + list($tagStart, $tagEnd) = $this->getTagStartAndEndFor($name, $columnsHaveInvalidChars); + if (strlen($value) == 0) { - $out .= $prefixLine . "\t\t<$name />\n"; + $out .= $prefixLine . "\t\t<$tagStart />\n"; } else { - $out .= $prefixLine . "\t\t<$name>" . $value . "\n"; + $out .= $prefixLine . "\t\t<$tagStart>" . $value . "\n"; } } $out .= "\t"; @@ -420,24 +403,62 @@ class Xml extends Renderer $array = array('value' => $array); } + $columnsHaveInvalidChars = $this->areTableLabelsInvalidXmlTagNames($array); + $out = ''; foreach ($array as $keyName => $value) { $xmlValue = self::formatValueXml($value); + list($tagStart, $tagEnd) = $this->getTagStartAndEndFor($keyName, $columnsHaveInvalidChars); if (strlen($xmlValue) == 0) { - $out .= $prefixLine . "\t<$keyName />\n"; + $out .= $prefixLine . "\t<$tagStart />\n"; } else { - $out .= $prefixLine . "\t<$keyName>" . $xmlValue . "\n"; + $out .= $prefixLine . "\t<$tagStart>" . $xmlValue . "\n"; } } return $out; } /** - * Sends the XML headers + * Returns true if a string is a valid XML tag name, false if otherwise. + * + * @param string $str + * @return bool */ - protected function renderHeader() + private static function isValidXmlTagName($str) { - // silent fail because otherwise it throws an exception in the unit tests - @header('Content-Type: text/xml; charset=utf-8'); + static $validTagRegex = null; + + if ($validTagRegex === null) { + $invalidTagChars = "!\"#$%&'()*+,\\/;<=>?@[\\]\\\\^`{|}~"; + $invalidTagStartChars = $invalidTagChars . "\\-.0123456789"; + $validTagRegex = "/^[^" . $invalidTagStartChars . "][^" . $invalidTagChars . "]*$/"; + } + + $result = preg_match($validTagRegex, $str); + return !empty($result); + } + + private function areTableLabelsInvalidXmlTagNames($rowArray) + { + if (!empty($rowArray)) { + foreach ($rowArray as $name => $value) { + if (!self::isValidXmlTagName($name)) { + return true; + } + } + } + return false; + } + + private function getTagStartAndEndFor($keyName, $columnsHaveInvalidChars) + { + if ($columnsHaveInvalidChars) { + $tagStart = "col name=\"" . self::formatValueXml($keyName) . "\""; + $tagEnd = "col"; + } else { + $tagStart = $tagEnd = $keyName; + } + + return array($tagStart, $tagEnd); } } diff --git a/www/analytics/core/DataTable/Row.php b/www/analytics/core/DataTable/Row.php index 2137da25..ad8a850c 100644 --- a/www/analytics/core/DataTable/Row.php +++ b/www/analytics/core/DataTable/Row.php @@ -1,6 +1,6 @@ value mappings. * - * * @api */ -class Row +class Row implements \ArrayAccess, \IteratorAggregate { /** * List of columns that cannot be summed. An associative array for speed. @@ -30,27 +30,21 @@ class Row */ private static $unsummableColumns = array( 'label' => true, - 'full_url' => true // column used w/ old Piwik versions + 'full_url' => true // column used w/ old Piwik versions, ); - /** - * This array contains the row information: - * - array indexed by self::COLUMNS contains the columns, pairs of (column names, value) - * - (optional) array indexed by self::METADATA contains the metadata, pairs of (metadata name, value) - * - (optional) integer indexed by self::DATATABLE_ASSOCIATED contains the ID of the DataTable associated to this row. - * This ID can be used to read the DataTable from the DataTable_Manager. - * - * @var array - * @see constructor for more information - * @ignore - */ - public $c = array(); - - private $subtableIdWasNegativeBeforeSerialize = false; - // @see sumRow - implementation detail public $maxVisitsSummed = 0; + private $columns = array(); + private $metadata = array(); + private $isSubtableLoaded = false; + + /** + * @internal + */ + public $subtableId = null; + const COLUMNS = 0; const METADATA = 1; const DATATABLE_ASSOCIATED = 3; @@ -59,7 +53,7 @@ class Row * Constructor. * * @param array $row An array with the following structure: - * + * * array( * Row::COLUMNS => array('label' => 'Piwik', * 'column1' => 42, @@ -72,51 +66,33 @@ class Row */ public function __construct($row = array()) { - $this->c[self::COLUMNS] = array(); - $this->c[self::METADATA] = array(); - $this->c[self::DATATABLE_ASSOCIATED] = null; - if (isset($row[self::COLUMNS])) { - $this->c[self::COLUMNS] = $row[self::COLUMNS]; + $this->columns = $row[self::COLUMNS]; } if (isset($row[self::METADATA])) { - $this->c[self::METADATA] = $row[self::METADATA]; + $this->metadata = $row[self::METADATA]; } - if (isset($row[self::DATATABLE_ASSOCIATED]) - && $row[self::DATATABLE_ASSOCIATED] instanceof DataTable - ) { - $this->setSubtable($row[self::DATATABLE_ASSOCIATED]); + if (isset($row[self::DATATABLE_ASSOCIATED])) { + if ($row[self::DATATABLE_ASSOCIATED] instanceof DataTable) { + $this->setSubtable($row[self::DATATABLE_ASSOCIATED]); + } else { + $this->subtableId = $row[self::DATATABLE_ASSOCIATED]; + } } } /** - * Because $this->c[self::DATATABLE_ASSOCIATED] is negative when the table is in memory, - * we must prior to serialize() call, make sure the ID is saved as positive integer - * - * Only serialize the "c" member + * Used when archiving to serialize the Row's properties. + * @return array * @ignore */ - public function __sleep() + public function export() { - if (!empty($this->c[self::DATATABLE_ASSOCIATED]) - && $this->c[self::DATATABLE_ASSOCIATED] < 0 - ) { - $this->c[self::DATATABLE_ASSOCIATED] = -1 * $this->c[self::DATATABLE_ASSOCIATED]; - $this->subtableIdWasNegativeBeforeSerialize = true; - } - return array('c'); - } - - /** - * Must be called after the row was serialized and __sleep was called. - * @ignore - */ - public function cleanPostSerialize() - { - if ($this->subtableIdWasNegativeBeforeSerialize) { - $this->c[self::DATATABLE_ASSOCIATED] = -1 * $this->c[self::DATATABLE_ASSOCIATED]; - $this->subtableIdWasNegativeBeforeSerialize = false; - } + return array( + self::COLUMNS => $this->columns, + self::METADATA => $this->metadata, + self::DATATABLE_ASSOCIATED => $this->subtableId, + ); } /** @@ -125,9 +101,10 @@ class Row */ public function __destruct() { - if ($this->isSubtableLoaded()) { - Manager::getInstance()->deleteTable($this->getIdSubDataTable()); - $this->c[self::DATATABLE_ASSOCIATED] = null; + if ($this->isSubtableLoaded) { + Manager::getInstance()->deleteTable($this->subtableId); + $this->subtableId = null; + $this->isSubtableLoaded = false; } } @@ -141,15 +118,21 @@ class Row { $columns = array(); foreach ($this->getColumns() as $column => $value) { - if (is_string($value)) $value = "'$value'"; - elseif (is_array($value)) $value = var_export($value, true); + if (is_string($value)) { + $value = "'$value'"; + } elseif (is_array($value)) { + $value = var_export($value, true); + } $columns[] = "'$column' => $value"; } $columns = implode(", ", $columns); $metadata = array(); foreach ($this->getMetadata() as $name => $value) { - if (is_string($value)) $value = "'$value'"; - elseif (is_array($value)) $value = var_export($value, true); + if (is_string($value)) { + $value = "'$value'"; + } elseif (is_array($value)) { + $value = var_export($value, true); + } $metadata[] = "'$name' => $value"; } $metadata = implode(", ", $metadata); @@ -165,10 +148,11 @@ class Row */ public function deleteColumn($name) { - if (!array_key_exists($name, $this->c[self::COLUMNS])) { + if (!array_key_exists($name, $this->columns)) { return false; } - unset($this->c[self::COLUMNS][$name]); + + unset($this->columns[$name]); return true; } @@ -180,11 +164,12 @@ class Row */ public function renameColumn($oldName, $newName) { - if (isset($this->c[self::COLUMNS][$oldName])) { - $this->c[self::COLUMNS][$newName] = $this->c[self::COLUMNS][$oldName]; + if (isset($this->columns[$oldName])) { + $this->columns[$newName] = $this->columns[$oldName]; } - // outside the if() since we want to delete nulled columns - unset($this->c[self::COLUMNS][$oldName]); + + // outside the if () since we want to delete nulled columns + unset($this->columns[$oldName]); } /** @@ -195,10 +180,11 @@ class Row */ public function getColumn($name) { - if (!isset($this->c[self::COLUMNS][$name])) { + if (!isset($this->columns[$name])) { return false; } - return $this->c[self::COLUMNS][$name]; + + return $this->columns[$name]; } /** @@ -210,19 +196,31 @@ class Row public function getMetadata($name = null) { if (is_null($name)) { - return $this->c[self::METADATA]; + return $this->metadata; } - if (!isset($this->c[self::METADATA][$name])) { + if (!isset($this->metadata[$name])) { return false; } - return $this->c[self::METADATA][$name]; + return $this->metadata[$name]; + } + + /** + * Returns true if a column having the given name is already registered. The value will not be evaluated, it will + * just check whether a column exists independent of its value. + * + * @param string $name + * @return bool + */ + public function hasColumn($name) + { + return array_key_exists($name, $this->columns); } /** * Returns the array containing all the columns. * * @return array Example: - * + * * array( * 'column1' => VALUE, * 'label' => 'www.php.net' @@ -231,7 +229,7 @@ class Row */ public function getColumns() { - return $this->c[self::COLUMNS]; + return $this->columns; } /** @@ -242,10 +240,7 @@ class Row */ public function getIdSubDataTable() { - return !is_null($this->c[self::DATATABLE_ASSOCIATED]) - // abs() is to ensure we return a positive int, @see isSubtableLoaded() - ? abs($this->c[self::DATATABLE_ASSOCIATED]) - : null; + return $this->subtableId; } /** @@ -255,48 +250,49 @@ class Row */ public function getSubtable() { - if ($this->isSubtableLoaded()) { - return Manager::getInstance()->getTable($this->getIdSubDataTable()); + if ($this->isSubtableLoaded) { + try { + return Manager::getInstance()->getTable($this->subtableId); + } catch (TableNotFoundException $e) { + // edge case + } } return false; } + /** + * @param int $subtableId + * @ignore + */ + public function setNonLoadedSubtableId($subtableId) + { + $this->subtableId = $subtableId; + $this->isSubtableLoaded = false; + } + /** * Sums a DataTable to this row's subtable. If this row has no subtable a new * one is created. - * + * * See {@link Piwik\DataTable::addDataTable()} to learn how DataTables are summed. - * + * * @param DataTable $subTable Table to sum to this row's subtable. */ public function sumSubtable(DataTable $subTable) { - if ($this->isSubtableLoaded()) { + if ($this->isSubtableLoaded) { $thisSubTable = $this->getSubtable(); } else { + $this->warnIfSubtableAlreadyExists(); + $thisSubTable = new DataTable(); - $this->addSubtable($thisSubTable); + $this->setSubtable($thisSubTable); } $columnOps = $subTable->getMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME); $thisSubTable->setMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME, $columnOps); $thisSubTable->addDataTable($subTable); } - /** - * Attaches a subtable to this row. - * - * @param DataTable $subTable DataTable to associate to this row. - * @return DataTable Returns `$subTable`. - * @throws Exception if a subtable already exists for this row. - */ - public function addSubtable(DataTable $subTable) - { - if (!is_null($this->c[self::DATATABLE_ASSOCIATED])) { - throw new Exception("Adding a subtable to the row, but it already has a subtable associated."); - } - return $this->setSubtable($subTable); - } - /** * Attaches a subtable to this row, overwriting the existing subtable, * if any. @@ -306,9 +302,9 @@ class Row */ public function setSubtable(DataTable $subTable) { - // Hacking -1 to ensure value is negative, so we know the table was loaded - // @see isSubtableLoaded() - $this->c[self::DATATABLE_ASSOCIATED] = -1 * $subTable->getId(); + $this->subtableId = $subTable->getId(); + $this->isSubtableLoaded = true; + return $subTable; } @@ -321,8 +317,7 @@ class Row { // self::DATATABLE_ASSOCIATED are set as negative values, // as a flag to signify that the subtable is loaded in memory - return !is_null($this->c[self::DATATABLE_ASSOCIATED]) - && $this->c[self::DATATABLE_ASSOCIATED] < 0; + return $this->isSubtableLoaded; } /** @@ -330,17 +325,18 @@ class Row */ public function removeSubtable() { - $this->c[self::DATATABLE_ASSOCIATED] = null; + $this->subtableId = null; + $this->isSubtableLoaded = false; } /** * Set all the columns at once. Overwrites **all** previously set columns. * - * @param array eg, `array('label' => 'www.php.net', 'nb_visits' => 15894)` + * @param array $columns eg, `array('label' => 'www.php.net', 'nb_visits' => 15894)` */ public function setColumns($columns) { - $this->c[self::COLUMNS] = $columns; + $this->columns = $columns; } /** @@ -351,7 +347,7 @@ class Row */ public function setColumn($name, $value) { - $this->c[self::COLUMNS][$name] = $value; + $this->columns[$name] = $value; } /** @@ -362,7 +358,7 @@ class Row */ public function setMetadata($name, $value) { - $this->c[self::METADATA][$name] = $value; + $this->metadata[$name] = $value; } /** @@ -374,13 +370,13 @@ class Row public function deleteMetadata($name = false) { if ($name === false) { - $this->c[self::METADATA] = array(); + $this->metadata = array(); return true; } - if (!isset($this->c[self::METADATA][$name])) { + if (!isset($this->metadata[$name])) { return false; } - unset($this->c[self::METADATA][$name]); + unset($this->metadata[$name]); return true; } @@ -388,15 +384,15 @@ class Row * Add a new column to the row. If the column already exists, throws an exception. * * @param string $name name of the column to add. - * @param mixed $value value of the column to set. + * @param mixed $value value of the column to set or a PHP callable. * @throws Exception if the column already exists. */ public function addColumn($name, $value) { - if (isset($this->c[self::COLUMNS][$name])) { + if (isset($this->columns[$name])) { throw new Exception("Column $name already in the array!"); } - $this->c[self::COLUMNS][$name] = $value; + $this->setColumn($name, $value); } /** @@ -429,51 +425,61 @@ class Row */ public function addMetadata($name, $value) { - if (isset($this->c[self::METADATA][$name])) { + if (isset($this->metadata[$name])) { throw new Exception("Metadata $name already in the array!"); } - $this->c[self::METADATA][$name] = $value; + $this->setMetadata($name, $value); + } + + private function isSummableColumn($columnName) + { + return empty(self::$unsummableColumns[$columnName]); } /** * Sums the given `$rowToSum` columns values to the existing row column values. * Only the int or float values will be summed. Label columns will be ignored * even if they have a numeric value. - * + * * Columns in `$rowToSum` that don't exist in `$this` are added to `$this`. * * @param \Piwik\DataTable\Row $rowToSum The row to sum to this row. * @param bool $enableCopyMetadata Whether metadata should be copied or not. - * @param array $aggregationOperations for columns that should not be summed, determine which + * @param array|bool $aggregationOperations for columns that should not be summed, determine which * aggregation should be used (min, max). format: * `array('column name' => 'function name')` + * @throws Exception */ public function sumRow(Row $rowToSum, $enableCopyMetadata = true, $aggregationOperations = false) { foreach ($rowToSum->getColumns() as $columnToSumName => $columnToSumValue) { - if (!isset(self::$unsummableColumns[$columnToSumName])) // make sure we can add this column - { - $thisColumnValue = $this->getColumn($columnToSumName); - - $operation = (is_array($aggregationOperations) && isset($aggregationOperations[$columnToSumName]) ? - strtolower($aggregationOperations[$columnToSumName]) : 'sum'); - - // max_actions is a core metric that is generated in ArchiveProcess_Day. Therefore, it can be - // present in any data table and is not part of the $aggregationOperations mechanism. - if ($columnToSumName == Metrics::INDEX_MAX_ACTIONS) { - $operation = 'max'; - } - if(empty($operation)) { - throw new Exception("Unknown aggregation operation for column $columnToSumName."); - } - $newValue = $this->getColumnValuesMerged($operation, $thisColumnValue, $columnToSumValue); - - $this->setColumn($columnToSumName, $newValue); + if (!$this->isSummableColumn($columnToSumName)) { + continue; } + + $thisColumnValue = $this->getColumn($columnToSumName); + + $operation = 'sum'; + if (is_array($aggregationOperations) && isset($aggregationOperations[$columnToSumName])) { + $operation = strtolower($aggregationOperations[$columnToSumName]); + } + + // max_actions is a core metric that is generated in ArchiveProcess_Day. Therefore, it can be + // present in any data table and is not part of the $aggregationOperations mechanism. + if ($columnToSumName == Metrics::INDEX_MAX_ACTIONS) { + $operation = 'max'; + } + if (empty($operation)) { + throw new Exception("Unknown aggregation operation for column $columnToSumName."); + } + + $newValue = $this->getColumnValuesMerged($operation, $thisColumnValue, $columnToSumValue); + + $this->setColumn($columnToSumName, $newValue); } if ($enableCopyMetadata) { - $this->sumRowMetadata($rowToSum); + $this->sumRowMetadata($rowToSum, $aggregationOperations); } } @@ -491,7 +497,7 @@ class Row case 'min': if (!$thisColumnValue) { $newValue = $columnToSumValue; - } else if (!$columnToSumValue) { + } elseif (!$columnToSumValue) { $newValue = $thisColumnValue; } else { $newValue = min($thisColumnValue, $columnToSumValue); @@ -500,6 +506,19 @@ class Row case 'sum': $newValue = $this->sumRowArray($thisColumnValue, $columnToSumValue); break; + case 'uniquearraymerge': + if (is_array($thisColumnValue) && is_array($columnToSumValue)) { + foreach ($columnToSumValue as $columnSum) { + if (!in_array($columnSum, $thisColumnValue)) { + $thisColumnValue[] = $columnSum; + } + } + } elseif (!is_array($thisColumnValue) && is_array($columnToSumValue)) { + $thisColumnValue = $columnToSumValue; + } + + $newValue = $thisColumnValue; + break; default: throw new Exception("Unknown operation '$operation'."); } @@ -508,23 +527,45 @@ class Row /** * Sums the metadata in `$rowToSum` with the metadata in `$this` row. - * + * * @param Row $rowToSum + * @param array $aggregationOperations */ - public function sumRowMetadata($rowToSum) + public function sumRowMetadata($rowToSum, $aggregationOperations = array()) { - if (!empty($rowToSum->c[self::METADATA]) + if (!empty($rowToSum->metadata) && !$this->isSummaryRow() ) { + $aggregatedMetadata = array(); + + if (is_array($aggregationOperations)) { + // we need to aggregate value before value is overwritten by maybe another row + foreach ($aggregationOperations as $columnn => $operation) { + $thisMetadata = $this->getMetadata($columnn); + $sumMetadata = $rowToSum->getMetadata($columnn); + + if ($thisMetadata === false && $sumMetadata === false) { + continue; + } + + $aggregatedMetadata[$columnn] = $this->getColumnValuesMerged($operation, $thisMetadata, $sumMetadata); + } + } + // We shall update metadata, and keep the metadata with the _most visits or pageviews_, rather than first or last seen $visits = max($rowToSum->getColumn(Metrics::INDEX_PAGE_NB_HITS) || $rowToSum->getColumn(Metrics::INDEX_NB_VISITS), // Old format pre-1.2, @see also method doSumVisitsMetrics() $rowToSum->getColumn('nb_actions') || $rowToSum->getColumn('nb_visits')); if (($visits && $visits > $this->maxVisitsSummed) - || empty($this->c[self::METADATA]) + || empty($this->metadata) ) { $this->maxVisitsSummed = $visits; - $this->c[self::METADATA] = $rowToSum->c[self::METADATA]; + $this->metadata = $rowToSum->metadata; + } + + foreach ($aggregatedMetadata as $column => $value) { + // we need to make sure aggregated value is used, and not metadata from $rowToSum + $this->setMetadata($column, $value); } } } @@ -532,7 +573,7 @@ class Row /** * Returns `true` if this row is the summary row, `false` if otherwise. This function * depends on the label of the row, and so, is not 100% accurate. - * + * * @return bool */ public function isSummaryRow() @@ -558,10 +599,15 @@ class Row return $thisColumnValue + $columnToSumValue; } + if ($columnToSumValue === false) { + return $thisColumnValue; + } + + if ($thisColumnValue === false) { + return $columnToSumValue; + } + if (is_array($columnToSumValue)) { - if ($thisColumnValue == false) { - return $columnToSumValue; - } $newValue = $thisColumnValue; foreach ($columnToSumValue as $arrayIndex => $arrayValue) { if (!isset($newValue[$arrayIndex])) { @@ -572,16 +618,7 @@ class Row return $newValue; } - if (is_string($columnToSumValue)) { - if ($thisColumnValue === false) { - return $columnToSumValue; - } else if ($columnToSumValue === false) { - return $thisColumnValue; - } else { - throw new Exception("Trying to add two strings values in DataTable\Row::sumRowArray: " - . "'$thisColumnValue' + '$columnToSumValue'"); - } - } + $this->warnWhenSummingTwoStrings($thisColumnValue, $columnToSumValue); return 0; } @@ -594,7 +631,7 @@ class Row * @return bool * @ignore */ - static public function compareElements($elem1, $elem2) + public static function compareElements($elem1, $elem2) { if (is_array($elem1)) { if (is_array($elem2)) { @@ -602,11 +639,13 @@ class Row } return 1; } - if (is_array($elem2)) + if (is_array($elem2)) { return -1; + } - if ((string)$elem1 === (string)$elem2) + if ((string)$elem1 === (string)$elem2) { return 0; + } return ((string)$elem1 > (string)$elem2) ? 1 : -1; } @@ -615,17 +654,17 @@ class Row * Helper function that tests if two rows are equal. * * Two rows are equal if: - * + * * - they have exactly the same columns / metadata * - they have a subDataTable associated, then we check that both of them are the same. - * + * * Column order is not important. * * @param \Piwik\DataTable\Row $row1 first to compare * @param \Piwik\DataTable\Row $row2 second to compare * @return bool */ - static public function isEqual(Row $row1, Row $row2) + public static function isEqual(Row $row1, Row $row2) { //same columns $cols1 = $row1->getColumns(); @@ -662,4 +701,53 @@ class Row } return true; } + + public function offsetExists($offset) + { + return $this->hasColumn($offset); + } + + public function offsetGet($offset) + { + return $this->getColumn($offset); + } + + public function offsetSet($offset, $value) + { + $this->setColumn($offset, $value); + } + + public function offsetUnset($offset) + { + $this->deleteColumn($offset); + } + + public function getIterator() + { + return new \ArrayIterator($this->columns); + } + + private function warnIfSubtableAlreadyExists() + { + if (!is_null($this->subtableId)) { + Log::warning( + "Row with label '%s' (columns = %s) has already a subtable id=%s but it was not loaded - overwriting the existing sub-table.", + $this->getColumn('label'), + implode(", ", $this->getColumns()), + $this->getIdSubDataTable() + ); + } + } + + protected function warnWhenSummingTwoStrings($thisColumnValue, $columnToSumValue) + { + if (is_string($columnToSumValue)) { + Log::warning( + "Trying to add two strings in DataTable\Row::sumRowArray: %s + %s for row %s", + $thisColumnValue, + $columnToSumValue, + $this->__toString() + ); + } + } } diff --git a/www/analytics/core/DataTable/Row/DataTableSummaryRow.php b/www/analytics/core/DataTable/Row/DataTableSummaryRow.php index 479ac745..64e45d49 100644 --- a/www/analytics/core/DataTable/Row/DataTableSummaryRow.php +++ b/www/analytics/core/DataTable/Row/DataTableSummaryRow.php @@ -1,6 +1,6 @@ sumTable($subTable); } } @@ -47,9 +44,8 @@ class DataTableSummaryRow extends Row */ public function recalculate() { - $id = $this->getIdSubDataTable(); - if ($id !== null) { - $subTable = Manager::getInstance()->getTable($id); + $subTable = $this->getSubtable(); + if ($subTable) { $this->sumTable($subTable); } } @@ -61,8 +57,17 @@ class DataTableSummaryRow extends Row */ private function sumTable($table) { - foreach ($table->getRows() as $row) { - $this->sumRow($row, $enableCopyMetadata = false, $table->getMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME)); + $metadata = $table->getMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME); + $enableCopyMetadata = false; + + foreach ($table->getRowsWithoutSummaryRow() as $row) { + $this->sumRow($row, $enableCopyMetadata, $metadata); + } + + $summaryRow = $table->getRowFromId(DataTable::ID_SUMMARY_ROW); + + if ($summaryRow) { + $this->sumRow($summaryRow, $enableCopyMetadata, $metadata); } } } diff --git a/www/analytics/core/DataTable/Simple.php b/www/analytics/core/DataTable/Simple.php index 010e6586..018818c2 100644 --- a/www/analytics/core/DataTable/Simple.php +++ b/www/analytics/core/DataTable/Simple.php @@ -1,6 +1,6 @@ $value1, * 'Label row 2' => $value2, diff --git a/www/analytics/core/DataTable/TableNotFoundException.php b/www/analytics/core/DataTable/TableNotFoundException.php index 6ccd5e12..4286405b 100644 --- a/www/analytics/core/DataTable/TableNotFoundException.php +++ b/www/analytics/core/DataTable/TableNotFoundException.php @@ -1,6 +1,6 @@ addHour(5); - * echo $date->getLocalized("%longDay% the %day% of %longMonth% at %time%"); - * + * echo $date->getLocalized("EEE, d. MMM y 'at' HH:mm:ss"); + * * @api */ class Date @@ -40,6 +42,36 @@ class Date /** The default date time string format. */ const DATE_TIME_FORMAT = 'Y-m-d H:i:s'; + const DATETIME_FORMAT_LONG = DateTimeFormatProvider::DATE_FORMAT_LONG; + const DATETIME_FORMAT_SHORT = DateTimeFormatProvider::DATETIME_FORMAT_SHORT; + const DATE_FORMAT_LONG = DateTimeFormatProvider::DATE_FORMAT_LONG; + const DATE_FORMAT_DAY_MONTH = DateTimeFormatProvider::DATE_FORMAT_DAY_MONTH; + const DATE_FORMAT_SHORT = DateTimeFormatProvider::DATE_FORMAT_SHORT; + const DATE_FORMAT_MONTH_SHORT = DateTimeFormatProvider::DATE_FORMAT_MONTH_SHORT; + const DATE_FORMAT_MONTH_LONG = DateTimeFormatProvider::DATE_FORMAT_MONTH_LONG; + const DATE_FORMAT_YEAR = DateTimeFormatProvider::DATE_FORMAT_YEAR; + const TIME_FORMAT = DateTimeFormatProvider::TIME_FORMAT; + + /** + * Max days for months (non-leap-year). See {@link addPeriod()} implementation. + * + * @var int[] + */ + private static $maxDaysInMonth = array( + '1' => 31, + '2' => 28, + '3' => 31, + '4' => 30, + '5' => 31, + '6' => 30, + '7' => 31, + '8' => 31, + '9' => 30, + '10' => 31, + '11' => 30, + '12' => 31 + ); + /** * The stored timestamp is always UTC based. * The returned timestamp via getTimestamp() will have the conversion applied @@ -84,7 +116,6 @@ class Date */ public static function factory($dateString, $timezone = null) { - $invalidDateException = new Exception(Piwik::translate('General_ExceptionInvalidDateFormat', array("YYYY-MM-DD, or 'today' or 'yesterday'", "strtotime", "http://php.net/strtotime")) . ": $dateString"); if ($dateString instanceof self) { $dateString = $dateString->toString(); } @@ -105,7 +136,7 @@ class Date ($dateString = strtotime($dateString)) === false ) ) { - throw $invalidDateException; + throw self::getInvalidDateFormatException($dateString); } else { $date = new Date($dateString); } @@ -113,7 +144,7 @@ class Date // can't be doing web analytics before the 1st website // Tue, 06 Aug 1991 00:00:00 GMT if ($timestamp < 681436800) { - throw $invalidDateException; + throw self::getInvalidDateFormatException($dateString); } if (empty($timezone)) { return $date; @@ -133,6 +164,19 @@ class Date return $this->toString(self::DATE_TIME_FORMAT); } + /** + * Returns the current hour in UTC timezone. + * @return string + * @throws Exception + */ + public function getHourUTC() + { + $dateTime = $this->getDatetime(); + $hourInTz = Date::factory($dateTime, 'UTC')->toString('G'); + + return $hourInTz; + } + /** * Returns the start of the day of the current timestamp in UTC. For example, * if the current timestamp is `'2007-07-24 14:04:24'` in UTC, the result will @@ -164,7 +208,7 @@ class Date /** * Returns a new date object with the same timestamp as `$this` but with a new * timezone. - * + * * See {@link getTimestamp()} to see how the timezone is used. * * @param string $timezone eg, `'UTC'`, `'Europe/London'`, etc. @@ -222,6 +266,17 @@ class Date return strtotime($datetime); } + /** + * Returns the date in the "Y-m-d H:i:s" PHP format + * + * @param int $timestamp + * @return string + */ + public static function getDatetimeFromTimestamp($timestamp) + { + return date("Y-m-d H:i:s", $timestamp); + } + /** * Returns the Unix timestamp of the date in UTC. * @@ -253,15 +308,16 @@ class Date // Unit tests pass (@see Date.test.php) but I'm pretty sure this is not the right way to do it date_default_timezone_set($this->timezone); $dtzone = timezone_open('UTC'); - $time = date('r', $this->timestamp); - $dtime = date_create($time); + $time = date('r', $this->timestamp); + $dtime = date_create($time); + date_timezone_set($dtime, $dtzone); - $dateWithTimezone = date_format($dtime, 'r'); + $dateWithTimezone = date_format($dtime, 'r'); $dateWithoutTimezone = substr($dateWithTimezone, 0, -6); - $timestamp = strtotime($dateWithoutTimezone); + $timestamp = strtotime($dateWithoutTimezone); date_default_timezone_set('UTC'); - return (int)$timestamp; + return (int) $timestamp; } /** @@ -375,7 +431,6 @@ class Date return 0; } if ($currentYear < $toCompareYear) { - return -1; } return 1; @@ -383,7 +438,7 @@ class Date /** * Returns `true` if current date is today. - * + * * @return bool */ public function isToday() @@ -560,9 +615,39 @@ class Date /** * Returns a localized date string using the given template. * The template should contain tags that will be replaced with localized date strings. - * - * Allowed tags include: - * + * + * @param string $template eg. `"MMM y"` + * @return string eg. `"Aug 2009"` + */ + public function getLocalized($template) + { + $template = $this->replaceLegacyPlaceholders($template); + + $dateTimeFormatProvider = StaticContainer::get('Piwik\Intl\Data\Provider\DateTimeFormatProvider'); + + $template = $dateTimeFormatProvider->getFormatPattern($template); + + $tokens = self::parseFormat($template); + + $out = ''; + + foreach ($tokens AS $token) { + if (is_array($token)) { + $out .= $this->formatToken(array_shift($token)); + + } else { + $out .= $token; + } + } + + return $out; + } + + /** + * Replaces legacy placeholders + * + * @deprecated should be removed in Piwik 3.0.0 or later + * * - **%day%**: replaced with the day of the month without leading zeros, eg, **1** or **20**. * - **%shortMonth%**: the short month in the current language, eg, **Jan**, **Feb**. * - **%longMonth%**: the whole month name in the current language, eg, **January**, **February**. @@ -571,27 +656,183 @@ class Date * - **%longYear%**: the four digit year, eg, **2007**, **2013**. * - **%shortYear%**: the two digit year, eg, **07**, **13**. * - **%time%**: the time of day, eg, **07:35:00**, or **15:45:00**. - * - * @param string $template eg. `"%shortMonth% %longYear%"` - * @return string eg. `"Aug 2009"` */ - public function getLocalized($template) + protected function replaceLegacyPlaceholders($template) + { + if (strpos($template, '%') === false) { + return $template; + } + + $mapping = array( + '%day%' => 'd', + '%shortMonth%' => 'MMM', + '%longMonth%' => 'MMMM', + '%shortDay%' => 'EEE', + '%longDay%' => 'EEEE', + '%longYear%' => 'y', + '%shortYear%' => 'yy', + '%time%' => 'HH:mm:ss' + ); + + return str_replace(array_keys($mapping), array_values($mapping), $template); + } + + protected function formatToken($token) { - $day = $this->toString('j'); $dayOfWeek = $this->toString('N'); $monthOfYear = $this->toString('n'); - $patternToValue = array( - "%day%" => $day, - "%shortMonth%" => Piwik::translate('General_ShortMonth_' . $monthOfYear), - "%longMonth%" => Piwik::translate('General_LongMonth_' . $monthOfYear), - "%shortDay%" => Piwik::translate('General_ShortDay_' . $dayOfWeek), - "%longDay%" => Piwik::translate('General_LongDay_' . $dayOfWeek), - "%longYear%" => $this->toString('Y'), - "%shortYear%" => $this->toString('y'), - "%time%" => $this->toString('H:i:s') - ); - $out = str_replace(array_keys($patternToValue), array_values($patternToValue), $template); - return $out; + $translator = StaticContainer::get('Piwik\Translation\Translator'); + + switch ($token) { + // year + case "yyyy": + case "y": + return $this->toString('Y'); + case "yy": + return $this->toString('y'); + // month + case "MMMM": + return $translator->translate('Intl_Month_Long_' . $monthOfYear); + case "MMM": + return $translator->translate('Intl_Month_Short_' . $monthOfYear); + case "MM": + return $this->toString('n'); + case "M": + return $this->toString('m'); + case "LLLL": + return $translator->translate('Intl_Month_Long_StandAlone_' . $monthOfYear); + case "LLL": + return $translator->translate('Intl_Month_Short_StandAlone_' . $monthOfYear); + case "LL": + return $this->toString('n'); + case "L": + return $this->toString('m'); + // day + case "dd": + return $this->toString('d'); + case "d": + return $this->toString('j'); + case "EEEE": + return $translator->translate('Intl_Day_Long_' . $dayOfWeek); + case "EEE": + case "EE": + case "E": + return $translator->translate('Intl_Day_Short_' . $dayOfWeek); + case "CCCC": + return $translator->translate('Intl_Day_Long_StandAlone_' . $dayOfWeek); + case "CCC": + case "CC": + case "C": + return $translator->translate('Intl_Day_Short_StandAlone_' . $dayOfWeek); + case "D": + return 1 + (int)$this->toString('z'); // 1 - 366 + case "F": + return (int)(((int)$this->toString('j') + 6) / 7); + // week in month + case "w": + $weekDay = date('N', mktime(0, 0, 0, $this->toString('m'), 1, $this->toString('y'))); + return floor(($weekDay + (int)$this->toString('m') - 2) / 7) + 1; + // week in year + case "W": + return $this->toString('N'); + // hour + case "HH": + return $this->toString('H'); + case "H": + return $this->toString('G'); + case "hh": + return $this->toString('h'); + case "h": + return $this->toString('g'); + // minute + case "mm": + case "m": + return $this->toString('i'); + // second + case "ss": + case "s": + return $this->toString('s'); + // am / pm + case "a": + return $this->toString('a') == 'am' ? $translator->translate('Intl_Time_AM') : $translator->translate('Intl_Time_PM'); + + // currently not implemented: + case "G": + case "GG": + case "GGG": + case "GGGG": + case "GGGGG": + return ''; // era + case "z": + case "Z": + case "v": + return ''; // time zone + + } + + return ''; + } + + protected static $tokens = array( + 'G', 'y', 'M', 'L', 'd', 'h', 'H', 'm', 's', 'E', 'c', 'e', 'D', 'F', 'w', 'W', 'a', 'z', 'Z', 'v', + ); + + /** + * Parses the datetime format pattern and returns a tokenized result array + * + * Examples: + * Input Output + * 'dd.mm.yyyy' array(array('dd'), '.', array('mm'), '.', array('yyyy')) + * 'y?M?d?EEEE ah:mm:ss' array(array('y'), '?', array('M'), '?', array('d'), '?', array('EEEE'), ' ', array('a'), array('h'), ':', array('mm'), ':', array('ss')) + * + * @param string $pattern the pattern to be parsed + * @return array tokenized parsing result + */ + protected static function parseFormat($pattern) + { + static $formats = array(); // cache + if (isset($formats[$pattern])) { + return $formats[$pattern]; + } + $tokens = array(); + $n = strlen($pattern); + $isLiteral = false; + $literal = ''; + for ($i = 0; $i < $n; ++$i) { + $c = $pattern[$i]; + if ($c === "'") { + if ($i < $n - 1 && $pattern[$i + 1] === "'") { + $tokens[] = "'"; + $i++; + } elseif ($isLiteral) { + $tokens[] = $literal; + $literal = ''; + $isLiteral = false; + } else { + $isLiteral = true; + $literal = ''; + } + } elseif ($isLiteral) { + $literal .= $c; + } else { + for ($j = $i + 1; $j < $n; ++$j) { + if ($pattern[$j] !== $c) { + break; + } + } + $p = str_repeat($c, $j - $i); + if (in_array($c, self::$tokens)) { + $tokens[] = array($p); + } else { + $tokens[] = $p; + } + $i = $j - 1; + } + } + if ($literal !== '') { + $tokens[] = $literal; + } + return $formats[$pattern] = $tokens; } /** @@ -664,6 +905,17 @@ class Date return $this->addHour(-$n); } + /** + * Subtracts `$n` seconds from `$this` date and returns the result in a new Date. + * + * @param int $n Number of seconds to subtract. Can be less than 0. + * @return \Piwik\Date + */ + public function subSeconds($n) + { + return new Date($this->timestamp - $n, $this->timezone); + } + /** * Adds a period to `$this` date and returns the result in a new Date instance. * @@ -673,14 +925,41 @@ class Date */ public function addPeriod($n, $period) { - if ($n < 0) { - $ts = strtotime("$n $period", $this->timestamp); + if (strtolower($period) == 'month') { // TODO: comments + $dateInfo = getdate($this->timestamp); + + $ts = mktime( + $dateInfo['hours'], + $dateInfo['minutes'], + $dateInfo['seconds'], + $dateInfo['mon'] + (int)$n, + 1, + $dateInfo['year'] + ); + + $daysToAdd = min($dateInfo['mday'], self::getMaxDaysInMonth($ts)) - 1; + $ts += self::NUM_SECONDS_IN_DAY * $daysToAdd; } else { - $ts = strtotime("+$n $period", $this->timestamp); + $time = $n < 0 ? "$n $period" : "+$n $period"; + + $ts = strtotime($time, $this->timestamp); } + return new Date($ts, $this->timezone); } + private static function getMaxDaysInMonth($timestamp) + { + $month = (int)date('m', $timestamp); + if (date('L', $timestamp) == 1 + && $month == 2 + ) { + return 29; + } else { + return self::$maxDaysInMonth[$month]; + } + } + /** * Subtracts a period from `$this` date and returns the result in a new Date instance. * @@ -703,4 +982,10 @@ class Date { return $secs / self::NUM_SECONDS_IN_DAY; } + + private static function getInvalidDateFormatException($dateString) + { + $message = Piwik::translate('General_ExceptionInvalidDateFormat', array("YYYY-MM-DD, or 'today' or 'yesterday'", "strtotime", "http://php.net/strtotime")); + return new Exception($message . ": $dateString"); + } } diff --git a/www/analytics/core/Db.php b/www/analytics/core/Db.php index a418e490..a4b114d6 100644 --- a/www/analytics/core/Db.php +++ b/www/analytics/core/Db.php @@ -1,6 +1,6 @@ Debug['enable_sql_profiler']; + $dbConfig['profiler'] = @$config->Debug['enable_sql_profiler']; return $dbConfig; } + /** + * For tests only. + * @param $connection + * @ignore + * @internal + */ + public static function setDatabaseObject($connection) + { + self::$connection = $connection; + } + /** * Connects to the database. - * + * * Shouldn't be called directly, use {@link get()} instead. - * + * * @param array|null $dbConfig Connection parameters in an array. Defaults to the `[database]` * INI config section. */ @@ -103,6 +118,16 @@ class Db self::$connection = $db; } + /** + * Detect whether a database object is initialized / created or not. + * + * @internal + */ + public static function hasDatabaseObject() + { + return isset(self::$connection); + } + /** * Disconnects and destroys the database connection. * @@ -124,7 +149,7 @@ class Db * @throws \Exception If there is an error in the SQL. * @return integer|\Zend_Db_Statement */ - static public function exec($sql) + public static function exec($sql) { /** @var \Zend_Db_Adapter_Abstract $db */ $db = self::get(); @@ -132,6 +157,8 @@ class Db $q = $profiler->queryStart($sql, \Zend_Db_Profiler::INSERT); try { + self::logSql(__FUNCTION__, $sql); + $return = self::get()->exec($sql); } catch (Exception $ex) { self::logExtraInfoIfDeadlock($ex); @@ -139,13 +166,14 @@ class Db } $profiler->queryEnd($q); + return $return; } /** * Executes an SQL query and returns the [Zend_Db_Statement](http://framework.zend.com/manual/1.12/en/zend.db.statement.html) * for the query. - * + * * This method is meant for non-query SQL statements like `INSERT` and `UPDATE. If you want to fetch * data from the DB you should use one of the fetch... functions. * @@ -154,9 +182,11 @@ class Db * @throws \Exception If there is a problem with the SQL or bind parameters. * @return \Zend_Db_Statement */ - static public function query($sql, $parameters = array()) + public static function query($sql, $parameters = array()) { try { + self::logSql(__FUNCTION__, $sql, $parameters); + return self::get()->query($sql, $parameters); } catch (Exception $ex) { self::logExtraInfoIfDeadlock($ex); @@ -173,9 +203,11 @@ class Db * @return array The fetched rows, each element is an associative array mapping column names * with column values. */ - static public function fetchAll($sql, $parameters = array()) + public static function fetchAll($sql, $parameters = array()) { try { + self::logSql(__FUNCTION__, $sql, $parameters); + return self::get()->fetchAll($sql, $parameters); } catch (Exception $ex) { self::logExtraInfoIfDeadlock($ex); @@ -192,9 +224,11 @@ class Db * @return array The fetched row, each element is an associative array mapping column names * with column values. */ - static public function fetchRow($sql, $parameters = array()) + public static function fetchRow($sql, $parameters = array()) { try { + self::logSql(__FUNCTION__, $sql, $parameters); + return self::get()->fetchRow($sql, $parameters); } catch (Exception $ex) { self::logExtraInfoIfDeadlock($ex); @@ -211,9 +245,11 @@ class Db * @throws \Exception If there is a problem with the SQL or bind parameters. * @return string */ - static public function fetchOne($sql, $parameters = array()) + public static function fetchOne($sql, $parameters = array()) { try { + self::logSql(__FUNCTION__, $sql, $parameters); + return self::get()->fetchOne($sql, $parameters); } catch (Exception $ex) { self::logExtraInfoIfDeadlock($ex); @@ -234,9 +270,11 @@ class Db * 'col1value2' => array('col2' => '...', 'col3' => ...)) * ``` */ - static public function fetchAssoc($sql, $parameters = array()) + public static function fetchAssoc($sql, $parameters = array()) { try { + self::logSql(__FUNCTION__, $sql, $parameters); + return self::get()->fetchAssoc($sql, $parameters); } catch (Exception $ex) { self::logExtraInfoIfDeadlock($ex); @@ -247,33 +285,33 @@ class Db /** * Deletes all desired rows in a table, while using a limit. This function will execute many * DELETE queries until there are no more rows to delete. - * + * * Use this function when you need to delete many thousands of rows from a table without * locking the table for too long. - * + * * **Example** - * + * * // delete all visit rows whose ID is less than a certain value, 100000 rows at a time * $idVisit = // ... * Db::deleteAllRows(Common::prefixTable('log_visit'), "WHERE idvisit <= ?", "idvisit ASC", 100000, array($idVisit)); - * + * * @param string $table The name of the table to delete from. Must be prefixed (see {@link Piwik\Common::prefixTable()}). * @param string $where The where clause of the query. Must include the WHERE keyword. - * @param $orderBy The column to order by and the order by direction, eg, `idvisit ASC`. + * @param string $orderBy The column to order by and the order by direction, eg, `idvisit ASC`. * @param int $maxRowsPerQuery The maximum number of rows to delete per `DELETE` query. * @param array $parameters Parameters to bind for each query. * @return int The total number of rows deleted. */ - static public function deleteAllRows($table, $where, $orderBy, $maxRowsPerQuery = 100000, $parameters = array()) + public static function deleteAllRows($table, $where, $orderBy, $maxRowsPerQuery = 100000, $parameters = array()) { $orderByClause = $orderBy ? "ORDER BY $orderBy" : ""; - $sql = "DELETE FROM $table - $where - $orderByClause + + $sql = "DELETE FROM $table $where $orderByClause LIMIT " . (int)$maxRowsPerQuery; // delete rows w/ a limit $totalRowsDeleted = 0; + do { $rowsDeleted = self::query($sql, $parameters)->rowCount(); @@ -285,44 +323,60 @@ class Db /** * Runs an `OPTIMIZE TABLE` query on the supplied table or tables. - * + * * Tables will only be optimized if the `[General] enable_sql_optimize_queries` INI config option is * set to **1**. * * @param string|array $tables The name of the table to optimize or an array of tables to optimize. * Table names must be prefixed (see {@link Piwik\Common::prefixTable()}). + * @param bool $force If true, the `OPTIMIZE TABLE` query will be run even if InnoDB tables are being used. * @return \Zend_Db_Statement */ - static public function optimizeTables($tables) + public static function optimizeTables($tables, $force = false) { $optimize = Config::getInstance()->General['enable_sql_optimize_queries']; - if (empty($optimize)) { - return; + + if (empty($optimize) + && !$force + ) { + return false; } if (empty($tables)) { return false; } + if (!is_array($tables)) { $tables = array($tables); } - // filter out all InnoDB tables - $myisamDbTables = array(); - foreach (Db::fetchAll("SHOW TABLE STATUS") as $row) { - if (strtolower($row['Engine']) == 'myisam' - && in_array($row['Name'], $tables) - ) { - $myisamDbTables[] = $row['Name']; + if (!self::isOptimizeInnoDBSupported() + && !$force + ) { + // filter out all InnoDB tables + $myisamDbTables = array(); + foreach (self::getTableStatus() as $row) { + if (strtolower($row['Engine']) == 'myisam' + && in_array($row['Name'], $tables) + ) { + $myisamDbTables[] = $row['Name']; + } } + + $tables = $myisamDbTables; } - if (empty($myisamDbTables)) { + if (empty($tables)) { return false; } // optimize the tables - return self::query("OPTIMIZE TABLE " . implode(',', $myisamDbTables)); + return self::query("OPTIMIZE TABLE " . implode(',', $tables)); + } + + private static function getTableStatus() + { + return Db::fetchAll("SHOW TABLE STATUS"); } /** @@ -332,19 +386,19 @@ class Db * Table names must be prefixed (see {@link Piwik\Common::prefixTable()}). * @return \Zend_Db_Statement */ - static public function dropTables($tables) + public static function dropTables($tables) { if (!is_array($tables)) { $tables = array($tables); } - return self::query("DROP TABLE " . implode(',', $tables)); + return self::query("DROP TABLE `" . implode('`,`', $tables) . "`"); } /** * Drops all tables */ - static public function dropAllTables() + public static function dropAllTables() { $tablesAlreadyInstalled = DbHelper::getTablesInstalled(); self::dropTables($tablesAlreadyInstalled); @@ -355,36 +409,32 @@ class Db * * @param string|array $table The name of the table you want to get the columns definition for. * @return \Zend_Db_Statement + * @deprecated since 2.11.0 */ - static public function getColumnNamesFromTable($table) + public static function getColumnNamesFromTable($table) { - $columns = self::fetchAll("SHOW COLUMNS FROM " . $table); - - $columnNames = array(); - foreach ($columns as $column) { - $columnNames[] = $column['Field']; - } - - return $columnNames; + $tableMetadataAccess = new TableMetadata(); + return $tableMetadataAccess->getColumns($table); } /** * Locks the supplied table or tables. - * + * * **NOTE:** Piwik does not require the `LOCK TABLES` privilege to be available. Piwik * should still work if it has not been granted. - * + * * @param string|array $tablesToRead The table or tables to obtain 'read' locks on. Table names must * be prefixed (see {@link Piwik\Common::prefixTable()}). * @param string|array $tablesToWrite The table or tables to obtain 'write' locks on. Table names must * be prefixed (see {@link Piwik\Common::prefixTable()}). * @return \Zend_Db_Statement */ - static public function lockTables($tablesToRead, $tablesToWrite = array()) + public static function lockTables($tablesToRead, $tablesToWrite = array()) { if (!is_array($tablesToRead)) { $tablesToRead = array($tablesToRead); } + if (!is_array($tablesToWrite)) { $tablesToWrite = array($tablesToWrite); } @@ -393,6 +443,7 @@ class Db foreach ($tablesToWrite as $table) { $lockExprs[] = $table . " WRITE"; } + foreach ($tablesToRead as $table) { $lockExprs[] = $table . " READ"; } @@ -405,10 +456,10 @@ class Db * * **NOTE:** Piwik does not require the `LOCK TABLES` privilege to be available. Piwik * should still work if it has not been granted. - * + * * @return \Zend_Db_Statement */ - static public function unlockAllTables() + public static function unlockAllTables() { return self::exec("UNLOCK TABLES"); } @@ -416,7 +467,7 @@ class Db /** * Performs a `SELECT` statement on a table one chunk at a time and returns the first * successfully fetched value. - * + * * This function will execute a query on one set of rows in a table. If nothing * is fetched, it will execute the query on the next set of rows and so on until * the query returns a value. @@ -425,10 +476,10 @@ class Db * should be used when performing a `SELECT` that can take a long time to finish. * Using several smaller `SELECT`s will ensure that the table will not be locked * for too long. - * + * * **Example** - * - * // find the most recent visit that is older than a certain date + * + * // find the most recent visit that is older than a certain date * $dateStart = // ... * $sql = "SELECT idvisit * FROM $logVisit @@ -451,9 +502,10 @@ class Db * * @return string */ - static public function segmentedFetchFirst($sql, $first, $last, $step, $params = array()) + public static function segmentedFetchFirst($sql, $first, $last, $step, $params = array()) { $result = false; + if ($step > 0) { for ($i = $first; $result === false && $i <= $last; $i += $step) { $result = self::fetchOne($sql, array_merge($params, array($i, $i + $step))); @@ -463,17 +515,18 @@ class Db $result = self::fetchOne($sql, array_merge($params, array($i, $i + $step))); } } + return $result; } /** * Performs a `SELECT` on a table one chunk at a time and returns an array * of every fetched value. - * + * * This function will break up a `SELECT` query into several smaller queries by * using only a limited number of rows at a time. It will accumulate the results * of each smaller query and return the result. - * + * * This function should be used when performing a `SELECT` that can * take a long time to finish. Using several smaller queries will ensure that * the table will not be locked for too long. @@ -487,9 +540,10 @@ class Db * @param array $params Parameters to bind in the query, `array(param1 => value1, param2 => value2)` * @return array An array of primitive values. */ - static public function segmentedFetchOne($sql, $first, $last, $step, $params = array()) + public static function segmentedFetchOne($sql, $first, $last, $step, $params = array()) { $result = array(); + if ($step > 0) { for ($i = $first; $i <= $last; $i += $step) { $result[] = self::fetchOne($sql, array_merge($params, array($i, $i + $step))); @@ -499,6 +553,7 @@ class Db $result[] = self::fetchOne($sql, array_merge($params, array($i, $i + $step))); } } + return $result; } @@ -509,11 +564,11 @@ class Db * This function will break up a `SELECT` query into several smaller queries by * using only a limited number of rows at a time. It will accumulate the results * of each smaller query and return the result. - * + * * This function should be used when performing a `SELECT` that can * take a long time to finish. Using several smaller queries will ensure that * the table will not be locked for too long. - * + * * @param string $sql The SQL to perform. The last two conditions of the `WHERE` * expression must be as follows: `'id >= ? AND id < ?'` where * **id** is the int id of the table. @@ -524,33 +579,35 @@ class Db * @return array An array of rows that includes the result set of every smaller * query. */ - static public function segmentedFetchAll($sql, $first, $last, $step, $params = array()) + public static function segmentedFetchAll($sql, $first, $last, $step, $params = array()) { $result = array(); + if ($step > 0) { for ($i = $first; $i <= $last; $i += $step) { $currentParams = array_merge($params, array($i, $i + $step)); - $result = array_merge($result, self::fetchAll($sql, $currentParams)); + $result = array_merge($result, self::fetchAll($sql, $currentParams)); } } else { for ($i = $first; $i >= $last; $i += $step) { $currentParams = array_merge($params, array($i, $i + $step)); - $result = array_merge($result, self::fetchAll($sql, $currentParams)); + $result = array_merge($result, self::fetchAll($sql, $currentParams)); } } + return $result; } /** * Performs a `UPDATE` or `DELETE` statement on a table one chunk at a time. - * + * * This function will break up a query into several smaller queries by * using only a limited number of rows at a time. - * + * * This function should be used when executing a non-query statement will * take a long time to finish. Using several smaller queries will ensure that * the table will not be locked for too long. - * + * * @param string $sql The SQL to perform. The last two conditions of the `WHERE` * expression must be as follows: `'id >= ? AND id < ?'` where * **id** is the int id of the table. @@ -559,7 +616,7 @@ class Db * @param int $step The maximum number of rows to scan in one query. * @param array $params Parameters to bind in the query, `array(param1 => value1, param2 => value2)` */ - static public function segmentedQuery($sql, $first, $last, $step, $params = array()) + public static function segmentedQuery($sql, $first, $last, $step, $params = array()) { if ($step > 0) { for ($i = $first; $i <= $last; $i += $step) { @@ -574,17 +631,6 @@ class Db } } - /** - * Returns `true` if a table in the database, `false` if otherwise. - * - * @param string $tableName The name of the table to check for. Must be prefixed. - * @return bool - */ - static public function tableExists($tableName) - { - return self::query("SHOW TABLES LIKE ?", $tableName)->rowCount() > 0; - } - /** * Attempts to get a named lock. This function uses a timeout of 1s, but will * retry a set number of times. @@ -592,9 +638,14 @@ class Db * @param string $lockName The lock name. * @param int $maxRetries The max number of times to retry. * @return bool `true` if the lock was obtained, `false` if otherwise. + * @throws \Exception if Lock name is too long */ - static public function getDbLock($lockName, $maxRetries = 30) + public static function getDbLock($lockName, $maxRetries = 30) { + if (strlen($lockName) > 64) { + throw new \Exception('DB lock name has to be 64 characters or less for MySQL 5.7 compatibility.'); + } + /* * the server (e.g., shared hosting) may have a low wait timeout * so instead of a single GET_LOCK() with a 30 second timeout, @@ -606,11 +657,13 @@ class Db $db = self::get(); while ($maxRetries > 0) { - if ($db->fetchOne($sql, array($lockName)) == '1') { + $result = $db->fetchOne($sql, array($lockName)); + if ($result == '1') { return true; } $maxRetries--; } + return false; } @@ -620,7 +673,7 @@ class Db * @param string $lockName The lock name. * @return bool `true` if the lock was released, `false` if otherwise. */ - static public function releaseDbLock($lockName) + public static function releaseDbLock($lockName) { $sql = 'SELECT RELEASE_LOCK(?)'; @@ -661,11 +714,62 @@ class Db private static function logExtraInfoIfDeadlock($ex) { - if (self::get()->isErrNo($ex, 1213)) { + if (!self::get()->isErrNo($ex, 1213)) { + return; + } + + try { $deadlockInfo = self::fetchAll("SHOW ENGINE INNODB STATUS"); // log using exception so backtrace appears in log output Log::debug(new Exception("Encountered deadlock: " . print_r($deadlockInfo, true))); + + } catch(\Exception $e) { + // 1227 Access denied; you need (at least one of) the PROCESS privilege(s) for this operation } } + + private static function logSql($functionName, $sql, $parameters = array()) + { + if (self::$logQueries === false + || @Config::getInstance()->Debug['log_sql_queries'] != 1 + ) { + return; + } + + // NOTE: at the moment we don't log parameters in order to avoid sensitive information leaks + Log::debug("Db::%s() executing SQL: %s", $functionName, $sql); + } + + /** + * @param bool $enable + */ + public static function enableQueryLog($enable) + { + self::$logQueries = $enable; + } + + /** + * @return boolean + */ + public static function isQueryLogEnabled() + { + return self::$logQueries; + } + + public static function isOptimizeInnoDBSupported($version = null) + { + if ($version === null) { + $version = Db::fetchOne("SELECT VERSION()"); + } + + $version = strtolower($version); + + if (strpos($version, "mariadb") === false) { + return false; + } + + $semanticVersion = strstr($version, '-', $beforeNeedle = true); + return version_compare($semanticVersion, '10.1.1', '>='); + } } diff --git a/www/analytics/core/Db/Adapter.php b/www/analytics/core/Db/Adapter.php index ea017733..bea69e1d 100644 --- a/www/analytics/core/Db/Adapter.php +++ b/www/analytics/core/Db/Adapter.php @@ -1,6 +1,6 @@ $val) { + $infos[$key] = $val; + } + + $adapter = new $className($infos); if ($connect) { $adapter->getConnection(); @@ -60,10 +63,15 @@ class Adapter * * @param string $adapterName * @return string + * @throws \Exception */ private static function getAdapterClassName($adapterName) { - return 'Piwik\Db\Adapter\\' . str_replace(' ', '\\', ucwords(str_replace(array('_', '\\'), ' ', strtolower($adapterName)))); + $className = 'Piwik\Db\Adapter\\' . str_replace(' ', '\\', ucwords(str_replace(array('_', '\\'), ' ', strtolower($adapterName)))); + if (!class_exists($className)) { + throw new \Exception("Adapter $adapterName is not valid."); + } + return $className; } /** diff --git a/www/analytics/core/Db/Adapter/Mysqli.php b/www/analytics/core/Db/Adapter/Mysqli.php index c3990bfe..c06c8440 100644 --- a/www/analytics/core/Db/Adapter/Mysqli.php +++ b/www/analytics/core/Db/Adapter/Mysqli.php @@ -1,6 +1,6 @@ _connection) { + return; + } + + parent::_connect(); + + $this->_connection->query('SET sql_mode = "' . Db::SQL_MODE . '"'); + } + /** * Check MySQL version * @@ -56,8 +68,9 @@ class Mysqli extends Zend_Db_Adapter_Mysqli implements AdapterInterface */ public function checkServerVersion() { - $serverVersion = $this->getServerVersion(); + $serverVersion = $this->getServerVersion(); $requiredVersion = Config::getInstance()->General['minimum_mysql_version']; + if (version_compare($serverVersion, $requiredVersion) === -1) { throw new Exception(Piwik::translate('General_ExceptionDatabaseVersion', array('MySQL', $serverVersion, $requiredVersion))); } @@ -72,6 +85,7 @@ class Mysqli extends Zend_Db_Adapter_Mysqli implements AdapterInterface { $serverVersion = $this->getServerVersion(); $clientVersion = $this->getClientVersion(); + // incompatible change to DECIMAL implementation in 5.0.3 if (version_compare($serverVersion, '5.0.3') >= 0 && version_compare($clientVersion, '5.0.3') < 0 @@ -168,10 +182,12 @@ class Mysqli extends Zend_Db_Adapter_Mysqli implements AdapterInterface public function getClientVersion() { $this->_connect(); - $version = $this->_connection->server_version; - $major = (int)($version / 10000); - $minor = (int)($version % 10000 / 100); + + $version = $this->_connection->server_version; + $major = (int)($version / 10000); + $minor = (int)($version % 10000 / 100); $revision = (int)($version % 100); + return $major . '.' . $minor . '.' . $revision; } } diff --git a/www/analytics/core/Db/Adapter/Pdo/Mssql.php b/www/analytics/core/Db/Adapter/Pdo/Mssql.php index cca882b3..96e1c729 100644 --- a/www/analytics/core/Db/Adapter/Pdo/Mssql.php +++ b/www/analytics/core/Db/Adapter/Pdo/Mssql.php @@ -1,6 +1,6 @@ _config["host"]; - $database = $this->_config["dbname"]; + $database = $this->_config["dbname"]; if (is_null($database)) { $database = 'master'; } @@ -134,8 +134,9 @@ class Mssql extends Zend_Db_Adapter_Pdo_Mssql implements AdapterInterface */ public function checkServerVersion() { - $serverVersion = $this->getServerVersion(); + $serverVersion = $this->getServerVersion(); $requiredVersion = Config::getInstance()->General['minimum_mssql_version']; + if (version_compare($serverVersion, $requiredVersion) === -1) { throw new Exception(Piwik::translate('General_ExceptionDatabaseVersion', array('MSSQL', $serverVersion, $requiredVersion))); } @@ -149,7 +150,7 @@ class Mssql extends Zend_Db_Adapter_Pdo_Mssql implements AdapterInterface public function getServerVersion() { try { - $stmt = $this->query("SELECT CAST(SERVERPROPERTY('productversion') as VARCHAR) as productversion"); + $stmt = $this->query("SELECT CAST(SERVERPROPERTY('productversion') as VARCHAR) as productversion"); $result = $stmt->fetchAll(Zend_Db::FETCH_NUM); if (count($result)) { return $result[0][0]; @@ -169,6 +170,7 @@ class Mssql extends Zend_Db_Adapter_Pdo_Mssql implements AdapterInterface { $serverVersion = $this->getServerVersion(); $clientVersion = $this->getClientVersion(); + if (version_compare($serverVersion, '10') >= 0 && version_compare($clientVersion, '10') < 0 ) { @@ -183,8 +185,7 @@ class Mssql extends Zend_Db_Adapter_Pdo_Mssql implements AdapterInterface */ public static function isEnabled() { - $extensions = @get_loaded_extensions(); - return in_array('PDO', $extensions) && in_array('pdo_sqlsrv', $extensions); + return extension_loaded('PDO') && extension_loaded('pdo_sqlsrv'); } /** @@ -224,6 +225,7 @@ class Mssql extends Zend_Db_Adapter_Pdo_Mssql implements AdapterInterface if (preg_match('/(?:\[|\s)([0-9]{4})(?:\]|\s)/', $e->getMessage(), $match)) { return $match[1] == $errno; } + return false; } diff --git a/www/analytics/core/Db/Adapter/Pdo/Mysql.php b/www/analytics/core/Db/Adapter/Pdo/Mysql.php index 368c7810..9e26fb7a 100644 --- a/www/analytics/core/Db/Adapter/Pdo/Mysql.php +++ b/www/analytics/core/Db/Adapter/Pdo/Mysql.php @@ -1,6 +1,6 @@ _connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); + return $this->_connection; + } + + protected function _connect() + { + if ($this->_connection) { + return; + } + + parent::_connect(); + // MYSQL_ATTR_USE_BUFFERED_QUERY will use more memory when enabled // $this->_connection->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true); - return $this->_connection; + $this->_connection->exec('SET sql_mode = "' . Db::SQL_MODE . '"'); } /** @@ -92,8 +104,9 @@ class Mysql extends Zend_Db_Adapter_Pdo_Mysql implements AdapterInterface */ public function checkServerVersion() { - $serverVersion = $this->getServerVersion(); + $serverVersion = $this->getServerVersion(); $requiredVersion = Config::getInstance()->General['minimum_mysql_version']; + if (version_compare($serverVersion, $requiredVersion) === -1) { throw new Exception(Piwik::translate('General_ExceptionDatabaseVersion', array('MySQL', $serverVersion, $requiredVersion))); } @@ -108,6 +121,7 @@ class Mysql extends Zend_Db_Adapter_Pdo_Mysql implements AdapterInterface { $serverVersion = $this->getServerVersion(); $clientVersion = $this->getClientVersion(); + // incompatible change to DECIMAL implementation in 5.0.3 if (version_compare($serverVersion, '5.0.3') >= 0 && version_compare($clientVersion, '5.0.3') < 0 @@ -123,8 +137,7 @@ class Mysql extends Zend_Db_Adapter_Pdo_Mysql implements AdapterInterface */ public static function isEnabled() { - $extensions = @get_loaded_extensions(); - return in_array('PDO', $extensions) && in_array('pdo_mysql', $extensions) && in_array('mysql', PDO::getAvailableDrivers()); + return extension_loaded('PDO') && extension_loaded('pdo_mysql') && in_array('mysql', PDO::getAvailableDrivers()); } /** @@ -159,6 +172,7 @@ class Mysql extends Zend_Db_Adapter_Pdo_Mysql implements AdapterInterface if (preg_match('/(?:\[|\s)([0-9]{4})(?:\]|\s)/', $e->getMessage(), $match)) { return $match[1] == $errno; } + return false; } @@ -170,9 +184,11 @@ class Mysql extends Zend_Db_Adapter_Pdo_Mysql implements AdapterInterface public function isConnectionUTF8() { $charsetInfo = $this->fetchAll('SHOW VARIABLES LIKE ?', array('character_set_connection')); + if (empty($charsetInfo)) { return false; } + $charset = $charsetInfo[0]['Value']; return $charset === 'utf8'; } @@ -230,4 +246,19 @@ class Mysql extends Zend_Db_Adapter_Pdo_Mysql implements AdapterInterface $this->cachePreparedStatement[$sql] = $stmt; return $stmt; } + + /** + * Override _dsn() to ensure host and port to not be passed along + * if unix_socket is set since setting both causes unexpected behaviour + * @see http://php.net/manual/en/ref.pdo-mysql.connection.php + */ + protected function _dsn() + { + if (!empty($this->_config['unix_socket'])) { + unset($this->_config['host']); + unset($this->_config['port']); + } + + return parent::_dsn(); + } } diff --git a/www/analytics/core/Db/Adapter/Pdo/Pgsql.php b/www/analytics/core/Db/Adapter/Pdo/Pgsql.php index b93d5191..109c881c 100644 --- a/www/analytics/core/Db/Adapter/Pdo/Pgsql.php +++ b/www/analytics/core/Db/Adapter/Pdo/Pgsql.php @@ -1,6 +1,6 @@ getServerVersion(); $requiredVersion = Config::getInstance()->General['minimum_pgsql_version']; + if (version_compare($databaseVersion, $requiredVersion) === -1) { throw new Exception(Piwik::translate('General_ExceptionDatabaseVersion', array('PostgreSQL', $databaseVersion, $requiredVersion))); } @@ -66,8 +67,7 @@ class Pgsql extends Zend_Db_Adapter_Pdo_Pgsql implements AdapterInterface */ public static function isEnabled() { - $extensions = @get_loaded_extensions(); - return in_array('PDO', $extensions) && in_array('pdo_pgsql', $extensions); + return extension_loaded('PDO') && extension_loaded('pdo_pgsql'); } /** @@ -146,6 +146,7 @@ class Pgsql extends Zend_Db_Adapter_Pdo_Pgsql implements AdapterInterface if (preg_match('/([0-9]{2}[0-9P][0-9]{2})/', $e->getMessage(), $match)) { return $match[1] == $map[$errno]; } + return false; } diff --git a/www/analytics/core/Db/AdapterInterface.php b/www/analytics/core/Db/AdapterInterface.php index 38a81a9e..cd0e4cde 100644 --- a/www/analytics/core/Db/AdapterInterface.php +++ b/www/analytics/core/Db/AdapterInterface.php @@ -1,6 +1,6 @@ General['enable_load_data_infile']; if ($loadDataInfileEnabled && Db::get()->hasBulkLoader()) { + + $path = self::getBestPathForLoadData(); + $filePath = $path . $tableName . '-' . Common::generateUniqId() . '.csv'; + try { $fileSpec = array( 'delim' => "\t", @@ -76,13 +75,9 @@ class BatchInsert }, 'eol' => "\r\n", 'null' => 'NULL', + 'charset' => $charset ); - // hack for charset mismatch - if (!DbHelper::isDatabaseConnectionUTF8() && !isset(Config::getInstance()->database['charset'])) { - $fileSpec['charset'] = 'latin1'; - } - self::createCSVFile($filePath, $fileSpec, $values); if (!is_readable($filePath)) { @@ -95,20 +90,40 @@ class BatchInsert return true; } } catch (Exception $e) { - Log::info("LOAD DATA INFILE failed or not supported, falling back to normal INSERTs... Error was: %s", $e->getMessage()); - if ($throwException) { throw $e; } } + + // if all else fails, fallback to a series of INSERTs + if (file_exists($filePath)) { + @unlink($filePath); + } } - // if all else fails, fallback to a series of INSERTs - @unlink($filePath); self::tableInsertBatchIterate($tableName, $fields, $values); + return false; } + private static function getBestPathForLoadData() + { + try { + $path = Db::fetchOne('SELECT @@secure_file_priv'); // was introduced in 5.0.38 + } catch (Exception $e) { + // we do not rethrow exception as an error is expected if MySQL is < 5.0.38 + // in this case tableInsertBatch might still work + } + + if (empty($path) || !is_dir($path) || !is_writable($path)) { + $path = StaticContainer::get('path.tmp') . '/assets/'; + } elseif (!Common::stringEndsWith($path, '/')) { + $path .= '/'; + } + + return $path; + } + /** * Batch insert into table from CSV (or other delimited) file. * @@ -124,7 +139,7 @@ class BatchInsert { // Chroot environment: prefix the path with the absolute chroot path $chrootPath = Config::getInstance()->General['absolute_chroot_path']; - if(!empty($chrootPath)) { + if (!empty($chrootPath)) { $filePath = $chrootPath . $filePath; } @@ -161,19 +176,20 @@ class BatchInsert "; /* - * First attempt: assume web server and MySQL server are on the same machine; - * this requires that the db user have the FILE privilege; however, since this is - * a global privilege, it may not be granted due to security concerns - */ + * First attempt: assume web server and MySQL server are on the same machine; + * this requires that the db user have the FILE privilege; however, since this is + * a global privilege, it may not be granted due to security concerns + */ $keywords = array(''); /* - * Second attempt: using the LOCAL keyword means the client reads the file and sends it to the server; - * the LOCAL keyword may trigger a known PHP PDO_MYSQL bug when MySQL not built with --enable-local-infile - * @see http://bugs.php.net/bug.php?id=54158 - */ + * Second attempt: using the LOCAL keyword means the client reads the file and sends it to the server; + * the LOCAL keyword may trigger a known PHP PDO\MYSQL bug when MySQL not built with --enable-local-infile + * @see http://bugs.php.net/bug.php?id=54158 + */ $openBaseDir = ini_get('open_basedir'); - $safeMode = ini_get('safe_mode'); + $safeMode = ini_get('safe_mode'); + if (empty($openBaseDir) && empty($safeMode)) { // php 5.x - LOAD DATA LOCAL INFILE is disabled if open_basedir restrictions or safe_mode enabled $keywords[] = 'LOCAL '; @@ -191,22 +207,21 @@ class BatchInsert return true; } catch (Exception $e) { -// echo $sql . ' ---- ' . $e->getMessage(); $code = $e->getCode(); $message = $e->getMessage() . ($code ? "[$code]" : ''); - if (!Db::get()->isErrNo($e, '1148')) { - Log::info("LOAD DATA INFILE failed... Error was: %s", $message); - } $exceptions[] = "\n Try #" . (count($exceptions) + 1) . ': ' . $queryStart . ": " . $message; } } + if (count($exceptions)) { - throw new Exception(implode(",", $exceptions)); + $message = "LOAD DATA INFILE failed... Error was: " . implode(",", $exceptions); + Log::info($message); + throw new Exception($message); } + return false; } - /** * Create CSV (or other delimited) files * @@ -215,13 +230,13 @@ class BatchInsert * @param array $rows Array of array corresponding to rows of values * @throws Exception if unable to create or write to file */ - static protected function createCSVFile($filePath, $fileSpec, $rows) + protected static function createCSVFile($filePath, $fileSpec, $rows) { // Set up CSV delimiters, quotes, etc $delim = $fileSpec['delim']; $quote = $fileSpec['quote']; - $eol = $fileSpec['eol']; - $null = $fileSpec['null']; + $eol = $fileSpec['eol']; + $null = $fileSpec['null']; $escapespecial_cb = $fileSpec['escapespecial_cb']; $fp = @fopen($filePath, 'wb'); @@ -248,6 +263,7 @@ class BatchInsert throw new Exception('Error writing to the tmp file ' . $filePath); } } + fclose($fp); @chmod($filePath, 0777); diff --git a/www/analytics/core/Db/Schema.php b/www/analytics/core/Db/Schema.php index b7de26b6..b973af0b 100644 --- a/www/analytics/core/Db/Schema.php +++ b/www/analytics/core/Db/Schema.php @@ -1,6 +1,6 @@ array( - self::DEFAULT_SCHEMA, - // InfiniDB - ), - - // Microsoft SQL Server -// 'MSSQL' => array( 'Mssql' ), - - // PostgreSQL -// 'PDO_PGSQL' => array( 'Pgsql' ), - - // IBM DB2 -// 'IBM' => array( 'Ibm' ), - - // Oracle -// 'OCI' => array( 'Oci' ), - ); - - $adapterName = strtoupper($adapterName); - switch ($adapterName) { - case 'PDO_MYSQL': - case 'MYSQLI': - $adapterName = 'MYSQL'; - break; - - case 'PDO_MSSQL': - case 'SQLSRV': - $adapterName = 'MSSQL'; - break; - - case 'PDO_IBM': - case 'DB2': - $adapterName = 'IBM'; - break; - - case 'PDO_OCI': - case 'ORACLE': - $adapterName = 'OCI'; - break; - } - $schemaNames = $allSchemaNames[$adapterName]; - - $schemas = array(); - - foreach ($schemaNames as $schemaName) { - $className = __NAMESPACE__ . '\\Schema\\' . $schemaName; - if (call_user_func(array($className, 'isAvailable'))) { - $schemas[] = $schemaName; - } - } - - return $schemas; - } /** * Load schema */ private function loadSchema() { - $config = Config::getInstance(); - $dbInfos = $config->database; + $config = Config::getInstance(); + $dbInfos = $config->database; $schemaName = trim($dbInfos['schema']); - $className = self::getSchemaClassName($schemaName); + $className = self::getSchemaClassName($schemaName); $this->schema = new $className(); } @@ -134,6 +71,7 @@ class Schema extends Singleton if ($this->schema === null) { $this->loadSchema(); } + return $this->schema; } @@ -233,6 +171,18 @@ class Schema extends Singleton return $this->getSchema()->getTablesInstalled($forceReload); } + /** + * Get list of installed columns in a table + * + * @param string $tableName The name of a table. + * + * @return array Installed columns indexed by the column name. + */ + public function getTableColumns($tableName) + { + return $this->getSchema()->getTableColumns($tableName); + } + /** * Returns true if Piwik tables exist * diff --git a/www/analytics/core/Db/Schema/Mysql.php b/www/analytics/core/Db/Schema/Mysql.php index bb5cc214..07cb1490 100644 --- a/www/analytics/core/Db/Schema/Mysql.php +++ b/www/analytics/core/Db/Schema/Mysql.php @@ -1,6 +1,6 @@ fetchAssoc('SHOW ENGINES'); - if (array_key_exists($engineName, $allEngines)) { - $support = $allEngines[$engineName]['Support']; - return $support == 'DEFAULT' || $support == 'YES'; - } - return false; - } - - /** - * Is this schema available? - * - * @return bool True if schema is available; false otherwise - */ - static public function isAvailable() - { - return self::hasStorageEngine('InnoDB'); - } + private $tablesInstalled = null; /** * Get the SQL to create Piwik tables @@ -58,284 +33,229 @@ class Mysql implements SchemaInterface $prefixTables = $this->getTablePrefix(); $tables = array( - 'user' => "CREATE TABLE {$prefixTables}user ( - login VARCHAR(100) NOT NULL, - password CHAR(32) NOT NULL, - alias VARCHAR(45) NOT NULL, - email VARCHAR(100) NOT NULL, - token_auth CHAR(32) NOT NULL, - superuser_access TINYINT(2) unsigned NOT NULL DEFAULT '0', - date_registered TIMESTAMP NULL, - PRIMARY KEY(login), - UNIQUE KEY uniq_keytoken(token_auth) - ) ENGINE=$engine DEFAULT CHARSET=utf8 - ", + 'user' => "CREATE TABLE {$prefixTables}user ( + login VARCHAR(100) NOT NULL, + password CHAR(32) NOT NULL, + alias VARCHAR(45) NOT NULL, + email VARCHAR(100) NOT NULL, + token_auth CHAR(32) NOT NULL, + superuser_access TINYINT(2) unsigned NOT NULL DEFAULT '0', + date_registered TIMESTAMP NULL, + PRIMARY KEY(login), + UNIQUE KEY uniq_keytoken(token_auth) + ) ENGINE=$engine DEFAULT CHARSET=utf8 + ", - 'access' => "CREATE TABLE {$prefixTables}access ( - login VARCHAR(100) NOT NULL, - idsite INTEGER UNSIGNED NOT NULL, - access VARCHAR(10) NULL, - PRIMARY KEY(login, idsite) - ) ENGINE=$engine DEFAULT CHARSET=utf8 - ", + 'access' => "CREATE TABLE {$prefixTables}access ( + login VARCHAR(100) NOT NULL, + idsite INTEGER UNSIGNED NOT NULL, + access VARCHAR(10) NULL, + PRIMARY KEY(login, idsite) + ) ENGINE=$engine DEFAULT CHARSET=utf8 + ", - 'site' => "CREATE TABLE {$prefixTables}site ( - idsite INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT, - name VARCHAR(90) NOT NULL, - main_url VARCHAR(255) NOT NULL, - ts_created TIMESTAMP NULL, - ecommerce TINYINT DEFAULT 0, - sitesearch TINYINT DEFAULT 1, - sitesearch_keyword_parameters TEXT NOT NULL, - sitesearch_category_parameters TEXT NOT NULL, - timezone VARCHAR( 50 ) NOT NULL, - currency CHAR( 3 ) NOT NULL, - excluded_ips TEXT NOT NULL, - excluded_parameters TEXT NOT NULL, - excluded_user_agents TEXT NOT NULL, - `group` VARCHAR(250) NOT NULL, - `type` VARCHAR(255) NOT NULL, - keep_url_fragment TINYINT NOT NULL DEFAULT 0, - PRIMARY KEY(idsite) - ) ENGINE=$engine DEFAULT CHARSET=utf8 - ", + 'site' => "CREATE TABLE {$prefixTables}site ( + idsite INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT, + name VARCHAR(90) NOT NULL, + main_url VARCHAR(255) NOT NULL, + ts_created TIMESTAMP NULL, + ecommerce TINYINT DEFAULT 0, + sitesearch TINYINT DEFAULT 1, + sitesearch_keyword_parameters TEXT NOT NULL, + sitesearch_category_parameters TEXT NOT NULL, + timezone VARCHAR( 50 ) NOT NULL, + currency CHAR( 3 ) NOT NULL, + exclude_unknown_urls TINYINT(1) DEFAULT 0, + excluded_ips TEXT NOT NULL, + excluded_parameters TEXT NOT NULL, + excluded_user_agents TEXT NOT NULL, + `group` VARCHAR(250) NOT NULL, + `type` VARCHAR(255) NOT NULL, + keep_url_fragment TINYINT NOT NULL DEFAULT 0, + PRIMARY KEY(idsite) + ) ENGINE=$engine DEFAULT CHARSET=utf8 + ", - 'site_url' => "CREATE TABLE {$prefixTables}site_url ( - idsite INTEGER(10) UNSIGNED NOT NULL, - url VARCHAR(255) NOT NULL, - PRIMARY KEY(idsite, url) - ) ENGINE=$engine DEFAULT CHARSET=utf8 - ", + 'site_setting' => "CREATE TABLE {$prefixTables}site_setting ( + idsite INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `setting_name` VARCHAR(255) NOT NULL, + `setting_value` LONGTEXT NOT NULL, + PRIMARY KEY(idsite, setting_name) + ) ENGINE=$engine DEFAULT CHARSET=utf8 + ", - 'goal' => " CREATE TABLE `{$prefixTables}goal` ( - `idsite` int(11) NOT NULL, - `idgoal` int(11) NOT NULL, - `name` varchar(50) NOT NULL, - `match_attribute` varchar(20) NOT NULL, - `pattern` varchar(255) NOT NULL, - `pattern_type` varchar(10) NOT NULL, - `case_sensitive` tinyint(4) NOT NULL, - `allow_multiple` tinyint(4) NOT NULL, - `revenue` float NOT NULL, - `deleted` tinyint(4) NOT NULL default '0', - PRIMARY KEY (`idsite`,`idgoal`) - ) ENGINE=$engine DEFAULT CHARSET=utf8 - ", + 'site_url' => "CREATE TABLE {$prefixTables}site_url ( + idsite INTEGER(10) UNSIGNED NOT NULL, + url VARCHAR(255) NOT NULL, + PRIMARY KEY(idsite, url) + ) ENGINE=$engine DEFAULT CHARSET=utf8 + ", - 'logger_message' => "CREATE TABLE {$prefixTables}logger_message ( - idlogger_message INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, + 'goal' => "CREATE TABLE `{$prefixTables}goal` ( + `idsite` int(11) NOT NULL, + `idgoal` int(11) NOT NULL, + `name` varchar(50) NOT NULL, + `match_attribute` varchar(20) NOT NULL, + `pattern` varchar(255) NOT NULL, + `pattern_type` varchar(10) NOT NULL, + `case_sensitive` tinyint(4) NOT NULL, + `allow_multiple` tinyint(4) NOT NULL, + `revenue` float NOT NULL, + `deleted` tinyint(4) NOT NULL default '0', + PRIMARY KEY (`idsite`,`idgoal`) + ) ENGINE=$engine DEFAULT CHARSET=utf8 + ", + + 'logger_message' => "CREATE TABLE {$prefixTables}logger_message ( + idlogger_message INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, tag VARCHAR(50) NULL, - timestamp TIMESTAMP NULL, + timestamp TIMESTAMP NULL, level VARCHAR(16) NULL, - message TEXT NULL, - PRIMARY KEY(idlogger_message) - ) ENGINE=$engine DEFAULT CHARSET=utf8 - ", + message TEXT NULL, + PRIMARY KEY(idlogger_message) + ) ENGINE=$engine DEFAULT CHARSET=utf8 + ", + 'log_action' => "CREATE TABLE {$prefixTables}log_action ( + idaction INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT, + name TEXT, + hash INTEGER(10) UNSIGNED NOT NULL, + type TINYINT UNSIGNED NULL, + url_prefix TINYINT(2) NULL, + PRIMARY KEY(idaction), + INDEX index_type_hash (type, hash) + ) ENGINE=$engine DEFAULT CHARSET=utf8 + ", - 'log_action' => "CREATE TABLE {$prefixTables}log_action ( - idaction INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT, - name TEXT, - hash INTEGER(10) UNSIGNED NOT NULL, - type TINYINT UNSIGNED NULL, - url_prefix TINYINT(2) NULL, - PRIMARY KEY(idaction), - INDEX index_type_hash (type, hash) - ) ENGINE=$engine DEFAULT CHARSET=utf8 - ", - - 'log_visit' => "CREATE TABLE {$prefixTables}log_visit ( - idvisit INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT, - idsite INTEGER(10) UNSIGNED NOT NULL, - idvisitor BINARY(8) NOT NULL, - visitor_localtime TIME NOT NULL, - visitor_returning TINYINT(1) NOT NULL, - visitor_count_visits SMALLINT(5) UNSIGNED NOT NULL, - visitor_days_since_last SMALLINT(5) UNSIGNED NOT NULL, - visitor_days_since_order SMALLINT(5) UNSIGNED NOT NULL, - visitor_days_since_first SMALLINT(5) UNSIGNED NOT NULL, - visit_first_action_time DATETIME NOT NULL, - visit_last_action_time DATETIME NOT NULL, - visit_exit_idaction_url INTEGER(11) UNSIGNED NULL DEFAULT 0, - visit_exit_idaction_name INTEGER(11) UNSIGNED NOT NULL, - visit_entry_idaction_url INTEGER(11) UNSIGNED NOT NULL, - visit_entry_idaction_name INTEGER(11) UNSIGNED NOT NULL, - visit_total_actions SMALLINT(5) UNSIGNED NOT NULL, - visit_total_searches SMALLINT(5) UNSIGNED NOT NULL, - visit_total_events SMALLINT(5) UNSIGNED NOT NULL, - visit_total_time SMALLINT(5) UNSIGNED NOT NULL, - visit_goal_converted TINYINT(1) NOT NULL, - visit_goal_buyer TINYINT(1) NOT NULL, - referer_type TINYINT(1) UNSIGNED NULL, - referer_name VARCHAR(70) NULL, - referer_url TEXT NOT NULL, - referer_keyword VARCHAR(255) NULL, - config_id BINARY(8) NOT NULL, - config_os CHAR(3) NOT NULL, - config_browser_name VARCHAR(10) NOT NULL, - config_browser_version VARCHAR(20) NOT NULL, - config_resolution VARCHAR(9) NOT NULL, - config_pdf TINYINT(1) NOT NULL, - config_flash TINYINT(1) NOT NULL, - config_java TINYINT(1) NOT NULL, - config_director TINYINT(1) NOT NULL, - config_quicktime TINYINT(1) NOT NULL, - config_realplayer TINYINT(1) NOT NULL, - config_windowsmedia TINYINT(1) NOT NULL, - config_gears TINYINT(1) NOT NULL, - config_silverlight TINYINT(1) NOT NULL, - config_cookie TINYINT(1) NOT NULL, - location_ip VARBINARY(16) NOT NULL, - location_browser_lang VARCHAR(20) NOT NULL, - location_country CHAR(3) NOT NULL, - location_region char(2) DEFAULT NULL, - location_city varchar(255) DEFAULT NULL, - location_latitude float(10, 6) DEFAULT NULL, - location_longitude float(10, 6) DEFAULT NULL, - PRIMARY KEY(idvisit), - INDEX index_idsite_config_datetime (idsite, config_id, visit_last_action_time), - INDEX index_idsite_datetime (idsite, visit_last_action_time), - INDEX index_idsite_idvisitor (idsite, idvisitor) - ) ENGINE=$engine DEFAULT CHARSET=utf8 - ", + 'log_visit' => "CREATE TABLE {$prefixTables}log_visit ( + idvisit INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT, + idsite INTEGER(10) UNSIGNED NOT NULL, + idvisitor BINARY(8) NOT NULL, + visit_last_action_time DATETIME NOT NULL, + config_id BINARY(8) NOT NULL, + location_ip VARBINARY(16) NOT NULL, + PRIMARY KEY(idvisit), + INDEX index_idsite_config_datetime (idsite, config_id, visit_last_action_time), + INDEX index_idsite_datetime (idsite, visit_last_action_time), + INDEX index_idsite_idvisitor (idsite, idvisitor) + ) ENGINE=$engine DEFAULT CHARSET=utf8 + ", 'log_conversion_item' => "CREATE TABLE `{$prefixTables}log_conversion_item` ( - idsite int(10) UNSIGNED NOT NULL, - idvisitor BINARY(8) NOT NULL, - server_time DATETIME NOT NULL, - idvisit INTEGER(10) UNSIGNED NOT NULL, - idorder varchar(100) NOT NULL, + idsite int(10) UNSIGNED NOT NULL, + idvisitor BINARY(8) NOT NULL, + server_time DATETIME NOT NULL, + idvisit INTEGER(10) UNSIGNED NOT NULL, + idorder varchar(100) NOT NULL, + idaction_sku INTEGER(10) UNSIGNED NOT NULL, + idaction_name INTEGER(10) UNSIGNED NOT NULL, + idaction_category INTEGER(10) UNSIGNED NOT NULL, + idaction_category2 INTEGER(10) UNSIGNED NOT NULL, + idaction_category3 INTEGER(10) UNSIGNED NOT NULL, + idaction_category4 INTEGER(10) UNSIGNED NOT NULL, + idaction_category5 INTEGER(10) UNSIGNED NOT NULL, + price FLOAT NOT NULL, + quantity INTEGER(10) UNSIGNED NOT NULL, + deleted TINYINT(1) UNSIGNED NOT NULL, + PRIMARY KEY(idvisit, idorder, idaction_sku), + INDEX index_idsite_servertime ( idsite, server_time ) + ) ENGINE=$engine DEFAULT CHARSET=utf8 + ", - idaction_sku INTEGER(10) UNSIGNED NOT NULL, - idaction_name INTEGER(10) UNSIGNED NOT NULL, - idaction_category INTEGER(10) UNSIGNED NOT NULL, - idaction_category2 INTEGER(10) UNSIGNED NOT NULL, - idaction_category3 INTEGER(10) UNSIGNED NOT NULL, - idaction_category4 INTEGER(10) UNSIGNED NOT NULL, - idaction_category5 INTEGER(10) UNSIGNED NOT NULL, - price FLOAT NOT NULL, - quantity INTEGER(10) UNSIGNED NOT NULL, - deleted TINYINT(1) UNSIGNED NOT NULL, - - PRIMARY KEY(idvisit, idorder, idaction_sku), - INDEX index_idsite_servertime ( idsite, server_time ) - ) ENGINE=$engine DEFAULT CHARSET=utf8 - ", - - 'log_conversion' => "CREATE TABLE `{$prefixTables}log_conversion` ( - idvisit int(10) unsigned NOT NULL, - idsite int(10) unsigned NOT NULL, - idvisitor BINARY(8) NOT NULL, - server_time datetime NOT NULL, - idaction_url int(11) default NULL, - idlink_va int(11) default NULL, - referer_visit_server_date date default NULL, - referer_type int(10) unsigned default NULL, - referer_name varchar(70) default NULL, - referer_keyword varchar(255) default NULL, - visitor_returning tinyint(1) NOT NULL, - visitor_count_visits SMALLINT(5) UNSIGNED NOT NULL, - visitor_days_since_first SMALLINT(5) UNSIGNED NOT NULL, - visitor_days_since_order SMALLINT(5) UNSIGNED NOT NULL, - location_country char(3) NOT NULL, - location_region char(2) DEFAULT NULL, - location_city varchar(255) DEFAULT NULL, - location_latitude float(10, 6) DEFAULT NULL, - location_longitude float(10, 6) DEFAULT NULL, - url text NOT NULL, - idgoal int(10) NOT NULL, - buster int unsigned NOT NULL, - - idorder varchar(100) default NULL, - items SMALLINT UNSIGNED DEFAULT NULL, - revenue float default NULL, - revenue_subtotal float default NULL, - revenue_tax float default NULL, - revenue_shipping float default NULL, - revenue_discount float default NULL, - - PRIMARY KEY (idvisit, idgoal, buster), - UNIQUE KEY unique_idsite_idorder (idsite, idorder), - INDEX index_idsite_datetime ( idsite, server_time ) - ) ENGINE=$engine DEFAULT CHARSET=utf8 - ", + 'log_conversion' => "CREATE TABLE `{$prefixTables}log_conversion` ( + idvisit int(10) unsigned NOT NULL, + idsite int(10) unsigned NOT NULL, + idvisitor BINARY(8) NOT NULL, + server_time datetime NOT NULL, + idaction_url int(11) default NULL, + idlink_va int(11) default NULL, + idgoal int(10) NOT NULL, + buster int unsigned NOT NULL, + idorder varchar(100) default NULL, + items SMALLINT UNSIGNED DEFAULT NULL, + url text NOT NULL, + PRIMARY KEY (idvisit, idgoal, buster), + UNIQUE KEY unique_idsite_idorder (idsite, idorder), + INDEX index_idsite_datetime ( idsite, server_time ) + ) ENGINE=$engine DEFAULT CHARSET=utf8 + ", 'log_link_visit_action' => "CREATE TABLE {$prefixTables}log_link_visit_action ( - idlink_va INTEGER(11) UNSIGNED NOT NULL AUTO_INCREMENT, - idsite int(10) UNSIGNED NOT NULL, - idvisitor BINARY(8) NOT NULL, - server_time DATETIME NOT NULL, - idvisit INTEGER(10) UNSIGNED NOT NULL, - idaction_url INTEGER(10) UNSIGNED DEFAULT NULL, - idaction_url_ref INTEGER(10) UNSIGNED NULL DEFAULT 0, - idaction_name INTEGER(10) UNSIGNED, - idaction_name_ref INTEGER(10) UNSIGNED NOT NULL, - idaction_event_category INTEGER(10) UNSIGNED DEFAULT NULL, - idaction_event_action INTEGER(10) UNSIGNED DEFAULT NULL, - time_spent_ref_action INTEGER(10) UNSIGNED NOT NULL, + idlink_va INTEGER(11) UNSIGNED NOT NULL AUTO_INCREMENT, + idsite int(10) UNSIGNED NOT NULL, + idvisitor BINARY(8) NOT NULL, + idvisit INTEGER(10) UNSIGNED NOT NULL, + idaction_url_ref INTEGER(10) UNSIGNED NULL DEFAULT 0, + idaction_name_ref INTEGER(10) UNSIGNED NOT NULL, + custom_float FLOAT NULL DEFAULT NULL, + PRIMARY KEY(idlink_va), + INDEX index_idvisit(idvisit) + ) ENGINE=$engine DEFAULT CHARSET=utf8 + ", - custom_float FLOAT NULL DEFAULT NULL, - PRIMARY KEY(idlink_va), - INDEX index_idvisit(idvisit), - INDEX index_idsite_servertime ( idsite, server_time ) - ) ENGINE=$engine DEFAULT CHARSET=utf8 - ", + 'log_profiling' => "CREATE TABLE {$prefixTables}log_profiling ( + query TEXT NOT NULL, + count INTEGER UNSIGNED NULL, + sum_time_ms FLOAT NULL, + UNIQUE KEY query(query(100)) + ) ENGINE=$engine DEFAULT CHARSET=utf8 + ", - 'log_profiling' => "CREATE TABLE {$prefixTables}log_profiling ( - query TEXT NOT NULL, - count INTEGER UNSIGNED NULL, - sum_time_ms FLOAT NULL, - UNIQUE KEY query(query(100)) - ) ENGINE=$engine DEFAULT CHARSET=utf8 - ", + 'option' => "CREATE TABLE `{$prefixTables}option` ( + option_name VARCHAR( 255 ) NOT NULL, + option_value LONGTEXT NOT NULL, + autoload TINYINT NOT NULL DEFAULT '1', + PRIMARY KEY ( option_name ), + INDEX autoload( autoload ) + ) ENGINE=$engine DEFAULT CHARSET=utf8 + ", - 'option' => "CREATE TABLE `{$prefixTables}option` ( - option_name VARCHAR( 255 ) NOT NULL, - option_value LONGTEXT NOT NULL, - autoload TINYINT NOT NULL DEFAULT '1', - PRIMARY KEY ( option_name ), - INDEX autoload( autoload ) - ) ENGINE=$engine DEFAULT CHARSET=utf8 - ", + 'session' => "CREATE TABLE {$prefixTables}session ( + id VARCHAR( 255 ) NOT NULL, + modified INTEGER, + lifetime INTEGER, + data TEXT, + PRIMARY KEY ( id ) + ) ENGINE=$engine DEFAULT CHARSET=utf8 + ", - 'session' => "CREATE TABLE {$prefixTables}session ( - id CHAR(32) NOT NULL, - modified INTEGER, - lifetime INTEGER, - data TEXT, - PRIMARY KEY ( id ) - ) ENGINE=$engine DEFAULT CHARSET=utf8 - ", + 'archive_numeric' => "CREATE TABLE {$prefixTables}archive_numeric ( + idarchive INTEGER UNSIGNED NOT NULL, + name VARCHAR(255) NOT NULL, + idsite INTEGER UNSIGNED NULL, + date1 DATE NULL, + date2 DATE NULL, + period TINYINT UNSIGNED NULL, + ts_archived DATETIME NULL, + value DOUBLE NULL, + PRIMARY KEY(idarchive, name), + INDEX index_idsite_dates_period(idsite, date1, date2, period, ts_archived), + INDEX index_period_archived(period, ts_archived) + ) ENGINE=$engine DEFAULT CHARSET=utf8 + ", - 'archive_numeric' => "CREATE TABLE {$prefixTables}archive_numeric ( - idarchive INTEGER UNSIGNED NOT NULL, - name VARCHAR(255) NOT NULL, - idsite INTEGER UNSIGNED NULL, - date1 DATE NULL, - date2 DATE NULL, - period TINYINT UNSIGNED NULL, - ts_archived DATETIME NULL, - value DOUBLE NULL, - PRIMARY KEY(idarchive, name), - INDEX index_idsite_dates_period(idsite, date1, date2, period, ts_archived), - INDEX index_period_archived(period, ts_archived) - ) ENGINE=$engine DEFAULT CHARSET=utf8 - ", + 'archive_blob' => "CREATE TABLE {$prefixTables}archive_blob ( + idarchive INTEGER UNSIGNED NOT NULL, + name VARCHAR(255) NOT NULL, + idsite INTEGER UNSIGNED NULL, + date1 DATE NULL, + date2 DATE NULL, + period TINYINT UNSIGNED NULL, + ts_archived DATETIME NULL, + value MEDIUMBLOB NULL, + PRIMARY KEY(idarchive, name), + INDEX index_period_archived(period, ts_archived) + ) ENGINE=$engine DEFAULT CHARSET=utf8 + ", - 'archive_blob' => "CREATE TABLE {$prefixTables}archive_blob ( - idarchive INTEGER UNSIGNED NOT NULL, - name VARCHAR(255) NOT NULL, - idsite INTEGER UNSIGNED NULL, - date1 DATE NULL, - date2 DATE NULL, - period TINYINT UNSIGNED NULL, - ts_archived DATETIME NULL, - value MEDIUMBLOB NULL, - PRIMARY KEY(idarchive, name), - INDEX index_period_archived(period, ts_archived) - ) ENGINE=$engine DEFAULT CHARSET=utf8 - ", + 'sequence' => "CREATE TABLE {$prefixTables}sequence ( + `name` VARCHAR(120) NOT NULL, + `value` BIGINT(20) UNSIGNED NOT NULL , + PRIMARY KEY(`name`) + ) ENGINE=$engine DEFAULT CHARSET=utf8 + ", ); + return $tables; } @@ -365,16 +285,37 @@ class Mysql implements SchemaInterface */ public function getTablesNames() { - $aTables = array_keys($this->getTablesCreateSql()); + $aTables = array_keys($this->getTablesCreateSql()); $prefixTables = $this->getTablePrefix(); + $return = array(); foreach ($aTables as $table) { $return[] = $prefixTables . $table; } + return $return; } - private $tablesInstalled = null; + /** + * Get list of installed columns in a table + * + * @param string $tableName The name of a table. + * + * @return array Installed columns indexed by the column name. + */ + public function getTableColumns($tableName) + { + $db = $this->getDb(); + + $allColumns = $db->fetchAll("SHOW COLUMNS FROM . $tableName"); + + $fields = array(); + foreach ($allColumns as $column) { + $fields[trim($column['Field'])] = $column; + } + + return $fields; + } /** * Get list of tables installed @@ -387,13 +328,10 @@ class Mysql implements SchemaInterface if (is_null($this->tablesInstalled) || $forceReload === true ) { - $db = Db::get(); - $prefixTables = $this->getTablePrefix(); + $db = $this->getDb(); + $prefixTables = $this->getTablePrefixEscaped(); - // '_' matches any character; force it to be literal - $prefixTables = str_replace('_', '\_', $prefixTables); - - $allTables = $db->fetchCol("SHOW TABLES LIKE '" . $prefixTables . "%'"); + $allTables = $this->getAllExistingTables($prefixTables); // all the tables to be installed $allMyTables = $this->getTablesNames(); @@ -403,12 +341,13 @@ class Mysql implements SchemaInterface // at this point we have the static list of core tables, but let's add the monthly archive tables $allArchiveNumeric = $db->fetchCol("SHOW TABLES LIKE '" . $prefixTables . "archive_numeric%'"); - $allArchiveBlob = $db->fetchCol("SHOW TABLES LIKE '" . $prefixTables . "archive_blob%'"); + $allArchiveBlob = $db->fetchCol("SHOW TABLES LIKE '" . $prefixTables . "archive_blob%'"); $allTablesReallyInstalled = array_merge($tablesInstalled, $allArchiveNumeric, $allArchiveBlob); $this->tablesInstalled = $allTablesReallyInstalled; } + return $this->tablesInstalled; } @@ -432,6 +371,7 @@ class Mysql implements SchemaInterface if (is_null($dbName)) { $dbName = $this->getDbName(); } + Db::exec("CREATE DATABASE IF NOT EXISTS " . $dbName . " DEFAULT CHARACTER SET utf8"); } @@ -454,8 +394,8 @@ class Mysql implements SchemaInterface Db::exec($statement); } catch (Exception $e) { // mysql code error 1050:table already exists - // see bug #153 http://dev.piwik.org/trac/ticket/153 - if (!Db::get()->isErrNo($e, '1050')) { + // see bug #153 https://github.com/piwik/piwik/issues/153 + if (!$this->getDb()->isErrNo($e, '1050')) { throw $e; } } @@ -475,7 +415,7 @@ class Mysql implements SchemaInterface */ public function createTables() { - $db = Db::get(); + $db = $this->getDb(); $prefixTables = $this->getTablePrefix(); $tablesAlreadyInstalled = $this->getTablesInstalled(); @@ -498,9 +438,9 @@ class Mysql implements SchemaInterface { // The anonymous user is the user that is assigned by default // note that the token_auth value is anonymous, which is assigned by default as well in the Login plugin - $db = Db::get(); + $db = $this->getDb(); $db->query("INSERT IGNORE INTO " . Common::prefixTable("user") . " - VALUES ( 'anonymous', '', 'anonymous', 'anonymous@example.org', 'anonymous', 0, '" . Date::factory('now')->getDatetime() . "' );"); + VALUES ( 'anonymous', '', 'anonymous', 'anonymous@example.org', 'anonymous', 0, '" . Date::factory('now')->getDatetime() . "' );"); } /** @@ -508,32 +448,51 @@ class Mysql implements SchemaInterface */ public function truncateAllTables() { - $tablesAlreadyInstalled = $this->getTablesInstalled($forceReload = true); - foreach ($tablesAlreadyInstalled as $table) { + $tables = $this->getAllExistingTables(); + foreach ($tables as $table) { Db::query("TRUNCATE `$table`"); } } private function getTablePrefix() { - $dbInfos = Db::getDatabaseConfig(); - $prefixTables = $dbInfos['tables_prefix']; - - return $prefixTables; + return $this->getDbSettings()->getTablePrefix(); } private function getTableEngine() { - $dbInfos = Db::getDatabaseConfig(); - $engine = $dbInfos['type']; - return $engine; + return $this->getDbSettings()->getEngine(); + } + + private function getDb() + { + return Db::get(); + } + + private function getDbSettings() + { + return new Db\Settings(); } private function getDbName() { - $dbInfos = Db::getDatabaseConfig(); - $dbName = $dbInfos['dbname']; + return $this->getDbSettings()->getDbName(); + } - return $dbName; + private function getAllExistingTables($prefixTables = false) + { + if (empty($prefixTables)) { + $prefixTables = $this->getTablePrefixEscaped(); + } + + return Db::get()->fetchCol("SHOW TABLES LIKE '" . $prefixTables . "%'"); + } + + private function getTablePrefixEscaped() + { + $prefixTables = $this->getTablePrefix(); + // '_' matches any character; force it to be literal + $prefixTables = str_replace('_', '\_', $prefixTables); + return $prefixTables; } } diff --git a/www/analytics/core/Db/SchemaInterface.php b/www/analytics/core/Db/SchemaInterface.php index 71bc1b7d..8c22bb68 100644 --- a/www/analytics/core/Db/SchemaInterface.php +++ b/www/analytics/core/Db/SchemaInterface.php @@ -1,6 +1,6 @@ getDbSetting('type'); + } + + public function getTablePrefix() + { + return $this->getDbSetting('tables_prefix'); + } + + public function getDbName() + { + return $this->getDbSetting('dbname'); + } + + private function getDbSetting($key) + { + $dbInfos = Db::getDatabaseConfig(); + $engine = $dbInfos[$key]; + + return $engine; + } +} diff --git a/www/analytics/core/DbHelper.php b/www/analytics/core/DbHelper.php index 761bce26..25b1f564 100644 --- a/www/analytics/core/DbHelper.php +++ b/www/analytics/core/DbHelper.php @@ -1,6 +1,6 @@ getTablesInstalled($forceReload); } + /** + * Get list of installed columns in a table + * + * @param string $tableName The name of a table. + * + * @return array Installed columns indexed by the column name. + */ + public static function getTableColumns($tableName) + { + return Schema::getInstance()->getTableColumns($tableName); + } + /** * Creates a new table in the database. * @@ -153,7 +165,7 @@ class DbHelper /** * Get the SQL to create a specific Piwik table * - * @param string $tableName + * @param string $tableName Unprefixed table name. * @return string SQL */ public static function getTableCreateSql($tableName) @@ -161,4 +173,17 @@ class DbHelper return Schema::getInstance()->getTableCreateSql($tableName); } + /** + * Deletes archive tables. For use in tests. + */ + public static function deleteArchiveTables() + { + foreach (ArchiveTableCreator::getTablesArchivesInstalled() as $table) { + Log::debug("Dropping table $table"); + + Db::query("DROP TABLE IF EXISTS `$table`"); + } + + ArchiveTableCreator::refreshTableList($forceReload = true); + } } diff --git a/www/analytics/core/Development.php b/www/analytics/core/Development.php new file mode 100644 index 00000000..3b44eba7 --- /dev/null +++ b/www/analytics/core/Development.php @@ -0,0 +1,196 @@ +Development['enabled']; + } + + return self::$isEnabled; + } + + /** + * Verifies whether a className of object implements the given method. It does not check whether the given method + * is actually callable (public). + * + * @param string|object $classOrObject + * @param string $method + * + * @return bool true if the method exists, false otherwise. + */ + public static function methodExists($classOrObject, $method) + { + if (is_string($classOrObject)) { + return class_exists($classOrObject) && method_exists($classOrObject, $method); + } + + return method_exists($classOrObject, $method); + } + + /** + * Formats a method call depending on the given class/object and method name. It does not perform any checks whether + * does actually exists. + * + * @param string|object $classOrObject + * @param string $method + * + * @return string Formatted method call. Example: "MyNamespace\MyClassname::methodName()" + */ + public static function formatMethodCall($classOrObject, $method) + { + if (is_object($classOrObject)) { + $classOrObject = get_class($classOrObject); + } + + return $classOrObject . '::' . $method . '()'; + } + + /** + * Checks whether the given method is actually callable on the given class/object if the development mode is + * enabled. En error will be triggered if the method does not exist or is not callable (public) containing a useful + * error message for the developer. + * + * @param string|object $classOrObject + * @param string $method + * @param string $prefixMessageIfError You can prepend any string to the error message in case the method is not + * callable. + */ + public static function checkMethodIsCallable($classOrObject, $method, $prefixMessageIfError) + { + if (!self::isEnabled()) { + return; + } + + self::checkMethodExists($classOrObject, $method, $prefixMessageIfError); + + if (!self::isCallableMethod($classOrObject, $method)) { + self::error($prefixMessageIfError . ' "' . self::formatMethodCall($classOrObject, $method) . '" is not callable. Please make sure to method is public'); + } + } + + /** + * Checks whether the given method is actually callable on the given class/object if the development mode is + * enabled. En error will be triggered if the method does not exist or is not callable (public) containing a useful + * error message for the developer. + * + * @param string|object $classOrObject + * @param string $method + * @param string $prefixMessageIfError You can prepend any string to the error message in case the method is not + * callable. + */ + public static function checkMethodExists($classOrObject, $method, $prefixMessageIfError) + { + if (!self::isEnabled()) { + return; + } + + if (!self::methodExists($classOrObject, $method)) { + self::error($prefixMessageIfError . ' "' . self::formatMethodCall($classOrObject, $method) . '" does not exist. Please make sure to define such a method.'); + } + } + + /** + * Verify whether the given method actually exists and is callable (public). + * + * @param string|object $classOrObject + * @param string $method + * @return bool + */ + public static function isCallableMethod($classOrObject, $method) + { + if (!self::methodExists($classOrObject, $method)) { + return false; + } + + $reflection = new \ReflectionMethod($classOrObject, $method); + return $reflection->isPublic(); + } + + /** + * Triggers an error if the development mode is enabled. Depending on the current environment / mode it will either + * log the given message or throw an exception to make sure it will be displayed in the Piwik UI. + * + * @param string $message + * @throws Exception + */ + public static function error($message) + { + if (!self::isEnabled()) { + return; + } + + $message .= ' (This error is only shown in development mode)'; + + if (SettingsServer::isTrackerApiRequest() + || Common::isPhpCliMode()) { + Log::error($message); + } else { + throw new Exception($message); + } + } + + public static function getMethodSourceCode($className, $methodName) + { + $method = new \ReflectionMethod($className, $methodName); + + $file = new \SplFileObject($method->getFileName()); + $offset = $method->getStartLine() - 1; + $count = $method->getEndLine() - $method->getStartLine() + 1; + + $fileIterator = new \LimitIterator($file, $offset, $count); + + $methodCode = "\n " . $method->getDocComment() . "\n"; + foreach ($fileIterator as $line) { + $methodCode .= $line; + } + $methodCode .= "\n"; + + return $methodCode; + } + + public static function getUseStatements($className) + { + $class = new \ReflectionClass($className); + + $file = new \SplFileObject($class->getFileName()); + + $fileIterator = new \LimitIterator($file, 0, $class->getStartLine()); + + $uses = array(); + foreach ($fileIterator as $line) { + if (preg_match('/(\s*)use (.+)/', $line, $match)) { + $uses[] = trim($match[2]); + } + } + + return $uses; + } +} diff --git a/www/analytics/core/DeviceDetectorCache.php b/www/analytics/core/DeviceDetectorCache.php new file mode 100644 index 00000000..543769fc --- /dev/null +++ b/www/analytics/core/DeviceDetectorCache.php @@ -0,0 +1,95 @@ +ttl = (int) $ttl; + $this->cache = PiwikCache::getEagerCache(); + } + + /** + * Function to fetch a cache entry + * + * @param string $id The cache entry ID + * @return array|bool False on error, or array the cache content + */ + public function fetch($id) + { + if (empty($id)) { + return false; + } + + if (array_key_exists($id, self::$staticCache)) { + return self::$staticCache[$id]; + } + + if (!$this->cache->contains($id)) { + return false; + } + + return $this->cache->fetch($id); + } + + /** + * A function to store content a cache entry. + * + * @param string $id The cache entry ID + * @param array $content The cache content + * @throws \Exception + * @return bool True if the entry was succesfully stored + */ + public function save($id, $content, $ttl=0) + { + if (empty($id)) { + return false; + } + + self::$staticCache[$id] = $content; + + return $this->cache->save($id, $content, $this->ttl); + } + + public function contains($id) + { + return !empty(self::$staticCache[$id]) && $this->cache->contains($id); + } + + public function delete($id) + { + if (empty($id)) { + return false; + } + + unset(self::$staticCache[$id]); + + return $this->cache->delete($id); + } + + public function flushAll() + { + return $this->cache->flushAll(); + } +} diff --git a/www/analytics/core/DeviceDetectorFactory.php b/www/analytics/core/DeviceDetectorFactory.php new file mode 100644 index 00000000..896132e7 --- /dev/null +++ b/www/analytics/core/DeviceDetectorFactory.php @@ -0,0 +1,37 @@ +discardBotInformation(); + $deviceDetector->setCache(new DeviceDetectorCache(86400)); + $deviceDetector->parse(); + + self::$deviceDetectorInstances[$userAgent] = $deviceDetector; + + return $deviceDetector; + } +} diff --git a/www/analytics/core/Error.php b/www/analytics/core/Error.php deleted file mode 100644 index 486ee7d5..00000000 --- a/www/analytics/core/Error.php +++ /dev/null @@ -1,222 +0,0 @@ -errno = $errno; - $this->errstr = $errstr; - $this->errfile = $errfile; - $this->errline = $errline; - $this->backtrace = $backtrace; - } - - public function getErrNoString() - { - switch ($this->errno) { - case E_ERROR: - return "Error"; - case E_WARNING: - return "Warning"; - case E_PARSE: - return "Parse Error"; - case E_NOTICE: - return "Notice"; - case E_CORE_ERROR: - return "Core Error"; - case E_CORE_WARNING: - return "Core Warning"; - case E_COMPILE_ERROR: - return "Compile Error"; - case E_COMPILE_WARNING: - return "Compile Warning"; - case E_USER_ERROR: - return "User Error"; - case E_USER_WARNING: - return "User Warning"; - case E_USER_NOTICE: - return "User Notice"; - case E_STRICT: - return "Strict Notice"; - case E_RECOVERABLE_ERROR: - return "Recoverable Error"; - case E_DEPRECATED: - return "Deprecated"; - case E_USER_DEPRECATED: - return "User Deprecated"; - default: - return "Unknown error ({$this->errno})"; - } - } - - public static function formatFileAndDBLogMessage(&$message, $level, $tag, $datetime, $log) - { - if ($message instanceof Error) { - $message = $message->errfile . '(' . $message->errline . '): ' . $message->getErrNoString() - . ' - ' . $message->errstr . "\n" . $message->backtrace; - - $message = $log->formatMessage($level, $tag, $datetime, $message); - } - } - - public static function formatScreenMessage(&$message, $level, $tag, $datetime, $log) - { - if ($message instanceof Error) { - $errno = $message->errno & error_reporting(); - - // problem when using error_reporting with the @ silent fail operator - // it gives an errno 0, and in this case the objective is to NOT display anything on the screen! - // is there any other case where the errno is zero at this point? - if ($errno == 0) { - $message = false; - return; - } - - if (!Common::isPhpCliMode()) { - @header('Content-Type: text/html; charset=utf-8'); - } - - $htmlString = ''; - $htmlString .= "\n
- There is an error. Please report the message (Piwik " . (class_exists('Piwik\Version') ? Version::VERSION : '') . ") - and full backtrace in the Piwik forums (please do a Search first as it might have been reported already!).

- "; - $htmlString .= $message->getErrNoString(); - $htmlString .= ":
{$message->errstr} in {$message->errfile}"; - $htmlString .= " on line {$message->errline}\n"; - $htmlString .= "

Backtrace -->

\n"; - $htmlString .= str_replace("\n", "
\n", $message->backtrace); - $htmlString .= "

"; - $htmlString .= "\n

"; - - $message = $htmlString; - } - } - - public static function setErrorHandler() - { - Piwik::addAction('Log.formatFileMessage', array('\\Piwik\\Error', 'formatFileAndDBLogMessage')); - Piwik::addAction('Log.formatDatabaseMessage', array('\\Piwik\\Error', 'formatFileAndDBLogMessage')); - Piwik::addAction('Log.formatScreenMessage', array('\\Piwik\\Error', 'formatScreenMessage')); - - set_error_handler(array('\\Piwik\\Error', 'errorHandler')); - } - - public static function errorHandler($errno, $errstr, $errfile, $errline) - { - // if the error has been suppressed by the @ we don't handle the error - if (error_reporting() == 0) { - return; - } - - $backtrace = ''; - if (empty(self::$debugBacktraceForTests)) { - $bt = @debug_backtrace(); - if ($bt !== null && isset($bt[0])) { - foreach ($bt as $i => $debug) { - $backtrace .= "#$i " - . (isset($debug['class']) ? $debug['class'] : '') - . (isset($debug['type']) ? $debug['type'] : '') - . (isset($debug['function']) ? $debug['function'] : '') - . '(...) called at [' - . (isset($debug['file']) ? $debug['file'] : '') . ':' - . (isset($debug['line']) ? $debug['line'] : '') . ']' . "\n"; - } - } - } else { - $backtrace = self::$debugBacktraceForTests; - } - - $error = new Error($errno, $errstr, $errfile, $errline, $backtrace); - Log::error($error); - - switch ($errno) { - case E_ERROR: - case E_PARSE: - case E_CORE_ERROR: - case E_CORE_WARNING: - case E_COMPILE_ERROR: - case E_COMPILE_WARNING: - case E_USER_ERROR: - exit; - break; - - case E_WARNING: - case E_NOTICE: - case E_USER_WARNING: - case E_USER_NOTICE: - case E_STRICT: - case E_RECOVERABLE_ERROR: - case E_DEPRECATED: - case E_USER_DEPRECATED: - default: - // do not exit - break; - } - } -} diff --git a/www/analytics/core/ErrorHandler.php b/www/analytics/core/ErrorHandler.php new file mode 100644 index 00000000..965257c8 --- /dev/null +++ b/www/analytics/core/ErrorHandler.php @@ -0,0 +1,135 @@ +getTraceAsString()); + throw new ErrorException($message, 0, $errno, $errfile, $errline); + break; + + case E_WARNING: + case E_NOTICE: + case E_USER_WARNING: + case E_USER_NOTICE: + case E_STRICT: + case E_RECOVERABLE_ERROR: + case E_DEPRECATED: + case E_USER_DEPRECATED: + default: + try { + Log::warning(self::createLogMessage($errno, $errstr, $errfile, $errline)); + } catch (\Exception $ex) { + // ignore (it's possible for this to happen if the StaticContainer hasn't been created yet) + } + + break; + } + } + + private static function createLogMessage($errno, $errstr, $errfile, $errline) + { + return sprintf( + "%s(%d): %s - %s - Piwik " . (class_exists('Piwik\Version') ? Version::VERSION : '') . " - Please report this message in the Piwik forums: http://forum.piwik.org (please do a search first as it might have been reported already)", + $errfile, + $errline, + ErrorHandler::getErrNoString($errno), + $errstr + ); + } + + private static function getHtmlMessage($errno, $errstr, $errfile, $errline, $trace) + { + $trace = Log::$debugBacktraceForTests ?: $trace; + + $message = ErrorHandler::getErrNoString($errno) . ' - ' . $errstr; + + $html = "

There is an error. Please report the message (Piwik " . (class_exists('Piwik\Version') ? Version::VERSION : '') . ") + and full backtrace in the Piwik forums (please do a Search firit might have been reported already!).

"; + $html .= "

{$message} in {$errfile}"; + $html .= " on line {$errline}

"; + $html .= "Backtrace:
";
+        $html .= str_replace("\n", "\n", $trace);
+        $html .= "
"; + + return $html; + } +} diff --git a/www/analytics/core/EventDispatcher.php b/www/analytics/core/EventDispatcher.php index 88e84d88..5795200c 100644 --- a/www/analytics/core/EventDispatcher.php +++ b/www/analytics/core/EventDispatcher.php @@ -1,6 +1,6 @@ pluginManager = $pluginManager; + + foreach ($observers as $observerInfo) { + list($eventName, $callback) = $observerInfo; + $this->extraObservers[$eventName][] = $callback; + } + } + /** * Triggers an event, executing all callbacks associated with it. * @@ -63,24 +91,36 @@ class EventDispatcher extends Singleton $this->pendingEvents[] = array($eventName, $params); } + $manager = $this->pluginManager; + if (empty($plugins)) { - $plugins = \Piwik\Plugin\Manager::getInstance()->getPluginsLoadedAndActivated(); + $plugins = $manager->getPluginsLoadedAndActivated(); } $callbacks = array(); // collect all callbacks to execute - foreach ($plugins as $plugin) { - if (is_string($plugin)) { - $plugin = \Piwik\Plugin\Manager::getInstance()->getLoadedPlugin($plugin); + foreach ($plugins as $pluginName) { + if (!is_string($pluginName)) { + $pluginName = $pluginName->getPluginName(); } - $hooks = $plugin->getListHooksRegistered(); + if (!isset($this->pluginHooks[$pluginName])) { + $plugin = $manager->getLoadedPlugin($pluginName); + $this->pluginHooks[$pluginName] = $plugin->getListHooksRegistered(); + } + + $hooks = $this->pluginHooks[$pluginName]; if (isset($hooks[$eventName])) { list($pluginFunction, $callbackGroup) = $this->getCallbackFunctionAndGroupNumber($hooks[$eventName]); - $callbacks[$callbackGroup][] = is_string($pluginFunction) ? array($plugin, $pluginFunction) : $pluginFunction; + if (is_string($pluginFunction)) { + $plugin = $manager->getLoadedPlugin($pluginName); + $callbacks[$callbackGroup][] = array($plugin, $pluginFunction) ; + } else { + $callbacks[$callbackGroup][] = $pluginFunction; + } } } @@ -92,6 +132,9 @@ class EventDispatcher extends Singleton } } + // sort callbacks by their importance + ksort($callbacks); + // execute callbacks in order foreach ($callbacks as $callbackGroup) { foreach ($callbackGroup as $callback) { @@ -125,30 +168,6 @@ class EventDispatcher extends Singleton $this->extraObservers[$eventName][] = $callback; } - /** - * Removes all registered extra observers for an event name. Only used for testing. - * - * @param string $eventName - */ - public function clearObservers($eventName) - { - $this->extraObservers[$eventName] = array(); - } - - /** - * Removes all registered extra observers. Only used for testing. - */ - public function clearAllObservers() - { - foreach ($this->extraObservers as $eventName => $eventObservers) { - if (strpos($eventName, 'Log.format') === 0) { - continue; - } - - $this->extraObservers[$eventName] = array(); - } - } - /** * Re-posts all pending events to the given plugin. * @@ -170,10 +189,10 @@ class EventDispatcher extends Singleton $pluginFunction = $hookInfo['function']; if (!empty($hookInfo['before'])) { $callbackGroup = self::EVENT_CALLBACK_GROUP_FIRST; - } else if (!empty($hookInfo['after'])) { - $callbackGroup = self::EVENT_CALLBACK_GROUP_SECOND; - } else { + } elseif (!empty($hookInfo['after'])) { $callbackGroup = self::EVENT_CALLBACK_GROUP_THIRD; + } else { + $callbackGroup = self::EVENT_CALLBACK_GROUP_SECOND; } } else { $pluginFunction = $hookInfo; @@ -183,4 +202,3 @@ class EventDispatcher extends Singleton return array($pluginFunction, $callbackGroup); } } - diff --git a/www/analytics/core/Exception/AuthenticationFailedException.php b/www/analytics/core/Exception/AuthenticationFailedException.php new file mode 100644 index 00000000..4091979f --- /dev/null +++ b/www/analytics/core/Exception/AuthenticationFailedException.php @@ -0,0 +1,13 @@ + + */ +class ErrorException extends \ErrorException +{ + public function isHtmlMessage() + { + return true; + } +} diff --git a/www/analytics/core/Exception/Exception.php b/www/analytics/core/Exception/Exception.php new file mode 100644 index 00000000..66d95eb4 --- /dev/null +++ b/www/analytics/core/Exception/Exception.php @@ -0,0 +1,30 @@ +isHtmlMessage = true; + } + + public function isHtmlMessage() + { + return $this->isHtmlMessage; + } +} diff --git a/www/analytics/core/Exception/InvalidRequestParameterException.php b/www/analytics/core/Exception/InvalidRequestParameterException.php new file mode 100644 index 00000000..4ea468c3 --- /dev/null +++ b/www/analytics/core/Exception/InvalidRequestParameterException.php @@ -0,0 +1,13 @@ +getFile(), $message->getLine(), $message->getMessage(), - self::$debugBacktraceForTests ? : $message->getTraceAsString()); - - $message = $log->formatMessage($level, $tag, $datetime, $message); + if (Common::isPhpCliMode()) { + self::dieWithCliError($exception); } + + self::dieWithHtmlErrorPage($exception); } - public static function formatScreenMessage(&$message, $level, $tag, $datetime, $log) + /** + * @param Exception|\Throwable $exception + */ + public static function dieWithCliError($exception) { - if ($message instanceof \Exception) { - if (!Common::isPhpCliMode()) { - @header('Content-Type: text/html; charset=utf-8'); - } + $message = $exception->getMessage(); - $outputFormat = strtolower(Common::getRequestVar('format', 'html', 'string')); + if (!method_exists($exception, 'isHtmlMessage') || !$exception->isHtmlMessage()) { + $message = strip_tags(str_replace('
', PHP_EOL, $message)); + } + + $message = sprintf( + "Uncaught exception: %s\nin %s line %d\n%s\n", + $message, + $exception->getFile(), + $exception->getLine(), + $exception->getTraceAsString() + ); + + echo $message; + + exit(1); + } + + /** + * @param Exception|\Throwable $exception + */ + public static function dieWithHtmlErrorPage($exception) + { + Common::sendHeader('Content-Type: text/html; charset=utf-8'); + + echo self::getErrorResponse($exception); + + exit(1); + } + + /** + * @param Exception|\Throwable $ex + */ + private static function getErrorResponse($ex) + { + $debugTrace = $ex->getTraceAsString(); + + $message = $ex->getMessage(); + + $isHtmlMessage = method_exists($ex, 'isHtmlMessage') && $ex->isHtmlMessage(); + + if (!$isHtmlMessage && Request::isApiRequest($_GET)) { + + $outputFormat = strtolower(Common::getRequestVar('format', 'xml', 'string', $_GET + $_POST)); $response = new ResponseBuilder($outputFormat); - $message = $response->getResponseException(new \Exception($message->getMessage())); - } - } + return $response->getResponseException($ex); - public static function logException(\Exception $exception) - { - Log::error($exception); + } elseif (!$isHtmlMessage) { + $message = Common::sanitizeInputValue($message); + } + + $logo = new CustomLogo(); + + $logoHeaderUrl = false; + $logoFaviconUrl = false; + try { + $logoHeaderUrl = $logo->getHeaderLogoUrl(); + $logoFaviconUrl = $logo->getPathUserFavicon(); + } catch (Exception $ex) { + try { + Log::debug($ex); + } catch (\Exception $otherEx) { + // DI container may not be setup at this point + } + } + + $result = Piwik_GetErrorMessagePage($message, $debugTrace, true, true, $logoHeaderUrl, $logoFaviconUrl); + + try { + /** + * Triggered before a Piwik error page is displayed to the user. + * + * This event can be used to modify the content of the error page that is displayed when + * an exception is caught. + * + * @param string &$result The HTML of the error page. + * @param Exception $ex The Exception displayed in the error page. + */ + Piwik::postEvent('FrontController.modifyErrorPage', array(&$result, $ex)); + } catch (ContainerDoesNotExistException $ex) { + // this can happen when an error occurs before the Piwik environment is created + } + + return $result; } } diff --git a/www/analytics/core/Filechecks.php b/www/analytics/core/Filechecks.php index 52cb995b..bf3e36f1 100644 --- a/www/analytics/core/Filechecks.php +++ b/www/analytics/core/Filechecks.php @@ -1,6 +1,6 @@ chown -R www-data:www-data " . $realpath . "
" . $directoryList; + $directoryList = "chown -R ". self::getUserAndGroup() ." " . $realpath . "
" . $directoryList; } - if(function_exists('shell_exec')) { - $currentUser = trim(shell_exec('whoami')); - if(!empty($currentUser)) { + if (function_exists('shell_exec')) { + $currentUser = self::getUser(); + if (!empty($currentUser)) { $optionalUserInfo = " (running as user '" . $currentUser . "')"; } } - $directoryMessage = "

Piwik couldn't write to some directories $optionalUserInfo.

"; + $directoryMessage = "

Piwik couldn't write to some directories $optionalUserInfo.

"; $directoryMessage .= "

Try to Execute the following commands on your server, to allow Write access on these directories" . ":

" . "
$directoryList
" @@ -102,7 +96,10 @@ class Filechecks . "

After applying the modifications, you can refresh the page.

" . "

If you need more help, try Piwik.org.

"; - Piwik_ExitWithMessage($directoryMessage, false, true); + $ex = new MissingFilePermissionException($directoryMessage); + $ex->setIsHtmlMessage(); + + throw $ex; } /** @@ -117,7 +114,6 @@ class Filechecks $manifest = PIWIK_INCLUDE_PATH . '/config/manifest.inc.php'; - if (file_exists($manifest)) { require_once $manifest; } @@ -139,7 +135,7 @@ class Filechecks if (!file_exists($file) || !is_readable($file)) { $messages[] = Piwik::translate('General_ExceptionMissingFile', $file); - } else if (filesize($file) != $props[0]) { + } elseif (filesize($file) != $props[0]) { if (!$hasMd5 || in_array(substr($path, -4), array('.gif', '.ico', '.jpg', '.png', '.swf'))) { // files that contain binary data (e.g., images) must match the file size $messages[] = Piwik::translate('General_ExceptionFilesizeMismatch', array($file, $props[0], filesize($file))); @@ -153,7 +149,7 @@ class Filechecks $messages[] = Piwik::translate('General_ExceptionFilesizeMismatch', array($file, $props[0], filesize($file))); } } - } else if ($hasMd5file && (@md5_file($file) !== $props[1])) { + } elseif ($hasMd5file && (@md5_file($file) !== $props[1])) { $messages[] = Piwik::translate('General_ExceptionFileIntegrity', $file); } } @@ -178,7 +174,7 @@ class Filechecks { $realpath = Filesystem::realpath(PIWIK_INCLUDE_PATH . '/'); $message = ''; - $message .= "chown -R www-data:www-data " . $realpath . "
"; + $message .= "chown -R ". self::getUserAndGroup() ." " . $realpath . "
"; $message .= "chmod -R 0755 " . $realpath . "
"; $message .= 'After you execute these commands (or change permissions via your FTP software), refresh the page and you should be able to use the "Automatic Update" feature.'; return $message; @@ -198,9 +194,10 @@ class Filechecks $message .= "On Windows, check that the folder is not read only and is writable.\n You can try to execute:
"; } else { - $message .= "For example, on a Linux server if your Apache httpd user - is www-data, you can try to execute:
\n" - . "chown -R www-data:www-data " . $path . "
"; + $message .= "For example, on a GNU/Linux server if your Apache httpd user is " + . self::getUser() + . ", you can try to execute:
\n" + . "chown -R ". self::getUserAndGroup() ." " . $path . "
"; } $message .= self::getMakeWritableCommand($path); @@ -208,6 +205,29 @@ class Filechecks return $message; } + private static function getUserAndGroup() + { + $user = self::getUser(); + if (!function_exists('shell_exec')) { + return $user . ':' . $user; + } + + $group = trim(shell_exec('groups '. $user .' | cut -f3 -d" "')); + + if (empty($group)) { + $group = 'www-data'; + } + return $user . ':' . $group; + } + + private static function getUser() + { + if (!function_exists('shell_exec')) { + return 'www-data'; + } + return trim(shell_exec('whoami')); + } + /** * Returns the help text displayed to suggest which command to run to give writable access to a file or directory * diff --git a/www/analytics/core/Filesystem.php b/www/analytics/core/Filesystem.php index fc7c8e78..c22a4d0b 100644 --- a/www/analytics/core/Filesystem.php +++ b/www/analytics/core/Filesystem.php @@ -1,6 +1,6 @@ removeMergedAssets($pluginName); View::clearCompiledTemplates(); - Cache::deleteTrackerCache(); + TrackerCache::deleteTrackerCache(); + PiwikCache::flushAll(); + self::clearPhpCaches(); } /** @@ -38,25 +42,6 @@ class Filesystem return realpath(dirname(__FILE__) . "/.."); } - /** - * Create .htaccess file in specified directory - * - * Apache-specific; for IIS @see web.config - * - * @param string $path without trailing slash - * @param bool $overwrite whether to overwrite an existing file or not - * @param string $content - */ - public static function createHtAccess($path, $overwrite = true, $content = "\n\nDeny from all\n\n\n\nDeny from all\n\n\n\nDeny from all\n\n\n") - { - if (SettingsServer::isApache()) { - $file = $path . '/.htaccess'; - if ($overwrite || !file_exists($file)) { - @file_put_contents($file, $content); - } - } - } - /** * Returns true if the string is a valid filename * File names that start with a-Z or 0-9 and contain a-Z, 0-9, underscore(_), dash(-), and dot(.) will be accepted. @@ -88,19 +73,17 @@ class Filesystem /** * Attempts to create a new directory. All errors are silenced. - * + * * _Note: This function does **not** create directories recursively._ * * @param string $path The path of the directory to create. - * @param bool $denyAccess Whether to deny browser access to this new folder by - * creating an **.htaccess** file. * @api */ - public static function mkdir($path, $denyAccess = true) + public static function mkdir($path) { if (!is_dir($path)) { // the mode in mkdir is modified by the current umask - @mkdir($path, $mode = 0755, $recursive = true); + @mkdir($path, self::getChmodForPath($path), $recursive = true); } // try to overcome restrictive umask (mis-)configuration @@ -111,10 +94,6 @@ class Filesystem // enough! we're not going to make the directory world-writeable } } - - if ($denyAccess) { - self::createHtAccess($path, $overwrite = false); - } } /** @@ -139,8 +118,9 @@ class Filesystem // and the return code 1. if NFS, it will return 0 and at least 2 lines of text. $command = "df -T -t nfs \"$sessionsPath\" 2>&1"; - if (function_exists('exec')) // use exec - { + if (function_exists('exec')) { + // use exec + $output = $returnCode = null; @exec($command, $output, $returnCode); @@ -150,13 +130,16 @@ class Filesystem ) { return true; } - } else if (function_exists('shell_exec')) // use shell_exec - { + } elseif (function_exists('shell_exec')) { + // use shell_exec + $output = @shell_exec($command); if ($output) { - $output = explode("\n", $output); - if (count($output) > 1) // check if filesystem is NFS - { + $commandFailed = (false !== strpos($output, "no file systems processed")); + $output = explode("\n", trim($output)); + if (!$commandFailed + && count($output) > 1) { + // check if filesystem is NFS return true; } } @@ -167,7 +150,7 @@ class Filesystem /** * Recursively find pathnames that match a pattern. - * + * * See {@link http://php.net/manual/en/function.glob.php glob} for more info. * * @param string $sDir directory The directory to glob in. @@ -228,6 +211,77 @@ class Filesystem return; } + /** + * Removes all files and directories that are present in the target directory but are not in the source directory. + * + * @param string $source Path to the source directory + * @param string $target Path to the target + */ + public static function unlinkTargetFilesNotPresentInSource($source, $target) + { + $diff = self::directoryDiff($source, $target); + $diff = self::sortFilesDescByPathLength($diff); + + foreach ($diff as $file) { + $remove = $target . $file; + + if (is_dir($remove)) { + @rmdir($remove); + } else { + self::deleteFileIfExists($remove); + } + } + } + + /** + * Sort all given paths/filenames by its path length. Long path names will be listed first. This method can be + * useful if you have for instance a bunch of files/directories to delete. By sorting them by lengh you can make + * sure to delete all files within the folders before deleting the actual folder. + * + * @param string[] $files + * @return string[] + */ + public static function sortFilesDescByPathLength($files) + { + usort($files, function ($a, $b) { + // sort by filename length so we kinda make sure to remove files before its directories + if ($a == $b) { + return 0; + } + + return (strlen($a) > strlen($b) ? -1 : 1); + }); + + return $files; + } + + /** + * Computes the difference of directories. Compares $target against $source and returns a relative path to all files + * and directories in $target that are not present in $source. + * + * @param $source + * @param $target + * + * @return string[] + */ + public static function directoryDiff($source, $target) + { + $sourceFiles = self::globr($source, '*'); + $targetFiles = self::globr($target, '*'); + + $sourceFiles = array_map(function ($file) use ($source) { + return str_replace($source, '', $file); + }, $sourceFiles); + + $targetFiles = array_map(function ($file) use ($target) { + return str_replace($target, '', $file); + }, $targetFiles); + + $diff = array_diff($targetFiles, $sourceFiles); + + return array_values($diff); + } + /** * Copies a file from `$source` to `$dest`. * @@ -241,29 +295,42 @@ class Filesystem */ public static function copy($source, $dest, $excludePhp = false) { - static $phpExtensions = array('php', 'tpl', 'twig'); - if ($excludePhp) { - $path_parts = pathinfo($source); - if (in_array($path_parts['extension'], $phpExtensions)) { + if (self::hasPHPExtension($source)) { return true; } } - if (!@copy($source, $dest)) { - @chmod($dest, 0755); - if (!@copy($source, $dest)) { - $message = "Error while creating/copying file to $dest.
" - . Filechecks::getErrorMessageMissingPermissions(self::getPathToPiwikRoot()); - throw new Exception($message); - } + $success = self::tryToCopyFileAndVerifyItWasCopied($source, $dest); + + if (!$success) { + $success = self::tryToCopyFileAndVerifyItWasCopied($source, $dest); } + + if (!$success) { + throw new Exception("Error while creating/copying file from $source to $dest. Content of copied file is different."); + } + return true; } + private static function hasPHPExtension($file) + { + static $phpExtensions = array('php', 'tpl', 'twig'); + + $path_parts = pathinfo($file); + + if (!empty($path_parts['extension']) + && in_array($path_parts['extension'], $phpExtensions)) { + return true; + } + + return false; + } + /** * Copies the contents of a directory recursively from `$source` to `$target`. - * + * * @param string $source A directory or file to copy, eg. './tmp/latest'. * @param string $target A directory to copy to, eg. '.'. * @param bool $excludePhp Whether to avoid copying files if the file is related to PHP @@ -274,7 +341,7 @@ class Filesystem public static function copyRecursive($source, $target, $excludePhp = false) { if (is_dir($source)) { - self::mkdir($target, false); + self::mkdir($target); $d = dir($source); while (false !== ($entry = $d->read())) { if ($entry == '.' || $entry == '..') { @@ -311,4 +378,130 @@ class Filesystem return @unlink($pathToFile); } + + /** + * Get the size of a file in the specified unit. + * + * @param string $pathToFile + * @param string $unit eg 'B' for Byte, 'KB', 'MB', 'GB', 'TB'. + * + * @return float|null Returns null if file does not exist or the size of the file in the specified unit + * + * @throws Exception In case the unit is invalid + */ + public static function getFileSize($pathToFile, $unit = 'B') + { + $unit = strtoupper($unit); + $units = array('TB' => pow(1024, 4), + 'GB' => pow(1024, 3), + 'MB' => pow(1024, 2), + 'KB' => 1024, + 'B' => 1); + + if (!array_key_exists($unit, $units)) { + throw new Exception('Invalid unit given'); + } + + if (!file_exists($pathToFile)) { + return; + } + + $filesize = filesize($pathToFile); + $factor = $units[$unit]; + $converted = $filesize / $factor; + + return $converted; + } + + /** + * Remove a file. + * + * @param string $file + * @param bool $silenceErrors If true, no exception will be thrown in case removing fails. + */ + public static function remove($file, $silenceErrors = false) + { + if (!file_exists($file)) { + return; + } + + $result = @unlink($file); + + // Testing if the file still exist avoids race conditions + if (!$result && file_exists($file)) { + if ($silenceErrors) { + Log::warning('Failed to delete file ' . $file); + } else { + throw new \RuntimeException('Unable to delete file ' . $file); + } + } + } + + /** + * @param $path + * @return int + */ + private static function getChmodForPath($path) + { + $pathIsTmp = StaticContainer::get('path.tmp'); + if (strpos($path, $pathIsTmp) === 0) { + // tmp/* folder + return 0750; + } + // plugins/* and all others + return 0755; + } + + public static function clearPhpCaches() + { + if (function_exists('apc_clear_cache')) { + apc_clear_cache(); // clear the system (aka 'opcode') cache + } + + if (function_exists('opcache_reset')) { + @opcache_reset(); // reset the opcode cache (php 5.5.0+) + } + + if (function_exists('wincache_refresh_if_changed')) { + @wincache_refresh_if_changed(); // reset the wincache + } + + if (function_exists('xcache_clear_cache') && defined('XC_TYPE_VAR')) { + if (ini_get('xcache.admin.enable_auth')) { + // XCache will not be cleared because "xcache.admin.enable_auth" is enabled in php.ini. + } else { + @xcache_clear_cache(XC_TYPE_VAR); + } + } + } + + private static function havePhpFilesSameContent($file1, $file2) + { + if (self::hasPHPExtension($file1)) { + $sourceMd5 = md5_file($file1); + $destMd5 = md5_file($file2); + + return $sourceMd5 === $destMd5; + } + + return true; + } + + private static function tryToCopyFileAndVerifyItWasCopied($source, $dest) + { + if (!@copy($source, $dest)) { + @chmod($dest, 0755); + if (!@copy($source, $dest)) { + $message = "Error while creating/copying file to $dest.
" + . Filechecks::getErrorMessageMissingPermissions(self::getPathToPiwikRoot()); + throw new Exception($message); + } + } + + if (file_exists($source) && file_exists($dest)) { + return self::havePhpFilesSameContent($source, $dest); + } + + return true; + } } diff --git a/www/analytics/core/FrontController.php b/www/analytics/core/FrontController.php index 63e8a9d3..40f8f381 100644 --- a/www/analytics/core/FrontController.php +++ b/www/analytics/core/FrontController.php @@ -1,6 +1,6 @@ dispatch('UserCountryMap', 'realtimeMap'); * } - * + * * **Using other plugin controller actions** - * + * * public function myPopupWithRealtimeMap() * { * $_GET['changeVisitAlpha'] = false; * $_GET['removeOldVisits'] = false; * $_GET['showFooterMessage'] = false; * $realtimeMap = FrontController::getInstance()->fetchDispatch('UserCountryMap', 'realtimeMap'); - * + * * $view = new View('@MyPlugin/myPopupWithRealtimeMap.twig'); * $view->realtimeMap = $realtimeMap; * return $realtimeMap->render(); @@ -54,6 +57,7 @@ use Piwik\Session; class FrontController extends Singleton { const DEFAULT_MODULE = 'CoreHome'; + /** * Set to false and the Front Controller will not dispatch the request * @@ -61,11 +65,14 @@ class FrontController extends Singleton */ public static $enableDispatch = true; + /** + * @var bool + */ + private $initialized = false; + /** * Executes the requested plugin controller method. - * - * See also {@link fetchDispatch()}. - * + * * @throws Exception|\Piwik\PluginDeactivatedException in case the plugin doesn't exist, the action doesn't exist, * there is not enough permission, etc. * @@ -81,67 +88,40 @@ class FrontController extends Singleton return; } + $filter = new Router(); + $redirection = $filter->filterUrl(Url::getCurrentUrl()); + if ($redirection !== null) { + Url::redirectToUrl($redirection); + return; + } + try { $result = $this->doDispatch($module, $action, $parameters); return $result; } catch (NoAccessException $exception) { + Log::debug($exception); /** * Triggered when a user with insufficient access permissions tries to view some resource. - * + * * This event can be used to customize the error that occurs when a user is denied access * (for example, displaying an error message, redirecting to a page other than login, etc.). - * + * * @param \Piwik\NoAccessException $exception The exception that was caught. */ Piwik::postEvent('User.isNotAuthorized', array($exception), $pending = true); - } catch (Exception $e) { - $debugTrace = $e->getTraceAsString(); - $message = Common::sanitizeInputValue($e->getMessage()); - Piwik_ExitWithMessage($message, $debugTrace, true, true); } } - protected function makeController($module, $action) - { - $controllerClassName = $this->getClassNameController($module); - - // FrontController's autoloader - if (!class_exists($controllerClassName, false)) { - $moduleController = PIWIK_INCLUDE_PATH . '/plugins/' . $module . '/Controller.php'; - if (!is_readable($moduleController)) { - throw new Exception("Module controller $moduleController not found!"); - } - require_once $moduleController; // prefixed by PIWIK_INCLUDE_PATH - } - - $class = $this->getClassNameController($module); - /** @var $controller Controller */ - $controller = new $class; - if ($action === false) { - $action = $controller->getDefaultAction(); - } - - if (!is_callable(array($controller, $action))) { - throw new Exception("Action '$action' not found in the controller '$controllerClassName'."); - } - return array($controller, $action); - } - - protected function getClassNameController($module) - { - return "\\Piwik\\Plugins\\$module\\Controller"; - } - /** * Executes the requested plugin controller method and returns the data, capturing anything the * method `echo`s. - * + * * _Note: If the plugin controller returns something, the return value is returned instead * of whatever is in the output buffer._ - * + * * @param string $module The name of the plugin whose controller to execute, eg, `'UserCountryMap'`. - * @param string $action The controller action name, eg, `'realtimeMap'`. + * @param string $actionName The controller action name, eg, `'realtimeMap'`. * @param array $parameters Array of parameters to pass to the controller action method. * @return string The `echo`'d data or the return value of the controller action. * @deprecated @@ -168,13 +148,14 @@ class FrontController extends Singleton { try { if (class_exists('Piwik\\Profiler') - && !SettingsServer::isTrackerApiRequest()) { + && !SettingsServer::isTrackerApiRequest() + ) { // in tracker mode Piwik\Tracker\Db\Pdo\Mysql does currently not implement profiling Profiler::displayDbProfileReport(); Profiler::printQueryCount(); - Log::debug(Registry::get('timer')); } } catch (Exception $e) { + Log::debug($e); } } @@ -184,21 +165,22 @@ class FrontController extends Singleton // If we are in no dispatch mode, eg. a script reusing Piwik libs, // then we should return the exception directly, rather than trigger the event "bad config file" // which load the HTML page of the installer with the error. - // This is at least required for misc/cron/archive.php and useful to all other scripts return (defined('PIWIK_ENABLE_DISPATCH') && !PIWIK_ENABLE_DISPATCH) || Common::isPhpCliMode() || SettingsServer::isArchivePhpTriggered(); } - static public function setUpSafeMode() + public static function setUpSafeMode() { - register_shutdown_function(array('\\Piwik\\FrontController','triggerSafeModeWhenError')); + register_shutdown_function(array('\\Piwik\\FrontController', 'triggerSafeModeWhenError')); } - static public function triggerSafeModeWhenError() + public static function triggerSafeModeWhenError() { $lastError = error_get_last(); if (!empty($lastError) && $lastError['type'] == E_ERROR) { + Common::sendResponseCode(500); + $controller = FrontController::getInstance(); $controller->init(); $message = $controller->dispatch('CorePluginsAdmin', 'safemode', array($lastError)); @@ -207,33 +189,6 @@ class FrontController extends Singleton } } - /** - * Loads the config file and assign to the global registry - * This is overridden in tests to ensure test config file is used - * - * @return Exception - */ - static public function createConfigObject() - { - $exceptionToThrow = false; - try { - Config::getInstance()->database; // access property to check if the local file exists - } catch (Exception $exception) { - - /** - * Triggered when the configuration file cannot be found or read, which usually - * means Piwik is not installed yet. - * - * This event can be used to start the installation process or to display a custom error message. - * - * @param Exception $exception The exception that was thrown by `Config::getInstance()`. - */ - Piwik::postEvent('Config.NoConfigurationFile', array($exception), $pending = true); - $exceptionToThrow = $exception; - } - return $exceptionToThrow; - } - /** * Must be called before dispatch() * - checks that directories are writable, @@ -247,135 +202,146 @@ class FrontController extends Singleton */ public function init() { - static $initialized = false; - if ($initialized) { + if ($this->initialized) { return; } - $initialized = true; + $this->initialized = true; + + $tmpPath = StaticContainer::get('path.tmp'); + + $directoriesToCheck = array( + $tmpPath, + $tmpPath . '/assets/', + $tmpPath . '/cache/', + $tmpPath . '/logs/', + $tmpPath . '/tcpdf/', + $tmpPath . '/templates_c/', + ); + + Filechecks::dieIfDirectoriesNotWritable($directoriesToCheck); + + $this->handleMaintenanceMode(); + $this->handleProfiler(); + $this->handleSSLRedirection(); + + Plugin\Manager::getInstance()->loadPluginTranslations(); + Plugin\Manager::getInstance()->loadActivatedPlugins(); + + // try to connect to the database try { - Registry::set('timer', new Timer); - - $directoriesToCheck = array( - '/tmp/', - '/tmp/assets/', - '/tmp/cache/', - '/tmp/logs/', - '/tmp/tcpdf/', - '/tmp/templates_c/', - ); - - Filechecks::dieIfDirectoriesNotWritable($directoriesToCheck); - - Translate::loadEnglishTranslation(); - - $exceptionToThrow = self::createConfigObject(); - - if (Session::isFileBasedSessions()) { - Session::start(); - } - - $this->handleMaintenanceMode(); - $this->handleProfiler(); - $this->handleSSLRedirection(); - - Plugin\Manager::getInstance()->loadActivatedPlugins(); - - if ($exceptionToThrow) { - throw $exceptionToThrow; - } - - try { - Db::createDatabaseObject(); - Option::get('TestingIfDatabaseConnectionWorked'); - - } catch (Exception $exception) { - if (self::shouldRethrowException()) { - throw $exception; - } - - /** - * Triggered if the INI config file has the incorrect format or if certain required configuration - * options are absent. - * - * This event can be used to start the installation process or to display a custom error message. - * - * @param Exception $exception The exception thrown from creating and testing the database - * connection. - */ - Piwik::postEvent('Config.badConfigurationFile', array($exception), $pending = true); + Db::createDatabaseObject(); + Db::fetchAll("SELECT DATABASE()"); + } catch (Exception $exception) { + if (self::shouldRethrowException()) { throw $exception; } - // Init the Access object, so that eg. core/Updates/* can enforce Super User and use some APIs - Access::getInstance(); + Log::debug($exception); /** - * Triggered just after the platform is initialized and plugins are loaded. - * - * This event can be used to do early initialization. - * - * _Note: At this point the user is not authenticated yet._ + * Triggered when Piwik cannot connect to the database. + * + * This event can be used to start the installation process or to display a custom error + * message. + * + * @param Exception $exception The exception thrown from creating and testing the database + * connection. */ - Piwik::postEvent('Request.dispatchCoreAndPluginUpdatesScreen'); + Piwik::postEvent('Db.cannotConnectToDb', array($exception), $pending = true); - \Piwik\Plugin\Manager::getInstance()->installLoadedPlugins(); - - // ensure the current Piwik URL is known for later use - if (method_exists('Piwik\SettingsPiwik', 'getPiwikUrl')) { - $host = SettingsPiwik::getPiwikUrl(); - } - - /** - * Triggered before the user is authenticated, when the global authentication object - * should be created. - * - * Plugins that provide their own authentication implementation should use this event - * to set the global authentication object (which must derive from {@link Piwik\Auth}). - * - * **Example** - * - * Piwik::addAction('Request.initAuthenticationObject', function() { - * Piwik\Registry::set('auth', new MyAuthImplementation()); - * }); - */ - Piwik::postEvent('Request.initAuthenticationObject'); - try { - $authAdapter = Registry::get('auth'); - } catch (Exception $e) { - throw new Exception("Authentication object cannot be found in the Registry. Maybe the Login plugin is not activated? -
You can activate the plugin by adding:
- Plugins[] = Login
- under the [Plugins] section in your config/config.ini.php"); - } - Access::getInstance()->reloadAccess($authAdapter); - - // Force the auth to use the token_auth if specified, so that embed dashboard - // and all other non widgetized controller methods works fine - if (($token_auth = Common::getRequestVar('token_auth', false, 'string')) !== false) { - Request::reloadAuthUsingTokenAuth(); - } - SettingsServer::raiseMemoryLimitIfNecessary(); - - Translate::reloadLanguage(); - \Piwik\Plugin\Manager::getInstance()->postLoadPlugins(); - - /** - * Triggered after the platform is initialized and after the user has been authenticated, but - * before the platform has handled the request. - * - * Piwik uses this event to check for updates to Piwik. - */ - Piwik::postEvent('Platform.initialized'); - } catch (Exception $e) { - - if (self::shouldRethrowException()) { - throw $e; - } - - $debugTrace = $e->getTraceAsString(); - Piwik_ExitWithMessage($e->getMessage(), $debugTrace, true); + throw $exception; } + + // try to get an option (to check if data can be queried) + try { + Option::get('TestingIfDatabaseConnectionWorked'); + } catch (Exception $exception) { + if (self::shouldRethrowException()) { + throw $exception; + } + + Log::debug($exception); + + /** + * Triggered when Piwik cannot access database data. + * + * This event can be used to start the installation process or to display a custom error + * message. + * + * @param Exception $exception The exception thrown from trying to get an option value. + */ + Piwik::postEvent('Config.badConfigurationFile', array($exception), $pending = true); + + throw $exception; + } + + // Init the Access object, so that eg. core/Updates/* can enforce Super User and use some APIs + Access::getInstance(); + + /** + * Triggered just after the platform is initialized and plugins are loaded. + * + * This event can be used to do early initialization. + * + * _Note: At this point the user is not authenticated yet._ + */ + Piwik::postEvent('Request.dispatchCoreAndPluginUpdatesScreen'); + + $this->throwIfPiwikVersionIsOlderThanDBSchema(); + + \Piwik\Plugin\Manager::getInstance()->installLoadedPlugins(); + + // ensure the current Piwik URL is known for later use + if (method_exists('Piwik\SettingsPiwik', 'getPiwikUrl')) { + SettingsPiwik::getPiwikUrl(); + } + + /** + * Triggered before the user is authenticated, when the global authentication object + * should be created. + * + * Plugins that provide their own authentication implementation should use this event + * to set the global authentication object (which must derive from {@link Piwik\Auth}). + * + * **Example** + * + * Piwik::addAction('Request.initAuthenticationObject', function() { + * StaticContainer::getContainer()->set('Piwik\Auth', new MyAuthImplementation()); + * }); + */ + Piwik::postEvent('Request.initAuthenticationObject'); + try { + $authAdapter = StaticContainer::get('Piwik\Auth'); + } catch (Exception $e) { + $message = "Authentication object cannot be found in the container. Maybe the Login plugin is not activated? +
You can activate the plugin by adding:
+ Plugins[] = Login
+ under the [Plugins] section in your config/config.ini.php"; + + $ex = new AuthenticationFailedException($message); + $ex->setIsHtmlMessage(); + + throw $ex; + } + Access::getInstance()->reloadAccess($authAdapter); + + // Force the auth to use the token_auth if specified, so that embed dashboard + // and all other non widgetized controller methods works fine + if (Common::getRequestVar('token_auth', false, 'string') !== false) { + Request::reloadAuthUsingTokenAuth(); + } + SettingsServer::raiseMemoryLimitIfNecessary(); + + \Piwik\Plugin\Manager::getInstance()->postLoadPlugins(); + + /** + * Triggered after the platform is initialized and after the user has been authenticated, but + * before the platform has handled the request. + * + * Piwik uses this event to check for updates to Piwik. + */ + Piwik::postEvent('Platform.initialized'); } protected function prepareDispatch($module, $action, $parameters) @@ -388,10 +354,12 @@ class FrontController extends Singleton $action = Common::getRequestVar('action', false); } - if (!Session::isFileBasedSessions() + if (SettingsPiwik::isPiwikInstalled() && ($module !== 'API' || ($action && $action !== 'index')) ) { Session::start(); + + $this->closeSessionEarlyForFasterUI(); } if (is_null($parameters)) { @@ -402,7 +370,7 @@ class FrontController extends Singleton throw new Exception("Invalid module name '$module'"); } - $module = Request::renameModule($module); + list($module, $action) = Request::getRenamedModuleAndAction($module, $action); if (!\Piwik\Plugin\Manager::getInstance()->isPluginActivated($module)) { throw new PluginDeactivatedException($module); @@ -413,54 +381,73 @@ class FrontController extends Singleton protected function handleMaintenanceMode() { - if (Config::getInstance()->General['maintenance_mode'] == 1 - && !Common::isPhpCliMode() - ) { - $format = Common::getRequestVar('format', ''); - - $message = "Piwik is in scheduled maintenance. Please come back later." - . " The administrator can disable maintenance by editing the file piwik/config/config.ini.php and removing the following: " - . " maintenance_mode=1 "; - if (Config::getInstance()->Tracker['record_statistics'] == 0) { - $message .= ' and record_statistics=0'; - } - - $exception = new Exception($message); - // extend explain how to re-enable - // show error message when record stats = 0 - if (empty($format)) { - throw $exception; - } - $response = new ResponseBuilder($format); - echo $response->getResponseException($exception); - exit; + if ((Config::getInstance()->General['maintenance_mode'] != 1) || Common::isPhpCliMode()) { + return; } + Common::sendResponseCode(503); + + $logoUrl = null; + $faviconUrl = null; + try { + $logo = new CustomLogo(); + $logoUrl = $logo->getHeaderLogoUrl(); + $faviconUrl = $logo->getPathUserFavicon(); + } catch (Exception $ex) { + } + $logoUrl = $logoUrl ?: 'plugins/Morpheus/images/logo-header.png'; + $faviconUrl = $faviconUrl ?: 'plugins/CoreHome/images/favicon.png'; + + $page = file_get_contents(PIWIK_INCLUDE_PATH . '/plugins/Morpheus/templates/maintenance.tpl'); + $page = str_replace('%logoUrl%', $logoUrl, $page); + $page = str_replace('%faviconUrl%', $faviconUrl, $page); + $page = str_replace('%piwikTitle%', Piwik::getRandomTitle(), $page); + echo $page; + exit; } protected function handleSSLRedirection() { // Specifically disable for the opt out iframe - if(Piwik::getModule() == 'CoreAdminHome' && Piwik::getAction() == 'optOut') { + if (Piwik::getModule() == 'CoreAdminHome' && Piwik::getAction() == 'optOut') { return; } - if(Common::isPhpCliMode()) { + // Disable Https for VisitorGenerator + if (Piwik::getModule() == 'VisitorGenerator') { return; } - // Only enable this feature after Piwik is already installed - if(!SettingsPiwik::isPiwikInstalled()) { + if (Common::isPhpCliMode()) { return; } // proceed only when force_ssl = 1 - if(!SettingsPiwik::isHttpsForced()) { + if (!SettingsPiwik::isHttpsForced()) { return; } Url::redirectToHttps(); } + private function closeSessionEarlyForFasterUI() + { + $isDashboardReferrer = !empty($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], 'module=CoreHome&action=index') !== false; + $isAllWebsitesReferrer = !empty($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], 'module=MultiSites&action=index') !== false; + + if ($isDashboardReferrer + && !empty($_POST['token_auth']) + && Common::getRequestVar('widget', 0, 'int') === 1 + ) { + Session::close(); + } + + if (($isDashboardReferrer || $isAllWebsitesReferrer) + && Common::getRequestVar('viewDataTable', '', 'string') === 'sparkline' + ) { + Session::close(); + } + } + private function handleProfiler() { if (!empty($_GET['xhprof'])) { - $mainRun = $_GET['xhprof'] == 1; // archive.php sets xhprof=2 + $mainRun = $_GET['xhprof'] == 1; // core:archive command sets xhprof=2 Profiler::setupProfilerXHProf($mainRun); } } @@ -487,7 +474,10 @@ class FrontController extends Singleton */ Piwik::postEvent('Request.dispatch', array(&$module, &$action, &$parameters)); - list($controller, $action) = $this->makeController($module, $action); + /** @var ControllerResolver $controllerResolver */ + $controllerResolver = StaticContainer::get('Piwik\Http\ControllerResolver'); + + $controller = $controllerResolver->getController($module, $action, $parameters); /** * Triggered directly before controller actions are dispatched. @@ -502,7 +492,7 @@ class FrontController extends Singleton */ Piwik::postEvent(sprintf('Controller.%s.%s', $module, $action), array(&$parameters)); - $result = call_user_func_array(array($controller, $action), $parameters); + $result = call_user_func_array($controller, $parameters); /** * Triggered after a controller action is successfully called. @@ -527,19 +517,33 @@ class FrontController extends Singleton * @param mixed &$result The controller action result. * @param array $parameters The arguments passed to the controller action. */ - Piwik::postEvent('Request.dispatch.end', array(&$result, $parameters)); + Piwik::postEvent('Request.dispatch.end', array(&$result, $module, $action, $parameters)); + return $result; } -} - -/** - * Exception thrown when the requested plugin is not activated in the config file - */ -class PluginDeactivatedException extends Exception -{ - public function __construct($module) + /** + * This method ensures that Piwik Platform cannot be running when using a NEWER database. + */ + private function throwIfPiwikVersionIsOlderThanDBSchema() { - parent::__construct("The plugin $module is not enabled. You can activate the plugin on Settings > Plugins page in Piwik."); + // When developing this situation happens often when switching branches + if (Development::isEnabled()) { + return; + } + + $updater = new Updater(); + + $dbSchemaVersion = $updater->getCurrentComponentVersion('core'); + $current = Version::VERSION; + if (-1 === version_compare($current, $dbSchemaVersion)) { + $messages = array( + Piwik::translate('General_ExceptionDatabaseVersionNewerThanCodebase', array($current, $dbSchemaVersion)), + Piwik::translate('General_ExceptionDatabaseVersionNewerThanCodebaseWait'), + // we cannot fill in the Super User emails as we are failing before Authentication was ready + Piwik::translate('General_ExceptionContactSupportGeneric', array('', '')) + ); + throw new DatabaseSchemaIsNewerThanCodebaseException(implode(" ", $messages)); + } } } diff --git a/www/analytics/core/Http.php b/www/analytics/core/Http.php index d6e73769..7e7602c2 100644 --- a/www/analytics/core/Http.php +++ b/www/analytics/core/Http.php @@ -1,6 +1,6 @@ 5) { throw new Exception('Too many redirects (' . $followDepth . ')'); } @@ -136,6 +154,10 @@ class Http $contentLength = 0; $fileLength = 0; + if (!empty($requestBody) && is_array($requestBody)) { + $requestBody = http_build_query($requestBody); + } + // Piwik services behave like a proxy, so we should act like one. $xff = 'X-Forwarded-For: ' . (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && !empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] . ',' : '') @@ -156,16 +178,17 @@ class Http $rangeHeader = 'Range: bytes=' . $byteRange[0] . '-' . $byteRange[1] . "\r\n"; } - // proxy configuration - $proxyHost = Config::getInstance()->proxy['host']; - $proxyPort = Config::getInstance()->proxy['port']; - $proxyUser = Config::getInstance()->proxy['username']; - $proxyPassword = Config::getInstance()->proxy['password']; + list($proxyHost, $proxyPort, $proxyUser, $proxyPassword) = self::getProxyConfiguration($aUrl); + + + $aUrl = trim($aUrl); // other result data - $status = null; + $status = null; $headers = array(); + $httpAuthIsUsed = !empty($httpUsername) || !empty($httpPassword); + if ($method == 'socket') { if (!self::isSocketEnabled()) { // can be triggered in tests @@ -177,11 +200,11 @@ class Http throw new Exception('Malformed URL: ' . $aUrl); } - if ($url['scheme'] != 'http') { + if ($url['scheme'] != 'http' && $url['scheme'] != 'https') { throw new Exception('Invalid protocol/scheme: ' . $url['scheme']); } $host = $url['host']; - $port = isset($url['port)']) ? $url['port'] : 80; + $port = isset($url['port']) ? $url['port'] : 80; $path = isset($url['path']) ? $url['path'] : '/'; if (isset($url['query'])) { $path .= '?' . $url['query']; @@ -219,19 +242,32 @@ class Http throw new Exception("Error while connecting to: $host. Please try again later. $errstr"); } + $httpAuth = ''; + if ($httpAuthIsUsed) { + $httpAuth = 'Authorization: Basic ' . base64_encode($httpUsername.':'.$httpPassword) . "\r\n"; + } + // send HTTP request header $requestHeader .= "Host: $host" . ($port != 80 ? ':' . $port : '') . "\r\n" + . ($httpAuth ? $httpAuth : '') . ($proxyAuth ? $proxyAuth : '') . 'User-Agent: ' . $userAgent . "\r\n" . ($acceptLanguage ? $acceptLanguage . "\r\n" : '') . $xff . "\r\n" . $via . "\r\n" . $rangeHeader - . "Connection: close\r\n" - . "\r\n"; + . "Connection: close\r\n"; fwrite($fsock, $requestHeader); + if (strtolower($httpMethod) === 'post' && !empty($requestBody)) { + fwrite($fsock, self::buildHeadersForPost($requestBody)); + fwrite($fsock, "\r\n"); + fwrite($fsock, $requestBody); + } else { + fwrite($fsock, "\r\n"); + } + $streamMetaData = array('timed_out' => false); @stream_set_blocking($fsock, true); @@ -313,7 +349,9 @@ class Http $acceptInvalidSslCertificate = false, $byteRange, $getExtendedInfo, - $httpMethod + $httpMethod, + $httpUsername, + $httpPassword ); } @@ -359,7 +397,7 @@ class Http // determine success or failure @fclose(@$fsock); - } else if ($method == 'fopen') { + } elseif ($method == 'fopen') { $response = false; // we make sure the request takes less than a few seconds to fail @@ -368,11 +406,17 @@ class Http $default_socket_timeout = @ini_get('default_socket_timeout'); @ini_set('default_socket_timeout', $timeout); + $httpAuth = ''; + if ($httpAuthIsUsed) { + $httpAuth = 'Authorization: Basic ' . base64_encode($httpUsername.':'.$httpPassword) . "\r\n"; + } + $ctx = null; if (function_exists('stream_context_create')) { $stream_options = array( 'http' => array( 'header' => 'User-Agent: ' . $userAgent . "\r\n" + . ($httpAuth ? $httpAuth : '') . ($acceptLanguage ? $acceptLanguage . "\r\n" : '') . $xff . "\r\n" . $via . "\r\n" @@ -390,12 +434,22 @@ class Http } } + if (strtolower($httpMethod) === 'post' && !empty($requestBody)) { + $postHeader = self::buildHeadersForPost($requestBody); + $postHeader .= "\r\n"; + $stream_options['http']['method'] = 'POST'; + $stream_options['http']['header'] .= $postHeader; + $stream_options['http']['content'] = $requestBody; + } + $ctx = stream_context_create($stream_options); } // save to file if (is_resource($file)) { - $handle = fopen($aUrl, 'rb', false, $ctx); + if (!($handle = fopen($aUrl, 'rb', false, $ctx))) { + throw new Exception("Unable to open $aUrl"); + } while (!feof($handle)) { $response = fread($handle, 8192); $fileLength += strlen($response); @@ -403,7 +457,17 @@ class Http } fclose($handle); } else { - $response = file_get_contents($aUrl, 0, $ctx); + $response = @file_get_contents($aUrl, 0, $ctx); + + // try to get http status code from response headers + if (isset($http_response_header) && preg_match('~^HTTP/(\d\.\d)\s+(\d+)(\s*.*)?~', implode("\n", $http_response_header), $m)) { + $status = (int)$m[2]; + } + + if (!$status && $response === false) { + $error = error_get_last(); + throw new \Exception($error['message']); + } $fileLength = strlen($response); } @@ -411,7 +475,7 @@ class Http if (!empty($default_socket_timeout)) { @ini_set('default_socket_timeout', $default_socket_timeout); } - } else if ($method == 'curl') { + } elseif ($method == 'curl') { if (!self::isCurlEnabled()) { // can be triggered in tests throw new Exception("CURL is not enabled in php.ini, but is being used."); @@ -442,8 +506,9 @@ class Http // only get header info if not saving directly to file CURLOPT_HEADER => is_resource($file) ? false : true, CURLOPT_CONNECTTIMEOUT => $timeout, + CURLOPT_TIMEOUT => $timeout, ); - // Case archive.php is triggering archiving on https:// and the certificate is not valid + // Case core:archive command is triggering archiving on https:// and the certificate is not valid if ($acceptInvalidSslCertificate) { $curl_options += array( CURLOPT_SSL_VERIFYHOST => false, @@ -455,6 +520,17 @@ class Http @curl_setopt($ch, CURLOPT_NOBODY, true); } + if (strtolower($httpMethod) === 'post' && !empty($requestBody)) { + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $requestBody); + } + + if (!empty($httpUsername) && !empty($httpPassword)) { + $curl_options += array( + CURLOPT_USERPWD => $httpUsername . ':' . $httpPassword, + ); + } + @curl_setopt_array($ch, $curl_options); self::configCurlCertificate($ch); @@ -485,10 +561,11 @@ class Http if ($response === true) { $response = ''; - } else if ($response === false) { + } elseif ($response === false) { $errstr = curl_error($ch); if ($errstr != '') { - throw new Exception('curl_exec: ' . $errstr); + throw new Exception('curl_exec: ' . $errstr + . '. Hostname requested was: ' . UrlHelper::getHostFromUrl($aUrl)); } $response = ''; } else { @@ -496,7 +573,14 @@ class Http // redirects are included in the output html, so we look for the last line that starts w/ HTTP/... // to split the response while (substr($response, 0, 5) == "HTTP/") { - list($header, $response) = explode("\r\n\r\n", $response, 2); + $split = explode("\r\n\r\n", $response, 2); + + if(count($split) == 2) { + list($header, $response) = $split; + } else { + $response = ''; + $header = $split; + } } foreach (explode("\r\n", $header) as $line) { @@ -538,22 +622,30 @@ class Http } } + private static function buildHeadersForPost($requestBody) + { + $postHeader = "Content-Type: application/x-www-form-urlencoded\r\n"; + $postHeader .= "Content-Length: " . strlen($requestBody) . "\r\n"; + + return $postHeader; + } + /** * Downloads the next chunk of a specific file. The next chunk's byte range * is determined by the existing file's size and the expected file size, which * is stored in the piwik_option table before starting a download. The expected * file size is obtained through a `HEAD` HTTP request. - * + * * _Note: this function uses the **Range** HTTP header to accomplish downloading in * parts. Not every server supports this header._ - * + * * The proper use of this function is to call it once per request. The browser * should continue to send requests to Piwik which will in turn call this method * until the file has completely downloaded. In this way, the user can be informed * of a download's progress. - * + * * **Example Usage** - * + * * ``` * // browser JavaScript * var downloadFile = function (isStart) { @@ -571,10 +663,10 @@ class Http * }); * ajax.send(); * } - * + * * downloadFile(true); * ``` - * + * * ``` * // PHP controller action * public function myAction() @@ -584,7 +676,7 @@ class Http * Http::downloadChunk("http://bigfiles.com/averybigfile.zip", $outputPath, $isStart == 1); * } * ``` - * + * * @param string $url The url to download from. * @param string $outputPath The path to the file to save/append to. * @param bool $isContinuation `true` if this is the continuation of a download, @@ -751,4 +843,47 @@ class Http } return $str; } + + /** + * Returns the If-Modified-Since HTTP header if it can be found. If it cannot be + * found, an empty string is returned. + * + * @return string + */ + public static function getModifiedSinceHeader() + { + $modifiedSince = ''; + if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { + $modifiedSince = $_SERVER['HTTP_IF_MODIFIED_SINCE']; + + // strip any trailing data appended to header + if (false !== ($semicolonPos = strpos($modifiedSince, ';'))) { + $modifiedSince = substr($modifiedSince, 0, $semicolonPos); + } + } + return $modifiedSince; + } + + /** + * Returns Proxy to use for connecting via HTTP to given URL + * + * @param string $url + * @return array + */ + private static function getProxyConfiguration($url) + { + $hostname = UrlHelper::getHostFromUrl($url); + + if (Url::isLocalHost($hostname)) { + return array(null, null, null, null); + } + + // proxy configuration + $proxyHost = Config::getInstance()->proxy['host']; + $proxyPort = Config::getInstance()->proxy['port']; + $proxyUser = Config::getInstance()->proxy['username']; + $proxyPassword = Config::getInstance()->proxy['password']; + + return array($proxyHost, $proxyPort, $proxyUser, $proxyPassword); + } } diff --git a/www/analytics/core/Http/ControllerResolver.php b/www/analytics/core/Http/ControllerResolver.php new file mode 100644 index 00000000..569fbee4 --- /dev/null +++ b/www/analytics/core/Http/ControllerResolver.php @@ -0,0 +1,141 @@ +abstractFactory = $abstractFactory; + } + + /** + * @param string $module + * @param string|null $action + * @param array $parameters + * @throws Exception Controller not found. + * @return callable The controller is a PHP callable. + */ + public function getController($module, $action, array &$parameters) + { + $controller = $this->createPluginController($module, $action); + if ($controller) { + return $controller; + } + + $controller = $this->createWidgetController($module, $action, $parameters); + if ($controller) { + return $controller; + } + + $controller = $this->createReportController($module, $action, $parameters); + if ($controller) { + return $controller; + } + + $controller = $this->createReportMenuController($module, $action, $parameters); + if ($controller) { + return $controller; + } + + throw new Exception(sprintf("Action '%s' not found in the module '%s'", $action, $module)); + } + + private function createPluginController($module, $action) + { + $controllerClass = "Piwik\\Plugins\\$module\\Controller"; + if (!class_exists($controllerClass)) { + return null; + } + + /** @var $controller Controller */ + $controller = $this->abstractFactory->make($controllerClass); + + $action = $action ?: $controller->getDefaultAction(); + + if (!is_callable(array($controller, $action))) { + return null; + } + + return array($controller, $action); + } + + private function createWidgetController($module, $action, array &$parameters) + { + $widget = Widgets::factory($module, $action); + + if (!$widget) { + return null; + } + + $parameters['widget'] = $widget; + $parameters['method'] = $action; + + return array($this->createCoreHomeController(), 'renderWidget'); + } + + private function createReportController($module, $action, array &$parameters) + { + $report = Report::factory($module, $action); + + if (!$report) { + return null; + } + + $parameters['report'] = $report; + + return array($this->createCoreHomeController(), 'renderReportWidget'); + } + + private function createReportMenuController($module, $action, array &$parameters) + { + if (!$this->isReportMenuAction($action)) { + return null; + } + + $action = lcfirst(substr($action, 4)); // menuGetPageUrls => getPageUrls + $report = Report::factory($module, $action); + + if (!$report) { + return null; + } + + $parameters['report'] = $report; + + return array($this->createCoreHomeController(), 'renderReportMenu'); + } + + private function isReportMenuAction($action) + { + $startsWithMenu = (Report::PREFIX_ACTION_IN_MENU === substr($action, 0, strlen(Report::PREFIX_ACTION_IN_MENU))); + + return !empty($action) && $startsWithMenu; + } + + private function createCoreHomeController() + { + return $this->abstractFactory->make('Piwik\Plugins\CoreHome\Controller'); + } +} diff --git a/www/analytics/core/Http/Router.php b/www/analytics/core/Http/Router.php new file mode 100644 index 00000000..62547c7c --- /dev/null +++ b/www/analytics/core/Http/Router.php @@ -0,0 +1,39 @@ + $posDot) { - $ipString = substr($ipString, 0, $posColon); - } - // else: Dotted quad IPv6 address, A:B:C:D:E:F:G.H.I.J - } else if (strpos($ipString, ':') === $posColon) { - $ipString = substr($ipString, 0, $posColon); - } - // else: IPv6 address, A:B:C:D:E:F:G:H - } - // else: IPv4 address, A.B.C.D - - return $ipString; - } - - /** - * Sanitize human-readable (user-supplied) IP address range. - * - * Accepts the following formats for $ipRange: - * - single IPv4 address, e.g., 127.0.0.1 - * - single IPv6 address, e.g., ::1/128 - * - IPv4 block using CIDR notation, e.g., 192.168.0.0/22 represents the IPv4 addresses from 192.168.0.0 to 192.168.3.255 - * - IPv6 block using CIDR notation, e.g., 2001:DB8::/48 represents the IPv6 addresses from 2001:DB8:0:0:0:0:0:0 to 2001:DB8:0:FFFF:FFFF:FFFF:FFFF:FFFF - * - wildcards, e.g., 192.168.0.* - * - * @param string $ipRangeString IP address range - * @return string|bool IP address range in CIDR notation OR false - */ - public static function sanitizeIpRange($ipRangeString) - { - $ipRangeString = trim($ipRangeString); - if (empty($ipRangeString)) { - return false; - } - - // IPv4 address with wildcards '*' - if (strpos($ipRangeString, '*') !== false) { - if (preg_match('~(^|\.)\*\.\d+(\.|$)~D', $ipRangeString)) { - return false; - } - - $bits = 32 - 8 * substr_count($ipRangeString, '*'); - $ipRangeString = str_replace('*', '0', $ipRangeString); - } - - // CIDR - if (($pos = strpos($ipRangeString, '/')) !== false) { - $bits = substr($ipRangeString, $pos + 1); - $ipRangeString = substr($ipRangeString, 0, $pos); - } - - // single IP - if (($ip = @inet_pton($ipRangeString)) === false) - return false; - - $maxbits = strlen($ip) * 8; - if (!isset($bits)) - $bits = $maxbits; - - if ($bits < 0 || $bits > $maxbits) { - return false; - } - - return "$ipRangeString/$bits"; - } - - /** - * Converts an IP address in presentation format to network address format. - * - * @param string $ipString IP address, either IPv4 or IPv6, e.g., `"127.0.0.1"`. - * @return string Binary-safe string, e.g., `"\x7F\x00\x00\x01"`. - */ - public static function P2N($ipString) - { - // use @inet_pton() because it throws an exception and E_WARNING on invalid input - $ip = @inet_pton($ipString); - return $ip === false ? "\x00\x00\x00\x00" : $ip; - } - - /** - * Convert network address format to presentation format. - * - * See also {@link prettyPrint()}. - * - * @param string $ip IP address in network address format. - * @return string IP address in presentation format. - */ - public static function N2P($ip) - { - // use @inet_ntop() because it throws an exception and E_WARNING on invalid input - $ipStr = @inet_ntop($ip); - return $ipStr === false ? '0.0.0.0' : $ipStr; - } - - /** - * Alias for {@link N2P()}. - * - * @param string $ip IP address in network address format. - * @return string IP address in presentation format. - */ - public static function prettyPrint($ip) - { - return self::N2P($ip); - } - - /** - * Returns true if `$ip` is an IPv4, IPv4-compat, or IPv4-mapped address, false - * if otherwise. - * - * @param string $ip IP address in network address format. - * @return bool True if IPv4, else false. - */ - public static function isIPv4($ip) - { - // in case mbstring overloads strlen and substr functions - $strlen = function_exists('mb_orig_strlen') ? 'mb_orig_strlen' : 'strlen'; - $substr = function_exists('mb_orig_substr') ? 'mb_orig_substr' : 'substr'; - - // IPv4 - if ($strlen($ip) == 4) { - return true; - } - - // IPv6 - transitional address? - if ($strlen($ip) == 16) { - if (substr_compare($ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff", 0, 12) === 0 - || substr_compare($ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 0, 12) === 0 - ) { - return true; - } - } - - return false; - } - - /** - * Converts an IP address (in network address format) to presentation format. - * This is a backward compatibility function for code that only expects - * IPv4 addresses (i.e., doesn't support IPv6). - * - * This function does not support the long (or its string representation) - * returned by the built-in ip2long() function, from Piwik 1.3 and earlier. - * - * @param string $ip IPv4 address in network address format. - * @return string IP address in presentation format. - */ - public static function long2ip($ip) - { - // IPv4 - if (strlen($ip) == 4) { - return self::N2P($ip); - } - - // IPv6 - transitional address? - if (strlen($ip) == 16) { - if (substr_compare($ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff", 0, 12) === 0 - || substr_compare($ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 0, 12) === 0 - ) { - // remap 128-bit IPv4-mapped and IPv4-compat addresses - return self::N2P(substr($ip, 12)); - } - } - - return '0.0.0.0'; - } - - /** - * Returns true if $ip is an IPv6 address, false if otherwise. This function does - * a naive check. It assumes that whatever format $ip is in, it is well-formed. - * - * @param string $ip - * @return bool - */ - public static function isIPv6($ip) - { - return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6); - } - - /** - * Returns true if $ip is a IPv4 mapped address, false if otherwise. - * - * @param string $ip - * @return bool - */ - public static function isMappedIPv4($ip) - { - return substr($ip, 0, strlen(self::MAPPED_IPv4_START)) === self::MAPPED_IPv4_START; - } - - /** - * Returns an IPv4 address from a 'mapped' IPv6 address. - * - * @param string $ip eg, `'::ffff:192.0.2.128'` - * @return string eg, `'192.0.2.128'` - */ - public static function getIPv4FromMappedIPv6($ip) - { - return substr($ip, strlen(self::MAPPED_IPv4_START)); - } - - /** - * Get low and high IP addresses for a specified range. - * - * @param array $ipRange An IP address range in presentation format. - * @return array|bool Array `array($lowIp, $highIp)` in network address format, or false on failure. - */ - public static function getIpsForRange($ipRange) - { - if (strpos($ipRange, '/') === false) { - $ipRange = self::sanitizeIpRange($ipRange); - } - $pos = strpos($ipRange, '/'); - - $bits = substr($ipRange, $pos + 1); - $range = substr($ipRange, 0, $pos); - $high = $low = @inet_pton($range); - if ($low === false) { - return false; - } - - $lowLen = strlen($low); - $i = $lowLen - 1; - $bits = $lowLen * 8 - $bits; - - for ($n = (int)($bits / 8); $n > 0; $n--, $i--) { - $low[$i] = chr(0); - $high[$i] = chr(255); - } - - $n = $bits % 8; - if ($n) { - $low[$i] = chr(ord($low[$i]) & ~((1 << $n) - 1)); - $high[$i] = chr(ord($high[$i]) | ((1 << $n) - 1)); - } - - return array($low, $high); - } - - /** - * Determines if an IP address is in a specified IP address range. - * - * An IPv4-mapped address should be range checked with an IPv4-mapped address range. - * - * @param string $ip IP address in network address format - * @param array $ipRanges List of IP address ranges - * @return bool True if in any of the specified IP address ranges; else false. - */ - public static function isIpInRange($ip, $ipRanges) - { - $ipLen = strlen($ip); - if (empty($ip) || empty($ipRanges) || ($ipLen != 4 && $ipLen != 16)) { - return false; - } - - foreach ($ipRanges as $range) { - if (is_array($range)) { - // already split into low/high IP addresses - $range[0] = self::P2N($range[0]); - $range[1] = self::P2N($range[1]); - } else { - // expect CIDR format but handle some variations - $range = self::getIpsForRange($range); - } - if ($range === false) { - continue; - } - - $low = $range[0]; - $high = $range[1]; - if (strlen($low) != $ipLen) { - continue; - } - - // binary-safe string comparison - if ($ip >= $low && $ip <= $high) { - return true; - } - } - - return false; - } - /** * Returns the most accurate IP address availble for the current user, in * IPv4 format. This could be the proxy client's IP address. @@ -346,7 +45,8 @@ class IP */ public static function getIpFromHeader() { - $clientHeaders = @Config::getInstance()->General['proxy_client_headers']; + $general = Config::getInstance()->General; + $clientHeaders = @$general['proxy_client_headers']; if (!is_array($clientHeaders)) { $clientHeaders = array(); } @@ -357,7 +57,7 @@ class IP } $ipString = self::getNonProxyIpFromHeader($default, $clientHeaders); - return self::sanitizeIp($ipString); + return IPUtils::sanitizeIp($ipString); } /** @@ -371,7 +71,7 @@ class IP { $proxyIps = array(); $config = Config::getInstance()->General; - if(isset($config['proxy_ips'])) { + if (isset($config['proxy_ips'])) { $proxyIps = $config['proxy_ips']; } if (!is_array($proxyIps)) { @@ -383,6 +83,9 @@ class IP // examine proxy headers foreach ($proxyHeaders as $proxyHeader) { if (!empty($_SERVER[$proxyHeader])) { + // this may be buggy if someone has proxy IPs and proxy host headers configured as + // `$_SERVER[$proxyHeader]` could be eg $_SERVER['HTTP_X_FORWARDED_HOST'] and + // include an actual host name, not an IP $proxyIp = self::getLastIpFromList($_SERVER[$proxyHeader], $proxyIps); if (strlen($proxyIp) && stripos($proxyIp, 'unknown') === false) { return $proxyIp; @@ -398,7 +101,7 @@ class IP * * @param string $csv Comma separated list of elements. * @param array $excludedIps Optional list of excluded IP addresses (or IP address ranges). - * @return string Last (non-excluded) IP address in the list. + * @return string Last (non-excluded) IP address in the list or an empty string if all given IPs are excluded. */ public static function getLastIpFromList($csv, $excludedIps = null) { @@ -407,25 +110,14 @@ class IP $elements = explode(',', $csv); for ($i = count($elements); $i--;) { $element = trim(Common::sanitizeInputValue($elements[$i])); - if (empty($excludedIps) || (!in_array($element, $excludedIps) && !self::isIpInRange(self::P2N(self::sanitizeIp($element)), $excludedIps))) { + $ip = \Piwik\Network\IP::fromStringIP(IPUtils::sanitizeIp($element)); + if (empty($excludedIps) || (!in_array($element, $excludedIps) && !$ip->isInRanges($excludedIps))) { return $element; } } + + return ''; } return trim(Common::sanitizeInputValue($csv)); } - - /** - * Retirms the hostname for a given IP address. - * - * @param string $ipStr Human-readable IP address. - * @return string The hostname or unmodified $ipStr on failure. - */ - public static function getHostByAddr($ipStr) - { - // PHP's reverse lookup supports ipv4 and ipv6 - // except on Windows before PHP 5.3 - $host = strtolower(@gethostbyaddr($ipStr)); - return $host === '' ? $ipStr : $host; - } } diff --git a/www/analytics/core/Intl/Data/Provider/CurrencyDataProvider.php b/www/analytics/core/Intl/Data/Provider/CurrencyDataProvider.php new file mode 100644 index 00000000..ae73a5bc --- /dev/null +++ b/www/analytics/core/Intl/Data/Provider/CurrencyDataProvider.php @@ -0,0 +1,33 @@ + array('$', 'US dollar'))`. + * @api + */ + public function getCurrencyList() + { + if ($this->currencyList === null) { + $this->currencyList = require __DIR__ . '/../Resources/currencies.php'; + } + + return $this->currencyList; + } +} diff --git a/www/analytics/core/Intl/Data/Provider/DateTimeFormatProvider.php b/www/analytics/core/Intl/Data/Provider/DateTimeFormatProvider.php new file mode 100644 index 00000000..90ffad60 --- /dev/null +++ b/www/analytics/core/Intl/Data/Provider/DateTimeFormatProvider.php @@ -0,0 +1,83 @@ + language name (in english). + * E.g. `array('en' => 'English', 'ja' => 'Japanese')`. + * @api + */ + public function getLanguageList() + { + if ($this->languageList === null) { + $this->languageList = require __DIR__ . '/../Resources/languages.php'; + } + + return $this->languageList; + } + + /** + * Returns the list of language to country mappings. + * + * @return string[] Array of 2 letter ISO language code => 2 letter ISO country code. + * E.g. `array('fr' => 'fr') // French => France`. + * @api + */ + public function getLanguageToCountryList() + { + if ($this->languageToCountryList === null) { + $this->languageToCountryList = require __DIR__ . '/../Resources/languages-to-countries.php'; + } + + return $this->languageToCountryList; + } +} diff --git a/www/analytics/core/Intl/Data/Provider/RegionDataProvider.php b/www/analytics/core/Intl/Data/Provider/RegionDataProvider.php new file mode 100644 index 00000000..e6661487 --- /dev/null +++ b/www/analytics/core/Intl/Data/Provider/RegionDataProvider.php @@ -0,0 +1,57 @@ +continentList === null) { + $this->continentList = require __DIR__ . '/../Resources/continents.php'; + } + + return $this->continentList; + } + + /** + * Returns the list of valid country codes. + * + * @param bool $includeInternalCodes + * @return string[] Array of 2 letter country ISO codes => 3 letter continent code + * @api + */ + public function getCountryList($includeInternalCodes = false) + { + if ($this->countryList === null) { + $this->countryList = require __DIR__ . '/../Resources/countries.php'; + } + if ($this->countryExtraList === null) { + $this->countryExtraList = require __DIR__ . '/../Resources/countries-extra.php'; + } + + if ($includeInternalCodes) { + return array_merge($this->countryList, $this->countryExtraList); + } + + return $this->countryList; + } +} diff --git a/www/analytics/core/Intl/Data/Resources/continents.php b/www/analytics/core/Intl/Data/Resources/continents.php new file mode 100644 index 00000000..4e346b2c --- /dev/null +++ b/www/analytics/core/Intl/Data/Resources/continents.php @@ -0,0 +1,24 @@ + 'unk', + + // exceptionally reserved + 'ac' => 'afr', // .ac TLD + 'cp' => 'amc', + 'dg' => 'asi', + 'ea' => 'afr', + 'eu' => 'eur', // .eu TLD + 'fx' => 'eur', + 'ic' => 'afr', + 'su' => 'eur', // .su TLD + 'ta' => 'afr', + 'uk' => 'eur', // .uk TLD + + // transitionally reserved + 'an' => 'amc', // former Netherlands Antilles + 'bu' => 'asi', + 'cs' => 'eur', // former Serbia and Montenegro + 'nt' => 'asi', + 'sf' => 'eur', + 'tp' => 'oce', // .tp TLD + 'yu' => 'eur', // .yu TLD + 'zr' => 'afr', + + // MaxMind GeoIP specific + 'a1' => 'unk', + 'a2' => 'unk', + 'ap' => 'asi', + 'o1' => 'unk', + + // Catalonia (Spain) + 'cat' => 'eur', +); diff --git a/www/analytics/core/Intl/Data/Resources/countries.php b/www/analytics/core/Intl/Data/Resources/countries.php new file mode 100644 index 00000000..90485095 --- /dev/null +++ b/www/analytics/core/Intl/Data/Resources/countries.php @@ -0,0 +1,272 @@ + 'eur', + 'ae' => 'asi', + 'af' => 'asi', + 'ag' => 'amc', + 'ai' => 'amc', + 'al' => 'eur', + 'am' => 'asi', + 'ao' => 'afr', + 'aq' => 'ant', + 'ar' => 'ams', + 'as' => 'oce', + 'at' => 'eur', + 'au' => 'oce', + 'aw' => 'amc', + 'ax' => 'eur', + 'az' => 'asi', + 'ba' => 'eur', + 'bb' => 'amc', + 'bd' => 'asi', + 'be' => 'eur', + 'bf' => 'afr', + 'bg' => 'eur', + 'bh' => 'asi', + 'bi' => 'afr', + 'bj' => 'afr', + 'bl' => 'amc', + 'bm' => 'amc', + 'bn' => 'asi', + 'bo' => 'ams', + 'bq' => 'amc', + 'br' => 'ams', + 'bs' => 'amc', + 'bt' => 'asi', + 'bv' => 'ant', + 'bw' => 'afr', + 'by' => 'eur', + 'bz' => 'amc', + 'ca' => 'amn', + 'cc' => 'asi', + 'cd' => 'afr', + 'cf' => 'afr', + 'cg' => 'afr', + 'ch' => 'eur', + 'ci' => 'afr', + 'ck' => 'oce', + 'cl' => 'ams', + 'cm' => 'afr', + 'cn' => 'asi', + 'co' => 'ams', + 'cr' => 'amc', + 'cu' => 'amc', + 'cv' => 'afr', + 'cw' => 'amc', + 'cx' => 'asi', + 'cy' => 'eur', + 'cz' => 'eur', + 'de' => 'eur', + 'dj' => 'afr', + 'dk' => 'eur', + 'dm' => 'amc', + 'do' => 'amc', + 'dz' => 'afr', + 'ec' => 'ams', + 'ee' => 'eur', + 'eg' => 'afr', + 'eh' => 'afr', + 'er' => 'afr', + 'es' => 'eur', + 'et' => 'afr', + 'fi' => 'eur', + 'fj' => 'oce', + 'fk' => 'ams', + 'fm' => 'oce', + 'fo' => 'eur', + 'fr' => 'eur', + 'ga' => 'afr', + 'gb' => 'eur', + 'gd' => 'amc', + 'ge' => 'asi', + 'gf' => 'ams', + 'gg' => 'eur', + 'gh' => 'afr', + 'gi' => 'eur', + 'gl' => 'amn', + 'gm' => 'afr', + 'gn' => 'afr', + 'gp' => 'amc', + 'gq' => 'afr', + 'gr' => 'eur', + 'gs' => 'ant', + 'gt' => 'amc', + 'gu' => 'oce', + 'gw' => 'afr', + 'gy' => 'ams', + 'hk' => 'asi', + 'hm' => 'ant', + 'hn' => 'amc', + 'hr' => 'eur', + 'ht' => 'amc', + 'hu' => 'eur', + 'id' => 'asi', + 'ie' => 'eur', + 'il' => 'asi', + 'im' => 'eur', + 'in' => 'asi', + 'io' => 'asi', + 'iq' => 'asi', + 'ir' => 'asi', + 'is' => 'eur', + 'it' => 'eur', + 'je' => 'eur', + 'jm' => 'amc', + 'jo' => 'asi', + 'jp' => 'asi', + 'ke' => 'afr', + 'kg' => 'asi', + 'kh' => 'asi', + 'ki' => 'oce', + 'km' => 'afr', + 'kn' => 'amc', + 'kp' => 'asi', + 'kr' => 'asi', + 'kw' => 'asi', + 'ky' => 'amc', + 'kz' => 'asi', + 'la' => 'asi', + 'lb' => 'asi', + 'lc' => 'amc', + 'li' => 'eur', + 'lk' => 'asi', + 'lr' => 'afr', + 'ls' => 'afr', + 'lt' => 'eur', + 'lu' => 'eur', + 'lv' => 'eur', + 'ly' => 'afr', + 'ma' => 'afr', + 'mc' => 'eur', + 'md' => 'eur', + 'me' => 'eur', + 'mf' => 'amc', + 'mg' => 'afr', + 'mh' => 'oce', + 'mk' => 'eur', + 'ml' => 'afr', + 'mm' => 'asi', + 'mn' => 'asi', + 'mo' => 'asi', + 'mp' => 'oce', + 'mq' => 'amc', + 'mr' => 'afr', + 'ms' => 'amc', + 'mt' => 'eur', + 'mu' => 'afr', + 'mv' => 'asi', + 'mw' => 'afr', + 'mx' => 'amn', + 'my' => 'asi', + 'mz' => 'afr', + 'na' => 'afr', + 'nc' => 'oce', + 'ne' => 'afr', + 'nf' => 'oce', + 'ng' => 'afr', + 'ni' => 'amc', + 'nl' => 'eur', + 'no' => 'eur', + 'np' => 'asi', + 'nr' => 'oce', + 'nu' => 'oce', + 'nz' => 'oce', + 'om' => 'asi', + 'pa' => 'amc', + 'pe' => 'ams', + 'pf' => 'oce', + 'pg' => 'oce', + 'ph' => 'asi', + 'pk' => 'asi', + 'pl' => 'eur', + 'pm' => 'amn', + 'pn' => 'oce', + 'pr' => 'amc', + 'ps' => 'asi', + 'pt' => 'eur', + 'pw' => 'oce', + 'py' => 'ams', + 'qa' => 'asi', + 're' => 'afr', + 'ro' => 'eur', + 'rs' => 'eur', + 'ru' => 'eur', + 'rw' => 'afr', + 'sa' => 'asi', + 'sb' => 'oce', + 'sc' => 'afr', + 'sd' => 'afr', + 'se' => 'eur', + 'sg' => 'asi', + 'sh' => 'afr', + 'si' => 'eur', + 'sj' => 'eur', + 'sk' => 'eur', + 'sl' => 'afr', + 'sm' => 'eur', + 'sn' => 'afr', + 'so' => 'afr', + 'sr' => 'ams', + 'ss' => 'afr', + 'st' => 'afr', + 'sv' => 'amc', + 'sx' => 'amc', + 'sy' => 'asi', + 'sz' => 'afr', + 'tc' => 'amc', + 'td' => 'afr', + 'tf' => 'ant', + 'tg' => 'afr', + 'th' => 'asi', + 'ti' => 'asi', // Tibet (no iso 3166 code) + 'tj' => 'asi', + 'tk' => 'oce', + 'tl' => 'asi', + 'tm' => 'asi', + 'tn' => 'afr', + 'to' => 'oce', + 'tr' => 'eur', + 'tt' => 'amc', + 'tv' => 'oce', + 'tw' => 'asi', + 'tz' => 'afr', + 'ua' => 'eur', + 'ug' => 'afr', + 'um' => 'oce', + 'us' => 'amn', + 'uy' => 'ams', + 'uz' => 'asi', + 'va' => 'eur', + 'vc' => 'amc', + 've' => 'ams', + 'vg' => 'amc', + 'vi' => 'amc', + 'vn' => 'asi', + 'vu' => 'oce', + 'wf' => 'oce', + 'ws' => 'oce', + 'ye' => 'asi', + 'yt' => 'afr', + 'za' => 'afr', + 'zm' => 'afr', + 'zw' => 'afr', +); diff --git a/www/analytics/core/Intl/Data/Resources/currencies.php b/www/analytics/core/Intl/Data/Resources/currencies.php new file mode 100644 index 00000000..6190c310 --- /dev/null +++ b/www/analytics/core/Intl/Data/Resources/currencies.php @@ -0,0 +1,183 @@ + array('currency symbol', 'description'), + + // Top 5 by global trading volume + 'USD' => array('$', 'US dollar'), + 'EUR' => array('€', 'Euro'), + 'JPY' => array('¥', 'Japanese yen'), + 'GBP' => array('£', 'British pound'), + 'CHF' => array('Fr', 'Swiss franc'), + + 'AFN' => array('؋', 'Afghan afghani'), + 'ALL' => array('L', 'Albanian lek'), + 'DZD' => array('د.ج', 'Algerian dinar'), + 'AOA' => array('Kz', 'Angolan kwanza'), + 'ARS' => array('$', 'Argentine peso'), + 'AMD' => array('դր.', 'Armenian dram'), + 'AWG' => array('ƒ', 'Aruban florin'), + 'AUD' => array('$', 'Australian dollar'), + 'AZN' => array('m', 'Azerbaijani manat'), + 'BSD' => array('$', 'Bahamian dollar'), + 'BHD' => array('.د.ب', 'Bahraini dinar'), + 'BDT' => array('৳', 'Bangladeshi taka'), + 'BBD' => array('$', 'Barbadian dollar'), + 'BYR' => array('Br', 'Belarusian ruble'), + 'BZD' => array('$', 'Belize dollar'), + 'BMD' => array('$', 'Bermudian dollar'), + 'BTC' => array('BTC', 'Bitcoin'), + 'BTN' => array('Nu.', 'Bhutanese ngultrum'), + 'BOB' => array('Bs.', 'Bolivian boliviano'), + 'BAM' => array('KM', 'Bosnia Herzegovina mark'), + 'BWP' => array('P', 'Botswana pula'), + 'BRL' => array('R$', 'Brazilian real'), +// 'GBP' => array('£', 'British pound'), + 'BND' => array('$', 'Brunei dollar'), + 'BGN' => array('лв', 'Bulgarian lev'), + 'BIF' => array('Fr', 'Burundian franc'), + 'KHR' => array('៛', 'Cambodian riel'), + 'CAD' => array('$', 'Canadian dollar'), + 'CVE' => array('$', 'Cape Verdean escudo'), + 'KYD' => array('$', 'Cayman Islands dollar'), + 'XAF' => array('Fr', 'Central African CFA franc'), + 'CLP' => array('$', 'Chilean peso'), + 'CNY' => array('元', 'Chinese yuan'), + 'COP' => array('$', 'Colombian peso'), + 'KMF' => array('Fr', 'Comorian franc'), + 'CDF' => array('Fr', 'Congolese franc'), + 'CRC' => array('₡', 'Costa Rican colón'), + 'HRK' => array('kn', 'Croatian kuna'), + 'XPF' => array('F', 'CFP franc'), + 'CUC' => array('$', 'Cuban convertible peso'), + 'CUP' => array('$', 'Cuban peso'), + 'CMG' => array('ƒ', 'Curaçao and Sint Maarten guilder'), + 'CZK' => array('Kč', 'Czech koruna'), + 'DKK' => array('kr', 'Danish krone'), + 'DJF' => array('Fr', 'Djiboutian franc'), + 'DOP' => array('$', 'Dominican peso'), + 'XCD' => array('$', 'East Caribbean dollar'), + 'EGP' => array('ج.م', 'Egyptian pound'), + 'ERN' => array('Nfk', 'Eritrean nakfa'), + 'ETB' => array('Br', 'Ethiopian birr'), +// 'EUR' => array('€', 'Euro'), + 'FKP' => array('£', 'Falkland Islands pound'), + 'FJD' => array('$', 'Fijian dollar'), + 'GMD' => array('D', 'Gambian dalasi'), + 'GEL' => array('ლ', 'Georgian lari'), + 'GHS' => array('₵', 'Ghanaian cedi'), + 'GIP' => array('£', 'Gibraltar pound'), + 'GTQ' => array('Q', 'Guatemalan quetzal'), + 'GNF' => array('Fr', 'Guinean franc'), + 'GYD' => array('$', 'Guyanese dollar'), + 'HTG' => array('G', 'Haitian gourde'), + 'HNL' => array('L', 'Honduran lempira'), + 'HKD' => array('$', 'Hong Kong dollar'), + 'HUF' => array('Ft', 'Hungarian forint'), + 'ISK' => array('kr', 'Icelandic króna'), + 'INR' => array('‎₹', 'Indian rupee'), + 'IDR' => array('Rp', 'Indonesian rupiah'), + 'IRR' => array('﷼', 'Iranian rial'), + 'IQD' => array('ع.د', 'Iraqi dinar'), + 'ILS' => array('₪', 'Israeli new shekel'), + 'JMD' => array('$', 'Jamaican dollar'), +// 'JPY' => array('¥', 'Japanese yen'), + 'JOD' => array('د.ا', 'Jordanian dinar'), + 'KZT' => array('₸', 'Kazakhstani tenge'), + 'KES' => array('Sh', 'Kenyan shilling'), + 'KWD' => array('د.ك', 'Kuwaiti dinar'), + 'KGS' => array('лв', 'Kyrgyzstani som'), + 'LAK' => array('₭', 'Lao kip'), + 'LBP' => array('ل.ل', 'Lebanese pound'), + 'LSL' => array('L', 'Lesotho loti'), + 'LRD' => array('$', 'Liberian dollar'), + 'LYD' => array('ل.د', 'Libyan dinar'), + 'LTL' => array('Lt', 'Lithuanian litas'), + 'MOP' => array('P', 'Macanese pataca'), + 'MKD' => array('ден', 'Macedonian denar'), + 'MGA' => array('Ar', 'Malagasy ariary'), + 'MWK' => array('MK', 'Malawian kwacha'), + 'MYR' => array('RM', 'Malaysian ringgit'), + 'MVR' => array('ރ.', 'Maldivian rufiyaa'), + 'MRO' => array('UM', 'Mauritanian ouguiya'), + 'MUR' => array('₨', 'Mauritian rupee'), + 'MXN' => array('$', 'Mexican peso'), + 'MDL' => array('L', 'Moldovan leu'), + 'MNT' => array('₮', 'Mongolian tögrög'), + 'MAD' => array('د.م.', 'Moroccan dirham'), + 'MZN' => array('MTn', 'Mozambican metical'), + 'MMK' => array('K', 'Myanma kyat'), + 'NAD' => array('$', 'Namibian dollar'), + 'NPR' => array('₨', 'Nepalese rupee'), + 'ANG' => array('ƒ', 'Netherlands Antillean guilder'), + 'TWD' => array('$', 'New Taiwan dollar'), + 'NZD' => array('$', 'New Zealand dollar'), + 'NIO' => array('C$', 'Nicaraguan córdoba'), + 'NGN' => array('₦', 'Nigerian naira'), + 'KPW' => array('₩', 'North Korean won'), + 'NOK' => array('kr', 'Norwegian krone'), + 'OMR' => array('ر.ع.', 'Omani rial'), + 'PKR' => array('₨', 'Pakistani rupee'), + 'PAB' => array('B/.', 'Panamanian balboa'), + 'PGK' => array('K', 'Papua New Guinean kina'), + 'PYG' => array('₲', 'Paraguayan guaraní'), + 'PEN' => array('S/.', 'Peruvian nuevo sol'), + 'PHP' => array('₱', 'Philippine peso'), + 'PLN' => array('zł', 'Polish złoty'), + 'QAR' => array('ر.ق', 'Qatari riyal'), + 'RON' => array('L', 'Romanian leu'), + 'RUB' => array('руб.', 'Russian ruble'), + 'RWF' => array('Fr', 'Rwandan franc'), + 'SHP' => array('£', 'Saint Helena pound'), + 'SVC' => array('₡', 'Salvadoran colón'), + 'WST' => array('T', 'Samoan tala'), + 'STD' => array('Db', 'São Tomé and Príncipe dobra'), + 'SAR' => array('ر.س', 'Saudi riyal'), + 'RSD' => array('дин. or din.', 'Serbian dinar'), + 'SCR' => array('₨', 'Seychellois rupee'), + 'SLL' => array('Le', 'Sierra Leonean leone'), + 'SGD' => array('$', 'Singapore dollar'), + 'SBD' => array('$', 'Solomon Islands dollar'), + 'SOS' => array('Sh', 'Somali shilling'), + 'ZAR' => array('R', 'South African rand'), + 'KRW' => array('₩', 'South Korean won'), + 'LKR' => array('Rs', 'Sri Lankan rupee'), + 'SDG' => array('جنيه سوداني', 'Sudanese pound'), + 'SRD' => array('$', 'Surinamese dollar'), + 'SZL' => array('L', 'Swazi lilangeni'), + 'SEK' => array('kr', 'Swedish krona'), +// 'CHF' => array('Fr', 'Swiss franc'), + 'SYP' => array('ل.س', 'Syrian pound'), + 'TJS' => array('ЅМ', 'Tajikistani somoni'), + 'TZS' => array('Sh', 'Tanzanian shilling'), + 'THB' => array('฿', 'Thai baht'), + 'TOP' => array('T$', 'Tongan paʻanga'), + 'TTD' => array('$', 'Trinidad and Tobago dollar'), + 'TND' => array('د.ت', 'Tunisian dinar'), + 'TRY' => array('TL', 'Turkish lira'), + 'TMM' => array('m', 'Turkmenistani manat'), + 'UGX' => array('Sh', 'Ugandan shilling'), + 'UAH' => array('₴', 'Ukrainian hryvnia'), + 'AED' => array('د.إ', 'United Arab Emirates dirham'), +// 'USD' => array('$', 'United States dollar'), + 'UYU' => array('$', 'Uruguayan peso'), + 'UZS' => array('лв', 'Uzbekistani som'), + 'VUV' => array('Vt', 'Vanuatu vatu'), + 'VEF' => array('Bs F', 'Venezuelan bolívar'), + 'VND' => array('₫', 'Vietnamese đồng'), + 'XOF' => array('Fr', 'West African CFA franc'), + 'YER' => array('﷼', 'Yemeni rial'), + 'ZMW' => array('ZK', 'Zambian kwacha'), + 'ZWL' => array('$', 'Zimbabwean dollar'), +); diff --git a/www/analytics/core/Intl/Data/Resources/languages-to-countries.php b/www/analytics/core/Intl/Data/Resources/languages-to-countries.php new file mode 100644 index 00000000..91ab0940 --- /dev/null +++ b/www/analytics/core/Intl/Data/Resources/languages-to-countries.php @@ -0,0 +1,60 @@ + 'bg', // Bulgarian => Bulgaria + 'ca' => 'es', // Catalan => Spain + 'cs' => 'cz', // Czech => Czech Republic + 'da' => 'dk', // Danish => Denmark + 'de' => 'de', // German => Germany + 'el' => 'gr', // Greek => Greece + 'es' => 'es', // Spanish => Spain + 'et' => 'ee', // Estonian => Estonia + 'fa' => 'ir', // Farsi => Iran + 'fi' => 'fi', // Finnish => Finland + 'fr' => 'fr', // French => France + 'he' => 'il', // Hebrew => Israel + 'hr' => 'hr', // Croatian => Croatia + 'hu' => 'hu', // Hungarian => Hungary + 'id' => 'id', // Indonesian => Indonesia + 'is' => 'is', // Icelandic => Iceland + 'it' => 'it', // Italian => Italy + 'ja' => 'jp', // Japanese => Japan + 'ko' => 'kr', // Korean => South Korea + 'lt' => 'lt', // Lithuanian => Lithuania + 'lv' => 'lv', // Latvian => Latvia + 'mk' => 'mk', // Macedonian => Macedonia + 'ms' => 'my', // Malay => Malaysia + 'nb' => 'no', // Bokmål => Norway + 'nl' => 'nl', // Dutch => Netherlands + 'nn' => 'no', // Nynorsk => Norway + 'no' => 'no', // Norwegian => Norway + 'pl' => 'pl', // Polish => Poland + 'pt' => 'pt', // Portugese => Portugal + 'ro' => 'ro', // Romanian => Romania + 'ru' => 'ru', // Russian => Russia + 'sk' => 'sk', // Slovak => Slovakia + 'sl' => 'si', // Slovene => Slovenia + 'sq' => 'al', // Albanian => Albania + 'sr' => 'rs', // Serbian => Serbia + 'sv' => 'se', // Swedish => Sweden + 'th' => 'th', // Thai => Thailand + 'bo' => 'ti', // Tibetan => Tibet + 'tr' => 'tr', // Turkish => Turkey + 'uk' => 'ua', // Ukrainian => Ukraine +); diff --git a/www/analytics/core/Intl/Data/Resources/languages.php b/www/analytics/core/Intl/Data/Resources/languages.php new file mode 100644 index 00000000..ca6930f3 --- /dev/null +++ b/www/analytics/core/Intl/Data/Resources/languages.php @@ -0,0 +1,201 @@ + array('Afar'), + 'ab' => array('Abkhazian'), + 'ae' => array('Avestan'), + 'af' => array('Afrikaans'), + 'ak' => array('Akan'), + 'am' => array('Amharic'), + 'an' => array('Aragonese'), + 'ar' => array('Arabic'), + 'as' => array('Assamese'), + 'av' => array('Avaric'), + 'ay' => array('Aymara'), + 'az' => array('Azerbaijani'), + 'ba' => array('Bashkir'), + 'be' => array('Belarusian'), + 'bg' => array('Bulgarian'), + 'bh' => array('Bihari'), // 'Bihari languages' + 'bi' => array('Bislama'), + 'bm' => array('Bambara'), + 'bn' => array('Bengali'), + 'bo' => array('Tibetan'), + 'br' => array('Breton'), + 'bs' => array('Bosnian'), + 'ca' => array('Catalan', 'Valencian'), + 'ce' => array('Chechen'), + 'ch' => array('Chamorro'), + 'co' => array('Corsican'), + 'cr' => array('Cree'), + 'cs' => array('Czech'), + 'cu' => array('Church Slavic', 'Old Slavonic', 'Church Slavonic', 'Old Bulgarian', 'Old Church Slavonic'), + 'cv' => array('Chuvash'), + 'cy' => array('Welsh'), + 'da' => array('Danish'), + 'de' => array('German'), + 'dv' => array('Divehi', 'Dhivehi', 'Maldivian'), + 'dz' => array('Dzongkha'), + 'ee' => array('Ewe'), + 'el' => array('Greek', 'Modern Greek', 'Hellenic'), // Greek, Modern (1453-) + 'en' => array('English'), + 'eo' => array('Esperanto'), + 'es' => array('Spanish', 'Castilian'), + 'et' => array('Estonian'), + 'eu' => array('Basque'), + 'fa' => array('Persian'), + 'ff' => array('Fulah'), + 'fi' => array('Finnish'), + 'fj' => array('Fijian'), + 'fo' => array('Faroese'), + 'fr' => array('French'), + 'fy' => array('Western Frisian'), + 'ga' => array('Irish'), + 'gd' => array('Gaelic', 'Scottish Gaelic'), + 'gl' => array('Galician'), + 'gn' => array('Guarani'), + 'gu' => array('Gujarati'), + 'gv' => array('Manx'), + 'ha' => array('Hausa'), + 'he' => array('Hebrew'), + 'hi' => array('Hindi'), + 'ho' => array('Hiri Motu'), + 'hr' => array('Croatian'), + 'ht' => array('Haitian', 'Haitian Creole'), + 'hu' => array('Hungarian'), + 'hy' => array('Armenian'), + 'hz' => array('Herero'), + 'ia' => array('Interlingua'), // 'Interlingua (International Auxiliary Language Association)' + 'id' => array('Indonesian'), + 'ie' => array('Interlingue', 'Occidental'), + 'ig' => array('Igbo'), + 'ii' => array('Sichuan Yi', 'Nuosu'), + 'ik' => array('Inupiaq'), + 'io' => array('Ido'), + 'is' => array('Icelandic'), + 'it' => array('Italian'), + 'iu' => array('Inuktitut'), + 'ja' => array('Japanese'), + 'jv' => array('Javanese'), + 'ka' => array('Georgian'), + 'kg' => array('Kongo'), + 'ki' => array('Kikuyu', 'Gikuyu'), + 'kj' => array('Kuanyama', 'Kwanyama'), + 'kk' => array('Kazakh'), + 'kl' => array('Kalaallisut', 'Greenlandic'), + 'km' => array('Central Khmer'), + 'kn' => array('Kannada'), + 'ko' => array('Korean'), + 'kr' => array('Kanuri'), + 'ks' => array('Kashmiri'), + 'ku' => array('Kurdish'), + 'kv' => array('Komi'), + 'kw' => array('Cornish'), + 'ky' => array('Kirghiz', 'Kyrgyz'), + 'la' => array('Latin'), + 'lb' => array('Luxembourgish', 'Letzeburgesch'), + 'lg' => array('Ganda'), + 'li' => array('Limburgan', 'Limburger', 'Limburgish'), + 'ln' => array('Lingala'), + 'lo' => array('Lao'), + 'lt' => array('Lithuanian'), + 'lu' => array('Luba-Katanga'), + 'lv' => array('Latvian'), + 'mg' => array('Malagasy'), + 'mh' => array('Marshallese'), + 'mi' => array('Maori'), + 'mk' => array('Macedonian'), + 'ml' => array('Malayalam'), + 'mn' => array('Mongolian'), +// 'mo' => array('Moldavian'), // deprecated + 'mr' => array('Marathi'), + 'ms' => array('Malay'), + 'mt' => array('Maltese'), + 'my' => array('Burmese'), + 'na' => array('Nauru'), + 'nb' => array('Norwegian Bokmål'), + 'nd' => array('North Ndebele'), + 'ne' => array('Nepali'), + 'ng' => array('Ndonga'), + 'nl' => array('Dutch', 'Flemish'), + 'nn' => array('Norwegian Nynorsk'), + 'no' => array('Norwegian'), + 'nr' => array('South Ndebele'), + 'nv' => array('Navajo', 'Navaho'), + 'ny' => array('Chichewa', 'Chewa', 'Nyanja'), + 'oc' => array('Occitan', 'Provençal'), // Occitan (post 1500) + 'oj' => array('Ojibwa'), + 'om' => array('Oromo'), + 'or' => array('Oriya'), + 'os' => array('Ossetian', 'Ossetic'), + 'pa' => array('Panjabi', 'Punjabi'), + 'pi' => array('Pali'), + 'pl' => array('Polish'), + 'ps' => array('Pushto', 'Pashto'), + 'pt' => array('Portuguese'), + 'qu' => array('Quechua'), + 'rm' => array('Romansh'), + 'rn' => array('Rundi'), + 'ro' => array('Romanian', 'Moldavian', 'Moldovan'), + 'ru' => array('Russian'), + 'rw' => array('Kinyarwanda'), + 'sa' => array('Sanskrit'), + 'sc' => array('Sardinian'), + 'sd' => array('Sindhi'), + 'se' => array('Northern Sami'), + 'sg' => array('Sango'), +// 'sh' => array('Serbo-Croatian'), // deprecated + 'si' => array('Sinhala', 'Sinhalese'), + 'sk' => array('Slovak'), + 'sl' => array('Slovenian'), + 'sm' => array('Samoan'), + 'sn' => array('Shona'), + 'so' => array('Somali'), + 'sq' => array('Albanian'), + 'sr' => array('Serbian'), + 'ss' => array('Swati'), + 'st' => array('Southern Soth'), + 'su' => array('Sundanese'), + 'sv' => array('Swedish'), + 'sw' => array('Swahili'), + 'ta' => array('Tamil'), + 'te' => array('Telugu'), + 'tg' => array('Tajik'), + 'th' => array('Thai'), + 'ti' => array('Tigrinya'), + 'tk' => array('Turkmen'), + 'tl' => array('Tagalog'), + 'tn' => array('Tswana'), + 'to' => array('Tonga'), // Tonga (Tonga Islands) + 'tr' => array('Turkish'), + 'ts' => array('Tsonga'), + 'tt' => array('Tatar'), + 'tw' => array('Twi'), + 'ty' => array('Tahitian'), + 'ug' => array('Uighur', 'Uyghur'), + 'uk' => array('Ukrainian'), + 'ur' => array('Urdu'), + 'uz' => array('Uzbek'), + 've' => array('Venda'), + 'vi' => array('Vietnamese'), + 'vo' => array('Volapük'), + 'wa' => array('Walloon'), + 'wo' => array('Wolof'), + 'xh' => array('Xhosa'), + 'yi' => array('Yiddish'), + 'yo' => array('Yoruba'), + 'za' => array('Zhuang', 'Chuang'), + 'zh' => array('Chinese'), + 'zu' => array('Zulu'), +); diff --git a/www/analytics/core/Intl/Locale.php b/www/analytics/core/Intl/Locale.php new file mode 100644 index 00000000..05cf51a8 --- /dev/null +++ b/www/analytics/core/Intl/Locale.php @@ -0,0 +1,42 @@ +doSomething(); - * } catch (Exception $unexpectedError) { - * $debugInfo = new MyDebugInfo($unexpectedError, $myThirdPartyServiceClient); - * Log::debug($debugInfo); - * } - * - * @method static \Piwik\Log getInstance() + * + * + * @deprecated Inject and use Psr\Log\LoggerInterface instead of this class. + * @see \Psr\Log\LoggerInterface */ class Log extends Singleton { @@ -116,86 +69,62 @@ class Log extends Singleton const LOGGER_FILE_PATH_CONFIG_OPTION = 'logger_file_path'; const STRING_MESSAGE_FORMAT_OPTION = 'string_message_format'; - const FORMAT_FILE_MESSAGE_EVENT = 'Log.formatFileMessage'; - - const FORMAT_SCREEN_MESSAGE_EVENT = 'Log.formatScreenMessage'; - - const FORMAT_DATABASE_MESSAGE_EVENT = 'Log.formatDatabaseMessage'; - - const GET_AVAILABLE_WRITERS_EVENT = 'Log.getAvailableWriters'; - /** - * The current logging level. Everything of equal or greater priority will be logged. - * Everything else will be ignored. - * - * @var int - */ - private $currentLogLevel = self::WARN; - - /** - * The array of callbacks executed when logging to a file. Each callback writes a log - * message to a logging backend. - * - * @var array - */ - private $writers = array(); - - /** - * The log message format string that turns a tag name, date-time and message into - * one string to log. + * The backtrace string to use when testing. * * @var string */ - private $logMessageFormat = "%level% %tag%[%datetime%] %message%"; + public static $debugBacktraceForTests; /** - * If we're logging to a file, this is the path to the file to log to. + * Singleton instance. * - * @var string + * @var Log */ - private $logToFilePath; + private static $instance; /** - * True if we're currently setup to log to a screen, false if otherwise. - * - * @var bool + * @var LoggerInterface */ - private $loggingToScreen; + private $logger; - /** - * Constructor. - */ - protected function __construct() + public static function getInstance() { - /** - * access a property that is not overriden by TestingEnvironment before accessing log as the - * log section is used in TestingEnvironment. Otherwise access to magic __get('log') fails in - * TestingEnvironment as it tries to acccess it already here with __get('log'). - * $config->log ==> __get('log') ==> Config.createConfigInstance ==> nested __get('log') ==> returns null - */ - $initConfigToPreventErrorWhenAccessingLog = Config::getInstance()->mail; + if (self::$instance === null) { + self::$instance = StaticContainer::get(__CLASS__); + } + return self::$instance; + } + public static function unsetInstance() + { + self::$instance = null; + } + public static function setSingletonInstance($instance) + { + self::$instance = $instance; + } - $logConfig = Config::getInstance()->log; - $this->setCurrentLogLevelFromConfig($logConfig); - $this->setLogWritersFromConfig($logConfig); - $this->setLogFilePathFromConfig($logConfig); - $this->setStringLogMessageFormat($logConfig); - $this->disableLoggingBasedOnConfig($logConfig); + /** + * @param LoggerInterface $logger + */ + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; } /** * Logs a message using the ERROR log level. * - * _Note: Messages logged with the ERROR level are always logged to the screen in addition - * to configured writers._ - * * @param string $message The log message. This can be a sprintf format string. * @param ... mixed Optional sprintf params. * @api + * + * @deprecated Inject and call Psr\Log\LoggerInterface::error() instead. + * @see \Psr\Log\LoggerInterface::error() */ public static function error($message /* ... */) { - self::logMessage(self::ERROR, $message, array_slice(func_get_args(), 1)); + self::logMessage(Logger::ERROR, $message, array_slice(func_get_args(), 1)); } /** @@ -204,10 +133,13 @@ class Log extends Singleton * @param string $message The log message. This can be a sprintf format string. * @param ... mixed Optional sprintf params. * @api + * + * @deprecated Inject and call Psr\Log\LoggerInterface::warning() instead. + * @see \Psr\Log\LoggerInterface::warning() */ public static function warning($message /* ... */) { - self::logMessage(self::WARN, $message, array_slice(func_get_args(), 1)); + self::logMessage(Logger::WARNING, $message, array_slice(func_get_args(), 1)); } /** @@ -216,10 +148,13 @@ class Log extends Singleton * @param string $message The log message. This can be a sprintf format string. * @param ... mixed Optional sprintf params. * @api + * + * @deprecated Inject and call Psr\Log\LoggerInterface::info() instead. + * @see \Psr\Log\LoggerInterface::info() */ public static function info($message /* ... */) { - self::logMessage(self::INFO, $message, array_slice(func_get_args(), 1)); + self::logMessage(Logger::INFO, $message, array_slice(func_get_args(), 1)); } /** @@ -228,10 +163,13 @@ class Log extends Singleton * @param string $message The log message. This can be a sprintf format string. * @param ... mixed Optional sprintf params. * @api + * + * @deprecated Inject and call Psr\Log\LoggerInterface::debug() instead. + * @see \Psr\Log\LoggerInterface::debug() */ public static function debug($message /* ... */) { - self::logMessage(self::DEBUG, $message, array_slice(func_get_args(), 1)); + self::logMessage(Logger::DEBUG, $message, array_slice(func_get_args(), 1)); } /** @@ -240,396 +178,70 @@ class Log extends Singleton * @param string $message The log message. This can be a sprintf format string. * @param ... mixed Optional sprintf params. * @api + * + * @deprecated Inject and call Psr\Log\LoggerInterface::debug() instead (the verbose level doesn't exist in the PSR standard). + * @see \Psr\Log\LoggerInterface::debug() */ public static function verbose($message /* ... */) { - self::logMessage(self::VERBOSE, $message, array_slice(func_get_args(), 1)); + self::logMessage(Logger::DEBUG, $message, array_slice(func_get_args(), 1)); } /** - * Creates log message combining logging info including a log level, tag name, - * date time, and caller-provided log message. The log message can be set through - * the `[log] string_message_format` INI config option. By default it will - * create log messages like: - * - * **LEVEL [tag:datetime] log message** - * - * @param int $level - * @param string $tag - * @param string $datetime - * @param string $message - * @return string + * @param int $logLevel + * @deprecated Will be removed, log levels are now applied on each Monolog handler. */ - public function formatMessage($level, $tag, $datetime, $message) - { - return str_replace( - array("%tag%", "%message%", "%datetime%", "%level%"), - array($tag, $message, $datetime, $this->getStringLevel($level)), - $this->logMessageFormat - ); - } - - private function setLogWritersFromConfig($logConfig) - { - // set the log writers - $logWriters = $logConfig[self::LOG_WRITERS_CONFIG_OPTION]; - - $logWriters = array_map('trim', $logWriters); - foreach ($logWriters as $writerName) { - $this->addLogWriter($writerName); - } - } - - public function addLogWriter($writerName) - { - if (array_key_exists($writerName, $this->writers)) { - return; - } - - $availableWritersByName = $this->getAvailableWriters(); - - if (empty($availableWritersByName[$writerName])) { - return; - } - - $this->writers[$writerName] = $availableWritersByName[$writerName]; - - if ($writerName == 'screen') { - $this->loggingToScreen = true; - } - } - - private function setCurrentLogLevelFromConfig($logConfig) - { - if (!empty($logConfig[self::LOG_LEVEL_CONFIG_OPTION])) { - $logLevel = $this->getLogLevelFromStringName($logConfig[self::LOG_LEVEL_CONFIG_OPTION]); - - if ($logLevel >= self::NONE // sanity check - && $logLevel <= self::VERBOSE - ) { - $this->setLogLevel($logLevel); - } - } - } - - private function setStringLogMessageFormat($logConfig) - { - if (isset($logConfig['string_message_format'])) { - $this->logMessageFormat = $logConfig['string_message_format']; - } - } - - private function setLogFilePathFromConfig($logConfig) - { - $logPath = $logConfig[self::LOGGER_FILE_PATH_CONFIG_OPTION]; - if (!SettingsServer::isWindows() - && $logPath[0] != '/' - ) { - $logPath = PIWIK_USER_PATH . DIRECTORY_SEPARATOR . $logPath; - } - $logPath = SettingsPiwik::rewriteTmpPathWithHostname($logPath); - if (is_dir($logPath)) { - $logPath .= '/piwik.log'; - } - $this->logToFilePath = $logPath; - } - - private function getAvailableWriters() - { - $writers = array(); - - /** - * This event is called when the Log instance is created. Plugins can use this event to - * make new logging writers available. - * - * A logging writer is a callback with the following signature: - * - * function (int $level, string $tag, string $datetime, string $message) - * - * `$level` is the log level to use, `$tag` is the log tag used, `$datetime` is the date time - * of the logging call and `$message` is the formatted log message. - * - * Logging writers must be associated by name in the array passed to event handlers. The - * name specified can be used in Piwik's INI configuration. - * - * **Example** - * - * public function getAvailableWriters(&$writers) { - * $writers['myloggername'] = function ($level, $tag, $datetime, $message) { - * // ... - * }; - * } - * - * // 'myloggername' can now be used in the log_writers config option. - * - * @param array $writers Array mapping writer names with logging writers. - */ - Piwik::postEvent(self::GET_AVAILABLE_WRITERS_EVENT, array(&$writers)); - - $writers['file'] = array($this, 'logToFile'); - $writers['screen'] = array($this, 'logToScreen'); - $writers['database'] = array($this, 'logToDatabase'); - return $writers; - } - public function setLogLevel($logLevel) { - $this->currentLogLevel = $logLevel; } - private function logToFile($level, $tag, $datetime, $message) + /** + * @deprecated Will be removed, log levels are now applied on each Monolog handler. + */ + public function getLogLevel() { - if (is_string($message)) { - $message = $this->formatMessage($level, $tag, $datetime, $message); - } else { - $logger = $this; + } - /** - * Triggered when trying to log an object to a file. Plugins can use - * this event to convert objects to strings before they are logged. - * - * **Example** - * - * public function formatFileMessage(&$message, $level, $tag, $datetime, $logger) { - * if ($message instanceof MyCustomDebugInfo) { - * $message = $message->formatForFile(); - * } - * } - * - * @param mixed &$message The object that is being logged. Event handlers should - * check if the object is of a certain type and if it is, - * set `$message` to the string that should be logged. - * @param int $level The log level used with this log entry. - * @param string $tag The current plugin that started logging (or if no plugin, - * the current class). - * @param string $datetime Datetime of the logging call. - * @param Log $logger The Log singleton. - */ - Piwik::postEvent(self::FORMAT_FILE_MESSAGE_EVENT, array(&$message, $level, $tag, $datetime, $logger)); + private function doLog($level, $message, $parameters = array()) + { + // To ensure the compatibility with PSR-3, the message must be a string + if ($message instanceof \Exception) { + $parameters['exception'] = $message; + $message = $message->getMessage(); } - if (empty($message)) { + if (is_object($message) || is_array($message) || is_resource($message)) { + $this->logger->warning('Trying to log a message that is not a string', array( + 'exception' => new \InvalidArgumentException('Trying to log a message that is not a string') + )); return; } - if(!file_put_contents($this->logToFilePath, $message . "\n", FILE_APPEND)) { - $message = Filechecks::getErrorMessageMissingPermissions($this->logToFilePath); - throw new \Exception( $message ); - } + $this->logger->log($level, $message, $parameters); } - private function logToScreen($level, $tag, $datetime, $message) + private static function logMessage($level, $message, $parameters) { - static $currentRequestKey; - if (empty($currentRequestKey)) { - $currentRequestKey = substr(Common::generateUniqId(), 0, 5); - } - - if (is_string($message)) { - if (!defined('PIWIK_TEST_MODE')) { - $message = '[' . $currentRequestKey . '] ' . $message; - } - $message = $this->formatMessage($level, $tag, $datetime, $message); - - if (!Common::isPhpCliMode()) { - $message = Common::sanitizeInputValue($message); - $message = '
' . $message . '
'; - } - - } else { - $logger = $this; - - /** - * Triggered when trying to log an object to the screen. Plugins can use - * this event to convert objects to strings before they are logged. - * - * The result of this callback can be HTML so no sanitization is done on the result. - * This means **YOU MUST SANITIZE THE MESSAGE YOURSELF** if you use this event. - * - * **Example** - * - * public function formatScreenMessage(&$message, $level, $tag, $datetime, $logger) { - * if ($message instanceof MyCustomDebugInfo) { - * $message = Common::sanitizeInputValue($message->formatForScreen()); - * } - * } - * - * @param mixed &$message The object that is being logged. Event handlers should - * check if the object is of a certain type and if it is, - * set `$message` to the string that should be logged. - * @param int $level The log level used with this log entry. - * @param string $tag The current plugin that started logging (or if no plugin, - * the current class). - * @param string $datetime Datetime of the logging call. - * @param Log $logger The Log singleton. - */ - Piwik::postEvent(self::FORMAT_SCREEN_MESSAGE_EVENT, array(&$message, $level, $tag, $datetime, $logger)); - } - - if (empty($message)) { - return; - } - - echo $message . "\n"; + self::getInstance()->doLog($level, $message, $parameters); } - private function logToDatabase($level, $tag, $datetime, $message) + public static function getMonologLevel($level) { - if (is_string($message)) { - $message = $this->formatMessage($level, $tag, $datetime, $message); - } else { - $logger = $this; - - /** - * Triggered when trying to log an object to a database table. Plugins can use - * this event to convert objects to strings before they are logged. - * - * **Example** - * - * public function formatDatabaseMessage(&$message, $level, $tag, $datetime, $logger) { - * if ($message instanceof MyCustomDebugInfo) { - * $message = $message->formatForDatabase(); - * } - * } - * - * @param mixed &$message The object that is being logged. Event handlers should - * check if the object is of a certain type and if it is, - * set `$message` to the string that should be logged. - * @param int $level The log level used with this log entry. - * @param string $tag The current plugin that started logging (or if no plugin, - * the current class). - * @param string $datetime Datetime of the logging call. - * @param Log $logger The Log singleton. - */ - Piwik::postEvent(self::FORMAT_DATABASE_MESSAGE_EVENT, array(&$message, $level, $tag, $datetime, $logger)); - } - - if (empty($message)) { - return; - } - - $sql = "INSERT INTO " . Common::prefixTable('logger_message') - . " (tag, timestamp, level, message)" - . " VALUES (?, ?, ?, ?)"; - Db::query($sql, array($tag, $datetime, self::getStringLevel($level), (string)$message)); - } - - private function doLog($level, $message, $sprintfParams = array()) - { - if ($this->shouldLoggerLog($level)) { - $datetime = date("Y-m-d H:i:s"); - if (is_string($message) - && !empty($sprintfParams) - ) { - $message = vsprintf($message, $sprintfParams); - } - - if (version_compare(phpversion(), '5.3.6', '>=')) { - $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT); - } else { - $backtrace = debug_backtrace(); - } - $tag = Plugin::getPluginNameFromBacktrace($backtrace); - - // if we can't determine the plugin, use the name of the calling class - if ($tag == false) { - $tag = $this->getClassNameThatIsLogging($backtrace); - } - - $this->writeMessage($level, $tag, $datetime, $message); - } - } - - private function writeMessage($level, $tag, $datetime, $message) - { - foreach ($this->writers as $writer) { - call_user_func($writer, $level, $tag, $datetime, $message); - } - - // errors are always printed to screen - if ($level == self::ERROR - && !$this->loggingToScreen - ) { - $this->logToScreen($level, $tag, $datetime, $message); - } - } - - private static function logMessage($level, $message, $sprintfParams) - { - self::getInstance()->doLog($level, $message, $sprintfParams); - } - - private function shouldLoggerLog($level) - { - return $level <= $this->currentLogLevel; - } - - private function disableLoggingBasedOnConfig($logConfig) - { - $disableLogging = false; - - if (!empty($logConfig['log_only_when_cli']) - && !Common::isPhpCliMode() - ) { - $disableLogging = true; - } - - if (!empty($logConfig['log_only_when_debug_parameter']) - && !isset($_REQUEST['debug']) - ) { - $disableLogging = true; - } - - if ($disableLogging) { - $this->currentLogLevel = self::NONE; - } - } - - private function getLogLevelFromStringName($name) - { - $name = strtoupper($name); - switch ($name) { - case 'NONE': - return self::NONE; - case 'ERROR': - return self::ERROR; - case 'WARN': - return self::WARN; - case 'INFO': - return self::INFO; - case 'DEBUG': - return self::DEBUG; - case 'VERBOSE': - return self::VERBOSE; + switch ($level) { + case self::ERROR: + return Logger::ERROR; + case self::WARN: + return Logger::WARNING; + case self::INFO: + return Logger::INFO; + case self::DEBUG: + return Logger::DEBUG; + case self::VERBOSE: + return Logger::DEBUG; + case self::NONE: default: - return -1; + // Highest level possible, need to do better in the future... + return Logger::EMERGENCY; } } - - private function getStringLevel($level) - { - static $levelToName = array( - self::NONE => 'NONE', - self::ERROR => 'ERROR', - self::WARN => 'WARN', - self::INFO => 'INFO', - self::DEBUG => 'DEBUG', - self::VERBOSE => 'VERBOSE' - ); - return $levelToName[$level]; - } - - private function getClassNameThatIsLogging($backtrace) - { - foreach ($backtrace as $tracepoint) { - if (isset($tracepoint['class']) - && $tracepoint['class'] != "Piwik\\Log" - && $tracepoint['class'] != "Piwik\\Piwik" - && $tracepoint['class'] != "Piwik\\CronArchive" - ) { - return $tracepoint['class']; - } - } - return false; - } } diff --git a/www/analytics/core/LogDeleter.php b/www/analytics/core/LogDeleter.php new file mode 100644 index 00000000..fc61ec93 --- /dev/null +++ b/www/analytics/core/LogDeleter.php @@ -0,0 +1,110 @@ +rawLogDao = $rawLogDao; + } + + /** + * Deletes visits by ID. This method cascades, so conversions, conversion items and visit actions for + * the visits are also deleted. + * + * @param int[] $visitIds + * @return int The number of deleted visits. + */ + public function deleteVisits($visitIds) + { + $this->deleteConversions($visitIds); + $this->rawLogDao->deleteVisitActionsForVisits($visitIds); + + return $this->rawLogDao->deleteVisits($visitIds); + } + + /** + * Deletes conversions by visit ID. This method cascades, so conversion items are also deleted. + * + * @param int[] $visitIds The list of visits to delete conversions for. + * @return int The number rows deleted. + */ + public function deleteConversions($visitIds) + { + $this->deleteConversionItems($visitIds); + return $this->rawLogDao->deleteConversions($visitIds); + } + + /** + * Deletes conversion items by visit ID. + * + * @param int[] $visitIds The list of visits to delete conversions for. + * @return int The number rows deleted. + */ + public function deleteConversionItems($visitIds) + { + return $this->rawLogDao->deleteConversionItems($visitIds); + } + + /** + * Deletes visits within the specified date range and belonging to the specified site (if any). Visits are + * deleted in chunks, so only `$iterationStep` visits are deleted at a time. + * + * @param string|null $startDatetime A datetime string. Visits that occur at this time or after are deleted. If not supplied, + * visits from the beginning of time are deleted. + * @param string|null $endDatetime A datetime string. Visits that occur before this time are deleted. If not supplied, + * visits from the end of time are deleted. + * @param int|null $idSite The site to delete visits from. + * @param int $iterationStep The number of visits to delete at a single time. + * @param callable $afterChunkDeleted Callback executed after every chunk of visits are deleted. + * @return int The number of visits deleted. + */ + public function deleteVisitsFor($startDatetime, $endDatetime, $idSite = null, $iterationStep = 1000, $afterChunkDeleted = null) + { + $fields = array('idvisit'); + $conditions = array(); + + if (!empty($startDatetime)) { + $conditions[] = array('visit_last_action_time', '>=', $startDatetime); + } + + if (!empty($endDatetime)) { + $conditions[] = array('visit_last_action_time', '<', $endDatetime); + } + + if (!empty($idSite)) { + $conditions[] = array('idsite', '=', $idSite); + } + + $logsDeleted = 0; + $logPurger = $this; + $this->rawLogDao->forAllLogs('log_visit', $fields, $conditions, $iterationStep, function ($logs) use ($logPurger, &$logsDeleted, $afterChunkDeleted) { + $ids = array_map(function ($row) { return reset($row); }, $logs); + $logsDeleted += $logPurger->deleteVisits($ids); + + if (!empty($afterChunkDeleted)) { + $afterChunkDeleted($logsDeleted); + } + }); + + return $logsDeleted; + } +} \ No newline at end of file diff --git a/www/analytics/core/Mail.php b/www/analytics/core/Mail.php index 1fc1faf7..c6c8623c 100644 --- a/www/analytics/core/Mail.php +++ b/www/analytics/core/Mail.php @@ -1,6 +1,6 @@ isEnabled() - ? Piwik::translate('CoreHome_WebAnalyticsReports') - : Piwik::translate('ScheduledReports_PiwikReports'); + + /** @var Translator $translator */ + $translator = StaticContainer::get('Piwik\Translation\Translator'); + + $fromEmailName = Config::getInstance()->General['noreply_email_name']; + + if (empty($fromEmailName) && $customLogo->isEnabled()) { + $fromEmailName = $translator->translate('CoreHome_WebAnalyticsReports'); + } elseif (empty($fromEmailName)) { + $fromEmailName = $translator->translate('ScheduledReports_PiwikReports'); + } + $fromEmailAddress = Config::getInstance()->General['noreply_email_address']; $this->setFrom($fromEmailAddress, $fromEmailName); } @@ -50,20 +61,25 @@ class Mail extends Zend_Mail */ public function setFrom($email, $name = null) { - $hostname = Config::getInstance()->mail['defaultHostnameIfEmpty']; - $piwikHost = Url::getCurrentHost($hostname); + return parent::setFrom( + $this->parseDomainPlaceholderAsPiwikHostName($email), + $name + ); + } - // If known Piwik URL, use it instead of "localhost" - $piwikUrl = SettingsPiwik::getPiwikUrl(); - $url = parse_url($piwikUrl); - if (isset($url['host']) - && $url['host'] != 'localhost' - && $url['host'] != '127.0.0.1' - ) { - $piwikHost = $url['host']; - } - $email = str_replace('{DOMAIN}', $piwikHost, $email); - return parent::setFrom($email, $name); + /** + * Set Reply-To Header + * + * @param string $email + * @param null|string $name + * @return Zend_Mail + */ + public function setReplyTo($email, $name = null) + { + return parent::setReplyTo( + $this->parseDomainPlaceholderAsPiwikHostName($email), + $name + ); } /** @@ -72,27 +88,37 @@ class Mail extends Zend_Mail private function initSmtpTransport() { $mailConfig = Config::getInstance()->mail; + if (empty($mailConfig['host']) || $mailConfig['transport'] != 'smtp' ) { return; } - $smtpConfig = array(); - if (!empty($mailConfig['type'])) - $smtpConfig['auth'] = strtolower($mailConfig['type']); - if (!empty($mailConfig['username'])) - $smtpConfig['username'] = $mailConfig['username']; - if (!empty($mailConfig['password'])) - $smtpConfig['password'] = $mailConfig['password']; - if (!empty($mailConfig['encryption'])) - $smtpConfig['ssl'] = $mailConfig['encryption']; - $tr = new \Zend_Mail_Transport_Smtp($mailConfig['host'], $smtpConfig); + $smtpConfig = array(); + if (!empty($mailConfig['type'])) { + $smtpConfig['auth'] = strtolower($mailConfig['type']); + } + + if (!empty($mailConfig['username'])) { + $smtpConfig['username'] = $mailConfig['username']; + } + + if (!empty($mailConfig['password'])) { + $smtpConfig['password'] = $mailConfig['password']; + } + + if (!empty($mailConfig['encryption'])) { + $smtpConfig['ssl'] = $mailConfig['encryption']; + } + + $host = trim($mailConfig['host']); + $tr = new \Zend_Mail_Transport_Smtp($host, $smtpConfig); Mail::setDefaultTransport($tr); - ini_set("smtp_port", $mailConfig['port']); + @ini_set("smtp_port", $mailConfig['port']); } - public function send($transport = NULL) + public function send($transport = null) { if (defined('PIWIK_TEST_MODE')) { // hack Piwik::postTestEvent("Test.Mail.send", array($this)); @@ -100,4 +126,58 @@ class Mail extends Zend_Mail return parent::send($transport); } } + + public function createAttachment($body, $mimeType = null, $disposition = null, $encoding = null, $filename = null) + { + $filename = $this->sanitiseString($filename); + return parent::createAttachment($body, $mimeType, $disposition, $encoding, $filename); + } + + public function setSubject($subject) + { + $subject = $this->sanitiseString($subject); + return parent::setSubject($subject); + } + + /** + * @param string $email + * @return string + */ + protected function parseDomainPlaceholderAsPiwikHostName($email) + { + $hostname = Config::getInstance()->mail['defaultHostnameIfEmpty']; + $piwikHost = Url::getCurrentHost($hostname); + + // If known Piwik URL, use it instead of "localhost" + $piwikUrl = SettingsPiwik::getPiwikUrl(); + $url = parse_url($piwikUrl); + if ($this->isHostDefinedAndNotLocal($url)) { + $piwikHost = $url['host']; + } + + return str_replace('{DOMAIN}', $piwikHost, $email); + } + + /** + * @param array $url + * @return bool + */ + protected function isHostDefinedAndNotLocal($url) + { + return isset($url['host']) && !Url::isLocalHost($url['host']); + } + + /** + * Replaces characters known to appear incorrectly in some email clients + * + * @param $string + * @return mixed + */ + function sanitiseString($string) + { + $search = array('–', '’'); + $replace = array('-', '\''); + $string = str_replace($search, $replace, $string); + return $string; + } } diff --git a/www/analytics/core/Measurable/Measurable.php b/www/analytics/core/Measurable/Measurable.php new file mode 100644 index 00000000..d80c1f03 --- /dev/null +++ b/www/analytics/core/Measurable/Measurable.php @@ -0,0 +1,32 @@ +id, $this->getType()); + $setting = $settings->getSetting($name); + + if (!empty($setting)) { + return $setting->getValue(); // Calling `getValue` makes sure we respect read permission of this setting + } + + throw new Exception(sprintf('Setting %s does not exist', $name)); + } +} diff --git a/www/analytics/core/Measurable/MeasurableSetting.php b/www/analytics/core/Measurable/MeasurableSetting.php new file mode 100644 index 00000000..91e09704 --- /dev/null +++ b/www/analytics/core/Measurable/MeasurableSetting.php @@ -0,0 +1,70 @@ +writableByCurrentUser = Piwik::isUserHasSomeAdminAccess(); + $this->readableByCurrentUser = Piwik::isUserHasSomeViewAccess(); + } + + /** + * Returns `true` if this setting is writable for the current user, `false` if otherwise. In case it returns + * writable for the current user it will be visible in the Plugin settings UI. + * + * @return bool + */ + public function isWritableByCurrentUser() + { + return $this->writableByCurrentUser; + } + + /** + * Returns `true` if this setting can be displayed for the current user, `false` if otherwise. + * + * @return bool + */ + public function isReadableByCurrentUser() + { + return $this->readableByCurrentUser; + } +} diff --git a/www/analytics/core/Measurable/MeasurableSettings.php b/www/analytics/core/Measurable/MeasurableSettings.php new file mode 100644 index 00000000..7d627e0a --- /dev/null +++ b/www/analytics/core/Measurable/MeasurableSettings.php @@ -0,0 +1,103 @@ +idSite = $idSite; + $this->idType = $idType; + $this->storage = new Storage(Db::get(), $this->idSite); + $this->pluginName = 'MeasurableSettings'; + + $this->init(); + } + + protected function init() + { + $typeManager = new TypeManager(); + $type = $typeManager->getType($this->idType); + $type->configureMeasurableSettings($this); + + /** + * This event is posted when generating settings for a Measurable (website). You can add any Measurable settings + * that you wish to be shown in the Measurable manager (websites manager). If you need to add settings only for + * eg MobileApp measurables you can use eg `$type->getId() === Piwik\Plugins\MobileAppMeasurable\Type::ID` and + * add only settings if the condition is true. + * + * @since Piwik 2.14.0 + * @deprecated will be removed in Piwik 3.0.0 + * + * @param MeasurableSettings $this + * @param \Piwik\Measurable\Type $type + * @param int $idSite + */ + Piwik::postEvent('Measurable.initMeasurableSettings', array($this, $type, $this->idSite)); + } + + public function addSetting(Setting $setting) + { + if ($this->idSite && $setting instanceof MeasurableSetting) { + $setting->writableByCurrentUser = Piwik::isUserHasAdminAccess($this->idSite); + } + + parent::addSetting($setting); + } + + public function save() + { + Piwik::checkUserHasAdminAccess($this->idSite); + + $typeManager = new TypeManager(); + $type = $typeManager->getType($this->idType); + + /** + * Triggered just before Measurable settings are about to be saved. You can use this event for example + * to validate not only one setting but multiple ssetting. For example whether username + * and password matches. + * + * @since Piwik 2.14.0 + * @deprecated will be removed in Piwik 3.0.0 + * + * @param MeasurableSettings $this + * @param \Piwik\Measurable\Type $type + * @param int $idSite + */ + Piwik::postEvent('Measurable.beforeSaveSettings', array($this, $type, $this->idSite)); + + $this->storage->save(); + } + +} + diff --git a/www/analytics/core/Measurable/Settings/Storage.php b/www/analytics/core/Measurable/Settings/Storage.php new file mode 100644 index 00000000..df9748af --- /dev/null +++ b/www/analytics/core/Measurable/Settings/Storage.php @@ -0,0 +1,104 @@ +db = $db; + $this->idSite = $idSite; + } + + protected function deleteSettingsFromStorage() + { + $table = $this->getTableName(); + $sql = "DELETE FROM $table WHERE `idsite` = ?"; + $bind = array($this->idSite); + + $this->db->query($sql, $bind); + } + + public function deleteValue(Setting $setting) + { + $this->toBeDeleted[$setting->getName()] = true; + parent::deleteValue($setting); + } + + public function setValue(Setting $setting, $value) + { + $this->toBeDeleted[$setting->getName()] = false; // prevent from deleting this setting, we will create/update it + parent::setValue($setting, $value); + } + + /** + * Saves (persists) the current setting values in the database. + */ + public function save() + { + $table = $this->getTableName(); + + foreach ($this->toBeDeleted as $name => $delete) { + if ($delete) { + $sql = "DELETE FROM $table WHERE `idsite` = ? and `setting_name` = ?"; + $bind = array($this->idSite, $name); + + $this->db->query($sql, $bind); + } + } + + $this->toBeDeleted = array(); + + foreach ($this->settingsValues as $name => $value) { + $value = serialize($value); + + $sql = "INSERT INTO $table (`idsite`, `setting_name`, `setting_value`) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE `setting_value` = ?"; + $bind = array($this->idSite, $name, $value, $value); + + $this->db->query($sql, $bind); + } + } + + protected function loadSettings() + { + $sql = "SELECT `setting_name`, `setting_value` FROM " . $this->getTableName() . " WHERE idsite = ?"; + $bind = array($this->idSite); + + $settings =$this->db->fetchAll($sql, $bind); + + $flat = array(); + foreach ($settings as $setting) { + $flat[$setting['setting_name']] = unserialize($setting['setting_value']); + } + + return $flat; + } + + private function getTableName() + { + return Common::prefixTable('site_setting'); + } +} diff --git a/www/analytics/core/Measurable/Type.php b/www/analytics/core/Measurable/Type.php new file mode 100644 index 00000000..e9457a66 --- /dev/null +++ b/www/analytics/core/Measurable/Type.php @@ -0,0 +1,62 @@ +isType('website') to be true (maybe) + return $this->getId() === $typeId; + } + + public function getId() + { + $id = static::ID; + + if (empty($id)) { + $message = 'Type %s does not define an ID. Set the ID constant to fix this issue';; + throw new \Exception(sprintf($message, get_called_class())); + } + + return $id; + } + + public function getDescription() + { + return $this->description; + } + + public function getName() + { + return $this->name; + } + + public function getNamePlural() + { + return $this->namePlural; + } + + public function getHowToSetupUrl() + { + return $this->howToSetupUrl; + } + + public function configureMeasurableSettings(MeasurableSettings $settings) + { + } +} + diff --git a/www/analytics/core/Measurable/Type/TypeManager.php b/www/analytics/core/Measurable/Type/TypeManager.php new file mode 100644 index 00000000..af40d9c6 --- /dev/null +++ b/www/analytics/core/Measurable/Type/TypeManager.php @@ -0,0 +1,39 @@ +findComponents('Type', '\\Piwik\\Measurable\\Type'); + } + + /** + * @param string $typeId + * @return Type|null + */ + public function getType($typeId) + { + foreach ($this->getAllTypes() as $type) { + if ($type->getId() === $typeId) { + return $type; + } + } + + return new Type(); + } +} + diff --git a/www/analytics/core/Menu/Group.php b/www/analytics/core/Menu/Group.php new file mode 100644 index 00000000..a13da035 --- /dev/null +++ b/www/analytics/core/Menu/Group.php @@ -0,0 +1,31 @@ +items[] = array( + 'name' => $subTitleMenu, + 'url' => $url, + 'tooltip' => $tooltip + ); + } + + public function getItems() + { + return $this->items; + } +} diff --git a/www/analytics/core/Menu/MenuAbstract.php b/www/analytics/core/Menu/MenuAbstract.php index 781aed7b..777d97b5 100644 --- a/www/analytics/core/Menu/MenuAbstract.php +++ b/www/analytics/core/Menu/MenuAbstract.php @@ -1,6 +1,6 @@ menu; } + /** + * Let's you register a menu icon for a certain menu category to replace the default arrow icon. + * + * @param string $menuName The translation key of a main menu category, eg 'Dashboard_Dashboard' + * @param string $iconCssClass The css class name of an icon, eg 'icon-user' + */ + public function registerMenuIcon($menuName, $iconCssClass) + { + $this->menuIcons[$menuName] = $iconCssClass; + } + + /** + * Returns a list of available plugin menu instances. + * + * @return \Piwik\Plugin\Menu[] + */ + protected function getAllMenus() + { + if (!empty(self::$menus)) { + return self::$menus; + } + + self::$menus = PluginManager::getInstance()->findComponents('Menu', 'Piwik\\Plugin\\Menu'); + + return self::$menus; + } + + /** + * To use only for tests. + * + * @deprecated The whole $menus cache should be replaced by a real transient cache + */ + public static function clearMenus() + { + self::$menus = array(); + } + /** * Adds a new entry to the menu. * @@ -57,8 +96,9 @@ abstract class MenuAbstract extends Singleton * @param boolean $displayedForCurrentUser Whether this menu entry should be displayed for the * current user. If false, the entry will not be added. * @param int $order The order hint. - * @param false|string $tooltip An optional tooltip to display. - * @api + * @param bool|string $tooltip An optional tooltip to display or false to display the tooltip. + * + * @deprecated since 2.7.0 Use {@link addItem() instead}. Method will be removed in Piwik 3.0 */ public function add($menuName, $subMenuName, $url, $displayedForCurrentUser = true, $order = 50, $tooltip = false) { @@ -66,8 +106,25 @@ abstract class MenuAbstract extends Singleton return; } + $this->addItem($menuName, $subMenuName, $url, $order, $tooltip); + } + + /** + * Adds a new entry to the menu. + * + * @param string $menuName The menu's category name. Can be a translation token. + * @param string $subMenuName The menu item's name. Can be a translation token. + * @param string|array $url The URL the admin menu entry should link to, or an array of query parameters + * that can be used to build the URL. + * @param int $order The order hint. + * @param bool|string $tooltip An optional tooltip to display or false to display the tooltip. + * @since 2.7.0 + * @api + */ + public function addItem($menuName, $subMenuName, $url, $order = 50, $tooltip = false) + { // make sure the idSite value used is numeric (hack-y fix for #3426) - if (!is_numeric(Common::getRequestVar('idSite', false))) { + if (isset($url['idSite']) && !is_numeric($url['idSite'])) { $idSites = API::getInstance()->getSitesIdWithAtLeastViewAccess(); $url['idSite'] = reset($idSites); } @@ -81,6 +138,13 @@ abstract class MenuAbstract extends Singleton ); } + /** + * Removes an existing entry from the menu. + * + * @param string $menuName The menu's category name. Can be a translation token. + * @param bool|string $subMenuName The menu item's name. Can be a translation token. + * @api + */ public function remove($menuName, $subMenuName = false) { $this->menuEntriesToRemove[] = array( @@ -100,21 +164,34 @@ abstract class MenuAbstract extends Singleton */ private function buildMenuItem($menuName, $subMenuName, $url, $order = 50, $tooltip = false) { - if (!isset($this->menu[$menuName]) || empty($subMenuName)) { - $this->menu[$menuName]['_url'] = $url; - if (empty($subMenuName)) { - $this->menu[$menuName]['_order'] = $order; - } - $this->menu[$menuName]['_name'] = $menuName; - $this->menu[$menuName]['_hasSubmenu'] = false; + if (!isset($this->menu[$menuName])) { + $this->menu[$menuName] = array( + '_hasSubmenu' => false, + '_order' => $order + ); + } + + if (empty($subMenuName)) { + $this->menu[$menuName]['_url'] = $url; + $this->menu[$menuName]['_order'] = $order; + $this->menu[$menuName]['_name'] = $menuName; $this->menu[$menuName]['_tooltip'] = $tooltip; + if (!empty($this->menuIcons[$menuName])) { + $this->menu[$menuName]['_icon'] = $this->menuIcons[$menuName]; + } else { + $this->menu[$menuName]['_icon'] = ''; + } } if (!empty($subMenuName)) { $this->menu[$menuName][$subMenuName]['_url'] = $url; $this->menu[$menuName][$subMenuName]['_order'] = $order; $this->menu[$menuName][$subMenuName]['_name'] = $subMenuName; + $this->menu[$menuName][$subMenuName]['_tooltip'] = $tooltip; $this->menu[$menuName]['_hasSubmenu'] = true; - $this->menu[$menuName]['_tooltip'] = $tooltip; + + if (!array_key_exists('_tooltip', $this->menu[$menuName])) { + $this->menu[$menuName]['_tooltip'] = $tooltip; + } } } @@ -135,6 +212,7 @@ abstract class MenuAbstract extends Singleton * @param $subMenuOriginal * @param $mainMenuRenamed * @param $subMenuRenamed + * @api */ public function rename($mainMenuOriginal, $subMenuOriginal, $mainMenuRenamed, $subMenuRenamed) { @@ -148,6 +226,7 @@ abstract class MenuAbstract extends Singleton * @param $mainMenuToEdit * @param $subMenuToEdit * @param $newUrl + * @api */ public function editUrl($mainMenuToEdit, $subMenuToEdit, $newUrl) { @@ -161,13 +240,21 @@ abstract class MenuAbstract extends Singleton { foreach ($this->edits as $edit) { $mainMenuToEdit = $edit[0]; - $subMenuToEdit = $edit[1]; - $newUrl = $edit[2]; + $subMenuToEdit = $edit[1]; + $newUrl = $edit[2]; if ($subMenuToEdit === null) { - $menuDataToEdit = @$this->menu[$mainMenuToEdit]; + if (isset($this->menu[$mainMenuToEdit])) { + $menuDataToEdit = &$this->menu[$mainMenuToEdit]; + } else { + $menuDataToEdit = null; + } } else { - $menuDataToEdit = @$this->menu[$mainMenuToEdit][$subMenuToEdit]; + if (isset($this->menu[$mainMenuToEdit][$subMenuToEdit])) { + $menuDataToEdit = &$this->menu[$mainMenuToEdit][$subMenuToEdit]; + } else { + $menuDataToEdit = null; + } } if (empty($menuDataToEdit)) { @@ -180,16 +267,15 @@ abstract class MenuAbstract extends Singleton private function applyRemoves() { - foreach($this->menuEntriesToRemove as $menuToDelete) { - - if(empty($menuToDelete[1])) { + foreach ($this->menuEntriesToRemove as $menuToDelete) { + if (empty($menuToDelete[1])) { // Delete Main Menu - if(isset($this->menu[$menuToDelete[0]])) { + if (isset($this->menu[$menuToDelete[0]])) { unset($this->menu[$menuToDelete[0]]); } } else { // Delete Sub Menu - if(isset($this->menu[$menuToDelete[0]][$menuToDelete[1]])) { + if (isset($this->menu[$menuToDelete[0]][$menuToDelete[1]])) { unset($this->menu[$menuToDelete[0]][$menuToDelete[1]]); } } @@ -202,9 +288,10 @@ abstract class MenuAbstract extends Singleton { foreach ($this->renames as $rename) { $mainMenuOriginal = $rename[0]; - $subMenuOriginal = $rename[1]; - $mainMenuRenamed = $rename[2]; - $subMenuRenamed = $rename[3]; + $subMenuOriginal = $rename[1]; + $mainMenuRenamed = $rename[2]; + $subMenuRenamed = $rename[3]; + // Are we changing a submenu? if (!empty($subMenuOriginal)) { if (isset($this->menu[$mainMenuOriginal][$subMenuOriginal])) { @@ -214,7 +301,7 @@ abstract class MenuAbstract extends Singleton $this->menu[$mainMenuRenamed][$subMenuRenamed] = $save; } } // Changing a first-level element - else if (isset($this->menu[$mainMenuOriginal])) { + elseif (isset($this->menu[$mainMenuOriginal])) { $save = $this->menu[$mainMenuOriginal]; $save['_name'] = $mainMenuRenamed; unset($this->menu[$mainMenuOriginal]); @@ -238,7 +325,7 @@ abstract class MenuAbstract extends Singleton foreach ($this->menu as $key => &$element) { if (is_null($element)) { unset($this->menu[$key]); - } else if ($element['_hasSubmenu']) { + } elseif ($element['_hasSubmenu']) { uasort($element, array($this, 'menuCompare')); } } @@ -255,15 +342,36 @@ abstract class MenuAbstract extends Singleton */ protected function menuCompare($itemOne, $itemTwo) { - if (!is_array($itemOne) || !is_array($itemTwo) - || !isset($itemOne['_order']) || !isset($itemTwo['_order']) - ) { + if (!is_array($itemOne) && !is_array($itemTwo)) { return 0; } - if ($itemOne['_order'] == $itemTwo['_order']) { - return strcmp($itemOne['_name'], $itemTwo['_name']); + if (!is_array($itemOne) && is_array($itemTwo)) { + return -1; } + + if (is_array($itemOne) && !is_array($itemTwo)) { + return 1; + } + + if (!isset($itemOne['_order']) && !isset($itemTwo['_order'])) { + return 0; + } + + if (!isset($itemOne['_order']) && isset($itemTwo['_order'])) { + return -1; + } + + if (isset($itemOne['_order']) && !isset($itemTwo['_order'])) { + return 1; + } + + if ($itemOne['_order'] == $itemTwo['_order']) { + return strcmp( + @$itemOne['_name'], + @$itemTwo['_name']); + } + return ($itemOne['_order'] < $itemTwo['_order']) ? -1 : 1; } } diff --git a/www/analytics/core/Menu/MenuAdmin.php b/www/analytics/core/Menu/MenuAdmin.php index ee985314..9c05734e 100644 --- a/www/analytics/core/Menu/MenuAdmin.php +++ b/www/analytics/core/Menu/MenuAdmin.php @@ -1,6 +1,6 @@ add( + * $menu->add( * 'MyPlugin_MyTranslatedAdminMenuCategory', * 'MyPlugin_MyTranslatedAdminPageName', * array('module' => 'MyPlugin', 'action' => 'index'), @@ -27,25 +27,79 @@ use Piwik\Piwik; * $order = 2 * ); * } - * + * * @method static \Piwik\Menu\MenuAdmin getInstance() */ class MenuAdmin extends MenuAbstract { /** - * Adds a new AdminMenu entry under the 'Settings' category. - * - * @param string $adminMenuName The name of the admin menu entry. Can be a translation token. - * @param string|array $url The URL the admin menu entry should link to, or an array of query parameters - * that can be used to build the URL. - * @param boolean $displayedForCurrentUser Whether this menu entry should be displayed for the - * current user. If false, the entry will not be added. - * @param int $order The order hint. + * See {@link add()}. Adds a new menu item to the development section of the admin menu. + * @param string $menuName + * @param array $url + * @param int $order + * @param bool|string $tooltip * @api + * @since 2.5.0 */ - public static function addEntry($adminMenuName, $url, $displayedForCurrentUser = true, $order = 20) + public function addDevelopmentItem($menuName, $url, $order = 50, $tooltip = false) { - self::getInstance()->add('General_Settings', $adminMenuName, $url, $displayedForCurrentUser, $order); + $this->addItem('CoreAdminHome_MenuDevelopment', $menuName, $url, $order, $tooltip); + } + + /** + * See {@link add()}. Adds a new menu item to the diagnostic section of the admin menu. + * @param string $menuName + * @param array $url + * @param int $order + * @param bool|string $tooltip + * @api + * @since 2.5.0 + */ + public function addDiagnosticItem($menuName, $url, $order = 50, $tooltip = false) + { + $this->addItem('CoreAdminHome_MenuDiagnostic', $menuName, $url, $order, $tooltip); + } + + /** + * See {@link add()}. Adds a new menu item to the platform section of the admin menu. + * @param string $menuName + * @param array $url + * @param int $order + * @param bool|string $tooltip + * @api + * @since 2.5.0 + */ + public function addPlatformItem($menuName, $url, $order = 50, $tooltip = false) + { + $this->addItem('CorePluginsAdmin_MenuPlatform', $menuName, $url, $order, $tooltip); + } + + /** + * See {@link add()}. Adds a new menu item to the settings section of the admin menu. + * @param string $menuName + * @param array $url + * @param int $order + * @param bool|string $tooltip + * @api + * @since 2.5.0 + */ + public function addSettingsItem($menuName, $url, $order = 50, $tooltip = false) + { + $this->addItem('General_Settings', $menuName, $url, $order, $tooltip); + } + + /** + * See {@link add()}. Adds a new menu item to the manage section of the admin menu. + * @param string $menuName + * @param array $url + * @param int $order + * @param bool|string $tooltip + * @api + * @since 2.5.0 + */ + public function addManageItem($menuName, $url, $order = 50, $tooltip = false) + { + $this->addItem('CoreAdminHome_Administration', $menuName, $url, $order, $tooltip); } /** @@ -58,56 +112,16 @@ class MenuAdmin extends MenuAbstract if (!$this->menu) { /** - * Triggered when collecting all available admin menu items. Subscribe to this event if you want - * to add one or more items to the Piwik admin menu. - * - * Menu items should be added via the {@link add()} method. - * - * **Example** - * - * use Piwik\Menu\MenuAdmin; - * - * public function addMenuItems() - * { - * MenuAdmin::getInstance()->add( - * 'MenuName', - * 'SubmenuName', - * array('module' => 'MyPlugin', 'action' => 'index'), - * $showOnlyIf = Piwik::hasUserSuperUserAccess(), - * $order = 6 - * ); - * } + * @ignore + * @deprecated */ - Piwik::postEvent('Menu.Admin.addItems'); - } - return parent::getMenu(); - } + Piwik::postEvent('Menu.Admin.addItems', array()); - /** - * Returns the current AdminMenu name - * - * @return boolean - */ - public function getCurrentAdminMenuName() - { - $menu = MenuAdmin::getInstance()->getMenu(); - $currentModule = Piwik::getModule(); - $currentAction = Piwik::getAction(); - foreach ($menu as $submenu) { - foreach ($submenu as $subMenuName => $parameters) { - if (strpos($subMenuName, '_') !== 0 && - $parameters['_url']['module'] == $currentModule - && $parameters['_url']['action'] == $currentAction - ) { - return $subMenuName; - } + foreach ($this->getAllMenus() as $menu) { + $menu->configureAdminMenu($this); } } - return false; - } - public static function removeEntry($menuName, $subMenuName = false) - { - MenuAdmin::getInstance()->remove($menuName, $subMenuName); + return parent::getMenu(); } } diff --git a/www/analytics/core/Menu/MenuMain.php b/www/analytics/core/Menu/MenuMain.php index 2cbffe0e..adb6b538 100644 --- a/www/analytics/core/Menu/MenuMain.php +++ b/www/analytics/core/Menu/MenuMain.php @@ -1,91 +1,19 @@ add( - * 'MyPlugin_MyTranslatedMenuCategory', - * 'MyPlugin_MyTranslatedMenuName', - * array('module' => 'MyPlugin', 'action' => 'index'), - * Piwik::isUserHasSomeAdminAccess(), - * $order = 2 - * ); - * } - * - * @api - * @method static \Piwik\Menu\MenuMain getInstance() + * @deprecated since 2.4.0 + * @see MenuReporting + * @method static MenuMain getInstance() + * @ignore */ -class MenuMain extends MenuAbstract +class MenuMain extends MenuReporting { - /** - * Returns if the URL was found in the menu. - * - * @param string $url - * @return boolean - */ - public function isUrlFound($url) - { - $menu = MenuMain::getInstance()->getMenu(); - - foreach ($menu as $subMenus) { - foreach ($subMenus as $subMenuName => $menuUrl) { - if (strpos($subMenuName, '_') !== 0 && $menuUrl['_url'] == $url) { - return true; - } - } - } - return false; - } - - /** - * Triggers the Menu.Reporting.addItems hook and returns the menu. - * - * @return Array - */ - public function getMenu() - { - // We trigger the Event only once! - if (!$this->menu) { - - /** - * Triggered when collecting all available reporting menu items. Subscribe to this event if you - * want to add one or more items to the Piwik reporting menu. - * - * Menu items should be added via the {@link add()} method. - * - * **Example** - * - * use Piwik\Menu\Main; - * - * public function addMenuItems() - * { - * Main::getInstance()->add( - * 'CustomMenuName', - * 'CustomSubmenuName', - * array('module' => 'MyPlugin', 'action' => 'index'), - * $showOnlyIf = Piwik::hasUserSuperUserAccess(), - * $order = 6 - * ); - * } - */ - Piwik::postEvent('Menu.Reporting.addItems'); - } - return parent::getMenu(); - } } diff --git a/www/analytics/core/Menu/MenuReporting.php b/www/analytics/core/Menu/MenuReporting.php new file mode 100644 index 00000000..598a7576 --- /dev/null +++ b/www/analytics/core/Menu/MenuReporting.php @@ -0,0 +1,143 @@ +add( + * 'MyPlugin_MyTranslatedMenuCategory', + * 'MyPlugin_MyTranslatedMenuName', + * array('module' => 'MyPlugin', 'action' => 'index'), + * Piwik::isUserHasSomeAdminAccess(), + * $order = 2 + * ); + * } + * + * @api + * @method static \Piwik\Menu\MenuReporting getInstance() + */ +class MenuReporting extends MenuAbstract +{ + + /** + * See {@link add()}. Adds a new menu item to the visitors section of the reporting menu. + * @param string $menuName + * @param array $url + * @param int $order + * @param bool|string $tooltip + * @api + * @since 2.5.0 + */ + public function addVisitorsItem($menuName, $url, $order = 50, $tooltip = false) + { + $this->addItem('General_Visitors', $menuName, $url, $order, $tooltip); + } + + /** + * See {@link add()}. Adds a new menu item to the actions section of the reporting menu. + * @param string $menuName + * @param array $url + * @param int $order + * @param bool|string $tooltip + * @api + * @since 2.5.0 + */ + public function addActionsItem($menuName, $url, $order = 50, $tooltip = false) + { + $this->addItem('General_Actions', $menuName, $url, $order, $tooltip); + } + + /** + * Should not be a public API yet. We probably have to change the API once we have another use case. + * @ignore + */ + public function addGroup($menuName, $defaultTitle, Group $group, $order = 50, $tooltip = false) + { + $this->menuEntries[] = array( + $menuName, + $defaultTitle, + $group, + $order, + $tooltip + ); + } + + /** + * See {@link add()}. Adds a new menu item to the referrers section of the reporting menu. + * @param string $menuName + * @param array $url + * @param int $order + * @param bool|string $tooltip + * @api + * @since 2.5.0 + */ + public function addReferrersItem($menuName, $url, $order = 50, $tooltip = false) + { + $this->addItem('Referrers_Referrers', $menuName, $url, $order, $tooltip); + } + + /** + * Returns if the URL was found in the menu. + * + * @param string $url + * @return boolean + */ + public function isUrlFound($url) + { + $menu = $this->getMenu(); + + foreach ($menu as $subMenus) { + foreach ($subMenus as $subMenuName => $menuUrl) { + if (strpos($subMenuName, '_') !== 0 && $menuUrl['_url'] == $url) { + return true; + } + } + } + return false; + } + + /** + * Triggers the Menu.Reporting.addItems hook and returns the menu. + * + * @return Array + */ + public function getMenu() + { + if (!$this->menu) { + + /** + * @ignore + * @deprecated + */ + Piwik::postEvent('Menu.Reporting.addItems', array()); + + foreach (Report::getAllReports() as $report) { + if ($report->isEnabled()) { + $report->configureReportingMenu($this); + } + } + + foreach ($this->getAllMenus() as $menu) { + $menu->configureReportingMenu($this); + } + } + + return parent::getMenu(); + } +} diff --git a/www/analytics/core/Menu/MenuTop.php b/www/analytics/core/Menu/MenuTop.php index 98d639b4..687b1b46 100644 --- a/www/analytics/core/Menu/MenuTop.php +++ b/www/analytics/core/Menu/MenuTop.php @@ -1,26 +1,25 @@ add( + * $menu->add( * 'MyPlugin_MyTranslatedMenuCategory', * 'MyPlugin_MyTranslatedMenuName', * array('module' => 'MyPlugin', 'action' => 'index'), @@ -28,40 +27,11 @@ use Piwik\Piwik; * $order = 2 * ); * } - * + * * @method static \Piwik\Menu\MenuTop getInstance() */ class MenuTop extends MenuAbstract { - /** - * Adds a new entry to the TopMenu. - * - * @param string $topMenuName The menu item name. Can be a translation token. - * @param string|array $url The URL the admin menu entry should link to, or an array of query parameters - * that can be used to build the URL. If `$isHTML` is true, this can be a string with - * HTML that is simply embedded. - * @param boolean $displayedForCurrentUser Whether this menu entry should be displayed for the - * current user. If false, the entry will not be added. - * @param int $order The order hint. - * @param bool $isHTML Whether `$url` is an HTML string or a URL that will be rendered as a link. - * @param bool|string $tooltip Optional tooltip to display. - * @api - */ - public static function addEntry($topMenuName, $url, $displayedForCurrentUser = true, $order = 10, $isHTML = false, $tooltip = false) - { - if ($isHTML) { - MenuTop::getInstance()->addHtml($topMenuName, $url, $displayedForCurrentUser, $order, $tooltip); - } else { - MenuTop::getInstance()->add($topMenuName, null, $url, $displayedForCurrentUser, $order, $tooltip); - } - } - - public static function removeEntry($menuName, $subMenuName = false) - { - MenuTop::getInstance()->remove($menuName, $subMenuName); - } - - /** * Directly adds a menu entry containing html. * @@ -70,11 +40,13 @@ class MenuTop extends MenuAbstract * @param boolean $displayedForCurrentUser * @param int $order * @param string $tooltip Tooltip to display. + * @api */ public function addHtml($menuName, $data, $displayedForCurrentUser, $order, $tooltip) { if ($displayedForCurrentUser) { if (!isset($this->menu[$menuName])) { + $this->menu[$menuName]['_name'] = $menuName; $this->menu[$menuName]['_html'] = $data; $this->menu[$menuName]['_order'] = $order; $this->menu[$menuName]['_hasSubmenu'] = false; @@ -93,29 +65,16 @@ class MenuTop extends MenuAbstract if (!$this->menu) { /** - * Triggered when collecting all available menu items that are be displayed on the very top of every - * page, next to the login/logout links. - * - * Subscribe to this event if you want to add one or more items to the top menu. - * - * Menu items should be added via the {@link addEntry()} method. - * - * **Example** - * - * use Piwik\Menu\MenuTop; - * - * public function addMenuItems() - * { - * MenuTop::addEntry( - * 'TopMenuName', - * array('module' => 'MyPlugin', 'action' => 'index'), - * $showOnlyIf = Piwik::hasUserSuperUserAccess(), - * $order = 6 - * ); - * } + * @ignore + * @deprecated */ - Piwik::postEvent('Menu.Top.addItems'); + Piwik::postEvent('Menu.Top.addItems', array()); + + foreach ($this->getAllMenus() as $menu) { + $menu->configureTopMenu($this); + } } + return parent::getMenu(); } } diff --git a/www/analytics/core/Menu/MenuUser.php b/www/analytics/core/Menu/MenuUser.php new file mode 100755 index 00000000..ac3bc295 --- /dev/null +++ b/www/analytics/core/Menu/MenuUser.php @@ -0,0 +1,91 @@ +add( + * 'MyPlugin_MyTranslatedMenuCategory', + * 'MyPlugin_MyTranslatedMenuName', + * array('module' => 'MyPlugin', 'action' => 'index'), + * Piwik::isUserHasSomeAdminAccess(), + * $order = 2 + * ); + * } + * + * @method static MenuUser getInstance() + */ +class MenuUser extends MenuAbstract +{ + + /** + * See {@link add()}. Adds a new menu item to the manage section of the user menu. + * @param string $menuName + * @param array $url + * @param int $order + * @param bool|string $tooltip + * @api + * @since 2.5.0 + */ + public function addPersonalItem($menuName, $url, $order = 50, $tooltip = false) + { + $this->addItem('UsersManager_MenuPersonal', $menuName, $url, $order, $tooltip); + } + + /** + * See {@link add()}. Adds a new menu item to the manage section of the user menu. + * @param string $menuName + * @param array $url + * @param int $order + * @param bool|string $tooltip + * @api + * @since 2.5.0 + */ + public function addManageItem($menuName, $url, $order = 50, $tooltip = false) + { + $this->addItem('CoreAdminHome_MenuManage', $menuName, $url, $order, $tooltip); + } + + /** + * See {@link add()}. Adds a new menu item to the platform section of the user menu. + * @param string $menuName + * @param array $url + * @param int $order + * @param bool|string $tooltip + * @api + * @since 2.5.0 + */ + public function addPlatformItem($menuName, $url, $order = 50, $tooltip = false) + { + $this->addItem('CorePluginsAdmin_MenuPlatform', $menuName, $url, $order, $tooltip); + } + + /** + * Triggers the Menu.User.addItems hook and returns the menu. + * + * @return Array + */ + public function getMenu() + { + if (!$this->menu) { + foreach ($this->getAllMenus() as $menu) { + $menu->configureUserMenu($this); + } + } + + return parent::getMenu(); + } +} diff --git a/www/analytics/core/Metrics.php b/www/analytics/core/Metrics.php index b2ae4c73..50cd33c0 100644 --- a/www/analytics/core/Metrics.php +++ b/www/analytics/core/Metrics.php @@ -1,6 +1,6 @@ 'nb_uniq_visitors', + Metrics::INDEX_NB_UNIQ_FINGERPRINTS => 'nb_uniq_fingerprints', Metrics::INDEX_NB_VISITS => 'nb_visits', Metrics::INDEX_NB_ACTIONS => 'nb_actions', + Metrics::INDEX_NB_USERS => 'nb_users', Metrics::INDEX_MAX_ACTIONS => 'max_actions', Metrics::INDEX_SUM_VISIT_LENGTH => 'sum_visit_length', Metrics::INDEX_BOUNCE_COUNT => 'bounce_count', @@ -98,6 +113,7 @@ class Metrics Metrics::INDEX_REVENUE => 'revenue', Metrics::INDEX_GOALS => 'goals', Metrics::INDEX_SUM_DAILY_NB_UNIQ_VISITORS => 'sum_daily_nb_uniq_visitors', + Metrics::INDEX_SUM_DAILY_NB_USERS => 'sum_daily_nb_users', // Actions metrics Metrics::INDEX_PAGE_NB_HITS => 'nb_hits', @@ -131,11 +147,14 @@ class Metrics Metrics::INDEX_EVENT_SUM_EVENT_VALUE => 'sum_event_value', Metrics::INDEX_EVENT_MIN_EVENT_VALUE => 'min_event_value', Metrics::INDEX_EVENT_MAX_EVENT_VALUE => 'max_event_value', - Metrics::INDEX_EVENT_NB_HITS_WITH_VALUE => 'nb_events_with_value' + Metrics::INDEX_EVENT_NB_HITS_WITH_VALUE => 'nb_events_with_value', + // Contents + Metrics::INDEX_CONTENT_NB_IMPRESSIONS => 'nb_impressions', + Metrics::INDEX_CONTENT_NB_INTERACTIONS => 'nb_interactions' ); - static public $mappingFromIdToNameGoal = array( + public static $mappingFromIdToNameGoal = array( Metrics::INDEX_GOAL_NB_CONVERSIONS => 'nb_conversions', Metrics::INDEX_GOAL_NB_VISITS_CONVERTED => 'nb_visits_converted', Metrics::INDEX_GOAL_REVENUE => 'revenue', @@ -146,29 +165,44 @@ class Metrics Metrics::INDEX_GOAL_ECOMMERCE_ITEMS => 'items', ); - static protected $metricsAggregatedFromLogs = array( + protected static $metricsAggregatedFromLogs = array( Metrics::INDEX_NB_UNIQ_VISITORS, Metrics::INDEX_NB_VISITS, Metrics::INDEX_NB_ACTIONS, + Metrics::INDEX_NB_USERS, Metrics::INDEX_MAX_ACTIONS, Metrics::INDEX_SUM_VISIT_LENGTH, Metrics::INDEX_BOUNCE_COUNT, Metrics::INDEX_NB_VISITS_CONVERTED, ); - static public function getVisitsMetricNames() + public static function getVisitsMetricNames() { $names = array(); + foreach (self::$metricsAggregatedFromLogs as $metricId) { $names[$metricId] = self::$mappingFromIdToName[$metricId]; } + return $names; } - static public function getMappingFromIdToName() + public static function getMappingFromNameToId() { - $idToName = array_flip(self::$mappingFromIdToName); - return $idToName; + static $nameToId = null; + if ($nameToId === null) { + $nameToId = array_flip(self::$mappingFromIdToName); + } + return $nameToId; + } + + public static function getMappingFromNameToIdGoal() + { + static $nameToId = null; + if ($nameToId === null) { + $nameToId = array_flip(self::$mappingFromIdToNameGoal); + } + return $nameToId; } /** @@ -178,7 +212,7 @@ class Metrics * * @ignore */ - static public function isLowerValueBetter($column) + public static function isLowerValueBetter($column) { $lowerIsBetterPatterns = array( 'bounce', 'exit' @@ -200,11 +234,11 @@ class Metrics * @return string * @ignore */ - static public function getUnit($column, $idSite) + public static function getUnit($column, $idSite) { $nameToUnit = array( '_rate' => '%', - 'revenue' => MetricsFormatter::getCurrencySymbol($idSite), + 'revenue' => Site::getCurrencySymbolFor($idSite), '_time_' => 's' ); @@ -217,8 +251,15 @@ class Metrics return ''; } - static public function getDefaultMetricTranslations() + public static function getDefaultMetricTranslations() { + $cacheId = CacheId::pluginAware('DefaultMetricTranslations'); + $cache = PiwikCache::getTransientCache(); + + if ($cache->contains($cacheId)) { + return $cache->fetch($cacheId); + } + $translations = array( 'label' => 'General_ColumnLabel', 'date' => 'General_Date', @@ -248,6 +289,7 @@ class Metrics $afterEntry = ' ' . Piwik::translate('General_AfterEntry'); $translations['sum_daily_nb_uniq_visitors'] = Piwik::translate('General_ColumnNbUniqVisitors') . $dailySum; + $translations['sum_daily_nb_users'] = Piwik::translate('General_ColumnNbUsers') . $dailySum; $translations['sum_daily_entry_nb_uniq_visitors'] = Piwik::translate('General_ColumnUniqueEntrances') . $dailySum; $translations['sum_daily_exit_nb_uniq_visitors'] = Piwik::translate('General_ColumnUniqueExits') . $dailySum; $translations['entry_nb_actions'] = Piwik::translate('General_ColumnNbActions') . $afterEntry; @@ -262,24 +304,44 @@ class Metrics */ Piwik::postEvent('Metrics.getDefaultMetricTranslations', array(&$translations)); - $translations = array_map(array('\\Piwik\\Piwik','translate'), $translations); + $translations = array_map(array('\\Piwik\\Piwik', 'translate'), $translations); + + $cache->save($cacheId, $translations); return $translations; } - static public function getDefaultMetrics() + public static function getDefaultMetrics() { + $cacheId = CacheId::languageAware('DefaultMetrics'); + $cache = PiwikCache::getTransientCache(); + + if ($cache->contains($cacheId)) { + return $cache->fetch($cacheId); + } + $translations = array( 'nb_visits' => 'General_ColumnNbVisits', 'nb_uniq_visitors' => 'General_ColumnNbUniqVisitors', 'nb_actions' => 'General_ColumnNbActions', + 'nb_users' => 'General_ColumnNbUsers', ); - $translations = array_map(array('\\Piwik\\Piwik','translate'), $translations); + $translations = array_map(array('\\Piwik\\Piwik', 'translate'), $translations); + + $cache->save($cacheId, $translations); + return $translations; } - static public function getDefaultProcessedMetrics() + public static function getDefaultProcessedMetrics() { + $cacheId = CacheId::languageAware('DefaultProcessedMetrics'); + $cache = PiwikCache::getTransientCache(); + + if ($cache->contains($cacheId)) { + return $cache->fetch($cacheId); + } + $translations = array( // Processed in AddColumnsProcessedMetrics 'nb_actions_per_visit' => 'General_ColumnActionsPerVisit', @@ -287,22 +349,25 @@ class Metrics 'bounce_rate' => 'General_ColumnBounceRate', 'conversion_rate' => 'General_ColumnConversionRate', ); - return array_map(array('\\Piwik\\Piwik','translate'), $translations); + $translations = array_map(array('\\Piwik\\Piwik', 'translate'), $translations); + + $cache->save($cacheId, $translations); + + return $translations; } - static public function getReadableColumnName($columnIdRaw) + public static function getReadableColumnName($columnIdRaw) { $mappingIdToName = self::$mappingFromIdToName; if (array_key_exists($columnIdRaw, $mappingIdToName)) { - return $mappingIdToName[$columnIdRaw]; } return $columnIdRaw; } - static public function getMetricIdsToProcessReportTotal() + public static function getMetricIdsToProcessReportTotal() { return array( self::INDEX_NB_VISITS, @@ -321,12 +386,20 @@ class Metrics ); } - static public function getDefaultMetricsDocumentation() + public static function getDefaultMetricsDocumentation() { - $documentation = array( + $cacheId = CacheId::pluginAware('DefaultMetricsDocumentation'); + $cache = PiwikCache::getTransientCache(); + + if ($cache->contains($cacheId)) { + return $cache->fetch($cacheId); + } + + $translations = array( 'nb_visits' => 'General_ColumnNbVisitsDocumentation', 'nb_uniq_visitors' => 'General_ColumnNbUniqVisitorsDocumentation', 'nb_actions' => 'General_ColumnNbActionsDocumentation', + 'nb_users' => 'General_ColumnNbUsersDocumentation', 'nb_actions_per_visit' => 'General_ColumnActionsPerVisitDocumentation', 'avg_time_on_site' => 'General_ColumnAvgTimeOnSiteDocumentation', 'bounce_rate' => 'General_ColumnBounceRateDocumentation', @@ -335,7 +408,19 @@ class Metrics 'nb_hits' => 'General_ColumnPageviewsDocumentation', 'exit_rate' => 'General_ColumnExitRateDocumentation' ); - return array_map(array('\\Piwik\\Piwik','translate'), $documentation); + + /** + * Use this event to register translations for metrics documentation processed by your plugin. + * + * @param string[] $translations The array mapping of column_name => Plugin_TranslationForColumnDocumentation + */ + Piwik::postEvent('Metrics.getDefaultMetricDocumentationTranslations', array(&$translations)); + + $translations = array_map(array('\\Piwik\\Piwik', 'translate'), $translations); + + $cache->save($cacheId, $translations); + + return $translations; } public static function getPercentVisitColumn() diff --git a/www/analytics/core/Metrics/Formatter.php b/www/analytics/core/Metrics/Formatter.php new file mode 100644 index 00000000..078edb23 --- /dev/null +++ b/www/analytics/core/Metrics/Formatter.php @@ -0,0 +1,286 @@ +decimalPoint === null) { + $locale = localeconv(); + + $this->decimalPoint = $locale['decimal_point']; + $this->thousandsSeparator = $locale['thousands_sep']; + } + + return number_format($value, $precision, $this->decimalPoint, $this->thousandsSeparator); + } + + /** + * Returns a prettified time value (in seconds). + * + * @param int $numberOfSeconds The number of seconds. + * @param bool $displayTimeAsSentence If set to true, will output `"5min 17s"`, if false `"00:05:17"`. + * @param bool $round Whether to round to the nearest second or not. + * @return string + * @api + */ + public function getPrettyTimeFromSeconds($numberOfSeconds, $displayTimeAsSentence = false, $round = false) + { + $numberOfSeconds = $round ? (int)$numberOfSeconds : (float)$numberOfSeconds; + + $isNegative = false; + if ($numberOfSeconds < 0) { + $numberOfSeconds = -1 * $numberOfSeconds; + $isNegative = true; + } + + // Display 01:45:17 time format + if ($displayTimeAsSentence === false) { + $days = floor($numberOfSeconds / 86400); + $hours = floor(($reminder = ($numberOfSeconds - $days * 86400)) / 3600); + $minutes = floor(($reminder = ($reminder - $hours * 3600)) / 60); + $seconds = floor($reminder - $minutes * 60); + if ($days == 0) { + $time = sprintf("%02s", $hours) . ':' . sprintf("%02s", $minutes) . ':' . sprintf("%02s", $seconds); + } else { + $time = sprintf(Piwik::translate('Intl_NDays'), $days) . " " . sprintf("%02s", $hours) . ':' . sprintf("%02s", $minutes) . ':' . sprintf("%02s", $seconds); + } + $centiSeconds = ($numberOfSeconds * 100) % 100; + if ($centiSeconds) { + $time .= '.' . sprintf("%02s", $centiSeconds); + } + if ($isNegative) { + $time = '-' . $time; + } + return $time; + } + $secondsInYear = 86400 * 365.25; + + $years = floor($numberOfSeconds / $secondsInYear); + $minusYears = $numberOfSeconds - $years * $secondsInYear; + $days = floor($minusYears / 86400); + + $minusDays = $numberOfSeconds - $days * 86400; + $hours = floor($minusDays / 3600); + + $minusDaysAndHours = $minusDays - $hours * 3600; + $minutes = floor($minusDaysAndHours / 60); + + $seconds = $minusDaysAndHours - $minutes * 60; + $precision = ($seconds > 0 && $seconds < 0.01 ? 3 : 2); + $seconds = NumberFormatter::getInstance()->formatNumber(round($seconds, $precision), $precision); + + if ($years > 0) { + $return = sprintf(Piwik::translate('General_YearsDays'), $years, $days); + } elseif ($days > 0) { + $return = sprintf(Piwik::translate('General_DaysHours'), $days, $hours); + } elseif ($hours > 0) { + $return = sprintf(Piwik::translate('General_HoursMinutes'), $hours, $minutes); + } elseif ($minutes > 0) { + $return = sprintf(Piwik::translate('General_MinutesSeconds'), $minutes, $seconds); + } else { + $return = sprintf(Piwik::translate('Intl_NSecondsShort'), $seconds); + } + + if ($isNegative) { + $return = '-' . $return; + } + + return $return; + } + + /** + * Returns a prettified memory size value. + * + * @param number $size The size in bytes. + * @param string $unit The specific unit to use, if any. If null, the unit is determined by $size. + * @param int $precision The precision to use when rounding. + * @return string eg, `'128 M'` or `'256 K'`. + * @api + */ + public function getPrettySizeFromBytes($size, $unit = null, $precision = 1) + { + if ($size == 0) { + return '0 M'; + } + + list($size, $sizeUnit) = $this->getPrettySizeFromBytesWithUnit($size, $unit, $precision); + return $size . " " . $sizeUnit; + } + + /** + * Returns a pretty formated monetary value using the currency associated with a site. + * + * @param int|string $value The monetary value to format. + * @param int $idSite The ID of the site whose currency will be used. + * @return string + * @api + */ + public function getPrettyMoney($value, $idSite) + { + $space = ' '; + $currencySymbol = Site::getCurrencySymbolFor($idSite); + $currencyBefore = $currencySymbol . $space; + $currencyAfter = ''; + // (maybe more currencies prefer this notation?) + $currencySymbolToAppend = array('€', 'kr', 'zł'); + // manually put the currency symbol after the amount + if (in_array($currencySymbol, $currencySymbolToAppend)) { + $currencyAfter = $space . $currencySymbol; + $currencyBefore = ''; + } + // if the input is a number (it could be a string or INPUT form), + // and if this number is not an int, we round to precision 2 + if (is_numeric($value)) { + if ($value == round($value)) { + // 0.0 => 0 + $value = round($value); + } else { + $precision = GoalManager::REVENUE_PRECISION; + $value = sprintf("%01." . $precision . "f", $value); + } + } + $prettyMoney = $currencyBefore . $value . $currencyAfter; + return $prettyMoney; + } + + /** + * Returns a percent string from a quotient value. Forces the use of a '.' + * decimal place. + * + * @param float $value + * @return string + * @api + */ + public function getPrettyPercentFromQuotient($value) + { + $result = ($value * 100) . '%'; + return Common::forceDotAsSeparatorForDecimalPoint($result); + } + + /** + * Formats all metrics, including processed metrics, for a DataTable. Metrics to format + * are found through report metadata and DataTable metadata. + * + * @param DataTable $dataTable The table to format metrics for. + * @param Report|null $report The report the table belongs to. + * @param string[]|null $metricsToFormat Whitelist of names of metrics to format. + * @api + */ + public function formatMetrics(DataTable $dataTable, Report $report = null, $metricsToFormat = null) + { + $metrics = $this->getMetricsToFormat($dataTable, $report); + if (empty($metrics) + || $dataTable->getMetadata(self::PROCESSED_METRICS_FORMATTED_FLAG) + ) { + return; + } + + $dataTable->setMetadata(self::PROCESSED_METRICS_FORMATTED_FLAG, true); + + if ($metricsToFormat !== null) { + $metricMatchRegex = $this->makeRegexToMatchMetrics($metricsToFormat); + $metrics = array_filter($metrics, function (ProcessedMetric $metric) use ($metricMatchRegex) { + return preg_match($metricMatchRegex, $metric->getName()); + }); + } + + foreach ($metrics as $name => $metric) { + if (!$metric->beforeFormat($report, $dataTable)) { + continue; + } + + foreach ($dataTable->getRows() as $row) { + $columnValue = $row->getColumn($name); + if ($columnValue !== false) { + $row->setColumn($name, $metric->format($columnValue, $this)); + } + + $subtable = $row->getSubtable(); + if (!empty($subtable)) { + $this->formatMetrics($subtable, $report, $metricsToFormat); + } + } + } + } + + protected function getPrettySizeFromBytesWithUnit($size, $unit = null, $precision = 1) + { + $units = array('B', 'K', 'M', 'G', 'T'); + $numUnits = count($units) - 1; + + $currentUnit = null; + foreach ($units as $idx => $currentUnit) { + if ($unit && $unit !== $currentUnit) { + $size = $size / 1024; + } elseif ($unit && $unit === $currentUnit) { + break; + } elseif ($size >= 1024 && $idx != $numUnits) { + $size = $size / 1024; + } else { + break; + } + } + + $size = round($size, $precision); + + return array($size, $currentUnit); + } + + private function makeRegexToMatchMetrics($metricsToFormat) + { + $metricsRegexParts = array(); + foreach ($metricsToFormat as $metricFilter) { + if ($metricFilter[0] == '/') { + $metricsRegexParts[] = '(?:' . substr($metricFilter, 1, strlen($metricFilter) - 2) . ')'; + } else { + $metricsRegexParts[] = preg_quote($metricFilter); + } + } + return '/^' . implode('|', $metricsRegexParts) . '$/'; + } + + /** + * @param DataTable $dataTable + * @param Report $report + * @return Metric[] + */ + private function getMetricsToFormat(DataTable $dataTable, Report $report = null) + { + return Report::getMetricsForTable($dataTable, $report, $baseType = 'Piwik\\Plugin\\Metric'); + } +} diff --git a/www/analytics/core/Metrics/Formatter/Html.php b/www/analytics/core/Metrics/Formatter/Html.php new file mode 100644 index 00000000..60c2f43c --- /dev/null +++ b/www/analytics/core/Metrics/Formatter/Html.php @@ -0,0 +1,43 @@ +replaceSpaceWithNonBreakingSpace($result); + return $result; + } + + public function getPrettySizeFromBytes($size, $unit = null, $precision = 1) + { + $result = parent::getPrettySizeFromBytes($size, $unit, $precision); + $result = $this->replaceSpaceWithNonBreakingSpace($result); + return $result; + } + + public function getPrettyMoney($value, $idSite) + { + $result = parent::getPrettyMoney($value, $idSite); + $result = $this->replaceSpaceWithNonBreakingSpace($result); + return $result; + } + + private function replaceSpaceWithNonBreakingSpace($value) + { + return str_replace(' ', ' ', $value); + } +} diff --git a/www/analytics/core/MetricsFormatter.php b/www/analytics/core/MetricsFormatter.php index 10efb19c..0bf5a1a7 100644 --- a/www/analytics/core/MetricsFormatter.php +++ b/www/analytics/core/MetricsFormatter.php @@ -1,242 +1,72 @@ getPrettyNumber($value); } - /** - * Returns a prettified time value (in seconds). - * - * @param int $numberOfSeconds The number of seconds. - * @param bool $displayTimeAsSentence If set to true, will output `"5min 17s"`, if false `"00:05:17"`. - * @param bool $isHtml If true, replaces all spaces with `' '`. - * @param bool $round Whether to round to the nearest second or not. - * @return string - */ public static function getPrettyTimeFromSeconds($numberOfSeconds, $displayTimeAsSentence = true, $isHtml = true, $round = false) { - $numberOfSeconds = $round ? (int)$numberOfSeconds : (float)$numberOfSeconds; - - $isNegative = false; - if ($numberOfSeconds < 0) { - $numberOfSeconds = -1 * $numberOfSeconds; - $isNegative = true; - } - - // Display 01:45:17 time format - if ($displayTimeAsSentence === false) { - $hours = floor($numberOfSeconds / 3600); - $minutes = floor(($reminder = ($numberOfSeconds - $hours * 3600)) / 60); - $seconds = floor($reminder - $minutes * 60); - $time = sprintf("%02s", $hours) . ':' . sprintf("%02s", $minutes) . ':' . sprintf("%02s", $seconds); - $centiSeconds = ($numberOfSeconds * 100) % 100; - if ($centiSeconds) { - $time .= '.' . sprintf("%02s", $centiSeconds); - } - if ($isNegative) { - $time = '-' . $time; - } - return $time; - } - $secondsInYear = 86400 * 365.25; - - $years = floor($numberOfSeconds / $secondsInYear); - $minusYears = $numberOfSeconds - $years * $secondsInYear; - $days = floor($minusYears / 86400); - - $minusDays = $numberOfSeconds - $days * 86400; - $hours = floor($minusDays / 3600); - - $minusDaysAndHours = $minusDays - $hours * 3600; - $minutes = floor($minusDaysAndHours / 60); - - $seconds = $minusDaysAndHours - $minutes * 60; - $precision = ($seconds > 0 && $seconds < 0.01 ? 3 : 2); - $seconds = round($seconds, $precision); - - if ($years > 0) { - $return = sprintf(Piwik::translate('General_YearsDays'), $years, $days); - } elseif ($days > 0) { - $return = sprintf(Piwik::translate('General_DaysHours'), $days, $hours); - } elseif ($hours > 0) { - $return = sprintf(Piwik::translate('General_HoursMinutes'), $hours, $minutes); - } elseif ($minutes > 0) { - $return = sprintf(Piwik::translate('General_MinutesSeconds'), $minutes, $seconds); - } else { - $return = sprintf(Piwik::translate('General_Seconds'), $seconds); - } - - if ($isNegative) { - $return = '-' . $return; - } - - if ($isHtml) { - return str_replace(' ', ' ', $return); - } - return $return; + return self::getFormatter($isHtml)->getPrettyTimeFromSeconds($numberOfSeconds, $displayTimeAsSentence, $round); } - /** - * Returns a prettified memory size value. - * - * @param number $size The size in bytes. - * @param string $unit The specific unit to use, if any. If null, the unit is determined by $size. - * @param int $precision The precision to use when rounding. - * @return string eg, `'128 M'` or `'256 K'`. - */ public static function getPrettySizeFromBytes($size, $unit = null, $precision = 1) { - if ($size == 0) { - return '0 M'; - } - - $units = array('B', 'K', 'M', 'G', 'T'); - foreach ($units as $currentUnit) { - if ($size >= 1024 && $unit != $currentUnit) { - $size = $size / 1024; - } else { - break; - } - } - return round($size, $precision) . " " . $currentUnit; + return self::getFormatter()->getPrettySizeFromBytes($size, $unit, $precision); } - /** - * Returns a pretty formated monetary value using the currency associated with a site. - * - * @param int|string $value The monetary value to format. - * @param int $idSite The ID of the site whose currency will be used. - * @param bool $isHtml If true, replaces all spaces with `' '`. - * @return string - */ public static function getPrettyMoney($value, $idSite, $isHtml = true) { - $currencyBefore = MetricsFormatter::getCurrencySymbol($idSite); - - $space = ' '; - if ($isHtml) { - $space = ' '; - } - - $currencyAfter = ''; - // manually put the currency symbol after the amount for euro - // (maybe more currencies prefer this notation?) - if (in_array($currencyBefore, array('€', 'kr'))) { - $currencyAfter = $space . $currencyBefore; - $currencyBefore = ''; - } - - // if the input is a number (it could be a string or INPUT form), - // and if this number is not an int, we round to precision 2 - if (is_numeric($value)) { - if ($value == round($value)) { - // 0.0 => 0 - $value = round($value); - } else { - $precision = GoalManager::REVENUE_PRECISION; - $value = sprintf("%01." . $precision . "f", $value); - } - } - $prettyMoney = $currencyBefore . $space . $value . $currencyAfter; - return $prettyMoney; + return self::getFormatter($isHtml)->getPrettyMoney($value, $idSite); } - /** - * Prettifies a metric value based on the column name. - * - * @param int $idSite The ID of the site the metric is for (used if the column value is an amount of money). - * @param string $columnName The metric name. - * @param mixed $value The metric value. - * @param bool $isHtml If true, replaces all spaces with `' '`. - * @return string - */ public static function getPrettyValue($idSite, $columnName, $value, $isHtml) { - // Display time in human readable - if (strpos($columnName, 'time') !== false) { - // Little hack: Display 15s rather than 00:00:15, only for "(avg|min|max)_generation_time" - $timeAsSentence = (substr($columnName, -16) == '_time_generation'); - return self::getPrettyTimeFromSeconds($value, $timeAsSentence); - } - // Add revenue symbol to revenues - if (strpos($columnName, 'revenue') !== false && strpos($columnName, 'evolution') === false) { - return self::getPrettyMoney($value, $idSite, $isHtml); - } - // Add % symbol to rates - if (strpos($columnName, '_rate') !== false) { - if (strpos($value, "%") === false) { - return $value . "%"; - } - } - return $value; + return ProcessedReport::getPrettyValue(self::getFormatter($isHtml), $idSite, $columnName, $value); } - /** - * Returns the currency symbol for a site. - * - * @param int $idSite The ID of the site to return the currency symbol for. - * @return string eg, `'$'`. - */ public static function getCurrencySymbol($idSite) { - $symbols = MetricsFormatter::getCurrencyList(); - $site = new Site($idSite); - $currency = $site->getCurrency(); - if (isset($symbols[$currency])) { - return $symbols[$currency][0]; - } - return ''; + return Site::getCurrencySymbolFor($idSite); } - /** - * Returns the list of all known currency symbols. - * - * @return array An array mapping currency codes to their respective currency symbols - * and a description, eg, `array('USD' => array('$', 'US dollar'))`. - */ public static function getCurrencyList() { - static $currenciesList = null; - if (is_null($currenciesList)) { - require_once PIWIK_INCLUDE_PATH . '/core/DataFiles/Currencies.php'; - $currenciesList = $GLOBALS['Piwik_CurrencyList']; - } - return $currenciesList; + return Site::getCurrencyList(); } } diff --git a/www/analytics/core/Nonce.php b/www/analytics/core/Nonce.php index 1538e312..1b64b879 100644 --- a/www/analytics/core/Nonce.php +++ b/www/analytics/core/Nonce.php @@ -1,6 +1,6 @@ nonce; @@ -100,7 +100,7 @@ class Nonce * * @param string $id The unique nonce ID. */ - static public function discardNonce($id) + public static function discardNonce($id) { $ns = new SessionNamespace($id); $ns->unsetAll(); @@ -108,10 +108,10 @@ class Nonce /** * Returns the **Origin** HTTP header or `false` if not found. - * + * * @return string|bool */ - static public function getOrigin() + public static function getOrigin() { if (!empty($_SERVER['HTTP_ORIGIN'])) { return $_SERVER['HTTP_ORIGIN']; @@ -124,7 +124,7 @@ class Nonce * * @return array */ - static public function getAcceptableOrigins() + public static function getAcceptableOrigins() { $host = Url::getCurrentHost(null); $port = ''; @@ -140,8 +140,10 @@ class Nonce } // standard ports - $origins[] = 'http://' . $host; - $origins[] = 'https://' . $host; + $origins = array( + 'http://' . $host, + 'https://' . $host, + ); // non-standard ports if (!empty($port) && $port != 80 && $port != 443) { @@ -154,13 +156,13 @@ class Nonce /** * Verifies and discards a nonce. - * + * * @param string $nonceName The nonce's unique ID. See {@link getNonce()}. * @param string|null $nonce The nonce from the client. If `null`, the value from the * **nonce** query parameter is used. - * @throws Exception if the nonce is invalid. See {@link verifyNonce()}. + * @throws \Exception if the nonce is invalid. See {@link verifyNonce()}. */ - static public function checkNonce($nonceName, $nonce = null) + public static function checkNonce($nonceName, $nonce = null) { if ($nonce === null) { $nonce = Common::getRequestVar('nonce', null, 'string'); diff --git a/www/analytics/core/Notification.php b/www/analytics/core/Notification.php index b7465fd6..b7c576eb 100644 --- a/www/analytics/core/Notification.php +++ b/www/analytics/core/Notification.php @@ -1,6 +1,6 @@ context = Notification::CONTEXT_ERROR; * Notification\Manager::notify('myUniqueNotificationId', $notification); - * + * * **Display a temporary success message** - * + * * $notification = new Notificiation('Success'); * $notification->context = Notification::CONTEXT_SUCCESS; * $notification->type = Notification::TYPE_TOAST; * Notification\Manager::notify('myUniqueNotificationId', $notification); - * + * * **Display a message near the top of the screen** - * + * * $notification = new Notification('Urgent: Your password has expired!'); * $notification->context = Notification::CONTEXT_INFO; * $notification->type = Notification::TYPE_PERSISTENT; * $notification->priority = Notification::PRIORITY_MAX; - * + * * @api */ class Notification @@ -75,7 +75,7 @@ class Notification /** * If this flag is applied, no close icon will be displayed. _Note: persistent notifications always have a close * icon._ - * + * * See {@link $flags}. */ const FLAG_NO_CLEAR = 1; @@ -99,39 +99,39 @@ class Notification /** * The notification title. The title is optional and is displayed directly before the message content. - * + * * @var string */ public $title; /** * The notification message. Must be set. - * + * * @var string */ public $message; /** * Contains extra display options. - * + * * Usage: `$notification->flags = Notification::FLAG_BAR | Notification::FLAG_FOO`. - * + * * @var int */ public $flags = self::FLAG_NO_CLEAR; /** * The notification's display type. See `TYPE_*` constants in {@link Notification}. - * + * * @var string */ public $type = self::TYPE_TRANSIENT; /** * The notification's context (message type). See `CONTEXT_*` constants in {@link Notification}. - * + * * A notification's context determines how it will be styled. - * + * * @var string */ public $context = self::CONTEXT_INFO; @@ -139,7 +139,7 @@ class Notification /** * The notification's priority. The higher the priority, the higher the order. See `PRIORITY_*` * constants in {@link Notification} to see possible priority values. - * + * * @var int */ public $priority; @@ -147,14 +147,14 @@ class Notification /** * If true, the message will not be escaped before being outputted as HTML. If you set this to * `true`, make sure you escape text yourself in order to avoid XSS vulnerabilities. - * + * * @var bool */ public $raw = false; /** * Constructor. - * + * * @param string $message The notification message. * @throws \Exception If the message is empty. */ @@ -169,7 +169,7 @@ class Notification /** * Returns `1` if the notification will be displayed without a close button, `0` if otherwise. - * + * * @return int `1` or `0`. */ public function hasNoClear() @@ -184,7 +184,7 @@ class Notification /** * Returns the notification's priority. If no priority has been set, a priority will be set based * on the notification's context. - * + * * @return int */ public function getPriority() diff --git a/www/analytics/core/Notification/Manager.php b/www/analytics/core/Notification/Manager.php index 57471e1a..e7eafec4 100644 --- a/www/analytics/core/Notification/Manager.php +++ b/www/analytics/core/Notification/Manager.php @@ -1,6 +1,6 @@ notifications[$id] = $notification; } + private static function removeOldestNotificationsIfThereAreTooMany() + { + $maxNotificationsInSession = 30; + + $session = static::getSession(); + + while (count($session->notifications) >= $maxNotificationsInSession) { + array_shift($session->notifications); + } + } + private static function getAllNotifications() { + if (!self::isEnabled()) { + return array(); + } + $session = static::getSession(); return $session->notifications; @@ -116,12 +137,21 @@ class Manager private static function removeNotification($id) { + if (!self::isEnabled()) { + return; + } + $session = static::getSession(); if (array_key_exists($id, $session->notifications)) { unset($session->notifications[$id]); } } + private static function isEnabled() + { + return Session::isWritable() && Session::isReadable(); + } + /** * @return SessionNamespace */ @@ -131,7 +161,7 @@ class Manager static::$session = new SessionNamespace('notification'); } - if (empty(static::$session->notifications)) { + if (empty(static::$session->notifications) && self::isEnabled()) { static::$session->notifications = array(); } diff --git a/www/analytics/core/NumberFormatter.php b/www/analytics/core/NumberFormatter.php new file mode 100644 index 00000000..2b8656b8 --- /dev/null +++ b/www/analytics/core/NumberFormatter.php @@ -0,0 +1,325 @@ +patternNumber = $translator->translate('Intl_NumberFormatNumber'); + $this->patternCurrency = $translator->translate('Intl_NumberFormatCurrency'); + $this->patternPercent = $translator->translate('Intl_NumberFormatPercent'); + $this->symbolPlus = $translator->translate('Intl_NumberSymbolPlus'); + $this->symbolMinus = $translator->translate('Intl_NumberSymbolMinus'); + $this->symbolPercent = $translator->translate('Intl_NumberSymbolPercent'); + $this->symbolGroup = $translator->translate('Intl_NumberSymbolGroup'); + $this->symbolDecimal = $translator->translate('Intl_NumberSymbolDecimal'); + } + + /** + * Parses the given pattern and returns patterns for positive and negative numbers + * + * @param string $pattern + * @return array + */ + protected function parsePattern($pattern) + { + $patterns = explode(';', $pattern); + if (!isset($patterns[1])) { + // No explicit negative pattern was provided, construct it. + $patterns[1] = '-' . $patterns[0]; + } + return $patterns; + } + + /** + * Formats a given number or percent value (if $value starts or ends with a %) + * + * @param string|int|float $value + * @param int $maximumFractionDigits + * @param int $minimumFractionDigits + * @return mixed|string + */ + public function format($value, $maximumFractionDigits=0, $minimumFractionDigits=0) + { + if (is_string($value) + && trim($value, '%') != $value + ) { + return $this->formatPercent($value, $maximumFractionDigits, $minimumFractionDigits); + } + + return $this->formatNumber($value, $maximumFractionDigits, $minimumFractionDigits); + } + + /** + * Formats a given number + * + * @see \Piwik\NumberFormatter::format() + * + * @param string|int|float $value + * @param int $maximumFractionDigits + * @param int $minimumFractionDigits + * @return mixed|string + */ + public function formatNumber($value, $maximumFractionDigits=0, $minimumFractionDigits=0) + { + + static $positivePattern, $negativePattern; + + if (empty($positivePatter) || empty($negativePattern)) { + list($positivePattern, $negativePattern) = $this->parsePattern($this->patternNumber); + } + $negative = $this->isNegative($value); + $pattern = $negative ? $negativePattern : $positivePattern; + + return $this->formatNumberWithPattern($pattern, $value, $maximumFractionDigits, $minimumFractionDigits); + } + + /** + * Formats given number as percent value + * @param string|int|float $value + * @param int $maximumFractionDigits + * @param int $minimumFractionDigits + * @return mixed|string + */ + public function formatPercent($value, $maximumFractionDigits=0, $minimumFractionDigits=0) + { + static $positivePattern, $negativePattern; + + if (empty($positivePatter) || empty($negativePattern)) { + list($positivePattern, $negativePattern) = $this->parsePattern($this->patternPercent); + } + + $newValue = trim($value, " \0\x0B%"); + if (!is_numeric($newValue)) { + return $value; + } + + $negative = $this->isNegative($value); + $pattern = $negative ? $negativePattern : $positivePattern; + + return $this->formatNumberWithPattern($pattern, $newValue, $maximumFractionDigits, $minimumFractionDigits); + } + + + /** + * Formats given number as percent value, but keep the leading + sign if found + * + * @param $value + * @return string + */ + public function formatPercentEvolution($value) + { + $isPositiveEvolution = !empty($value) && ($value > 0 || $value[0] == '+'); + + $formatted = self::formatPercent($value); + + if($isPositiveEvolution) { + return $this->symbolPlus . $formatted; + } + return $formatted; + } + + /** + * Formats given number as percent value + * @param string|int|float $value + * @param string $currency + * @param int $precision + * @return mixed|string + */ + public function formatCurrency($value, $currency, $precision=2) + { + static $positivePattern, $negativePattern; + + if (empty($positivePatter) || empty($negativePattern)) { + list($positivePattern, $negativePattern) = $this->parsePattern($this->patternCurrency); + } + + $newValue = trim($value, " \0\x0B$currency"); + if (!is_numeric($newValue)) { + return $value; + } + + $negative = $this->isNegative($value); + $pattern = $negative ? $negativePattern : $positivePattern; + + if ($newValue == round($newValue)) { + // if no fraction digits available, don't show any + $value = $this->formatNumberWithPattern($pattern, $newValue, 0, 0); + } else { + // show given count of fraction digits otherwise + $value = $this->formatNumberWithPattern($pattern, $newValue, $precision, $precision); + } + + return str_replace('¤', $currency, $value); + } + + /** + * Formats the given number with the given pattern + * + * @param string $pattern + * @param string|int|float $value + * @param int $maximumFractionDigits + * @param int $minimumFractionDigits + * @return mixed|string + */ + protected function formatNumberWithPattern($pattern, $value, $maximumFractionDigits=0, $minimumFractionDigits=0) + { + if (!is_numeric($value)) { + return $value; + } + + $this->usesGrouping = (strpos($pattern, ',') !== false); + // if pattern has number groups, parse them. + if ($this->usesGrouping) { + preg_match('/#+0/', $pattern, $primaryGroupMatches); + $this->primaryGroupSize = $this->secondaryGroupSize = strlen($primaryGroupMatches[0]); + $numberGroups = explode(',', $pattern); + // check for distinct secondary group size. + if (count($numberGroups) > 2) { + $this->secondaryGroupSize = strlen($numberGroups[1]); + } + } + + // Ensure that the value is positive and has the right number of digits. + $negative = $this->isNegative($value); + $signMultiplier = $negative ? '-1' : '1'; + $value = $value / $signMultiplier; + $value = round($value, $maximumFractionDigits); + // Split the number into major and minor digits. + $valueParts = explode('.', $value); + $majorDigits = $valueParts[0]; + // Account for maximumFractionDigits = 0, where the number won't + // have a decimal point, and $valueParts[1] won't be set. + $minorDigits = isset($valueParts[1]) ? $valueParts[1] : ''; + if ($this->usesGrouping) { + // Reverse the major digits, since they are grouped from the right. + $majorDigits = array_reverse(str_split($majorDigits)); + // Group the major digits. + $groups = array(); + $groups[] = array_splice($majorDigits, 0, $this->primaryGroupSize); + while (!empty($majorDigits)) { + $groups[] = array_splice($majorDigits, 0, $this->secondaryGroupSize); + } + // Reverse the groups and the digits inside of them. + $groups = array_reverse($groups); + foreach ($groups as &$group) { + $group = implode(array_reverse($group)); + } + // Reconstruct the major digits. + $majorDigits = implode(',', $groups); + } + if ($minimumFractionDigits < $maximumFractionDigits) { + // Strip any trailing zeroes. + $minorDigits = rtrim($minorDigits, '0'); + if (strlen($minorDigits) < $minimumFractionDigits) { + // Now there are too few digits, re-add trailing zeroes + // until the desired length is reached. + $neededZeroes = $minimumFractionDigits - strlen($minorDigits); + $minorDigits .= str_repeat('0', $neededZeroes); + } + } + // Assemble the final number and insert it into the pattern. + $value = $minorDigits ? $majorDigits . '.' . $minorDigits : $majorDigits; + $value = preg_replace('/#(?:[\.,]#+)*0(?:[,\.][0#]+)*/', $value, $pattern); + // Localize the number. + $value = $this->replaceSymbols($value); + return $value; + } + + + /** + * Replaces number symbols with their localized equivalents. + * + * @param string $value The value being formatted. + * + * @return string + * + * @see http://cldr.unicode.org/translation/number-symbols + */ + protected function replaceSymbols($value) + { + $replacements = array( + '.' => $this->symbolDecimal, + ',' => $this->symbolGroup, + '+' => $this->symbolPlus, + '-' => $this->symbolMinus, + '%' => $this->symbolPercent, + ); + return strtr($value, $replacements); + } + + /** + * @param $value + * @return bool + */ + protected function isNegative($value) + { + return $value < 0; + } + + /** + * @deprecated + * @return self + */ + public static function getInstance() + { + return StaticContainer::get('Piwik\NumberFormatter'); + } +} \ No newline at end of file diff --git a/www/analytics/core/Option.php b/www/analytics/core/Option.php index 4a98138d..d79df967 100644 --- a/www/analytics/core/Option.php +++ b/www/analytics/core/Option.php @@ -1,6 +1,6 @@ setValue($name, $value, $autoload); + self::getInstance()->setValue($name, $value, $autoload); } /** @@ -79,7 +79,7 @@ class Option */ public static function delete($name, $value = null) { - return self::getInstance()->deleteValue($name, $value); + self::getInstance()->deleteValue($name, $value); } /** @@ -91,7 +91,7 @@ class Option */ public static function deleteLike($namePattern, $value = null) { - return self::getInstance()->deleteNameLike($namePattern, $value); + self::getInstance()->deleteNameLike($namePattern, $value); } public static function clearCachedOption($name) @@ -127,21 +127,33 @@ class Option * Singleton instance * @var \Piwik\Option */ - static private $instance = null; + private static $instance = null; /** * Returns Singleton instance * * @return \Piwik\Option */ - static private function getInstance() + private static function getInstance() { if (self::$instance == null) { self::$instance = new self; } + return self::$instance; } + /** + * Sets the singleton instance. For testing purposes. + * + * @param mixed + * @ignore + */ + public static function setSingletonInstance($instance) + { + self::$instance = $instance; + } + /** * Private Constructor */ @@ -162,12 +174,14 @@ class Option if (isset($this->all[$name])) { return $this->all[$name]; } - $value = Db::fetchOne('SELECT option_value ' . - 'FROM `' . Common::prefixTable('option') . '`' . - 'WHERE option_name = ?', $name); + + $value = Db::fetchOne('SELECT option_value FROM `' . Common::prefixTable('option') . '` ' . + 'WHERE option_name = ?', $name); + if ($value === false) { return false; } + $this->all[$name] = $value; return $value; } @@ -175,20 +189,24 @@ class Option protected function setValue($name, $value, $autoLoad = 0) { $autoLoad = (int)$autoLoad; - Db::query('INSERT INTO `' . Common::prefixTable('option') . '` (option_name, option_value, autoload) ' . - ' VALUES (?, ?, ?) ' . - ' ON DUPLICATE KEY UPDATE option_value = ?', - array($name, $value, $autoLoad, $value)); + + $sql = 'INSERT INTO `' . Common::prefixTable('option') . '` (option_name, option_value, autoload) ' . + ' VALUES (?, ?, ?) ' . + ' ON DUPLICATE KEY UPDATE option_value = ?'; + $bind = array($name, $value, $autoLoad, $value); + + Db::query($sql, $bind); + $this->all[$name] = $value; } protected function deleteValue($name, $value) { - $sql = 'DELETE FROM `' . Common::prefixTable('option') . '` WHERE option_name = ?'; + $sql = 'DELETE FROM `' . Common::prefixTable('option') . '` WHERE option_name = ?'; $bind[] = $name; if (isset($value)) { - $sql .= ' AND option_value = ?'; + $sql .= ' AND option_value = ?'; $bind[] = $value; } @@ -199,11 +217,11 @@ class Option protected function deleteNameLike($name, $value = null) { - $sql = 'DELETE FROM `' . Common::prefixTable('option') . '` WHERE option_name LIKE ?'; + $sql = 'DELETE FROM `' . Common::prefixTable('option') . '` WHERE option_name LIKE ?'; $bind[] = $name; if (isset($value)) { - $sql .= ' AND option_value = ?'; + $sql .= ' AND option_value = ?'; $bind[] = $value; } @@ -214,13 +232,15 @@ class Option protected function getNameLike($name) { - $sql = 'SELECT option_name, option_value FROM ' . Common::prefixTable('option') . ' WHERE option_name LIKE ?'; + $sql = 'SELECT option_name, option_value FROM `' . Common::prefixTable('option') . '` WHERE option_name LIKE ?'; $bind = array($name); + $rows = Db::fetchAll($sql, $bind); $result = array(); - foreach (Db::fetchAll($sql, $bind) as $row) { + foreach ($rows as $row) { $result[$row['option_name']] = $row['option_value']; } + return $result; } @@ -235,9 +255,10 @@ class Option return; } - $all = Db::fetchAll('SELECT option_value, option_name - FROM `' . Common::prefixTable('option') . '` - WHERE autoload = 1'); + $table = Common::prefixTable('option'); + $sql = 'SELECT option_value, option_name FROM `' . $table . '` WHERE autoload = 1'; + $all = Db::fetchAll($sql); + foreach ($all as $option) { $this->all[$option['option_name']] = $option['option_value']; } diff --git a/www/analytics/core/Period.php b/www/analytics/core/Period.php index f97fbcaa..0abd1f4b 100644 --- a/www/analytics/core/Period.php +++ b/www/analytics/core/Period.php @@ -1,6 +1,6 @@ date = clone $date; - } - /** - * Creates a new Period instance with a period ID and {@link Date} instance. - * - * _Note: This method cannot create {@link Period\Range} periods._ - * - * @param string $strPeriod `"day"`, `"week"`, `"month"`, `"year"`, `"range"`. - * @param Date|string $date A date within the period or the range of dates. - * @throws Exception If `$strPeriod` is invalid. - * @return \Piwik\Period - */ - static public function factory($strPeriod, $date) - { - if (is_string($date)) { - if (Period::isMultiplePeriod($date, $strPeriod) || $strPeriod == 'range') { - return new Range($strPeriod, $date); - } - - $date = Date::factory($date); - } - - switch ($strPeriod) { - case 'day': - return new Day($date); - break; - - case 'week': - return new Week($date); - break; - - case 'month': - return new Month($date); - break; - - case 'year': - return new Year($date); - break; - - default: - $message = Piwik::translate( - 'General_ExceptionInvalidPeriod', array($strPeriod, 'day, week, month, year, range')); - throw new Exception($message); - break; - } + $this->translator = StaticContainer::get('Piwik\Translation\Translator'); } /** * Returns true if `$dateString` and `$period` represent multiple periods. - * + * * Will return true for date/period combinations where date references multiple * dates and period is not `'range'`. For example, will return true for: - * + * * - **date** = `2012-01-01,2012-02-01` and **period** = `'day'` * - **date** = `2012-01-01,2012-02-01` and **period** = `'week'` * - **date** = `last7` and **period** = `'month'` - * + * * etc. - * + * * @static - * @param $dateString The **date** query parameter value. - * @param $period The **period** query parameter value. + * @param $dateString string The **date** query parameter value. + * @param $period string The **period** query parameter value. * @return boolean */ public static function isMultiplePeriod($dateString, $period) @@ -139,35 +91,22 @@ abstract class Period } /** - * Creates a Period instance using a period, date and timezone. + * Checks the given date format whether it is a correct date format and if not, throw an exception. * - * @param string $timezone The timezone of the date. Only used if `$date` is `'now'`, `'today'`, - * `'yesterday'` or `'yesterdaySameTime'`. - * @param string $period The period string: `"day"`, `"week"`, `"month"`, `"year"`, `"range"`. - * @param string $date The date or date range string. Can be a special value including - * `'now'`, `'today'`, `'yesterday'`, `'yesterdaySameTime'`. - * @return \Piwik\Period + * For valid date formats have a look at the {@link \Piwik\Date::factory()} method and + * {@link isMultiplePeriod()} method. + * + * @param string $dateString + * @throws \Exception If `$dateString` is in an invalid format or if the time is before + * Tue, 06 Aug 1991. */ - public static function makePeriodFromQueryParams($timezone, $period, $date) + public static function checkDateFormat($dateString) { - if (empty($timezone)) { - $timezone = 'UTC'; + if (self::isMultiplePeriod($dateString, 'day')) { + return; } - if ($period == 'range') { - $oPeriod = new Period\Range('range', $date, $timezone, Date::factory('today', $timezone)); - } else { - if (!($date instanceof Date)) { - if ($date == 'now' || $date == 'today') { - $date = date('Y-m-d', Date::factory('now', $timezone)->getTimestamp()); - } elseif ($date == 'yesterday' || $date == 'yesterdaySameTime') { - $date = date('Y-m-d', Date::factory('now', $timezone)->subDay(1)->getTimestamp()); - } - $date = Date::factory($date); - } - $oPeriod = Period::factory($period, $date); - } - return $oPeriod; + Date::factory($dateString); } /** @@ -178,16 +117,20 @@ abstract class Period public function getDateStart() { $this->generate(); + if (count($this->subperiods) == 0) { return $this->getDate(); } + $periods = $this->getSubperiods(); + /** @var $currentPeriod Period */ $currentPeriod = $periods[0]; while ($currentPeriod->getNumberOfSubperiods() > 0) { - $periods = $currentPeriod->getSubperiods(); + $periods = $currentPeriod->getSubperiods(); $currentPeriod = $periods[0]; } + return $currentPeriod->getDate(); } @@ -199,22 +142,26 @@ abstract class Period public function getDateEnd() { $this->generate(); + if (count($this->subperiods) == 0) { return $this->getDate(); } + $periods = $this->getSubperiods(); + /** @var $currentPeriod Period */ $currentPeriod = $periods[count($periods) - 1]; while ($currentPeriod->getNumberOfSubperiods() > 0) { - $periods = $currentPeriod->getSubperiods(); + $periods = $currentPeriod->getSubperiods(); $currentPeriod = $periods[count($periods) - 1]; } + return $currentPeriod->getDate(); } /** * Returns the period ID. - * + * * @return int A unique integer for this type of period. */ public function getId() @@ -224,7 +171,7 @@ abstract class Period /** * Returns the label for the current period. - * + * * @return string `"day"`, `"week"`, `"month"`, `"year"`, `"range"` */ public function getLabel() @@ -247,7 +194,7 @@ abstract class Period /** * Returns the number of available subperiods. - * + * * @return int */ public function getNumberOfSubperiods() @@ -259,7 +206,7 @@ abstract class Period /** * Returns the set of Period instances that together make up this period. For a year, * this would be 12 months. For a month this would be 28-31 days. Etc. - * + * * @return Period[] */ public function getSubperiods() @@ -290,16 +237,18 @@ abstract class Period public function toString($format = "Y-m-d") { $this->generate(); + $dateString = array(); foreach ($this->subperiods as $period) { $dateString[] = $period->toString($format); } + return $dateString; } /** * See {@link toString()}. - * + * * @return string */ public function __toString() @@ -309,7 +258,7 @@ abstract class Period /** * Returns a pretty string describing this period. - * + * * @return string */ abstract public function getPrettyString(); @@ -317,7 +266,7 @@ abstract class Period /** * Returns a short string description of this period that is localized with the currently used * language. - * + * * @return string */ abstract public function getLocalizedShortString(); @@ -325,18 +274,141 @@ abstract class Period /** * Returns a long string description of this period that is localized with the currently used * language. - * + * * @return string */ abstract public function getLocalizedLongString(); /** - * Returns a succinct string describing this period. - * + * Returns the label of the period type that is one size smaller than this one, or null if + * it's the smallest. + * + * Range periods and other such 'period collections' are not considered as separate from + * the value type of the collection. So a range period will return the result of the + * subperiod's `getImmediateChildPeriodLabel()` method. + * + * @ignore + * @return string|null + */ + abstract public function getImmediateChildPeriodLabel(); + + /** + * Returns the label of the period type that is one size bigger than this one, or null + * if it's the biggest. + * + * Range periods and other such 'period collections' are not considered as separate from + * the value type of the collection. So a range period will return the result of the + * subperiod's `getParentPeriodLabel()` method. + * + * @ignore + */ + abstract public function getParentPeriodLabel(); + + /** + * Returns the date range string comprising two dates + * * @return string eg, `'2012-01-01,2012-01-31'`. */ public function getRangeString() { - return $this->getDateStart()->toString("Y-m-d") . "," . $this->getDateEnd()->toString("Y-m-d"); + $dateStart = $this->getDateStart(); + $dateEnd = $this->getDateEnd(); + + return $dateStart->toString("Y-m-d") . "," . $dateEnd->toString("Y-m-d"); + } + + /** + * @param string $format + * + * @return mixed + */ + protected function getTranslatedRange($format) + { + $dateStart = $this->getDateStart(); + $dateEnd = $this->getDateEnd(); + list($formatStart, $formatEnd) = $this->explodeFormat($format); + + $string = $dateStart->getLocalized($formatStart); + $string .= $dateEnd->getLocalized($formatEnd); + + return $string; + } + + /** + * Explodes the given format into two pieces. One that can be user for start date and the other for end date + * + * @param $format + * @return array + */ + protected function explodeFormat($format) + { + $intervalTokens = array( + array('d', 'E', 'C'), + array('M', 'L'), + array('y') + ); + + $offset = strlen($format); + // replace string literals encapsulated by ' with same country of * + $cleanedFormat = preg_replace_callback('/(\'[^\']+\')/', array($this, 'replaceWithStars'), $format); + + // search for first duplicate date field + foreach ($intervalTokens AS $tokens) { + if (preg_match_all('/[' . implode('|', $tokens) . ']+/', $cleanedFormat, $matches, PREG_OFFSET_CAPTURE) && + count($matches[0]) > 1 && $offset > $matches[0][1][1] + ) { + $offset = $matches[0][1][1]; + } + } + + return array(substr($format, 0, $offset), substr($format, $offset)); + } + + private function replaceWithStars($matches) + { + return str_repeat("*", strlen($matches[0])); + } + + protected function getRangeFormat($short = false) + { + $maxDifference = 'D'; + if ($this->getDateStart()->toString('y') != $this->getDateEnd()->toString('y')) { + $maxDifference = 'Y'; + } elseif ($this->getDateStart()->toString('m') != $this->getDateEnd()->toString('m')) { + $maxDifference = 'M'; + } + + $dateTimeFormatProvider = StaticContainer::get('Piwik\Intl\Data\Provider\DateTimeFormatProvider'); + + return $dateTimeFormatProvider->getRangeFormatPattern($short, $maxDifference); + } + + /** + * Returns all child periods that exist within this periods entire date range. Cascades + * downwards over all period types that are smaller than this one. For example, month periods + * will cascade to week and day periods and year periods will cascade to month, week and day + * periods. + * + * The method will not return periods that are outside the range of this period. + * + * @return Period[] + * @ignore + */ + public function getAllOverlappingChildPeriods() + { + return $this->getAllOverlappingChildPeriodsInRange($this->getDateStart(), $this->getDateEnd()); + } + + private function getAllOverlappingChildPeriodsInRange(Date $dateStart, Date $dateEnd) + { + $result = array(); + + $childPeriodType = $this->getImmediateChildPeriodLabel(); + if (empty($childPeriodType)) { + return $result; + } + + $childPeriods = Factory::build($childPeriodType, $dateStart->toString() . ',' . $dateEnd->toString()); + return array_merge($childPeriods->getSubperiods(), $childPeriods->getAllOverlappingChildPeriodsInRange($dateStart, $dateEnd)); } } diff --git a/www/analytics/core/Period/Day.php b/www/analytics/core/Period/Day.php index 422843e1..108d42e2 100644 --- a/www/analytics/core/Period/Day.php +++ b/www/analytics/core/Period/Day.php @@ -1,6 +1,6 @@ getDateStart(); - $out = $date->getLocalized(Piwik::translate('CoreHome_ShortDateFormat')); + $date = $this->getDateStart(); + $out = $date->getLocalized(Date::DATE_FORMAT_DAY_MONTH); return $out; } @@ -50,9 +52,8 @@ class Day extends Period public function getLocalizedLongString() { //"Mon 15 Aug" - $date = $this->getDateStart(); - $template = Piwik::translate('CoreHome_DateFormat'); - $out = $date->getLocalized($template); + $date = $this->getDateStart(); + $out = $date->getLocalized(Date::DATE_FORMAT_LONG); return $out; } @@ -99,4 +100,14 @@ class Day extends Period { return $this->toString(); } + + public function getImmediateChildPeriodLabel() + { + return null; + } + + public function getParentPeriodLabel() + { + return 'week'; + } } diff --git a/www/analytics/core/Period/Factory.php b/www/analytics/core/Period/Factory.php new file mode 100644 index 00000000..88c29a92 --- /dev/null +++ b/www/analytics/core/Period/Factory.php @@ -0,0 +1,130 @@ +getTimestamp()); + } elseif ($date == 'yesterday' || $date == 'yesterdaySameTime') { + $date = date('Y-m-d', Date::factory('now', $timezone)->subDay(1)->getTimestamp()); + } + $date = Date::factory($date); + } + $oPeriod = Factory::build($period, $date); + } + return $oPeriod; + } + + /** + * @param $period + * @return bool + */ + public static function isPeriodEnabledForAPI($period) + { + $periodValidator = new PeriodValidator(); + return $periodValidator->isPeriodAllowedForAPI($period); + } + + /** + * @return array + */ + public static function getPeriodsEnabledForAPI() + { + $periodValidator = new PeriodValidator(); + return $periodValidator->getPeriodsAllowedForAPI(); + } +} diff --git a/www/analytics/core/Period/Month.php b/www/analytics/core/Period/Month.php index 4132c9c8..7a52bd06 100644 --- a/www/analytics/core/Period/Month.php +++ b/www/analytics/core/Period/Month.php @@ -1,6 +1,6 @@ getDateStart()->getLocalized(Piwik::translate('CoreHome_ShortMonthFormat')); + $out = $this->getDateStart()->getLocalized(Date::DATE_FORMAT_MONTH_SHORT); return $out; } @@ -37,7 +39,7 @@ class Month extends Period public function getLocalizedLongString() { //"August 2009" - $out = $this->getDateStart()->getLocalized(Piwik::translate('CoreHome_LongMonthFormat')); + $out = $this->getDateStart()->getLocalized(Date::DATE_FORMAT_MONTH_LONG); return $out; } @@ -60,15 +62,65 @@ class Month extends Period if ($this->subperiodsProcessed) { return; } + parent::generate(); $date = $this->date; - $startMonth = $date->setDay(1); - $currentDay = clone $startMonth; - while ($currentDay->compareMonth($startMonth) == 0) { - $this->addSubperiod(new Day($currentDay)); - $currentDay = $currentDay->addDay(1); + $startMonth = $date->setDay(1)->setTime('00:00:00'); + $endMonth = $startMonth->addPeriod(1, 'month')->setDay(1)->subDay(1); + + $this->processOptimalSubperiods($startMonth, $endMonth); + } + + /** + * Determine which kind of period is best to use. + * See Range.test.php + * + * @param Date $startDate + * @param Date $endDate + */ + protected function processOptimalSubperiods($startDate, $endDate) + { + while ($startDate->isEarlier($endDate) + || $startDate == $endDate) { + $week = new Week($startDate); + $startOfWeek = $week->getDateStart(); + $endOfWeek = $week->getDateEnd(); + + if ($endOfWeek->isLater($endDate)) { + $this->fillDayPeriods($startDate, $endDate); + } elseif ($startOfWeek == $startDate) { + $this->addSubperiod($week); + } else { + $this->fillDayPeriods($startDate, $endOfWeek); + } + + $startDate = $endOfWeek->addDay(1); } } + + /** + * Fills the periods from startDate to endDate with days + * + * @param Date $startDate + * @param Date $endDate + */ + private function fillDayPeriods($startDate, $endDate) + { + while (($startDate->isEarlier($endDate) || $startDate == $endDate)) { + $this->addSubperiod(new Day($startDate)); + $startDate = $startDate->addDay(1); + } + } + + public function getImmediateChildPeriodLabel() + { + return 'week'; + } + + public function getParentPeriodLabel() + { + return 'year'; + } } diff --git a/www/analytics/core/Period/PeriodValidator.php b/www/analytics/core/Period/PeriodValidator.php new file mode 100644 index 00000000..b29077b3 --- /dev/null +++ b/www/analytics/core/Period/PeriodValidator.php @@ -0,0 +1,52 @@ +getPeriodsAllowedForUI()); + } + + /** + * @param string $period + * @return bool + */ + public function isPeriodAllowedForAPI($period) + { + return in_array($period, $this->getPeriodsAllowedForAPI()); + } + + /** + * @return string[] + */ + public function getPeriodsAllowedForUI() + { + $periodsAllowed = Config::getInstance()->General['enabled_periods_UI']; + + return array_map('trim', explode(',', $periodsAllowed)); + } + + /** + * @return string[] + */ + public function getPeriodsAllowedForAPI() + { + $periodsAllowed = Config::getInstance()->General['enabled_periods_API']; + + return array_map('trim', explode(',', $periodsAllowed)); + } +} diff --git a/www/analytics/core/Period/Range.php b/www/analytics/core/Period/Range.php index ab542fe8..7073ad2c 100644 --- a/www/analytics/core/Period/Range.php +++ b/www/analytics/core/Period/Range.php @@ -1,6 +1,6 @@ strPeriod = $strPeriod; - $this->strDate = $strDate; + $this->strDate = $strDate; + $this->timezone = $timezone; $this->defaultEndDate = null; - $this->timezone = $timezone; + if ($today === false) { $today = Date::factory('now', $this->timezone); } + $this->today = $today; + + $this->translator = StaticContainer::get('Piwik\Translation\Translator'); + } + + private function getCache() + { + return Cache::getTransientCache(); + } + + private function getCacheId() + { + $end = ''; + if ($this->defaultEndDate) { + $end = $this->defaultEndDate->getTimestamp(); + } + + $today = $this->today->getTimestamp(); + + return 'range' . $this->strPeriod . $this->strDate . $this->timezone . $end . $today; + } + + private function loadAllFromCache() + { + $range = $this->getCache()->fetch($this->getCacheId()); + + if (!empty($range)) { + foreach ($range as $key => $val) { + $this->$key = $val; + } + } + } + + private function cacheAll() + { + $props = get_object_vars($this); + + $this->getCache()->save($this->getCacheId(), $props); } /** @@ -61,14 +108,7 @@ class Range extends Period */ public function getLocalizedShortString() { - //"30 Dec 08 - 26 Feb 09" - $dateStart = $this->getDateStart(); - $dateEnd = $this->getDateEnd(); - $template = Piwik::translate('CoreHome_ShortDateFormatWithYear'); - $shortDateStart = $dateStart->getLocalized($template); - $shortDateEnd = $dateEnd->getLocalized($template); - $out = "$shortDateStart - $shortDateEnd"; - return $out; + return $this->getTranslatedRange($this->getRangeFormat(true)); } /** @@ -78,7 +118,7 @@ class Range extends Period */ public function getLocalizedLongString() { - return $this->getLocalizedShortString(); + return $this->getTranslatedRange($this->getRangeFormat()); } /** @@ -90,9 +130,11 @@ class Range extends Period public function getDateStart() { $dateStart = parent::getDateStart(); + if (empty($dateStart)) { throw new Exception("Specified date range is invalid."); } + return $dateStart; } @@ -103,7 +145,7 @@ class Range extends Period */ public function getPrettyString() { - $out = Piwik::translate('General_DateRangeFromTo', array($this->getDateStart()->toString(), $this->getDateEnd()->toString())); + $out = $this->translator->translate('General_DateRangeFromTo', array($this->getDateStart()->toString(), $this->getDateEnd()->toString())); return $out; } @@ -150,6 +192,12 @@ class Range extends Period return; } + $this->loadAllFromCache(); + + if ($this->subperiodsProcessed) { + return; + } + parent::generate(); if (preg_match('/(last|previous)([0-9]*)/', $this->strDate, $regs)) { @@ -180,20 +228,16 @@ class Range extends Period // last1 means only one result ; last2 means 2 results so we remove only 1 to the days/weeks/etc $lastN--; - $lastN = abs($lastN); + if ($lastN < 0) { + $lastN = 0; + } $startDate = $endDate->addPeriod(-1 * $lastN, $period); - } elseif ($dateRange = Range::parseDateRange($this->strDate)) { $strDateStart = $dateRange[1]; $strDateEnd = $dateRange[2]; $startDate = Date::factory($strDateStart); - if ($strDateEnd == 'today') { - $strDateEnd = 'now'; - } elseif ($strDateEnd == 'yesterday') { - $strDateEnd = 'yesterdaySameTime'; - } // we set the timezone in the Date object only if the date is relative eg. 'today', 'yesterday', 'now' $timezone = null; if (strpos($strDateEnd, '-') === false) { @@ -201,16 +245,20 @@ class Range extends Period } $endDate = Date::factory($strDateEnd, $timezone); } else { - throw new Exception(Piwik::translate('General_ExceptionInvalidDateRange', array($this->strDate, ' \'lastN\', \'previousN\', \'YYYY-MM-DD,YYYY-MM-DD\''))); + throw new Exception($this->translator->translate('General_ExceptionInvalidDateRange', array($this->strDate, ' \'lastN\', \'previousN\', \'YYYY-MM-DD,YYYY-MM-DD\''))); } + if ($this->strPeriod != 'range') { $this->fillArraySubPeriods($startDate, $endDate, $this->strPeriod); + $this->cacheAll(); return; } + $this->processOptimalSubperiods($startDate, $endDate); // When period=range, we want End Date to be the actual specified end date, // rather than the end of the month / week / whatever is used for processing this range $this->endDate = $endDate; + $this->cacheAll(); } /** @@ -220,12 +268,14 @@ class Range extends Period * @param string $dateString * @return mixed array(1 => dateStartString, 2 => dateEndString) or `false` if the input was not a date range. */ - static public function parseDateRange($dateString) + public static function parseDateRange($dateString) { $matched = preg_match('/^([0-9]{4}-[0-9]{1,2}-[0-9]{1,2}),(([0-9]{4}-[0-9]{1,2}-[0-9]{1,2})|today|now|yesterday)$/D', trim($dateString), $regs); + if (empty($matched)) { return false; } + return $regs; } @@ -241,6 +291,7 @@ class Range extends Period if (!is_null($this->endDate)) { return $this->endDate; } + return parent::getDateEnd(); } @@ -278,7 +329,7 @@ class Range extends Period ) { $this->addSubperiod($year); $endOfPeriod = $endOfYear; - } else if ($startDate == $startOfMonth + } elseif ($startDate == $startOfMonth && ($endOfMonth->isEarlier($endDate) || $endOfMonth == $endDate || $endOfMonth->isLater($this->today) @@ -338,14 +389,14 @@ class Range extends Period protected function fillArraySubPeriods($startDate, $endDate, $period) { $arrayPeriods = array(); - $endSubperiod = Period::factory($period, $endDate); + $endSubperiod = Period\Factory::build($period, $endDate); $arrayPeriods[] = $endSubperiod; // set end date to start of end period since we're comparing against start date. $endDate = $endSubperiod->getDateStart(); while ($endDate->isLater($startDate)) { $endDate = $endDate->addPeriod(-1, $period); - $subPeriod = Period::factory($period, $endDate); + $subPeriod = Period\Factory::build($period, $endDate); $arrayPeriods[] = $subPeriod; } $arrayPeriods = array_reverse($arrayPeriods); @@ -400,8 +451,9 @@ class Range extends Period $strLastDate = false; $lastPeriod = false; if ($period != 'range' && !preg_match('/(last|previous)([0-9]*)/', $date, $regs)) { - if (strpos($date, ',')) // date in the form of 2011-01-01,2011-02-02 - { + if (strpos($date, ',')) { + // date in the form of 2011-01-01,2011-02-02 + $rangePeriod = new Range($period, $date); $lastStartDate = $rangePeriod->getDateStart()->subPeriod($subXPeriods, $period); @@ -425,7 +477,7 @@ class Range extends Period * @param int $lastN The number of periods of type `$period` that the result range should * span. * @param string $endDate The desired end date of the range. - * @param Site $site The site whose timezone should be used. + * @param \Piwik\Site $site The site whose timezone should be used. * @return string The date range string, eg, `'2012-01-02,2013-01-02'`. * @api */ @@ -434,6 +486,7 @@ class Range extends Period $last30Relative = new Range($period, $lastN, $site->getTimezone()); $last30Relative->setDefaultEndDate(Date::factory($endDate)); $date = $last30Relative->getDateStart()->toString() . "," . $last30Relative->getDateEnd()->toString(); + return $date; } @@ -448,4 +501,28 @@ class Range extends Period return $isEndOfWeekLaterThanEndDate; } + + /** + * Returns the date range string comprising two dates + * + * @return string eg, `'2012-01-01,2012-01-31'`. + */ + public function getRangeString() + { + $dateStart = $this->getDateStart(); + $dateEnd = $this->getDateEnd(); + + return $dateStart->toString("Y-m-d") . "," . $dateEnd->toString("Y-m-d"); + } + + public function getImmediateChildPeriodLabel() + { + $subperiods = $this->getSubperiods(); + return reset($subperiods)->getImmediateChildPeriodLabel(); + } + + public function getParentPeriodLabel() + { + return null; + } } diff --git a/www/analytics/core/Period/Week.php b/www/analytics/core/Period/Week.php index 0cf18c06..0029855a 100644 --- a/www/analytics/core/Period/Week.php +++ b/www/analytics/core/Period/Week.php @@ -1,6 +1,6 @@ getDateStart(); - $dateEnd = $this->getDateEnd(); - - $string = Piwik::translate('CoreHome_ShortWeekFormat'); - $string = self::getTranslatedRange($string, $dateStart, $dateEnd); - return $string; + return $this->getTranslatedRange($this->getRangeFormat(true)); } /** @@ -41,25 +35,8 @@ class Week extends Period */ public function getLocalizedLongString() { - $format = Piwik::translate('CoreHome_LongWeekFormat'); - $string = self::getTranslatedRange($format, $this->getDateStart(), $this->getDateEnd()); - return Piwik::translate('CoreHome_PeriodWeek') . " " . $string; - } - - /** - * @param string $format - * @param \Piwik\Date $dateStart - * @param \Piwik\Date $dateEnd - * - * @return mixed - */ - static protected function getTranslatedRange($format, $dateStart, $dateEnd) - { - $string = str_replace('From%', '%', $format); - $string = $dateStart->getLocalized($string); - $string = str_replace('To%', '%', $string); - $string = $dateEnd->getLocalized($string); - return $string; + $string = $this->getTranslatedRange($this->getRangeFormat()); + return $this->translator->translate('Intl_PeriodWeek') . " " . $string; } /** @@ -69,10 +46,11 @@ class Week extends Period */ public function getPrettyString() { - $out = Piwik::translate('General_DateRangeFromTo', - array($this->getDateStart()->toString(), - $this->getDateEnd()->toString()) - ); + $dateStart = $this->getDateStart(); + $dateEnd = $this->getDateEnd(); + + $out = $this->translator->translate('General_DateRangeFromTo', array($dateStart->toString(), $dateEnd->toString())); + return $out; } @@ -84,6 +62,7 @@ class Week extends Period if ($this->subperiodsProcessed) { return; } + parent::generate(); $date = $this->date; @@ -99,4 +78,14 @@ class Week extends Period $currentDay = $currentDay->addDay(1); } } + + public function getImmediateChildPeriodLabel() + { + return 'day'; + } + + public function getParentPeriodLabel() + { + return 'month'; + } } diff --git a/www/analytics/core/Period/Year.php b/www/analytics/core/Period/Year.php index 97fb8fa0..208c6bbf 100644 --- a/www/analytics/core/Period/Year.php +++ b/www/analytics/core/Period/Year.php @@ -1,6 +1,6 @@ getDateStart()->getLocalized("%longYear%"); + $out = $this->getDateStart()->getLocalized(Date::DATE_FORMAT_YEAR); return $out; } @@ -58,6 +60,7 @@ class Year extends Period if ($this->subperiodsProcessed) { return; } + parent::generate(); $year = $this->date->toString("Y"); @@ -75,13 +78,25 @@ class Year extends Period * @param string $format * @return array */ - function toString($format = 'ignored') + public function toString($format = 'ignored') { $this->generate(); + $stringMonth = array(); foreach ($this->subperiods as $month) { $stringMonth[] = $month->getDateStart()->toString("Y") . "-" . $month->getDateStart()->toString("m") . "-01"; } + return $stringMonth; } + + public function getImmediateChildPeriodLabel() + { + return 'month'; + } + + public function getParentPeriodLabel() + { + return null; + } } diff --git a/www/analytics/core/Piwik.php b/www/analytics/core/Piwik.php index 95abfe78..89b1d6ce 100644 --- a/www/analytics/core/Piwik.php +++ b/www/analytics/core/Piwik.php @@ -1,6 +1,6 @@ 1, - 'week' => 2, - 'month' => 3, - 'year' => 4, - 'range' => 5, + 'day' => Day::PERIOD_ID, + 'week' => Week::PERIOD_ID, + 'month' => Month::PERIOD_ID, + 'year' => Year::PERIOD_ID, + 'range' => Range::PERIOD_ID, ); /** * The idGoal query parameter value for the special 'abandoned carts' goal. - * + * * @api */ const LABEL_ID_GOAL_IS_ECOMMERCE_CART = 'ecommerceAbandonedCart'; /** * The idGoal query parameter value for the special 'ecommerce' goal. - * + * * @api */ const LABEL_ID_GOAL_IS_ECOMMERCE_ORDER = 'ecommerceOrder'; @@ -64,7 +62,7 @@ class Piwik * * @param string $message */ - static public function error($message = '') + public static function error($message = '') { trigger_error($message, E_USER_ERROR); } @@ -75,15 +73,13 @@ class Piwik * * @param string $message */ - static public function exitWithErrorMessage($message) + public static function exitWithErrorMessage($message) { - if (!Common::isPhpCliMode()) { - @header('Content-Type: text/html; charset=utf-8'); - } + Common::sendHeader('Content-Type: text/html; charset=utf-8'); $output = "\n" . - "
" . - "

" . + "

" . + "

" . $message . "

"; print($output); @@ -98,7 +94,7 @@ class Piwik * @param number $i2 * @return number The result of the division or zero */ - static public function secureDiv($i1, $i2) + public static function secureDiv($i1, $i2) { if (is_numeric($i1) && is_numeric($i2) && floatval($i2) != 0) { return $i1 / $i2; @@ -114,110 +110,25 @@ class Piwik * @param int $precision * @return number */ - static public function getPercentageSafe($dividend, $divisor, $precision = 0) + public static function getPercentageSafe($dividend, $divisor, $precision = 0) + { + return self::getQuotientSafe(100 * $dividend, $divisor, $precision); + } + + /** + * Safely compute a ratio. Returns 0 if divisor is 0 (to avoid division by 0 error). + * + * @param number $dividend + * @param number $divisor + * @param int $precision + * @return number + */ + public static function getQuotientSafe($dividend, $divisor, $precision = 0) { if ($divisor == 0) { return 0; } - return round(100 * $dividend / $divisor, $precision); - } - - /** - * Returns the Javascript code to be inserted on every page to track - * - * @param int $idSite - * @param string $piwikUrl http://path/to/piwik/directory/ - * @return string - */ - static public function getJavascriptCode($idSite, $piwikUrl, $mergeSubdomains = false, $groupPageTitlesByDomain = false, - $mergeAliasUrls = false, $visitorCustomVariables = false, $pageCustomVariables = false, - $customCampaignNameQueryParam = false, $customCampaignKeywordParam = false, - $doNotTrack = false) - { - // changes made to this code should be mirrored in plugins/CoreAdminHome/javascripts/jsTrackingGenerator.js var generateJsCode - $jsCode = file_get_contents(PIWIK_INCLUDE_PATH . "/plugins/Zeitgeist/templates/javascriptCode.tpl"); - $jsCode = htmlentities($jsCode); - if(substr($piwikUrl, 0, 4) !== 'http') { - $piwikUrl = 'http://' . $piwikUrl; - } - preg_match('~^(http|https)://(.*)$~D', $piwikUrl, $matches); - $piwikUrl = rtrim(@$matches[2], "/"); - - // Build optional parameters to be added to text - $options = ''; - if ($groupPageTitlesByDomain) { - $options .= ' _paq.push(["setDocumentTitle", document.domain + "/" + document.title]);' . PHP_EOL; - } - if ($mergeSubdomains || $mergeAliasUrls) { - $options .= self::getJavascriptTagOptions($idSite, $mergeSubdomains, $mergeAliasUrls); - } - $maxCustomVars = Plugins\CustomVariables\CustomVariables::getMaxCustomVariables(); - if ($visitorCustomVariables) { - $options .= ' // you can set up to ' . $maxCustomVars . ' custom variables for each visitor' . PHP_EOL; - $index = 0; - foreach ($visitorCustomVariables as $visitorCustomVariable) { - $options .= ' _paq.push(["setCustomVariable", '.$index++.', "'.$visitorCustomVariable[0].'", "'.$visitorCustomVariable[1].'", "visit"]);' . PHP_EOL; - } - } - if ($pageCustomVariables) { - $options .= ' // you can set up to ' . $maxCustomVars . ' custom variables for each action (page view, download, click, site search)' . PHP_EOL; - $index = 0; - foreach ($pageCustomVariables as $pageCustomVariable) { - $options .= ' _paq.push(["setCustomVariable", '.$index++.', "'.$pageCustomVariable[0].'", "'.$pageCustomVariable[1].'", "page"]);' . PHP_EOL; - } - } - if ($customCampaignNameQueryParam) { - $options .= ' _paq.push(["setCampaignNameKey", "'.$customCampaignNameQueryParam.'"]);' . PHP_EOL; - } - if ($customCampaignKeywordParam) { - $options .= ' _paq.push(["setCampaignKeywordKey", "'.$customCampaignKeywordParam.'"]);' . PHP_EOL; - } - if ($doNotTrack) { - $options .= ' _paq.push(["setDoNotTrack", true]);' . PHP_EOL; - } - - $codeImpl = array( - 'idSite' => $idSite, - 'piwikUrl' => Common::sanitizeInputValue($piwikUrl), - 'options' => $options - ); - $parameters = compact('mergeSubdomains', 'groupPageTitlesByDomain', 'mergeAliasUrls', 'visitorCustomVariables', - 'pageCustomVariables', 'customCampaignNameQueryParam', 'customCampaignKeywordParam', - 'doNotTrack'); - - /** - * Triggered when generating JavaScript tracking code server side. Plugins can use - * this event to customise the JavaScript tracking code that is displayed to the - * user. - * - * @param array &$codeImpl An array containing snippets of code that the event handler - * can modify. Will contain the following elements: - * - * - **idSite**: The ID of the site being tracked. - * - **piwikUrl**: The tracker URL to use. - * - **options**: A string of JavaScript code that customises - * the JavaScript tracker. - * - * The **httpsPiwikUrl** element can be set if the HTTPS - * domain is different from the normal domain. - * @param array $parameters The parameters supplied to the `Piwik::getJavascriptCode()`. - */ - self::postEvent('Piwik.getJavascriptCode', array(&$codeImpl, $parameters)); - - if (!empty($codeImpl['httpsPiwikUrl'])) { - $setTrackerUrl = 'var u=(("https:" == document.location.protocol) ? "https://{$httpsPiwikUrl}/" : ' - . '"http://{$piwikUrl}/");'; - - $codeImpl['httpsPiwikUrl'] = rtrim($codeImpl['httpsPiwikUrl'], "/"); - } else { - $setTrackerUrl = 'var u=(("https:" == document.location.protocol) ? "https" : "http") + "://{$piwikUrl}/";'; - } - $codeImpl = array('setTrackerUrl' => htmlentities($setTrackerUrl)) + $codeImpl; - - foreach ($codeImpl as $keyToReplace => $replaceWith) { - $jsCode = str_replace('{$' . $keyToReplace . '}', $replaceWith, $jsCode); - } - return $jsCode; + return round($dividend / $divisor, $precision); } /** @@ -225,7 +136,7 @@ class Piwik * * @return string */ - static public function getRandomTitle() + public static function getRandomTitle() { static $titles = array( 'Web analytics', @@ -234,11 +145,8 @@ class Piwik 'Analytics', 'Real Time Analytics', 'Analytics in Real time', - 'Open Source Analytics', - 'Open Source Web Analytics', - 'Free Website Analytics', - 'Free Web Analytics', 'Analytics Platform', + 'Data Platform', ); $id = abs(intval(md5(Url::getCurrentHost()))); $title = $titles[$id % count($titles)]; @@ -255,7 +163,7 @@ class Piwik * @return string * @api */ - static public function getCurrentUserEmail() + public static function getCurrentUserEmail() { $user = APIUsersManager::getInstance()->getUser(Piwik::getCurrentUserLogin()); return $user['email']; @@ -266,7 +174,7 @@ class Piwik * * @return array */ - static public function getAllSuperUserAccessEmailAddresses() + public static function getAllSuperUserAccessEmailAddresses() { $emails = array(); @@ -289,9 +197,14 @@ class Piwik * @return string * @api */ - static public function getCurrentUserLogin() + public static function getCurrentUserLogin() { - return Access::getInstance()->getLogin(); + $login = Access::getInstance()->getLogin(); + + if (empty($login)) { + return 'anonymous'; + } + return $login; } /** @@ -300,7 +213,7 @@ class Piwik * @return string * @api */ - static public function getCurrentUserTokenAuth() + public static function getCurrentUserTokenAuth() { return Access::getInstance()->getTokenAuth(); } @@ -313,7 +226,7 @@ class Piwik * @return bool * @api */ - static public function hasUserSuperUserAccessOrIsTheUser($theUser) + public static function hasUserSuperUserAccessOrIsTheUser($theUser) { try { self::checkUserHasSuperUserAccessOrIsTheUser($theUser); @@ -330,7 +243,7 @@ class Piwik * @throws NoAccessException If the user is neither the Super User nor the user `$theUser`. * @api */ - static public function checkUserHasSuperUserAccessOrIsTheUser($theUser) + public static function checkUserHasSuperUserAccessOrIsTheUser($theUser) { try { if (Piwik::getCurrentUserLogin() !== $theUser) { @@ -349,7 +262,7 @@ class Piwik * @return bool * @api */ - static public function hasTheUserSuperUserAccess($theUser) + public static function hasTheUserSuperUserAccess($theUser) { if (empty($theUser)) { return false; @@ -374,18 +287,18 @@ class Piwik return false; } - /** * Returns true if the current user has Super User access. * * @return bool * @api */ - static public function hasUserSuperUserAccess() + public static function hasUserSuperUserAccess() { try { - self::checkUserHasSuperUserAccess(); - return true; + $hasAccess = Access::getInstance()->hasSuperUserAccess(); + + return $hasAccess; } catch (Exception $e) { return false; } @@ -397,9 +310,10 @@ class Piwik * @return bool * @api */ - static public function isUserIsAnonymous() + public static function isUserIsAnonymous() { - return Piwik::getCurrentUserLogin() == 'anonymous'; + $currentUserLogin = Piwik::getCurrentUserLogin(); + return $currentUserLogin == 'anonymous'; } /** @@ -408,7 +322,7 @@ class Piwik * @throws NoAccessException if the current user is the anonymous user. * @api */ - static public function checkUserIsNotAnonymous() + public static function checkUserIsNotAnonymous() { if (Access::getInstance()->hasSuperUserAccess()) { return; @@ -422,9 +336,12 @@ class Piwik * Helper method user to set the current as superuser. * This should be used with great care as this gives the user all permissions. * + * This method is deprecated, use {@link Access::doAsSuperUser()} instead. + * * @param bool $bool true to set current user as Super User + * @deprecated */ - static public function setUserHasSuperUserAccess($bool = true) + public static function setUserHasSuperUserAccess($bool = true) { Access::getInstance()->setSuperUserAccess($bool); } @@ -435,7 +352,7 @@ class Piwik * @throws Exception if the current user is not the superuser. * @api */ - static public function checkUserHasSuperUserAccess() + public static function checkUserHasSuperUserAccess() { Access::getInstance()->checkUserHasSuperUserAccess(); } @@ -447,7 +364,7 @@ class Piwik * @return bool * @api */ - static public function isUserHasAdminAccess($idSites) + public static function isUserHasAdminAccess($idSites) { try { self::checkUserHasAdminAccess($idSites); @@ -464,7 +381,7 @@ class Piwik * @throws Exception If user doesn't have admin access. * @api */ - static public function checkUserHasAdminAccess($idSites) + public static function checkUserHasAdminAccess($idSites) { Access::getInstance()->checkUserHasAdminAccess($idSites); } @@ -475,14 +392,9 @@ class Piwik * @return bool * @api */ - static public function isUserHasSomeAdminAccess() + public static function isUserHasSomeAdminAccess() { - try { - self::checkUserHasSomeAdminAccess(); - return true; - } catch (Exception $e) { - return false; - } + return Access::getInstance()->isUserHasSomeAdminAccess(); } /** @@ -491,7 +403,7 @@ class Piwik * @throws Exception if user doesn't have admin access to any site. * @api */ - static public function checkUserHasSomeAdminAccess() + public static function checkUserHasSomeAdminAccess() { Access::getInstance()->checkUserHasSomeAdminAccess(); } @@ -503,7 +415,7 @@ class Piwik * @return bool * @api */ - static public function isUserHasViewAccess($idSites) + public static function isUserHasViewAccess($idSites) { try { self::checkUserHasViewAccess($idSites); @@ -520,7 +432,7 @@ class Piwik * @throws Exception if the current user does not have view access to every site in the list. * @api */ - static public function checkUserHasViewAccess($idSites) + public static function checkUserHasViewAccess($idSites) { Access::getInstance()->checkUserHasViewAccess($idSites); } @@ -531,7 +443,7 @@ class Piwik * @return bool * @api */ - static public function isUserHasSomeViewAccess() + public static function isUserHasSomeViewAccess() { try { self::checkUserHasSomeViewAccess(); @@ -547,7 +459,7 @@ class Piwik * @throws Exception if user doesn't have view access to any site. * @api */ - static public function checkUserHasSomeViewAccess() + public static function checkUserHasSomeViewAccess() { Access::getInstance()->checkUserHasSomeViewAccess(); } @@ -562,10 +474,11 @@ class Piwik * in case another Login plugin is being used. * * @return string + * @api */ - static public function getLoginPluginName() + public static function getLoginPluginName() { - return Registry::get('auth')->getName(); + return StaticContainer::get('Piwik\Auth')->getName(); } /** @@ -573,17 +486,17 @@ class Piwik * * @return Plugin */ - static public function getCurrentPlugin() + public static function getCurrentPlugin() { return \Piwik\Plugin\Manager::getInstance()->getLoadedPlugin(Piwik::getModule()); } /** - * Returns the current module read from the URL (eg. 'API', 'UserSettings', etc.) + * Returns the current module read from the URL (eg. 'API', 'DevicesDetection', etc.) * * @return string */ - static public function getModule() + public static function getModule() { return Common::getRequestVar('module', '', 'string'); } @@ -593,7 +506,7 @@ class Piwik * * @return string */ - static public function getAction() + public static function getAction() { return Common::getRequestVar('action', '', 'string'); } @@ -607,7 +520,7 @@ class Piwik * @param array|string $columns * @return array */ - static public function getArrayFromApiParameter($columns) + public static function getArrayFromApiParameter($columns) { if (empty($columns)) { return array(); @@ -628,7 +541,7 @@ class Piwik * @param array $parameters The query parameter values to modify before redirecting. * @api */ - static public function redirectToModule($newModule, $newAction = '', $parameters = array()) + public static function redirectToModule($newModule, $newAction = '', $parameters = array()) { $newUrl = 'index.php' . Url::getCurrentQueryStringWithParametersModified( array('module' => $newModule, 'action' => $newAction) @@ -648,28 +561,30 @@ class Piwik * @return bool * @api */ - static public function isValidEmailString($emailAddress) + public static function isValidEmailString($emailAddress) { - return (preg_match('/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9_.-]+\.[a-zA-Z]{2,7}$/D', $emailAddress) > 0); + /** @var \Zend_Validate_EmailAddress $zendEmailValidator */ + $zendEmailValidator = StaticContainer::get('Zend_Validate_EmailAddress'); + return $zendEmailValidator->isValid($emailAddress); } /** * Returns `true` if the login is valid. - * + * * _Warning: does not check if the login already exists! You must use UsersManager_API->userExists as well._ * * @param string $userLogin * @throws Exception * @return bool */ - static public function checkValidLoginString($userLogin) + public static function checkValidLoginString($userLogin) { if (!SettingsPiwik::isUserCredentialsSanityCheckEnabled() && !empty($userLogin) ) { return; } - $loginMinimumLength = 3; + $loginMinimumLength = 2; $loginMaximumLength = 100; $l = strlen($userLogin); if (!($l >= $loginMinimumLength @@ -687,7 +602,7 @@ class Piwik * @param array $types List of class names that $o is expected to be one of. * @throws Exception if $o is not an instance of the types contained in $types. */ - static public function checkObjectTypeIs($o, $types) + public static function checkObjectTypeIs($o, $types) { foreach ($types as $type) { if ($o instanceof $type) { @@ -709,13 +624,14 @@ class Piwik * @param array $array * @return bool */ - static public function isAssociativeArray($array) + public static function isAssociativeArray($array) { reset($array); if (!is_numeric(key($array)) || key($array) != 0 - ) // first key must be 0 - { + ) { + // first key must be 0 + return true; } @@ -728,7 +644,7 @@ class Piwik if ($next === null) { break; - } else if ($current + 1 != $next) { + } elseif ($current + 1 != $next) { return true; } } @@ -736,6 +652,18 @@ class Piwik return false; } + public static function isMultiDimensionalArray($array) + { + $first = reset($array); + foreach ($array as $first) { + if (is_array($first)) { + // Yes, this is a multi dim array + return true; + } + } + + return false; + } /** * Returns the class name of an object without its namespace. @@ -750,7 +678,6 @@ class Piwik return end($parts); } - /** * Post an event to Piwik's event dispatcher which will execute the event's observers. * @@ -769,7 +696,7 @@ class Piwik /** * Register an observer to an event. - * + * * **_Note: Observers should normally be defined in plugin objects. It is unlikely that you will * need to use this function._** * @@ -800,48 +727,43 @@ class Piwik * @param string $translationId Translation ID, eg, `'General_Date'`. * @param array|string|int $args `sprintf` arguments to be applied to the internationalized * string. + * @param string|null $language Optionally force the language. * @return string The translated string or `$translationId`. * @api */ - public static function translate($translationId, $args = array()) + public static function translate($translationId, $args = array(), $language = null) { - if (!is_array($args)) { - $args = array($args); - } + /** @var Translator $translator */ + $translator = StaticContainer::get('Piwik\Translation\Translator'); - if (strpos($translationId, "_") !== false) { - list($plugin, $key) = explode("_", $translationId, 2); - if (isset($GLOBALS['Piwik_translations'][$plugin]) && isset($GLOBALS['Piwik_translations'][$plugin][$key])) { - $translationId = $GLOBALS['Piwik_translations'][$plugin][$key]; - } - } - if (count($args) == 0) { - return $translationId; - } - return vsprintf($translationId, $args); + return $translator->translate($translationId, $args, $language); } - protected static function getJavascriptTagOptions($idSite, $mergeSubdomains, $mergeAliasUrls) + /** + * Executes a callback with superuser privileges, making sure those privileges are rescinded + * before this method exits. Privileges will be rescinded even if an exception is thrown. + * + * @param callback $function The callback to execute. Should accept no arguments. + * @return mixed The result of `$function`. + * @throws Exception rethrows any exceptions thrown by `$function`. + * @api + */ + public static function doAsSuperUser($function) { + $isSuperUser = self::hasUserSuperUserAccess(); + + self::setUserHasSuperUserAccess(); + try { - $websiteUrls = APISitesManager::getInstance()->getSiteUrlsFromId($idSite); - } catch (\Exception $e) { - return ''; + $result = $function(); + } catch (Exception $ex) { + self::setUserHasSuperUserAccess($isSuperUser); + + throw $ex; } - // We need to parse_url to isolate hosts - $websiteHosts = array(); - foreach ($websiteUrls as $site_url) { - $referrerParsed = parse_url($site_url); - $websiteHosts[] = $referrerParsed['host']; - } - $options = ''; - if ($mergeSubdomains && !empty($websiteHosts)) { - $options .= ' _paq.push(["setCookieDomain", "*.' . $websiteHosts[0] . '"]);' . PHP_EOL; - } - if ($mergeAliasUrls && !empty($websiteHosts)) { - $urls = '["*.' . implode('","*.', $websiteHosts) . '"]'; - $options .= ' _paq.push(["setDomains", ' . $urls . ']);' . PHP_EOL; - } - return $options; + + self::setUserHasSuperUserAccess($isSuperUser); + + return $result; } } diff --git a/www/analytics/core/PiwikPro/Advertising.php b/www/analytics/core/PiwikPro/Advertising.php new file mode 100644 index 00000000..deea4fa9 --- /dev/null +++ b/www/analytics/core/PiwikPro/Advertising.php @@ -0,0 +1,141 @@ +pluginManager = $pluginManager; + $this->config = $config; + } + + /** + * Returns true if it is ok to show some Piwik PRO advertising in the Piwik UI. + * @return bool + */ + public function arePiwikProAdsEnabled() + { + if ($this->pluginManager->isPluginActivated('EnterpriseAdmin') + || $this->pluginManager->isPluginActivated('LoginAdmin') + || $this->pluginManager->isPluginActivated('CloudAdmin') + || $this->pluginManager->isPluginActivated('WhiteLabel')) { + return false; + } + + $showAds = $this->config->General['piwik_pro_ads_enabled']; + + return !empty($showAds); + } + + /** + * Get URL for promoting the Piwik Cloud. + * + * @param string $campaignMedium + * @param string $campaignContent + * @return string + */ + public function getPromoUrlForCloud($campaignMedium, $campaignContent = '') + { + $url = 'https://piwik.pro/cloud/?'; + + $campaign = $this->getCampaignParametersForPromoUrl( + $name = self::CAMPAIGN_NAME_UPGRADE_TO_CLOUD, + $campaignMedium, + $campaignContent + ); + + return $url . $campaign; + } + + /** + * Get URL for promoting Piwik On Premises. + * @param string $campaignMedium + * @param string $campaignContent + * @return string + */ + public function getPromoUrlForOnPremises($campaignMedium, $campaignContent = '') + { + $url = 'https://piwik.pro/c/upgrade/?'; + + $campaign = $this->getCampaignParametersForPromoUrl( + $name = self::CAMPAIGN_NAME_UPGRADE_TO_PRO, + $campaignMedium, + $campaignContent + ); + + return $url . $campaign; + } + + /** + * Appends campaign parameters to the given URL for promoting any Piwik PRO service. + * @param string $url + * @param string $campaignName + * @param string $campaignMedium + * @param string $campaignContent + * @return string + */ + public function addPromoCampaignParametersToUrl($url, $campaignName, $campaignMedium, $campaignContent = '') + { + if (empty($url)) { + return ''; + } + + if (strpos($url, '?') === false) { + $url .= '?'; + } else { + $url .= '&'; + } + + $url .= $this->getCampaignParametersForPromoUrl($campaignName, $campaignMedium, $campaignContent); + + return $url; + } + + /** + * Generates campaign URL parameters that can be used with any promotion link for Piwik PRO. + * + * @param string $campaignName + * @param string $campaignMedium + * @param string $campaignContent Optional + * @return string URL parameters without a leading ? or & + */ + private function getCampaignParametersForPromoUrl($campaignName, $campaignMedium, $campaignContent = '') + { + $campaignName = sprintf('pk_campaign=%s&pk_medium=%s&pk_source=Piwik_App', $campaignName, $campaignMedium); + + if (!empty($campaignContent)) { + $campaignName .= '&pk_content=' . $campaignContent; + } + + return $campaignName; + } +} diff --git a/www/analytics/core/Plugin.php b/www/analytics/core/Plugin.php index 1392fc3b..ff938a65 100644 --- a/www/analytics/core/Plugin.php +++ b/www/analytics/core/Plugin.php @@ -1,6 +1,6 @@ 'getReportMetadata', @@ -65,17 +66,17 @@ require_once PIWIK_INCLUDE_PATH . '/core/Plugin/MetadataLoader.php'; * ) * ); * } - * + * * public function install() * { * Db::exec("CREATE TABLE " . Common::prefixTable('mytable') . "..."); * } - * + * * public function uninstall() * { * Db::exec("DROP TABLE IF EXISTS " . Common::prefixTable('mytable')); * } - * + * * public function getReportMetadata(&$metadata) * { * // ... @@ -86,7 +87,7 @@ require_once PIWIK_INCLUDE_PATH . '/core/Plugin/MetadataLoader.php'; * // ... * } * } - * + * * @api */ class Plugin @@ -105,6 +106,15 @@ class Plugin */ private $pluginInformation; + /** + * As the cache is used quite often we avoid having to create instances all the time. We reuse it which is not + * perfect but efficient. If the cache is used we need to make sure to call setId() before usage as there + * is maybe a different key set since last usage. + * + * @var \Piwik\Cache\Eager + */ + private $cache; + /** * Constructor. * @@ -121,11 +131,27 @@ class Plugin } $this->pluginName = $pluginName; - $metadataLoader = new MetadataLoader($pluginName); - $this->pluginInformation = $metadataLoader->load(); + $cacheId = 'Plugin' . $pluginName . 'Metadata'; + $cache = Cache::getEagerCache(); - if ($this->hasDefinedPluginInformationInPluginClass() && $metadataLoader->hasPluginJson()) { - throw new \Exception('Plugin ' . $pluginName . ' has defined the method getInformation() and as well as having a plugin.json file. Please delete the getInformation() method from the plugin class. Alternatively, you may delete the plugin directory from plugins/' . $pluginName); + if ($cache->contains($cacheId)) { + $this->pluginInformation = $cache->fetch($cacheId); + } else { + $metadataLoader = new MetadataLoader($pluginName); + $this->pluginInformation = $metadataLoader->load(); + + if ($this->hasDefinedPluginInformationInPluginClass() && $metadataLoader->hasPluginJson()) { + throw new \Exception('Plugin ' . $pluginName . ' has defined the method getInformation() and as well as having a plugin.json file. Please delete the getInformation() method from the plugin class. Alternatively, you may delete the plugin directory from plugins/' . $pluginName); + } + + $cache->save($cacheId, $this->pluginInformation); + } + } + + private function createCacheIfNeeded() + { + if (is_null($this->cache)) { + $this->cache = Cache::getEagerCache(); } } @@ -147,7 +173,7 @@ class Plugin /** * Returns plugin information, including: - * + * * - 'description' => string // 1-2 sentence description of the plugin * - 'author' => string // plugin author * - 'author_homepage' => string // author homepage URL (or email "mailto:youremail@example.org") @@ -166,12 +192,12 @@ class Plugin } /** - * Returns a list of hooks with associated event observers. - * + * Returns a list of events with associated event observers. + * * Derived classes should use this method to associate callbacks with events. * * @return array eg, - * + * * array( * 'API.getReportMetadata' => 'myPluginFunction', * 'Another.event' => array( @@ -183,10 +209,20 @@ class Plugin * 'before' => true // execute before callbacks w/o ordering * ) * ) + * @since 2.15.0 + */ + public function registerEvents() + { + return array(); + } + + /** + * @deprecated since 2.15.0 use {@link registerEvents()} instead. + * @return array */ public function getListHooksRegistered() { - return array(); + return $this->registerEvents(); } /** @@ -201,12 +237,12 @@ class Plugin /** * Installs the plugin. Derived classes should implement this class if the plugin * needs to: - * + * * - create tables * - update existing tables * - etc. - * - * @throws Exception if installation of fails for some reason. + * + * @throws \Exception if installation of fails for some reason. */ public function install() { @@ -216,10 +252,10 @@ class Plugin /** * Uninstalls the plugins. Derived classes should implement this method if the changes * made in {@link install()} need to be undone during uninstallation. - * + * * In most cases, if you have an {@link install()} method, you should provide * an {@link uninstall()} method. - * + * * @throws \Exception if uninstallation of fails for some reason. */ public function uninstall() @@ -276,6 +312,89 @@ class Plugin return $this->pluginName; } + /** + * Tries to find a component such as a Menu or Tasks within this plugin. + * + * @param string $componentName The name of the component you want to look for. In case you request a + * component named 'Menu' it'll look for a file named 'Menu.php' within the + * root of the plugin folder that implements a class named + * Piwik\Plugin\$PluginName\Menu . If such a file exists but does not implement + * this class it'll silently ignored. + * @param string $expectedSubclass If not empty, a check will be performed whether a found file extends the + * given subclass. If the requested file exists but does not extend this class + * a warning will be shown to advice a developer to extend this certain class. + * + * @return \stdClass|null Null if the requested component does not exist or an instance of the found + * component. + */ + public function findComponent($componentName, $expectedSubclass) + { + $this->createCacheIfNeeded(); + + $cacheId = 'Plugin' . $this->pluginName . $componentName . $expectedSubclass; + + $componentFile = sprintf('%s/plugins/%s/%s.php', PIWIK_INCLUDE_PATH, $this->pluginName, $componentName); + + if ($this->cache->contains($cacheId)) { + $classname = $this->cache->fetch($cacheId); + + if (empty($classname)) { + return null; // might by "false" in case has no menu, widget, ... + } + + if (file_exists($componentFile)) { + include_once $componentFile; + } + } else { + $this->cache->save($cacheId, false); // prevent from trying to load over and over again for instance if there is no Menu for a plugin + + if (!file_exists($componentFile)) { + return null; + } + + require_once $componentFile; + + $classname = sprintf('Piwik\\Plugins\\%s\\%s', $this->pluginName, $componentName); + + if (!class_exists($classname)) { + return null; + } + + if (!empty($expectedSubclass) && !is_subclass_of($classname, $expectedSubclass)) { + Log::warning(sprintf('Cannot use component %s for plugin %s, class %s does not extend %s', + $componentName, $this->pluginName, $classname, $expectedSubclass)); + return null; + } + + $this->cache->save($cacheId, $classname); + } + + return StaticContainer::get($classname); + } + + public function findMultipleComponents($directoryWithinPlugin, $expectedSubclass) + { + $this->createCacheIfNeeded(); + + $cacheId = 'Plugin' . $this->pluginName . $directoryWithinPlugin . $expectedSubclass; + + if ($this->cache->contains($cacheId)) { + $components = $this->cache->fetch($cacheId); + + if ($this->includeComponents($components)) { + return $components; + } else { + // problem including one cached file, refresh cache + } + } + + $components = $this->doFindMultipleComponents($directoryWithinPlugin, $expectedSubclass); + + $this->cache->save($cacheId, $components); + + return $components; + } + /** * Detect whether there are any missing dependencies. * @@ -315,12 +434,97 @@ class Plugin { foreach ($backtrace as $tracepoint) { // try and discern the plugin name - if (isset($tracepoint['class']) - && preg_match("/Piwik\\\\Plugins\\\\([a-zA-Z_0-9]+)\\\\/", $tracepoint['class'], $matches) - ) { - return $matches[1]; + if (isset($tracepoint['class'])) { + $className = self::getPluginNameFromNamespace($tracepoint['class']); + if ($className) { + return $className; + } } } return false; } + + /** + * Extracts the plugin name from a namespace name or a fully qualified class name. Returns `false` + * if we can't find one. + * + * @param string $namespaceOrClassName The namespace or class string. + * @return string|false + */ + public static function getPluginNameFromNamespace($namespaceOrClassName) + { + if (preg_match("/Piwik\\\\Plugins\\\\([a-zA-Z_0-9]+)\\\\/", $namespaceOrClassName, $matches)) { + return $matches[1]; + } else { + return false; + } + } + + /** + * Override this method in your plugin class if you want your plugin to be loaded during tracking. + * + * Note: If you define your own dimension or handle a tracker event, your plugin will automatically + * be detected as a tracker plugin. + * + * @return bool + * @internal + */ + public function isTrackerPlugin() + { + return false; + } + + /** + * @param $directoryWithinPlugin + * @param $expectedSubclass + * @return array + */ + private function doFindMultipleComponents($directoryWithinPlugin, $expectedSubclass) + { + $components = array(); + + $baseDir = PIWIK_INCLUDE_PATH . '/plugins/' . $this->pluginName . '/' . $directoryWithinPlugin; + $files = Filesystem::globr($baseDir, '*.php'); + + foreach ($files as $file) { + require_once $file; + + $fileName = str_replace(array($baseDir . '/', '.php'), '', $file); + $klassName = sprintf('Piwik\\Plugins\\%s\\%s\\%s', $this->pluginName, $directoryWithinPlugin, str_replace('/', '\\', $fileName)); + + if (!class_exists($klassName)) { + continue; + } + + if (!empty($expectedSubclass) && !is_subclass_of($klassName, $expectedSubclass)) { + continue; + } + + $klass = new \ReflectionClass($klassName); + + if ($klass->isAbstract()) { + continue; + } + + $components[$file] = $klassName; + } + return $components; + } + + /** + * @param $components + * @return bool true if all files were included, false if any file cannot be read + */ + private function includeComponents($components) + { + foreach ($components as $file => $klass) { + if (!is_readable($file)) { + return false; + } + } + foreach ($components as $file => $klass) { + include_once $file; + } + return true; + } } diff --git a/www/analytics/core/Plugin/API.php b/www/analytics/core/Plugin/API.php index 3a4e26a5..c54e10a8 100644 --- a/www/analytics/core/Plugin/API.php +++ b/www/analytics/core/Plugin/API.php @@ -1,6 +1,6 @@ Link - * + * * @api */ -abstract class API extends Singleton +abstract class API { + private static $instances; + /** + * Returns the singleton instance for the derived class. If the singleton instance + * has not been created, this method will create it. + * + * @return static + */ + public static function getInstance() + { + $class = get_called_class(); + + if (!isset(self::$instances[$class])) { + $container = StaticContainer::getContainer(); + + $refl = new \ReflectionClass($class); + + if (!$refl->getConstructor() || $refl->getConstructor()->isPublic()) { + self::$instances[$class] = $container->get($class); + } else { + /** @var LoggerInterface $logger */ + $logger = $container->get('Psr\Log\LoggerInterface'); + + // BC with API defining a protected constructor + $logger->notice('The API class {class} defines a protected constructor which is deprecated, make the constructor public instead', array('class' => $class)); + self::$instances[$class] = new $class; + } + } + + return self::$instances[$class]; + } + + /** + * Used in tests only + * @ignore + * @deprecated + */ + public static function unsetInstance() + { + $class = get_called_class(); + unset(self::$instances[$class]); + } + + /** + * Sets the singleton instance. For testing purposes. + * @ignore + * @deprecated + */ + public static function setSingletonInstance($instance) + { + $class = get_called_class(); + self::$instances[$class] = $instance; + } } diff --git a/www/analytics/core/Plugin/AggregatedMetric.php b/www/analytics/core/Plugin/AggregatedMetric.php new file mode 100644 index 00000000..6324390e --- /dev/null +++ b/www/analytics/core/Plugin/AggregatedMetric.php @@ -0,0 +1,21 @@ +getLogAggregator(); - * + * * $data = $logAggregator->queryVisitsByDimension(...); - * + * * $dataTable = new DataTable(); * $dataTable->addRowsFromSimpleArray($data); - * + * * $archiveProcessor = $this->getProcessor(); * $archiveProcessor->insertBlobRecords('MyPlugin_myReport', $dataTable->getSerialized(500)); * } - * + * * public function aggregateMultipleReports() * { * $archiveProcessor = $this->getProcessor(); * $archiveProcessor->aggregateDataTableRecords('MyPlugin_myReport', 500); * } * } - * + * * @api */ abstract class Archiver @@ -61,7 +61,7 @@ abstract class Archiver /** * Constructor. - * + * * @param ArchiveProcessor $processor The ArchiveProcessor instance to use when persisting archive * data. */ @@ -73,12 +73,12 @@ abstract class Archiver /** * Archives data for a day period. - * + * * Implementations of this method should do more computation intensive activities such * as aggregating data across log tables. Since this method only deals w/ data logged for a day, * aggregating individual log table rows isn't a problem. Doing this for any larger period, * however, would cause performance degradation. - * + * * Aggregate log table rows using a {@link Piwik\DataAccess\LogAggregator} instance. Get a * {@link Piwik\DataAccess\LogAggregator} instance using the {@link getLogAggregator()} method. */ @@ -86,11 +86,11 @@ abstract class Archiver /** * Archives data for a non-day period. - * + * * Implementations of this method should only aggregate existing reports of subperiods of the * current period. For example, it is more efficient to aggregate reports for each day of a * week than to aggregate each log entry of the week. - * + * * Use {@link Piwik\ArchiveProcessor::aggregateNumericMetrics()} and {@link Piwik\ArchiveProcessor::aggregateDataTableRecords()} * to aggregate archived reports. Get the {@link Piwik\ArchiveProcessor} instance using the {@link getProcessor()} * method. @@ -100,7 +100,7 @@ abstract class Archiver /** * Returns a {@link Piwik\ArchiveProcessor} instance that can be used to insert archive data for * the period, segment and site we are archiving data for. - * + * * @return \Piwik\ArchiveProcessor * @api */ @@ -112,7 +112,7 @@ abstract class Archiver /** * Returns a {@link Piwik\DataAccess\LogAggregator} instance that can be used to aggregate log table rows * for this period, segment and site. - * + * * @return \Piwik\DataAccess\LogAggregator * @api */ diff --git a/www/analytics/core/Plugin/ComponentFactory.php b/www/analytics/core/Plugin/ComponentFactory.php new file mode 100644 index 00000000..c0b3c7bf --- /dev/null +++ b/www/analytics/core/Plugin/ComponentFactory.php @@ -0,0 +1,131 @@ +findMultipleComponents($subnamespace, $componentTypeClass); + foreach ($components as $class) { + if ($class == $desiredComponentClass) { + return new $class(); + } + } + + Log::debug("ComponentFactory::%s: Could not find requested component (args = ['%s', '%s', '%s']).", + __FUNCTION__, $pluginName, $componentClassSimpleName, $componentTypeClass); + + return null; + } + + /** + * Finds a component instance that satisfies a given predicate. + * + * @param string $componentTypeClass The fully qualified class name of the component type, eg, + * `"Piwik\Plugin\Report"`. + * @param string $pluginName|false The name of the plugin the component is expected to belong to, + * eg, `'DevicesDetection'`. + * @param callback $predicate + * @return mixed The component that satisfies $predicate or null if not found. + */ + public static function getComponentIf($componentTypeClass, $pluginName, $predicate) + { + $pluginManager = PluginManager::getInstance(); + + // get components to search through + $subnamespace = $componentTypeClass::COMPONENT_SUBNAMESPACE; + if (empty($pluginName)) { + $components = $pluginManager->findMultipleComponents($subnamespace, $componentTypeClass); + } else { + $plugin = self::getActivatedPlugin(__FUNCTION__, $pluginName); + if (empty($plugin)) { + return null; + } + + $components = $plugin->findMultipleComponents($subnamespace, $componentTypeClass); + } + + // find component that satisfieds predicate + foreach ($components as $class) { + $component = new $class(); + if ($predicate($component)) { + return $component; + } + } + + Log::debug("ComponentFactory::%s: Could not find component that satisfies predicate (args = ['%s', '%s', '%s']).", + __FUNCTION__, $componentTypeClass, $pluginName, get_class($predicate)); + + return null; + } + + /** + * @param string $function + * @param string $pluginName + * @return null|\Piwik\Plugin + */ + private static function getActivatedPlugin($function, $pluginName) + { + $pluginManager = PluginManager::getInstance(); + try { + if (!$pluginManager->isPluginActivated($pluginName)) { + Log::debug("ComponentFactory::%s: component for deactivated plugin ('%s') requested.", + $function, $pluginName); + + return null; + } + + return $pluginManager->getLoadedPlugin($pluginName); + } catch (Exception $e) { + Log::debug($e); + + return null; + } + } +} diff --git a/www/analytics/core/Plugin/ConsoleCommand.php b/www/analytics/core/Plugin/ConsoleCommand.php index ede6952f..29e2fc65 100644 --- a/www/analytics/core/Plugin/ConsoleCommand.php +++ b/www/analytics/core/Plugin/ConsoleCommand.php @@ -1,6 +1,6 @@ render(); * } * } - * + * * **Linking to a controller action** * * Link - * + * */ abstract class Controller { /** * The plugin name, eg. `'Referrers'`. - * + * * @var string * @api */ @@ -93,7 +97,7 @@ abstract class Controller /** * The value of the **idSite** query parameter. - * + * * @var int * @api */ @@ -101,7 +105,7 @@ abstract class Controller /** * The Site object created with {@link $idSite}. - * + * * @var Site * @api */ @@ -109,7 +113,7 @@ abstract class Controller /** * Constructor. - * + * * @api */ public function __construct() @@ -177,6 +181,50 @@ abstract class Controller $this->strDate = $date->toString(); } + /** + * Returns values that are enabled for the parameter &period= + * @return array eg. array('day', 'week', 'month', 'year', 'range') + */ + protected static function getEnabledPeriodsInUI() + { + $periodValidator = new PeriodValidator(); + return $periodValidator->getPeriodsAllowedForUI(); + } + + /** + * @return array + */ + private static function getEnabledPeriodsNames() + { + $availablePeriods = self::getEnabledPeriodsInUI(); + $periodNames = array( + 'day' => array( + 'singular' => Piwik::translate('Intl_PeriodDay'), + 'plural' => Piwik::translate('Intl_PeriodDays') + ), + 'week' => array( + 'singular' => Piwik::translate('Intl_PeriodWeek'), + 'plural' => Piwik::translate('Intl_PeriodWeeks') + ), + 'month' => array( + 'singular' => Piwik::translate('Intl_PeriodMonth'), + 'plural' => Piwik::translate('Intl_PeriodMonths') + ), + 'year' => array( + 'singular' => Piwik::translate('Intl_PeriodYear'), + 'plural' => Piwik::translate('Intl_PeriodYears') + ), + // Note: plural is not used for date range + 'range' => array( + 'singular' => Piwik::translate('General_DateRangeInPeriodList'), + 'plural' => Piwik::translate('General_DateRangeInPeriodList') + ), + ); + + $periodNames = array_intersect_key($periodNames, array_fill_keys($availablePeriods, true)); + return $periodNames; + } + /** * Returns the name of the default method that will be called * when visiting: index.php?module=PluginName without the action parameter. @@ -200,10 +248,60 @@ abstract class Controller return $view->render(); } + /** + * Assigns the given variables to the template and renders it. + * + * Example: + * + * public function myControllerAction () { + * return $this->renderTemplate('index', array( + * 'answerToLife' => '42' + * )); + * } + * + * This will render the 'index.twig' file within the plugin templates folder and assign the view variable + * `answerToLife` to `42`. + * + * @param string $template The name of the template file. If only a name is given it will automatically use + * the template within the plugin folder. For instance 'myTemplate' will result in + * '@$pluginName/myTemplate.twig'. Alternatively you can include the full path: + * '@anyOtherFolder/otherTemplate'. The trailing '.twig' is not needed. + * @param array $variables For instance array('myViewVar' => 'myValue'). In template you can use {{ myViewVar }} + * @return string + * @since 2.5.0 + * @api + */ + protected function renderTemplate($template, array $variables = array()) + { + if (false === strpos($template, '@') || false === strpos($template, '/')) { + $template = '@' . $this->pluginName . '/' . $template; + } + + $view = new View($template); + + // alternatively we could check whether the templates extends either admin.twig or dashboard.twig and based on + // that call the correct method. This will be needed once we unify Controller and ControllerAdmin see + // https://github.com/piwik/piwik/issues/6151 + if ($this instanceof ControllerAdmin) { + $this->setBasicVariablesView($view); + } elseif (empty($this->site) || empty($this->idSite)) { + $this->setBasicVariablesView($view); + } else { + $this->setGeneralVariablesView($view); + } + + foreach ($variables as $key => $value) { + $view->$key = $value; + } + + return $view->render(); + } + /** * Convenience method that creates and renders a ViewDataTable for a API method. * - * @param string $apiAction The name of the API action (eg, `'getResolution'`). + * @param string|\Piwik\Plugin\Report $apiAction The name of the API action (eg, `'getResolution'`) or + * an instance of an report. * @param bool $controllerAction The name of the Controller action name that is rendering the report. Defaults * to the `$apiAction`. * @param bool $fetch If `true`, the rendered string is returned, if `false` it is `echo`'d. @@ -214,6 +312,21 @@ abstract class Controller */ protected function renderReport($apiAction, $controllerAction = false) { + if (empty($controllerAction) && is_string($apiAction)) { + $report = Report::factory($this->pluginName, $apiAction); + + if (!empty($report)) { + $apiAction = $report; + } + } + + if ($apiAction instanceof Report) { + $this->checkSitePermission(); + $apiAction->checkIsEnabled(); + + return $apiAction->render(); + } + $pluginName = $this->pluginName; /** @var Proxy $apiProxy */ @@ -250,7 +363,7 @@ abstract class Controller protected function getLastUnitGraph($currentModuleName, $currentControllerAction, $apiMethod) { $view = ViewDataTableFactory::build( - 'graphEvolution', $apiMethod, $currentModuleName . '.' . $currentControllerAction, $forceDefault = true); + Evolution::ID, $apiMethod, $currentModuleName . '.' . $currentControllerAction, $forceDefault = true); $view->config->show_goals = false; return $view; } @@ -282,7 +395,7 @@ abstract class Controller $date = Common::getRequestVar('date'); $meta = \Piwik\Plugins\API\API::getInstance()->getReportMetadata($idSite, $period, $date); - $columns = array_merge($columnsToDisplay, $selectableColumns); + $columns = array_merge($columnsToDisplay ? $columnsToDisplay : array(), $selectableColumns); $translations = array_combine($columns, $columns); foreach ($meta as $reportMeta) { if ($reportMeta['action'] == 'get' && !isset($reportMeta['parameters'])) { @@ -296,6 +409,7 @@ abstract class Controller // initialize the graph and load the data $view = $this->getLastUnitGraph($currentModuleName, $currentControllerAction, $apiMethod); + if ($columnsToDisplay !== false) { $view->config->columns_to_display = $columnsToDisplay; } @@ -379,11 +493,11 @@ abstract class Controller /** * Returns a URL to a sparkline image for a report served by the current plugin. - * + * * The result of this URL should be used with the [sparkline()](/api-reference/Piwik/View#twig) twig function. - * + * * The current site ID and period will be used. - * + * * @param string $action Method name of the controller that serves the report. * @param array $customParameters The array of query parameter name/value pairs that * should be set in result URL. @@ -439,9 +553,9 @@ abstract class Controller /** * Assigns variables to {@link Piwik\View} instances that display an entire page. - * + * * The following variables assigned: - * + * * **date** - The value of the **date** query parameter. * **idSite** - The value of the **idSite** query parameter. * **rawDate** - The value of the **date** query parameter. @@ -454,81 +568,105 @@ abstract class Controller * **config_action_url_category_delimiter** - The value of the `[General] action_url_category_delimiter` * INI config option. * **topMenu** - The result of `MenuTop::getInstance()->getMenu()`. - * + * * As well as the variables set by {@link setPeriodVariablesView()}. - * + * * Will exit on error. - * + * * @param View $view * @return void * @api */ protected function setGeneralVariablesView($view) { + $view->idSite = $this->idSite; + $this->checkSitePermission(); + $this->setPeriodVariablesView($view); + + $view->siteName = $this->site->getName(); + $view->siteMainUrl = $this->site->getMainUrl(); + + $siteTimezone = $this->site->getTimezone(); + + $datetimeMinDate = $this->site->getCreationDate()->getDatetime(); + $minDate = Date::factory($datetimeMinDate, $siteTimezone); + $this->setMinDateView($minDate, $view); + + $maxDate = Date::factory('now', $siteTimezone); + $this->setMaxDateView($maxDate, $view); + + $rawDate = Common::getRequestVar('date'); + Period::checkDateFormat($rawDate); + + $periodStr = Common::getRequestVar('period'); + + if ($periodStr != 'range') { + $date = Date::factory($this->strDate); + $validDate = $this->getValidDate($date, $minDate, $maxDate); + $period = Period\Factory::build($periodStr, $validDate); + + if ($date->toString() !== $validDate->toString()) { + // we to not always change date since it could convert a strDate "today" to "YYYY-MM-DD" + // only change $this->strDate if it was not valid before + $this->setDate($validDate); + } + } else { + $period = new Range($periodStr, $rawDate, $siteTimezone); + } + + // Setting current period start & end dates, for pre-setting the calendar when "Date Range" is selected + $dateStart = $period->getDateStart(); + $dateStart = $this->getValidDate($dateStart, $minDate, $maxDate); + + $dateEnd = $period->getDateEnd(); + $dateEnd = $this->getValidDate($dateEnd, $minDate, $maxDate); + + if ($periodStr == 'range') { + // make sure we actually display the correct calendar pretty date + $newRawDate = $dateStart->toString() . ',' . $dateEnd->toString(); + $period = new Range($periodStr, $newRawDate, $siteTimezone); + } + $view->date = $this->strDate; + $view->prettyDate = self::getCalendarPrettyDate($period); + $view->prettyDateLong = $period->getLocalizedLongString(); + $view->rawDate = $rawDate; + $view->startDate = $dateStart; + $view->endDate = $dateEnd; - try { - $view->idSite = $this->idSite; - if (empty($this->site) || empty($this->idSite)) { - throw new Exception("The requested website idSite is not found in the request, or is invalid. - Please check that you are logged in Piwik and have permission to access the specified website."); - } - $this->setPeriodVariablesView($view); + $language = LanguagesManager::getLanguageForSession(); + $view->language = !empty($language) ? $language : LanguagesManager::getLanguageCodeForCurrentUser(); - $rawDate = Common::getRequestVar('date'); - $periodStr = Common::getRequestVar('period'); - if ($periodStr != 'range') { - $date = Date::factory($this->strDate); - $period = Period::factory($periodStr, $date); - } else { - $period = new Range($periodStr, $rawDate, $this->site->getTimezone()); - } - $view->rawDate = $rawDate; - $view->prettyDate = self::getCalendarPrettyDate($period); + $this->setBasicVariablesView($view); - $view->siteName = $this->site->getName(); - $view->siteMainUrl = $this->site->getMainUrl(); + $view->topMenu = MenuTop::getInstance()->getMenu(); + $view->userMenu = MenuUser::getInstance()->getMenu(); - $datetimeMinDate = $this->site->getCreationDate()->getDatetime(); - $minDate = Date::factory($datetimeMinDate, $this->site->getTimezone()); - $this->setMinDateView($minDate, $view); - - $maxDate = Date::factory('now', $this->site->getTimezone()); - $this->setMaxDateView($maxDate, $view); - - // Setting current period start & end dates, for pre-setting the calendar when "Date Range" is selected - $dateStart = $period->getDateStart(); - if ($dateStart->isEarlier($minDate)) { - $dateStart = $minDate; - } - $dateEnd = $period->getDateEnd(); - if ($dateEnd->isLater($maxDate)) { - $dateEnd = $maxDate; - } - - $view->startDate = $dateStart; - $view->endDate = $dateEnd; - - $language = LanguagesManager::getLanguageForSession(); - $view->language = !empty($language) ? $language : LanguagesManager::getLanguageCodeForCurrentUser(); - - $this->setBasicVariablesView($view); - - $view->topMenu = MenuTop::getInstance()->getMenu(); + $notifications = $view->notifications; + if (empty($notifications)) { $view->notifications = NotificationManager::getAllNotificationsToDisplay(); NotificationManager::cancelAllNonPersistent(); - } catch (Exception $e) { - Piwik_ExitWithMessage($e->getMessage(), $e->getTraceAsString()); } } + private function getValidDate(Date $date, Date $minDate, Date $maxDate) + { + if ($date->isEarlier($minDate)) { + $date = $minDate; + } + + if ($date->isLater($maxDate)) { + $date = $maxDate; + } + + return $date; + } + /** * Assigns a set of generally useful variables to a {@link Piwik\View} instance. - * + * * The following variables assigned: - * - * **debugTrackVisitsInsidePiwikUI** - The value of the `[Debug] track_visits_inside_piwik_ui` - * INI config option. + * * **isSuperUser** - True if the current user is the Super User, false if otherwise. * **hasSomeAdminAccess** - True if the current user has admin access to at least one site, * false if otherwise. @@ -539,7 +677,7 @@ abstract class Controller * **hasSVGLogo** - True if there is a SVG logo, false if otherwise. * **enableFrames** - The value of the `[General] enable_framed_pages` INI config option. If * true, {@link Piwik\View::setXFrameOptions()} is called on the view. - * + * * Also calls {@link setHostValidationVariablesView()}. * * @param View $view @@ -548,15 +686,17 @@ abstract class Controller protected function setBasicVariablesView($view) { $view->clientSideConfig = PiwikConfig::getInstance()->getClientSideOptions(); - $view->debugTrackVisitsInsidePiwikUI = PiwikConfig::getInstance()->Debug['track_visits_inside_piwik_ui']; $view->isSuperUser = Access::getInstance()->hasSuperUserAccess(); $view->hasSomeAdminAccess = Piwik::isUserHasSomeAdminAccess(); $view->hasSomeViewAccess = Piwik::isUserHasSomeViewAccess(); $view->isUserIsAnonymous = Piwik::isUserIsAnonymous(); $view->hasSuperUserAccess = Piwik::hasUserSuperUserAccess(); - $customLogo = new CustomLogo(); - $view->isCustomLogo = $customLogo->isEnabled(); + if (!Piwik::isUserIsAnonymous()) { + $view->emailSuperUser = implode(',', Piwik::getAllSuperUserAccessEmailAddresses()); + } + + $this->addCustomLogoInfo($view); $view->logoHeader = \Piwik\Plugins\API\API::getInstance()->getHeaderLogoUrl(); $view->logoLarge = \Piwik\Plugins\API\API::getInstance()->getLogoUrl(); @@ -574,6 +714,13 @@ abstract class Controller self::setHostValidationVariablesView($view); } + protected function addCustomLogoInfo($view) + { + $customLogo = new CustomLogo(); + $view->isCustomLogo = $customLogo->isEnabled(); + $view->customFavicon = $customLogo->getPathUserFavicon(); + } + /** * Checks if the current host is valid and sets variables on the given view, including: * @@ -631,7 +778,7 @@ abstract class Controller $validHost, '' )); - } else if (Piwik::isUserIsAnonymous()) { + } elseif (Piwik::isUserIsAnonymous()) { $view->invalidHostMessage = $warningStart . ' ' . Piwik::translate('CoreHome_InjectedHostNonSuperUserWarning', array( "
", @@ -660,7 +807,7 @@ abstract class Controller /** * Sets general period variables on a view, including: - * + * * - **displayUniqueVisitors** - Whether unique visitors should be displayed for the current * period. * - **period** - The value of the **period** query parameter. @@ -677,33 +824,27 @@ abstract class Controller return; } + $periodValidator = new PeriodValidator(); + $currentPeriod = Common::getRequestVar('period'); $view->displayUniqueVisitors = SettingsPiwik::isUniqueVisitorsEnabled($currentPeriod); - $availablePeriods = array('day', 'week', 'month', 'year', 'range'); - if (!in_array($currentPeriod, $availablePeriods)) { - throw new Exception("Period must be one of: " . implode(",", $availablePeriods)); + $availablePeriods = $periodValidator->getPeriodsAllowedForUI(); + + if (! $periodValidator->isPeriodAllowedForUI($currentPeriod)) { + throw new Exception("Period must be one of: " . implode(", ", $availablePeriods)); } - $periodNames = array( - 'day' => array('singular' => Piwik::translate('CoreHome_PeriodDay'), 'plural' => Piwik::translate('CoreHome_PeriodDays')), - 'week' => array('singular' => Piwik::translate('CoreHome_PeriodWeek'), 'plural' => Piwik::translate('CoreHome_PeriodWeeks')), - 'month' => array('singular' => Piwik::translate('CoreHome_PeriodMonth'), 'plural' => Piwik::translate('CoreHome_PeriodMonths')), - 'year' => array('singular' => Piwik::translate('CoreHome_PeriodYear'), 'plural' => Piwik::translate('CoreHome_PeriodYears')), - // Note: plural is not used for date range - 'range' => array('singular' => Piwik::translate('General_DateRangeInPeriodList'), 'plural' => Piwik::translate('General_DateRangeInPeriodList')), - ); $found = array_search($currentPeriod, $availablePeriods); - if ($found !== false) { - unset($availablePeriods[$found]); - } + unset($availablePeriods[$found]); + $view->period = $currentPeriod; $view->otherPeriods = $availablePeriods; - $view->periodsNames = $periodNames; + $view->periodsNames = self::getEnabledPeriodsNames(); } /** * Helper method used to redirect the current HTTP request to another module/action. - * + * * This function will exit immediately after executing. * * @param string $moduleToRedirect The plugin to redirect to, eg. `"MultiSites"`. @@ -717,132 +858,46 @@ abstract class Controller public function redirectToIndex($moduleToRedirect, $actionToRedirect, $websiteId = null, $defaultPeriod = null, $defaultDate = null, $parameters = array()) { - if (empty($websiteId)) { - $websiteId = $this->getDefaultWebsiteId(); - } - if (empty($defaultDate)) { - $defaultDate = $this->getDefaultDate(); - } - if (empty($defaultPeriod)) { - $defaultPeriod = $this->getDefaultPeriod(); - } - $parametersString = ''; - if (!empty($parameters)) { - $parametersString = '&' . Url::getQueryStringFromParameters($parameters); - } - - if ($websiteId) { - $url = "Location: index.php?module=" . $moduleToRedirect - . "&action=" . $actionToRedirect - . "&idSite=" . $websiteId - . "&period=" . $defaultPeriod - . "&date=" . $defaultDate - . $parametersString; - header($url); - exit; + try { + $this->doRedirectToUrl($moduleToRedirect, $actionToRedirect, $websiteId, $defaultPeriod, $defaultDate, $parameters); + } catch (Exception $e) { + // no website ID to default to, so could not redirect } if (Piwik::hasUserSuperUserAccess()) { - Piwik_ExitWithMessage("Error: no website was found in this Piwik installation. -
Check the table '" . Common::prefixTable('site') . "' in your database, it should contain your Piwik websites.", false, true); + $siteTableName = Common::prefixTable('site'); + $message = "Error: no website was found in this Piwik installation. +
Check the table '$siteTableName' in your database, it should contain your Piwik websites."; + + $ex = new NoWebsiteFoundException($message); + $ex->setIsHtmlMessage(); + + throw $ex; } - $currentLogin = Piwik::getCurrentUserLogin(); - if (!empty($currentLogin) - && $currentLogin != 'anonymous' - ) { + if (!Piwik::isUserIsAnonymous()) { + $currentLogin = Piwik::getCurrentUserLogin(); $emails = implode(',', Piwik::getAllSuperUserAccessEmailAddresses()); - $errorMessage = sprintf(Piwik::translate('CoreHome_NoPrivilegesAskPiwikAdmin'), $currentLogin, "
", ""); - $errorMessage .= "

   getName() . "&action=logout'>› " . Piwik::translate('General_Logout') . "
"; - Piwik_ExitWithMessage($errorMessage, false, true); + $errorMessage = sprintf(Piwik::translate('CoreHome_NoPrivilegesAskPiwikAdmin'), $currentLogin, "
", ""); + $errorMessage .= "

   › " . Piwik::translate('General_Logout') . "
"; + + $ex = new NoPrivilegesException($errorMessage); + $ex->setIsHtmlMessage(); + + throw $ex; } echo FrontController::getInstance()->dispatch(Piwik::getLoginPluginName(), false); exit; } - /** - * Returns default site ID that Piwik should load. - * - * _Note: This value is a Piwik setting set by each user._ - * - * @return bool|int - * @api - */ - protected function getDefaultWebsiteId() - { - $defaultWebsiteId = false; - - // User preference: default website ID to load - $defaultReport = APIUsersManager::getInstance()->getUserPreference(Piwik::getCurrentUserLogin(), APIUsersManager::PREFERENCE_DEFAULT_REPORT); - if (is_numeric($defaultReport)) { - $defaultWebsiteId = $defaultReport; - } - - if ($defaultWebsiteId && Piwik::isUserHasViewAccess($defaultWebsiteId)) { - return $defaultWebsiteId; - } - - $sitesId = APISitesManager::getInstance()->getSitesIdWithAtLeastViewAccess(); - if (!empty($sitesId)) { - return $sitesId[0]; - } - return false; - } - - /** - * Returns default date for Piwik reports. - * - * _Note: This value is a Piwik setting set by each user._ - * - * @return string `'today'`, `'2010-01-01'`, etc. - * @api - */ - protected function getDefaultDate() - { - // NOTE: a change in this function might mean a change in plugins/UsersManager/javascripts/usersSettings.js as well - $userSettingsDate = APIUsersManager::getInstance()->getUserPreference(Piwik::getCurrentUserLogin(), APIUsersManager::PREFERENCE_DEFAULT_REPORT_DATE); - if ($userSettingsDate == 'yesterday') { - return $userSettingsDate; - } - // if last7, last30, etc. - if (strpos($userSettingsDate, 'last') === 0 - || strpos($userSettingsDate, 'previous') === 0 - ) { - return $userSettingsDate; - } - return 'today'; - } - - /** - * Returns default period type for Piwik reports. - * - * @return string `'day'`, `'week'`, `'month'`, `'year'` or `'range'` - * @api - */ - protected function getDefaultPeriod() - { - $userSettingsDate = APIUsersManager::getInstance()->getUserPreference(Piwik::getCurrentUserLogin(), APIUsersManager::PREFERENCE_DEFAULT_REPORT_DATE); - if ($userSettingsDate === false) { - return PiwikConfig::getInstance()->General['default_period']; - } - if (in_array($userSettingsDate, array('today', 'yesterday'))) { - return 'day'; - } - if (strpos($userSettingsDate, 'last') === 0 - || strpos($userSettingsDate, 'previous') === 0 - ) { - return 'range'; - } - return $userSettingsDate; - } /** * Checks that the token_auth in the URL matches the currently logged-in user's token_auth. - * + * * This is a protection against CSRF and should be used in all controller * methods that modify Piwik or any user settings. - * + * * **The token_auth should never appear in the browser's address bar.** * * @throws \Piwik\NoAccessException If the token doesn't match. @@ -850,7 +905,14 @@ abstract class Controller */ protected function checkTokenInUrl() { - if (Common::getRequestVar('token_auth', false) != Piwik::getCurrentUserTokenAuth()) { + $tokenRequest = Common::getRequestVar('token_auth', false); + $tokenUser = Piwik::getCurrentUserTokenAuth(); + + if (empty($tokenRequest) && empty($tokenUser)) { + return; // UI tests + } + + if ($tokenRequest !== $tokenUser) { throw new NoAccessException(Piwik::translate('General_ExceptionInvalidToken')); } } @@ -864,8 +926,9 @@ abstract class Controller */ public static function getCalendarPrettyDate($period) { - if ($period instanceof Month) // show month name when period is for a month - { + if ($period instanceof Month) { + // show month name when period is for a month + return $period->getLocalizedLongString(); } else { return $period->getPrettyString(); @@ -881,7 +944,7 @@ abstract class Controller */ public static function getPrettyDate($date, $period) { - return self::getCalendarPrettyDate(Period::factory($period, Date::factory($date))); + return self::getCalendarPrettyDate(Period\Factory::build($period, Date::factory($date))); } /** @@ -914,7 +977,7 @@ abstract class Controller if ($evolutionPercent < 0) { $class = "negative-evolution"; $img = "arrow_down.png"; - } else if ($evolutionPercent == 0) { + } elseif ($evolutionPercent == 0) { $class = "neutral-evolution"; $img = "stop.png"; } else { @@ -923,6 +986,9 @@ abstract class Controller $titleEvolutionPercent = '+' . $titleEvolutionPercent; } + $currentValue = NumberFormatter::getInstance()->format($currentValue); + $pastValue = NumberFormatter::getInstance()->format($pastValue); + $title = Piwik::translate('General_EvolutionSummaryGeneric', array( Piwik::translate('General_NVisits', $currentValue), $date, @@ -941,4 +1007,38 @@ abstract class Controller return $result; } + + protected function checkSitePermission() + { + if (!empty($this->idSite) && empty($this->site)) { + throw new NoAccessException(Piwik::translate('General_ExceptionPrivilegeAccessWebsite', array("'view'", $this->idSite))); + } elseif (empty($this->site) || empty($this->idSite)) { + throw new Exception("The requested website idSite is not found in the request, or is invalid. + Please check that you are logged in Piwik and have permission to access the specified website."); + } + } + + /** + * @param $moduleToRedirect + * @param $actionToRedirect + * @param $websiteId + * @param $defaultPeriod + * @param $defaultDate + * @param $parameters + * @throws Exception + */ + private function doRedirectToUrl($moduleToRedirect, $actionToRedirect, $websiteId, $defaultPeriod, $defaultDate, $parameters) + { + $menu = new Menu(); + + $parameters = array_merge( + $menu->urlForDefaultUserParams($websiteId, $defaultPeriod, $defaultDate), + $parameters + ); + $queryParams = !empty($parameters) ? '&' . Url::getQueryStringFromParameters($parameters) : ''; + $url = "index.php?module=%s&action=%s"; + $url = sprintf($url, $moduleToRedirect, $actionToRedirect); + $url = $url . $queryParams; + Url::redirectToUrl($url); + } } diff --git a/www/analytics/core/Plugin/ControllerAdmin.php b/www/analytics/core/Plugin/ControllerAdmin.php index 56051341..e503a615 100644 --- a/www/analytics/core/Plugin/ControllerAdmin.php +++ b/www/analytics/core/Plugin/ControllerAdmin.php @@ -1,6 +1,6 @@ Tracker['record_statistics']; @@ -42,6 +44,7 @@ abstract class ControllerAdmin extends Controller private static function notifyAnyInvalidPlugin() { $missingPlugins = \Piwik\Plugin\Manager::getInstance()->getMissingPlugins(); + if (empty($missingPlugins)) { return; } @@ -49,12 +52,15 @@ abstract class ControllerAdmin extends Controller if (!Piwik::hasUserSuperUserAccess()) { return; } + $pluginsLink = Url::getCurrentQueryStringWithParametersModified(array( 'module' => 'CorePluginsAdmin', 'action' => 'plugins' )); + $invalidPluginsWarning = Piwik::translate('CoreAdminHome_InvalidPluginsWarning', array( self::getPiwikVersion(), '' . implode('', $missingPlugins) . '')) + . "
" . Piwik::translate('CoreAdminHome_InvalidPluginsYouCanUninstall', array( '', '' @@ -63,7 +69,7 @@ abstract class ControllerAdmin extends Controller $notification = new Notification($invalidPluginsWarning); $notification->raw = true; $notification->context = Notification::CONTEXT_WARNING; - $notification->title = Piwik::translate('General_Warning') . ':'; + $notification->title = Piwik::translate('General_Warning'); Notification\Manager::notify('ControllerAdmin_InvalidPluginsWarning', $notification); } @@ -81,10 +87,40 @@ abstract class ControllerAdmin extends Controller self::setBasicVariablesAdminView($view); } + private static function notifyIfURLIsNotSecure() + { + $isURLSecure = ProxyHttp::isHttps(); + if ($isURLSecure) { + return; + } + + if (!Piwik::hasUserSuperUserAccess()) { + return; + } + + if(Url::isLocalHost(Url::getCurrentHost())) { + return; + } + + + $message = Piwik::translate('General_CurrentlyUsingUnsecureHttp'); + + $message .= " "; + + $message .= Piwik::translate('General_ReadThisToLearnMore', + array('', '') + ); + + $notification = new Notification($message); + $notification->context = Notification::CONTEXT_WARNING; + $notification->raw = true; + Notification\Manager::notify('ControllerAdmin_HttpIsUsed', $notification); + } + /** * @ignore */ - static public function displayWarningIfConfigFileNotWritable() + public static function displayWarningIfConfigFileNotWritable() { $isConfigFileWritable = PiwikConfig::getInstance()->isFileWritable(); @@ -99,36 +135,69 @@ abstract class ControllerAdmin extends Controller } } - /** - * See http://dev.piwik.org/trac/ticket/4439#comment:8 and https://github.com/eaccelerator/eaccelerator/issues/12 - * - * Eaccelerator does not support closures and is known to be not comptabile with Piwik. Therefore we are disabling - * it automatically. At this point it looks like Eaccelerator is no longer under development and the bug has not - * been fixed within a year. - */ - public static function disableEacceleratorIfEnabled() - { - $isEacceleratorUsed = ini_get('eaccelerator.enable'); - - if (!empty($isEacceleratorUsed)) { - self::$isEacceleratorUsed = true; - - @ini_set('eaccelerator.enable', 0); - } - } private static function notifyIfEAcceleratorIsUsed() { - if (self::$isEacceleratorUsed) { - $message = sprintf("You are using the PHP accelerator & optimizer eAccelerator which is known to be not compatible with Piwik. - We have disabled eAccelerator, which might affect the performance of Piwik. - Read the %srelated ticket%s for more information and how to fix this problem.", - '', ''); + $isEacceleratorUsed = ini_get('eaccelerator.enable'); + if (empty($isEacceleratorUsed)) { + return; + } + $message = sprintf("You are using the PHP accelerator & optimizer eAccelerator which is known to be not compatible with Piwik. + We have disabled eAccelerator, which might affect the performance of Piwik. + Read the %srelated ticket%s for more information and how to fix this problem.", + '', ''); + $notification = new Notification($message); + $notification->context = Notification::CONTEXT_WARNING; + $notification->raw = true; + Notification\Manager::notify('ControllerAdmin_EacceleratorIsUsed', $notification); + } + + private static function notifyWhenPhpVersionIsEOL() + { + $deprecatedMajorPhpVersion = null; + if(self::isPhpVersion53()) { + $deprecatedMajorPhpVersion = '5.3'; + } elseif(self::isPhpVersion54()) { + $deprecatedMajorPhpVersion = '5.4'; + } + + $notifyPhpIsEOL = Piwik::hasUserSuperUserAccess() && $deprecatedMajorPhpVersion; + if (!$notifyPhpIsEOL) { + return; + } + + $nextRequiredMinimumPHP = '5.5'; + + $message = Piwik::translate('General_WarningPiwikWillStopSupportingPHPVersion', array($deprecatedMajorPhpVersion, $nextRequiredMinimumPHP)) + . "\n " + . Piwik::translate('General_WarningPhpVersionXIsTooOld', $deprecatedMajorPhpVersion); + + $notification = new Notification($message); + $notification->title = Piwik::translate('General_Warning'); + $notification->priority = Notification::PRIORITY_LOW; + $notification->context = Notification::CONTEXT_WARNING; + $notification->type = Notification::TYPE_TRANSIENT; + $notification->flags = Notification::FLAG_NO_CLEAR; + NotificationManager::notify('DeprecatedPHPVersionCheck', $notification); + } + + private static function notifyWhenDebugOnDemandIsEnabled($trackerSetting) + { + if (!Development::isEnabled() + && Piwik::hasUserSuperUserAccess() && + TrackerConfig::getConfigValue($trackerSetting)) { + + $message = Piwik::translate('General_WarningDebugOnDemandEnabled'); + $message = sprintf($message, '"' . $trackerSetting . '"', '"[Tracker] ' . $trackerSetting . '"', '"0"', + '"config/config.ini.php"'); $notification = new Notification($message); + $notification->title = Piwik::translate('General_Warning'); + $notification->priority = Notification::PRIORITY_LOW; $notification->context = Notification::CONTEXT_WARNING; - $notification->raw = true; - Notification\Manager::notify('ControllerAdmin_EacceleratorIsUsed', $notification); + $notification->type = Notification::TYPE_TRANSIENT; + $notification->flags = Notification::FLAG_NO_CLEAR; + NotificationManager::notify('Tracker' . $trackerSetting, $notification); } } @@ -140,7 +209,6 @@ abstract class ControllerAdmin extends Controller * - **statisticsNotRecorded** - Set to true if the `[Tracker] record_statistics` INI * config is `0`. If not `0`, this variable will not be defined. * - **topMenu** - The result of `MenuTop::getInstance()->getMenu()`. - * - **currentAdminMenuName** - The currently selected admin menu name. * - **enableFrames** - The value of the `[General] enable_framed_pages` INI config option. If * true, {@link Piwik\View::setXFrameOptions()} is called on the view. * - **isSuperUser** - Whether the current user is a superuser or not. @@ -155,17 +223,20 @@ abstract class ControllerAdmin extends Controller * @param View $view * @api */ - static public function setBasicVariablesAdminView(View $view) + public static function setBasicVariablesAdminView(View $view) { self::notifyWhenTrackingStatisticsDisabled(); self::notifyIfEAcceleratorIsUsed(); + self::notifyIfURLIsNotSecure(); - $view->topMenu = MenuTop::getInstance()->getMenu(); - $view->currentAdminMenuName = MenuAdmin::getInstance()->getCurrentAdminMenuName(); + $view->topMenu = MenuTop::getInstance()->getMenu(); + $view->userMenu = MenuUser::getInstance()->getMenu(); $view->isDataPurgeSettingsEnabled = self::isDataPurgeSettingsEnabled(); - $view->enableFrames = PiwikConfig::getInstance()->General['enable_framed_settings']; - if (!$view->enableFrames) { + $enableFrames = PiwikConfig::getInstance()->General['enable_framed_settings']; + $view->enableFrames = $enableFrames; + + if (!$enableFrames) { $view->setXFrameOptions('sameorigin'); } @@ -175,19 +246,27 @@ abstract class ControllerAdmin extends Controller self::checkPhpVersion($view); + self::notifyWhenPhpVersionIsEOL(); + self::notifyWhenDebugOnDemandIsEnabled('debug'); + self::notifyWhenDebugOnDemandIsEnabled('debug_on_demand'); + $adminMenu = MenuAdmin::getInstance()->getMenu(); $view->adminMenu = $adminMenu; - $view->notifications = NotificationManager::getAllNotificationsToDisplay(); - NotificationManager::cancelAllNonPersistent(); + $notifications = $view->notifications; + + if (empty($notifications)) { + $view->notifications = NotificationManager::getAllNotificationsToDisplay(); + NotificationManager::cancelAllNonPersistent(); + } } - static public function isDataPurgeSettingsEnabled() + public static function isDataPurgeSettingsEnabled() { return (bool) Config::getInstance()->General['enable_delete_old_data_settings_admin']; } - static protected function getPiwikVersion() + protected static function getPiwikVersion() { return "Piwik " . Version::VERSION; } @@ -202,12 +281,13 @@ abstract class ControllerAdmin extends Controller $view->phpIsNewEnough = version_compare($view->phpVersion, '5.3.0', '>='); } - protected function getDefaultWebsiteId() + private static function isPhpVersion53() { - $sitesId = \Piwik\Plugins\SitesManager\API::getInstance()->getSitesIdWithAdminAccess(); - if (!empty($sitesId)) { - return $sitesId[0]; - } - return parent::getDefaultWebsiteId(); + return strpos(PHP_VERSION, '5.3') === 0; + } + + private static function isPhpVersion54() + { + return strpos(PHP_VERSION, '5.4') === 0; } } diff --git a/www/analytics/core/Plugin/Dependency.php b/www/analytics/core/Plugin/Dependency.php index 0448f8a7..bcade2b3 100644 --- a/www/analytics/core/Plugin/Dependency.php +++ b/www/analytics/core/Plugin/Dependency.php @@ -1,6 +1,6 @@ ='; $required = trim($required); @@ -97,7 +97,8 @@ class Dependency if (!empty($plugin)) { return $plugin->getVersion(); } - } catch (\Exception $e) {} + } catch (\Exception $e) { + } } return ''; diff --git a/www/analytics/core/Plugin/Dimension/ActionDimension.php b/www/analytics/core/Plugin/Dimension/ActionDimension.php new file mode 100644 index 00000000..ce8a1eed --- /dev/null +++ b/www/analytics/core/Plugin/Dimension/ActionDimension.php @@ -0,0 +1,254 @@ + array("ADD COLUMN `$this->columnName` $this->columnType", "ADD INDEX ...") + ); + ``` + * @api + */ + public function install() + { + if (empty($this->columnName) || empty($this->columnType)) { + return array(); + } + + return array( + $this->tableName => array("ADD COLUMN `$this->columnName` $this->columnType") + ); + } + + /** + * Updates the action dimension in case the {@link $columnType} has changed. The update is already implemented based + * on the {@link $columnName} and {@link $columnType}. This method is intended not to overwritten by plugin + * developers as it is only supposed to make sure the column has the correct type. Adding additional custom "alter + * table" actions would not really work since they would be executed with every {@link $columnType} change. So + * adding an index here would be executed whenever the columnType changes resulting in an error if the index already + * exists. If an index needs to be added after the first version is released a plugin update class should be + * created since this makes sure it is only executed once. + * + * @return array An array containing the table name as key and an array of MySQL alter table statements that should + * be executed on the given table. Example: + * ``` + array( + 'log_link_visit_action' => array("MODIFY COLUMN `$this->columnName` $this->columnType", "DROP COLUMN ...") + ); + ``` + * @ignore + */ + public function update() + { + if (empty($this->columnName) || empty($this->columnType)) { + return array(); + } + + return array( + $this->tableName => array("MODIFY COLUMN `$this->columnName` $this->columnType") + ); + } + + /** + * Uninstalls the dimension if a {@link $columnName} and {@link columnType} is set. In case you perform any custom + * actions during {@link install()} - for instance adding an index - you should make sure to undo those actions by + * overwriting this method. Make sure to call this parent method to make sure the uninstallation of the column + * will be done. + * @throws Exception + * @api + */ + public function uninstall() + { + if (empty($this->columnName) || empty($this->columnType)) { + return; + } + + try { + $sql = "ALTER TABLE `" . Common::prefixTable($this->tableName) . "` DROP COLUMN `$this->columnName`"; + Db::exec($sql); + } catch (Exception $e) { + if (!Db::get()->isErrNo($e, '1091')) { + throw $e; + } + } + } + + /** + * Get the version of the dimension which is used for update checks. + * @return string + * @ignore + */ + public function getVersion() + { + return $this->columnType; + } + + /** + * If the value you want to save for your dimension is something like a page title or page url, you usually do not + * want to save the raw value over and over again to save bytes in the database. Instead you want to save each value + * once in the log_action table and refer to this value by its ID in the log_link_visit_action table. You can do + * this by returning an action id in "getActionId()" and by returning a value here. If a value should be ignored + * or not persisted just return boolean false. Please note if you return a value here and you implement the event + * "onNewAction" the value will be probably overwritten by the other event. So make sure to implement only one of + * those. + * + * @param Request $request + * @param Action $action + * + * @return false|mixed + * @api + */ + public function onLookupAction(Request $request, Action $action) + { + return false; + } + + /** + * An action id. The value returned by the lookup action will be associated with this id in the log_action table. + * @return int + * @throws Exception in case not implemented + */ + public function getActionId() + { + throw new Exception('You need to overwrite the getActionId method in case you implement the onLookupAction method in class: ' . get_class($this)); + } + + /** + * This event is triggered before a new action is logged to the `log_link_visit_action` table. It overwrites any + * looked up action so it makes usually no sense to implement both methods but it sometimes does. You can assign + * any value to the column or return boolan false in case you do not want to save any value. + * + * @param Request $request + * @param Visitor $visitor + * @param Action $action + * + * @return mixed|false + * @api + */ + public function onNewAction(Request $request, Visitor $visitor, Action $action) + { + return false; + } + + /** + * Adds a new segment. It automatically sets the SQL segment depending on the column name in case none is set + * already. + * @see \Piwik\Columns\Dimension::addSegment() + * @param Segment $segment + * @api + */ + protected function addSegment(Segment $segment) + { + $sqlSegment = $segment->getSqlSegment(); + if (!empty($this->columnName) && empty($sqlSegment)) { + $segment->setSqlSegment($this->tableName . '.' . $this->columnName); + } + + parent::addSegment($segment); + } + + /** + * Get all action dimensions that are defined by all activated plugins. + * @return ActionDimension[] + * @ignore + */ + public static function getAllDimensions() + { + $cacheId = CacheId::pluginAware('ActionDimensions'); + $cache = PiwikCache::getTransientCache(); + + if (!$cache->contains($cacheId)) { + $plugins = PluginManager::getInstance()->getPluginsLoadedAndActivated(); + $instances = array(); + + foreach ($plugins as $plugin) { + foreach (self::getDimensions($plugin) as $instance) { + $instances[] = $instance; + } + } + + $cache->save($cacheId, $instances); + } + + return $cache->fetch($cacheId); + } + + /** + * Get all action dimensions that are defined by the given plugin. + * @param Plugin $plugin + * @return ActionDimension[] + * @ignore + */ + public static function getDimensions(Plugin $plugin) + { + $dimensions = $plugin->findMultipleComponents('Columns', '\\Piwik\\Plugin\\Dimension\\ActionDimension'); + $instances = array(); + + foreach ($dimensions as $dimension) { + $instances[] = new $dimension(); + } + + return $instances; + } +} diff --git a/www/analytics/core/Plugin/Dimension/ConversionDimension.php b/www/analytics/core/Plugin/Dimension/ConversionDimension.php new file mode 100644 index 00000000..ff1bff55 --- /dev/null +++ b/www/analytics/core/Plugin/Dimension/ConversionDimension.php @@ -0,0 +1,247 @@ + array("ADD COLUMN `$this->columnName` $this->columnType", "ADD INDEX ...") + ); + ``` + * @api + */ + public function install() + { + if (empty($this->columnName) || empty($this->columnType)) { + return array(); + } + + return array( + $this->tableName => array("ADD COLUMN `$this->columnName` $this->columnType") + ); + } + + /** + * @see ActionDimension::update() + * @return array + * @ignore + */ + public function update() + { + if (empty($this->columnName) || empty($this->columnType)) { + return array(); + } + + return array( + $this->tableName => array("MODIFY COLUMN `$this->columnName` $this->columnType") + ); + } + + /** + * Uninstalls the dimension if a {@link $columnName} and {@link columnType} is set. In case you perform any custom + * actions during {@link install()} - for instance adding an index - you should make sure to undo those actions by + * overwriting this method. Make sure to call this parent method to make sure the uninstallation of the column + * will be done. + * @throws Exception + * @api + */ + public function uninstall() + { + if (empty($this->columnName) || empty($this->columnType)) { + return; + } + + try { + $sql = "ALTER TABLE `" . Common::prefixTable($this->tableName) . "` DROP COLUMN `$this->columnName`"; + Db::exec($sql); + } catch (Exception $e) { + if (!Db::get()->isErrNo($e, '1091')) { + throw $e; + } + } + } + + /** + * @see ActionDimension::getVersion() + * @return string + * @ignore + */ + public function getVersion() + { + return $this->columnType; + } + + /** + * Adds a new segment. It automatically sets the SQL segment depending on the column name in case none is set + * already. + * + * @see \Piwik\Columns\Dimension::addSegment() + * @param Segment $segment + * @api + */ + protected function addSegment(Segment $segment) + { + $sqlSegment = $segment->getSqlSegment(); + if (!empty($this->columnName) && empty($sqlSegment)) { + $segment->setSqlSegment($this->tableName . '.' . $this->columnName); + } + + parent::addSegment($segment); + } + + /** + * Get all conversion dimensions that are defined by all activated plugins. + * @ignore + */ + public static function getAllDimensions() + { + $cacheId = CacheId::pluginAware('ConversionDimensions'); + $cache = PiwikCache::getTransientCache(); + + if (!$cache->contains($cacheId)) { + $plugins = PluginManager::getInstance()->getPluginsLoadedAndActivated(); + $instances = array(); + + foreach ($plugins as $plugin) { + foreach (self::getDimensions($plugin) as $instance) { + $instances[] = $instance; + } + } + + $cache->save($cacheId, $instances); + } + + return $cache->fetch($cacheId); + } + + /** + * Get all conversion dimensions that are defined by the given plugin. + * @param Plugin $plugin + * @return ConversionDimension[] + * @ignore + */ + public static function getDimensions(Plugin $plugin) + { + $dimensions = $plugin->findMultipleComponents('Columns', '\\Piwik\\Plugin\\Dimension\\ConversionDimension'); + $instances = array(); + + foreach ($dimensions as $dimension) { + $instances[] = new $dimension(); + } + + return $instances; + } + + /** + * This event is triggered when an ecommerce order is converted. Any returned value will be persist in the database. + * Return boolean `false` if you do not want to change the value in some cases. + * + * @param Request $request + * @param Visitor $visitor + * @param Action|null $action + * @param GoalManager $goalManager + * + * @return mixed|false + * @api + */ + public function onEcommerceOrderConversion(Request $request, Visitor $visitor, $action, GoalManager $goalManager) + { + return false; + } + + /** + * This event is triggered when an ecommerce cart update is converted. Any returned value will be persist in the + * database. Return boolean `false` if you do not want to change the value in some cases. + * + * @param Request $request + * @param Visitor $visitor + * @param Action|null $action + * @param GoalManager $goalManager + * + * @return mixed|false + * @api + */ + public function onEcommerceCartUpdateConversion(Request $request, Visitor $visitor, $action, GoalManager $goalManager) + { + return false; + } + + /** + * This event is triggered when an any custom goal is converted. Any returned value will be persist in the + * database. Return boolean `false` if you do not want to change the value in some cases. + * + * @param Request $request + * @param Visitor $visitor + * @param Action|null $action + * @param GoalManager $goalManager + * + * @return mixed|false + * @api + */ + public function onGoalConversion(Request $request, Visitor $visitor, $action, GoalManager $goalManager) + { + return false; + } +} diff --git a/www/analytics/core/Plugin/Dimension/DimensionMetadataProvider.php b/www/analytics/core/Plugin/Dimension/DimensionMetadataProvider.php new file mode 100644 index 00000000..089c60c0 --- /dev/null +++ b/www/analytics/core/Plugin/Dimension/DimensionMetadataProvider.php @@ -0,0 +1,107 @@ +actionReferenceColumnsOverride = $actionReferenceColumnsOverride; + } + + /** + * Returns a list of idaction column names organized by table name. Uses dimension metadata + * to find idaction columns dynamically. + * + * Note: It is not currently possible to use the Piwik platform to add idaction columns to tables + * other than log_link_visit_action (w/o doing something unsupported), so idaction columns in + * other tables are hard coded. + * + * @return array[] + */ + public function getActionReferenceColumnsByTable() + { + $result = array( + 'log_link_visit_action' => array('idaction_url', + 'idaction_url_ref', + 'idaction_name_ref' + ), + + 'log_conversion' => array('idaction_url'), + + 'log_visit' => array('visit_exit_idaction_url', + 'visit_exit_idaction_name', + 'visit_entry_idaction_url', + 'visit_entry_idaction_name'), + + 'log_conversion_item' => array('idaction_sku', + 'idaction_name', + 'idaction_category', + 'idaction_category2', + 'idaction_category3', + 'idaction_category4', + 'idaction_category5') + ); + + $dimensionIdActionColumns = $this->getVisitActionTableActionReferences(); + $result['log_link_visit_action'] = array_unique( + array_merge($result['log_link_visit_action'], $dimensionIdActionColumns)); + + foreach ($this->actionReferenceColumnsOverride as $table => $columns) { + if (empty($result[$table])) { + $result[$table] = $columns; + } else { + $result[$table] = array_unique(array_merge($result[$table], $columns)); + } + } + + return $result; + } + + private function getVisitActionTableActionReferences() + { + $idactionColumns = array(); + foreach (ActionDimension::getAllDimensions() as $actionDimension) { + if ($this->isActionReference($actionDimension)) { + $idactionColumns[] = $actionDimension->getColumnName(); + } + } + return $idactionColumns; + } + + + /** + * Returns `true` if the column for this dimension is a reference to the `log_action` table (ie, an "idaction column"), + * `false` if otherwise. + * + * @return bool + */ + private function isActionReference(ActionDimension $dimension) + { + try { + $dimension->getActionId(); + + return true; + } catch (\Exception $ex) { + return false; + } + } +} diff --git a/www/analytics/core/Plugin/Dimension/VisitDimension.php b/www/analytics/core/Plugin/Dimension/VisitDimension.php new file mode 100644 index 00000000..3f258da4 --- /dev/null +++ b/www/analytics/core/Plugin/Dimension/VisitDimension.php @@ -0,0 +1,396 @@ + array("ADD COLUMN `$this->columnName` $this->columnType", "ADD INDEX ...") + ); + ``` + * @api + */ + public function install() + { + if (empty($this->columnType) || empty($this->columnName)) { + return array(); + } + + $changes = array( + $this->tableName => array("ADD COLUMN `$this->columnName` $this->columnType") + ); + + if ($this->isHandlingLogConversion()) { + $changes['log_conversion'] = array("ADD COLUMN `$this->columnName` $this->columnType"); + } + + return $changes; + } + + /** + * @see ActionDimension::update() + * @param array $conversionColumns An array of currently installed columns in the conversion table. + * @return array + * @ignore + */ + public function update($conversionColumns) + { + if (!$this->columnType) { + return array(); + } + + $changes = array(); + + $changes[$this->tableName] = array("MODIFY COLUMN `$this->columnName` $this->columnType"); + + $handlingConversion = $this->isHandlingLogConversion(); + $hasConversionColumn = array_key_exists($this->columnName, $conversionColumns); + + if ($hasConversionColumn && $handlingConversion) { + $changes['log_conversion'] = array("MODIFY COLUMN `$this->columnName` $this->columnType"); + } elseif (!$hasConversionColumn && $handlingConversion) { + $changes['log_conversion'] = array("ADD COLUMN `$this->columnName` $this->columnType"); + } elseif ($hasConversionColumn && !$handlingConversion) { + $changes['log_conversion'] = array("DROP COLUMN `$this->columnName`"); + } + + return $changes; + } + + /** + * @see ActionDimension::getVersion() + * @return string + * @ignore + */ + public function getVersion() + { + return $this->columnType . $this->isHandlingLogConversion(); + } + + private function isHandlingLogConversion() + { + if (empty($this->columnName) || empty($this->columnType)) { + return false; + } + + return $this->hasImplementedEvent('onAnyGoalConversion'); + } + + /** + * Uninstalls the dimension if a {@link $columnName} and {@link columnType} is set. In case you perform any custom + * actions during {@link install()} - for instance adding an index - you should make sure to undo those actions by + * overwriting this method. Make sure to call this parent method to make sure the uninstallation of the column + * will be done. + * @throws Exception + * @api + */ + public function uninstall() + { + if (empty($this->columnName) || empty($this->columnType)) { + return; + } + + try { + $sql = "ALTER TABLE `" . Common::prefixTable($this->tableName) . "` DROP COLUMN `$this->columnName`"; + Db::exec($sql); + } catch (Exception $e) { + if (!Db::get()->isErrNo($e, '1091')) { + throw $e; + } + } + + try { + if (!$this->isHandlingLogConversion()) { + return; + } + + $sql = "ALTER TABLE `" . Common::prefixTable('log_conversion') . "` DROP COLUMN `$this->columnName`"; + Db::exec($sql); + } catch (Exception $e) { + if (!Db::get()->isErrNo($e, '1091')) { + throw $e; + } + } + } + + /** + * Adds a new segment. It automatically sets the SQL segment depending on the column name in case none is set + * already. + * @see \Piwik\Columns\Dimension::addSegment() + * @param Segment $segment + * @api + */ + protected function addSegment(Segment $segment) + { + $sqlSegment = $segment->getSqlSegment(); + if (!empty($this->columnName) && empty($sqlSegment)) { + $segment->setSqlSegment('log_visit.' . $this->columnName); + } + + parent::addSegment($segment); + } + + /** + * Sometimes you may want to make sure another dimension is executed before your dimension so you can persist + * this dimensions' value depending on the value of other dimensions. You can do this by defining an array of + * dimension names. If you access any value of any other column within your events, you should require them here. + * Otherwise those values may not be available. + * @return array + * @api + */ + public function getRequiredVisitFields() + { + return array(); + } + + /** + * The `onNewVisit` method is triggered when a new visitor is detected. This means you can define an initial + * value for this user here. By returning boolean `false` no value will be saved. Once the user makes another action + * the event "onExistingVisit" is executed. Meaning for each visitor this method is executed once. + * + * @param Request $request + * @param Visitor $visitor + * @param Action|null $action + * @return mixed|false + * @api + */ + public function onNewVisit(Request $request, Visitor $visitor, $action) + { + return false; + } + + /** + * The `onExistingVisit` method is triggered when a visitor was recognized meaning it is not a new visitor. + * You can overwrite any previous value set by the event `onNewVisit` by implemting this event. By returning boolean + * `false` no value will be updated. + * + * @param Request $request + * @param Visitor $visitor + * @param Action|null $action + * @return mixed|false + * @api + */ + public function onExistingVisit(Request $request, Visitor $visitor, $action) + { + return false; + } + + /** + * This event is executed shortly after `onNewVisit` or `onExistingVisit` in case the visitor converted a goal. + * Usually this event is not needed and you can simply remove this method therefore. An example would be for + * instance to persist the last converted action url. Return boolean `false` if you do not want to change the + * current value. + * + * @param Request $request + * @param Visitor $visitor + * @param Action|null $action + * @return mixed|false + * @api + */ + public function onConvertedVisit(Request $request, Visitor $visitor, $action) + { + return false; + } + + /** + * By implementing this event you can persist a value to the `log_conversion` table in case a conversion happens. + * The persisted value will be logged along the conversion and will not be changed afterwards. This allows you to + * generate reports that shows for instance which url was called how often for a specific conversion. Once you + * implement this event and a $columnType is defined a column in the `log_conversion` MySQL table will be + * created automatically. + * + * @param Request $request + * @param Visitor $visitor + * @param Action|null $action + * @return mixed|false + * @api + */ + public function onAnyGoalConversion(Request $request, Visitor $visitor, $action) + { + return false; + } + + /** + * This hook is executed by the tracker when determining if an action is the start of a new visit + * or part of an existing one. Derived classes can use it to force new visits based on dimension + * data. + * + * For example, the Campaign dimension in the Referrers plugin will force a new visit if the + * campaign information for the current action is different from the last. + * + * @param Request $request The current tracker request information. + * @param Visitor $visitor The information for the currently recognized visitor. + * @param Action|null $action The current action information (if any). + * @return bool Return true to force a visit, false if otherwise. + * @api + */ + public function shouldForceNewVisit(Request $request, Visitor $visitor, Action $action = null) + { + return false; + } + + /** + * Get all visit dimensions that are defined by all activated plugins. + * @return VisitDimension[] + */ + public static function getAllDimensions() + { + $cacheId = CacheId::pluginAware('VisitDimensions'); + $cache = PiwikCache::getTransientCache(); + + if (!$cache->contains($cacheId)) { + $plugins = PluginManager::getInstance()->getPluginsLoadedAndActivated(); + $instances = array(); + + foreach ($plugins as $plugin) { + foreach (self::getDimensions($plugin) as $instance) { + $instances[] = $instance; + } + } + + $instances = self::sortDimensions($instances); + + $cache->save($cacheId, $instances); + } + + return $cache->fetch($cacheId); + } + + /** + * @ignore + * @param VisitDimension[] $dimensions + */ + public static function sortDimensions($dimensions) + { + $sorted = array(); + $exists = array(); + + // we first handle all the once without dependency + foreach ($dimensions as $index => $dimension) { + $fields = $dimension->getRequiredVisitFields(); + if (empty($fields)) { + $sorted[] = $dimension; + $exists[] = $dimension->getColumnName(); + unset($dimensions[$index]); + } + } + + // find circular references + // and remove dependencies whose column cannot be resolved because it is not installed / does not exist / is defined by core + $depenencies = array(); + foreach ($dimensions as $dimension) { + $depenencies[$dimension->getColumnName()] = $dimension->getRequiredVisitFields(); + } + + foreach ($depenencies as $column => $fields) { + foreach ($fields as $key => $field) { + if (empty($depenencies[$field]) && !in_array($field, $exists)) { + // we cannot resolve that dependency as it does not exist + unset($depenencies[$column][$key]); + } elseif (!empty($depenencies[$field]) && in_array($column, $depenencies[$field])) { + throw new Exception("Circular reference detected for required field $field in dimension $column"); + } + } + } + + $count = 0; + while (count($dimensions) > 0) { + $count++; + if ($count > 1000) { + foreach ($dimensions as $dimension) { + $sorted[] = $dimension; + } + break; // to prevent an endless loop + } + foreach ($dimensions as $key => $dimension) { + $fields = $depenencies[$dimension->getColumnName()]; + if (count(array_intersect($fields, $exists)) === count($fields)) { + $sorted[] = $dimension; + $exists[] = $dimension->getColumnName(); + unset($dimensions[$key]); + } + } + } + + return $sorted; + } + + /** + * Get all visit dimensions that are defined by the given plugin. + * @param Plugin $plugin + * @return VisitDimension[] + * @ignore + */ + public static function getDimensions(Plugin $plugin) + { + $dimensions = $plugin->findMultipleComponents('Columns', '\\Piwik\\Plugin\\Dimension\\VisitDimension'); + $instances = array(); + + foreach ($dimensions as $dimension) { + $instances[] = new $dimension(); + } + + return $instances; + } +} diff --git a/www/analytics/core/Plugin/Manager.php b/www/analytics/core/Plugin/Manager.php index b8f135dc..63a3e0aa 100644 --- a/www/analytics/core/Plugin/Manager.php +++ b/www/analytics/core/Plugin/Manager.php @@ -1,6 +1,6 @@ pluginList = $pluginList; + } /** * Loads plugin that are enabled */ public function loadActivatedPlugins() { - $pluginsToLoad = Config::getInstance()->Plugins['Plugins']; + $pluginsToLoad = $this->getActivatedPluginsFromConfig(); $this->loadPlugins($pluginsToLoad); } @@ -97,11 +109,8 @@ class Manager extends Singleton */ public function loadCorePluginsDuringTracker() { - $pluginsToLoad = Config::getInstance()->Plugins['Plugins']; - $pluginsToLoad = array_diff($pluginsToLoad, Tracker::getPluginsNotToLoad()); - if(defined('PIWIK_TEST_MODE')) { - $pluginsToLoad = array_intersect($pluginsToLoad, $this->getPluginsToLoadDuringTests()); - } + $pluginsToLoad = $this->pluginList->getActivatedPlugins(); + $pluginsToLoad = array_diff($pluginsToLoad, $this->getTrackerPluginsNotToLoad()); $this->loadPlugins($pluginsToLoad); } @@ -110,72 +119,62 @@ class Manager extends Singleton */ public function loadTrackerPlugins() { - $this->unloadPlugins(); - $pluginsTracker = PiwikConfig::getInstance()->Plugins_Tracker['Plugins_Tracker']; + $cacheId = 'PluginsTracker'; + $cache = Cache::getEagerCache(); + + if ($cache->contains($cacheId)) { + $pluginsTracker = $cache->fetch($cacheId); + } else { + $this->unloadPlugins(); + $this->loadActivatedPlugins(); + + $pluginsTracker = array(); + + foreach ($this->loadedPlugins as $pluginName => $plugin) { + if ($this->isTrackerPlugin($plugin)) { + $pluginsTracker[] = $pluginName; + } + } + + if (!empty($pluginsTracker)) { + $cache->save($cacheId, $pluginsTracker); + } + } + if (empty($pluginsTracker)) { + $this->unloadPlugins(); return array(); } - $pluginsTracker = array_diff($pluginsTracker, Tracker::getPluginsNotToLoad()); - if(defined('PIWIK_TEST_MODE')) { - $pluginsTracker = array_intersect($pluginsTracker, $this->getPluginsToLoadDuringTests()); - } + $pluginsTracker = array_diff($pluginsTracker, $this->getTrackerPluginsNotToLoad()); $this->doNotLoadAlwaysActivatedPlugins(); $this->loadPlugins($pluginsTracker); + + // we could simply unload all plugins first before loading plugins but this way it is much faster + // since we won't have to create each plugin again and we won't have to parse each plugin metadata file + // again etc + $this->makeSureOnlyActivatedPluginsAreLoaded(); + return $pluginsTracker; } - public function getPluginsToLoadDuringTests() + /** + * Do not load the specified plugins (used during testing, to disable Provider plugin) + * @param array $plugins + */ + public function setTrackerPluginsNotToLoad($plugins) { - $toLoad = array(); - - $loadStandalonePluginsDuringTests = @Config::getInstance()->DebugTests['enable_load_standalone_plugins_during_tests']; - - foreach($this->readPluginsDirectory() as $plugin) { - $forceDisable = array( - 'ExampleVisualization', // adds an icon - 'LoginHttpAuth', // other Login plugins would conflict - ); - if(in_array($plugin, $forceDisable)) { - continue; - } - - // Load all default plugins - $isPluginBundledWithCore = $this->isPluginBundledWithCore($plugin); - - // Load plugins from submodules - $isPluginOfficiallySupported = $this->isPluginOfficialAndNotBundledWithCore($plugin); - - // Also load plugins which are Git repositories (eg. being developed) - $isPluginHasGitRepository = file_exists( PIWIK_INCLUDE_PATH . '/plugins/' . $plugin . '/.git/config'); - - $loadPlugin = $isPluginBundledWithCore || $isPluginOfficiallySupported; - - if($loadStandalonePluginsDuringTests) { - $loadPlugin = $loadPlugin || $isPluginHasGitRepository; - } else { - $loadPlugin = $loadPlugin && !$isPluginHasGitRepository; - } - - // Do not enable other Themes - $disabledThemes = $this->coreThemesDisabledByDefault; - - // PleineLune is officially supported, yet we don't want to enable another theme in tests (we test for Morpheus) - $disabledThemes[] = "PleineLune"; - - $isThemeDisabled = in_array($plugin, $disabledThemes); - - $loadPlugin = $loadPlugin && !$isThemeDisabled; - if($loadPlugin) { - $toLoad[] = $plugin; - } - } - return $toLoad; + $this->trackerPluginsNotToLoad = $plugins; } - public function getCorePluginsDisabledByDefault() + /** + * Get list of plugins to not load + * + * @return array + */ + public function getTrackerPluginsNotToLoad() { - return array_merge( $this->corePluginsDisabledByDefault, $this->coreThemesDisabledByDefault); + return $this->trackerPluginsNotToLoad; } // If a plugin hooks onto at least an event starting with "Tracker.", we load the plugin during tracker @@ -188,7 +187,7 @@ class Manager extends Singleton public function isPluginOfficialAndNotBundledWithCore($pluginName) { static $gitModules; - if(empty($gitModules)) { + if (empty($gitModules)) { $gitModules = file_get_contents(PIWIK_INCLUDE_PATH . '/.gitmodules'); } // All submodules are officially maintained plugins @@ -199,27 +198,16 @@ class Manager extends Singleton /** * Update Plugins config * - * @param array $plugins Plugins + * @param array $pluginsToLoad Plugins */ private function updatePluginsConfig($pluginsToLoad) { + $pluginsToLoad = $this->pluginList->sortPlugins($pluginsToLoad); $section = PiwikConfig::getInstance()->Plugins; $section['Plugins'] = $pluginsToLoad; PiwikConfig::getInstance()->Plugins = $section; } - /** - * Update Plugins_Tracker config - * - * @param array $plugins Plugins - */ - private function updatePluginsTrackerConfig($plugins) - { - $section = PiwikConfig::getInstance()->Plugins_Tracker; - $section['Plugins_Tracker'] = $plugins; - PiwikConfig::getInstance()->Plugins_Tracker = $section; - } - /** * Update PluginsInstalled config * @@ -232,6 +220,12 @@ class Manager extends Singleton PiwikConfig::getInstance()->PluginsInstalled = $section; } + public function clearPluginsInstalledConfig() + { + $this->updatePluginsInstalledConfig(array()); + PiwikConfig::getInstance()->forceSave(); + } + /** * Returns true if plugin is always activated * @@ -264,7 +258,20 @@ class Manager extends Singleton public function isPluginActivated($name) { return in_array($name, $this->pluginsToLoad) - || $this->isPluginAlwaysActivated($name); + || ($this->doLoadAlwaysActivatedPlugins && $this->isPluginAlwaysActivated($name)); + } + + /** + * Checks whether the given plugin is activated, if not triggers an exception. + * + * @param string $pluginName + * @throws PluginDeactivatedException + */ + public function checkIsPluginActivated($pluginName) + { + if (!$this->isPluginActivated($pluginName)) { + throw new PluginDeactivatedException($pluginName); + } } /** @@ -310,6 +317,8 @@ class Manager extends Singleton */ public function deactivatePlugin($pluginName) { + $this->clearCache($pluginName); + // execute deactivate() to let the plugin do cleanups $this->executePluginDeactivate($pluginName); @@ -317,7 +326,59 @@ class Manager extends Singleton $this->removePluginFromConfig($pluginName); - $this->clearCache($pluginName); + /** + * Event triggered after a plugin has been deactivated. + * + * @param string $pluginName The plugin that has been deactivated. + */ + Piwik::postEvent('PluginManager.pluginDeactivated', array($pluginName)); + } + + /** + * Tries to find the given components such as a Menu or Tasks implemented by plugins. + * This method won't cache the found components. If you need to find the same component multiple times you might + * want to cache the result to save a tiny bit of time. + * + * @param string $componentName The name of the component you want to look for. In case you request a + * component named 'Menu' it'll look for a file named 'Menu.php' within the + * root of all plugin folders that implement a class named + * Piwik\Plugin\$PluginName\Menu. + * @param string $expectedSubclass If not empty, a check will be performed whether a found file extends the + * given subclass. If the requested file exists but does not extend this class + * a warning will be shown to advice a developer to extend this certain class. + * + * @return \stdClass[] + */ + public function findComponents($componentName, $expectedSubclass) + { + $plugins = $this->getPluginsLoadedAndActivated(); + $components = array(); + + foreach ($plugins as $plugin) { + $component = $plugin->findComponent($componentName, $expectedSubclass); + + if (!empty($component)) { + $components[] = $component; + } + } + + return $components; + } + + public function findMultipleComponents($directoryWithinPlugin, $expectedSubclass) + { + $plugins = $this->getPluginsLoadedAndActivated(); + $found = array(); + + foreach ($plugins as $plugin) { + $components = $plugin->findMultipleComponents($directoryWithinPlugin, $expectedSubclass); + + if (!empty($components)) { + $found = array_merge($found, $components); + } + } + + return $found; } /** @@ -333,7 +394,9 @@ class Manager extends Singleton if ($this->isPluginLoaded($pluginName)) { throw new \Exception("To uninstall the plugin $pluginName, first disable it in Piwik > Settings > Plugins"); } - $this->returnLoadedPluginsInfo(); + $this->loadAllPluginsAndGetTheirInfo(); + + \Piwik\Settings\Manager::cleanupPluginSettings($pluginName); $this->executePluginDeactivate($pluginName); $this->executePluginUninstall($pluginName); @@ -343,9 +406,7 @@ class Manager extends Singleton $this->unloadPluginFromMemory($pluginName); $this->removePluginFromConfig($pluginName); - - Option::delete('version_' . $pluginName); - \Piwik\Settings\Manager::cleanupPluginSettings($pluginName); + $this->removeInstalledVersionFromOptionTable($pluginName); $this->clearCache($pluginName); self::deletePluginFromFilesystem($pluginName); @@ -360,6 +421,7 @@ class Manager extends Singleton */ private function clearCache($pluginName) { + $this->resetTransientCache(); Filesystem::deleteAllCacheOnUpdate($pluginName); } @@ -371,19 +433,16 @@ class Manager extends Singleton /** * Install loaded plugins * + * @throws * @return array Error messages of plugin install fails */ public function installLoadedPlugins() { - $messages = array(); + Log::debug("Loaded plugins: " . implode(", ", array_keys($this->getLoadedPlugins()))); + foreach ($this->getLoadedPlugins() as $plugin) { - try { - $this->installPluginIfNecessary($plugin); - } catch (\Exception $e) { - $messages[] = $e->getMessage(); - } + $this->installPluginIfNecessary($plugin); } - return $messages; } /** @@ -394,7 +453,7 @@ class Manager extends Singleton */ public function activatePlugin($pluginName) { - $plugins = PiwikConfig::getInstance()->Plugins['Plugins']; + $plugins = $this->pluginList->getActivatedPlugins(); if (in_array($pluginName, $plugins)) { throw new \Exception("Plugin '$pluginName' already activated."); } @@ -421,20 +480,26 @@ class Manager extends Singleton $this->clearCache($pluginName); + /** + * Event triggered after a plugin has been activated. + * + * @param string $pluginName The plugin that has been activated. + */ + Piwik::postEvent('PluginManager.pluginActivated', array($pluginName)); } protected function isPluginInFilesystem($pluginName) { $existingPlugins = $this->readPluginsDirectory(); $isPluginInFilesystem = array_search($pluginName, $existingPlugins) !== false; - return Filesystem::isValidFilename($pluginName) + return $this->isValidPluginName($pluginName) && $isPluginInFilesystem; } /** * Returns the currently enabled theme. - * - * If no theme is enabled, the **Zeitgeist** plugin is returned (this is the base and default theme). + * + * If no theme is enabled, the **Morpheus** plugin is returned (this is the base and default theme). * * @return Plugin * @api @@ -494,7 +559,7 @@ class Manager extends Singleton * * @return array An array that maps plugin names with arrays of plugin information. Plugin * information consists of the following entries: - * + * * - **activated**: Whether the plugin is activated. * - **alwaysActivated**: Whether the plugin should always be activated, * or not. @@ -505,20 +570,21 @@ class Manager extends Singleton * See {@link Piwik\Plugin::getInformation()}. * @api */ - public function returnLoadedPluginsInfo() + public function loadAllPluginsAndGetTheirInfo() { - $language = Translate::getLanguageToLoad(); + /** @var Translator $translator */ + $translator = StaticContainer::get('Piwik\Translation\Translator'); $plugins = array(); $listPlugins = array_merge( $this->readPluginsDirectory(), - PiwikConfig::getInstance()->Plugins['Plugins'] + $this->pluginList->getActivatedPlugins() ); $listPlugins = array_unique($listPlugins); foreach ($listPlugins as $pluginName) { // Hide plugins that are never going to be used - if($this->isPluginBogus($pluginName)) { + if ($this->isPluginBogus($pluginName)) { continue; } @@ -531,7 +597,7 @@ class Manager extends Singleton 'uninstallable' => true, ); } else { - $this->loadTranslation($pluginName, $language); + $translator->addDirectory(self::getPluginsDirectory() . $pluginName . '/lang'); $this->loadPlugin($pluginName); $info = array( 'activated' => $this->isPluginActivated($pluginName), @@ -542,7 +608,6 @@ class Manager extends Singleton $plugins[$pluginName] = $info; } - $this->loadPluginTranslations(); $loadedPlugins = $this->getLoadedPlugins(); foreach ($loadedPlugins as $oPlugin) { @@ -557,10 +622,10 @@ class Manager extends Singleton ); $plugins[$pluginName] = $info; } + return $plugins; } - protected static function isManifestFileFound($path) { return file_exists($path . "/" . MetadataLoader::PLUGIN_JSON_FILENAME); @@ -574,43 +639,38 @@ class Manager extends Singleton */ public function isPluginBundledWithCore($name) { - // Reading the plugins from the global.ini.php config file - $pluginsBundledWithPiwik = PiwikConfig::getInstance()->getFromGlobalConfig('Plugins'); - $pluginsBundledWithPiwik = $pluginsBundledWithPiwik['Plugins']; - - return (!empty($pluginsBundledWithPiwik) - && in_array($name, $pluginsBundledWithPiwik)) - || in_array($name, $this->getCorePluginsDisabledByDefault()) - || $name == self::DEFAULT_THEME; + return $this->isPluginEnabledByDefault($name) + || in_array($name, $this->pluginList->getCorePluginsDisabledByDefault()) + || $name == self::DEFAULT_THEME; } protected function isPluginThirdPartyAndBogus($pluginName) { - if($this->isPluginBundledWithCore($pluginName)) { + if ($this->isPluginBundledWithCore($pluginName)) { return false; } - if($this->isPluginBogus($pluginName)) { + if ($this->isPluginBogus($pluginName)) { return true; } $path = $this->getPluginsDirectory() . $pluginName; - if(!$this->isManifestFileFound($path)) { + if (!$this->isManifestFileFound($path)) { return true; } return false; } - /** - * Load the specified plugins. + * Load AND activates the specified plugins. It will also overwrite all previously loaded plugins, so it acts + * as a setter. * * @param array $pluginsToLoad Array of plugins to load. */ public function loadPlugins(array $pluginsToLoad) { - $pluginsToLoad = array_unique($pluginsToLoad); - $this->pluginsToLoad = $pluginsToLoad; - $this->reloadPlugins(); + $this->resetTransientCache(); + $this->pluginsToLoad = $this->makePluginsToLoad($pluginsToLoad); + $this->reloadActivatedPlugins(); } /** @@ -629,23 +689,6 @@ class Manager extends Singleton $this->doLoadAlwaysActivatedPlugins = false; } - /** - * Load translations for loaded plugins - * - * @param bool|string $language Optional language code - */ - public function loadPluginTranslations($language = false) - { - if (empty($language)) { - $language = Translate::getLanguageToLoad(); - } - $plugins = $this->getLoadedPlugins(); - - foreach ($plugins as $plugin) { - $this->loadTranslation($plugin, $language); - } - } - /** * Execute postLoad() hook for loaded plugins */ @@ -672,7 +715,7 @@ class Manager extends Singleton * * array( * 'UserCountry' => Plugin $pluginObject, - * 'UserSettings' => Plugin $pluginObject, + * 'UserLanguage' => Plugin $pluginObject, * ); * * @return Plugin[] @@ -706,22 +749,28 @@ class Manager extends Singleton * * array( * 'UserCountry' => Plugin $pluginObject, - * 'UserSettings' => Plugin $pluginObject, + * 'UserLanguage' => Plugin $pluginObject, * ); * * @return Plugin[] */ public function getPluginsLoadedAndActivated() { - $plugins = $this->getLoadedPlugins(); - $enabled = $this->getActivatedPlugins(); + if (is_null($this->pluginsLoadedAndActivated)) { + $enabled = $this->getActivatedPlugins(); - if(empty($enabled)) { - return array(); + if (empty($enabled)) { + return array(); + } + + $plugins = $this->getLoadedPlugins(); + $enabled = array_combine($enabled, $enabled); + $plugins = array_intersect_key($plugins, $enabled); + + $this->pluginsLoadedAndActivated = $plugins; } - $enabled = array_combine($enabled, $enabled); - $plugins = array_intersect_key($plugins, $enabled); - return $plugins; + + return $this->pluginsLoadedAndActivated; } /** @@ -729,7 +778,7 @@ class Manager extends Singleton * * array( * 'UserCountry' - * 'UserSettings' + * 'UserLanguage' * ); * * @return string[] @@ -739,6 +788,13 @@ class Manager extends Singleton return $this->pluginsToLoad; } + public function getActivatedPluginsFromConfig() + { + $plugins = $this->pluginList->getActivatedPlugins(); + + return $this->makePluginsToLoad($plugins); + } + /** * Returns a Plugin object by name. * @@ -758,14 +814,8 @@ class Manager extends Singleton * Load the plugins classes installed. * Register the observers for every plugin. */ - private function reloadPlugins() + private function reloadActivatedPlugins() { - if ($this->doLoadAlwaysActivatedPlugins) { - $this->pluginsToLoad = array_merge($this->pluginsToLoad, $this->pluginToAlwaysActivate); - } - - $this->pluginsToLoad = array_unique($this->pluginsToLoad); - $pluginsToPostPendingEventsTo = array(); foreach ($this->pluginsToLoad as $pluginName) { if (!$this->isPluginLoaded($pluginName) @@ -802,7 +852,6 @@ class Manager extends Singleton return $ignored; } - /** * Returns the name of all plugins found in this Piwik instance * (including those not enabled and themes) @@ -811,18 +860,19 @@ class Manager extends Singleton */ public static function getAllPluginsNames() { + $pluginList = StaticContainer::get('Piwik\Application\Kernel\PluginList'); + $pluginsToLoad = array_merge( - PiwikConfig::getInstance()->Plugins['Plugins'], self::getInstance()->readPluginsDirectory(), - self::getInstance()->getCorePluginsDisabledByDefault() + $pluginList->getCorePluginsDisabledByDefault() ); $pluginsToLoad = array_values(array_unique($pluginsToLoad)); return $pluginsToLoad; } - /** - * Loads the plugin filename and instantiates the plugin with the given name, eg. UserCountry + * Loads the plugin filename and instantiates the plugin with the given name, eg. UserCountry. + * Contrary to loadPlugins() it does not activate the plugin, it only loads it. * * @param string $pluginName * @throws \Exception @@ -839,6 +889,11 @@ class Manager extends Singleton return $newPlugin; } + public function isValidPluginName($pluginName) + { + return (bool) preg_match('/^[a-zA-Z]([a-zA-Z0-9]*)$/D', $pluginName); + } + /** * @param $pluginName * @return Plugin @@ -849,16 +904,15 @@ class Manager extends Singleton $pluginFileName = sprintf("%s/%s.php", $pluginName, $pluginName); $pluginClassName = $pluginName; - if (!Filesystem::isValidFilename($pluginName)) { - throw new \Exception(sprintf("The plugin filename '%s' is not a valid filename", $pluginFileName)); + if (!$this->isValidPluginName($pluginName)) { + throw new \Exception(sprintf("The plugin filename '%s' is not a valid plugin name", $pluginFileName)); } $path = self::getPluginsDirectory() . $pluginFileName; - if (!file_exists($path)) { // Create the smallest minimal Piwik Plugin - // Eg. Used for Zeitgeist default theme which does not have a Zeitgeist.php file + // Eg. Used for Morpheus default theme which does not have a Morpheus.php file return new Plugin($pluginName); } @@ -885,6 +939,11 @@ class Manager extends Singleton return "\\Piwik\\Plugins\\$pluginName\\$className"; } + private function resetTransientCache() + { + $this->pluginsLoadedAndActivated = null; + } + /** * Unload plugin * @@ -893,6 +952,8 @@ class Manager extends Singleton */ public function unloadPlugin($plugin) { + $this->resetTransientCache(); + if (!($plugin instanceof Plugin)) { $oPlugin = $this->loadPlugin($plugin); if ($oPlugin === null) { @@ -911,6 +972,8 @@ class Manager extends Singleton */ public function unloadPlugins() { + $this->resetTransientCache(); + $pluginsLoaded = $this->getLoadedPlugins(); foreach ($pluginsLoaded as $plugin) { $this->unloadPlugin($plugin); @@ -937,63 +1000,15 @@ class Manager extends Singleton * * @param string $pluginName plugin name without prefix (eg. 'UserCountry') * @param Plugin $newPlugin + * @internal */ - private function addLoadedPlugin($pluginName, Plugin $newPlugin) + public function addLoadedPlugin($pluginName, Plugin $newPlugin) { + $this->resetTransientCache(); + $this->loadedPlugins[$pluginName] = $newPlugin; } - /** - * Load translation - * - * @param Plugin $plugin - * @param string $langCode - * @throws \Exception - * @return bool whether the translation was found and loaded - */ - private function loadTranslation($plugin, $langCode) - { - // we are in Tracker mode if Loader is not (yet) loaded - if (!class_exists('Piwik\\Loader', false)) { - return false; - } - - if (is_string($plugin)) { - $pluginName = $plugin; - } else { - $pluginName = $plugin->getPluginName(); - } - - $path = self::getPluginsDirectory() . $pluginName . '/lang/%s.json'; - - $defaultLangPath = sprintf($path, $langCode); - $defaultEnglishLangPath = sprintf($path, 'en'); - - $translationsLoaded = false; - - // merge in english translations as default first - if (file_exists($defaultEnglishLangPath)) { - $translations = $this->getTranslationsFromFile($defaultEnglishLangPath); - $translationsLoaded = true; - if (isset($translations[$pluginName])) { - // only merge translations of plugin - prevents overwritten strings - Translate::mergeTranslationArray(array($pluginName => $translations[$pluginName])); - } - } - - // merge in specific language translations (to overwrite english defaults) - if (file_exists($defaultLangPath)) { - $translations = $this->getTranslationsFromFile($defaultLangPath); - $translationsLoaded = true; - if (isset($translations[$pluginName])) { - // only merge translations of plugin - prevents overwritten strings - Translate::mergeTranslationArray(array($pluginName => $translations[$pluginName])); - } - } - - return $translationsLoaded; - } - /** * Return names of all installed plugins. * @@ -1002,7 +1017,7 @@ class Manager extends Singleton */ public function getInstalledPluginsName() { - $pluginNames = PiwikConfig::getInstance()->PluginsInstalled['PluginsInstalled']; + $pluginNames = Config::getInstance()->PluginsInstalled['PluginsInstalled']; return $pluginNames; } @@ -1016,17 +1031,17 @@ class Manager extends Singleton public function getMissingPlugins() { $missingPlugins = array(); - if (isset(PiwikConfig::getInstance()->Plugins['Plugins'])) { - $plugins = PiwikConfig::getInstance()->Plugins['Plugins']; - foreach ($plugins as $pluginName) { - // if a plugin is listed in the config, but is not loaded, it does not exist in the folder - if (!self::getInstance()->isPluginLoaded($pluginName) - && !$this->isPluginBogus($pluginName) - ) { - $missingPlugins[] = $pluginName; - } + + $plugins = $this->pluginList->getActivatedPlugins(); + foreach ($plugins as $pluginName) { + // if a plugin is listed in the config, but is not loaded, it does not exist in the folder + if (!self::getInstance()->isPluginLoaded($pluginName) + && !$this->isPluginBogus($pluginName) + ) { + $missingPlugins[] = $pluginName; } } + return $missingPlugins; } @@ -1047,29 +1062,37 @@ class Manager extends Singleton $this->executePluginInstall($plugin); $pluginsInstalled[] = $pluginName; $this->updatePluginsInstalledConfig($pluginsInstalled); - Updater::recordComponentSuccessfullyUpdated($plugin->getPluginName(), $plugin->getVersion()); + $updater = new Updater(); + $updater->markComponentSuccessfullyUpdated($plugin->getPluginName(), $plugin->getVersion()); $saveConfig = true; } - if ($this->isTrackerPlugin($plugin)) { - $pluginsTracker = PiwikConfig::getInstance()->Plugins_Tracker['Plugins_Tracker']; - if (is_null($pluginsTracker)) { - $pluginsTracker = array(); - } - if (!in_array($pluginName, $pluginsTracker)) { - $pluginsTracker[] = $pluginName; - $this->updatePluginsTrackerConfig($pluginsTracker); - $saveConfig = true; - } - } - if ($saveConfig) { PiwikConfig::getInstance()->forceSave(); + $this->clearCache($pluginName); } } public function isTrackerPlugin(Plugin $plugin) { + if (!$this->isPluginInstalled($plugin->getPluginName())) { + return false; + } + + if ($plugin->isTrackerPlugin()) { + return true; + } + + $dimensions = VisitDimension::getDimensions($plugin); + if (!empty($dimensions)) { + return true; + } + + $dimensions = ActionDimension::getDimensions($plugin); + if (!empty($dimensions)) { + return true; + } + $hooks = $plugin->getListHooksRegistered(); $hookNames = array_keys($hooks); foreach ($hookNames as $name) { @@ -1080,6 +1103,12 @@ class Manager extends Singleton return true; } } + + $dimensions = ConversionDimension::getDimensions($plugin); + if (!empty($dimensions)) { + return true; + } + return false; } @@ -1095,7 +1124,7 @@ class Manager extends Singleton */ private function removePluginFromPluginsInstalledConfig($pluginName) { - $pluginsInstalled = PiwikConfig::getInstance()->PluginsInstalled['PluginsInstalled']; + $pluginsInstalled = Config::getInstance()->PluginsInstalled['PluginsInstalled']; $key = array_search($pluginName, $pluginsInstalled); if ($key !== false) { unset($pluginsInstalled[$key]); @@ -1109,7 +1138,7 @@ class Manager extends Singleton */ private function removePluginFromPluginsConfig($pluginName) { - $pluginsEnabled = PiwikConfig::getInstance()->Plugins['Plugins']; + $pluginsEnabled = $this->pluginList->getActivatedPlugins(); $key = array_search($pluginName, $pluginsEnabled); if ($key !== false) { unset($pluginsEnabled[$key]); @@ -1117,39 +1146,6 @@ class Manager extends Singleton $this->updatePluginsConfig($pluginsEnabled); } - private function removePluginFromTrackerConfig($pluginName) - { - $pluginsTracker = PiwikConfig::getInstance()->Plugins_Tracker['Plugins_Tracker']; - if (!is_null($pluginsTracker)) { - $key = array_search($pluginName, $pluginsTracker); - if ($key !== false) { - unset($pluginsTracker[$key]); - $this->updatePluginsTrackerConfig($pluginsTracker); - } - } - } - - /** - * @param string $pathToTranslationFile - * @throws \Exception - * @return mixed - */ - private function getTranslationsFromFile($pathToTranslationFile) - { - $data = file_get_contents($pathToTranslationFile); - $translations = json_decode($data, true); - - if (is_null($translations) && Common::hasJsonErrorOccurred()) { - $jsonError = Common::getLastJsonError(); - - $message = sprintf('Not able to load translation file %s: %s', $pathToTranslationFile, $jsonError); - - throw new \Exception($message); - } - - return $translations; - } - /** * @param $pluginName * @return bool @@ -1197,6 +1193,8 @@ class Manager extends Singleton */ private function unloadPluginFromMemory($pluginName) { + $this->unloadPlugin($pluginName); + $key = array_search($pluginName, $this->pluginsToLoad); if ($key !== false) { unset($this->pluginsToLoad[$key]); @@ -1209,7 +1207,6 @@ class Manager extends Singleton private function removePluginFromConfig($pluginName) { $this->removePluginFromPluginsConfig($pluginName); - $this->removePluginFromTrackerConfig($pluginName); PiwikConfig::getInstance()->forceSave(); } @@ -1223,6 +1220,72 @@ class Manager extends Singleton $plugin->uninstall(); } catch (\Exception $e) { } + + if (empty($plugin)) { + return; + } + + try { + $visitDimensions = VisitDimension::getAllDimensions(); + + foreach (VisitDimension::getDimensions($plugin) as $dimension) { + $this->uninstallDimension(VisitDimension::INSTALLER_PREFIX, $dimension, $visitDimensions); + } + } catch (\Exception $e) { + } + + try { + $actionDimensions = ActionDimension::getAllDimensions(); + + foreach (ActionDimension::getDimensions($plugin) as $dimension) { + $this->uninstallDimension(ActionDimension::INSTALLER_PREFIX, $dimension, $actionDimensions); + } + } catch (\Exception $e) { + } + + try { + $conversionDimensions = ConversionDimension::getAllDimensions(); + + foreach (ConversionDimension::getDimensions($plugin) as $dimension) { + $this->uninstallDimension(ConversionDimension::INSTALLER_PREFIX, $dimension, $conversionDimensions); + } + } catch (\Exception $e) { + } + } + + /** + * @param VisitDimension|ActionDimension|ConversionDimension $dimension + * @param VisitDimension[]|ActionDimension[]|ConversionDimension[] $allDimensions + * @return bool + */ + private function doesAnotherPluginDefineSameColumnWithDbEntry($dimension, $allDimensions) + { + $module = $dimension->getModule(); + $columnName = $dimension->getColumnName(); + + foreach ($allDimensions as $dim) { + if ($dim->getColumnName() === $columnName && + $dim->hasColumnType() && + $dim->getModule() !== $module) { + return true; + } + } + + return false; + } + + /** + * @param string $prefix column installer prefix + * @param ConversionDimension|VisitDimension|ActionDimension $dimension + * @param VisitDimension[]|ActionDimension[]|ConversionDimension[] $allDimensions + */ + private function uninstallDimension($prefix, Dimension $dimension, $allDimensions) + { + if (!$this->doesAnotherPluginDefineSameColumnWithDbEntry($dimension, $allDimensions)) { + $dimension->uninstall(); + + $this->removeInstalledVersionFromOptionTable($prefix . $dimension->getColumnName()); + } } /** @@ -1234,18 +1297,66 @@ class Manager extends Singleton $pluginsInstalled = $this->getInstalledPluginsName(); return in_array($pluginName, $pluginsInstalled); } -} -/** - */ -class PluginException extends \Exception -{ - function __construct($pluginName, $message) + private function removeInstalledVersionFromOptionTable($name) { - parent::__construct("There was a problem installing the plugin " . $pluginName . ": " . $message . " - If this plugin has already been installed, and if you want to hide this message, you must add the following line under the - [PluginsInstalled] - entry in your config/config.ini.php file: - PluginsInstalled[] = $pluginName"); + $updater = new Updater(); + $updater->markComponentSuccessfullyUninstalled($name); + } + + private function makeSureOnlyActivatedPluginsAreLoaded() + { + foreach ($this->getLoadedPlugins() as $pluginName => $plugin) { + if (!in_array($pluginName, $this->pluginsToLoad)) { + $this->unloadPlugin($plugin); + } + } + } + + /** + * Reading the plugins from the global.ini.php config file + * + * @return array + */ + protected function getPluginsFromGlobalIniConfigFile() + { + return $this->pluginList->getPluginsBundledWithPiwik(); + } + + /** + * @param $name + * @return bool + */ + protected function isPluginEnabledByDefault($name) + { + $pluginsBundledWithPiwik = $this->getPluginsFromGlobalIniConfigFile(); + if (empty($pluginsBundledWithPiwik)) { + return false; + } + return in_array($name, $pluginsBundledWithPiwik); + } + + /** + * @param array $pluginsToLoad + * @return array + */ + private function makePluginsToLoad(array $pluginsToLoad) + { + $pluginsToLoad = array_unique($pluginsToLoad); + if ($this->doLoadAlwaysActivatedPlugins) { + $pluginsToLoad = array_merge($pluginsToLoad, $this->pluginToAlwaysActivate); + } + $pluginsToLoad = array_unique($pluginsToLoad); + $pluginsToLoad = $this->pluginList->sortPlugins($pluginsToLoad); + return $pluginsToLoad; + } + + public function loadPluginTranslations() + { + /** @var Translator $translator */ + $translator = StaticContainer::get('Piwik\Translation\Translator'); + foreach ($this->getAllPluginsNames() as $pluginName) { + $translator->addDirectory(self::getPluginsDirectory() . $pluginName . '/lang'); + } } } diff --git a/www/analytics/core/Plugin/Menu.php b/www/analytics/core/Plugin/Menu.php new file mode 100644 index 00000000..dd1dcaf7 --- /dev/null +++ b/www/analytics/core/Plugin/Menu.php @@ -0,0 +1,271 @@ +addItem('UI Framework', '', $this->urlForDefaultAction(), $orderId = 30); + * // will add a menu item that leads to the default action of the plugin controller when a user clicks on it. + * // The default action is usually the `index` action - meaning the `index()` method the controller - + * // but the default action can be customized within a controller + * ``` + * + * @param array $additionalParams Optional URL parameters that will be appended to the URL + * @return array + * + * @since 2.7.0 + * @api + */ + protected function urlForDefaultAction($additionalParams = array()) + { + $params = (array) $additionalParams; + $params['action'] = ''; + $params['module'] = $this->getModule(); + + return $params; + } + + /** + * Generates a URL for the given action. In your plugin controller you have to create a method with the same name + * as this method will be executed when a user clicks on the menu item. If you want to generate a URL for the + * action of another module, meaning not your plugin, you should use the method {@link urlForModuleAction()}. + * + * @param string $controllerAction The name of the action that should be executed within your controller + * @param array $additionalParams Optional URL parameters that will be appended to the URL + * @return array + * + * @since 2.7.0 + * @api + */ + protected function urlForAction($controllerAction, $additionalParams = array()) + { + $module = $this->getModule(); + $this->checkisValidCallable($module, $controllerAction); + + $params = (array) $additionalParams; + $params['action'] = $controllerAction; + $params['module'] = $module; + + return $params; + } + + /** + * Generates a URL for the given action of the given module. We usually do not recommend to use this method as you + * should make sure the method of that module actually exists. If the plugin owner of that module changes the method + * in a future version your link might no longer work. If you want to link to an action of your controller use the + * method {@link urlForAction()}. Note: We will generate a link only if the given module is installed and activated. + * + * @param string $module The name of the module/plugin the action belongs to. The module name is case sensitive. + * @param string $controllerAction The name of the action that should be executed within your controller + * @param array $additionalParams Optional URL parameters that will be appended to the URL + * @return array|null Returns null if the given module is either not installed or not activated. Returns the array + * of query parameter names and values to the given module action otherwise. + * + * @since 2.7.0 + * // not API for now + */ + protected function urlForModuleAction($module, $controllerAction, $additionalParams = array()) + { + $this->checkisValidCallable($module, $controllerAction); + + $pluginManager = PluginManager::getInstance(); + + if (!$pluginManager->isPluginLoaded($module) || + !$pluginManager->isPluginActivated($module)) { + return null; + } + + $params = (array) $additionalParams; + $params['action'] = $controllerAction; + $params['module'] = $module; + + return $params; + } + + /** + * Generates a URL to the given action of the current module, and it will also append some URL query parameters from the + * User preferences: idSite, period, date. If you do not need the parameters idSite, period and date to be generated + * use {@link urlForAction()} instead. + * + * @param string $controllerAction The name of the action that should be executed within your controller + * @param array $additionalParams Optional URL parameters that will be appended to the URL + * @return array Returns the array of query parameter names and values to the given module action and idSite date and period. + * + */ + protected function urlForActionWithDefaultUserParams($controllerAction, $additionalParams = array()) + { + $module = $this->getModule(); + + return $this->urlForModuleActionWithDefaultUserParams($module, $controllerAction, $additionalParams); + } + + /** + * Generates a URL to the given action of the given module, and it will also append some URL query parameters from the + * User preferences: idSite, period, date. If you do not need the parameters idSite, period and date to be generated + * use {@link urlForModuleAction()} instead. + * + * @param string $module The name of the module/plugin the action belongs to. The module name is case sensitive. + * @param string $controllerAction The name of the action that should be executed within your controller + * @param array $additionalParams Optional URL parameters that will be appended to the URL + * @return array|null Returns the array of query parameter names and values to the given module action and idSite date and period. + * Returns null if the module or action is invalid. + * + */ + protected function urlForModuleActionWithDefaultUserParams($module, $controllerAction, $additionalParams = array()) + { + $urlModuleAction = $this->urlForModuleAction($module, $controllerAction); + + $date = Common::getRequestVar('date', false); + if ($date) { + $urlModuleAction['date'] = $date; + } + $period = Common::getRequestVar('period', false); + if ($period) { + $urlModuleAction['period'] = $period; + } + + // We want the current query parameters to override the user's defaults + return array_merge( + $this->urlForDefaultUserParams(), + $urlModuleAction, + $additionalParams + ); + } + + /** + * Returns the &idSite=X&period=Y&date=Z query string fragment, + * fetched from current logged-in user's preferences. + * + * @param bool $websiteId + * @param bool $defaultPeriod + * @param bool $defaultDate + * @return string eg '&idSite=1&period=week&date=today' + * @throws \Exception in case a website was not specified and a default website id could not be found + */ + public function urlForDefaultUserParams($websiteId = false, $defaultPeriod = false, $defaultDate = false) + { + $userPreferences = new UserPreferences(); + if (empty($websiteId)) { + $websiteId = $userPreferences->getDefaultWebsiteId(); + } + if (empty($websiteId)) { + throw new \Exception("A website ID was not specified and a website to default to could not be found."); + } + if (empty($defaultDate)) { + $defaultDate = $userPreferences->getDefaultDate(); + } + if (empty($defaultPeriod)) { + $defaultPeriod = $userPreferences->getDefaultPeriod(false); + } + return array( + 'idSite' => $websiteId, + 'period' => $defaultPeriod, + 'date' => $defaultDate, + ); + } + + /** + * Configures the reporting menu which should only contain links to reports of a specific site such as + * "Search Engines", "Page Titles" or "Locations & Provider". + */ + public function configureReportingMenu(MenuReporting $menu) + { + } + + /** + * Configures the top menu which is supposed to contain analytics related items such as the + * "All Websites Dashboard". + */ + public function configureTopMenu(MenuTop $menu) + { + } + + /** + * Configures the user menu which is supposed to contain user and help related items such as + * "User settings", "Alerts" or "Email Reports". + */ + public function configureUserMenu(MenuUser $menu) + { + } + + /** + * Configures the admin menu which is supposed to contain only administration related items such as + * "Websites", "Users" or "Plugin settings". + */ + public function configureAdminMenu(MenuAdmin $menu) + { + } + + private function checkisValidCallable($module, $action) + { + if (!Development::isEnabled()) { + return; + } + + $prefix = 'Menu item added in ' . get_class($this) . ' will fail when being selected. '; + + if (!is_string($action)) { + Development::error($prefix . 'No valid action is specified. Make sure the defined action that should be executed is a string.'); + } + + $reportAction = lcfirst(substr($action, 4)); + if (Report::factory($module, $reportAction)) { + return; + } + + $controllerClass = '\\Piwik\\Plugins\\' . $module . '\\Controller'; + + if (!Development::methodExists($controllerClass, $action)) { + Development::error($prefix . 'The defined action "' . $action . '" does not exist in ' . $controllerClass . '". Make sure to define such a method.'); + } + + if (!Development::isCallableMethod($controllerClass, $action)) { + Development::error($prefix . 'The defined action "' . $action . '" is not callable on "' . $controllerClass . '". Make sure the method is public.'); + } + } +} diff --git a/www/analytics/core/Plugin/MetadataLoader.php b/www/analytics/core/Plugin/MetadataLoader.php index 088c6b29..5a6b2617 100644 --- a/www/analytics/core/Plugin/MetadataLoader.php +++ b/www/analytics/core/Plugin/MetadataLoader.php @@ -1,6 +1,6 @@ getDefaultPluginInformation(); + $plugin = $this->loadPluginInfoJson(); + + // use translated plugin description if available + if ($defaults['description'] != Piwik::translate($defaults['description'])) { + unset($plugin['description']); + } + return array_merge( - $this->getDefaultPluginInformation(), - $this->loadPluginInfoJson() + $defaults, + $plugin ); } @@ -67,7 +74,7 @@ class MetadataLoader { $descriptionKey = $this->pluginName . '_PluginDescription'; return array( - 'description' => Piwik::translate($descriptionKey), + 'description' => $descriptionKey, 'homepage' => 'http://piwik.org/', 'authors' => array(array('name' => 'Piwik', 'homepage' => 'http://piwik.org/')), 'license' => 'GPL v3+', @@ -95,12 +102,13 @@ class MetadataLoader return array(); } - $info = Common::json_decode($json, $assoc = true); + $info = json_decode($json, $assoc = true); if (!is_array($info) || empty($info) ) { throw new Exception("Invalid JSON file: $path"); } + return $info; } } diff --git a/www/analytics/core/Plugin/Metric.php b/www/analytics/core/Plugin/Metric.php new file mode 100644 index 00000000..6349ca69 --- /dev/null +++ b/www/analytics/core/Plugin/Metric.php @@ -0,0 +1,189 @@ +getColumn($columnName); + + if ($value === false) { + if (empty($mappingNameToId)) { + $mappingNameToId = Metrics::getMappingFromNameToId(); + } + + if (isset($mappingNameToId[$columnName])) { + return $row->getColumn($mappingNameToId[$columnName]); + } + } + + return $value; + } elseif (!empty($row)) { + if (array_key_exists($columnName, $row)) { + return $row[$columnName]; + } else { + if (empty($mappingNameToId)) { + $mappingNameToId = Metrics::getMappingFromNameToId(); + } + + if (isset($mappingNameToId[$columnName])) { + $columnName = $mappingNameToId[$columnName]; + + if (array_key_exists($columnName, $row)) { + return $row[$columnName]; + } + } + } + } + + return null; + } + + /** + * Helper method that will determine the actual column name for a metric in a + * {@link Piwik\DataTable} and return every column value for this name. + * + * @param DataTable $table + * @param string $columnName + * @param int[]|null $mappingNameToId A custom mapping of metric names to special index values. By + * default {@link Metrics::getMappingFromNameToId()} is used. + * @return array + */ + public static function getMetricValues(DataTable $table, $columnName, $mappingNameToId = null) + { + if (empty($mappingIdToName)) { + $mappingNameToId = Metrics::getMappingFromNameToId(); + } + + $columnName = self::getActualMetricColumn($table, $columnName, $mappingNameToId); + return $table->getColumn($columnName); + } + + /** + * Helper method that determines the actual column for a metric in a {@link Piwik\DataTable}. + * + * @param DataTable $table + * @param string $columnName + * @param int[]|null $mappingNameToId A custom mapping of metric names to special index values. By + * default {@link Metrics::getMappingFromNameToId()} is used. + * @return string + */ + public static function getActualMetricColumn(DataTable $table, $columnName, $mappingNameToId = null) + { + if (empty($mappingIdToName)) { + $mappingNameToId = Metrics::getMappingFromNameToId(); + } + + $firstRow = $table->getFirstRow(); + if (!empty($firstRow) + && $firstRow->getColumn($columnName) === false + ) { + $columnName = $mappingNameToId[$columnName]; + } + return $columnName; + } +} diff --git a/www/analytics/core/Plugin/PluginException.php b/www/analytics/core/Plugin/PluginException.php new file mode 100644 index 00000000..83816ca3 --- /dev/null +++ b/www/analytics/core/Plugin/PluginException.php @@ -0,0 +1,35 @@ +
+ $message +

+ If you want to hide this message you must remove the following line under the [Plugins] entry in your + 'config/config.ini.php' file to disable this plugin.
+ Plugins[] = $pluginName +

If this plugin has already been installed, you must add the following line under the + [PluginsInstalled] entry in your 'config/config.ini.php' file:
+ PluginsInstalled[] = $pluginName"); + } + + public function isHtmlMessage() + { + return true; + } +} diff --git a/www/analytics/core/Plugin/ProcessedMetric.php b/www/analytics/core/Plugin/ProcessedMetric.php new file mode 100644 index 00000000..108612c8 --- /dev/null +++ b/www/analytics/core/Plugin/ProcessedMetric.php @@ -0,0 +1,70 @@ +pluginManager = $pluginManager; + } + + /** + * @return ReleaseChannel[] + */ + public function getAllReleaseChannels() + { + $classNames = $this->pluginManager->findMultipleComponents('ReleaseChannel', 'Piwik\\UpdateCheck\\ReleaseChannel'); + $channels = array(); + + foreach ($classNames as $className) { + $channels[] = StaticContainer::get($className); + } + + usort($channels, function (ReleaseChannel $a, ReleaseChannel $b) { + if ($a->getOrder() === $b->getOrder()) { + return 0; + } + + return ($a->getOrder() < $b->getOrder()) ? -1 : 1; + }); + + return $channels; + } + + /** + * @return ReleaseChannel + */ + public function getActiveReleaseChannel() + { + $channel = Config::getInstance()->General['release_channel']; + $channel = $this->factory($channel); + + if (!empty($channel)) { + return $channel; + } + + $channels = $this->getAllReleaseChannels(); + + // we default to the one with lowest id + return reset($channels); + } + + /** + * Sets the given release channel in config but does not save id. $config->forceSave() still needs to be called + * @param string $channel + */ + public function setActiveReleaseChannelId($channel) + { + $general = Config::getInstance()->General; + $general['release_channel'] = $channel; + Config::getInstance()->General = $general; + } + + public function isValidReleaseChannelId($releaseChannelId) + { + $channel = $this->factory($releaseChannelId); + + return !empty($channel); + } + + /** + * @param string $releaseChannelId + * @return ReleaseChannel + */ + private function factory($releaseChannelId) + { + $releaseChannelId = strtolower($releaseChannelId); + + foreach ($this->getAllReleaseChannels() as $releaseChannel) { + if ($releaseChannelId === strtolower($releaseChannel->getId())) { + return $releaseChannel; + } + } + } +} \ No newline at end of file diff --git a/www/analytics/core/Plugin/Report.php b/www/analytics/core/Plugin/Report.php new file mode 100644 index 00000000..5ab8583a --- /dev/null +++ b/www/analytics/core/Plugin/Report.php @@ -0,0 +1,1001 @@ + 5)`. + * @var null|array + * @api + */ + protected $parameters = null; + + /** + * An instance of a dimension if the report has one. You can create a new dimension using the Piwik console CLI tool + * if needed. + * @var \Piwik\Columns\Dimension + */ + protected $dimension; + + /** + * The name of the API action to load a subtable if supported. The action has to be of the same module. For instance + * a report "getKeywords" might support a subtable "getSearchEngines" which shows how often a keyword was searched + * via a specific search engine. + * @var string + * @api + */ + protected $actionToLoadSubTables = ''; + + /** + * The order of the report. Depending on the order the report gets a different position in the list of widgets, + * the menu and the mobile app. + * @var int + * @api + */ + protected $order = 1; + + /** + * Separator for building recursive labels (or paths) + * @var string + * @api + */ + protected $recursiveLabelSeparator = ' - '; + + /** + * Default sort column. Either a column name or a column id. + * + * @var string|int + */ + protected $defaultSortColumn = 'nb_visits'; + + /** + * Default sort desc. If true will sort by default desc, if false will sort by default asc + * + * @var bool + */ + protected $defaultSortOrderDesc = true; + + /** + * @var array + * @ignore + */ + public static $orderOfReports = array( + 'General_MultiSitesSummary', + 'VisitsSummary_VisitsSummary', + 'Goals_Ecommerce', + 'General_Actions', + 'Events_Events', + 'Actions_SubmenuSitesearch', + 'Referrers_Referrers', + 'Goals_Goals', + 'General_Visitors', + 'DevicesDetection_DevicesDetection', + 'General_VisitorSettings', + 'API' + ); + + /** + * The constructur initializes the module, action and the default metrics. If you want to overwrite any of those + * values or if you want to do any work during initializing overwrite the method {@link init()}. + * @ignore + */ + final public function __construct() + { + $classname = get_class($this); + $parts = explode('\\', $classname); + + if (5 === count($parts)) { + $this->module = $parts[2]; + $this->action = lcfirst($parts[4]); + } + + $this->init(); + } + + /** + * Here you can do any instance initialization and overwrite any default values. You should avoid doing time + * consuming initialization here and if possible delay as long as possible. An instance of this report will be + * created in most page requests. + * @api + */ + protected function init() + { + } + + /** + * Defines whether a report is enabled or not. For instance some reports might not be available to every user or + * might depend on a setting (such as Ecommerce) of a site. In such a case you can perform any checks and then + * return `true` or `false`. If your report is only available to users having super user access you can do the + * following: `return Piwik::hasUserSuperUserAccess();` + * @return bool + * @api + */ + public function isEnabled() + { + return true; + } + + /** + * This method checks whether the report is available, see {@isEnabled()}. If not, it triggers an exception + * containing a message that will be displayed to the user. You can overwrite this message in case you want to + * customize the error message. Eg. + * ``` + if (!$this->isEnabled()) { + throw new Exception('Setting XYZ is not enabled or the user has not enough permission'); + } + * ``` + * @throws \Exception + * @api + */ + public function checkIsEnabled() + { + if (!$this->isEnabled()) { + throw new Exception(Piwik::translate('General_ExceptionReportNotEnabled')); + } + } + + /** + * Returns the id of the default visualization for this report. Eg 'table' or 'pie'. Defaults to the HTML table. + * @return string + * @api + */ + public function getDefaultTypeViewDataTable() + { + return HtmlTable::ID; + } + + /** + * Returns if the default viewDataTable type should always be used. e.g. the type won't be changeable through config or url params. + * Defaults to false + * @return bool + */ + public function alwaysUseDefaultViewDataTable() + { + return false; + } + + /** + * Here you can configure how your report should be displayed and which capabilities your report has. For instance + * whether your report supports a "search" or not. EG `$view->config->show_search = false`. You can also change the + * default request config. For instance you can change how many rows are displayed by default: + * `$view->requestConfig->filter_limit = 10;`. See {@link ViewDataTable} for more information. + * @param ViewDataTable $view + * @api + */ + public function configureView(ViewDataTable $view) + { + } + + /** + * Renders a report depending on the configured ViewDataTable see {@link configureView()} and + * {@link getDefaultTypeViewDataTable()}. If you want to customize the render process or just render any custom view + * you can overwrite this method. + * + * @return string + * @throws \Exception In case the given API action does not exist yet. + * @api + */ + public function render() + { + $apiProxy = Proxy::getInstance(); + + if (!$apiProxy->isExistingApiAction($this->module, $this->action)) { + throw new Exception("Invalid action name '$this->action' for '$this->module' plugin."); + } + + $apiAction = $apiProxy->buildApiActionName($this->module, $this->action); + + $view = ViewDataTableFactory::build(null, $apiAction, $this->module . '.' . $this->action); + + $rendered = $view->render(); + + return $rendered; + } + + /** + * By default a widget will be configured for this report if a {@link $widgetTitle} is set. If you want to customize + * the way the widget is added or modify any other behavior you can overwrite this method. + * @param WidgetsList $widget + * @api + */ + public function configureWidget(WidgetsList $widget) + { + if ($this->widgetTitle) { + $params = array(); + if (!empty($this->widgetParams) && is_array($this->widgetParams)) { + $params = $this->widgetParams; + } + $widget->add($this->category, $this->widgetTitle, $this->module, $this->action, $params); + } + } + + /** + * By default a menu item will be added to the reporting menu if a {@link $menuTitle} is set. If you want to + * customize the way the item is added or modify any other behavior you can overwrite this method. For instance + * in case you need to add additional url properties beside module and action which are added by default. + * @param \Piwik\Menu\MenuReporting $menu + * @api + */ + public function configureReportingMenu(MenuReporting $menu) + { + if ($this->menuTitle) { + $action = $this->getMenuControllerAction(); + if ($this->isEnabled()) { + $menu->addItem($this->category, + $this->menuTitle, + array('module' => $this->module, 'action' => $action), + $this->order); + } + } + } + + /** + * @ignore + * @see $recursiveLabelSeparator + */ + public function getRecursiveLabelSeparator() + { + return $this->recursiveLabelSeparator; + } + + /** + * Returns an array of supported metrics and their corresponding translations. Eg `array('nb_visits' => 'Visits')`. + * By default the given {@link $metrics} are used and their corresponding translations are looked up automatically. + * If a metric is not translated, you should add the default metric translation for this metric using + * the {@hook Metrics.getDefaultMetricTranslations} event. If you want to overwrite any default metric translation + * you should overwrite this method, call this parent method to get all default translations and overwrite any + * custom metric translations. + * @return array + * @api + */ + public function getMetrics() + { + return $this->getMetricTranslations($this->metrics); + } + + /** + * Returns the list of metrics required at minimum for a report factoring in the columns requested by + * the report requester. + * + * This will return all the metrics requested (or all the metrics in the report if nothing is requested) + * **plus** the metrics required to calculate the requested processed metrics. + * + * This method should be used in **Plugin.get** API methods. + * + * @param string[]|null $allMetrics The list of all available unprocessed metrics. Defaults to this report's + * metrics. + * @param string[]|null $restrictToColumns The requested columns. + * @return string[] + */ + public function getMetricsRequiredForReport($allMetrics = null, $restrictToColumns = null) + { + if (empty($allMetrics)) { + $allMetrics = $this->metrics; + } + + if (empty($restrictToColumns)) { + $restrictToColumns = array_merge($allMetrics, array_keys($this->getProcessedMetrics())); + } + + $processedMetricsById = $this->getProcessedMetricsById(); + $metricsSet = array_flip($allMetrics); + + $metrics = array(); + foreach ($restrictToColumns as $column) { + if (isset($processedMetricsById[$column])) { + $metrics = array_merge($metrics, $processedMetricsById[$column]->getDependentMetrics()); + } elseif (isset($metricsSet[$column])) { + $metrics[] = $column; + } + } + return array_unique($metrics); + } + + /** + * Returns an array of supported processed metrics and their corresponding translations. Eg + * `array('nb_visits' => 'Visits')`. By default the given {@link $processedMetrics} are used and their + * corresponding translations are looked up automatically. If a metric is not translated, you should add the + * default metric translation for this metric using the {@hook Metrics.getDefaultMetricTranslations} event. If you + * want to overwrite any default metric translation you should overwrite this method, call this parent method to + * get all default translations and overwrite any custom metric translations. + * @return array|mixed + * @api + */ + public function getProcessedMetrics() + { + if (!is_array($this->processedMetrics)) { + return $this->processedMetrics; + } + + return $this->getMetricTranslations($this->processedMetrics); + } + + /** + * Returns the array of all metrics displayed by this report. + * + * @return array + * @api + */ + public function getAllMetrics() + { + $processedMetrics = $this->getProcessedMetrics() ?: array(); + return array_keys(array_merge($this->getMetrics(), $processedMetrics)); + } + + /** + * Returns an array of metric documentations and their corresponding translations. Eg + * `array('nb_visits' => 'If a visitor comes to your website for the first time or if he visits a page more than 30 minutes after...')`. + * By default the given {@link $metrics} are used and their corresponding translations are looked up automatically. + * If there is a metric documentation not found, you should add the default metric documentation translation for + * this metric using the {@hook Metrics.getDefaultMetricDocumentationTranslations} event. If you want to overwrite + * any default metric translation you should overwrite this method, call this parent method to get all default + * translations and overwrite any custom metric translations. + * @return array + * @api + */ + protected function getMetricsDocumentation() + { + $translations = Metrics::getDefaultMetricsDocumentation(); + $documentation = array(); + + foreach ($this->metrics as $metric) { + if (!empty($translations[$metric])) { + $documentation[$metric] = $translations[$metric]; + } + } + + $processedMetrics = $this->processedMetrics ?: array(); + foreach ($processedMetrics as $processedMetric) { + if (is_string($processedMetric) && !empty($translations[$processedMetric])) { + $documentation[$processedMetric] = $translations[$processedMetric]; + } elseif ($processedMetric instanceof ProcessedMetric) { + $name = $processedMetric->getName(); + $metricDocs = $processedMetric->getDocumentation(); + if (empty($metricDocs)) { + $metricDocs = @$translations[$name]; + } + + if (!empty($metricDocs)) { + $documentation[$processedMetric->getName()] = $metricDocs; + } + } + } + + return $documentation; + } + + /** + * @return bool + * @ignore + */ + public function hasGoalMetrics() + { + return $this->hasGoalMetrics; + } + + /** + * If the report is enabled the report metadata for this report will be built and added to the list of available + * reports. Overwrite this method and leave it empty in case you do not want your report to be added to the report + * metadata. In this case your report won't be visible for instance in the mobile app and scheduled reports + * generator. We recommend to change this behavior only if you are familiar with the Piwik core. `$infos` contains + * the current requested date, period and site. + * @param $availableReports + * @param $infos + * @api + */ + public function configureReportMetadata(&$availableReports, $infos) + { + if (!$this->isEnabled()) { + return; + } + + $report = $this->buildReportMetadata(); + + if (!empty($report)) { + $availableReports[] = $report; + } + } + + /** + * Builts the report metadata for this report. Can be useful in case you want to change the behavior of + * {@link configureReportMetadata()}. + * @return array + * @ignore + */ + protected function buildReportMetadata() + { + $report = array( + 'category' => $this->getCategory(), + 'name' => $this->getName(), + 'module' => $this->getModule(), + 'action' => $this->getAction() + ); + + if (null !== $this->parameters) { + $report['parameters'] = $this->parameters; + } + + if (!empty($this->dimension)) { + $report['dimension'] = $this->dimension->getName(); + } + + if (!empty($this->documentation)) { + $report['documentation'] = $this->documentation; + } + + if (true === $this->isSubtableReport) { + $report['isSubtableReport'] = $this->isSubtableReport; + } + + $report['metrics'] = $this->getMetrics(); + $report['metricsDocumentation'] = $this->getMetricsDocumentation(); + $report['processedMetrics'] = $this->getProcessedMetrics(); + + if (!empty($this->actionToLoadSubTables)) { + $report['actionToLoadSubTables'] = $this->actionToLoadSubTables; + } + + if (true === $this->constantRowsCount) { + $report['constantRowsCount'] = $this->constantRowsCount; + } + + $report['order'] = $this->order; + + return $report; + } + + /** + * @ignore + */ + public function getDefaultSortColumn() + { + return $this->defaultSortColumn; + } + + /** + * @ignore + */ + public function getDefaultSortOrder() + { + if ($this->defaultSortOrderDesc) { + return Sort::ORDER_DESC; + } + + return Sort::ORDER_ASC; + } + + /** + * Get the list of related reports if there are any. They will be displayed for instance below a report as a + * recommended related report. + * + * @return Report[] + * @api + */ + public function getRelatedReports() + { + return array(); + } + + /** + * Gets the translated widget title if one is defined. + * @return string + * @ignore + */ + public function getWidgetTitle() + { + if ($this->widgetTitle) { + return Piwik::translate($this->widgetTitle); + } + } + + /** + * Get the name of the report + * @return string + * @ignore + */ + public function getName() + { + return $this->name; + } + + /** + * Get the name of the module. + * @return string + * @ignore + */ + public function getModule() + { + return $this->module; + } + + /** + * Get the name of the action. + * @return string + * @ignore + */ + public function getAction() + { + return $this->action; + } + + /** + * Get the translated name of the category the report belongs to. + * @return string + * @ignore + */ + public function getCategory() + { + return Piwik::translate($this->category); + } + + /** + * Get the translation key of the category the report belongs to. + * @return string + * @ignore + */ + public function getCategoryKey() + { + return $this->category; + } + + /** + * @return \Piwik\Columns\Dimension + * @ignore + */ + public function getDimension() + { + return $this->dimension; + } + + /** + * Returns the order of the report + * @return int + * @ignore + */ + public function getOrder() + { + return $this->order; + } + + /** + * Get the menu title if one is defined. + * @return string + * @ignore + */ + public function getMenuTitle() + { + return $this->menuTitle; + } + + /** + * Get the action to load sub tables if one is defined. + * @return string + * @ignore + */ + public function getActionToLoadSubTables() + { + return $this->actionToLoadSubTables; + } + + /** + * Returns the Dimension instance of this report's subtable report. + * + * @return Dimension|null The subtable report's dimension or null if there is subtable report or + * no dimension for the subtable report. + * @api + */ + public function getSubtableDimension() + { + if (empty($this->actionToLoadSubTables)) { + return null; + } + + list($subtableReportModule, $subtableReportAction) = $this->getSubtableApiMethod(); + + $subtableReport = self::factory($subtableReportModule, $subtableReportAction); + if (empty($subtableReport)) { + return null; + } + + return $subtableReport->getDimension(); + } + + /** + * Returns true if the report is for another report's subtable, false if otherwise. + * + * @return bool + */ + public function isSubtableReport() + { + return $this->isSubtableReport; + } + + /** + * Fetches the report represented by this instance. + * + * @param array $paramOverride Query parameter overrides. + * @return DataTable + * @api + */ + public function fetch($paramOverride = array()) + { + return Request::processRequest($this->module . '.' . $this->action, $paramOverride); + } + + /** + * Fetches a subtable for the report represented by this instance. + * + * @param int $idSubtable The subtable ID. + * @param array $paramOverride Query parameter overrides. + * @return DataTable + * @api + */ + public function fetchSubtable($idSubtable, $paramOverride = array()) + { + $paramOverride = array('idSubtable' => $idSubtable) + $paramOverride; + + list($module, $action) = $this->getSubtableApiMethod(); + return Request::processRequest($module . '.' . $action, $paramOverride); + } + + /** + * Get an instance of a specific report belonging to the given module and having the given action. + * @param string $module + * @param string $action + * @return null|\Piwik\Plugin\Report + * @api + */ + public static function factory($module, $action) + { + $listApiToReport = self::getMapOfModuleActionsToReport(); + $api = $module . '.' . ucfirst($action); + + if (!array_key_exists($api, $listApiToReport)) { + return null; + } + + $klassName = $listApiToReport[$api]; + + return new $klassName; + } + + private static function getMapOfModuleActionsToReport() + { + $cacheId = CacheId::pluginAware('ReportFactoryMap'); + + $cache = Cache::getEagerCache(); + if ($cache->contains($cacheId)) { + $mapApiToReport = $cache->fetch($cacheId); + } else { + $reports = self::getAllReports(); + + $mapApiToReport = array(); + foreach ($reports as $report) { + $key = $report->getModule() . '.' . ucfirst($report->getAction()); + $mapApiToReport[$key] = get_class($report); + } + + $cache->save($cacheId, $mapApiToReport); + } + + return $mapApiToReport; + } + + /** + * Returns a list of all available reports. Even not enabled reports will be returned. They will be already sorted + * depending on the order and category of the report. + * @return \Piwik\Plugin\Report[] + * @api + */ + public static function getAllReports() + { + $reports = self::getAllReportClasses(); + $cacheId = CacheId::languageAware('Reports' . md5(implode('', $reports))); + $cache = PiwikCache::getTransientCache(); + + + if (!$cache->contains($cacheId)) { + $instances = array(); + + foreach ($reports as $report) { + $instances[] = new $report(); + } + + usort($instances, array('self', 'sort')); + + $cache->save($cacheId, $instances); + } + + return $cache->fetch($cacheId); + } + + /** + * Returns class names of all Report metadata classes. + * + * @return string[] + * @api + */ + public static function getAllReportClasses() + { + return PluginManager::getInstance()->findMultipleComponents('Reports', '\\Piwik\\Plugin\\Report'); + } + + /** + * API metadata are sorted by category/name, + * with a little tweak to replicate the standard Piwik category ordering + * + * @param Report $a + * @param Report $b + * @return int + */ + private static function sort($a, $b) + { + return ($category = strcmp(array_search($a->category, self::$orderOfReports), array_search($b->category, self::$orderOfReports))) == 0 + ? ($a->order < $b->order ? -1 : 1) + : $category; + } + + private function getMetricTranslations($metricsToTranslate) + { + $translations = Metrics::getDefaultMetricTranslations(); + $metrics = array(); + + foreach ($metricsToTranslate as $metric) { + if ($metric instanceof Metric) { + $metricName = $metric->getName(); + $translation = $metric->getTranslatedName(); + } else { + $metricName = $metric; + $translation = @$translations[$metric]; + } + + $metrics[$metricName] = $translation ?: $metricName; + } + + return $metrics; + } + + private function getMenuControllerAction() + { + return self::PREFIX_ACTION_IN_MENU . ucfirst($this->action); + } + + private function getSubtableApiMethod() + { + if (strpos($this->actionToLoadSubTables, '.') !== false) { + return explode('.', $this->actionToLoadSubTables); + } else { + return array($this->module, $this->actionToLoadSubTables); + } + } + + /** + * Finds a top level report that provides stats for a specific Dimension. + * + * @param Dimension $dimension The dimension whose report we're looking for. + * @return Report|null The + * @api + */ + public static function getForDimension(Dimension $dimension) + { + return ComponentFactory::getComponentIf(__CLASS__, $dimension->getModule(), function (Report $report) use ($dimension) { + return !$report->isSubtableReport() + && $report->getDimension() + && $report->getDimension()->getId() == $dimension->getId(); + }); + } + + /** + * Returns an array mapping the ProcessedMetrics served by this report by their string names. + * + * @return ProcessedMetric[] + */ + public function getProcessedMetricsById() + { + $processedMetrics = $this->processedMetrics ?: array(); + + $result = array(); + foreach ($processedMetrics as $processedMetric) { + if ($processedMetric instanceof ProcessedMetric) { // instanceof check for backwards compatibility + $result[$processedMetric->getName()] = $processedMetric; + } + } + return $result; + } + + /** + * Returns the Metrics that are displayed by a DataTable of a certain Report type. + * + * Includes ProcessedMetrics and Metrics. + * + * @param DataTable $dataTable + * @param Report|null $report + * @param string $baseType The base type each metric class needs to be of. + * @return Metric[] + * @api + */ + public static function getMetricsForTable(DataTable $dataTable, Report $report = null, $baseType = 'Piwik\\Plugin\\Metric') + { + $metrics = $dataTable->getMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME) ?: array(); + + if (!empty($report)) { + $metrics = array_merge($metrics, $report->getProcessedMetricsById()); + } + + $result = array(); + + /** @var Metric $metric */ + foreach ($metrics as $metric) { + if (!($metric instanceof $baseType)) { + continue; + } + + $result[$metric->getName()] = $metric; + } + + return $result; + } + + /** + * Returns the ProcessedMetrics that should be computed and formatted for a DataTable of a + * certain report. The ProcessedMetrics returned are those specified by the Report metadata + * as well as the DataTable metadata. + * + * @param DataTable $dataTable + * @param Report|null $report + * @return ProcessedMetric[] + * @api + */ + public static function getProcessedMetricsForTable(DataTable $dataTable, Report $report = null) + { + return self::getMetricsForTable($dataTable, $report, 'Piwik\\Plugin\\ProcessedMetric'); + } +} diff --git a/www/analytics/core/Plugin/RequestProcessors.php b/www/analytics/core/Plugin/RequestProcessors.php new file mode 100644 index 00000000..82727448 --- /dev/null +++ b/www/analytics/core/Plugin/RequestProcessors.php @@ -0,0 +1,27 @@ +findMultipleComponents('Tracker', 'Piwik\\Tracker\\RequestProcessor'); + + $instances = array(); + foreach ($processors as $processor) { + $instances[] = StaticContainer::get($processor); + } + + return $instances; + } +} diff --git a/www/analytics/core/Plugin/Segment.php b/www/analytics/core/Plugin/Segment.php new file mode 100644 index 00000000..2a20208e --- /dev/null +++ b/www/analytics/core/Plugin/Segment.php @@ -0,0 +1,341 @@ +setType(\Piwik\Plugin\Segment::TYPE_DIMENSION); + $segment->setName('General_EntryKeyword'); + $segment->setCategory('General_Visit'); + $segment->setSegment('entryKeyword'); + $segment->setSqlSegment('log_visit.entry_keyword'); + $segment->setAcceptedValues('Any keywords people search for on your website such as "help" or "imprint"'); + ``` + * @api + * @since 2.5.0 + */ +class Segment +{ + /** + * Segment type 'dimension'. Can be used along with {@link setType()}. + * @api + */ + const TYPE_DIMENSION = 'dimension'; + + /** + * Segment type 'metric'. Can be used along with {@link setType()}. + * @api + */ + const TYPE_METRIC = 'metric'; + + private $type; + private $category; + private $name; + private $segment; + private $sqlSegment; + private $sqlFilter; + private $sqlFilterValue; + private $acceptValues; + private $permission; + private $suggestedValuesCallback; + private $unionOfSegments; + + /** + * If true, this segment will only be visible to the user if the user has view access + * to one of the requested sites (see API.getSegmentsMetadata). + * + * @var bool + */ + private $requiresAtLeastViewAccess = false; + + /** + * @ignore + */ + final public function __construct() + { + $this->init(); + } + + /** + * Here you can initialize this segment and set any default values. It is called directly after the object is + * created. + * @api + */ + protected function init() + { + } + + /** + * Here you should explain which values are accepted/useful for your segment, for example: + * "1, 2, 3, etc." or "comcast.net, proxad.net, etc.". If the value needs any special encoding you should mention + * this as well. For example "Any URL including protocol. The URL must be URL encoded." + * + * @param string $acceptedValues + * @api + */ + public function setAcceptedValues($acceptedValues) + { + $this->acceptValues = $acceptedValues; + } + + /** + * Set (overwrite) the category this segment belongs to. It should be a translation key such as 'General_Actions' + * or 'General_Visit'. + * @param string $category + * @api + */ + public function setCategory($category) + { + $this->category = $category; + } + + /** + * Set (overwrite) the segment display name. This name will be visible in the API and the UI. It should be a + * translation key such as 'Actions_ColumnEntryPageTitle' or 'Resolution_ColumnResolution'. + * @param string $name + * @api + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * Set (overwrite) the name of the segment. The name should be lower case first and has to be unique. The segment + * name defined here needs to be set in the URL to actually apply this segment. Eg if the segment is 'searches' + * you need to set "&segment=searches>0" in the UI. + * @param string $segment + * @api + */ + public function setSegment($segment) + { + $this->segment = $segment; + $this->check(); + } + + /** + * Sometimes you want users to set values that differ from the way they are actually stored. For instance if you + * want to allow to filter by any URL than you might have to resolve this URL to an action id. Or a country name + * maybe has to be mapped to a 2 letter country code. You can do this by specifing either a callable such as + * `array('Classname', 'methodName')` or by passing a closure. There will be four values passed to the given closure + * or callable: `string $valueToMatch`, `string $segment` (see {@link setSegment()}), `string $matchType` + * (eg SegmentExpression::MATCH_EQUAL or any other match constant of this class) and `$segmentName`. + * + * If the closure returns NULL, then Piwik assumes the segment sub-string will not match any visitor. + * + * @param string|\Closure $sqlFilter + * @api + */ + public function setSqlFilter($sqlFilter) + { + $this->sqlFilter = $sqlFilter; + } + + /** + * Similar to {@link setSqlFilter()} you can map a given segment value to another value. For instance you could map + * "new" to 0, 'returning' to 1 and any other value to '2'. You can either define a callable or a closure. There + * will be only one value passed to the closure or callable which contains the value a user has set for this + * segment. This callback is called shortly before {@link setSqlFilter()}. + * @param string|array $sqlFilterValue + * @api + */ + public function setSqlFilterValue($sqlFilterValue) + { + $this->sqlFilterValue = $sqlFilterValue; + } + + /** + * Defines to which column in the MySQL database the segment belongs: 'mytablename.mycolumnname'. Eg + * 'log_visit.idsite'. When a segment is applied the given or filtered value will be compared with this column. + * + * @param string $sqlSegment + * @api + */ + public function setSqlSegment($sqlSegment) + { + $this->sqlSegment = $sqlSegment; + $this->check(); + } + + /** + * Set a list of segments that should be used instead of fetching the values from a single column. + * All set segments will be applied via an OR operator. + * + * @param array $segments + * @api + */ + public function setUnionOfSegments($segments) + { + $this->unionOfSegments = $segments; + $this->check(); + } + + /** + * @return array + * @ignore + */ + public function getUnionOfSegments() + { + return $this->unionOfSegments; + } + + /** + * @return string + * @ignore + */ + public function getSqlSegment() + { + return $this->sqlSegment; + } + + /** + * Set (overwrite) the type of this segment which is usually either a 'dimension' or a 'metric'. + * @param string $type See constansts TYPE_* + * @api + */ + public function setType($type) + { + $this->type = $type; + } + + /** + * @return string + * @ignore + */ + public function getType() + { + return $this->type; + } + + /** + * @return string + * @ignore + */ + public function getName() + { + return $this->name; + } + + /** + * Returns the name of this segment as it should appear in segment expressions. + * + * @return string + */ + public function getSegment() + { + return $this->segment; + } + + /** + * Set callback which will be executed when user will call for suggested values for segment. + * + * @param callable $suggestedValuesCallback + */ + public function setSuggestedValuesCallback($suggestedValuesCallback) + { + $this->suggestedValuesCallback = $suggestedValuesCallback; + } + + /** + * You can restrict the access to this segment by passing a boolean `false`. For instance if you want to make + * a certain segment only available to users having super user access you could do the following: + * `$segment->setPermission(Piwik::hasUserSuperUserAccess());` + * @param bool $permission + * @api + */ + public function setPermission($permission) + { + $this->permission = $permission; + } + + /** + * @return array + * @ignore + */ + public function toArray() + { + $segment = array( + 'type' => $this->type, + 'category' => $this->category, + 'name' => $this->name, + 'segment' => $this->segment, + 'sqlSegment' => $this->sqlSegment, + ); + + if (!empty($this->unionOfSegments)) { + $segment['unionOfSegments'] = $this->unionOfSegments; + } + + if (!empty($this->sqlFilter)) { + $segment['sqlFilter'] = $this->sqlFilter; + } + + if (!empty($this->sqlFilterValue)) { + $segment['sqlFilterValue'] = $this->sqlFilterValue; + } + + if (!empty($this->acceptValues)) { + $segment['acceptedValues'] = $this->acceptValues; + } + + if (isset($this->permission)) { + $segment['permission'] = $this->permission; + } + + if (is_callable($this->suggestedValuesCallback)) { + $segment['suggestedValuesCallback'] = $this->suggestedValuesCallback; + } + + return $segment; + } + + /** + * Returns true if this segment should only be visible to the user if the user has view access + * to one of the requested sites (see API.getSegmentsMetadata), false if it should always be + * visible to the user (even the anonymous user). + * + * @return boolean + * @ignore + */ + public function isRequiresAtLeastViewAccess() + { + return $this->requiresAtLeastViewAccess; + } + + /** + * Sets whether the segment should only be visible if the user requesting it has view access + * to one of the requested sites and if the user is not the anonymous user. + * + * @param boolean $requiresAtLeastViewAccess + * @ignore + */ + public function setRequiresAtLeastViewAccess($requiresAtLeastViewAccess) + { + $this->requiresAtLeastViewAccess = $requiresAtLeastViewAccess; + } + + private function check() + { + if ($this->sqlSegment && $this->unionOfSegments) { + throw new Exception(sprintf('Union of segments and SQL segment is set for segment "%s", use only one of them', $this->name)); + } + + if ($this->segment && $this->unionOfSegments && in_array($this->segment, $this->unionOfSegments, true)) { + throw new Exception(sprintf('The segment %s contains a union segment to itself', $this->name)); + } + } +} diff --git a/www/analytics/core/Plugin/Settings.php b/www/analytics/core/Plugin/Settings.php index 20a5a305..c26581e4 100644 --- a/www/analytics/core/Plugin/Settings.php +++ b/www/analytics/core/Plugin/Settings.php @@ -1,6 +1,6 @@ [setting-value] ). - * - * @var array - */ - private $settingsValues = array(); - private $introduction; - private $pluginName; + protected $pluginName; + + /** + * @var StorageInterface + */ + protected $storage; /** * Constructor. - * - * @param string $pluginName The name of the plugin these settings are for. */ - public function __construct($pluginName) + public function __construct($pluginName = null) { - $this->pluginName = $pluginName; + if (!empty($pluginName)) { + $this->pluginName = $pluginName; + } else { + $classname = get_class($this); + $parts = explode('\\', $classname); + + if (3 <= count($parts)) { + $this->pluginName = $parts[2]; + } + } + + $this->storage = Storage\Factory::make($this->pluginName); $this->init(); - $this->loadSettings(); + } + + /** + * @ignore + */ + public function getPluginName() + { + return $this->pluginName; + } + + /** + * @ignore + * @return Setting + */ + public function getSetting($name) + { + if (array_key_exists($name, $this->settings)) { + return $this->settings[$name]; + } } /** @@ -90,7 +115,7 @@ abstract class Settings implements StorageInterface /** * Returns the introduction text for this plugin's settings. - * + * * @return string */ public function getIntroduction() @@ -106,14 +131,17 @@ abstract class Settings implements StorageInterface public function getSettingsForCurrentUser() { $settings = array_filter($this->getSettings(), function (Setting $setting) { - return $setting->canBeDisplayedForCurrentUser(); + return $setting->isWritableByCurrentUser(); }); - uasort($settings, function ($setting1, $setting2) use ($settings) { + $settings2 = $settings; + + uasort($settings, function ($setting1, $setting2) use ($settings2) { + /** @var Setting $setting1 */ /** @var Setting $setting2 */ if ($setting1->getOrder() == $setting2->getOrder()) { // preserve order for settings having same order - foreach ($settings as $setting) { + foreach ($settings2 as $setting) { if ($setting1 === $setting) { return -1; } @@ -142,12 +170,57 @@ abstract class Settings implements StorageInterface return $this->settings; } + /** + * Makes a new plugin setting available. + * + * @param Setting $setting + * @throws \Exception If there is a setting with the same name that already exists. + * If the name contains non-alphanumeric characters. + */ + protected function addSetting(Setting $setting) + { + $name = $setting->getName(); + + if (!ctype_alnum(str_replace('_', '', $name))) { + $msg = sprintf('The setting name "%s" in plugin "%s" is not valid. Only underscores, alpha and numerical characters are allowed', $setting->getName(), $this->pluginName); + throw new \Exception($msg); + } + + if (array_key_exists($name, $this->settings)) { + throw new \Exception(sprintf('A setting with name "%s" does already exist for plugin "%s"', $setting->getName(), $this->pluginName)); + } + + $this->setDefaultTypeAndFieldIfNeeded($setting); + $this->addValidatorIfNeeded($setting); + + $setting->setStorage($this->storage); + $setting->setPluginName($this->pluginName); + + $this->settings[$name] = $setting; + } + /** * Saves (persists) the current setting values in the database. */ public function save() { - Option::set($this->getOptionKey(), serialize($this->settingsValues)); + $this->storage->save(); + + SettingsStorage::clearCache(); + + /** + * Triggered after a plugin settings have been updated. + * + * **Example** + * + * Piwik::addAction('Settings.MyPlugin.settingsUpdated', function (Settings $settings) { + * $value = $settings->someSetting->getValue(); + * // Do something with the new setting value + * }); + * + * @param Settings $settings The plugin settings object. + */ + Piwik::postEvent(sprintf('Settings.%s.settingsUpdated', $this->pluginName), array($this)); } /** @@ -158,138 +231,9 @@ abstract class Settings implements StorageInterface { Piwik::checkUserHasSuperUserAccess(); - Option::delete($this->getOptionKey()); - $this->settingsValues = array(); - } + $this->storage->deleteAllValues(); - /** - * Returns the current value for a setting. If no value is stored, the default value - * is be returned. - * - * @param Setting $setting - * @return mixed - * @throws \Exception If the setting does not exist or if the current user is not allowed to change the value - * of this setting. - */ - public function getSettingValue(Setting $setting) - { - $this->checkIsValidSetting($setting->getName()); - - if (array_key_exists($setting->getKey(), $this->settingsValues)) { - - return $this->settingsValues[$setting->getKey()]; - } - - return $setting->defaultValue; - } - - /** - * Sets (overwrites) the value of a setting in memory. To persist the change, {@link save()} must be - * called afterwards, otherwise the change has no effect. - * - * Before the setting is changed, the {@link Piwik\Settings\Setting::$validate} and - * {@link Piwik\Settings\Setting::$transform} closures will be invoked (if defined). If there is no validation - * filter, the setting value will be casted to the appropriate data type. - * - * @param Setting $setting - * @param string $value - * @throws \Exception If the setting does not exist or if the current user is not allowed to change the value - * of this setting. - */ - public function setSettingValue(Setting $setting, $value) - { - $this->checkIsValidSetting($setting->getName()); - - if ($setting->validate && $setting->validate instanceof \Closure) { - call_user_func($setting->validate, $value, $setting); - } - - if ($setting->transform && $setting->transform instanceof \Closure) { - $value = call_user_func($setting->transform, $value, $setting); - } elseif (isset($setting->type)) { - settype($value, $setting->type); - } - - $this->settingsValues[$setting->getKey()] = $value; - } - - /** - * Unsets a setting value in memory. To persist the change, {@link save()} must be - * called afterwards, otherwise the change has no effect. - * - * @param Setting $setting - */ - public function removeSettingValue(Setting $setting) - { - $this->checkHasEnoughPermission($setting); - - $key = $setting->getKey(); - - if (array_key_exists($key, $this->settingsValues)) { - unset($this->settingsValues[$key]); - } - } - - /** - * Makes a new plugin setting available. - * - * @param Setting $setting - * @throws \Exception If there is a setting with the same name that already exists. - * If the name contains non-alphanumeric characters. - */ - protected function addSetting(Setting $setting) - { - if (!ctype_alnum($setting->getName())) { - $msg = sprintf('The setting name "%s" in plugin "%s" is not valid. Only alpha and numerical characters are allowed', $setting->getName(), $this->pluginName); - throw new \Exception($msg); - } - - if (array_key_exists($setting->getName(), $this->settings)) { - throw new \Exception(sprintf('A setting with name "%s" does already exist for plugin "%s"', $setting->getName(), $this->pluginName)); - } - - $this->setDefaultTypeAndFieldIfNeeded($setting); - $this->addValidatorIfNeeded($setting); - - $setting->setStorage($this); - - $this->settings[$setting->getName()] = $setting; - } - - private function getOptionKey() - { - return 'Plugin_' . $this->pluginName . '_Settings'; - } - - private function loadSettings() - { - $values = Option::get($this->getOptionKey()); - - if (!empty($values)) { - $this->settingsValues = unserialize($values); - } - } - - private function checkIsValidSetting($name) - { - $setting = $this->getSetting($name); - - if (empty($setting)) { - throw new \Exception(sprintf('The setting %s does not exist', $name)); - } - - $this->checkHasEnoughPermission($setting); - } - - /** - * @param $name - * @return Setting|null - */ - private function getSetting($name) - { - if (array_key_exists($name, $this->settings)) { - return $this->settings[$name]; - } + SettingsStorage::clearCache(); } private function getDefaultType($controlType) @@ -320,30 +264,16 @@ abstract class Settings implements StorageInterface return $defaultControlTypes[$type]; } - /** - * @param $setting - * @throws \Exception - */ - private function checkHasEnoughPermission(Setting $setting) - { - // When the request is a Tracker request, allow plugins to read/write settings - if(SettingsServer::isTrackerApiRequest()) { - return; - } - - if (!$setting->canBeDisplayedForCurrentUser()) { - $errorMsg = Piwik::translate('CoreAdminHome_PluginSettingChangeNotAllowed', array($setting->getName(), $this->pluginName)); - throw new \Exception($errorMsg); - } - } - private function setDefaultTypeAndFieldIfNeeded(Setting $setting) { - if (!is_null($setting->uiControlType) && is_null($setting->type)) { + $hasControl = !is_null($setting->uiControlType); + $hasType = !is_null($setting->type); + + if ($hasControl && !$hasType) { $setting->type = $this->getDefaultType($setting->uiControlType); - } elseif (!is_null($setting->type) && is_null($setting->uiControlType)) { + } elseif ($hasType && !$hasControl) { $setting->uiControlType = $this->getDefaultCONTROL($setting->type); - } elseif (is_null($setting->uiControlType) && is_null($setting->type)) { + } elseif (!$hasControl && !$hasType) { $setting->type = static::TYPE_STRING; $setting->uiControlType = static::CONTROL_TEXT; } @@ -360,7 +290,7 @@ abstract class Settings implements StorageInterface $setting->validate = function ($value) use ($setting, $pluginName) { $errorMsg = Piwik::translate('CoreAdminHome_PluginSettingsValueNotAllowed', - array($setting->title, $pluginName)); + array($setting->title, $pluginName)); if (is_array($value) && $setting->type == Settings::TYPE_ARRAY) { foreach ($value as $val) { diff --git a/www/analytics/core/Plugin/Tasks.php b/www/analytics/core/Plugin/Tasks.php new file mode 100644 index 00000000..19f9e5bb --- /dev/null +++ b/www/analytics/core/Plugin/Tasks.php @@ -0,0 +1,154 @@ +daily('myMethodName') + } + + /** + * @return Task[] $tasks + */ + public function getScheduledTasks() + { + return $this->tasks; + } + + /** + * Schedule the given tasks/method to run once every hour. + * + * @param string $methodName The name of the method that will be called when the task is being + * exectuted. To make it work you need to create a public method having the + * given method name in your Tasks class. + * @param null|string $methodParameter Can be null if the task does not need any parameter or a string. It is not + * possible to specify multiple parameters as an array etc. If you need to + * pass multiple parameters separate them via any characters such as '###'. + * For instance '$param1###$param2###$param3' + * @param int $priority Can be any constant such as self::LOW_PRIORITY + * + * @return Schedule + * @api + */ + protected function hourly($methodName, $methodParameter = null, $priority = self::NORMAL_PRIORITY) + { + return $this->custom($this, $methodName, $methodParameter, 'hourly', $priority); + } + + /** + * Schedule the given tasks/method to run once every day. + * + * See {@link hourly()} + * @api + */ + protected function daily($methodName, $methodParameter = null, $priority = self::NORMAL_PRIORITY) + { + return $this->custom($this, $methodName, $methodParameter, 'daily', $priority); + } + + /** + * Schedule the given tasks/method to run once every week. + * + * See {@link hourly()} + * @api + */ + protected function weekly($methodName, $methodParameter = null, $priority = self::NORMAL_PRIORITY) + { + return $this->custom($this, $methodName, $methodParameter, 'weekly', $priority); + } + + /** + * Schedule the given tasks/method to run once every month. + * + * See {@link hourly()} + * @api + */ + protected function monthly($methodName, $methodParameter = null, $priority = self::NORMAL_PRIORITY) + { + return $this->custom($this, $methodName, $methodParameter, 'monthly', $priority); + } + + /** + * Schedules the given tasks/method to run depending at the given scheduled time. Unlike the convenient methods + * such as {@link hourly()} you need to specify the object on which the given method should be called. This can be + * either an instance of a class or a class name. For more information about these parameters see {@link hourly()} + * + * @param string|object $objectOrClassName + * @param string $methodName + * @param null|string $methodParameter + * @param string|Schedule $time + * @param int $priority + * + * @return \Piwik\Scheduler\Schedule\Schedule + * + * @throws \Exception If a wrong time format is given. Needs to be either a string such as 'daily', 'weekly', ... + * or an instance of {@link Piwik\Scheduler\Schedule\Schedule} + * + * @api + */ + protected function custom($objectOrClassName, $methodName, $methodParameter, $time, $priority = self::NORMAL_PRIORITY) + { + $this->checkIsValidTask($objectOrClassName, $methodName); + + if (is_string($time)) { + $time = Schedule::factory($time); + } + + if (!($time instanceof Schedule)) { + throw new \Exception('$time should be an instance of Schedule'); + } + + $this->scheduleTask(new Task($objectOrClassName, $methodName, $methodParameter, $time, $priority)); + + return $time; + } + + /** + * In case you need very high flexibility and none of the other convenient methods such as {@link hourly()} or + * {@link custom()} suit you, you can use this method to add a custom scheduled task. + * + * @param Task $task + */ + protected function scheduleTask(Task $task) + { + $this->tasks[] = $task; + } + + private function checkIsValidTask($objectOrClassName, $methodName) + { + Development::checkMethodIsCallable($objectOrClassName, $methodName, 'The registered task is not valid as the method'); + } +} diff --git a/www/analytics/core/Plugin/ViewDataTable.php b/www/analytics/core/Plugin/ViewDataTable.php index 069e08de..6e190388 100644 --- a/www/analytics/core/Plugin/ViewDataTable.php +++ b/www/analytics/core/Plugin/ViewDataTable.php @@ -1,6 +1,6 @@ render(); * } - * + * * **Using {@link Piwik\Plugin\Controller::renderReport}** - * + * * First, a controller method that displays a single report: - * + * * public function myReport() * { * return $this->renderReport(__FUNCTION__);` * } - * + * * Then the event handler for the {@hook ViewDataTable.configure} event: - * + * * public function configureViewDataTable(ViewDataTable $view) * { * switch ($view->requestConfig->apiMethodToRequestDataTable) { @@ -111,32 +111,32 @@ use Piwik\ViewDataTable\RequestConfig as VizRequest; * break; * } * } - * + * * **Using custom configuration objects in a new visualization** - * + * * class MyVisualizationConfig extends Piwik\ViewDataTable\Config * { * public $my_new_property = true; * } - * + * * class MyVisualizationRequestConfig extends Piwik\ViewDataTable\RequestConfig * { * public $my_new_property = false; * } - * + * * class MyVisualization extends Piwik\Plugin\ViewDataTable * { * public static function getDefaultConfig() * { * return new MyVisualizationConfig(); * } - * + * * public static function getDefaultRequestConfig() * { * return new MyVisualizationRequestConfig(); * } * } - * + * * * @api */ @@ -153,14 +153,14 @@ abstract class ViewDataTable implements ViewInterface /** * Contains display properties for this visualization. - * + * * @var \Piwik\ViewDataTable\Config */ public $config; /** * Contains request properties for this visualization. - * + * * @var \Piwik\ViewDataTable\RequestConfig */ public $requestConfig; @@ -175,7 +175,7 @@ abstract class ViewDataTable implements ViewInterface * Posts the {@hook ViewDataTable.configure} event which plugins can use to configure the * way reports are displayed. */ - public function __construct($controllerAction, $apiMethodToRequestDataTable) + public function __construct($controllerAction, $apiMethodToRequestDataTable, $overrideParams = array()) { list($controllerName, $controllerAction) = explode('.', $controllerAction); @@ -191,13 +191,53 @@ abstract class ViewDataTable implements ViewInterface $this->requestConfig->apiMethodToRequestDataTable = $apiMethodToRequestDataTable; + $report = Report::factory($this->requestConfig->getApiModuleToRequest(), $this->requestConfig->getApiMethodToRequest()); + + if (!empty($report)) { + /** @var Report $report */ + $subtable = $report->getActionToLoadSubTables(); + if (!empty($subtable)) { + $this->config->subtable_controller_action = $subtable; + } + + $this->config->show_goals = $report->hasGoalMetrics(); + + $relatedReports = $report->getRelatedReports(); + if (!empty($relatedReports)) { + foreach ($relatedReports as $relatedReport) { + $widgetTitle = $relatedReport->getWidgetTitle(); + + if ($widgetTitle && Common::getRequestVar('widget', 0, 'int')) { + $relatedReportName = $widgetTitle; + } else { + $relatedReportName = $relatedReport->getName(); + } + + $this->config->addRelatedReport($relatedReport->getModule() . '.' . $relatedReport->getAction(), + $relatedReportName); + } + } + + $metrics = $report->getMetrics(); + if (!empty($metrics)) { + $this->config->addTranslations($metrics); + } + + $processedMetrics = $report->getProcessedMetrics(); + if (!empty($processedMetrics)) { + $this->config->addTranslations($processedMetrics); + } + + $report->configureView($this); + } + /** * Triggered during {@link ViewDataTable} construction. Subscribers should customize * the view based on the report that is being displayed. - * + * * Plugins that define their own reports must subscribe to this event in order to * specify how the Piwik UI should display the report. - * + * * **Example** * * // event handler @@ -210,7 +250,7 @@ abstract class ViewDataTable implements ViewInterface * break; * } * } - * + * * @param ViewDataTable $view The instance to configure. */ Piwik::postEvent('ViewDataTable.configure', array($this)); @@ -229,16 +269,17 @@ abstract class ViewDataTable implements ViewInterface $this->requestConfig->filter_excludelowpop_value = $function(); } + $this->overrideViewPropertiesWithParams($overrideParams); $this->overrideViewPropertiesWithQueryParams(); } protected function assignRelatedReportsTitle() { - if(!empty($this->config->related_reports_title)) { + if (!empty($this->config->related_reports_title)) { // title already assigned by a plugin return; } - if(count($this->config->related_reports) == 1) { + if (count($this->config->related_reports) == 1) { $this->config->related_reports_title = Piwik::translate('General_RelatedReport') . ':'; } else { $this->config->related_reports_title = Piwik::translate('General_RelatedReports') . ':'; @@ -247,12 +288,12 @@ abstract class ViewDataTable implements ViewInterface /** * Returns the default config instance. - * + * * Visualizations that define their own display properties should override this method and * return an instance of their new {@link Piwik\ViewDataTable\Config} descendant. * * See the last example {@link ViewDataTable here} for more information. - * + * * @return \Piwik\ViewDataTable\Config */ public static function getDefaultConfig() @@ -262,12 +303,12 @@ abstract class ViewDataTable implements ViewInterface /** * Returns the default request config instance. - * + * * Visualizations that define their own request properties should override this method and * return an instance of their new {@link Piwik\ViewDataTable\RequestConfig} descendant. * * See the last example {@link ViewDataTable here} for more information. - * + * * @return \Piwik\ViewDataTable\RequestConfig */ public static function getDefaultRequestConfig() @@ -275,7 +316,7 @@ abstract class ViewDataTable implements ViewInterface return new VizRequest(); } - protected function loadDataTableFromAPI($fixedRequestParams = array()) + protected function loadDataTableFromAPI() { if (!is_null($this->dataTable)) { // data table is already there @@ -283,14 +324,14 @@ abstract class ViewDataTable implements ViewInterface return $this->dataTable; } - $this->dataTable = $this->request->loadDataTableFromAPI($fixedRequestParams); + $this->dataTable = $this->request->loadDataTableFromAPI(); return $this->dataTable; } /** * Returns the viewDataTable ID for this DataTable visualization. - * + * * Derived classes should not override this method. They should instead declare a const ID field * with the viewDataTable ID. * @@ -306,13 +347,13 @@ abstract class ViewDataTable implements ViewInterface throw new \Exception($message); } - return $id; + return $id; } /** * Returns `true` if this instance's or any of its ancestors' viewDataTable IDs equals the supplied ID, * `false` if otherwise. - * + * * Can be used to test whether a ViewDataTable object is an instance of a certain visualization or not, * without having to know where that visualization is. * @@ -399,7 +440,7 @@ abstract class ViewDataTable implements ViewInterface if (property_exists($this->requestConfig, $name)) { $this->requestConfig->$name = $this->getPropertyFromQueryParam($name, $this->requestConfig->$name); } elseif (property_exists($this->config, $name)) { - $this->config->$name = $this->getPropertyFromQueryParam($name, $this->config->$name); + $this->config->$name = $this->getPropertyFromQueryParam($name, $this->config->$name); } } @@ -443,7 +484,7 @@ abstract class ViewDataTable implements ViewInterface /** * Returns `true` if this visualization can display some type of data or not. - * + * * New visualization classes should override this method if they can only visualize certain * types of data. The evolution graph visualization, for example, can only visualize * sets of DataTables. If the API method used results in a single DataTable, the evolution @@ -456,4 +497,63 @@ abstract class ViewDataTable implements ViewInterface { return $view->config->show_all_views_icons; } + + private function overrideViewPropertiesWithParams($overrideParams) + { + if (empty($overrideParams)) { + return; + } + + foreach ($overrideParams as $key => $value) { + if (property_exists($this->requestConfig, $key)) { + $this->requestConfig->$key = $value; + } elseif (property_exists($this->config, $key)) { + $this->config->$key = $value; + } elseif ($key != 'enable_filter_excludelowpop') { + $this->config->custom_parameters[$key] = $value; + } + } + } + + /** + * Display a meaningful error message when any invalid parameter is being set. + * + * @param $overrideParams + * @throws + */ + public function throwWhenSettingNonOverridableParameter($overrideParams) + { + $nonOverridableParams = $this->getNonOverridableParams($overrideParams); + if(count($nonOverridableParams) > 0) { + throw new \Exception(sprintf( + "Setting parameters %s is not allowed. Please report this bug to the Piwik team.", + implode(" and ", $nonOverridableParams) + )); + } + } + + /** + * @param $overrideParams + * @return array + */ + public function getNonOverridableParams($overrideParams) + { + $paramsCannotBeOverridden = array(); + foreach ($overrideParams as $paramName => $paramValue) { + if (property_exists($this->requestConfig, $paramName)) { + $allowedParams = $this->requestConfig->overridableProperties; + } elseif (property_exists($this->config, $paramName)) { + $allowedParams = $this->config->overridableProperties; + } else { + // setting Config.custom_parameters is always allowed + continue; + } + + if (!in_array($paramName, $allowedParams)) { + $paramsCannotBeOverridden[] = $paramName; + } + } + return $paramsCannotBeOverridden; + } + } diff --git a/www/analytics/core/Plugin/Visualization.php b/www/analytics/core/Plugin/Visualization.php index a8ab8292..fcd88912 100644 --- a/www/analytics/core/Plugin/Visualization.php +++ b/www/analytics/core/Plugin/Visualization.php @@ -1,6 +1,6 @@ requestConfig->request_parameters_to_modify['date'] = $previousDate . ',' . $date; * } - * + * * // since we load the previous period's data too, we need to override the logic to * // check if there is data or not. * public function isThereDataToDisplay() * { * $tables = $this->dataTable->getDataTables() * $requestedDataTable = end($tables); - * + * * return $requestedDataTable->getRowsCount() != 0; * } * } - * + * * **Force properties to be set to certain values** - * + * * class MyVisualization extends Visualization * { * // ensure that some properties are set to certain values before rendering. @@ -133,9 +139,9 @@ class Visualization extends ViewDataTable { /** * The Twig template file to use when rendering, eg, `"@MyPlugin/_myVisualization.twig"`. - * + * * Must be defined by classes that extend Visualization. - * + * * @api */ const TEMPLATE_FILE = ''; @@ -143,8 +149,14 @@ class Visualization extends ViewDataTable private $templateVars = array(); private $reportLastUpdatedMessage = null; private $metadata = null; + protected $metricsFormatter = null; - final public function __construct($controllerAction, $apiMethodToRequestDataTable) + /** + * @var Report + */ + protected $report; + + final public function __construct($controllerAction, $apiMethodToRequestDataTable, $params = array()) { $templateFile = static::TEMPLATE_FILE; @@ -152,7 +164,11 @@ class Visualization extends ViewDataTable throw new \Exception('You have not defined a constant named TEMPLATE_FILE in your visualization class.'); } - parent::__construct($controllerAction, $apiMethodToRequestDataTable); + $this->metricsFormatter = new HtmlFormatter(); + + parent::__construct($controllerAction, $apiMethodToRequestDataTable, $params); + + $this->report = Report::factory($this->requestConfig->getApiModuleToRequest(), $this->requestConfig->getApiMethodToRequest()); } protected function buildView() @@ -160,26 +176,29 @@ class Visualization extends ViewDataTable $this->overrideSomeConfigPropertiesIfNeeded(); try { - $this->beforeLoadDataTable(); - - $this->loadDataTableFromAPI(array('disable_generic_filters' => 1)); + $this->loadDataTableFromAPI(); $this->postDataTableLoadedFromAPI(); $requestPropertiesAfterLoadDataTable = $this->requestConfig->getProperties(); $this->applyFilters(); + $this->addVisualizationInfoFromMetricMetadata(); $this->afterAllFiltersAreApplied(); $this->beforeRender(); $this->logMessageIfRequestPropertiesHaveChanged($requestPropertiesAfterLoadDataTable); - } catch (NoAccessException $e) { throw $e; } catch (\Exception $e) { - Log::warning("Failed to get data from API: " . $e->getMessage() . "\n" . $e->getTraceAsString()); + Log::error("Failed to get data from API: " . $e->getMessage() . "\n" . $e->getTraceAsString()); - $loadingError = array('message' => $e->getMessage()); + $message = $e->getMessage(); + if (\Piwik_ShouldPrintBackTraceWithMessage()) { + $message .= "\n" . $e->getTraceAsString(); + } + + $loadingError = array('message' => $message); } $view = new View("@CoreHome/_dataTable"); @@ -192,6 +211,7 @@ class Visualization extends ViewDataTable $view->visualization = $this; $view->visualizationTemplate = static::TEMPLATE_FILE; $view->visualizationCssClass = $this->getDefaultDataTableCssClass(); + $view->reportMetdadata = $this->getReportMetadata(); if (null === $this->dataTable) { $view->dataTable = null; @@ -216,17 +236,78 @@ class Visualization extends ViewDataTable return $view; } + /** + * @internal + */ + protected function loadDataTableFromAPI() + { + if (!is_null($this->dataTable)) { + // data table is already there + // this happens when setDataTable has been used + return $this->dataTable; + } + + // we build the request (URL) to call the API + $request = $this->buildApiRequestArray(); + + $module = $this->requestConfig->getApiModuleToRequest(); + $method = $this->requestConfig->getApiMethodToRequest(); + + PluginManager::getInstance()->checkIsPluginActivated($module); + + $class = ApiRequest::getClassNameAPI($module); + $dataTable = Proxy::getInstance()->call($class, $method, $request); + + $response = new ResponseBuilder($format = 'original', $request); + $response->disableSendHeader(); + $response->disableDataTablePostProcessor(); + + $this->dataTable = $response->getResponse($dataTable, $module, $method); + } + + private function getReportMetadata() + { + $request = $this->request->getRequestArray() + $_GET + $_POST; + + $idSite = Common::getRequestVar('idSite', null, 'string', $request); + $module = $this->requestConfig->getApiModuleToRequest(); + $action = $this->requestConfig->getApiMethodToRequest(); + + $apiParameters = array(); + $idDimension = Common::getRequestVar('idDimension', 0, 'int'); + $idGoal = Common::getRequestVar('idGoal', 0, 'int'); + if ($idDimension > 0) { + $apiParameters['idDimension'] = $idDimension; + } + if ($idGoal > 0) { + $apiParameters['idGoal'] = $idGoal; + } + + $metadata = ApiApi::getInstance()->getMetadata($idSite, $module, $action, $apiParameters); + + if (!empty($metadata)) { + return array_shift($metadata); + } + + return false; + } + private function overrideSomeConfigPropertiesIfNeeded() { if (empty($this->config->footer_icons)) { $this->config->footer_icons = ViewDataTableManager::configureFooterIcons($this); } - if (!\Piwik\Plugin\Manager::getInstance()->isPluginActivated('Goals')) { + if (!$this->isPluginActivated('Goals')) { $this->config->show_goals = false; } } + private function isPluginActivated($pluginName) + { + return PluginManager::getInstance()->isPluginActivated($pluginName); + } + /** * Assigns a template variable making it available in the Twig template specified by * {@link TEMPLATE_FILE}. @@ -248,9 +329,9 @@ class Visualization extends ViewDataTable /** * Returns `true` if there is data to display, `false` if otherwise. - * + * * Derived classes should override this method if they change the amount of data that is loaded. - * + * * @api */ protected function isThereDataToDisplay() @@ -281,7 +362,7 @@ class Visualization extends ViewDataTable } if (empty($this->requestConfig->filter_sort_column)) { - $this->requestConfig->setDefaultSort($this->config->columns_to_display, $hasNbUniqVisitors); + $this->requestConfig->setDefaultSort($this->config->columns_to_display, $hasNbUniqVisitors, $columns); } // deal w/ table metadata @@ -289,38 +370,78 @@ class Visualization extends ViewDataTable $this->metadata = $this->dataTable->getAllTableMetadata(); if (isset($this->metadata[DataTable::ARCHIVED_DATE_METADATA_NAME])) { - $this->config->report_last_updated_message = $this->makePrettyArchivedOnText(); + $this->reportLastUpdatedMessage = $this->makePrettyArchivedOnText(); + } + } + + $pivotBy = Common::getRequestVar('pivotBy', false) ?: $this->requestConfig->pivotBy; + if (empty($pivotBy) + && $this->dataTable instanceof DataTable + ) { + $this->config->disablePivotBySubtableIfTableHasNoSubtables($this->dataTable); + } + } + + private function addVisualizationInfoFromMetricMetadata() + { + $dataTable = $this->dataTable instanceof DataTable\Map ? $this->dataTable->getFirstRow() : $this->dataTable; + + $metrics = Report::getMetricsForTable($dataTable, $this->report); + + // TODO: instead of iterating & calling translate everywhere, maybe we can get all translated names in one place. + // may be difficult, though, since translated metrics are specific to the report. + foreach ($metrics as $metric) { + $name = $metric->getName(); + + if (empty($this->config->translations[$name])) { + $this->config->translations[$name] = $metric->getTranslatedName(); + } + + if (empty($this->config->metrics_documentation[$name])) { + $this->config->metrics_documentation[$name] = $metric->getDocumentation(); } } } private function applyFilters() { - list($priorityFilters, $otherFilters) = $this->config->getFiltersToRun(); + $postProcessor = $this->makeDataTablePostProcessor(); // must be created after requestConfig is final + $self = $this; - // First, filters that delete rows - foreach ($priorityFilters as $filter) { - $this->dataTable->filter($filter[0], $filter[1]); - } + $postProcessor->setCallbackBeforeGenericFilters(function (DataTable\DataTableInterface $dataTable) use ($self, $postProcessor) { - $this->beforeGenericFiltersAreAppliedToLoadedDataTable(); + $self->setDataTable($dataTable); - if (!$this->requestConfig->areGenericFiltersDisabled()) { - $this->applyGenericFilters(); - } + // First, filters that delete rows + foreach ($self->config->getPriorityFilters() as $filter) { + $dataTable->filter($filter[0], $filter[1]); + } - $this->afterGenericFiltersAreAppliedToLoadedDataTable(); + $self->beforeGenericFiltersAreAppliedToLoadedDataTable(); - // queue other filters so they can be applied later if queued filters are disabled - foreach ($otherFilters as $filter) { - $this->dataTable->queueFilter($filter[0], $filter[1]); - } + if (!in_array($self->requestConfig->filter_sort_column, $self->config->columns_to_display)) { + $hasNbUniqVisitors = in_array('nb_uniq_visitors', $self->config->columns_to_display); + $columns = $dataTable->getColumns(); + $self->requestConfig->setDefaultSort($self->config->columns_to_display, $hasNbUniqVisitors, $columns); + } - // Finally, apply datatable filters that were queued (should be 'presentation' filters that - // do not affect the number of rows) - if (!$this->requestConfig->areQueuedFiltersDisabled()) { - $this->dataTable->applyQueuedFilters(); - } + $postProcessor->setRequest($self->buildApiRequestArray()); + }); + + $postProcessor->setCallbackAfterGenericFilters(function (DataTable\DataTableInterface $dataTable) use ($self) { + + $self->setDataTable($dataTable); + + $self->afterGenericFiltersAreAppliedToLoadedDataTable(); + + // queue other filters so they can be applied later if queued filters are disabled + foreach ($self->config->getPresentationFilters() as $filter) { + $dataTable->queueFilter($filter[0], $filter[1]); + } + + }); + + $this->dataTable = $postProcessor->process($this->dataTable); } private function removeEmptyColumnsFromDisplay() @@ -355,16 +476,16 @@ class Visualization extends ViewDataTable $today = mktime(0, 0, 0); if ($date->getTimestamp() > $today) { - $elapsedSeconds = time() - $date->getTimestamp(); - $timeAgo = MetricsFormatter::getPrettyTimeFromSeconds($elapsedSeconds); + $timeAgo = $this->metricsFormatter->getPrettyTimeFromSeconds($elapsedSeconds); return Piwik::translate('CoreHome_ReportGeneratedXAgo', $timeAgo); } - $prettyDate = $date->getLocalized("%longYear%, %longMonth% %day%") . $date->toString('S'); + $prettyDate = $date->getLocalized(Date::DATE_FORMAT_SHORT); - return Piwik::translate('CoreHome_ReportGeneratedOn', $prettyDate); + $timezoneAppend = ' (UTC)'; + return Piwik::translate('CoreHome_ReportGeneratedOn', $prettyDate) . $timezoneAppend; } /** @@ -379,7 +500,7 @@ class Visualization extends ViewDataTable */ private function hasReportBeenPurged() { - if (!\Piwik\Plugin\Manager::getInstance()->isPluginActivated('PrivacyManager')) { + if (!$this->isPluginActivated('PrivacyManager')) { return false; } @@ -399,7 +520,7 @@ class Visualization extends ViewDataTable foreach ($this->config->clientSideProperties as $name) { if (property_exists($this->requestConfig, $name)) { $result[$name] = $this->getIntIfValueIsBool($this->requestConfig->$name); - } else if (property_exists($this->config, $name)) { + } elseif (property_exists($this->config, $name)) { $result[$name] = $this->getIntIfValueIsBool($this->config->$name); } } @@ -453,7 +574,7 @@ class Visualization extends ViewDataTable if (property_exists($this->requestConfig, $name)) { $valueToConvert = $this->requestConfig->$name; - } else if (property_exists($this->config, $name)) { + } elseif (property_exists($this->config, $name)) { $valueToConvert = $this->config->$name; } @@ -481,6 +602,7 @@ class Visualization extends ViewDataTable 'filter_excludelowpop', 'filter_excludelowpop_value', ); + foreach ($deleteFromJavascriptVariables as $name) { if (isset($javascriptVariablesToSet[$name])) { unset($javascriptVariablesToSet[$name]); @@ -497,9 +619,11 @@ class Visualization extends ViewDataTable /** * Hook that is called before loading report data from the API. - * + * * Use this method to change the request parameters that is sent to the API when requesting * data. + * + * @api */ public function beforeLoadDataTable() { @@ -507,9 +631,11 @@ class Visualization extends ViewDataTable /** * Hook that is executed before generic filters are applied. - * + * * Use this method if you need access to the entire dataset (since generic filters will * limit and truncate reports). + * + * @api */ public function beforeGenericFiltersAreAppliedToLoadedDataTable() { @@ -517,6 +643,8 @@ class Visualization extends ViewDataTable /** * Hook that is executed after generic filters are applied. + * + * @api */ public function afterGenericFiltersAreAppliedToLoadedDataTable() { @@ -525,6 +653,8 @@ class Visualization extends ViewDataTable /** * Hook that is executed after the report data is loaded and after all filters have been applied. * Use this method to format the report data before the view is rendered. + * + * @api */ public function afterAllFiltersAreApplied() { @@ -533,27 +663,24 @@ class Visualization extends ViewDataTable /** * Hook that is executed directly before rendering. Use this hook to force display properties to * be a certain value, despite changes from plugins and query parameters. + * + * @api */ public function beforeRender() { // eg $this->config->showFooterColumns = true; } - /** - * Second, generic filters (Sort, Limit, Replace Column Names, etc.) - */ - private function applyGenericFilters() + private function makeDataTablePostProcessor() { - $requestArray = $this->request->getRequestArray(); - $request = \Piwik\API\Request::getRequestArrayFromString($requestArray); + $request = $this->buildApiRequestArray(); + $module = $this->requestConfig->getApiModuleToRequest(); + $method = $this->requestConfig->getApiMethodToRequest(); - if (false === $this->config->enable_sort) { - $request['filter_sort_column'] = ''; - $request['filter_sort_order'] = ''; - } + $processor = new DataTablePostProcessor($module, $method, $request); + $processor->setFormatter($this->metricsFormatter); - $genericFilter = new \Piwik\API\DataTableGenericFilter($request); - $genericFilter->filter($this->dataTable); + return $processor; } private function logMessageIfRequestPropertiesHaveChanged(array $requestPropertiesBefore) @@ -563,6 +690,15 @@ class Visualization extends ViewDataTable $diff = array_diff_assoc($this->makeSureArrayContainsOnlyStrings($requestProperties), $this->makeSureArrayContainsOnlyStrings($requestPropertiesBefore)); + if (!empty($diff['filter_sort_column'])) { + // this here might be ok as it can be changed after data loaded but before filters applied + unset($diff['filter_sort_column']); + } + if (!empty($diff['filter_sort_order'])) { + // this here might be ok as it can be changed after data loaded but before filters applied + unset($diff['filter_sort_order']); + } + if (empty($diff)) { return; } @@ -592,4 +728,30 @@ class Visualization extends ViewDataTable return $result; } + + /** + * @internal + * + * @return array + */ + public function buildApiRequestArray() + { + $requestArray = $this->request->getRequestArray(); + $request = APIRequest::getRequestArrayFromString($requestArray); + + if (false === $this->config->enable_sort) { + $request['filter_sort_column'] = ''; + $request['filter_sort_order'] = ''; + } + + if (!array_key_exists('format_metrics', $request) || $request['format_metrics'] === 'bc') { + $request['format_metrics'] = '1'; + } + + if (!$this->requestConfig->disable_queued_filters && array_key_exists('disable_queued_filters', $request)) { + unset($request['disable_queued_filters']); + } + + return $request; + } } diff --git a/www/analytics/core/Plugin/Widgets.php b/www/analytics/core/Plugin/Widgets.php new file mode 100644 index 00000000..10566a9a --- /dev/null +++ b/www/analytics/core/Plugin/Widgets.php @@ -0,0 +1,198 @@ +category; + } + + private function getModule() + { + $className = get_class($this); + $className = explode('\\', $className); + + return $className[2]; + } + + /** + * Adds a widget. You can add a widget by calling this method and passing the name of the widget as well as a method + * name that will be executed to render the widget. The method can be defined either directly here in this widget + * class or in the controller in case you want to reuse the same action for instance in the menu etc. + * @api + */ + protected function addWidget($name, $method, $parameters = array()) + { + $this->addWidgetWithCustomCategory($this->category, $name, $method, $parameters); + } + + /** + * Adds a widget with a custom category. By default all widgets that you define in your class will be added under + * the same category which is defined in the {@link $category} property. Sometimes you may have a widget that + * belongs to a different category where this method comes handy. It does the same as {@link addWidget()} but + * allows you to define the category name as well. + * @api + */ + protected function addWidgetWithCustomCategory($category, $name, $method, $parameters = array()) + { + $this->checkIsValidWidget($name, $method); + + $this->widgets[] = array('category' => $category, + 'name' => $name, + 'params' => $parameters, + 'method' => $method, + 'module' => $this->getModule()); + } + + /** + * Here you can add one or multiple widgets. To do so call the method {@link addWidget()} or + * {@link addWidgetWithCustomCategory()}. + * @api + */ + protected function init() + { + } + + /** + * @ignore + */ + public function getWidgets() + { + $this->widgets = array(); + + $this->init(); + + return $this->widgets; + } + + /** + * Allows you to configure previously added widgets. + * For instance you can remove any widgets defined by any plugin by calling the + * {@link \Piwik\WidgetsList::remove()} method. + * + * @param WidgetsList $widgetsList + * @api + */ + public function configureWidgetsList(WidgetsList $widgetsList) + { + } + + /** + * @return \Piwik\Plugin\Widgets[] + * @ignore + */ + public static function getAllWidgets() + { + return PluginManager::getInstance()->findComponents('Widgets', 'Piwik\\Plugin\\Widgets'); + } + + /** + * @ignore + * @return Widgets|null + */ + public static function factory($module, $action) + { + if (empty($module) || empty($action)) { + return; + } + + $pluginManager = PluginManager::getInstance(); + + try { + if (!$pluginManager->isPluginActivated($module)) { + return; + } + + $plugin = $pluginManager->getLoadedPlugin($module); + } catch (\Exception $e) { + // we are not allowed to use possible widgets, plugin is not active + return; + } + + /** @var Widgets $widgetContainer */ + $widgetContainer = $plugin->findComponent('Widgets', 'Piwik\\Plugin\\Widgets'); + + if (empty($widgetContainer)) { + // plugin does not define any widgets, we cannot do anything + return; + } + + if (!is_callable(array($widgetContainer, $action))) { + // widget does not implement such a method, we cannot do anything + return; + } + + // the widget class implements such an action, but we have to check whether it is actually exposed and whether + // it was maybe disabled by another plugin, this is only possible by checking the widgetslist, unfortunately + if (!WidgetsList::isDefined($module, $action)) { + return; + } + + return $widgetContainer; + } + + private function checkIsValidWidget($name, $method) + { + if (!Development::isEnabled()) { + return; + } + + if (empty($name)) { + Development::error('No name is defined for added widget having method "' . $method . '" in ' . get_class($this)); + } + + if (Development::isCallableMethod($this, $method)) { + return; + } + + $controllerClass = 'Piwik\\Plugins\\' . $this->getModule() . '\\Controller'; + + if (!Development::methodExists($this, $method) && + !Development::methodExists($controllerClass, $method)) { + Development::error('The added method "' . $method . '" neither exists in "' . get_class($this) . '" nor "' . $controllerClass . '". Make sure to define such a method.'); + } + + $definedInClass = get_class($this); + + if (Development::methodExists($controllerClass, $method)) { + if (Development::isCallableMethod($controllerClass, $method)) { + return; + } + + $definedInClass = $controllerClass; + } + + Development::error('The method "' . $method . '" is not callable on "' . $definedInClass . '". Make sure the method is public.'); + } +} diff --git a/www/analytics/core/PluginDeactivatedException.php b/www/analytics/core/PluginDeactivatedException.php new file mode 100644 index 00000000..fe426676 --- /dev/null +++ b/www/analytics/core/PluginDeactivatedException.php @@ -0,0 +1,20 @@ + Plugins page in Piwik."); + } +} diff --git a/www/analytics/core/Profiler.php b/www/analytics/core/Profiler.php index ae9c5e8b..12c1e46f 100644 --- a/www/analytics/core/Profiler.php +++ b/www/analytics/core/Profiler.php @@ -1,6 +1,6 @@ getProfiler(); if (!$profiler->getEnabled()) { - throw new \Exception("To display the profiler you should enable enable_sql_profiler on your config/config.ini.php file"); + // To display the profiler you should enable enable_sql_profiler on your config/config.ini.php file + return; } $infoIndexedByQuery = array(); @@ -93,7 +103,7 @@ class Profiler return $a['sum_time_ms'] < $b['sum_time_ms']; } - static private function sortTimeDesc($a, $b) + private static function sortTimeDesc($a, $b) { return $a['sumTimeMs'] < $b['sumTimeMs']; } @@ -133,7 +143,9 @@ class Profiler { $totalTime = self::getDbElapsedSecs(); $queryCount = Profiler::getQueryCount(); - Log::debug(sprintf("Total queries = %d (total sql time = %.2fs)", $queryCount, $totalTime)); + if ($queryCount > 0) { + Log::debug(sprintf("Total queries = %d (total sql time = %.2fs)", $queryCount, $totalTime)); + } } /** @@ -163,7 +175,7 @@ class Profiler * * @param array $infoIndexedByQuery */ - static private function getSqlProfilingQueryBreakdownOutput($infoIndexedByQuery) + private static function getSqlProfilingQueryBreakdownOutput($infoIndexedByQuery) { $output = '
Breakdown by query
'; foreach ($infoIndexedByQuery as $query => $queryInfo) { @@ -184,97 +196,139 @@ class Profiler * Initializes Profiling via XHProf. * See: https://github.com/piwik/piwik/blob/master/tests/README.xhprof.md */ - public static function setupProfilerXHProf($mainRun = false) + public static function setupProfilerXHProf($mainRun = false, $setupDuringTracking = false) { - if(SettingsServer::isTrackerApiRequest()) { + if (!$setupDuringTracking + && SettingsServer::isTrackerApiRequest() + ) { // do not profile Tracker return; } - $path = PIWIK_INCLUDE_PATH . '/tests/lib/xhprof-0.9.4/xhprof_lib/utils/xhprof_runs.php'; - - if(!file_exists($path)) { + if (self::$isXhprofSetup) { return; } - if(!function_exists('xhprof_enable')) { - return; + if (!function_exists('xhprof_enable')) { + $xhProfPath = PIWIK_INCLUDE_PATH . '/vendor/facebook/xhprof/extension/modules/xhprof.so'; + throw new Exception("Cannot find xhprof_enable, make sure to 1) install xhprof: run 'composer install --dev' and build the extension, and 2) add 'extension=$xhProfPath' to your php.ini."); } - if(!is_writable(ini_get("xhprof.output_dir"))) { - throw new \Exception("The profiler output dir '" .ini_get("xhprof.output_dir"). "' should exist and be writable."); + $outputDir = ini_get("xhprof.output_dir"); + if (empty($outputDir)) { + throw new Exception("The profiler output dir is not set. Add 'xhprof.output_dir=...' to your php.ini."); + } + if (!is_writable($outputDir)) { + throw new Exception("The profiler output dir '" . ini_get("xhprof.output_dir") . "' should exist and be writable."); } - require_once $path; - require_once PIWIK_INCLUDE_PATH . '/tests/lib/xhprof-0.9.4/xhprof_lib/utils/xhprof_lib.php'; - if(!function_exists('xhprof_error')) { - function xhprof_error($out) { + if (!function_exists('xhprof_error')) { + function xhprof_error($out) + { echo substr($out, 0, 300) . '...'; } } $currentGitBranch = SettingsPiwik::getCurrentGitBranch(); $profilerNamespace = "piwik"; - if($currentGitBranch != 'master') { - $profilerNamespace .= "." . $currentGitBranch; + if ($currentGitBranch != 'master') { + $profilerNamespace .= "-" . $currentGitBranch; + } + + if ($mainRun) { + self::setProfilingRunIds(array()); } xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY); - if($mainRun) { - self::setProfilingRunIds(array()); - } - - register_shutdown_function(function () use($profilerNamespace, $mainRun) { + register_shutdown_function(function () use ($profilerNamespace, $mainRun) { $xhprofData = xhprof_disable(); - $xhprofRuns = new \XHProfRuns_Default(); + $xhprofRuns = new XHProfRuns_Default(); $runId = $xhprofRuns->save_run($xhprofData, $profilerNamespace); - if(empty($runId)) { + if (empty($runId)) { die('could not write profiler run'); } - $runs = self::getProfilingRunIds(); - $runs[] = $runId; -// $weights = array_fill(0, count($runs), 1); -// $aggregate = xhprof_aggregate_runs($xhprofRuns, $runs, $weights, $profilerNamespace); -// $runId = $xhprofRuns->save_run($aggregate, $profilerNamespace); - if($mainRun) { - $runIds = implode(',', $runs); + $runs = Profiler::getProfilingRunIds(); + array_unshift($runs, $runId); + + if ($mainRun) { + Profiler::aggregateXhprofRuns($runs, $profilerNamespace, $saveTo = $runId); + + $baseUrlStored = SettingsPiwik::getPiwikUrl(); + $out = "\n\n"; $baseUrl = "http://" . @$_SERVER['HTTP_HOST'] . "/" . @$_SERVER['REQUEST_URI']; - $baseUrlStored = SettingsPiwik::getPiwikUrl(); - if(strlen($baseUrlStored) > strlen($baseUrl)) { + if (strlen($baseUrlStored) > strlen($baseUrl)) { $baseUrl = $baseUrlStored; } - $baseUrl = "\n" . $baseUrl - ."tests/lib/xhprof-0.9.4/xhprof_html/?source=$profilerNamespace&run="; + $baseUrl = $baseUrlStored . "vendor/facebook/xhprof/xhprof_html/?source=$profilerNamespace&run=$runId"; - $out .= "Profiler report is available at:"; - $out .= $baseUrl . $runId; - if($runId != $runIds) { - $out .= "\n\nProfiler Report aggregating all runs triggered from this process: "; - $out .= $baseUrl . $runIds; - } + $out .= "Profiler report is available at:\n"; + $out .= "$baseUrl"; $out .= "\n\n"; - echo ($out); + + if (Development::isEnabled()) { + $out .= "WARNING: Development mode is enabled. Many runtime optimizations are not applied in development mode. "; + $out .= "Unless you intend to profile Piwik in development mode, your profile may not be accurate."; + $out .= "\n\n"; + } + + echo $out; } else { - self::setProfilingRunIds($runs); + Profiler::setProfilingRunIds($runs); } }); + + self::$isXhprofSetup = true; } - private static function setProfilingRunIds($ids) + /** + * Aggregates xhprof runs w/o normalizing (xhprof_aggregate_runs will always average data which + * does not fit Piwik's use case). + */ + public static function aggregateXhprofRuns($runIds, $profilerNamespace, $saveToRunId) { - file_put_contents( self::getPathToXHProfRunIds(), json_encode($ids) ); + $xhprofRuns = new XHProfRuns_Default(); + + $aggregatedData = array(); + + foreach ($runIds as $runId) { + $xhprofRunData = $xhprofRuns->get_run($runId, $profilerNamespace, $description); + + foreach ($xhprofRunData as $key => $data) { + if (empty($aggregatedData[$key])) { + $aggregatedData[$key] = $data; + } else { + // don't aggregate main() metrics since only the super run has the correct metrics for the entire run + if ($key == "main()") { + continue; + } + + $aggregatedData[$key]["ct"] += $data["ct"]; // call count + $aggregatedData[$key]["wt"] += $data["wt"]; // incl. wall time + $aggregatedData[$key]["cpu"] += $data["cpu"]; // cpu time + $aggregatedData[$key]["mu"] += $data["mu"]; // memory usage + $aggregatedData[$key]["pmu"] = max($aggregatedData[$key]["pmu"], $data["pmu"]); // peak mem usage + } + } + } + + $xhprofRuns->save_run($aggregatedData, $profilerNamespace, $saveToRunId); + } + + public static function setProfilingRunIds($ids) + { + file_put_contents(self::getPathToXHProfRunIds(), json_encode($ids)); @chmod(self::getPathToXHProfRunIds(), 0777); } - private static function getProfilingRunIds() + public static function getProfilingRunIds() { - $runIds = file_get_contents( self::getPathToXHProfRunIds() ); + $runIds = file_get_contents(self::getPathToXHProfRunIds()); $array = json_decode($runIds, $assoc = true); - if(!is_array($array)) { + if (!is_array($array)) { $array = array(); } return $array; diff --git a/www/analytics/core/ProxyHeaders.php b/www/analytics/core/ProxyHeaders.php index 4a6fa3fd..3cefeb23 100644 --- a/www/analytics/core/ProxyHeaders.php +++ b/www/analytics/core/ProxyHeaders.php @@ -1,6 +1,6 @@ getAssetDirectory() . '/' . basename($file); + // Return 304 if the file has not modified since + if ($modifiedSince === $lastModified) { + Common::sendResponseCode(304); + return; + } - $phpOutputCompressionEnabled = ProxyHttp::isPhpOutputCompressed(); - if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) && !$phpOutputCompressionEnabled) { - $acceptEncoding = $_SERVER['HTTP_ACCEPT_ENCODING']; + // if we have to serve the file, serve it now, either in the clear or compressed + if ($byteStart === false) { + $byteStart = 0; + } - if (extension_loaded('zlib') && function_exists('file_get_contents') && function_exists('file_put_contents')) { - if (preg_match('/(?:^|, ?)(deflate)(?:,|$)/', $acceptEncoding, $matches)) { - $encoding = 'deflate'; - $filegz = $compressedFileLocation . '.deflate'; - } else if (preg_match('/(?:^|, ?)((x-)?gzip)(?:,|$)/', $acceptEncoding, $matches)) { - $encoding = $matches[1]; - $filegz = $compressedFileLocation . '.gz'; - } + if ($byteEnd === false) { + $byteEnd = filesize($file); + } - if (!empty($encoding)) { - // compress-on-demand and use cache - if (!file_exists($filegz) || ($fileModifiedTime > @filemtime($filegz))) { - $data = file_get_contents($file); + $compressed = false; + $encoding = ''; + $compressedFileLocation = AssetManager::getInstance()->getAssetDirectory() . '/' . basename($file); - if ($encoding == 'deflate') { - $data = gzdeflate($data, 9); - } else if ($encoding == 'gzip' || $encoding == 'x-gzip') { - $data = gzencode($data, 9); - } + if (!($byteStart == 0 + && $byteEnd == filesize($file)) + ) { + $compressedFileLocation .= ".$byteStart.$byteEnd"; + } - file_put_contents($filegz, $data); - } + $phpOutputCompressionEnabled = self::isPhpOutputCompressed(); + if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) && !$phpOutputCompressionEnabled) { + list($encoding, $extension) = self::getCompressionEncodingAcceptedByClient(); + $filegz = $compressedFileLocation . $extension; - $compressed = true; - $file = $filegz; - } - } else { - // manually compressed - $filegz = $compressedFileLocation . '.gz'; - if (preg_match('/(?:^|, ?)((x-)?gzip)(?:,|$)/', $acceptEncoding, $matches) && file_exists($filegz) && ($fileModifiedTime < @filemtime($filegz))) { - $encoding = $matches[1]; - $compressed = true; - $file = $filegz; - } + if (self::canCompressInPhp()) { + if (!empty($encoding)) { + // compress the file if it doesn't exist or is newer than the existing cached file, and cache + // the compressed result + if (self::shouldCompressFile($file, $filegz)) { + self::compressFile($file, $filegz, $encoding, $byteStart, $byteEnd); } + + $compressed = true; + $file = $filegz; + + $byteStart = 0; + $byteEnd = filesize($file); } + } else { + // if a compressed file exists, the file was manually compressed so we just serve that + if ($extension == '.gz' + && !self::shouldCompressFile($file, $filegz) + ) { + $compressed = true; + $file = $filegz; - @header('Last-Modified: ' . $lastModified); - - if (!$phpOutputCompressionEnabled) { - @header('Content-Length: ' . filesize($file)); - } - - if (!empty($contentType)) { - @header('Content-Type: ' . $contentType); - } - - if ($compressed) { - @header('Content-Encoding: ' . $encoding); - } - - if (!_readfile($file)) { - self::setHttpStatus('505 Internal server error'); + $byteStart = 0; + $byteEnd = filesize($file); } } - } else { - self::setHttpStatus('404 Not Found'); + } + + Common::sendHeader('Last-Modified: ' . $lastModified); + + if (!$phpOutputCompressionEnabled) { + Common::sendHeader('Content-Length: ' . ($byteEnd - $byteStart)); + } + + if (!empty($contentType)) { + Common::sendHeader('Content-Type: ' . $contentType); + } + + if ($compressed) { + Common::sendHeader('Content-Encoding: ' . $encoding); + } + + if (!_readfile($file, $byteStart, $byteEnd)) { + Common::sendResponseCode(500); } } @@ -185,7 +193,6 @@ class ProxyHttp !empty($autoAppendFile); } - /** * Workaround IE bug when downloading certain document types over SSL and * cache control headers are present, e.g., @@ -202,31 +209,63 @@ class ProxyHttp public static function overrideCacheControlHeaders($override = null) { if ($override || self::isHttps()) { - @header('Pragma: '); - @header('Expires: '); + Common::sendHeader('Pragma: '); + Common::sendHeader('Expires: '); if (in_array($override, array('public', 'private', 'no-cache', 'no-store'))) { - @header("Cache-Control: $override, must-revalidate"); + Common::sendHeader("Cache-Control: $override, must-revalidate"); } else { - @header('Cache-Control: must-revalidate'); + Common::sendHeader('Cache-Control: must-revalidate'); } } } - /** - * Set response header, e.g., HTTP/1.0 200 Ok - * - * @param string $status Status - * @return bool + * Returns a formatted Expires HTTP header for a certain number of days in the future. The result + * can be used in a call to `header()`. */ - protected static function setHttpStatus($status) + private static function getExpiresHeaderForFutureDay($expireFarFutureDays) { - if (substr_compare(PHP_SAPI, '-fcgi', -5)) { - @header($_SERVER['SERVER_PROTOCOL'] . ' ' . $status); + return "Expires: " . gmdate('D, d M Y H:i:s', time() + 86400 * (int)$expireFarFutureDays) . ' GMT'; + } + + private static function getCompressionEncodingAcceptedByClient() + { + $acceptEncoding = $_SERVER['HTTP_ACCEPT_ENCODING']; + + if (preg_match(self::DEFLATE_ENCODING_REGEX, $acceptEncoding, $matches)) { + return array('deflate', '.deflate'); + } elseif (preg_match(self::GZIP_ENCODING_REGEX, $acceptEncoding, $matches)) { + return array('gzip', '.gz'); } else { - // FastCGI - @header('Status: ' . $status); + return array(false, false); } } + private static function canCompressInPhp() + { + return extension_loaded('zlib') && function_exists('file_get_contents') && function_exists('file_put_contents'); + } + + private static function shouldCompressFile($fileToCompress, $compressedFilePath) + { + $toCompressLastModified = @filemtime($fileToCompress); + $compressedLastModified = @filemtime($compressedFilePath); + + return !file_exists($compressedFilePath) || ($toCompressLastModified > $compressedLastModified); + } + + private static function compressFile($fileToCompress, $compressedFilePath, $compressionEncoding, $byteStart, + $byteEnd) + { + $data = file_get_contents($fileToCompress); + $data = substr($data, $byteStart, $byteEnd - $byteStart); + + if ($compressionEncoding == 'deflate') { + $data = gzdeflate($data, 9); + } elseif ($compressionEncoding == 'gzip' || $compressionEncoding == 'x-gzip') { + $data = gzencode($data, 9); + } + + file_put_contents($compressedFilePath, $data); + } } diff --git a/www/analytics/core/QuickForm2.php b/www/analytics/core/QuickForm2.php index dc2ba9ab..49b21672 100644 --- a/www/analytics/core/QuickForm2.php +++ b/www/analytics/core/QuickForm2.php @@ -1,6 +1,6 @@ _elements as $key => $value) { if ($value->_attributes['name'] == $nameElement) { @@ -86,7 +86,7 @@ abstract class QuickForm2 extends HTML_QuickForm2 } } - function setSelected($nameElement, $value) + public function setSelected($nameElement, $value) { foreach ($this->_elements as $key => $value) { if ($value->_attributes['name'] == $nameElement) { @@ -101,7 +101,7 @@ abstract class QuickForm2 extends HTML_QuickForm2 * @param string $elementName * @return mixed */ - function getSubmitValue($elementName) + public function getSubmitValue($elementName) { $value = $this->getValue(); return isset($value[$elementName]) ? $value[$elementName] : null; @@ -118,7 +118,7 @@ abstract class QuickForm2 extends HTML_QuickForm2 return array_filter($messages); } - static protected $registered = false; + protected static $registered = false; /** * Returns the rendered form as an array. diff --git a/www/analytics/core/RankingQuery.php b/www/analytics/core/RankingQuery.php index de05ab39..cd4f8306 100644 --- a/www/analytics/core/RankingQuery.php +++ b/www/analytics/core/RankingQuery.php @@ -1,6 +1,6 @@ generateQuery($innerQuery); - $data = Db::fetchAll($query, $bind); + $query = $this->generateRankingQuery($innerQuery); + $data = Db::fetchAll($query, $bind); if ($this->columnToMarkExcludedRows !== false) { // split the result into the regular result and the rows with special treatment @@ -268,7 +268,7 @@ class RankingQuery * itself. * @return string The entire ranking query SQL. */ - public function generateQuery($innerQuery) + public function generateRankingQuery($innerQuery) { // +1 to include "Others" $limit = $this->limit + 1; diff --git a/www/analytics/core/Registry.php b/www/analytics/core/Registry.php index 166a48f7..bc80f225 100644 --- a/www/analytics/core/Registry.php +++ b/www/analytics/core/Registry.php @@ -1,27 +1,24 @@ data = array(); - } - public static function isRegistered($key) { return self::getInstance()->hasKey($key); @@ -39,19 +36,28 @@ class Registry extends Singleton public function setKey($key, $value) { - $this->data[$key] = $value; + if ($key === 'auth') { + $key = 'Piwik\Auth'; + } + + StaticContainer::getContainer()->set($key, $value); } public function getKey($key) { - if (!$this->hasKey($key)) { - throw new \Exception(sprintf("Key '%s' doesn't exist in Registry", $key)); + if ($key === 'auth') { + $key = 'Piwik\Auth'; } - return $this->data[$key]; + + return StaticContainer::get($key); } public function hasKey($key) { - return array_key_exists($key, $this->data); + if ($key === 'auth') { + $key = 'Piwik\Auth'; + } + + return StaticContainer::getContainer()->has($key); } } diff --git a/www/analytics/core/ReportRenderer.php b/www/analytics/core/ReportRenderer.php index 4634f919..3fa20aff 100644 --- a/www/analytics/core/ReportRenderer.php +++ b/www/analytics/core/ReportRenderer.php @@ -1,6 +1,6 @@ render($processedReport); - if(empty($reportData)) { + if (empty($reportData)) { $reportData = Piwik::translate('CoreHome_ThereIsNoDataForThisReport'); } @@ -148,4 +158,17 @@ class Csv extends ReportRenderer { return str_replace("_", ".", $uniqueId); } + + /** + * Get report attachments, ex. graph images + * + * @param $report + * @param $processedReports + * @param $prettyDate + * @return array + */ + public function getAttachments($report, $processedReports, $prettyDate) + { + return array(); + } } diff --git a/www/analytics/core/ReportRenderer/Html.php b/www/analytics/core/ReportRenderer/Html.php index 53fbe90a..9915f255 100644 --- a/www/analytics/core/ReportRenderer/Html.php +++ b/www/analytics/core/ReportRenderer/Html.php @@ -1,6 +1,6 @@ assign("reportFontFamily", ReportRenderer::DEFAULT_REPORT_FONT_FAMILY); $view->assign("reportTitleTextColor", ReportRenderer::REPORT_TITLE_TEXT_COLOR); $view->assign("reportTitleTextSize", self::REPORT_TITLE_TEXT_SIZE); $view->assign("reportTextColor", ReportRenderer::REPORT_TEXT_COLOR); @@ -113,7 +115,9 @@ class Html extends ReportRenderer $view->assign("tableHeaderTextColor", ReportRenderer::TABLE_HEADER_TEXT_COLOR); $view->assign("tableCellBorderColor", ReportRenderer::TABLE_CELL_BORDER_COLOR); $view->assign("tableBgColor", ReportRenderer::TABLE_BG_COLOR); + $view->assign("reportTableHeaderTextWeight", self::TABLE_HEADER_TEXT_WEIGHT); $view->assign("reportTableHeaderTextSize", self::REPORT_TABLE_HEADER_TEXT_SIZE); + $view->assign("reportTableHeaderTextTransform", ReportRenderer::TABLE_HEADER_TEXT_TRANSFORM); $view->assign("reportTableRowTextSize", self::REPORT_TABLE_ROW_TEXT_SIZE); $view->assign("reportBackToTopTextSize", self::REPORT_BACK_TO_TOP_TEXT_SIZE); $view->assign("currentPath", SettingsPiwik::getPiwikUrl()); @@ -161,4 +165,56 @@ class Html extends ReportRenderer $this->rendering .= $reportView->render(); } + + public function getAttachments($report, $processedReports, $prettyDate) + { + $additionalFiles = array(); + + foreach ($processedReports as $processedReport) { + if ($processedReport['displayGraph']) { + $additionalFiles[] = $this->getAttachment($report, $processedReport, $prettyDate); + } + } + + return $additionalFiles; + } + + protected function getAttachment($report, $processedReport, $prettyDate) + { + $additionalFile = array(); + + $segment = \Piwik\Plugins\ScheduledReports\API::getSegment($report['idsegment']); + + $segmentName = $segment != null ? sprintf(' (%s)', $segment['name']) : ''; + + $processedReportMetadata = $processedReport['metadata']; + + $additionalFile['filename'] = + sprintf( + '%s - %s - %d - %s %d%s.png', + $processedReportMetadata['name'], + $prettyDate, + $report['idsite'], + Piwik::translate('General_Report'), + $report['idreport'], + $segmentName + ); + + $additionalFile['cid'] = $processedReportMetadata['uniqueId']; + + $additionalFile['content'] = + ReportRenderer::getStaticGraph( + $processedReportMetadata, + Html::IMAGE_GRAPH_WIDTH, + Html::IMAGE_GRAPH_HEIGHT, + $processedReport['evolutionGraph'], + $segment + ); + + $additionalFile['mimeType'] = 'image/png'; + + $additionalFile['encoding'] = \Zend_Mime::ENCODING_BASE64; + + return $additionalFile; + } } diff --git a/www/analytics/core/ReportRenderer/Pdf.php b/www/analytics/core/ReportRenderer/Pdf.php index 66f47df2..59fb8626 100644 --- a/www/analytics/core/ReportRenderer/Pdf.php +++ b/www/analytics/core/ReportRenderer/Pdf.php @@ -1,6 +1,6 @@ reportFont = $reportFont; } public function sendToDisk($filename) { - $filename = ReportRenderer::appendExtension($filename, self::PDF_CONTENT_TYPE); + $filename = ReportRenderer::makeFilenameWithExtension($filename, self::PDF_CONTENT_TYPE); $outputFilename = ReportRenderer::getOutputPath($filename); $this->TCPDF->Output($outputFilename, 'F'); @@ -133,13 +151,13 @@ class Pdf extends ReportRenderer public function sendToBrowserDownload($filename) { - $filename = ReportRenderer::appendExtension($filename, self::PDF_CONTENT_TYPE); + $filename = ReportRenderer::makeFilenameWithExtension($filename, self::PDF_CONTENT_TYPE); $this->TCPDF->Output($filename, 'D'); } public function sendToBrowserInline($filename) { - $filename = ReportRenderer::appendExtension($filename, self::PDF_CONTENT_TYPE); + $filename = ReportRenderer::makeFilenameWithExtension($filename, self::PDF_CONTENT_TYPE); $this->TCPDF->Output($filename, 'I'); } @@ -185,7 +203,6 @@ class Pdf extends ReportRenderer // segment if ($segment != null) { - $this->TCPDF->Ln(); $this->TCPDF->Ln(); $this->TCPDF->SetFont($this->reportFont, '', $this->reportHeaderFontSize - 2); @@ -328,7 +345,7 @@ class Pdf extends ReportRenderer $this->TCPDF->SetTextColor($this->reportTextColor[0], $this->reportTextColor[1], $this->reportTextColor[2]); $this->TCPDF->SetFont(''); - $fill = false; + $fill = true; $url = false; $leftSpacesBeforeLogo = str_repeat(' ', $this->leftSpacesBeforeLogo); @@ -389,7 +406,7 @@ class Pdf extends ReportRenderer if (empty($rowMetrics[$columnId])) { $rowMetrics[$columnId] = 0; } - $this->TCPDF->Cell($this->cellWidth, $this->cellHeight, $rowMetrics[$columnId], 'LR', 0, 'L', $fill); + $this->TCPDF->Cell($this->cellWidth, $this->cellHeight, NumberFormatter::getInstance()->format($rowMetrics[$columnId]), 'LR', 0, 'L', $fill); } } @@ -461,7 +478,7 @@ class Pdf extends ReportRenderer && $columnsCount <= 3 ) { $totalWidth = $this->reportWidthPortrait * 2 / 3; - } else if ($this->orientation == self::LANDSCAPE) { + } elseif ($this->orientation == self::LANDSCAPE) { $totalWidth = $this->reportWidthLandscape; } else { $totalWidth = $this->reportWidthPortrait; @@ -494,12 +511,13 @@ class Pdf extends ReportRenderer $posX = $initPosX; foreach ($this->reportColumns as $columnName) { $columnName = $this->formatText($columnName); + //Label column if ($countColumns == 0) { - $this->TCPDF->MultiCell($this->labelCellWidth, $maxCellHeight, $columnName, 1, 'C', true); + $this->TCPDF->MultiCell($this->labelCellWidth, $maxCellHeight, $columnName, $border = 0, $align = 'L', true); $this->TCPDF->SetXY($posX + $this->labelCellWidth, $posY); } else { - $this->TCPDF->MultiCell($this->cellWidth, $maxCellHeight, $columnName, 1, 'C', true); + $this->TCPDF->MultiCell($this->cellWidth, $maxCellHeight, $columnName, $border = 0, $align = 'L', true); $this->TCPDF->SetXY($posX + $this->cellWidth, $posY); } $countColumns++; @@ -523,4 +541,17 @@ class Pdf extends ReportRenderer $this->TCPDF->Write("1em", $message); $this->TCPDF->Ln(); } + + /** + * Get report attachments, ex. graph images + * + * @param $report + * @param $processedReports + * @param $prettyDate + * @return array + */ + public function getAttachments($report, $processedReports, $prettyDate) + { + return array(); + } } diff --git a/www/analytics/core/ScheduledTask.php b/www/analytics/core/ScheduledTask.php index f126cecb..dd4063ef 100644 --- a/www/analytics/core/ScheduledTask.php +++ b/www/analytics/core/ScheduledTask.php @@ -1,6 +1,6 @@ className = $this->getClassNameFromInstance($objectInstance); - - if ($priority < self::HIGHEST_PRIORITY || $priority > self::LOWEST_PRIORITY) { - throw new Exception("Invalid priority for ScheduledTask '$this->className.$methodName': $priority"); - } - - $this->objectInstance = $objectInstance; - $this->methodName = $methodName; - $this->scheduledTime = $scheduledTime; - $this->methodParameter = $methodParameter; - $this->priority = $priority; - } - - protected function getClassNameFromInstance($_objectInstance) - { - if (is_string($_objectInstance)) { - return $_objectInstance; - } - - $namespaced = get_class($_objectInstance); - $class = explode('\\', $namespaced); - return end($class); - } - - /** - * Returns the object instance that contains the method to execute. Returns a class - * name if the method is static. - * - * @return mixed - */ - public function getObjectInstance() - { - return $this->objectInstance; - } - - /** - * Returns the name of the class that contains the method to execute. - * - * @return string - */ - public function getClassName() - { - return $this->className; - } - - /** - * Returns the name of the method that will be executed. - * - * @return string - */ - public function getMethodName() - { - return $this->methodName; - } - - /** - * Returns the value that will be passed to the method when executed, or `null` if - * no value will be supplied. - * - * @return string|null - */ - public function getMethodParameter() - { - return $this->methodParameter; - } - - /** - * Returns a {@link ScheduledTime} instance that describes when the method should be executed - * and how long before the next execution. - * - * @return ScheduledTime - */ - public function getScheduledTime() - { - return $this->scheduledTime; - } - - /** - * Returns the time in milliseconds when this task will be executed next. - * - * @return int - */ - public function getRescheduledTime() - { - return $this->getScheduledTime()->getRescheduledTime(); - } - - /** - * Returns the task priority. The priority will be an integer whose value is - * between {@link HIGH_PRIORITY} and {@link LOW_PRIORITY}. - * - * @return int - */ - public function getPriority() - { - return $this->priority; - } - - /** - * Returns a unique name for this scheduled task. The name is stored in the DB and is used - * to store a task's previous execution time. The name is created using: - * - * - the name of the class that contains the method to execute, - * - the name of the method to regularly execute, - * - and the value that is passed to the executed task. - * - * @return string - */ - public function getName() - { - return self::getTaskName($this->getClassName(), $this->getMethodName(), $this->getMethodParameter()); - } - - /** - * @ignore - */ - public static function getTaskName($className, $methodName, $methodParameter = null) - { - return $className . '.' . $methodName . ($methodParameter == null ? '' : '_' . $methodParameter); - } } diff --git a/www/analytics/core/ScheduledTaskTimetable.php b/www/analytics/core/ScheduledTaskTimetable.php deleted file mode 100644 index 13e48b8c..00000000 --- a/www/analytics/core/ScheduledTaskTimetable.php +++ /dev/null @@ -1,121 +0,0 @@ -timetable = $unserializedTimetable === false ? array() : $unserializedTimetable; - } - - public function getTimetable() - { - return $this->timetable; - } - - public function setTimetable($timetable) - { - $this->timetable = $timetable; - } - - public function removeInactiveTasks($activeTasks) - { - $activeTaskNames = array(); - foreach ($activeTasks as $task) { - $activeTaskNames[] = $task->getName(); - } - foreach (array_keys($this->timetable) as $taskName) { - if (!in_array($taskName, $activeTaskNames)) { - unset($this->timetable[$taskName]); - } - } - } - - public function getScheduledTaskNames() - { - return array_keys($this->timetable); - } - - public function getScheduledTaskTime($taskName) - { - return isset($this->timetable[$taskName]) ? Date::factory($this->timetable[$taskName]) : false; - } - - /** - * Checks if the task should be executed - * - * Task has to be executed if : - * - the task has already been scheduled once and the current system time is greater than the scheduled time. - * - execution is forced, see $forceTaskExecution - * - * @param string $taskName - * - * @return boolean - */ - public function shouldExecuteTask($taskName) - { - $forceTaskExecution = - (isset($GLOBALS['PIWIK_TRACKER_DEBUG_FORCE_SCHEDULED_TASKS']) && $GLOBALS['PIWIK_TRACKER_DEBUG_FORCE_SCHEDULED_TASKS']) - || DEBUG_FORCE_SCHEDULED_TASKS; - - return $forceTaskExecution || ($this->taskHasBeenScheduledOnce($taskName) && time() >= $this->timetable[$taskName]); - } - - /** - * Checks if a task should be rescheduled - * - * Task has to be rescheduled if : - * - the task has to be executed - * - the task has never been scheduled before - * - * @param string $taskName - * - * @return boolean - */ - public function taskShouldBeRescheduled($taskName) - { - return !$this->taskHasBeenScheduledOnce($taskName) || $this->shouldExecuteTask($taskName); - } - - public function rescheduleTask($task) - { - // update the scheduled time - $this->timetable[$task->getName()] = $task->getRescheduledTime(); - $this->save(); - } - - public function save() - { - Option::set(self::TIMETABLE_OPTION_STRING, serialize($this->timetable)); - } - - public function getScheduledTimeForMethod($className, $methodName, $methodParameter = null) - { - $taskName = ScheduledTask::getTaskName($className, $methodName, $methodParameter); - - return $this->taskHasBeenScheduledOnce($taskName) ? $this->timetable[$taskName] : false; - } - - public function taskHasBeenScheduledOnce($taskName) - { - return isset($this->timetable[$taskName]); - } -} diff --git a/www/analytics/core/ScheduledTime.php b/www/analytics/core/ScheduledTime.php deleted file mode 100644 index 53e1e867..00000000 --- a/www/analytics/core/ScheduledTime.php +++ /dev/null @@ -1,225 +0,0 @@ -= 0` and `< 24`. - * @throws Exception If the current scheduled period is **hourly** or if `$hour` is invalid. - * @api - */ - public function setHour($hour) - { - if (!($hour >= 0 && $hour < 24)) { - throw new Exception ("Invalid hour parameter, must be >=0 and < 24"); - } - - $this->hour = $hour; - } - - /** - * By setting a timezone you make sure the scheduled task will be run at the requested time in the - * given timezone. This is useful for instance in case you want to make sure a task runs at midnight in a website's - * timezone. - * - * @param string $timezone - */ - public function setTimezone($timezone) - { - $this->timezone = $timezone; - } - - protected function adjustTimezone($rescheduledTime) - { - if (is_null($this->timezone)) { - return $rescheduledTime; - } - - $arbitraryDateInUTC = Date::factory('2011-01-01'); - $dateInTimezone = Date::factory($arbitraryDateInUTC, $this->timezone); - - $midnightInTimezone = date('H', $dateInTimezone->getTimestamp()); - - if ($arbitraryDateInUTC->isEarlier($dateInTimezone)) { - $hoursDifference = 0 - $midnightInTimezone; - } else { - $hoursDifference = 24 - $midnightInTimezone; - } - - $hoursDifference = $hoursDifference % 24; - - $rescheduledTime += (3600 * $hoursDifference); - - if ($this->getTime() > $rescheduledTime) { - // make sure the rescheduled date is in the future - $rescheduledTime = (24 * 3600) + $rescheduledTime; - } - - return $rescheduledTime; - } - - /** - * Computes the delta in seconds needed to adjust the rescheduled time to the required hour. - * - * @param int $rescheduledTime The rescheduled time to be adjusted - * @return int adjusted rescheduled time - */ - protected function adjustHour($rescheduledTime) - { - if ($this->hour !== null) { - // Reset the number of minutes and set the scheduled hour to the one specified with setHour() - $rescheduledTime = mktime($this->hour, - 0, - date('s', $rescheduledTime), - date('n', $rescheduledTime), - date('j', $rescheduledTime), - date('Y', $rescheduledTime) - ); - } - return $rescheduledTime; - } - - /** - * Returns a new ScheduledTime instance using a string description of the scheduled period type - * and a string description of the day within the period to execute the task on. - * - * @param string $periodType The scheduled period type. Can be `'hourly'`, `'daily'`, `'weekly'`, or `'monthly'`. - * @param string|int|false $periodDay A string describing the day within the scheduled period to execute - * the task on. Only valid for week and month periods. - * - * If `'weekly'` is supplied for `$periodType`, this should be a day - * of the week, for example, `'monday'` or `'tuesday'`. - * - * If `'monthly'` is supplied for `$periodType`, this can be a numeric - * day in the month or a day in one week of the month. For example, - * `12`, `23`, `'first sunday'` or `'fourth tuesday'`. - * @api - */ - public static function factory($periodType, $periodDay = false) - { - switch ($periodType) { - case 'hourly': - return new Hourly(); - case 'daily': - return new Daily(); - case 'weekly': - $result = new Weekly(); - if($periodDay !== false) { - $result->setDay($periodDay); - } - return $result; - case 'monthly': - $result = new Monthly($periodDay); - if($periodDay !== false) { - if (is_int($periodDay)) { - $result->setDay($periodDay); - } else { - $result->setDayOfWeekFromString($periodDay); - } - } - return $result; - default: - throw new Exception("Unsupported scheduled period type: '$periodType'. Supported values are" - . " 'hourly', 'daily', 'weekly' or 'monthly'."); - } - } -} diff --git a/www/analytics/core/ScheduledTime/Daily.php b/www/analytics/core/Scheduler/Schedule/Daily.php similarity index 83% rename from www/analytics/core/ScheduledTime/Daily.php rename to www/analytics/core/Scheduler/Schedule/Daily.php index 8566ef9b..e459fbfe 100644 --- a/www/analytics/core/ScheduledTime/Daily.php +++ b/www/analytics/core/Scheduler/Schedule/Daily.php @@ -1,22 +1,22 @@ week !== null ) { $newTime = $rescheduledTime + $this->week * 7 * 86400; - while (date("w", $newTime) != $this->dayOfWeek % 7) // modulus for sanity check - { + while (date("w", $newTime) != $this->dayOfWeek % 7) { + // modulus for sanity check + $newTime += 86400; } $scheduledDay = ($newTime - $rescheduledTime) / 86400 + 1; @@ -116,7 +115,7 @@ class Monthly extends ScheduledTime public function setDay($_day) { if (!($_day >= 1 && $_day < 32)) { - throw new Exception ("Invalid day parameter, must be >=1 and < 32"); + throw new Exception("Invalid day parameter, must be >=1 and < 32"); } $this->day = $_day; diff --git a/www/analytics/core/Scheduler/Schedule/Schedule.php b/www/analytics/core/Scheduler/Schedule/Schedule.php new file mode 100644 index 00000000..a150850c --- /dev/null +++ b/www/analytics/core/Scheduler/Schedule/Schedule.php @@ -0,0 +1,224 @@ += 0` and `< 24`. + * @throws Exception If the current scheduled period is **hourly** or if `$hour` is invalid. + * @api + */ + public function setHour($hour) + { + if (!($hour >= 0 && $hour < 24)) { + throw new Exception("Invalid hour parameter, must be >=0 and < 24"); + } + + $this->hour = $hour; + } + + /** + * By setting a timezone you make sure the scheduled task will be run at the requested time in the + * given timezone. This is useful for instance in case you want to make sure a task runs at midnight in a website's + * timezone. + * + * @param string $timezone + */ + public function setTimezone($timezone) + { + $this->timezone = $timezone; + } + + protected function adjustTimezone($rescheduledTime) + { + if (is_null($this->timezone)) { + return $rescheduledTime; + } + + $arbitraryDateInUTC = Date::factory('2011-01-01'); + $dateInTimezone = Date::factory($arbitraryDateInUTC, $this->timezone); + + $midnightInTimezone = date('H', $dateInTimezone->getTimestamp()); + + if ($arbitraryDateInUTC->isEarlier($dateInTimezone)) { + $hoursDifference = 0 - $midnightInTimezone; + } else { + $hoursDifference = 24 - $midnightInTimezone; + } + + $hoursDifference = $hoursDifference % 24; + + $rescheduledTime += (3600 * $hoursDifference); + + if ($this->getTime() > $rescheduledTime) { + // make sure the rescheduled date is in the future + $rescheduledTime = (24 * 3600) + $rescheduledTime; + } + + return $rescheduledTime; + } + + /** + * Computes the delta in seconds needed to adjust the rescheduled time to the required hour. + * + * @param int $rescheduledTime The rescheduled time to be adjusted + * @return int adjusted rescheduled time + */ + protected function adjustHour($rescheduledTime) + { + if ($this->hour !== null) { + // Reset the number of minutes and set the scheduled hour to the one specified with setHour() + $rescheduledTime = mktime($this->hour, + 0, + date('s', $rescheduledTime), + date('n', $rescheduledTime), + date('j', $rescheduledTime), + date('Y', $rescheduledTime) + ); + } + return $rescheduledTime; + } + + /** + * Returns a new Schedule instance using a string description of the scheduled period type + * and a string description of the day within the period to execute the task on. + * + * @param string $periodType The scheduled period type. Can be `'hourly'`, `'daily'`, `'weekly'`, or `'monthly'`. + * @param bool|false|int|string $periodDay A string describing the day within the scheduled period to execute + * the task on. Only valid for week and month periods. + * + * If `'weekly'` is supplied for `$periodType`, this should be a day + * of the week, for example, `'monday'` or `'tuesday'`. + * + * If `'monthly'` is supplied for `$periodType`, this can be a numeric + * day in the month or a day in one week of the month. For example, + * `12`, `23`, `'first sunday'` or `'fourth tuesday'`. + * @return Hourly|Daily|Weekly|Monthly + * @throws Exception + * @api + */ + public static function factory($periodType, $periodDay = false) + { + switch ($periodType) { + case 'hourly': + return new Hourly(); + case 'daily': + return new Daily(); + case 'weekly': + $result = new Weekly(); + if ($periodDay !== false) { + $result->setDay($periodDay); + } + return $result; + case 'monthly': + $result = new Monthly($periodDay); + if ($periodDay !== false) { + if (is_int($periodDay)) { + $result->setDay($periodDay); + } else { + $result->setDayOfWeekFromString($periodDay); + } + } + return $result; + default: + throw new Exception("Unsupported scheduled period type: '$periodType'. Supported values are" + . " 'hourly', 'daily', 'weekly' or 'monthly'."); + } + } +} diff --git a/www/analytics/core/ScheduledTime/Weekly.php b/www/analytics/core/Scheduler/Schedule/Weekly.php similarity index 88% rename from www/analytics/core/ScheduledTime/Weekly.php rename to www/analytics/core/Scheduler/Schedule/Weekly.php index 7a9769ba..09fb5ec7 100644 --- a/www/analytics/core/ScheduledTime/Weekly.php +++ b/www/analytics/core/Scheduler/Schedule/Weekly.php @@ -1,23 +1,22 @@ adjustHour($rescheduledTime); $rescheduledTime = $this->adjustTimezone($rescheduledTime); @@ -65,7 +64,7 @@ class Weekly extends ScheduledTime } if (!($day >= 1 && $day < 8)) { - throw new Exception ("Invalid day parameter, must be >=1 and < 8"); + throw new Exception("Invalid day parameter, must be >=1 and < 8"); } $this->day = $day; diff --git a/www/analytics/core/Scheduler/Scheduler.php b/www/analytics/core/Scheduler/Scheduler.php new file mode 100644 index 00000000..86b8f446 --- /dev/null +++ b/www/analytics/core/Scheduler/Scheduler.php @@ -0,0 +1,241 @@ +hourly('myTask'); // myTask() will be executed once every hour + * } + * public function myTask() + * { + * // do something + * } + * } + * + * **Executing all pending tasks** + * + * $results = $scheduler->run(); + * $task1Result = $results[0]; + * $task1Name = $task1Result['task']; + * $task1Output = $task1Result['output']; + * + * echo "Executed task '$task1Name'. Task output:\n$task1Output"; + */ +class Scheduler +{ + /** + * Is the scheduler running any task. + * @var bool + */ + private $isRunningTask = false; + + /** + * @var Timetable + */ + private $timetable; + + /** + * @var TaskLoader + */ + private $loader; + + /** + * @var LoggerInterface + */ + private $logger; + + public function __construct(TaskLoader $loader, LoggerInterface $logger) + { + $this->timetable = new Timetable(); + $this->loader = $loader; + $this->logger = $logger; + } + + /** + * Executes tasks that are scheduled to run, then reschedules them. + * + * @return array An array describing the results of scheduled task execution. Each element + * in the array will have the following format: + * + * ``` + * array( + * 'task' => 'task name', + * 'output' => '... task output ...' + * ) + * ``` + */ + public function run() + { + $tasks = $this->loader->loadTasks(); + + $this->logger->debug('{count} scheduled tasks loaded', array('count' => count($tasks))); + + // remove from timetable tasks that are not active anymore + $this->timetable->removeInactiveTasks($tasks); + + $this->logger->info("Starting Scheduled tasks... "); + + // for every priority level, starting with the highest and concluding with the lowest + $executionResults = array(); + for ($priority = Task::HIGHEST_PRIORITY; $priority <= Task::LOWEST_PRIORITY; ++$priority) { + $this->logger->debug("Executing tasks with priority {priority}:", array('priority' => $priority)); + + // loop through each task + foreach ($tasks as $task) { + // if the task does not have the current priority level, don't execute it yet + if ($task->getPriority() != $priority) { + continue; + } + + $taskName = $task->getName(); + $shouldExecuteTask = $this->timetable->shouldExecuteTask($taskName); + + if ($this->timetable->taskShouldBeRescheduled($taskName)) { + $rescheduledDate = $this->timetable->rescheduleTask($task); + + $this->logger->debug("Task {task} is scheduled to run again for {date}.", array('task' => $taskName, 'date' => $rescheduledDate)); + } + + if ($shouldExecuteTask) { + $message = $this->executeTask($task); + + $executionResults[] = array('task' => $taskName, 'output' => $message); + } + } + } + + $this->logger->info("done"); + + return $executionResults; + } + + /** + * Run a specific task now. Will ignore the schedule completely. + * + * @param string $taskName + * @return string Task output. + */ + public function runTaskNow($taskName) + { + $tasks = $this->loader->loadTasks(); + + foreach ($tasks as $task) { + if ($task->getName() === $taskName) { + return $this->executeTask($task); + } + } + + throw new \InvalidArgumentException('Task ' . $taskName . ' not found'); + } + + /** + * Determines a task's scheduled time and persists it, overwriting the previous scheduled time. + * + * Call this method if your task's scheduled time has changed due to, for example, an option that + * was changed. + * + * @param Task $task Describes the scheduled task being rescheduled. + * @api + */ + public function rescheduleTask(Task $task) + { + $this->logger->debug('Rescheduling task {task}', array('task' => $task->getName())); + + $this->timetable->rescheduleTask($task); + } + + /** + * Returns true if the scheduler is currently running a task. + * + * @return bool + */ + public function isRunningTask() + { + return $this->isRunningTask; + } + + /** + * Return the next scheduled time given the class and method names of a scheduled task. + * + * @param string $className The name of the class that contains the scheduled task method. + * @param string $methodName The name of the scheduled task method. + * @param string|null $methodParameter Optional method parameter. + * @return mixed int|bool The time in miliseconds when the scheduled task will be executed + * next or false if it is not scheduled to run. + */ + public function getScheduledTimeForMethod($className, $methodName, $methodParameter = null) + { + return $this->timetable->getScheduledTimeForMethod($className, $methodName, $methodParameter); + } + + /** + * Returns the list of the task names. + * + * @return string[] + */ + public function getTaskList() + { + $tasks = $this->loader->loadTasks(); + + return array_map(function (Task $task) { + return $task->getName(); + }, $tasks); + } + + /** + * Executes the given task + * + * @param Task $task + * @return string + */ + private function executeTask($task) + { + $this->logger->info("Scheduler: executing task {taskName}...", array( + 'taskName' => $task->getName(), + )); + + $this->isRunningTask = true; + + $timer = new Timer(); + + try { + $callable = array($task->getObjectInstance(), $task->getMethodName()); + call_user_func($callable, $task->getMethodParameter()); + $message = $timer->__toString(); + } catch (Exception $e) { + $message = 'ERROR: ' . $e->getMessage(); + } + + $this->isRunningTask = false; + + $this->logger->info("Scheduler: finished. {timeElapsed}", array( + 'timeElapsed' => $timer, + )); + + return $message; + } +} diff --git a/www/analytics/core/Scheduler/Task.php b/www/analytics/core/Scheduler/Task.php new file mode 100644 index 00000000..f3c4381a --- /dev/null +++ b/www/analytics/core/Scheduler/Task.php @@ -0,0 +1,200 @@ +className = $this->getClassNameFromInstance($objectInstance); + + if ($priority < self::HIGHEST_PRIORITY || $priority > self::LOWEST_PRIORITY) { + throw new Exception("Invalid priority for ScheduledTask '$this->className.$methodName': $priority"); + } + + $this->objectInstance = $objectInstance; + $this->methodName = $methodName; + $this->scheduledTime = $scheduledTime; + $this->methodParameter = $methodParameter; + $this->priority = $priority; + } + + protected function getClassNameFromInstance($_objectInstance) + { + if (is_string($_objectInstance)) { + return $_objectInstance; + } + + $namespaced = get_class($_objectInstance); + + return $namespaced; + } + + /** + * Returns the object instance that contains the method to execute. Returns a class + * name if the method is static. + * + * @return mixed + */ + public function getObjectInstance() + { + return $this->objectInstance; + } + + /** + * Returns the name of the class that contains the method to execute. + * + * @return string + */ + public function getClassName() + { + return $this->className; + } + + /** + * Returns the name of the method that will be executed. + * + * @return string + */ + public function getMethodName() + { + return $this->methodName; + } + + /** + * Returns the value that will be passed to the method when executed, or `null` if + * no value will be supplied. + * + * @return string|null + */ + public function getMethodParameter() + { + return $this->methodParameter; + } + + /** + * Returns a {@link Schedule} instance that describes when the method should be executed + * and how long before the next execution. + * + * @return \Piwik\Scheduler\Schedule\Schedule + */ + public function getScheduledTime() + { + return $this->scheduledTime; + } + + /** + * Returns the time in milliseconds when this task will be executed next. + * + * @return int + */ + public function getRescheduledTime() + { + return $this->getScheduledTime()->getRescheduledTime(); + } + + /** + * Returns the task priority. The priority will be an integer whose value is + * between {@link HIGH_PRIORITY} and {@link LOW_PRIORITY}. + * + * @return int + */ + public function getPriority() + { + return $this->priority; + } + + /** + * Returns a unique name for this scheduled task. The name is stored in the DB and is used + * to store a task's previous execution time. The name is created using: + * + * - the name of the class that contains the method to execute, + * - the name of the method to regularly execute, + * - and the value that is passed to the executed task. + * + * @return string + */ + public function getName() + { + return self::getTaskName($this->getClassName(), $this->getMethodName(), $this->getMethodParameter()); + } + + /** + * @ignore + */ + public static function getTaskName($className, $methodName, $methodParameter = null) + { + return $className . '.' . $methodName . ($methodParameter == null ? '' : '_' . $methodParameter); + } +} diff --git a/www/analytics/core/Scheduler/TaskLoader.php b/www/analytics/core/Scheduler/TaskLoader.php new file mode 100644 index 00000000..60b9e328 --- /dev/null +++ b/www/analytics/core/Scheduler/TaskLoader.php @@ -0,0 +1,39 @@ +findComponents('Tasks', 'Piwik\Plugin\Tasks'); + + foreach ($pluginTasks as $pluginTask) { + $pluginTask->schedule(); + + foreach ($pluginTask->getScheduledTasks() as $task) { + $tasks[] = $task; + } + } + + return $tasks; + } +} diff --git a/www/analytics/core/Scheduler/Timetable.php b/www/analytics/core/Scheduler/Timetable.php new file mode 100644 index 00000000..c3c01ab1 --- /dev/null +++ b/www/analytics/core/Scheduler/Timetable.php @@ -0,0 +1,133 @@ +timetable = $unserializedTimetable === false ? array() : $unserializedTimetable; + } + + public function getTimetable() + { + return $this->timetable; + } + + public function setTimetable($timetable) + { + $this->timetable = $timetable; + } + + /** + * @param Task[] $activeTasks + */ + public function removeInactiveTasks($activeTasks) + { + $activeTaskNames = array(); + foreach ($activeTasks as $task) { + $activeTaskNames[] = $task->getName(); + } + foreach (array_keys($this->timetable) as $taskName) { + if (!in_array($taskName, $activeTaskNames)) { + unset($this->timetable[$taskName]); + } + } + } + + public function getScheduledTaskNames() + { + return array_keys($this->timetable); + } + + public function getScheduledTaskTime($taskName) + { + return isset($this->timetable[$taskName]) ? Date::factory($this->timetable[$taskName]) : false; + } + + /** + * Checks if the task should be executed + * + * Task has to be executed if : + * - the task has already been scheduled once and the current system time is greater than the scheduled time. + * - execution is forced, see $forceTaskExecution + * + * @param string $taskName + * + * @return boolean + */ + public function shouldExecuteTask($taskName) + { + $forceTaskExecution = (defined('DEBUG_FORCE_SCHEDULED_TASKS') && DEBUG_FORCE_SCHEDULED_TASKS); + + if ($forceTaskExecution) { + return true; + } + + return $this->taskHasBeenScheduledOnce($taskName) && time() >= $this->timetable[$taskName]; + } + + /** + * Checks if a task should be rescheduled + * + * Task has to be rescheduled if : + * - the task has to be executed + * - the task has never been scheduled before + * + * @param string $taskName + * + * @return boolean + */ + public function taskShouldBeRescheduled($taskName) + { + return !$this->taskHasBeenScheduledOnce($taskName) || $this->shouldExecuteTask($taskName); + } + + public function rescheduleTask(Task $task) + { + $rescheduledTime = $task->getRescheduledTime(); + + // update the scheduled time + $this->timetable[$task->getName()] = $rescheduledTime; + $this->save(); + + return Date::factory($rescheduledTime); + } + + public function save() + { + Option::set(self::TIMETABLE_OPTION_STRING, serialize($this->timetable)); + } + + public function getScheduledTimeForMethod($className, $methodName, $methodParameter = null) + { + $taskName = Task::getTaskName($className, $methodName, $methodParameter); + + return $this->taskHasBeenScheduledOnce($taskName) ? $this->timetable[$taskName] : false; + } + + public function taskHasBeenScheduledOnce($taskName) + { + return isset($this->timetable[$taskName]); + } +} diff --git a/www/analytics/core/Segment.php b/www/analytics/core/Segment.php index bf834543..d9f8d163 100644 --- a/www/analytics/core/Segment.php +++ b/www/analytics/core/Segment.php @@ -1,6 +1,6 @@ getSelectQuery( * $select = "table.col1, table2.col2", * $from = array("table", "table2"), @@ -41,15 +44,15 @@ use Piwik\Plugins\API\API; * $orderBy = "table.col1 DESC", * $groupBy = "table2.col2" * ); - * + * * Db::fetchAll($query['sql'], $query['bind']); - * + * * **Creating a _null_ segment** - * + * * $idSites = array(1,2,3); * $segment = new Segment('', $idSites); * // $segment->getSelectQuery will return a query that selects all visits - * + * * @api */ class Segment @@ -57,7 +60,22 @@ class Segment /** * @var SegmentExpression */ - protected $segment = null; + protected $segmentExpression = null; + + /** + * @var string + */ + protected $string = null; + + /** + * @var array + */ + protected $idSites = null; + + /** + * @var LogQueryBuilder + */ + private $segmentQueryBuilder; /** * Truncate the Segments to 8k @@ -66,13 +84,16 @@ class Segment /** * Constructor. - * + * * @param string $segmentCondition The segment condition, eg, `'browserCode=ff;countryCode=CA'`. * @param array $idSites The list of sites the segment will be used with. Some segments are * dependent on the site, such as goal segments. + * @throws */ public function __construct($segmentCondition, $idSites) { + $this->segmentQueryBuilder = StaticContainer::get('Piwik\DataAccess\LogQueryBuilder'); + $segmentCondition = trim($segmentCondition); if (!SettingsPiwik::isSegmentationEnabled() && !empty($segmentCondition) @@ -89,6 +110,35 @@ class Segment } } + private function getAvailableSegments() + { + // segment metadata + if (empty($this->availableSegments)) { + $this->availableSegments = API::getInstance()->getSegmentsMetadata($this->idSites, $_hideImplementationData = false); + } + + return $this->availableSegments; + } + + private function getSegmentByName($name) + { + $segments = $this->getAvailableSegments(); + + foreach ($segments as $segment) { + if ($segment['segment'] == $name && !empty($name)) { + + // check permission + if (isset($segment['permission']) && $segment['permission'] != 1) { + throw new NoAccessException("You do not have enough permission to access the segment " . $name); + } + + return $segment; + } + } + + throw new Exception("Segment '$name' is not a supported segment."); + } + /** * @param $string * @param $idSites @@ -99,13 +149,14 @@ class Segment // As a preventive measure, we restrict the filter size to a safe limit $string = substr($string, 0, self::SEGMENT_TRUNCATE_LIMIT); - $this->string = $string; + $this->string = $string; $this->idSites = $idSites; $segment = new SegmentExpression($string); - $this->segment = $segment; + $this->segmentExpression = $segment; // parse segments $expressions = $segment->parseSubExpressions(); + $expressions = $this->getExpressionsWithUnionsResolved($expressions); // convert segments name to sql segment // check that user is allowed to view this segment @@ -117,68 +168,86 @@ class Segment $expression[SegmentExpression::INDEX_OPERAND] = $cleanedExpression; $cleanedExpressions[] = $expression; } + $segment->setSubExpressionsAfterCleanup($cleanedExpressions); } + private function getExpressionsWithUnionsResolved($expressions) + { + $expressionsWithUnions = array(); + foreach ($expressions as $expression) { + $operand = $expression[SegmentExpression::INDEX_OPERAND]; + $name = $operand[SegmentExpression::INDEX_OPERAND_NAME]; + + $availableSegment = $this->getSegmentByName($name); + + if (!empty($availableSegment['unionOfSegments'])) { + $count = 0; + foreach ($availableSegment['unionOfSegments'] as $segmentNameOfUnion) { + $count++; + $operator = SegmentExpression::BOOL_OPERATOR_OR; // we connect all segments within that union via OR + if ($count === count($availableSegment['unionOfSegments'])) { + $operator = $expression[SegmentExpression::INDEX_BOOL_OPERATOR]; + } + + $operand[SegmentExpression::INDEX_OPERAND_NAME] = $segmentNameOfUnion; + $expressionsWithUnions[] = array( + SegmentExpression::INDEX_BOOL_OPERATOR => $operator, + SegmentExpression::INDEX_OPERAND => $operand + ); + } + } else { + $expressionsWithUnions[] = array( + SegmentExpression::INDEX_BOOL_OPERATOR => $expression[SegmentExpression::INDEX_BOOL_OPERATOR], + SegmentExpression::INDEX_OPERAND => $operand + ); + } + } + + return $expressionsWithUnions; + } + /** * Returns `true` if the segment is empty, `false` if otherwise. */ public function isEmpty() { - return empty($this->string); + return $this->segmentExpression->isEmpty(); } protected $availableSegments = array(); protected function getCleanedExpression($expression) { - if (empty($this->availableSegments)) { - $this->availableSegments = API::getInstance()->getSegmentsMetadata($this->idSites, $_hideImplementationData = false); - } + $name = $expression[SegmentExpression::INDEX_OPERAND_NAME]; + $matchType = $expression[SegmentExpression::INDEX_OPERAND_OPERATOR]; + $value = $expression[SegmentExpression::INDEX_OPERAND_VALUE]; - $name = $expression[0]; - $matchType = $expression[1]; - $value = $expression[2]; - $sqlName = ''; + $segment = $this->getSegmentByName($name); + $sqlName = $segment['sqlSegment']; - foreach ($this->availableSegments as $segment) { - if ($segment['segment'] != $name) { - continue; + if ($matchType != SegmentExpression::MATCH_IS_NOT_NULL_NOR_EMPTY + && $matchType != SegmentExpression::MATCH_IS_NULL_OR_EMPTY) { + + if (isset($segment['sqlFilterValue'])) { + $value = call_user_func($segment['sqlFilterValue'], $value); } - $sqlName = $segment['sqlSegment']; + // apply presentation filter + if (isset($segment['sqlFilter'])) { + $value = call_user_func($segment['sqlFilter'], $value, $segment['sqlSegment'], $matchType, $name); - // check permission - if (isset($segment['permission']) - && $segment['permission'] != 1 - ) { - throw new Exception("You do not have enough permission to access the segment " . $name); - } - - if($matchType != SegmentExpression::MATCH_IS_NOT_NULL_NOR_EMPTY - && $matchType != SegmentExpression::MATCH_IS_NULL_OR_EMPTY) { - - if(isset($segment['sqlFilterValue'])) { - $value = call_user_func($segment['sqlFilterValue'], $value); + if(is_null($value)) { // null is returned in TableLogAction::getIdActionFromSegment() + return array(null, $matchType, null); } - // apply presentation filter - if (isset($segment['sqlFilter'])) { - $value = call_user_func($segment['sqlFilter'], $value, $segment['sqlSegment'], $matchType, $name); - - // sqlFilter-callbacks might return arrays for more complex cases - // e.g. see TableLogAction::getIdActionFromSegment() - if (is_array($value) && isset($value['SQL'])) { - // Special case: returned value is a sub sql expression! - $matchType = SegmentExpression::MATCH_ACTIONS_CONTAINS; - } + // sqlFilter-callbacks might return arrays for more complex cases + // e.g. see TableLogAction::getIdActionFromSegment() + if (is_array($value) && isset($value['SQL'])) { + // Special case: returned value is a sub sql expression! + $matchType = SegmentExpression::MATCH_ACTIONS_CONTAINS; } } - break; - } - - if (empty($sqlName)) { - throw new Exception("Segment '$name' is not a supported segment."); } return array($sqlName, $matchType, $value); @@ -186,7 +255,7 @@ class Segment /** * Returns the segment condition. - * + * * @return string */ public function getString() @@ -197,7 +266,7 @@ class Segment /** * Returns a hash of the segment condition, or the empty string if the segment * condition is empty. - * + * * @return string */ public function getHash() @@ -220,235 +289,31 @@ class Segment * @param array|string $bind (optional) Bind parameters, eg, `array($col1Value, $col2Value)`. * @param false|string $orderBy (optional) Order by clause, eg, `"t1.col1 ASC"`. * @param false|string $groupBy (optional) Group by clause, eg, `"t2.col2"`. + * @param int $limit Limit number of result to $limit + * @param int $offset Specified the offset of the first row to return + * @param int If set to value >= 1 then the Select query (and All inner queries) will be LIMIT'ed by this value. + * Use only when you're not aggregating or it will sample the data. * @return string The entire select query. */ - public function getSelectQuery($select, $from, $where = false, $bind = array(), $orderBy = false, $groupBy = false) + public function getSelectQuery($select, $from, $where = false, $bind = array(), $orderBy = false, $groupBy = false, $limit = 0, $offset = 0) { - if (!is_array($from)) { - $from = array($from); + $segmentExpression = $this->segmentExpression; + + if ($offset > 0) { + $limit = (int) $offset . ', ' . (int) $limit; } - if (!$this->isEmpty()) { - $this->segment->parseSubExpressionsIntoSqlExpressions($from); - - $joins = $this->generateJoins($from); - $from = $joins['sql']; - $joinWithSubSelect = $joins['joinWithSubSelect']; - - $segmentSql = $this->segment->getSql(); - $segmentWhere = $segmentSql['where']; - if (!empty($segmentWhere)) { - if (!empty($where)) { - $where = "( $where ) - AND - ($segmentWhere)"; - } else { - $where = $segmentWhere; - } - } - - $bind = array_merge($bind, $segmentSql['bind']); - } else { - $joins = $this->generateJoins($from); - $from = $joins['sql']; - $joinWithSubSelect = $joins['joinWithSubSelect']; - } - - if ($joinWithSubSelect) { - $sql = $this->buildWrappedSelectQuery($select, $from, $where, $orderBy, $groupBy); - } else { - $sql = $this->buildSelectQuery($select, $from, $where, $orderBy, $groupBy); - } - return array( - 'sql' => $sql, - 'bind' => $bind - ); + return $this->segmentQueryBuilder->getSelectQueryString($segmentExpression, $select, $from, $where, $bind, + $groupBy, $orderBy, $limit); } /** - * Generate the join sql based on the needed tables - * @param array $tables tables to join - * @throws Exception if tables can't be joined - * @return array - */ - private function generateJoins($tables) - { - $knownTables = array("log_visit", "log_link_visit_action", "log_conversion", "log_conversion_item"); - $visitsAvailable = $actionsAvailable = $conversionsAvailable = $conversionItemAvailable = false; - $joinWithSubSelect = false; - $sql = ''; - - // make sure the tables are joined in the right order - // base table first, then action before conversion - // this way, conversions can be joined on idlink_va - $actionIndex = array_search("log_link_visit_action", $tables); - $conversionIndex = array_search("log_conversion", $tables); - if ($actionIndex > 0 && $conversionIndex > 0 && $actionIndex > $conversionIndex) { - $tables[$actionIndex] = "log_conversion"; - $tables[$conversionIndex] = "log_link_visit_action"; - } - - // same as above: action before visit - $actionIndex = array_search("log_link_visit_action", $tables); - $visitIndex = array_search("log_visit", $tables); - if ($actionIndex > 0 && $visitIndex > 0 && $actionIndex > $visitIndex) { - $tables[$actionIndex] = "log_visit"; - $tables[$visitIndex] = "log_link_visit_action"; - } - - foreach ($tables as $i => $table) { - if (is_array($table)) { - // join condition provided - $alias = isset($table['tableAlias']) ? $table['tableAlias'] : $table['table']; - $sql .= " - LEFT JOIN " . Common::prefixTable($table['table']) . " AS " . $alias - . " ON " . $table['joinOn']; - continue; - } - - if (!in_array($table, $knownTables)) { - throw new Exception("Table '$table' can't be used for segmentation"); - } - - $tableSql = Common::prefixTable($table) . " AS $table"; - - if ($i == 0) { - // first table - $sql .= $tableSql; - } else { - if ($actionsAvailable && $table == "log_conversion") { - // have actions, need conversions => join on idlink_va - $join = "log_conversion.idlink_va = log_link_visit_action.idlink_va " - . "AND log_conversion.idsite = log_link_visit_action.idsite"; - } else if ($actionsAvailable && $table == "log_visit") { - // have actions, need visits => join on idvisit - $join = "log_visit.idvisit = log_link_visit_action.idvisit"; - } else if ($visitsAvailable && $table == "log_link_visit_action") { - // have visits, need actions => we have to use a more complex join - // we don't hande this here, we just return joinWithSubSelect=true in this case - $joinWithSubSelect = true; - $join = "log_link_visit_action.idvisit = log_visit.idvisit"; - } else if ($conversionsAvailable && $table == "log_link_visit_action") { - // have conversions, need actions => join on idlink_va - $join = "log_conversion.idlink_va = log_link_visit_action.idlink_va"; - } else if (($visitsAvailable && $table == "log_conversion") - || ($conversionsAvailable && $table == "log_visit") - ) { - // have visits, need conversion (or vice versa) => join on idvisit - // notice that joining conversions on visits has lower priority than joining it on actions - $join = "log_conversion.idvisit = log_visit.idvisit"; - - // if conversions are joined on visits, we need a complex join - if ($table == "log_conversion") { - $joinWithSubSelect = true; - } - } elseif ($conversionItemAvailable && $table === 'log_visit') { - $join = "log_conversion_item.idvisit = log_visit.idvisit"; - } elseif ($conversionItemAvailable && $table === 'log_link_visit_action') { - $join = "log_conversion_item.idvisit = log_link_visit_action.idvisit"; - } elseif ($conversionItemAvailable && $table === 'log_conversion') { - $join = "log_conversion_item.idvisit = log_conversion.idvisit"; - } else { - throw new Exception("Table '$table' can't be joined for segmentation"); - } - - // the join sql the default way - $sql .= " - LEFT JOIN $tableSql ON $join"; - } - - // remember which tables are available - $visitsAvailable = ($visitsAvailable || $table == "log_visit"); - $actionsAvailable = ($actionsAvailable || $table == "log_link_visit_action"); - $conversionsAvailable = ($conversionsAvailable || $table == "log_conversion"); - $conversionItemAvailable = ($conversionItemAvailable || $table == "log_conversion_item"); - } - - $return = array( - 'sql' => $sql, - 'joinWithSubSelect' => $joinWithSubSelect - ); - return $return; - - } - - /** - * Build select query the normal way - * @param string $select fieldlist to be selected - * @param string $from tablelist to select from - * @param string $where where clause - * @param string $orderBy order by clause - * @param string $groupBy group by clause + * Returns the segment string. + * * @return string */ - private function buildSelectQuery($select, $from, $where, $orderBy, $groupBy) + public function __toString() { - $sql = " - SELECT - $select - FROM - $from"; - - if ($where) { - $sql .= " - WHERE - $where"; - } - - if ($groupBy) { - $sql .= " - GROUP BY - $groupBy"; - } - - if ($orderBy) { - $sql .= " - ORDER BY - $orderBy"; - } - - return $sql; - } - - /** - * Build a select query where actions have to be joined on visits (or conversions) - * In this case, the query gets wrapped in another query so that grouping by visit is possible - * @param string $select - * @param string $from - * @param string $where - * @param string $orderBy - * @param string $groupBy - * @throws Exception - * @return string - */ - private function buildWrappedSelectQuery($select, $from, $where, $orderBy, $groupBy) - { - $matchTables = "(log_visit|log_conversion_item|log_conversion|log_action)"; - preg_match_all("/". $matchTables ."\.[a-z0-9_\*]+/", $select, $matches); - $neededFields = array_unique($matches[0]); - - if (count($neededFields) == 0) { - throw new Exception("No needed fields found in select expression. " - . "Please use a table prefix."); - } - - $select = preg_replace('/'.$matchTables.'\./', 'log_inner.', $select); - $orderBy = preg_replace('/'.$matchTables.'\./', 'log_inner.', $orderBy); - $groupBy = preg_replace('/'.$matchTables.'\./', 'log_inner.', $groupBy); - - $from = "( - SELECT - " . implode(", - ", $neededFields) . " - FROM - $from - WHERE - $where - GROUP BY log_visit.idvisit - ) AS log_inner"; - - $where = false; - $query = $this->buildSelectQuery($select, $from, $where, $orderBy, $groupBy); - return $query; + return (string) $this->getString(); } } diff --git a/www/analytics/core/Segment/SegmentExpression.php b/www/analytics/core/Segment/SegmentExpression.php new file mode 100644 index 00000000..605cd136 --- /dev/null +++ b/www/analytics/core/Segment/SegmentExpression.php @@ -0,0 +1,452 @@ +='; + const MATCH_LESS_OR_EQUAL = '<='; + const MATCH_GREATER = '>'; + const MATCH_LESS = '<'; + const MATCH_CONTAINS = '=@'; + const MATCH_DOES_NOT_CONTAIN = '!@'; + const MATCH_STARTS_WITH = '=^'; + const MATCH_ENDS_WITH = '=$'; + + const BOOL_OPERATOR_OR = 'OR'; + const BOOL_OPERATOR_AND = 'AND'; + const BOOL_OPERATOR_END = ''; + + // Note: you can't write this in the API, but access this feature + // via field!= <- IS NOT NULL + // or via field== <- IS NULL / empty + const MATCH_IS_NOT_NULL_NOR_EMPTY = '::NOT_NULL'; + const MATCH_IS_NULL_OR_EMPTY = '::NULL'; + + // Special case, since we look up Page URLs/Page titles in a sub SQL query + const MATCH_ACTIONS_CONTAINS = 'IN'; + + const INDEX_BOOL_OPERATOR = 0; + const INDEX_OPERAND = 1; + + const INDEX_OPERAND_NAME = 0; + const INDEX_OPERAND_OPERATOR = 1; + const INDEX_OPERAND_VALUE = 2; + + const SQL_WHERE_DO_NOT_MATCH_ANY_ROW = "(1 = 0)"; + const SQL_WHERE_MATCHES_ALL_ROWS = "(1 = 1)"; + + public function __construct($string) + { + $this->string = $string; + $this->tree = $this->parseTree(); + } + + public function getSegmentDefinition() + { + return $this->string; + } + + public function isEmpty() + { + return count($this->tree) == 0; + } + + protected $joins = array(); + protected $valuesBind = array(); + protected $parsedTree = array(); + protected $tree = array(); + protected $parsedSubExpressions = array(); + + /** + * Given the array of parsed filters containing, for each filter, + * the boolean operator (AND/OR) and the operand, + * Will return the array where the filters are in SQL representation + * + * @throws Exception + * @return array + */ + public function parseSubExpressions() + { + $parsedSubExpressions = array(); + foreach ($this->tree as $leaf) { + $operand = $leaf[self::INDEX_OPERAND]; + + $operand = urldecode($operand); + + $operator = $leaf[self::INDEX_BOOL_OPERATOR]; + $pattern = '/^(.+?)(' . self::MATCH_EQUAL . '|' + . self::MATCH_NOT_EQUAL . '|' + . self::MATCH_GREATER_OR_EQUAL . '|' + . self::MATCH_GREATER . '|' + . self::MATCH_LESS_OR_EQUAL . '|' + . self::MATCH_LESS . '|' + . self::MATCH_CONTAINS . '|' + . self::MATCH_DOES_NOT_CONTAIN . '|' + . preg_quote(self::MATCH_STARTS_WITH) . '|' + . preg_quote(self::MATCH_ENDS_WITH) + . '){1}(.*)/'; + $match = preg_match($pattern, $operand, $matches); + if ($match == 0) { + throw new Exception('The segment \'' . $operand . '\' is not valid.'); + } + + $leftMember = $matches[1]; + $operation = $matches[2]; + $valueRightMember = urldecode($matches[3]); + + // is null / is not null + if ($valueRightMember === '') { + if ($operation == self::MATCH_NOT_EQUAL) { + $operation = self::MATCH_IS_NOT_NULL_NOR_EMPTY; + } elseif ($operation == self::MATCH_EQUAL) { + $operation = self::MATCH_IS_NULL_OR_EMPTY; + } else { + throw new Exception('The segment \'' . $operand . '\' has no value specified. You can leave this value empty ' . + 'only when you use the operators: ' . self::MATCH_NOT_EQUAL . ' (is not) or ' . self::MATCH_EQUAL . ' (is)'); + } + } + + $parsedSubExpressions[] = array( + self::INDEX_BOOL_OPERATOR => $operator, + self::INDEX_OPERAND => array( + self::INDEX_OPERAND_NAME => $leftMember, + self::INDEX_OPERAND_OPERATOR => $operation, + self::INDEX_OPERAND_VALUE => $valueRightMember, + )); + } + $this->parsedSubExpressions = $parsedSubExpressions; + return $parsedSubExpressions; + } + + /** + * Set the given expression + * @param $parsedSubExpressions + */ + public function setSubExpressionsAfterCleanup($parsedSubExpressions) + { + $this->parsedSubExpressions = $parsedSubExpressions; + } + + /** + * @param array $availableTables + */ + public function parseSubExpressionsIntoSqlExpressions(&$availableTables = array()) + { + $sqlSubExpressions = array(); + $this->valuesBind = array(); + $this->joins = array(); + + foreach ($this->parsedSubExpressions as $leaf) { + $operator = $leaf[self::INDEX_BOOL_OPERATOR]; + $operandDefinition = $leaf[self::INDEX_OPERAND]; + + $operand = $this->getSqlMatchFromDefinition($operandDefinition, $availableTables); + + if ($operand[self::INDEX_OPERAND_OPERATOR] !== null) { + if (is_array($operand[self::INDEX_OPERAND_OPERATOR])) { + $this->valuesBind = array_merge($this->valuesBind, $operand[self::INDEX_OPERAND_OPERATOR]); + } else { + $this->valuesBind[] = $operand[self::INDEX_OPERAND_OPERATOR]; + } + } + + $operand = $operand[self::INDEX_OPERAND_NAME]; + + $sqlSubExpressions[] = array( + self::INDEX_BOOL_OPERATOR => $operator, + self::INDEX_OPERAND => $operand, + ); + } + + $this->tree = $sqlSubExpressions; + } + + /** + * Given an array representing one filter operand ( left member , operation , right member) + * Will return an array containing + * - the SQL substring, + * - the values to bind to this substring + * + * @param array $def + * @param array $availableTables + * @throws Exception + * @return array + */ + protected function getSqlMatchFromDefinition($def, &$availableTables) + { + $field = $def[0]; + $matchType = $def[1]; + $value = $def[2]; + + // Segment::getCleanedExpression() may return array(null, $matchType, null) + $operandWillNotMatchAnyRow = empty($field) && is_null($value); + if($operandWillNotMatchAnyRow) { + if($matchType == self::MATCH_EQUAL) { + // eg. pageUrl==DoesNotExist + // Equal to NULL means it will match none + $sqlExpression = self::SQL_WHERE_DO_NOT_MATCH_ANY_ROW; + } elseif($matchType == self::MATCH_NOT_EQUAL) { + // eg. pageUrl!=DoesNotExist + // Not equal to NULL means it matches all rows + $sqlExpression = self::SQL_WHERE_MATCHES_ALL_ROWS; + } elseif($matchType == self::MATCH_CONTAINS + || $matchType == self::MATCH_DOES_NOT_CONTAIN + || $matchType == self::MATCH_STARTS_WITH + || $matchType == self::MATCH_ENDS_WITH) { + // no action was found for CONTAINS / DOES NOT CONTAIN + // eg. pageUrl=@DoesNotExist -> matches no row + // eg. pageUrl!@DoesNotExist -> matches no rows + $sqlExpression = self::SQL_WHERE_DO_NOT_MATCH_ANY_ROW; + } else { + // it is not expected to reach this code path + throw new Exception("Unexpected match type $matchType for your segment. " . + "Please report this issue to the Piwik team with the segment you are using."); + } + + return array($sqlExpression, $value = null); + } + + $alsoMatchNULLValues = false; + switch ($matchType) { + case self::MATCH_EQUAL: + $sqlMatch = '%s ='; + break; + case self::MATCH_NOT_EQUAL: + $sqlMatch = '%s <>'; + $alsoMatchNULLValues = true; + break; + case self::MATCH_GREATER: + $sqlMatch = '%s >'; + break; + case self::MATCH_LESS: + $sqlMatch = '%s <'; + break; + case self::MATCH_GREATER_OR_EQUAL: + $sqlMatch = '%s >='; + break; + case self::MATCH_LESS_OR_EQUAL: + $sqlMatch = '%s <='; + break; + case self::MATCH_CONTAINS: + $sqlMatch = '%s LIKE'; + $value = '%' . $this->escapeLikeString($value) . '%'; + break; + case self::MATCH_DOES_NOT_CONTAIN: + $sqlMatch = '%s NOT LIKE'; + $value = '%' . $this->escapeLikeString($value) . '%'; + $alsoMatchNULLValues = true; + break; + case self::MATCH_STARTS_WITH: + $sqlMatch = '%s LIKE'; + $value = $this->escapeLikeString($value) . '%'; + break; + case self::MATCH_ENDS_WITH: + $sqlMatch = '%s LIKE'; + $value = '%' . $this->escapeLikeString($value); + break; + + case self::MATCH_IS_NOT_NULL_NOR_EMPTY: + $sqlMatch = '%s IS NOT NULL AND (%s <> \'\' OR %s = 0)'; + $value = null; + break; + + case self::MATCH_IS_NULL_OR_EMPTY: + $sqlMatch = '%s IS NULL OR %s = \'\' '; + $value = null; + break; + + case self::MATCH_ACTIONS_CONTAINS: + // this match type is not accessible from the outside + // (it won't be matched in self::parseSubExpressions()) + // it can be used internally to inject sub-expressions into the query. + // see Segment::getCleanedExpression() + $sqlMatch = '%s IN (' . $value['SQL'] . ')'; + $value = $value['bind']; + break; + default: + throw new Exception("Filter contains the match type '" . $matchType . "' which is not supported"); + break; + } + + // We match NULL values when rows are excluded only when we are not doing a + $alsoMatchNULLValues = $alsoMatchNULLValues && !empty($value); + $sqlMatch = str_replace('%s', $field, $sqlMatch); + + if ($matchType === self::MATCH_ACTIONS_CONTAINS + || is_null($value) + ) { + $sqlExpression = "( $sqlMatch )"; + } else { + if ($alsoMatchNULLValues) { + $sqlExpression = "( $field IS NULL OR $sqlMatch ? )"; + } else { + $sqlExpression = "$sqlMatch ?"; + } + } + + $this->checkFieldIsAvailable($field, $availableTables); + + return array($sqlExpression, $value); + } + + /** + * Check whether the field is available + * If not, add it to the available tables + * + * @param string $field + * @param array $availableTables + */ + private function checkFieldIsAvailable($field, &$availableTables) + { + $fieldParts = explode('.', $field); + + $table = count($fieldParts) == 2 ? $fieldParts[0] : false; + + // remove sql functions from field name + // example: `HOUR(log_visit.visit_last_action_time)` gets `HOUR(log_visit` => remove `HOUR(` + $table = preg_replace('/^[A-Z_]+\(/', '', $table); + $tableExists = !$table || in_array($table, $availableTables); + + if (!$tableExists) { + $availableTables[] = $table; + } + } + + /** + * Escape the characters % and _ in the given string + * @param string $str + * @return string + */ + private function escapeLikeString($str) + { + if (false !== strpos($str, '%')) { + $str = str_replace("%", "\%", $str); + } + + if (false !== strpos($str, '_')) { + $str = str_replace("_", "\_", $str); + } + + return $str; + } + + /** + * Given a filter string, + * will parse it into an array where each row contains the boolean operator applied to it, + * and the operand + * + * @return array + */ + protected function parseTree() + { + $string = $this->string; + if (empty($string)) { + return array(); + } + $tree = array(); + $i = 0; + $length = strlen($string); + $isBackslash = false; + $operand = ''; + while ($i <= $length) { + $char = $string[$i]; + + $isAND = ($char == self::AND_DELIMITER); + $isOR = ($char == self::OR_DELIMITER); + $isEnd = ($length == $i + 1); + + if ($isEnd) { + if ($isBackslash && ($isAND || $isOR)) { + $operand = substr($operand, 0, -1); + } + $operand .= $char; + $tree[] = array(self::INDEX_BOOL_OPERATOR => self::BOOL_OPERATOR_END, self::INDEX_OPERAND => $operand); + break; + } + + if ($isAND && !$isBackslash) { + $tree[] = array(self::INDEX_BOOL_OPERATOR => self::BOOL_OPERATOR_AND, self::INDEX_OPERAND => $operand); + $operand = ''; + } elseif ($isOR && !$isBackslash) { + $tree[] = array(self::INDEX_BOOL_OPERATOR => self::BOOL_OPERATOR_OR, self::INDEX_OPERAND => $operand); + $operand = ''; + } else { + if ($isBackslash && ($isAND || $isOR)) { + $operand = substr($operand, 0, -1); + } + $operand .= $char; + } + $isBackslash = ($char == "\\"); + $i++; + } + return $tree; + } + + /** + * Given the array of parsed boolean logic, will return + * an array containing the full SQL string representing the filter, + * the needed joins and the values to bind to the query + * + * @throws Exception + * @return array SQL Query, Joins and Bind parameters + */ + public function getSql() + { + if ($this->isEmpty()) { + throw new Exception("Invalid segment, please specify a valid segment."); + } + $sql = ''; + $subExpression = false; + foreach ($this->tree as $expression) { + $operator = $expression[self::INDEX_BOOL_OPERATOR]; + $operand = $expression[self::INDEX_OPERAND]; + + if ($operator == self::BOOL_OPERATOR_OR + && !$subExpression + ) { + $sql .= ' ('; + $subExpression = true; + } else { + $sql .= ' '; + } + + $sql .= $operand; + + if ($operator == self::BOOL_OPERATOR_AND + && $subExpression + ) { + $sql .= ')'; + $subExpression = false; + } + + $sql .= " $operator"; + } + if ($subExpression) { + $sql .= ')'; + } + return array( + 'where' => $sql, + 'bind' => $this->valuesBind, + 'join' => implode(' ', $this->joins) + ); + } +} + diff --git a/www/analytics/core/SegmentExpression.php b/www/analytics/core/SegmentExpression.php deleted file mode 100644 index 96cac35c..00000000 --- a/www/analytics/core/SegmentExpression.php +++ /dev/null @@ -1,378 +0,0 @@ -='; - const MATCH_LESS_OR_EQUAL = '<='; - const MATCH_GREATER = '>'; - const MATCH_LESS = '<'; - const MATCH_CONTAINS = '=@'; - const MATCH_DOES_NOT_CONTAIN = '!@'; - - // Note: you can't write this in the API, but access this feature - // via field!= <- IS NOT NULL - // or via field== <- IS NULL / empty - const MATCH_IS_NOT_NULL_NOR_EMPTY = '::NOT_NULL'; - const MATCH_IS_NULL_OR_EMPTY = '::NULL'; - - // Special case, since we look up Page URLs/Page titles in a sub SQL query - const MATCH_ACTIONS_CONTAINS = 'IN'; - - const INDEX_BOOL_OPERATOR = 0; - const INDEX_OPERAND = 1; - - function __construct($string) - { - $this->string = $string; - $this->tree = $this->parseTree(); - } - - protected $joins = array(); - protected $valuesBind = array(); - protected $parsedTree = array(); - protected $tree = array(); - protected $parsedSubExpressions = array(); - - /** - * Given the array of parsed filters containing, for each filter, - * the boolean operator (AND/OR) and the operand, - * Will return the array where the filters are in SQL representation - * - * @throws Exception - * @return array - */ - public function parseSubExpressions() - { - $parsedSubExpressions = array(); - foreach ($this->tree as $id => $leaf) { - $operand = $leaf[self::INDEX_OPERAND]; - - $operand = urldecode($operand); - - $operator = $leaf[self::INDEX_BOOL_OPERATOR]; - $pattern = '/^(.+?)(' . self::MATCH_EQUAL . '|' - . self::MATCH_NOT_EQUAL . '|' - . self::MATCH_GREATER_OR_EQUAL . '|' - . self::MATCH_GREATER . '|' - . self::MATCH_LESS_OR_EQUAL . '|' - . self::MATCH_LESS . '|' - . self::MATCH_CONTAINS . '|' - . self::MATCH_DOES_NOT_CONTAIN - . '){1}(.*)/'; - $match = preg_match($pattern, $operand, $matches); - if ($match == 0) { - throw new Exception('The segment \'' . $operand . '\' is not valid.'); - } - - $leftMember = $matches[1]; - $operation = $matches[2]; - $valueRightMember = urldecode($matches[3]); - - // is null / is not null - if ($valueRightMember === '') { - if ($operation == self::MATCH_NOT_EQUAL) { - $operation = self::MATCH_IS_NOT_NULL_NOR_EMPTY; - } elseif ($operation == self::MATCH_EQUAL) { - $operation = self::MATCH_IS_NULL_OR_EMPTY; - } else { - throw new Exception('The segment \'' . $operand . '\' has no value specified. You can leave this value empty ' . - 'only when you use the operators: ' . self::MATCH_NOT_EQUAL . ' (is not) or ' . self::MATCH_EQUAL . ' (is)'); - } - } - - $parsedSubExpressions[] = array( - self::INDEX_BOOL_OPERATOR => $operator, - self::INDEX_OPERAND => array( - $leftMember, - $operation, - $valueRightMember, - )); - } - $this->parsedSubExpressions = $parsedSubExpressions; - return $parsedSubExpressions; - } - - /** - * Set the given expression - * @param $parsedSubExpressions - */ - public function setSubExpressionsAfterCleanup($parsedSubExpressions) - { - $this->parsedSubExpressions = $parsedSubExpressions; - } - - /** - * @param array $availableTables - */ - public function parseSubExpressionsIntoSqlExpressions(&$availableTables = array()) - { - $sqlSubExpressions = array(); - $this->valuesBind = array(); - $this->joins = array(); - - foreach ($this->parsedSubExpressions as $leaf) { - $operator = $leaf[self::INDEX_BOOL_OPERATOR]; - $operandDefinition = $leaf[self::INDEX_OPERAND]; - - $operand = $this->getSqlMatchFromDefinition($operandDefinition, $availableTables); - - if ($operand[1] !== null) { - $this->valuesBind[] = $operand[1]; - } - $operand = $operand[0]; - $sqlSubExpressions[] = array( - self::INDEX_BOOL_OPERATOR => $operator, - self::INDEX_OPERAND => $operand, - ); - } - - $this->tree = $sqlSubExpressions; - } - - /** - * Given an array representing one filter operand ( left member , operation , right member) - * Will return an array containing - * - the SQL substring, - * - the values to bind to this substring - * - * @param array $def - * @param array $availableTables - * @throws Exception - * @return array - */ - protected function getSqlMatchFromDefinition($def, &$availableTables) - { - $field = $def[0]; - $matchType = $def[1]; - $value = $def[2]; - - $alsoMatchNULLValues = false; - switch ($matchType) { - case self::MATCH_EQUAL: - $sqlMatch = '='; - break; - case self::MATCH_NOT_EQUAL: - $sqlMatch = '<>'; - $alsoMatchNULLValues = true; - break; - case self::MATCH_GREATER: - $sqlMatch = '>'; - break; - case self::MATCH_LESS: - $sqlMatch = '<'; - break; - case self::MATCH_GREATER_OR_EQUAL: - $sqlMatch = '>='; - break; - case self::MATCH_LESS_OR_EQUAL: - $sqlMatch = '<='; - break; - case self::MATCH_CONTAINS: - $sqlMatch = 'LIKE'; - $value = '%' . $this->escapeLikeString($value) . '%'; - break; - case self::MATCH_DOES_NOT_CONTAIN: - $sqlMatch = 'NOT LIKE'; - $value = '%' . $this->escapeLikeString($value) . '%'; - $alsoMatchNULLValues = true; - break; - - case self::MATCH_IS_NOT_NULL_NOR_EMPTY: - $sqlMatch = 'IS NOT NULL AND (' . $field . ' <> \'\' OR ' . $field . ' = 0)'; - $value = null; - break; - - case self::MATCH_IS_NULL_OR_EMPTY: - $sqlMatch = 'IS NULL OR ' . $field . ' = \'\' '; - $value = null; - break; - - case self::MATCH_ACTIONS_CONTAINS: - // this match type is not accessible from the outside - // (it won't be matched in self::parseSubExpressions()) - // it can be used internally to inject sub-expressions into the query. - // see Segment::getCleanedExpression() - $sqlMatch = 'IN (' . $value['SQL'] . ')'; - $value = $this->escapeLikeString($value['bind']); - break; - default: - throw new Exception("Filter contains the match type '" . $matchType . "' which is not supported"); - break; - } - - // We match NULL values when rows are excluded only when we are not doing a - $alsoMatchNULLValues = $alsoMatchNULLValues && !empty($value); - - if ($matchType === self::MATCH_ACTIONS_CONTAINS - || is_null($value) - ) { - $sqlExpression = "( $field $sqlMatch )"; - } else { - if ($alsoMatchNULLValues) { - $sqlExpression = "( $field IS NULL OR $field $sqlMatch ? )"; - } else { - $sqlExpression = "$field $sqlMatch ?"; - } - } - - $this->checkFieldIsAvailable($field, $availableTables); - - return array($sqlExpression, $value); - } - - /** - * Check whether the field is available - * If not, add it to the available tables - * - * @param string $field - * @param array $availableTables - */ - private function checkFieldIsAvailable($field, &$availableTables) - { - $fieldParts = explode('.', $field); - - $table = count($fieldParts) == 2 ? $fieldParts[0] : false; - - // remove sql functions from field name - // example: `HOUR(log_visit.visit_last_action_time)` gets `HOUR(log_visit` => remove `HOUR(` - $table = preg_replace('/^[A-Z_]+\(/', '', $table); - $tableExists = !$table || in_array($table, $availableTables); - - if (!$tableExists) { - $availableTables[] = $table; - } - } - - /** - * Escape the characters % and _ in the given string - * @param string $str - * @return string - */ - private function escapeLikeString($str) - { - $str = str_replace("%", "\%", $str); - $str = str_replace("_", "\_", $str); - return $str; - } - - /** - * Given a filter string, - * will parse it into an array where each row contains the boolean operator applied to it, - * and the operand - * - * @return array - */ - protected function parseTree() - { - $string = $this->string; - if (empty($string)) { - return array(); - } - $tree = array(); - $i = 0; - $length = strlen($string); - $isBackslash = false; - $operand = ''; - while ($i <= $length) { - $char = $string[$i]; - - $isAND = ($char == self::AND_DELIMITER); - $isOR = ($char == self::OR_DELIMITER); - $isEnd = ($length == $i + 1); - - if ($isEnd) { - if ($isBackslash && ($isAND || $isOR)) { - $operand = substr($operand, 0, -1); - } - $operand .= $char; - $tree[] = array(self::INDEX_BOOL_OPERATOR => '', self::INDEX_OPERAND => $operand); - break; - } - - if ($isAND && !$isBackslash) { - $tree[] = array(self::INDEX_BOOL_OPERATOR => 'AND', self::INDEX_OPERAND => $operand); - $operand = ''; - } elseif ($isOR && !$isBackslash) { - $tree[] = array(self::INDEX_BOOL_OPERATOR => 'OR', self::INDEX_OPERAND => $operand); - $operand = ''; - } else { - if ($isBackslash && ($isAND || $isOR)) { - $operand = substr($operand, 0, -1); - } - $operand .= $char; - } - $isBackslash = ($char == "\\"); - $i++; - } - return $tree; - } - - /** - * Given the array of parsed boolean logic, will return - * an array containing the full SQL string representing the filter, - * the needed joins and the values to bind to the query - * - * @throws Exception - * @return array SQL Query, Joins and Bind parameters - */ - public function getSql() - { - if (count($this->tree) == 0) { - throw new Exception("Invalid segment, please specify a valid segment."); - } - $bind = array(); - $sql = ''; - $subExpression = false; - foreach ($this->tree as $expression) { - $operator = $expression[self::INDEX_BOOL_OPERATOR]; - $operand = $expression[self::INDEX_OPERAND]; - - if ($operator == 'OR' - && !$subExpression - ) { - $sql .= ' ('; - $subExpression = true; - } else { - $sql .= ' '; - } - - $sql .= $operand; - - if ($operator == 'AND' - && $subExpression - ) { - $sql .= ')'; - $subExpression = false; - } - - $sql .= " $operator"; - } - if ($subExpression) { - $sql .= ')'; - } - return array( - 'where' => $sql, - 'bind' => $this->valuesBind, - 'join' => implode(' ', $this->joins) - ); - } -} diff --git a/www/analytics/core/Sequence.php b/www/analytics/core/Sequence.php new file mode 100644 index 00000000..7a25c239 --- /dev/null +++ b/www/analytics/core/Sequence.php @@ -0,0 +1,127 @@ +getNextId(); + * $db->insert('anytable', array('id' => $id, '...' => '...')); + */ +class Sequence +{ + const TABLE_NAME = 'sequence'; + /** + * @var string + */ + private $name; + + /** + * @var AdapterInterface + */ + private $db; + + /** + * @var string + */ + private $table; + + /** + * The name of the table or sequence you want to get an id for. + * + * @param string $name eg 'archive_numeric_2014_11' + * @param AdapterInterface $db You can optionally pass a DB adapter to make it work against another database. + * @param string|null $tablePrefix + */ + public function __construct($name, $db = null, $tablePrefix = null) + { + $this->name = $name; + $this->db = $db ?: Db::get(); + $this->table = $this->getTableName($tablePrefix); + } + + /** + * Creates / initializes a new sequence. + * + * @param int $initialValue + * @return int The actually used value to initialize the table. + * + * @throws \Exception in case a sequence having this name already exists. + */ + public function create($initialValue = 0) + { + $initialValue = (int) $initialValue; + + $this->db->insert($this->table, array('name' => $this->name, 'value' => $initialValue)); + + return $initialValue; + } + + /** + * Returns true if the sequence exist. + * + * @return bool + */ + public function exists() + { + $query = $this->db->query('SELECT * FROM ' . $this->table . ' WHERE name = ?', $this->name); + + return $query->rowCount() > 0; + } + + /** + * Get / allocate / reserve a new id for the current sequence. Important: Getting the next id will fail in case + * no such sequence exists. Make sure to create one if needed, see {@link create()}. + * + * @return int + * @throws Exception + */ + public function getNextId() + { + $sql = 'UPDATE ' . $this->table . ' SET value = LAST_INSERT_ID(value + 1) WHERE name = ?'; + + $result = $this->db->query($sql, array($this->name)); + $rowCount = $result->rowCount(); + + if (1 !== $rowCount) { + throw new Exception("Sequence '" . $this->name . "' not found."); + } + + $createdId = $this->db->lastInsertId(); + + return (int) $createdId; + } + + /** + * Returns the current max id. + * @return int + * @internal + */ + public function getCurrentId() + { + $sql = 'SELECT value FROM ' . $this->table . ' WHERE name = ?'; + + $id = $this->db->fetchOne($sql, array($this->name)); + + if (!empty($id) || '0' === $id || 0 === $id) { + return (int) $id; + } + } + + private function getTableName($prefix) + { + return ($prefix !== null) ? $prefix . self::TABLE_NAME : Common::prefixTable(self::TABLE_NAME); + } +} diff --git a/www/analytics/core/Session.php b/www/analytics/core/Session.php index 787affe2..965553b5 100644 --- a/www/analytics/core/Session.php +++ b/www/analytics/core/Session.php @@ -1,6 +1,6 @@ General['session_save_handler'] === 'dbtable' + } elseif ($config->General['session_save_handler'] === 'dbtable' || in_array($currentSaveHandler, array('user', 'mm')) ) { // We consider these to be misconfigurations, in that: @@ -109,19 +113,18 @@ class Session extends Zend_Session } try { - Zend_Session::start(); + parent::start(); register_shutdown_function(array('Zend_Session', 'writeClose'), true); } catch (Exception $e) { - Log::warning('Unable to start session: ' . $e->getMessage()); + Log::error('Unable to start session: ' . $e->getMessage()); $enableDbSessions = ''; if (DbHelper::isInstalled()) { $enableDbSessions = "
If you still experience issues after trying these changes, - we recommend that you enable database session storage."; + we recommend that you enable database session storage."; } - $pathToSessions = Filechecks::getErrorMessageMissingPermissions(Filesystem::getPathToPiwikRoot() . '/tmp/sessions/'); - $pathToSessions = SettingsPiwik::rewriteTmpPathWithHostname($pathToSessions); + $pathToSessions = Filechecks::getErrorMessageMissingPermissions(self::getSessionsDirectory()); $message = sprintf("Error: %s %s %s\n
Debug: the original error was \n%s
", Piwik::translate('General_ExceptionUnableToStartSession'), $pathToSessions, @@ -129,7 +132,10 @@ class Session extends Zend_Session $e->getMessage() ); - Piwik_ExitWithMessage($message); + $ex = new MissingFilePermissionException($message, $e->getCode(), $e); + $ex->setIsHtmlMessage(); + + throw $ex; } } @@ -140,7 +146,11 @@ class Session extends Zend_Session */ public static function getSessionsDirectory() { - $path = PIWIK_USER_PATH . '/tmp/sessions'; - return SettingsPiwik::rewriteTmpPathWithHostname($path); + return StaticContainer::get('path.tmp') . '/sessions'; + } + + public static function close() + { + parent::writeClose(); } } diff --git a/www/analytics/core/Session/SaveHandler/DbTable.php b/www/analytics/core/Session/SaveHandler/DbTable.php index 20494fe9..03b4afa8 100644 --- a/www/analytics/core/Session/SaveHandler/DbTable.php +++ b/www/analytics/core/Session/SaveHandler/DbTable.php @@ -1,6 +1,6 @@ config = $config; $this->maxLifetime = ini_get('session.gc_maxlifetime'); @@ -78,8 +78,9 @@ class DbTable implements Zend_Session_SaveHandler_Interface . ' AND ' . $this->config['modifiedColumn'] . ' + ' . $this->config['lifetimeColumn'] . ' >= ?'; $result = Db::get()->fetchOne($sql, array($id, time())); - if (!$result) + if (!$result) { $result = ''; + } return $result; } @@ -118,8 +119,7 @@ class DbTable implements Zend_Session_SaveHandler_Interface */ public function destroy($id) { - $sql = 'DELETE FROM ' . $this->config['name'] - . ' WHERE ' . $this->config['primary'] . ' = ?'; + $sql = 'DELETE FROM ' . $this->config['name'] . ' WHERE ' . $this->config['primary'] . ' = ?'; Db::get()->query($sql, array($id)); diff --git a/www/analytics/core/Session/SessionNamespace.php b/www/analytics/core/Session/SessionNamespace.php index 8f31a7e5..90a46625 100644 --- a/www/analytics/core/Session/SessionNamespace.php +++ b/www/analytics/core/Session/SessionNamespace.php @@ -1,6 +1,6 @@ findComponents('Settings', 'Piwik\\Plugin\\Settings'); + $byPluginName = array(); - $settings = array(); - - $pluginNames = PluginManager::getInstance()->getLoadedPluginsName(); - foreach ($pluginNames as $pluginName) { - $settings[$pluginName] = self::getPluginSettingsClass($pluginName); + foreach ($settings as $setting) { + $byPluginName[$setting->getPluginName()] = $setting; } - static::$settings = array_filter($settings); + static::$settings = $byPluginName; } return static::$settings; @@ -63,7 +62,14 @@ class Manager */ public static function cleanupPluginSettings($pluginName) { - $settings = self::getPluginSettingsClass($pluginName); + $pluginManager = PluginManager::getInstance(); + + if (!$pluginManager->isPluginLoaded($pluginName)) { + return; + } + + $plugin = $pluginManager->loadPlugin($pluginName); + $settings = $plugin->findComponent('Settings', 'Piwik\\Plugin\\Settings'); if (!empty($settings)) { $settings->removeAllPluginSettings(); @@ -95,39 +101,57 @@ class Manager return $settingsForUser; } - public static function hasPluginSettingsForCurrentUser($pluginName) + public static function hasSystemPluginSettingsForCurrentUser($pluginName) { - $pluginNames = array_keys(static::getPluginSettingsForCurrentUser()); + $pluginNames = static::getPluginNamesHavingSystemSettings(); return in_array($pluginName, $pluginNames); } /** - * Detects whether there are settings for activated plugins available that the current user can change. + * Detects whether there are user settings for activated plugins available that the current user can change. * * @return bool */ - public static function hasPluginsSettingsForCurrentUser() + public static function hasUserPluginsSettingsForCurrentUser() { $settings = static::getPluginSettingsForCurrentUser(); + foreach ($settings as $setting) { + foreach ($setting->getSettingsForCurrentUser() as $set) { + if ($set instanceof UserSetting) { + return true; + } + } + } + + return false; + } + + public static function getPluginNamesHavingSystemSettings() + { + $settings = static::getPluginSettingsForCurrentUser(); + $plugins = array(); + + foreach ($settings as $pluginName => $setting) { + foreach ($setting->getSettingsForCurrentUser() as $set) { + if ($set instanceof SystemSetting) { + $plugins[] = $pluginName; + } + } + } + + return array_unique($plugins); + } + /** + * Detects whether there are system settings for activated plugins available that the current user can change. + * + * @return bool + */ + public static function hasSystemPluginsSettingsForCurrentUser() + { + $settings = static::getPluginNamesHavingSystemSettings(); + return !empty($settings); } - - /** - * Tries to find a settings class for the specified plugin name. Returns null in case the plugin does not specify - * any settings, an instance of the settings class otherwise. - * - * @param string $pluginName - * @return \Piwik\Plugin\Settings|null - */ - private static function getPluginSettingsClass($pluginName) - { - $klassName = 'Piwik\\Plugins\\' . $pluginName . '\\Settings'; - - if (class_exists($klassName) && is_subclass_of($klassName, 'Piwik\\Plugin\\Settings')) { - return new $klassName($pluginName); - } - } - } diff --git a/www/analytics/core/Settings/Setting.php b/www/analytics/core/Settings/Setting.php index c36aa246..bf8947b1 100644 --- a/www/analytics/core/Settings/Setting.php +++ b/www/analytics/core/Settings/Setting.php @@ -1,6 +1,6 @@ 3)`. Attributes will be escaped before outputting. - * + * * @var array */ public $uiControlAttributes = array(); /** * The list of all available values for this setting. If null, the setting can have any value. - * + * * If supplied, this field should be an array mapping available values with their prettified * display value. Eg, if set to `array('nb_visits' => 'Visits', 'nb_actions' => 'Actions')`, * the UI will display **Visits** and **Actions**, and when the user selects one, Piwik will * set the setting to **nb_visits** or **nb_actions** respectively. - * + * * The setting value will be validated if this field is set. If the value is not one of the * available values, an error will be triggered. - * + * * _Note: If a custom validator is supplied (see {@link $validate}), the setting value will * not be validated._ * @@ -63,7 +66,7 @@ abstract class Setting /** * Text that will appear above this setting's section in the _Plugin Settings_ admin page. - * + * * @var null|string */ public $introduction = null; @@ -71,7 +74,7 @@ abstract class Setting /** * Text that will appear directly underneath the setting title in the _Plugin Settings_ admin * page. If set, should be a short description of the setting. - * + * * @var null|string */ public $description = null; @@ -80,20 +83,20 @@ abstract class Setting * Text that will appear next to the setting's section in the _Plugin Settings_ admin page. If set, * it should contain information about the setting that is more specific than a general description, * such as the format of the setting value if it has a special format. - * + * * @var null|string */ public $inlineHelp = null; /** * A closure that does some custom validation on the setting before the setting is persisted. - * + * * The closure should take two arguments: the setting value and the {@link Setting} instance being * validated. If the value is found to be invalid, the closure should throw an exception with * a message that describes the error. - * + * * **Example** - * + * * $setting->validate = function ($value, Setting $setting) { * if ($value > 60) { * throw new \Exception('The time limit is not allowed to be greater than 60 minutes.'); @@ -107,13 +110,13 @@ abstract class Setting /** * A closure that transforms the setting value. If supplied, this closure will be executed after * the setting has been validated. - * + * * _Note: If a transform is supplied, the setting's {@link $type} has no effect. This means the * transformation function will be responsible for casting the setting value to the appropriate * data type._ * * **Example** - * + * * $setting->transform = function ($value, Setting $setting) { * if ($value > 30) { * $value = 30; @@ -128,7 +131,7 @@ abstract class Setting /** * Default value of this setting. - * + * * The default value is not casted to the appropriate data type. This means _**you**_ have to make * sure the value is of the correct type. * @@ -145,12 +148,12 @@ abstract class Setting protected $key; protected $name; - protected $displayedForCurrentUser = false; /** * @var StorageInterface */ private $storage; + protected $pluginName; /** * Constructor. @@ -168,7 +171,7 @@ abstract class Setting /** * Returns the setting's persisted name, eg, `'refreshInterval'`. - * + * * @return string */ public function getName() @@ -177,46 +180,142 @@ abstract class Setting } /** - * Returns `true` if this setting can be displayed for the current user, `false` if otherwise. - * + * Returns `true` if this setting is writable for the current user, `false` if otherwise. In case it returns + * writable for the current user it will be visible in the Plugin settings UI. + * * @return bool */ - public function canBeDisplayedForCurrentUser() + public function isWritableByCurrentUser() { - return $this->displayedForCurrentUser; + return false; + } + + /** + * Returns `true` if this setting can be displayed for the current user, `false` if otherwise. + * + * @return bool + */ + public function isReadableByCurrentUser() + { + return false; } /** * Sets the object used to persist settings. - * - * @return StorageInterface + * + * @param StorageInterface $storage */ public function setStorage(StorageInterface $storage) { $this->storage = $storage; } + /** + * @internal + * @ignore + * @return StorageInterface + */ + public function getStorage() + { + return $this->storage; + } + + /** + * Sets th name of the plugin the setting belongs to + * + * @param string $pluginName + */ + public function setPluginName($pluginName) + { + $this->pluginName = $pluginName; + } + /** * Returns the previously persisted setting value. If no value was set, the default value * is returned. - * + * * @return mixed * @throws \Exception If the current user is not allowed to change the value of this setting. */ public function getValue() { - return $this->storage->getSettingValue($this); + $this->checkHasEnoughReadPermission(); + + return $this->storage->getValue($this); + } + + /** + * Returns the previously persisted setting value. If no value was set, the default value + * is returned. + * + * @return mixed + * @throws \Exception If the current user is not allowed to change the value of this setting. + */ + public function removeValue() + { + $this->checkHasEnoughWritePermission(); + + return $this->storage->deleteValue($this); } /** * Sets and persists this setting's value overwriting any existing value. - * + * * @param mixed $value * @throws \Exception If the current user is not allowed to change the value of this setting. */ public function setValue($value) { - return $this->storage->setSettingValue($this, $value); + $this->validateValue($value); + + if ($this->transform && $this->transform instanceof \Closure) { + $value = call_user_func($this->transform, $value, $this); + } elseif (isset($this->type)) { + settype($value, $this->type); + } + + return $this->storage->setValue($this, $value); + } + + private function validateValue($value) + { + $this->checkHasEnoughWritePermission(); + + if ($this->validate && $this->validate instanceof \Closure) { + call_user_func($this->validate, $value, $this); + } + } + + /** + * @throws \Exception + */ + private function checkHasEnoughWritePermission() + { + // When the request is a Tracker request, allow plugins to write settings + if (SettingsServer::isTrackerApiRequest()) { + return; + } + + if (!$this->isWritableByCurrentUser()) { + $errorMsg = Piwik::translate('CoreAdminHome_PluginSettingChangeNotAllowed', array($this->getName(), $this->pluginName)); + throw new \Exception($errorMsg); + } + } + + /** + * @throws \Exception + */ + private function checkHasEnoughReadPermission() + { + // When the request is a Tracker request, allow plugins to read settings + if (SettingsServer::isTrackerApiRequest()) { + return; + } + + if (!$this->isReadableByCurrentUser()) { + $errorMsg = Piwik::translate('CoreAdminHome_PluginSettingReadNotAllowed', array($this->getName(), $this->pluginName)); + throw new \Exception($errorMsg); + } } /** @@ -231,7 +330,7 @@ abstract class Setting /** * Returns the display order. The lower the return value, the earlier the setting will be displayed. - * + * * @return int */ public function getOrder() diff --git a/www/analytics/core/Settings/Storage.php b/www/analytics/core/Settings/Storage.php new file mode 100644 index 00000000..131c01b1 --- /dev/null +++ b/www/analytics/core/Settings/Storage.php @@ -0,0 +1,148 @@ + [setting-value] ). + * + * @var array + */ + protected $settingsValues = array(); + + // for lazy loading of setting values + private $settingValuesLoaded = false; + + private $pluginName; + + public function __construct($pluginName) + { + $this->pluginName = $pluginName; + } + + /** + * Saves (persists) the current setting values in the database. + */ + public function save() + { + $this->loadSettingsIfNotDoneYet(); + + Option::set($this->getOptionKey(), serialize($this->settingsValues)); + } + + /** + * Removes all settings for this plugin from the database. Useful when uninstalling + * a plugin. + */ + public function deleteAllValues() + { + $this->deleteSettingsFromStorage(); + + $this->settingsValues = array(); + $this->settingValuesLoaded = false; + } + + protected function deleteSettingsFromStorage() + { + Option::delete($this->getOptionKey()); + } + + /** + * Returns the current value for a setting. If no value is stored, the default value + * is be returned. + * + * @param Setting $setting + * @return mixed + * @throws \Exception If the setting does not exist or if the current user is not allowed to change the value + * of this setting. + */ + public function getValue(Setting $setting) + { + $this->loadSettingsIfNotDoneYet(); + + if (array_key_exists($setting->getKey(), $this->settingsValues)) { + return $this->settingsValues[$setting->getKey()]; + } + + return $setting->defaultValue; + } + + /** + * Sets (overwrites) the value of a setting in memory. To persist the change, {@link save()} must be + * called afterwards, otherwise the change has no effect. + * + * Before the setting is changed, the {@link Piwik\Settings\Setting::$validate} and + * {@link Piwik\Settings\Setting::$transform} closures will be invoked (if defined). If there is no validation + * filter, the setting value will be casted to the appropriate data type. + * + * @param Setting $setting + * @param string $value + * @throws \Exception If the setting does not exist or if the current user is not allowed to change the value + * of this setting. + */ + public function setValue(Setting $setting, $value) + { + $this->loadSettingsIfNotDoneYet(); + + $this->settingsValues[$setting->getKey()] = $value; + } + + /** + * Unsets a setting value in memory. To persist the change, {@link save()} must be + * called afterwards, otherwise the change has no effect. + * + * @param Setting $setting + */ + public function deleteValue(Setting $setting) + { + $this->loadSettingsIfNotDoneYet(); + + $key = $setting->getKey(); + + if (array_key_exists($key, $this->settingsValues)) { + unset($this->settingsValues[$key]); + } + } + + public function getOptionKey() + { + return 'Plugin_' . $this->pluginName . '_Settings'; + } + + private function loadSettingsIfNotDoneYet() + { + if ($this->settingValuesLoaded) { + return; + } + + $this->settingValuesLoaded = true; + $this->settingsValues = $this->loadSettings(); + } + + protected function loadSettings() + { + $values = Option::get($this->getOptionKey()); + + if (!empty($values)) { + return unserialize($values); + } + + return array(); + } +} diff --git a/www/analytics/core/Settings/Storage/Factory.php b/www/analytics/core/Settings/Storage/Factory.php new file mode 100644 index 00000000..87d54ad3 --- /dev/null +++ b/www/analytics/core/Settings/Storage/Factory.php @@ -0,0 +1,28 @@ +displayedForCurrentUser = Piwik::hasUserSuperUserAccess(); + $this->writableByCurrentUser = Piwik::hasUserSuperUserAccess(); + $this->readableByCurrentUser = $this->writableByCurrentUser; + } + + /** + * Returns `true` if this setting is writable for the current user, `false` if otherwise. In case it returns + * writable for the current user it will be visible in the Plugin settings UI. + * + * @return bool + */ + public function isWritableByCurrentUser() + { + if ($this->hasConfigValue()) { + return false; + } + + return $this->writableByCurrentUser; + } + + /** + * Returns `true` if this setting can be displayed for the current user, `false` if otherwise. + * + * @return bool + */ + public function isReadableByCurrentUser() + { + return $this->readableByCurrentUser; } /** * Returns the display order. System settings are displayed before user settings. - * + * * @return int */ public function getOrder() { return 30; } + + public function getValue() + { + $defaultValue = parent::getValue(); // we access value first to make sure permissions are checked + + $configValue = $this->getValueFromConfig(); + + if (isset($configValue)) { + $defaultValue = $configValue; + settype($defaultValue, $this->type); + } + + return $defaultValue; + } + + private function hasConfigValue() + { + $value = $this->getValueFromConfig(); + return isset($value); + } + + private function getValueFromConfig() + { + $config = Config::getInstance()->{$this->pluginName}; + + if (!empty($config) && array_key_exists($this->name, $config)) { + return $config[$this->name]; + } + } + } diff --git a/www/analytics/core/Settings/UserSetting.php b/www/analytics/core/Settings/UserSetting.php index caeb918e..aa3a08c4 100644 --- a/www/analytics/core/Settings/UserSetting.php +++ b/www/analytics/core/Settings/UserSetting.php @@ -1,6 +1,6 @@ setUserLogin($userLogin); + } - $this->displayedForCurrentUser = Piwik::isUserHasSomeViewAccess(); + /** + * Returns `true` if this setting can be displayed for the current user, `false` if otherwise. + * + * @return bool + */ + public function isReadableByCurrentUser() + { + return $this->isWritableByCurrentUser(); + } + + /** + * Returns `true` if this setting can be displayed for the current user, `false` if otherwise. + * + * @return bool + */ + public function isWritableByCurrentUser() + { + if (isset($this->hasReadAndWritePermission)) { + return $this->hasReadAndWritePermission; + } + + $this->hasReadAndWritePermission = Piwik::isUserHasSomeViewAccess(); + + return $this->hasReadAndWritePermission; } /** * Returns the display order. User settings are displayed after system settings. - * + * * @return int */ public function getOrder() @@ -100,16 +131,13 @@ class UserSetting extends Setting $pluginsSettings = Manager::getAllPluginSettings(); foreach ($pluginsSettings as $pluginSettings) { - $settings = $pluginSettings->getSettings(); foreach ($settings as $setting) { - if ($setting instanceof UserSetting) { $setting->setUserLogin($userLogin); - $pluginSettings->removeSettingValue($setting); + $setting->removeValue(); } - } $pluginSettings->save(); diff --git a/www/analytics/core/SettingsPiwik.php b/www/analytics/core/SettingsPiwik.php index 969e21a3..b0df27b2 100644 --- a/www/analytics/core/SettingsPiwik.php +++ b/www/analytics/core/SettingsPiwik.php @@ -1,6 +1,6 @@ General['disable_checks_usernames_attributes'] == 0; } - /** - * @see getKnownSegmentsToArchive - * - * @var array - */ - public static $cachedKnownSegmentsToArchive = null; - /** * Returns every stored segment to pre-process for each site during cron archiving. * @@ -55,83 +50,98 @@ class SettingsPiwik */ public static function getKnownSegmentsToArchive() { - if (self::$cachedKnownSegmentsToArchive === null) { - $segments = Config::getInstance()->Segments; - $segmentsToProcess = isset($segments['Segments']) ? $segments['Segments'] : array(); - - /** - * Triggered during the cron archiving process to collect segments that - * should be pre-processed for all websites. The archiving process will be launched - * for each of these segments when archiving data. - * - * This event can be used to add segments to be pre-processed. If your plugin depends - * on data from a specific segment, this event could be used to provide enhanced - * performance. - * - * _Note: If you just want to add a segment that is managed by the user, use the - * SegmentEditor API._ - * - * **Example** - * - * Piwik::addAction('Segments.getKnownSegmentsToArchiveAllSites', function (&$segments) { - * $segments[] = 'country=jp;city=Tokyo'; - * }); - * - * @param array &$segmentsToProcess List of segment definitions, eg, - * - * array( - * 'browserCode=ff;resolution=800x600', - * 'country=jp;city=Tokyo' - * ) - * - * Add segments to this array in your event handler. - */ - Piwik::postEvent('Segments.getKnownSegmentsToArchiveAllSites', array(&$segmentsToProcess)); - - self::$cachedKnownSegmentsToArchive = array_unique($segmentsToProcess); + $cacheId = 'KnownSegmentsToArchive'; + $cache = PiwikCache::getTransientCache(); + if ($cache->contains($cacheId)) { + return $cache->fetch($cacheId); } - return self::$cachedKnownSegmentsToArchive; + $segments = Config::getInstance()->Segments; + $segmentsToProcess = isset($segments['Segments']) ? $segments['Segments'] : array(); + + /** + * Triggered during the cron archiving process to collect segments that + * should be pre-processed for all websites. The archiving process will be launched + * for each of these segments when archiving data. + * + * This event can be used to add segments to be pre-processed. If your plugin depends + * on data from a specific segment, this event could be used to provide enhanced + * performance. + * + * _Note: If you just want to add a segment that is managed by the user, use the + * SegmentEditor API._ + * + * **Example** + * + * Piwik::addAction('Segments.getKnownSegmentsToArchiveAllSites', function (&$segments) { + * $segments[] = 'country=jp;city=Tokyo'; + * }); + * + * @param array &$segmentsToProcess List of segment definitions, eg, + * + * array( + * 'browserCode=ff;resolution=800x600', + * 'country=jp;city=Tokyo' + * ) + * + * Add segments to this array in your event handler. + */ + Piwik::postEvent('Segments.getKnownSegmentsToArchiveAllSites', array(&$segmentsToProcess)); + + $segmentsToProcess = array_unique($segmentsToProcess); + + $cache->save($cacheId, $segmentsToProcess); + return $segmentsToProcess; } /** * Returns the list of stored segments to pre-process for an individual site when executing * cron archiving. - * + * * @param int $idSite The ID of the site to get stored segments for. - * @return string The list of stored segments that apply to the requested site. + * @return string[] The list of stored segments that apply to the requested site. */ public static function getKnownSegmentsToArchiveForSite($idSite) { - $segments = array(); + $cacheId = 'KnownSegmentsToArchiveForSite' . $idSite; + $cache = PiwikCache::getTransientCache(); + if ($cache->contains($cacheId)) { + return $cache->fetch($cacheId); + } + $segments = array(); /** * Triggered during the cron archiving process to collect segments that * should be pre-processed for one specific site. The archiving process will be launched * for each of these segments when archiving data for that one site. - * + * * This event can be used to add segments to be pre-processed for one site. - * + * * _Note: If you just want to add a segment that is managed by the user, you should use the * SegmentEditor API._ - * + * * **Example** - * + * * Piwik::addAction('Segments.getKnownSegmentsToArchiveForSite', function (&$segments, $idSite) { * $segments[] = 'country=jp;city=Tokyo'; * }); - * + * * @param array &$segmentsToProcess List of segment definitions, eg, - * + * * array( * 'browserCode=ff;resolution=800x600', * 'country=JP;city=Tokyo' * ) - * + * * Add segments to this array in your event handler. * @param int $idSite The ID of the site to get segments for. */ Piwik::postEvent('Segments.getKnownSegmentsToArchiveForSite', array(&$segments, $idSite)); + + $segments = array_unique($segments); + + $cache->save($cacheId, $segments); + return $segments; } @@ -159,7 +169,7 @@ class SettingsPiwik $isPiwikCoreDispatching = defined('PIWIK_ENABLE_DISPATCH') && PIWIK_ENABLE_DISPATCH; if (Common::isPhpCliMode() - // in case archive.php is triggered with domain localhost + // in case core:archive command is triggered (often with localhost domain) || SettingsServer::isArchivePhpTriggered() // When someone else than core is dispatching this request then we return the URL as it is read only || !$isPiwikCoreDispatching @@ -169,17 +179,23 @@ class SettingsPiwik $currentUrl = Common::sanitizeInputValue(Url::getCurrentUrlWithoutFileName()); + // when script is called from /misc/cron/archive.php, Piwik URL is /index.php + $currentUrl = str_replace("/misc/cron", "", $currentUrl); + if (empty($url) // if URL changes, always update the cache || $currentUrl != $url ) { - if (strlen($currentUrl) >= strlen('http://a/')) { + $host = Url::getHostFromUrl($url); + + if (strlen($currentUrl) >= strlen('http://a/') + && !Url::isLocalHost($host)) { self::overwritePiwikUrl($currentUrl); } $url = $currentUrl; } - if(ProxyHttp::isHttps()) { + if (ProxyHttp::isHttps()) { $url = str_replace("http://", "https://", $url); } return $url; @@ -191,11 +207,29 @@ class SettingsPiwik */ public static function isPiwikInstalled() { - $config = Config::getInstance()->getLocalConfigPath(); + $config = Config::getInstance()->getLocalPath(); $exists = file_exists($config); // Piwik is installed if the config file is found - return $exists; + if (!$exists) { + return false; + } + + $general = Config::getInstance()->General; + + $isInstallationInProgress = false; + if (array_key_exists('installation_in_progress', $general)) { + $isInstallationInProgress = (bool) $general['installation_in_progress']; + } + if ($isInstallationInProgress) { + return false; + } + + // Check that the database section is really set, ie. file is not empty + if (empty(Config::getInstance()->database['username'])) { + return false; + } + return true; } /** @@ -212,7 +246,7 @@ class SettingsPiwik /** * Returns true if unique visitors should be processed for the given period type. - * + * * Unique visitor processing is controlled by the `[General] enable_processing_unique_visitors_...` * INI config options. By default, unique visitors are processed only for day/week/month periods. * @@ -237,38 +271,31 @@ class SettingsPiwik return $result; } - /** - * If Piwik uses per-domain config file, also make tmp/ folder per-domain - * @param $path - * @return string - * @throws \Exception - */ - public static function rewriteTmpPathWithHostname($path) - { - $tmp = '/tmp/'; - $path = self::rewritePathAppendHostname($path, $tmp); - return $path; - } - /** * If Piwik uses per-domain config file, make sure CustomLogo is unique * @param $path * @return mixed */ - public static function rewriteMiscUserPathWithHostname($path) + public static function rewriteMiscUserPathWithInstanceId($path) { $tmp = 'misc/user/'; - $path = self::rewritePathAppendHostname($path, $tmp); + $path = self::rewritePathAppendPiwikInstanceId($path, $tmp); return $path; } /** * Returns true if the Piwik server appears to be working. * + * If the Piwik server is in an error state (eg. some directories are not writable and Piwik displays error message), + * or if the Piwik server is "offline", + * this will return false.. + * * @param $piwikServerUrl + * @param bool $acceptInvalidSSLCertificates + * @throws Exception * @return bool */ - static public function checkPiwikServerWorking($piwikServerUrl, $acceptInvalidSSLCertificates = false) + public static function checkPiwikServerWorking($piwikServerUrl, $acceptInvalidSSLCertificates = false) { // Now testing if the webserver is running try { @@ -285,9 +312,22 @@ class SettingsPiwik } catch (Exception $e) { $fetched = "ERROR fetching: " . $e->getMessage(); } - $expectedString = 'plugins/CoreHome/images/favicon.ico'; + // this will match when Piwik not installed yet, or favicon not customised + $expectedStringAlt = 'plugins/CoreHome/images/favicon.png'; - if (strpos($fetched, $expectedString) === false) { + // this will match when Piwik is installed and favicon has been customised + $expectedString = 'misc/user/'; + + // see checkPiwikIsNotInstalled() + $expectedStringAlreadyInstalled = 'piwik-is-already-installed'; + + $expectedStringNotFound = strpos($fetched, $expectedString) === false + && strpos($fetched, $expectedStringAlt) === false + && strpos($fetched, $expectedStringAlreadyInstalled) === false; + + $hasError = false !== strpos($fetched, PAGE_TITLE_WHEN_ERROR); + + if ($hasError || $expectedStringNotFound) { throw new Exception("\nPiwik should be running at: " . $piwikServerUrl . " but this URL returned an unexpected response: '" @@ -295,10 +335,21 @@ class SettingsPiwik } } + /** + * Returns true if Piwik is deployed using git + * FAQ: http://piwik.org/faq/how-to-install/faq_18271/ + * + * @return bool + */ + public static function isGitDeployment() + { + return file_exists(PIWIK_INCLUDE_PATH . '/.git/HEAD'); + } + public static function getCurrentGitBranch() { $file = PIWIK_INCLUDE_PATH . '/.git/HEAD'; - if(!file_exists($file)) { + if (!file_exists($file)) { return ''; } $firstLineOfGitHead = file($file); @@ -321,10 +372,10 @@ class SettingsPiwik * @return mixed * @throws \Exception */ - protected static function rewritePathAppendHostname($pathToRewrite, $leadingPathToAppendHostnameTo) + protected static function rewritePathAppendPiwikInstanceId($pathToRewrite, $leadingPathToAppendHostnameTo) { - $hostname = self::getConfigHostname(); - if (empty($hostname)) { + $instanceId = self::getPiwikInstanceId(); + if (empty($instanceId)) { return $pathToRewrite; } @@ -332,7 +383,7 @@ class SettingsPiwik throw new Exception("The path $pathToRewrite was expected to contain the string $leadingPathToAppendHostnameTo"); } - $tmpToReplace = $leadingPathToAppendHostnameTo . $hostname . '/'; + $tmpToReplace = $leadingPathToAppendHostnameTo . $instanceId . '/'; // replace only the latest occurrence (in case path contains twice /tmp) $pathToRewrite = substr_replace($pathToRewrite, $tmpToReplace, $posTmp, strlen($leadingPathToAppendHostnameTo)); @@ -340,18 +391,30 @@ class SettingsPiwik } /** - * @return bool|string + * @throws \Exception + * @return string or False if not set */ - protected static function getConfigHostname() + protected static function getPiwikInstanceId() { - $configByHost = false; - try { - $configByHost = Config::getInstance()->getConfigHostnameIfSet(); - return $configByHost; - } catch (Exception $e) { - // Config file not found + // until Piwik is installed, we use hostname as instance_id + if (!self::isPiwikInstalled() + && Common::isPhpCliMode()) { + // enterprise:install use case + return Config::getHostname(); } - return $configByHost; + + // config.ini.php not ready yet, instance_id will not be set + if (!Config::getInstance()->existsLocalConfig()) { + return false; + } + + $instanceId = @Config::getInstance()->General['instance_id']; + if (!empty($instanceId)) { + return $instanceId; + } + + // do not rewrite the path as Piwik uses the standard config.ini.php file + return false; } /** @@ -367,6 +430,20 @@ class SettingsPiwik */ public static function isHttpsForced() { + if (!SettingsPiwik::isPiwikInstalled()) { + // Only enable this feature after Piwik is already installed + return false; + } return Config::getInstance()->General['force_ssl'] == 1; } + + /** + * Note: this config settig is also checked in the InterSites plugin + * + * @return bool + */ + public static function isSameFingerprintAcrossWebsites() + { + return (bool)Config::getInstance()->Tracker['enable_fingerprinting_across_websites']; + } } diff --git a/www/analytics/core/SettingsServer.php b/www/analytics/core/SettingsServer.php index e666c3fa..d84e3ff4 100644 --- a/www/analytics/core/SettingsServer.php +++ b/www/analytics/core/SettingsServer.php @@ -1,6 +1,6 @@ General['minimum_memory_limit']; if (self::isArchivePhpTriggered()) { - // archive.php: no time limit, high memory limit + // core:archive command: no time limit, high memory limit self::setMaxExecutionTime(0); $minimumMemoryLimitWhenArchiving = Config::getInstance()->General['minimum_memory_limit_when_archiving']; if ($memoryLimit < $minimumMemoryLimitWhenArchiving) { diff --git a/www/analytics/core/Singleton.php b/www/analytics/core/Singleton.php index c1f8682d..46e3c531 100644 --- a/www/analytics/core/Singleton.php +++ b/www/analytics/core/Singleton.php @@ -1,6 +1,6 @@ getName(); - * + * * **Without allocation** - * + * * $name = Site::getNameFor($idSite); - * + * * @api */ class Site @@ -54,7 +57,7 @@ class Site /** * Constructor. - * + * * @param int $idsite The ID of the site we want data for. */ public function __construct($idsite) @@ -62,7 +65,7 @@ class Site $this->id = (int)$idsite; if (!isset(self::$infoSites[$this->id])) { $site = API::getInstance()->getSiteFromId($this->id); - self::setSite($this->id, $site); + self::setSiteFromArray($this->id, $site); } } @@ -71,17 +74,44 @@ class Site * individual site data. * * @param array $sites The array of sites data. Indexed by site ID. eg, - * + * * array('1' => array('name' => 'Site 1', ...), * '2' => array('name' => 'Site 2', ...))` */ public static function setSites($sites) { - foreach($sites as $idsite => $site) { - self::setSite($idsite, $site); + self::triggerSetSitesEvent($sites); + + foreach ($sites as $idsite => $site) { + self::setSiteFromArray($idsite, $site); } } + private static function triggerSetSitesEvent(&$sites) + { + /** + * Triggered so plugins can modify website entities without modifying the database. + * + * This event should **not** be used to add data that is expensive to compute. If you + * need to make HTTP requests or query the database for more information, this is not + * the place to do it. + * + * **Example** + * + * Piwik::addAction('Site.setSites', function (&$sites) { + * foreach ($sites as &$site) { + * $site['name'] .= " (original)"; + * } + * }); + * + * @param array $sites An array of website entities. [Learn more.](/guides/persistence-and-the-mysql-backend#websites-aka-sites) + * + * This is not yet public as it doesn't work 100% accurately. Eg if `setSiteFromArray()` is called directly this event will not be triggered. + * @ignore + */ + Piwik::postEvent('Site.setSites', array(&$sites)); + } + /** * Sets a site information in memory (statically cached). * @@ -92,38 +122,20 @@ class Site * @param $infoSite * @throws Exception if website or idsite is invalid */ - protected static function setSite($idSite, $infoSite) + public static function setSiteFromArray($idSite, $infoSite) { - if(empty($idSite) || empty($infoSite)) { - throw new Exception("An unexpected website was found, check idSite in the request."); + if (empty($idSite) || empty($infoSite)) { + throw new UnexpectedWebsiteFoundException("An unexpected website was found in the request: website id was set to '$idSite' ."); } - /** - * Triggered so plugins can modify website entities without modifying the database. - * - * This event should **not** be used to add data that is expensive to compute. If you - * need to make HTTP requests or query the database for more information, this is not - * the place to do it. - * - * **Example** - * - * Piwik::addAction('Site.setSite', function ($idSite, &$info) { - * $info['name'] .= " (original)"; - * }); - * - * @param int $idSite The ID of the website entity that will be modified. - * @param array $infoSite The website entity. [Learn more.](/guides/persistence-and-the-mysql-backend#websites-aka-sites) - */ - Piwik::postEvent('Site.setSite', array($idSite, &$infoSite)); - self::$infoSites[$idSite] = $infoSite; } /** * Sets the cached Site data with a non-associated array of site data. - * + * * @param array $sites The array of sites data. eg, - * + * * array( * array('idsite' => '1', 'name' => 'Site 1', ...), * array('idsite' => '2', 'name' => 'Site 2', ...), @@ -131,8 +143,15 @@ class Site */ public static function setSitesFromArray($sites) { + self::triggerSetSitesEvent($sites); + foreach ($sites as $site) { - self::setSite($site['idsite'], $site); + $idSite = null; + if (!empty($site['idsite'])) { + $idSite = $site['idsite']; + } + + self::setSiteFromArray($idSite, $site); } } @@ -172,9 +191,9 @@ class Site /** * Returns a string representation of the site this instance references. - * + * * Useful for debugging. - * + * * @return string */ public function __toString() @@ -223,22 +242,31 @@ class Site /** * Returns a site property by name. - * + * * @param string $name Name of the property to return (eg, `'main_url'` or `'name'`). * @return mixed * @throws Exception */ protected function get($name) { + if (!isset(self::$infoSites[$this->id])) { + $site = API::getInstance()->getSiteFromId($this->id); + + if (empty($site)) { + throw new UnexpectedWebsiteFoundException('The requested website id = ' . (int)$this->id . ' couldn\'t be found'); + } + + self::setSiteFromArray($this->id, $site); + } if (!isset(self::$infoSites[$this->id][$name])) { - throw new Exception('The requested website id = ' . (int)$this->id . ' (or its property ' . $name . ') couldn\'t be found'); + throw new Exception("The property $name could not be found on the website ID " . (int)$this->id); } return self::$infoSites[$this->id][$name]; } /** * Returns the website type (by default `"website"`, which means it is a single website). - * + * * @return string */ public function getType() @@ -316,7 +344,7 @@ class Site /** * Returns the site search keyword query parameters for the site. - * + * * @return string * @throws Exception if data for the site cannot be found. */ @@ -327,7 +355,7 @@ class Site /** * Returns the site search category query parameters for the site. - * + * * @return string * @throws Exception if data for the site cannot be found. */ @@ -355,13 +383,13 @@ class Site * @param bool|string $_restrictSitesToLogin Implementation detail. Used only when running as a scheduled task. * @return array An array of valid, unique integers. */ - static public function getIdSitesFromIdSitesString($ids, $_restrictSitesToLogin = false) + public static function getIdSitesFromIdSitesString($ids, $_restrictSitesToLogin = false) { if ($ids === 'all') { return API::getInstance()->getSitesIdWithAtLeastViewAccess($_restrictSitesToLogin); } - if(is_bool($ids)) { + if (is_bool($ids)) { return array(); } if (!is_array($ids)) { @@ -382,10 +410,10 @@ class Site /** * Clears the site data cache. - * + * * See also {@link setSites()} and {@link setSitesFromArray()}. */ - static public function clearCache() + public static function clearCache() { self::$infoSites = array(); } @@ -395,21 +423,17 @@ class Site * site with the specified ID. * * @param int $idsite The ID of the site whose data is being accessed. - * @param bool|string $field The name of the field to get. - * @return array|string + * @param string $field The name of the field to get. + * @return string */ - static protected function getFor($idsite, $field = false) + protected static function getFor($idsite, $field) { - $idsite = (int)$idsite; - if (!isset(self::$infoSites[$idsite])) { $site = API::getInstance()->getSiteFromId($idsite); - self::setSite($idsite, $site); + self::setSiteFromArray($idsite, $site); } - if($field) { - return self::$infoSites[$idsite][$field]; - } - return self::$infoSites[$idsite]; + + return self::$infoSites[$idsite][$field]; } /** @@ -417,7 +441,7 @@ class Site * * @ignore */ - static public function getSites() + public static function getSites() { return self::$infoSites; } @@ -425,9 +449,16 @@ class Site /** * @ignore */ - static public function getSite($id) + public static function getSite($idsite) { - return self::getFor($id); + $idsite = (int)$idsite; + + if (!isset(self::$infoSites[$idsite])) { + $site = API::getInstance()->getSiteFromId($idsite); + self::setSiteFromArray($idsite, $site); + } + + return self::$infoSites[$idsite]; } /** @@ -436,7 +467,7 @@ class Site * @param int $idsite The site ID. * @return string */ - static public function getNameFor($idsite) + public static function getNameFor($idsite) { return self::getFor($idsite, 'name'); } @@ -447,7 +478,7 @@ class Site * @param int $idsite The site ID. * @return string */ - static public function getGroupFor($idsite) + public static function getGroupFor($idsite) { return self::getFor($idsite, 'group'); } @@ -458,7 +489,7 @@ class Site * @param int $idsite The site ID. * @return string */ - static public function getTimezoneFor($idsite) + public static function getTimezoneFor($idsite) { return self::getFor($idsite, 'timezone'); } @@ -469,7 +500,7 @@ class Site * @param $idsite * @return string */ - static public function getTypeFor($idsite) + public static function getTypeFor($idsite) { return self::getFor($idsite, 'type'); } @@ -480,7 +511,7 @@ class Site * @param int $idsite The site ID. * @return string */ - static public function getCreationDateFor($idsite) + public static function getCreationDateFor($idsite) { return self::getFor($idsite, 'ts_created'); } @@ -491,7 +522,7 @@ class Site * @param int $idsite The site ID. * @return string */ - static public function getMainUrlFor($idsite) + public static function getMainUrlFor($idsite) { return self::getFor($idsite, 'main_url'); } @@ -502,7 +533,7 @@ class Site * @param int $idsite The site ID. * @return string */ - static public function isEcommerceEnabledFor($idsite) + public static function isEcommerceEnabledFor($idsite) { return self::getFor($idsite, 'ecommerce') == 1; } @@ -513,7 +544,7 @@ class Site * @param int $idsite The site ID. * @return string */ - static public function isSiteSearchEnabledFor($idsite) + public static function isSiteSearchEnabledFor($idsite) { return self::getFor($idsite, 'sitesearch') == 1; } @@ -524,18 +555,54 @@ class Site * @param int $idsite The site ID. * @return string */ - static public function getCurrencyFor($idsite) + public static function getCurrencyFor($idsite) { return self::getFor($idsite, 'currency'); } + /** + * Returns the currency of the site with the specified ID. + * + * @param int $idsite The site ID. + * @return string + */ + public static function getCurrencySymbolFor($idsite) + { + $currency = self::getCurrencyFor($idsite); + $symbols = self::getCurrencyList(); + + if (isset($symbols[$currency])) { + return $symbols[$currency][0]; + } + + return ''; + } + + + /** + * Returns the list of all known currency symbols. + * + * @return array An array mapping currency codes to their respective currency symbols + * and a description, eg, `array('USD' => array('$', 'US dollar'))`. + * + * @deprecated Use Piwik\Intl\Data\Provider\CurrencyDataProvider instead. + * @see \Piwik\Intl\Data\Provider\CurrencyDataProvider::getCurrencyList() + * @api + */ + public static function getCurrencyList() + { + /** @var CurrencyDataProvider $dataProvider */ + $dataProvider = StaticContainer::get('Piwik\Intl\Data\Provider\CurrencyDataProvider'); + return $dataProvider->getCurrencyList(); + } + /** * Returns the excluded IP addresses of the site with the specified ID. * * @param int $idsite The site ID. * @return string */ - static public function getExcludedIpsFor($idsite) + public static function getExcludedIpsFor($idsite) { return self::getFor($idsite, 'excluded_ips'); } @@ -546,7 +613,7 @@ class Site * @param int $idsite The site ID. * @return string */ - static public function getExcludedQueryParametersFor($idsite) + public static function getExcludedQueryParametersFor($idsite) { return self::getFor($idsite, 'excluded_parameters'); } diff --git a/www/analytics/core/TCPDF.php b/www/analytics/core/TCPDF.php index 8a2669b5..fbbadbb3 100644 --- a/www/analytics/core/TCPDF.php +++ b/www/analytics/core/TCPDF.php @@ -1,6 +1,6 @@ currentPageNo > 1) { @@ -45,7 +40,7 @@ class TCPDF extends \TCPDF * @param $msg * @throws Exception */ - function Error($msg) + public function Error($msg) { $this->_destroy(true); throw new Exception($msg); @@ -54,7 +49,7 @@ class TCPDF extends \TCPDF /** * Set current page number */ - function setCurrentPageNo() + public function setCurrentPageNo() { if (empty($this->currentPageNo)) { $this->currentPageNo = 1; @@ -73,7 +68,7 @@ class TCPDF extends \TCPDF * @param bool $keepmargins * @param bool $tocpage */ - function AddPage($orientation = '', $format = '', $keepmargins = false, $tocpage = false) + public function AddPage($orientation = '', $format = '', $keepmargins = false, $tocpage = false) { parent::AddPage($orientation); $this->setCurrentPageNo(); @@ -84,7 +79,7 @@ class TCPDF extends \TCPDF * * @param string $footerContent */ - function SetFooterContent($footerContent) + public function SetFooterContent($footerContent) { $this->footerContent = $footerContent; } diff --git a/www/analytics/core/TaskScheduler.php b/www/analytics/core/TaskScheduler.php index dabf4241..46968ad3 100644 --- a/www/analytics/core/TaskScheduler.php +++ b/www/analytics/core/TaskScheduler.php @@ -1,6 +1,6 @@ hourly('myTask'); // myTask() will be executed once every hour + * } + * public function myTask() + * { + * // do something + * } * } - * + * * **Executing all pending tasks** - * + * * $results = TaskScheduler::runTasks(); * $task1Result = $results[0]; * $task1Name = $task1Result['task']; * $task1Output = $task1Result['output']; - * + * * echo "Executed task '$task1Name'. Task output:\n$task1Output"; * - * @method static \Piwik\TaskScheduler getInstance() + * @deprecated Use Piwik\Scheduler\Scheduler instead + * @see \Piwik\Scheduler\Scheduler */ -class TaskScheduler extends Singleton +class TaskScheduler { - const GET_TASKS_EVENT = "TaskScheduler.getScheduledTasks"; - - private $isRunning = false; - - private $timetable = null; - - public function __construct() - { - $this->timetable = new ScheduledTaskTimetable(); - } - /** * Executes tasks that are scheduled to run, then reschedules them. * * @return array An array describing the results of scheduled task execution. Each element * in the array will have the following format: - * + * * ``` * array( * 'task' => 'task name', @@ -76,97 +67,33 @@ class TaskScheduler extends Singleton * ) * ``` */ - static public function runTasks() + public static function runTasks() { - return self::getInstance()->doRunTasks(); - } - - private function doRunTasks() - { - // collect tasks - $tasks = array(); - - /** - * Triggered during scheduled task execution. Collects all the tasks to run. - * - * Subscribe to this event to schedule code execution on an hourly, daily, weekly or monthly - * basis. - * - * **Example** - * - * public function getScheduledTasks(&$tasks) - * { - * $tasks[] = new ScheduledTask( - * 'Piwik\Plugins\CorePluginsAdmin\MarketplaceApiClient', - * 'clearAllCacheEntries', - * null, - * ScheduledTime::factory('daily'), - * ScheduledTask::LOWEST_PRIORITY - * ); - * } - * - * @param ScheduledTask[] &$tasks List of tasks to run periodically. - */ - Piwik::postEvent(self::GET_TASKS_EVENT, array(&$tasks)); - /** @var ScheduledTask[] $tasks */ - - // remove from timetable tasks that are not active anymore - $this->timetable->removeInactiveTasks($tasks); - - // for every priority level, starting with the highest and concluding with the lowest - $executionResults = array(); - for ($priority = ScheduledTask::HIGHEST_PRIORITY; - $priority <= ScheduledTask::LOWEST_PRIORITY; - ++$priority) { - // loop through each task - foreach ($tasks as $task) { - // if the task does not have the current priority level, don't execute it yet - if ($task->getPriority() != $priority) { - continue; - } - - $taskName = $task->getName(); - $shouldExecuteTask = $this->timetable->shouldExecuteTask($taskName); - - if ($this->timetable->taskShouldBeRescheduled($taskName)) { - $this->timetable->rescheduleTask($task); - } - - if ($shouldExecuteTask) { - $this->isRunning = true; - $message = self::executeTask($task); - $this->isRunning = false; - - $executionResults[] = array('task' => $taskName, 'output' => $message); - } - } - } - - return $executionResults; + return self::getInstance()->run(); } /** * Determines a task's scheduled time and persists it, overwriting the previous scheduled time. - * + * * Call this method if your task's scheduled time has changed due to, for example, an option that * was changed. - * - * @param ScheduledTask $task Describes the scheduled task being rescheduled. + * + * @param Task $task Describes the scheduled task being rescheduled. * @api */ - static public function rescheduleTask(ScheduledTask $task) + public static function rescheduleTask(Task $task) { - self::getInstance()->timetable->rescheduleTask($task); + self::getInstance()->rescheduleTask($task); } /** * Returns true if the TaskScheduler is currently running a scheduled task. - * + * * @return bool */ - static public function isTaskBeingExecuted() + public static function isTaskBeingExecuted() { - return self::getInstance()->isRunning; + return self::getInstance()->isRunningTask(); } /** @@ -178,27 +105,16 @@ class TaskScheduler extends Singleton * @return mixed int|bool The time in miliseconds when the scheduled task will be executed * next or false if it is not scheduled to run. */ - static public function getScheduledTimeForMethod($className, $methodName, $methodParameter = null) + public static function getScheduledTimeForMethod($className, $methodName, $methodParameter = null) { - return self::getInstance()->timetable->getScheduledTimeForMethod($className, $methodName, $methodParameter); + return self::getInstance()->getScheduledTimeForMethod($className, $methodName, $methodParameter); } /** - * Executes the given taks - * - * @param ScheduledTask $task - * @return string + * @return Scheduler */ - static private function executeTask($task) + private static function getInstance() { - try { - $timer = new Timer(); - call_user_func(array($task->getObjectInstance(), $task->getMethodName()), $task->getMethodParameter()); - $message = $timer->__toString(); - } catch (Exception $e) { - $message = 'ERROR: ' . $e->getMessage(); - } - - return $message; + return StaticContainer::getContainer()->get('Piwik\Scheduler\Scheduler'); } } diff --git a/www/analytics/core/Theme.php b/www/analytics/core/Theme.php index 48ebbb67..8965a268 100644 --- a/www/analytics/core/Theme.php +++ b/www/analytics/core/Theme.php @@ -1,6 +1,6 @@ theme->getPluginName() . '/' . $jsFile; } return $jsFiles; @@ -97,7 +97,7 @@ class Theme // rewrites images in JS files '~(=)[\s]?[\'"]([^\'"]+[.jpg|.png|.gif|svg]?)[\'"]~', ); - return preg_replace_callback($pattern, array($this,'rewriteAssetPathIfOverridesFound'), $output); + return preg_replace_callback($pattern, array($this, 'rewriteAssetPathIfOverridesFound'), $output); } private function rewriteAssetPathIfOverridesFound($src) @@ -107,18 +107,18 @@ class Theme // Basic health check, we dont replace if not starting with plugins/ $posPluginsInPath = strpos($pathAsset, 'plugins'); - if( $posPluginsInPath !== 0) { + if ($posPluginsInPath !== 0) { return $source; } // or if it's already rewritten - if(strpos($pathAsset, $this->themeName) !== false) { + if (strpos($pathAsset, $this->themeName) !== false) { return $source; } $pathPluginName = substr($pathAsset, strlen('plugins/')); $nextSlash = strpos($pathPluginName, '/'); - if($nextSlash === false) { + if ($nextSlash === false) { return $source; } $pathPluginName = substr($pathPluginName, 0, $nextSlash); @@ -133,11 +133,11 @@ class Theme // Strip trailing query string $fileToCheck = $overridingAsset; $queryStringPos = strpos($fileToCheck, '?'); - if( $queryStringPos !== false) { + if ($queryStringPos !== false) { $fileToCheck = substr($fileToCheck, 0, $queryStringPos); } - if(file_exists($fileToCheck)) { + if (file_exists($fileToCheck)) { return str_replace($pathAsset, $overridingAsset, $source); } return $source; diff --git a/www/analytics/core/Timer.php b/www/analytics/core/Timer.php index 76bbd3a5..3aea3db1 100644 --- a/www/analytics/core/Timer.php +++ b/www/analytics/core/Timer.php @@ -1,6 +1,6 @@ formatter = new Formatter(); + $this->init(); } @@ -56,7 +61,7 @@ class Timer */ public function getMemoryLeak() { - return "Memory delta: " . MetricsFormatter::getPrettySizeFromBytes($this->getMemoryUsage() - $this->memoryStart); + return "Memory delta: " . $this->formatter->getPrettySizeFromBytes($this->getMemoryUsage() - $this->memoryStart); } /** diff --git a/www/analytics/core/Tracker.php b/www/analytics/core/Tracker.php index c84b3c0a..d64f1b7f 100644 --- a/www/analytics/core/Tracker.php +++ b/www/analytics/core/Tracker.php @@ -1,6 +1,6 @@ isInstalled(); + } + + public static function loadTrackerEnvironment() + { + SettingsServer::setIsTrackerApiRequest(); + $GLOBALS['PIWIK_TRACKER_DEBUG'] = self::isDebugEnabled(); + PluginManager::getInstance()->loadTrackerPlugins(); + } + + private function init() + { + $this->handleFatalErrors(); + + if ($this->isDebugModeEnabled()) { + ErrorHandler::registerErrorHandler(); + ExceptionHandler::setUp(); + + Common::printDebug("Debug enabled - Input parameters: "); + Common::printDebug(var_export($_GET, true)); } } - public function clear() + public function isInstalled() { - self::$forcedIpString = null; - self::$forcedDateTime = null; - self::$forcedVisitorId = null; - $this->stateValid = self::STATE_NOTHING_TO_NOTICE; - } - - public static function setForceIp($ipString) - { - self::$forcedIpString = $ipString; - } - - public static function setForceDateTime($dateTime) - { - self::$forcedDateTime = $dateTime; - } - - public static function setForceVisitorId($visitorId) - { - self::$forcedVisitorId = $visitorId; - } - - /** - * Do not load the specified plugins (used during testing, to disable Provider plugin) - * @param array $plugins - */ - static public function setPluginsNotToLoad($plugins) - { - self::$pluginsNotToLoad = $plugins; - } - - /** - * Get list of plugins to not load - * - * @return array - */ - static public function getPluginsNotToLoad() - { - return self::$pluginsNotToLoad; - } - - /** - * Update Tracker config - * - * @param string $name Setting name - * @param mixed $value Value - */ - static private function updateTrackerConfig($name, $value) - { - $section = Config::getInstance()->Tracker; - $section[$name] = $value; - Config::getInstance()->Tracker = $section; - } - - protected function initRequests($args) - { - $rawData = self::getRawBulkRequest(); - if (!empty($rawData)) { - $this->usingBulkTracking = strpos($rawData, '"requests"') || strpos($rawData, "'requests'"); - if ($this->usingBulkTracking) { - return $this->authenticateBulkTrackingRequests($rawData); - } + if (is_null($this->isInstalled)) { + $this->isInstalled = SettingsPiwik::isPiwikInstalled(); } - // Not using bulk tracking - $this->requests = $args ? $args : (!empty($_GET) || !empty($_POST) ? array($_GET + $_POST) : array()); + return $this->isInstalled; } - private static function getRequestsArrayFromBulkRequest($rawData) - { - $rawData = trim($rawData); - $rawData = Common::sanitizeLineBreaks($rawData); - - // POST data can be array of string URLs or array of arrays w/ visit info - $jsonData = json_decode($rawData, $assoc = true); - - $tokenAuth = Common::getRequestVar('token_auth', false, 'string', $jsonData); - - $requests = array(); - if (isset($jsonData['requests'])) { - $requests = $jsonData['requests']; - } - - return array( $requests, $tokenAuth); - } - - private function authenticateBulkTrackingRequests($rawData) - { - list($this->requests, $tokenAuth) = $this->getRequestsArrayFromBulkRequest($rawData); - - if (empty($tokenAuth)) { - throw new Exception( "token_auth must be specified when using Bulk Tracking Import. " - ." See Tracking Doc"); - } - if (!empty($this->requests)) { - - foreach ($this->requests as &$request) { - // if a string is sent, we assume its a URL and try to parse it - if (is_string($request)) { - $params = array(); - - $url = @parse_url($request); - if (!empty($url)) { - @parse_str($url['query'], $params); - $request = $params; - } - } - - $requestObj = new Request($request, $tokenAuth); - $this->loadTrackerPlugins($requestObj); - - // a Bulk Tracking request that is not authenticated should fail - if (!$requestObj->isAuthenticated()) { - throw new Exception(sprintf("token_auth specified does not have Admin permission for idsite=%s", - $requestObj->getIdSite())); - } - - $request = $requestObj; - } - } - - return $tokenAuth; - } - - /** - * Main - tracks the visit/action - * - * @param array $args Optional Request Array - */ - public function main($args = null) + public function main(Handler $handler, RequestSet $requestSet) { try { - $tokenAuth = $this->initRequests($args); - } catch (Exception $ex) { - $this->exitWithException($ex, true); + $this->init(); + $handler->init($this, $requestSet); + $this->track($handler, $requestSet); + } catch (Exception $e) { + $handler->onException($this, $requestSet, $e); } - $this->initOutputBuffer(); + Piwik::postEvent('Tracker.end'); + $response = $handler->finish($this, $requestSet); - if (!empty($this->requests)) { + $this->disconnectDatabase(); - try { - foreach ($this->requests as $params) { - $isAuthenticated = $this->trackRequest($params, $tokenAuth); - } - $this->runScheduledTasksIfAllowed($isAuthenticated); - } catch(DbException $e) { - Common::printDebug($e->getMessage()); - } - } else { - $this->handleEmptyRequest(new Request($_GET + $_POST)); - } - $this->end(); - - $this->flushOutputBuffer(); + return $response; } - protected function initOutputBuffer() + public function track(Handler $handler, RequestSet $requestSet) { - ob_start(); - } - - protected function flushOutputBuffer() - { - ob_end_flush(); - } - - protected function getOutputBuffer() - { - return ob_get_contents(); - } - - - protected function shouldRunScheduledTasks() - { - // don't run scheduled tasks in CLI mode from Tracker, this is the case - // where we bulk load logs & don't want to lose time with tasks - return !Common::isPhpCliMode() - && $this->getState() != self::STATE_LOGGING_DISABLE; - } - - /** - * Tracker requests will automatically trigger the Scheduled tasks. - * This is useful for users who don't setup the cron, - * but still want daily/weekly/monthly PDF reports emailed automatically. - * - * This is similar to calling the API CoreAdminHome.runScheduledTasks (see misc/cron/archive.php) - */ - protected static function runScheduledTasks() - { - $now = time(); - - // Currently, there are no hourly tasks. When there are some, - // this could be too aggressive minimum interval (some hours would be skipped in case of low traffic) - $minimumInterval = Config::getInstance()->Tracker['scheduled_tasks_min_interval']; - - // If the user disabled browser archiving, he has already setup a cron - // To avoid parallel requests triggering the Scheduled Tasks, - // Get last time tasks started executing - $cache = Cache::getCacheGeneral(); - - if ($minimumInterval <= 0 - || empty($cache['isBrowserTriggerEnabled']) - ) { - Common::printDebug("-> Scheduled tasks not running in Tracker: Browser archiving is disabled."); + if (!$this->shouldRecordStatistics()) { return; } - $nextRunTime = $cache['lastTrackerCronRun'] + $minimumInterval; + $requestSet->initRequestsAndTokenAuth(); - if ((isset($GLOBALS['PIWIK_TRACKER_DEBUG_FORCE_SCHEDULED_TASKS']) && $GLOBALS['PIWIK_TRACKER_DEBUG_FORCE_SCHEDULED_TASKS']) - || $cache['lastTrackerCronRun'] === false - || $nextRunTime < $now - ) { - $cache['lastTrackerCronRun'] = $now; - Cache::setCacheGeneral($cache); - self::initCorePiwikInTrackerMode(); - Option::set('lastTrackerCronRun', $cache['lastTrackerCronRun']); - Common::printDebug('-> Scheduled Tasks: Starting...'); - - // save current user privilege and temporarily assume Super User privilege - $isSuperUser = Piwik::hasUserSuperUserAccess(); - - // Scheduled tasks assume Super User is running - Piwik::setUserHasSuperUserAccess(); - - // While each plugins should ensure that necessary languages are loaded, - // we ensure English translations at least are loaded - Translate::loadEnglishTranslation(); - - ob_start(); - CronArchive::$url = SettingsPiwik::getPiwikUrl(); - $cronArchive = new CronArchive(); - $cronArchive->runScheduledTasksInTrackerMode(); - - $resultTasks = ob_get_contents(); - ob_clean(); - - // restore original user privilege - Piwik::setUserHasSuperUserAccess($isSuperUser); - - foreach (explode('', $resultTasks) as $resultTask) { - Common::printDebug(str_replace('
', '', $resultTask));
-            }
-
-            Common::printDebug('Finished Scheduled Tasks.');
-        } else {
-            Common::printDebug("-> Scheduled tasks not triggered.");
+        if ($requestSet->hasRequests()) {
+            $handler->onStartTrackRequests($this, $requestSet);
+            $handler->process($this, $requestSet);
+            $handler->onAllRequestsTracked($this, $requestSet);
         }
-        Common::printDebug("Next run will be from: " . date('Y-m-d H:i:s', $nextRunTime) . ' UTC');
     }
 
-    static public $initTrackerMode = false;
+    /**
+     * @param Request $request
+     * @return array
+     */
+    public function trackRequest(Request $request)
+    {
+        if ($request->isEmptyRequest()) {
+            Common::printDebug("The request is empty");
+        } else {
+            Common::printDebug("Current datetime: " . date("Y-m-d H:i:s", $request->getCurrentTimestamp()));
+
+            $visit = Visit\Factory::make();
+            $visit->setRequest($request);
+            $visit->handle();
+        }
+
+        // increment successfully logged request count. make sure to do this after try-catch,
+        // since an excluded visit is considered 'successfully logged'
+        ++$this->countOfLoggedRequests;
+    }
 
     /**
      * Used to initialize core Piwik components on a piwik.php request
      * Eg. when cache is missed and we will be calling some APIs to generate cache
      */
-    static public function initCorePiwikInTrackerMode()
+    public static function initCorePiwikInTrackerMode()
     {
         if (SettingsServer::isTrackerApiRequest()
             && self::$initTrackerMode === false
         ) {
             self::$initTrackerMode = true;
-            require_once PIWIK_INCLUDE_PATH . '/core/Loader.php';
             require_once PIWIK_INCLUDE_PATH . '/core/Option.php';
 
-            $access = Access::getInstance();
-            $config = Config::getInstance();
+            Access::getInstance();
+            Config::getInstance();
 
             try {
                 Db::get();
@@ -365,497 +164,174 @@ class Tracker
                 Db::createDatabaseObject();
             }
 
-            \Piwik\Plugin\Manager::getInstance()->loadCorePluginsDuringTracker();
+            PluginManager::getInstance()->loadCorePluginsDuringTracker();
         }
     }
 
-    /**
-     * Echos an error message & other information, then exits.
-     *
-     * @param Exception $e
-     * @param bool $authenticated
-     */
-    protected function exitWithException($e, $authenticated = false)
+    public static function restoreTrackerPlugins()
     {
-        if ($this->usingBulkTracking) {
-            // when doing bulk tracking we return JSON so the caller will know how many succeeded
-            $result = array(
-                'status'  => 'error',
-                'tracked' => $this->countOfLoggedRequests
-            );
-            // send error when in debug mode or when authenticated (which happens when doing log importing,
-            if ((isset($GLOBALS['PIWIK_TRACKER_DEBUG']) && $GLOBALS['PIWIK_TRACKER_DEBUG'])
-                || $authenticated
-            ) {
-                $result['message'] = $this->getMessageFromException($e);
-            }
-            Common::sendHeader('Content-Type: application/json');
-            echo Common::json_encode($result);
-            exit;
+        if (SettingsServer::isTrackerApiRequest() && Tracker::$initTrackerMode) {
+            Plugin\Manager::getInstance()->loadTrackerPlugins();
         }
+    }
 
-        if (isset($GLOBALS['PIWIK_TRACKER_DEBUG']) && $GLOBALS['PIWIK_TRACKER_DEBUG']) {
-            Common::sendHeader('Content-Type: text/html; charset=utf-8');
-            $trailer = 'Backtrace:
' . $e->getTraceAsString() . '
'; - $headerPage = file_get_contents(PIWIK_INCLUDE_PATH . '/plugins/Zeitgeist/templates/simpleLayoutHeader.tpl'); - $footerPage = file_get_contents(PIWIK_INCLUDE_PATH . '/plugins/Zeitgeist/templates/simpleLayoutFooter.tpl'); - $headerPage = str_replace('{$HTML_TITLE}', 'Piwik › Error', $headerPage); + public function getCountOfLoggedRequests() + { + return $this->countOfLoggedRequests; + } - echo $headerPage . '

' . $this->getMessageFromException($e) . '

' . $trailer . $footerPage; - } // If not debug, but running authenticated (eg. during log import) then we display raw errors - elseif ($authenticated) { - Common::sendHeader('Content-Type: text/html; charset=utf-8'); - echo $this->getMessageFromException($e); - } else { - $this->outputTransparentGif(); - } - exit; + public function setCountOfLoggedRequests($numLoggedRequests) + { + $this->countOfLoggedRequests = $numLoggedRequests; + } + + public function hasLoggedRequests() + { + return 0 !== $this->countOfLoggedRequests; } /** - * Returns the date in the "Y-m-d H:i:s" PHP format - * - * @param int $timestamp - * @return string + * @deprecated since 2.10.0 use {@link Date::getDatetimeFromTimestamp()} instead */ public static function getDatetimeFromTimestamp($timestamp) { - return date("Y-m-d H:i:s", $timestamp); + return Date::getDatetimeFromTimestamp($timestamp); } - /** - * Initialization - */ - protected function init(Request $request) + public function isDatabaseConnected() { - $this->loadTrackerPlugins($request); - $this->handleTrackingApi($request); - $this->handleDisabledTracker(); - $this->handleEmptyRequest($request); - - Common::printDebug("Current datetime: " . date("Y-m-d H:i:s", $request->getCurrentTimestamp())); + return !is_null(self::$db); } - /** - * Cleanup - */ - protected function end() + public static function getDatabase() { - if ($this->usingBulkTracking) { - $result = array( - 'status' => 'success', - 'tracked' => $this->countOfLoggedRequests - ); - Common::sendHeader('Content-Type: application/json'); - echo Common::json_encode($result); - exit; - } - switch ($this->getState()) { - case self::STATE_LOGGING_DISABLE: - $this->outputTransparentGif(); - Common::printDebug("Logging disabled, display transparent logo"); - break; - - case self::STATE_EMPTY_REQUEST: - Common::printDebug("Empty request => Piwik page"); - echo "Piwik is a free open source web analytics that lets you keep control of your data."; - break; - - case self::STATE_NOSCRIPT_REQUEST: - case self::STATE_NOTHING_TO_NOTICE: - default: - $this->outputTransparentGif(); - Common::printDebug("Nothing to notice => default behaviour"); - break; - } - Common::printDebug("End of the page."); - - if ($GLOBALS['PIWIK_TRACKER_DEBUG'] === true) { - if (isset(self::$db)) { - self::$db->recordProfiling(); - Profiler::displayDbTrackerProfile(self::$db); + if (is_null(self::$db)) { + try { + self::$db = TrackerDb::connectPiwikTrackerDb(); + } catch (Exception $e) { + throw new DbException($e->getMessage(), $e->getCode()); } } - self::disconnectDatabase(); - } - - /** - * Factory to create database objects - * - * @param array $configDb Database configuration - * @throws Exception - * @return \Piwik\Tracker\Db\Mysqli|\Piwik\Tracker\Db\Pdo\Mysql - */ - public static function factory($configDb) - { - /** - * Triggered before a connection to the database is established by the Tracker. - * - * This event can be used to change the database connection settings used by the Tracker. - * - * @param array $dbInfos Reference to an array containing database connection info, - * including: - * - * - **host**: The host name or IP address to the MySQL database. - * - **username**: The username to use when connecting to the - * database. - * - **password**: The password to use when connecting to the - * database. - * - **dbname**: The name of the Piwik MySQL database. - * - **port**: The MySQL database port to use. - * - **adapter**: either `'PDO_MYSQL'` or `'MYSQLI'` - * - **type**: The MySQL engine to use, for instance 'InnoDB' - */ - Piwik::postEvent('Tracker.getDatabaseConfig', array(&$configDb)); - - switch ($configDb['adapter']) { - case 'PDO\MYSQL': - case 'PDO_MYSQL': // old format pre Piwik 2 - require_once PIWIK_INCLUDE_PATH . '/core/Tracker/Db/Pdo/Mysql.php'; - return new Mysql($configDb); - - case 'MYSQLI': - require_once PIWIK_INCLUDE_PATH . '/core/Tracker/Db/Mysqli.php'; - return new Mysqli($configDb); - } - - throw new Exception('Unsupported database adapter ' . $configDb['adapter']); - } - - public static function connectPiwikTrackerDb() - { - $db = null; - $configDb = Config::getInstance()->database; - - if (!isset($configDb['port'])) { - // before 0.2.4 there is no port specified in config file - $configDb['port'] = '3306'; - } - - $db = Tracker::factory($configDb); - $db->connect(); - - return $db; - } - - protected static function connectDatabaseIfNotConnected() - { - if (!is_null(self::$db)) { - return; - } - - try { - self::$db = self::connectPiwikTrackerDb(); - } catch (Exception $e) { - throw new DbException($e->getMessage(), $e->getCode()); - } - } - - /** - * @return Db - */ - public static function getDatabase() - { - self::connectDatabaseIfNotConnected(); return self::$db; } - public static function disconnectDatabase() + protected function disconnectDatabase() { - if (isset(self::$db)) { + if ($this->isDatabaseConnected()) { // note: I think we do this only for the tests self::$db->disconnect(); self::$db = null; } } - /** - * Returns the Tracker_Visit object. - * This method can be overwritten to use a different Tracker_Visit object - * - * @throws Exception - * @return \Piwik\Tracker\Visit - */ - protected function getNewVisitObject() + // for tests + public static function disconnectCachedDbConnection() { - $visit = null; - - /** - * Triggered before a new **visit tracking object** is created. Subscribers to this - * event can force the use of a custom visit tracking object that extends from - * {@link Piwik\Tracker\VisitInterface}. - * - * @param \Piwik\Tracker\VisitInterface &$visit Initialized to null, but can be set to - * a new visit object. If it isn't modified - * Piwik uses the default class. - */ - Piwik::postEvent('Tracker.makeNewVisitObject', array(&$visit)); - - if (is_null($visit)) { - $visit = new Visit(); - } elseif (!($visit instanceof VisitInterface)) { - throw new Exception("The Visit object set in the plugin must implement VisitInterface"); - } - return $visit; - } - - protected function outputTransparentGif() - { - if (isset($GLOBALS['PIWIK_TRACKER_DEBUG']) - && $GLOBALS['PIWIK_TRACKER_DEBUG'] - ) { - return; - } - - if (strlen($this->getOutputBuffer()) > 0) { - // If there was an error during tracker, return so errors can be flushed - return; - } - $transGifBase64 = "R0lGODlhAQABAIAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="; - Common::sendHeader('Content-Type: image/gif'); - - $this->outputAccessControlHeaders(); - - print(base64_decode($transGifBase64)); - } - - protected function isVisitValid() - { - return $this->stateValid !== self::STATE_LOGGING_DISABLE - && $this->stateValid !== self::STATE_EMPTY_REQUEST; - } - - protected function getState() - { - return $this->stateValid; - } - - protected function setState($value) - { - $this->stateValid = $value; - } - - protected function loadTrackerPlugins(Request $request) - { - // Adding &dp=1 will disable the provider plugin, if token_auth is used (used to speed up bulk imports) - $disableProvider = $request->getParam('dp'); - if (!empty($disableProvider)) { - Tracker::setPluginsNotToLoad(array('Provider')); - } - - try { - $pluginsTracker = \Piwik\Plugin\Manager::getInstance()->loadTrackerPlugins(); - Common::printDebug("Loading plugins: { " . implode(",", $pluginsTracker) . " }"); - } catch (Exception $e) { - Common::printDebug("ERROR: " . $e->getMessage()); - } - } - - protected function handleEmptyRequest(Request $request) - { - $countParameters = $request->getParamsCount(); - if ($countParameters == 0) { - $this->setState(self::STATE_EMPTY_REQUEST); - } - if ($countParameters == 1) { - $this->setState(self::STATE_NOSCRIPT_REQUEST); - } - } - - protected function handleDisabledTracker() - { - $saveStats = Config::getInstance()->Tracker['record_statistics']; - if ($saveStats == 0) { - $this->setState(self::STATE_LOGGING_DISABLE); - } - } - - protected function getTokenAuth() - { - if (!is_null($this->tokenAuth)) { - return $this->tokenAuth; - } - - return Common::getRequestVar('token_auth', false); - } - - /** - * This method allows to set custom IP + server time + visitor ID, when using Tracking API. - * These two attributes can be only set by the Super User (passing token_auth). - */ - protected function handleTrackingApi(Request $request) - { - if (!$request->isAuthenticated()) { - return; - } - - // Custom IP to use for this visitor - $customIp = $request->getParam('cip'); - if (!empty($customIp)) { - $this->setForceIp($customIp); - } - - // Custom server date time to use - $customDatetime = $request->getParam('cdt'); - if (!empty($customDatetime)) { - $this->setForceDateTime($customDatetime); - } - - // Forced Visitor ID to record the visit / action - $customVisitorId = $request->getParam('cid'); - if (!empty($customVisitorId)) { - $this->setForceVisitorId($customVisitorId); + // code redundancy w/ above is on purpose; above disconnectDatabase depends on method that can potentially be overridden + if (!is_null(self::$db)) { + self::$db->disconnect(); + self::$db = null; } } public static function setTestEnvironment($args = null, $requestMethod = null) { if (is_null($args)) { - $postData = self::getRequestsArrayFromBulkRequest(self::getRawBulkRequest()); - $args = $_GET + $postData; + $requests = new Requests(); + $args = $requests->getRequestsArrayFromBulkRequest($requests->getRawBulkRequest()); + $args = $_GET + $args; } + if (is_null($requestMethod) && array_key_exists('REQUEST_METHOD', $_SERVER)) { $requestMethod = $_SERVER['REQUEST_METHOD']; - } else if (is_null($requestMethod)) { + } elseif (is_null($requestMethod)) { $requestMethod = 'GET'; } // Do not run scheduled tasks during tests - self::updateTrackerConfig('scheduled_tasks_min_interval', 0); + if (!defined('DEBUG_FORCE_SCHEDULED_TASKS')) { + TrackerConfig::setConfigValue('scheduled_tasks_min_interval', 0); + } // if nothing found in _GET/_POST and we're doing a POST, assume bulk request. in which case, // we have to bypass authentication if (empty($args) && $requestMethod == 'POST') { - self::updateTrackerConfig('tracking_requests_require_authentication', 0); + TrackerConfig::setConfigValue('tracking_requests_require_authentication', 0); + } + + // Tests can force the use of 3rd party cookie for ID visitor + if (Common::getRequestVar('forceEnableFingerprintingAcrossWebsites', false, null, $args) == 1) { + TrackerConfig::setConfigValue('enable_fingerprinting_across_websites', 1); } // Tests can force the use of 3rd party cookie for ID visitor if (Common::getRequestVar('forceUseThirdPartyCookie', false, null, $args) == 1) { - self::updateTrackerConfig('use_third_party_id_cookie', 1); + TrackerConfig::setConfigValue('use_third_party_id_cookie', 1); } // Tests using window_look_back_for_visitor if (Common::getRequestVar('forceLargeWindowLookBackForVisitor', false, null, $args) == 1 // also look for this in bulk requests (see fake_logs_replay.log) - || strpos( json_encode($args, true), '"forceLargeWindowLookBackForVisitor":"1"' ) !== false) { - self::updateTrackerConfig('window_look_back_for_visitor', 2678400); + || strpos(json_encode($args, true), '"forceLargeWindowLookBackForVisitor":"1"') !== false + ) { + TrackerConfig::setConfigValue('window_look_back_for_visitor', 2678400); } // Tests can force the enabling of IP anonymization if (Common::getRequestVar('forceIpAnonymization', false, null, $args) == 1) { - - self::connectDatabaseIfNotConnected(); + self::getDatabase(); // make sure db is initialized $privacyConfig = new PrivacyManagerConfig(); $privacyConfig->ipAddressMaskLength = 2; \Piwik\Plugins\PrivacyManager\IPAnonymizer::activate(); + + \Piwik\Tracker\Cache::deleteTrackerCache(); + Filesystem::clearPhpCaches(); } - // Custom IP to use for this visitor - $customIp = Common::getRequestVar('cip', false, null, $args); - if (!empty($customIp)) { - self::setForceIp($customIp); - } - - // Custom server date time to use - $customDatetime = Common::getRequestVar('cdt', false, null, $args); - if (!empty($customDatetime)) { - self::setForceDateTime($customDatetime); - } - - // Custom visitor id - $customVisitorId = Common::getRequestVar('cid', false, null, $args); - if (!empty($customVisitorId)) { - self::setForceVisitorId($customVisitorId); - } $pluginsDisabled = array('Provider'); // Disable provider plugin, because it is so slow to do many reverse ip lookups - self::setPluginsNotToLoad($pluginsDisabled); + PluginManager::getInstance()->setTrackerPluginsNotToLoad($pluginsDisabled); } - /** - * Gets the error message to output when a tracking request fails. - * - * @param Exception $e - * @return string - */ - private function getMessageFromException($e) + protected function loadTrackerPlugins() { - // Note: duplicated from FormDatabaseSetup.isAccessDenied - // Avoid leaking the username/db name when access denied - if ($e->getCode() == 1044 || $e->getCode() == 42000) { - return "Error while connecting to the Piwik database - please check your credentials in config/config.ini.php file"; - } else { - return $e->getMessage(); - } - } - - /** - * @param $params - * @param $tokenAuth - * @return array - */ - protected function trackRequest($params, $tokenAuth) - { - if ($params instanceof Request) { - $request = $params; - } else { - $request = new Request($params, $tokenAuth); - } - - $this->init($request); - - $isAuthenticated = $request->isAuthenticated(); - try { - if ($this->isVisitValid()) { - $visit = $this->getNewVisitObject(); - $request->setForcedVisitorId(self::$forcedVisitorId); - $request->setForceDateTime(self::$forcedDateTime); - $request->setForceIp(self::$forcedIpString); - - $visit->setRequest($request); - $visit->handle(); - } else { - Common::printDebug("The request is invalid: empty request, or maybe tracking is disabled in the config.ini.php via record_statistics=0"); - } - } catch (DbException $e) { - Common::printDebug("Exception: " . $e->getMessage()); - $this->exitWithException($e, $isAuthenticated); + $pluginManager = PluginManager::getInstance(); + $pluginsTracker = $pluginManager->loadTrackerPlugins(); + Common::printDebug("Loading plugins: { " . implode(", ", $pluginsTracker) . " }"); } catch (Exception $e) { - $this->exitWithException($e, $isAuthenticated); + Common::printDebug("ERROR: " . $e->getMessage()); } - $this->clear(); - - // increment successfully logged request count. make sure to do this after try-catch, - // since an excluded visit is considered 'successfully logged' - ++$this->countOfLoggedRequests; - return $isAuthenticated; } - - protected function runScheduledTasksIfAllowed($isAuthenticated) + private function handleFatalErrors() + { + register_shutdown_function(function () { + $lastError = error_get_last(); + if (!empty($lastError) && $lastError['type'] == E_ERROR) { + Common::sendResponseCode(500); + } + }); + } + + private static function isDebugEnabled() { - // Do not run schedule task if we are importing logs - // or doing custom tracking (as it could slow down) try { - if (!$isAuthenticated - && $this->shouldRunScheduledTasks() - ) { - self::runScheduledTasks(); + $debug = (bool) TrackerConfig::getConfigValue('debug'); + if ($debug) { + return true; + } + + $debugOnDemand = (bool) TrackerConfig::getConfigValue('debug_on_demand'); + if ($debugOnDemand) { + return (bool) Common::getRequestVar('debug', false); } } catch (Exception $e) { - $this->exitWithException($e); } - } - /** - * @return string - */ - protected static function getRawBulkRequest() - { - return file_get_contents("php://input"); + return false; } } diff --git a/www/analytics/core/Tracker/Action.php b/www/analytics/core/Tracker/Action.php index a1d879b5..b841c210 100644 --- a/www/analytics/core/Tracker/Action.php +++ b/www/analytics/core/Tracker/Action.php @@ -1,6 +1,6 @@ getParam('download'); - if (!empty($downloadUrl)) { - return new ActionClickUrl(self::TYPE_DOWNLOAD, $downloadUrl, $request); + /** @var Action[] $actions */ + $actions = self::getAllActions($request); + + foreach ($actions as $actionType) { + if (empty($action)) { + $action = $actionType; + continue; + } + + $posPrevious = self::getPriority($action); + $posCurrent = self::getPriority($actionType); + + if ($posCurrent > $posPrevious) { + $action = $actionType; + } } - $outlinkUrl = $request->getParam('link'); - if (!empty($outlinkUrl)) { - return new ActionClickUrl(self::TYPE_OUTLINK, $outlinkUrl, $request); - } - - $url = $request->getParam('url'); - - $eventCategory = $request->getParam('e_c'); - $eventAction = $request->getParam('e_a'); - if(strlen($eventCategory) > 0 && strlen($eventAction) > 0 ) { - return new ActionEvent($eventCategory, $eventAction, $url, $request); - } - - $action = new ActionSiteSearch($url, $request); - if ($action->isSearchDetected()) { + if (!empty($action)) { return $action; } - return new ActionPageview($url, $request); + + return new ActionPageview($request); } - /** - * @var Request - */ - protected $request; + private static function getPriority(Action $actionType) + { + $key = array_search($actionType->getActionType(), self::$factoryPriority); - private $idLinkVisitAction; - private $actionIdsCached = array(); - private $actionName; - private $actionType; - private $actionUrl; + if (false === $key) { + return -1; + } + + return $key; + } + + public static function shouldHandle(Request $request) + { + return false; + } + + private static function getAllActions(Request $request) + { + static $actions; + + if (is_null($actions)) { + $actions = Manager::getInstance()->findMultipleComponents('Actions', '\\Piwik\\Tracker\\Action'); + } + + $instances = array(); + + foreach ($actions as $action) { + /** @var \Piwik\Tracker\Action $action */ + if ($action::shouldHandle($request)) { + $instances[] = new $action($request); + } + } + + return $instances; + } public function __construct($type, Request $request) { $this->actionType = $type; - $this->request = $request; + $this->request = $request; } /** @@ -96,6 +160,14 @@ abstract class Action return $this->actionUrl; } + /** + * Returns URL of page being tracked, including all original Query parameters + */ + public function getActionUrlRaw() + { + return $this->rawActionUrl; + } + public function getActionName() { return $this->actionName; @@ -108,8 +180,7 @@ abstract class Action public function getCustomVariables() { - $customVariables = $this->request->getCustomVariables($scope = 'page'); - return $customVariables; + return $this->request->getCustomVariables($scope = 'page'); } // custom_float column @@ -118,24 +189,28 @@ abstract class Action return false; } - protected function setActionName($name) { - $name = PageUrl::cleanupString((string)$name); - $this->actionName = $name; + $this->actionName = PageUrl::cleanupString((string)$name); } protected function setActionUrl($url) { - $urlBefore = $url; + $this->rawActionUrl = PageUrl::getUrlIfLookValid($url); $url = PageUrl::excludeQueryParametersFromUrl($url, $this->request->getIdSite()); - if ($url != $urlBefore) { - Common::printDebug(' Before was "' . $urlBefore . '"'); + $this->actionUrl = PageUrl::getUrlIfLookValid($url); + + if ($url != $this->rawActionUrl) { + Common::printDebug(' Before was "' . $this->rawActionUrl . '"'); Common::printDebug(' After is "' . $url . '"'); } + } + protected function setActionUrlWithoutExcludingParameters($url) + { $url = PageUrl::getUrlIfLookValid($url); + $this->rawActionUrl = $url; $this->actionUrl = $url; } @@ -144,14 +219,26 @@ abstract class Action protected function getUrlAndType() { $url = $this->getActionUrl(); + if (!empty($url)) { // normalize urls by stripping protocol and www $url = PageUrl::normalizeUrl($url); - return array($url['url'], Tracker\Action::TYPE_PAGE_URL, $url['prefixId']); + return array($url['url'], self::TYPE_PAGE_URL, $url['prefixId']); } + return false; } + public function setCustomField($field, $value) + { + $this->customFields[$field] = $value; + } + + public function getCustomFields() + { + return $this->customFields; + } + public function getIdActionUrl() { $idUrl = $this->actionIdsCached['idaction_url']; @@ -159,7 +246,6 @@ abstract class Action return (int)$idUrl; } - public function getIdActionUrlForEntryAndExitIds() { return $this->getIdActionUrl(); @@ -172,9 +258,10 @@ abstract class Action public function getIdActionName() { - if(!isset($this->actionIdsCached['idaction_name'])) { + if (!isset($this->actionIdsCached['idaction_name'])) { return false; } + return $this->actionIdsCached['idaction_name']; } @@ -188,24 +275,17 @@ abstract class Action return $this->idLinkVisitAction; } - public function writeDebugInfo() - { - $type = self::getTypeAsString($this->getActionType()); - Common::printDebug("Action is a $type, - Action name = " . $this->getActionName() . ", - Action URL = " . $this->getActionUrl()); - return true; - } - public static function getTypeAsString($type) { - $class = new \ReflectionClass("\\Piwik\\Tracker\\Action"); + $class = new \ReflectionClass("\\Piwik\\Tracker\\Action"); $constants = $class->getConstants(); $typeId = array_search($type, $constants); - if($typeId === false) { + + if (false === $typeId) { throw new Exception("Unexpected action type " . $type); } + return str_replace('TYPE_', '', $typeId); } @@ -220,13 +300,38 @@ abstract class Action */ public function loadIdsFromLogActionTable() { - if(!empty($this->actionIdsCached)) { + if (!empty($this->actionIdsCached)) { return; } - $actions = $this->getActionsToLookup(); + + /** @var ActionDimension[] $dimensions */ + $dimensions = ActionDimension::getAllDimensions(); + $actions = $this->getActionsToLookup(); + + foreach ($dimensions as $dimension) { + $value = $dimension->onLookupAction($this->request, $this); + + if (false !== $value) { + if (is_float($value)) { + $value = Common::forceDotAsSeparatorForDecimalPoint($value); + } + + $field = $dimension->getColumnName(); + + if (empty($field)) { + $dimensionClass = get_class($dimension); + throw new Exception('Dimension ' . $dimensionClass . ' does not define a field name'); + } + + $actionId = $dimension->getActionId(); + $actions[$field] = array($value, $actionId); + Common::printDebug("$field = $value"); + } + } + $actions = array_filter($actions, 'count'); - if(empty($actions)) { + if (empty($actions)) { return; } @@ -239,72 +344,101 @@ abstract class Action /** * Records in the DB the association between the visit and this action. * - * @param int $idVisit is the ID of the current visit in the DB table log_visit - * @param $visitorIdCookie * @param int $idReferrerActionUrl is the ID of the last action done by the current visit. * @param $idReferrerActionName - * @param int $timeSpentReferrerAction is the number of seconds since the last action was done. - * It is directly related to idReferrerActionUrl. + * @param Visitor $visitor */ - public function record($idVisit, $visitorIdCookie, $idReferrerActionUrl, $idReferrerActionName, $timeSpentReferrerAction) + public function record(Visitor $visitor, $idReferrerActionUrl, $idReferrerActionName) { $this->loadIdsFromLogActionTable(); - $idActionName = in_array($this->getActionType(), array(Tracker\Action::TYPE_PAGE_TITLE, - Tracker\Action::TYPE_PAGE_URL, - Tracker\Action::TYPE_SITE_SEARCH - )) - ? (int)$this->getIdActionName() - : null; - $visitAction = array( - 'idvisit' => $idVisit, - 'idsite' => $this->request->getIdSite(), - 'idvisitor' => $visitorIdCookie, - 'server_time' => Tracker::getDatetimeFromTimestamp($this->request->getCurrentTimestamp()), - 'idaction_url' => $this->getIdActionUrl(), - 'idaction_name' => $idActionName, - 'idaction_url_ref' => $idReferrerActionUrl, - 'idaction_name_ref' => $idReferrerActionName, - 'time_spent_ref_action' => $timeSpentReferrerAction + 'idvisit' => $visitor->getVisitorColumn('idvisit'), + 'idsite' => $this->request->getIdSite(), + 'idvisitor' => $visitor->getVisitorColumn('idvisitor'), + 'idaction_url' => $this->getIdActionUrl(), + 'idaction_url_ref' => $idReferrerActionUrl, + 'idaction_name_ref' => $idReferrerActionName ); - foreach($this->actionIdsCached as $field => $idAction) { - $visitAction[$field] = $idAction; + /** @var ActionDimension[] $dimensions */ + $dimensions = ActionDimension::getAllDimensions(); + + foreach ($dimensions as $dimension) { + $value = $dimension->onNewAction($this->request, $visitor, $this); + + if ($value !== false) { + if (is_float($value)) { + $value = Common::forceDotAsSeparatorForDecimalPoint($value); + } + + $visitAction[$dimension->getColumnName()] = $value; + } + } + + // idaction_name is NULLable. we only set it when applicable + if ($this->isActionHasActionName()) { + $visitAction['idaction_name'] = (int)$this->getIdActionName(); + } + + foreach ($this->actionIdsCached as $field => $idAction) { + $visitAction[$field] = ($idAction === false) ? 0 : $idAction; } $customValue = $this->getCustomFloatValue(); if (!empty($customValue)) { - $visitAction[self::DB_COLUMN_CUSTOM_FLOAT] = $customValue; + $visitAction[self::DB_COLUMN_CUSTOM_FLOAT] = Common::forceDotAsSeparatorForDecimalPoint($customValue); } - $customVariables = $this->getCustomVariables(); - if (!empty($customVariables)) { - Common::printDebug("Page level Custom Variables: "); - Common::printDebug($customVariables); - } + $visitAction = array_merge($visitAction, $this->customFields); - $visitAction = array_merge($visitAction, $customVariables); - $fields = implode(", ", array_keys($visitAction)); - $bind = array_values($visitAction); - $values = Common::getSqlStringFieldsArray($visitAction); + $this->idLinkVisitAction = $this->getModel()->createAction($visitAction); - $sql = "INSERT INTO " . Common::prefixTable('log_link_visit_action') . " ($fields) VALUES ($values)"; - Tracker::getDatabase()->query($sql, $bind); - - $this->idLinkVisitAction = Tracker::getDatabase()->lastInsertId(); $visitAction['idlink_va'] = $this->idLinkVisitAction; Common::printDebug("Inserted new action:"); - Common::printDebug($visitAction); + $visitActionDebug = $visitAction; + $visitActionDebug['idvisitor'] = bin2hex($visitActionDebug['idvisitor']); + Common::printDebug($visitActionDebug); /** * Triggered after successfully persisting a [visit action entity](/guides/persistence-and-the-mysql-backend#visit-actions). - * + * + * This event is deprecated, use [Dimensions](http://developer.piwik.org/guides/dimensions) instead. + * * @param Action $tracker Action The Action tracker instance. * @param array $visitAction The visit action entity that was persisted. Read * [this](/guides/persistence-and-the-mysql-backend#visit-actions) to see what it contains. + * @deprecated */ Piwik::postEvent('Tracker.recordAction', array($trackerAction = $this, $visitAction)); } + + public function writeDebugInfo() + { + $type = self::getTypeAsString($this->getActionType()); + $name = $this->getActionName(); + $url = $this->getActionUrl(); + + Common::printDebug("Action is a $type, + Action name = " . $name . ", + Action URL = " . $url); + + return true; + } + + private function getModel() + { + return new Model(); + } + + /** + * @return bool + */ + private function isActionHasActionName() + { + $types = array(self::TYPE_PAGE_TITLE, self::TYPE_PAGE_URL, self::TYPE_SITE_SEARCH); + + return in_array($this->getActionType(), $types); + } } diff --git a/www/analytics/core/Tracker/ActionClickUrl.php b/www/analytics/core/Tracker/ActionClickUrl.php deleted file mode 100644 index 0eacedf1..00000000 --- a/www/analytics/core/Tracker/ActionClickUrl.php +++ /dev/null @@ -1,63 +0,0 @@ -setActionUrl($url); - } - - protected function getActionsToLookup() - { - return array( - // Note: we do not normalize download/oulink URL - 'idaction_url' => array($this->getActionUrl(), $this->getActionType()) - ); - } - - function writeDebugInfo() - { - parent::writeDebugInfo(); - - if (self::detectActionIsOutlinkOnAliasHost($this, $this->request->getIdSite())) { - Common::printDebug("INFO: The outlink URL host is one of the known host for this website. "); - } - } - - /** - * Detect whether action is an outlink given host aliases - * - * @param Action $action - * @return bool true if the outlink the visitor clicked on points to one of the known hosts for this website - */ - public static function detectActionIsOutlinkOnAliasHost(Action $action, $idSite) - { - if ($action->getActionType() != Action::TYPE_OUTLINK) { - return false; - } - $decodedActionUrl = $action->getActionUrl(); - $actionUrlParsed = @parse_url($decodedActionUrl); - if (!isset($actionUrlParsed['host'])) { - return false; - } - return Visit::isHostKnownAliasHost($actionUrlParsed['host'], $idSite); - } -} diff --git a/www/analytics/core/Tracker/ActionEvent.php b/www/analytics/core/Tracker/ActionEvent.php deleted file mode 100644 index 511cf792..00000000 --- a/www/analytics/core/Tracker/ActionEvent.php +++ /dev/null @@ -1,78 +0,0 @@ -setActionUrl($url); - $this->eventCategory = trim($eventCategory); - $this->eventAction = trim($eventAction); - $this->eventName = trim($request->getParam('e_n')); - $this->eventValue = trim($request->getParam('e_v')); - } - - function getCustomFloatValue() - { - return $this->eventValue; - } - - protected function getActionsToLookup() - { - $actions = array( - 'idaction_url' => $this->getUrlAndType() - ); - - if(strlen($this->eventName) > 0) { - $actions['idaction_name'] = array($this->eventName, Action::TYPE_EVENT_NAME); - } - if(strlen($this->eventCategory) > 0) { - $actions['idaction_event_category'] = array($this->eventCategory, Action::TYPE_EVENT_CATEGORY); - } - if(strlen($this->eventAction) > 0) { - $actions['idaction_event_action'] = array($this->eventAction, Action::TYPE_EVENT_ACTION); - } - return $actions; - } - - // Do not track this Event URL as Entry/Exit Page URL (leave the existing entry/exit) - public function getIdActionUrlForEntryAndExitIds() - { - return false; - } - - // Do not track this Event Name as Entry/Exit Page Title (leave the existing entry/exit) - public function getIdActionNameForEntryAndExitIds() - { - return false; - } - - public function writeDebugInfo() - { - $write = parent::writeDebugInfo(); - if($write) { - Common::printDebug("Event Category = " . $this->eventCategory . ", - Event Action = " . $this->eventAction . ", - Event Name = " . $this->eventName . ", - Event Value = " . $this->getCustomFloatValue()); - } - return $write; - } - -} diff --git a/www/analytics/core/Tracker/ActionPageview.php b/www/analytics/core/Tracker/ActionPageview.php index eae69145..eb98dd4b 100644 --- a/www/analytics/core/Tracker/ActionPageview.php +++ b/www/analytics/core/Tracker/ActionPageview.php @@ -1,6 +1,6 @@ getParam('url'); $this->setActionUrl($url); $actionName = $request->getParam('action_name'); @@ -38,34 +37,54 @@ class ActionPageview extends Action { return array( 'idaction_name' => array($this->getActionName(), Action::TYPE_PAGE_TITLE), - 'idaction_url' => $this->getUrlAndType() + 'idaction_url' => $this->getUrlAndType() ); } - function getCustomFloatValue() + public function getCustomFloatValue() { return $this->request->getPageGenerationTime(); } - protected function cleanupActionName($actionName) + public static function shouldHandle(Request $request) + { + return true; + } + + private function cleanupActionName($actionName) { // get the delimiter, by default '/'; BC, we read the old action_category_delimiter first (see #1067) - $actionCategoryDelimiter = isset(Config::getInstance()->General['action_category_delimiter']) - ? Config::getInstance()->General['action_category_delimiter'] - : Config::getInstance()->General['action_url_category_delimiter']; + $actionCategoryDelimiter = $this->getActionCategoryDelimiter(); // create an array of the categories delimited by the delimiter $split = explode($actionCategoryDelimiter, $actionName); + $split = $this->trimEveryCategory($split); + $split = $this->removeEmptyCategories($split); - // trim every category - $split = array_map('trim', $split); - - // remove empty categories - $split = array_filter($split, 'strlen'); - - // rebuild the name from the array of cleaned categories - $actionName = implode($actionCategoryDelimiter, $split); - return $actionName; + return $this->rebuildNameOfCleanedCategories($actionCategoryDelimiter, $split); } + private function rebuildNameOfCleanedCategories($actionCategoryDelimiter, $split) + { + return implode($actionCategoryDelimiter, $split); + } + + private function removeEmptyCategories($split) + { + return array_filter($split, 'strlen'); + } + + private function trimEveryCategory($split) + { + return array_map('trim', $split); + } + + private function getActionCategoryDelimiter() + { + if (isset(Config::getInstance()->General['action_category_delimiter'])) { + return Config::getInstance()->General['action_category_delimiter']; + } + + return Config::getInstance()->General['action_url_category_delimiter']; + } } diff --git a/www/analytics/core/Tracker/Cache.php b/www/analytics/core/Tracker/Cache.php index edf8185d..b18003aa 100644 --- a/www/analytics/core/Tracker/Cache.php +++ b/www/analytics/core/Tracker/Cache.php @@ -1,6 +1,6 @@ Tracker['tracker_cache_file_ttl']; - self::$trackerCache = new CacheFile('tracker', $ttl); + if (is_null(self::$cache)) { + self::$cache = PiwikCache::getLazyCache(); } - return self::$trackerCache; + + return self::$cache; + } + + private static function getTtl() + { + return Config::getInstance()->Tracker['tracker_cache_file_ttl']; } /** @@ -44,67 +54,68 @@ class Cache * @param int $idSite * @return array */ - static function getCacheWebsiteAttributes($idSite) + public static function getCacheWebsiteAttributes($idSite) { - if($idSite == 'all') { - return array(); - } - $idSite = (int)$idSite; - if($idSite <= 0) { + if ('all' == $idSite) { return array(); } - $cache = self::getInstance(); - if (($cacheContent = $cache->get($idSite)) !== false) { + $idSite = (int) $idSite; + if ($idSite <= 0) { + return array(); + } + + $cache = self::getCache(); + $cacheId = $idSite; + $cacheContent = $cache->fetch($cacheId); + + if (false !== $cacheContent) { return $cacheContent; } Tracker::initCorePiwikInTrackerMode(); - // save current user privilege and temporarily assume Super User privilege - $isSuperUser = Piwik::hasUserSuperUserAccess(); - Piwik::setUserHasSuperUserAccess(); - $content = array(); - - /** - * Triggered to get the attributes of a site entity that might be used by the - * Tracker. - * - * Plugins add new site attributes for use in other tracking events must - * use this event to put those attributes in the Tracker Cache. - * - * **Example** - * - * public function getSiteAttributes($content, $idSite) - * { - * $sql = "SELECT info FROM " . Common::prefixTable('myplugin_extra_site_info') . " WHERE idsite = ?"; - * $content['myplugin_site_data'] = Db::fetchOne($sql, array($idSite)); - * } - * - * @param array &$content Array mapping of site attribute names with values. - * @param int $idSite The site ID to get attributes for. - */ - Piwik::postEvent('Tracker.Cache.getSiteAttributes', array(&$content, $idSite)); - Common::printDebug("Website $idSite tracker cache was re-created."); - - // restore original user privilege - Piwik::setUserHasSuperUserAccess($isSuperUser); + Access::doAsSuperUser(function () use (&$content, $idSite) { + /** + * Triggered to get the attributes of a site entity that might be used by the + * Tracker. + * + * Plugins add new site attributes for use in other tracking events must + * use this event to put those attributes in the Tracker Cache. + * + * **Example** + * + * public function getSiteAttributes($content, $idSite) + * { + * $sql = "SELECT info FROM " . Common::prefixTable('myplugin_extra_site_info') . " WHERE idsite = ?"; + * $content['myplugin_site_data'] = Db::fetchOne($sql, array($idSite)); + * } + * + * @param array &$content Array mapping of site attribute names with values. + * @param int $idSite The site ID to get attributes for. + */ + Piwik::postEvent('Tracker.Cache.getSiteAttributes', array(&$content, $idSite)); + Common::printDebug("Website $idSite tracker cache was re-created."); + }); // if nothing is returned from the plugins, we don't save the content // this is not expected: all websites are expected to have at least one URL if (!empty($content)) { - $cache->set($idSite, $content); + $cache->save($cacheId, $content, self::getTtl()); } + + Tracker::restoreTrackerPlugins(); + return $content; } /** * Clear general (global) cache */ - static public function clearCacheGeneral() + public static function clearCacheGeneral() { - self::getInstance()->delete('general'); + self::getCache()->delete(self::$cacheIdGeneral); } /** @@ -113,12 +124,12 @@ class Cache * * @return array */ - static public function getCacheGeneral() + public static function getCacheGeneral() { - $cache = self::getInstance(); - $cacheId = 'general'; + $cache = self::getCache(); + $cacheContent = $cache->fetch(self::$cacheIdGeneral); - if (($cacheContent = $cache->get($cacheId)) !== false) { + if (false !== $cacheContent) { return $cacheContent; } @@ -131,26 +142,29 @@ class Cache /** * Triggered before the [general tracker cache](/guides/all-about-tracking#the-tracker-cache) * is saved to disk. This event can be used to add extra content to the cache. - * + * * Data that is used during tracking but is expensive to compute/query should be * cached to keep tracking efficient. One example of such data are options * that are stored in the piwik_option table. Querying data for each tracking * request means an extra unnecessary database query for each visitor action. Using * a cache solves this problem. - * + * * **Example** - * + * * public function setTrackerCacheGeneral(&$cacheContent) * { * $cacheContent['MyPlugin.myCacheKey'] = Option::get('MyPlugin_myOption'); * } - * + * * @param array &$cacheContent Array of cached data. Each piece of data must be * mapped by name. */ Piwik::postEvent('Tracker.setTrackerCacheGeneral', array(&$cacheContent)); self::setCacheGeneral($cacheContent); Common::printDebug("General tracker cache was re-created."); + + Tracker::restoreTrackerPlugins(); + return $cacheContent; } @@ -160,12 +174,11 @@ class Cache * @param mixed $value * @return bool */ - static public function setCacheGeneral($value) + public static function setCacheGeneral($value) { - $cache = self::getInstance(); - $cacheId = 'general'; - $cache->set($cacheId, $value); - return true; + $cache = self::getCache(); + + return $cache->save(self::$cacheIdGeneral, $value, self::getTtl()); } /** @@ -173,11 +186,12 @@ class Cache * * @param array|int $idSites Array of idSites to clear cache for */ - static public function regenerateCacheWebsiteAttributes($idSites = array()) + public static function regenerateCacheWebsiteAttributes($idSites = array()) { if (!is_array($idSites)) { $idSites = array($idSites); } + foreach ($idSites as $idSite) { self::deleteCacheWebsiteAttributes($idSite); self::getCacheWebsiteAttributes($idSite); @@ -189,17 +203,16 @@ class Cache * * @param string $idSite (website ID of the site to clear cache for */ - static public function deleteCacheWebsiteAttributes($idSite) + public static function deleteCacheWebsiteAttributes($idSite) { - $idSite = (int)$idSite; - self::getInstance()->delete($idSite); + self::getCache()->delete((int) $idSite); } /** * Deletes all Tracker cache files */ - static public function deleteTrackerCache() + public static function deleteTrackerCache() { - self::getInstance()->deleteAll(); + self::getCache()->flushAll(); } } diff --git a/www/analytics/core/Tracker/Db.php b/www/analytics/core/Tracker/Db.php index 495eb683..e5ec25f5 100644 --- a/www/analytics/core/Tracker/Db.php +++ b/www/analytics/core/Tracker/Db.php @@ -1,6 +1,6 @@ queriesProfiling[$query])) $this->queriesProfiling[$query] = array('sum_time_ms' => 0, 'count' => 0); - $time = $timer->getTimeMs(2); + if (!isset($this->queriesProfiling[$query])) { + $this->queriesProfiling[$query] = array('sum_time_ms' => 0, 'count' => 0); + } + + $time = $timer->getTimeMs(2); $time += $this->queriesProfiling[$query]['sum_time_ms']; $count = $this->queriesProfiling[$query]['count'] + 1; + $this->queriesProfiling[$query] = array('sum_time_ms' => $time, 'count' => $count); } @@ -97,13 +106,13 @@ abstract class Db self::$profiling = false; foreach ($this->queriesProfiling as $query => $info) { - $time = $info['sum_time_ms']; + $time = $info['sum_time_ms']; + $time = Common::forceDotAsSeparatorForDecimalPoint($time); $count = $info['count']; $queryProfiling = "INSERT INTO " . Common::prefixTable('log_profiling') . " (query,count,sum_time_ms) VALUES (?,$count,$time) - ON DUPLICATE KEY - UPDATE count=count+$count,sum_time_ms=sum_time_ms+$time"; + ON DUPLICATE KEY UPDATE count=count+$count,sum_time_ms=sum_time_ms+$time"; $this->query($queryProfiling, array($query)); } @@ -222,4 +231,63 @@ abstract class Db * @return bool True if error number matches; false otherwise */ abstract public function isErrNo($e, $errno); + + /** + * Factory to create database objects + * + * @param array $configDb Database configuration + * @throws Exception + * @return \Piwik\Tracker\Db\Mysqli|\Piwik\Tracker\Db\Pdo\Mysql + */ + public static function factory($configDb) + { + /** + * Triggered before a connection to the database is established by the Tracker. + * + * This event can be used to change the database connection settings used by the Tracker. + * + * @param array $dbInfos Reference to an array containing database connection info, + * including: + * + * - **host**: The host name or IP address to the MySQL database. + * - **username**: The username to use when connecting to the + * database. + * - **password**: The password to use when connecting to the + * database. + * - **dbname**: The name of the Piwik MySQL database. + * - **port**: The MySQL database port to use. + * - **adapter**: either `'PDO\MYSQL'` or `'MYSQLI'` + * - **type**: The MySQL engine to use, for instance 'InnoDB' + */ + Piwik::postEvent('Tracker.getDatabaseConfig', array(&$configDb)); + + switch ($configDb['adapter']) { + case 'PDO\MYSQL': + case 'PDO_MYSQL': // old format pre Piwik 2 + require_once PIWIK_INCLUDE_PATH . '/core/Tracker/Db/Pdo/Mysql.php'; + return new Mysql($configDb); + + case 'MYSQLI': + require_once PIWIK_INCLUDE_PATH . '/core/Tracker/Db/Mysqli.php'; + return new Mysqli($configDb); + } + + throw new Exception('Unsupported database adapter ' . $configDb['adapter']); + } + + public static function connectPiwikTrackerDb() + { + $db = null; + $configDb = Config::getInstance()->database; + + if (!isset($configDb['port'])) { + // before 0.2.4 there is no port specified in config file + $configDb['port'] = '3306'; + } + + $db = self::factory($configDb); + $db->connect(); + + return $db; + } } diff --git a/www/analytics/core/Tracker/Db/DbException.php b/www/analytics/core/Tracker/Db/DbException.php index fc3ffea9..a067f09f 100644 --- a/www/analytics/core/Tracker/Db/DbException.php +++ b/www/analytics/core/Tracker/Db/DbException.php @@ -1,6 +1,6 @@ host = null; $this->port = null; $this->socket = $dbInfo['unix_socket']; - } else if ($dbInfo['port'][0] == '/') { + } elseif ($dbInfo['port'][0] == '/') { $this->host = null; $this->port = null; $this->socket = $dbInfo['port']; } else { $this->host = $dbInfo['host']; - $this->port = $dbInfo['port']; + $this->port = (int)$dbInfo['port']; $this->socket = null; } $this->dbname = $dbInfo['dbname']; @@ -72,7 +73,14 @@ class Mysqli extends Db $timer = $this->initProfiler(); } - $this->connection = mysqli_connect($this->host, $this->username, $this->password, $this->dbname, $this->port, $this->socket); + $this->connection = mysqli_init(); + + // Make sure MySQL returns all matched rows on update queries including + // rows that actually didn't have to be updated because the values didn't + // change. This matches common behaviour among other database systems. + // See #6296 why this is important in tracker + $flags = MYSQLI_CLIENT_FOUND_ROWS; + mysqli_real_connect($this->connection, $this->host, $this->username, $this->password, $this->dbname, $this->port, $this->socket, $flags); if (!$this->connection || mysqli_connect_errno()) { throw new DbException("Connect failed: " . mysqli_connect_error()); } @@ -204,8 +212,8 @@ class Mysqli extends Db return $result; } catch (Exception $e) { throw new DbException("Error query: " . $e->getMessage() . " - In query: $query - Parameters: " . var_export($parameters, true)); + In query: $query + Parameters: " . var_export($parameters, true), $e->getCode()); } } @@ -231,7 +239,7 @@ class Mysqli extends Db { if (!$parameters) { $parameters = array(); - } else if (!is_array($parameters)) { + } elseif (!is_array($parameters)) { $parameters = array($parameters); } @@ -276,4 +284,62 @@ class Mysqli extends Db { return mysqli_affected_rows($this->connection); } + + /** + * Start Transaction + * @return string TransactionID + */ + public function beginTransaction() + { + if (!$this->activeTransaction === false) { + return; + } + + if ($this->connection->autocommit(false)) { + $this->activeTransaction = uniqid(); + return $this->activeTransaction; + } + } + + /** + * Commit Transaction + * @param $xid + * @throws DbException + * @internal param TransactionID $string from beginTransaction + */ + public function commit($xid) + { + if ($this->activeTransaction != $xid || $this->activeTransaction === false) { + return; + } + + $this->activeTransaction = false; + + if (!$this->connection->commit()) { + throw new DbException("Commit failed"); + } + + $this->connection->autocommit(true); + } + + /** + * Rollback Transaction + * @param $xid + * @throws DbException + * @internal param TransactionID $string from beginTransaction + */ + public function rollBack($xid) + { + if ($this->activeTransaction != $xid || $this->activeTransaction === false) { + return; + } + + $this->activeTransaction = false; + + if (!$this->connection->rollback()) { + throw new DbException("Rollback failed"); + } + + $this->connection->autocommit(true); + } } diff --git a/www/analytics/core/Tracker/Db/Pdo/Mysql.php b/www/analytics/core/Tracker/Db/Pdo/Mysql.php index 3a2e04ab..7e4f7458 100644 --- a/www/analytics/core/Tracker/Db/Pdo/Mysql.php +++ b/www/analytics/core/Tracker/Db/Pdo/Mysql.php @@ -1,6 +1,6 @@ dsn = $driverName . ':dbname=' . $dbInfo['dbname'] . ';unix_socket=' . $dbInfo['unix_socket']; - } else if (!empty($dbInfo['port']) && $dbInfo['port'][0] == '/') { + } elseif (!empty($dbInfo['port']) && $dbInfo['port'][0] == '/') { $this->dsn = $driverName . ':dbname=' . $dbInfo['dbname'] . ';unix_socket=' . $dbInfo['port']; } else { $this->dsn = $driverName . ':dbname=' . $dbInfo['dbname'] . ';host=' . $dbInfo['host'] . ';port=' . $dbInfo['port']; } + $this->username = $dbInfo['username']; $this->password = $dbInfo['password']; - $this->charset = isset($dbInfo['charset']) ? $dbInfo['charset'] : null; + + if (isset($dbInfo['charset'])) { + $this->charset = $dbInfo['charset']; + $this->dsn .= ';charset=' . $this->charset; + } } public function __destruct() @@ -66,8 +73,17 @@ class Mysql extends Db $timer = $this->initProfiler(); } - $this->connection = @new PDO($this->dsn, $this->username, $this->password, $config = array()); - $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + // Make sure MySQL returns all matched rows on update queries including + // rows that actually didn't have to be updated because the values didn't + // change. This matches common behaviour among other database systems. + // See #6296 why this is important in tracker + $config = array( + PDO::MYSQL_ATTR_FOUND_ROWS => true, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + ); + + $this->connection = @new PDO($this->dsn, $this->username, $this->password, $config); + // we may want to setAttribute(PDO::ATTR_TIMEOUT ) to a few seconds (default is 60) in case the DB is locked // the piwik.php would stay waiting for the database... bad! // we delete the password from this object "just in case" it could be printed @@ -192,9 +208,8 @@ class Mysql extends Db } return $sth; } catch (PDOException $e) { - throw new DbException("Error query: " . $e->getMessage() . " - In query: $query - Parameters: " . var_export($parameters, true)); + $message = $e->getMessage() . " In query: $query Parameters: " . var_export($parameters, true); + throw new DbException("Error query: " . $message, (int) $e->getCode()); } } @@ -234,4 +249,58 @@ class Mysql extends Db { return $queryResult->rowCount(); } + + /** + * Start Transaction + * @return string TransactionID + */ + public function beginTransaction() + { + if (!$this->activeTransaction === false) { + return; + } + + if ($this->connection->beginTransaction()) { + $this->activeTransaction = uniqid(); + return $this->activeTransaction; + } + } + + /** + * Commit Transaction + * @param $xid + * @throws DbException + * @internal param TransactionID $string from beginTransaction + */ + public function commit($xid) + { + if ($this->activeTransaction != $xid || $this->activeTransaction === false) { + return; + } + + $this->activeTransaction = false; + + if (!$this->connection->commit()) { + throw new DbException("Commit failed"); + } + } + + /** + * Rollback Transaction + * @param $xid + * @throws DbException + * @internal param TransactionID $string from beginTransaction + */ + public function rollBack($xid) + { + if ($this->activeTransaction != $xid || $this->activeTransaction === false) { + return; + } + + $this->activeTransaction = false; + + if (!$this->connection->rollBack()) { + throw new DbException("Rollback failed"); + } + } } diff --git a/www/analytics/core/Tracker/Db/Pdo/Pgsql.php b/www/analytics/core/Tracker/Db/Pdo/Pgsql.php index 60087c65..b70122aa 100644 --- a/www/analytics/core/Tracker/Db/Pdo/Pgsql.php +++ b/www/analytics/core/Tracker/Db/Pdo/Pgsql.php @@ -1,6 +1,6 @@ request = $request; - $this->init(); - } + if (empty($visitInformation['visit_goal_buyer'])) { + return false; + } - function init() - { - $this->orderId = $this->request->getParam('ec_id'); - $this->isGoalAnOrder = !empty($this->orderId); - $this->idGoal = $this->request->getParam('idgoal'); - $this->requestIsEcommerce = ($this->idGoal == 0); - } + $goalBuyer = $visitInformation['visit_goal_buyer']; + $types = array(GoalManager::TYPE_BUYER_OPEN_CART, GoalManager::TYPE_BUYER_ORDERED_AND_OPEN_CART); - function getBuyerType($existingType = GoalManager::TYPE_BUYER_NONE) - { // Was there a Cart for this visit prior to the order? - $this->isThereExistingCartInVisit = in_array($existingType, - array(GoalManager::TYPE_BUYER_OPEN_CART, - GoalManager::TYPE_BUYER_ORDERED_AND_OPEN_CART)); - - if (!$this->requestIsEcommerce) { - return $existingType; - } - if ($this->isGoalAnOrder) { - return self::TYPE_BUYER_ORDERED; - } - // request is Add to Cart - if ($existingType == self::TYPE_BUYER_ORDERED - || $existingType == self::TYPE_BUYER_ORDERED_AND_OPEN_CART - ) { - return self::TYPE_BUYER_ORDERED_AND_OPEN_CART; - } - return self::TYPE_BUYER_OPEN_CART; + return in_array($goalBuyer, $types); } - static public function getGoalDefinitions($idSite) + public static function getGoalDefinitions($idSite) { $websiteAttributes = Cache::getCacheWebsiteAttributes($idSite); + if (isset($websiteAttributes['goals'])) { return $websiteAttributes['goals']; } + return array(); } - static public function getGoalDefinition($idSite, $idGoal) + public static function getGoalDefinition($idSite, $idGoal) { $goals = self::getGoalDefinitions($idSite); + foreach ($goals as $goal) { if ($goal['idgoal'] == $idGoal) { return $goal; } } + throw new Exception('Goal not found'); } - static public function getGoalIds($idSite) + public static function getGoalIds($idSite) { - $goals = self::getGoalDefinitions($idSite); + $goals = self::getGoalDefinitions($idSite); $goalIds = array(); + foreach ($goals as $goal) { $goalIds[] = $goal['idgoal']; } + return $goalIds; } @@ -127,109 +117,124 @@ class GoalManager * @param int $idSite * @param Action $action * @throws Exception - * @return int Number of goals matched + * @return array[] Goals matched */ - function detectGoalsMatchingUrl($idSite, $action) + public function detectGoalsMatchingUrl($idSite, $action) { if (!Common::isGoalPluginEnabled()) { - return false; + return array(); } - $decodedActionUrl = $action->getActionUrl(); - $actionType = $action->getActionType(); $goals = $this->getGoalDefinitions($idSite); + + $convertedGoals = array(); foreach ($goals as $goal) { - $attribute = $goal['match_attribute']; - // if the attribute to match is not the type of the current action - if ( (($attribute == 'url' || $attribute == 'title') && $actionType != Action::TYPE_PAGE_URL) - || ($attribute == 'file' && $actionType != Action::TYPE_DOWNLOAD) - || ($attribute == 'external_website' && $actionType != Action::TYPE_OUTLINK) - || ($attribute == 'manually') - ) { - continue; - } - - $url = $decodedActionUrl; - // Matching on Page Title - if ($attribute == 'title') { - $url = $action->getActionName(); - } - $pattern_type = $goal['pattern_type']; - - $match = $this->isUrlMatchingGoal($goal, $pattern_type, $url); - if ($match) { - $goal['url'] = $decodedActionUrl; - $this->convertedGoals[] = $goal; + $convertedUrl = $this->detectGoalMatch($goal, $action); + if (!empty($convertedUrl)) { + $convertedGoals[] = array('url' => $convertedUrl) + $goal; } } - return count($this->convertedGoals) > 0; + return $convertedGoals; } - function detectGoalId($idSite) + /** + * Detects if an Action matches a given goal. If it does, the URL that triggered the goal + * is returned. Otherwise null is returned. + * + * @param array $goal + * @param Action $action + * @return string|null + */ + public function detectGoalMatch($goal, Action $action) + { + $actionType = $action->getActionType(); + + $attribute = $goal['match_attribute']; + + // if the attribute to match is not the type of the current action + if ((($attribute == 'url' || $attribute == 'title') && $actionType != Action::TYPE_PAGE_URL) + || ($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 + ) { + return null; + } + + + 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; + // url, external_website, file, manually... + default: + $url = $action->getActionUrlRaw(); + break; + } + + $pattern_type = $goal['pattern_type']; + + $match = $this->isUrlMatchingGoal($goal, $pattern_type, $url); + if (!$match) { + return null; + } + + return $action->getActionUrl(); + } + + public function detectGoalId($idSite, Request $request) { if (!Common::isGoalPluginEnabled()) { - return false; + return null; } - $goals = $this->getGoalDefinitions($idSite); - if (!isset($goals[$this->idGoal])) { - return false; - } - $goal = $goals[$this->idGoal]; - $url = $this->request->getParam('url'); + $idGoal = $request->getParam('idgoal'); + + $goals = $this->getGoalDefinitions($idSite); + + if (!isset($goals[$idGoal])) { + return null; + } + + $goal = $goals[$idGoal]; + + $url = $request->getParam('url'); $goal['url'] = PageUrl::excludeQueryParametersFromUrl($url, $idSite); - $goal['revenue'] = $this->getRevenue($this->request->getGoalRevenue($goal['revenue'])); - $this->convertedGoals[] = $goal; - return true; + return $goal; } /** * Records one or several goals matched in this request. * - * @param int $idSite + * @param Visitor $visitor * @param array $visitorInformation * @param array $visitCustomVariables * @param Action $action */ - public function recordGoals($idSite, $visitorInformation, $visitCustomVariables, $action) + public function recordGoals(VisitProperties $visitProperties, Request $request) { - $referrerTimestamp = $this->request->getParam('_refts'); - $referrerUrl = $this->request->getParam('_ref'); - $referrerCampaignName = trim(urldecode($this->request->getParam('_rcn'))); - $referrerCampaignKeyword = trim(urldecode($this->request->getParam('_rck'))); - $browserLanguage = $this->request->getBrowserLanguage(); + $visitorInformation = $visitProperties->getProperties(); + $visitCustomVariables = $request->getMetadata('CustomVariables', 'visitCustomVariables') ?: array(); - $location_country = isset($visitorInformation['location_country']) - ? $visitorInformation['location_country'] - : Common::getCountry( - $browserLanguage, - $enableLanguageToCountryGuess = Config::getInstance()->Tracker['enable_language_to_country_guess'], - $visitorInformation['location_ip'] - ); + /** @var Action $action */ + $action = $request->getMetadata('Actions', 'action'); - $goal = array( - 'idvisit' => $visitorInformation['idvisit'], - 'idsite' => $idSite, - 'idvisitor' => $visitorInformation['idvisitor'], - 'server_time' => Tracker::getDatetimeFromTimestamp($visitorInformation['visit_last_action_time']), - 'location_country' => $location_country, - 'visitor_returning' => $visitorInformation['visitor_returning'], - 'visitor_days_since_first' => $visitorInformation['visitor_days_since_first'], - 'visitor_days_since_order' => $visitorInformation['visitor_days_since_order'], - 'visitor_count_visits' => $visitorInformation['visitor_count_visits'], - ); - - $extraLocationCols = array('location_region', 'location_city', 'location_latitude', 'location_longitude'); - foreach ($extraLocationCols as $col) { - if (isset($visitorInformation[$col])) { - $goal[$col] = $visitorInformation[$col]; - } - } + $goal = $this->getGoalFromVisitor($visitProperties, $request, $action); // Copy Custom Variables from Visit row to the Goal conversion // Otherwise, set the Custom Variables found in the cookie sent with this request $goal += $visitCustomVariables; - $maxCustomVariables = CustomVariables::getMaxCustomVariables(); + $maxCustomVariables = CustomVariables::getNumUsableCustomVariables(); for ($i = 1; $i <= $maxCustomVariables; $i++) { if (isset($visitorInformation['custom_var_k' . $i]) @@ -244,60 +249,12 @@ class GoalManager } } - // Attributing the correct Referrer to this conversion. - // Priority order is as follows: - // 0) In some cases, the campaign is not passed from the JS so we look it up from the current visit - // 1) Campaign name/kwd parsed in the JS - // 2) Referrer URL stored in the _ref cookie - // 3) If no info from the cookie, attribute to the current visit referrer - - // 3) Default values: current referrer - $type = $visitorInformation['referer_type']; - $name = $visitorInformation['referer_name']; - $keyword = $visitorInformation['referer_keyword']; - $time = $visitorInformation['visit_first_action_time']; - - // 0) In some (unknown!?) cases the campaign is not found in the attribution cookie, but the URL ref was found. - // In this case we look up if the current visit is credited to a campaign and will credit this campaign rather than the URL ref (since campaigns have higher priority) - if (empty($referrerCampaignName) - && $type == Common::REFERRER_TYPE_CAMPAIGN - && !empty($name) - ) { - // Use default values per above - } // 1) Campaigns from 1st party cookie - elseif (!empty($referrerCampaignName)) { - $type = Common::REFERRER_TYPE_CAMPAIGN; - $name = $referrerCampaignName; - $keyword = $referrerCampaignKeyword; - $time = $referrerTimestamp; - } // 2) Referrer URL parsing - elseif (!empty($referrerUrl)) { - $referrer = new Referrer(); - $referrer = $referrer->getReferrerInformation($referrerUrl, $currentUrl = '', $idSite); - - // if the parsed referrer is interesting enough, ie. website or search engine - if (in_array($referrer['referer_type'], array(Common::REFERRER_TYPE_SEARCH_ENGINE, Common::REFERRER_TYPE_WEBSITE))) { - $type = $referrer['referer_type']; - $name = $referrer['referer_name']; - $keyword = $referrer['referer_keyword']; - $time = $referrerTimestamp; - } - } - $this->setCampaignValuesToLowercase($type, $name, $keyword); - - $goal += array( - 'referer_type' => $type, - 'referer_name' => $name, - 'referer_keyword' => $keyword, - // this field is currently unused - 'referer_visit_server_date' => date("Y-m-d", $time), - ); - // some goals are converted, so must be ecommerce Order or Cart Update - if ($this->requestIsEcommerce) { - $this->recordEcommerceGoal($goal, $visitorInformation); + $isRequestEcommerce = $request->getMetadata('Ecommerce', 'isRequestEcommerce'); + if ($isRequestEcommerce) { + $this->recordEcommerceGoal($visitProperties, $request, $goal, $action); } else { - $this->recordStandardGoals($goal, $action, $visitorInformation); + $this->recordStandardGoals($visitProperties, $request, $goal, $action); } } @@ -309,10 +266,13 @@ class GoalManager */ protected function getRevenue($revenue) { - if (round($revenue) == $revenue) { - return $revenue; + if (round($revenue) != $revenue) { + $revenue = round($revenue, self::REVENUE_PRECISION); } - return round($revenue, self::REVENUE_PRECISION); + + $revenue = Common::forceDotAsSeparatorForDecimalPoint($revenue); + + return $revenue; } /** @@ -320,92 +280,107 @@ class GoalManager * Will deal with 2 types of conversions: Ecommerce Order and Ecommerce Cart update (Add to cart, Update Cart etc). * * @param array $conversion + * @param Visitor $visitor + * @param Action $action * @param array $visitInformation */ - protected function recordEcommerceGoal($conversion, $visitInformation) + protected function recordEcommerceGoal(VisitProperties $visitProperties, Request $request, $conversion, $action) { - if ($this->isThereExistingCartInVisit) { + $isThereExistingCartInVisit = $request->getMetadata('Goals', 'isThereExistingCartInVisit'); + if ($isThereExistingCartInVisit) { Common::printDebug("There is an existing cart for this visit"); } - if ($this->isGoalAnOrder) { - $conversion['idgoal'] = self::IDGOAL_ORDER; - $conversion['idorder'] = $this->orderId; - $conversion['buster'] = Common::hashStringToInt($this->orderId); - $conversion['revenue_subtotal'] = $this->getRevenue($this->request->getParam('ec_st')); - $conversion['revenue_tax'] = $this->getRevenue($this->request->getParam('ec_tx')); - $conversion['revenue_shipping'] = $this->getRevenue($this->request->getParam('ec_sh')); - $conversion['revenue_discount'] = $this->getRevenue($this->request->getParam('ec_dt')); + $visitor = Visitor::makeFromVisitProperties($visitProperties, $request); + + $isGoalAnOrder = $request->getMetadata('Ecommerce', 'isGoalAnOrder'); + if ($isGoalAnOrder) { $debugMessage = 'The conversion is an Ecommerce order'; + + $orderId = $request->getParam('ec_id'); + + $conversion['idorder'] = $orderId; + $conversion['idgoal'] = self::IDGOAL_ORDER; + $conversion['buster'] = Common::hashStringToInt($orderId); + + $conversionDimensions = ConversionDimension::getAllDimensions(); + $conversion = $this->triggerHookOnDimensions($request, $conversionDimensions, 'onEcommerceOrderConversion', $visitor, $action, $conversion); } // If Cart update, select current items in the previous Cart else { + $debugMessage = 'The conversion is an Ecommerce Cart Update'; + $conversion['buster'] = 0; $conversion['idgoal'] = self::IDGOAL_CART; - $debugMessage = 'The conversion is an Ecommerce Cart Update'; + + $conversionDimensions = ConversionDimension::getAllDimensions(); + $conversion = $this->triggerHookOnDimensions($request, $conversionDimensions, 'onEcommerceCartUpdateConversion', $visitor, $action, $conversion); } - $conversion['revenue'] = $this->getRevenue($this->request->getGoalRevenue($defaultRevenue = 0)); Common::printDebug($debugMessage . ':' . var_export($conversion, true)); // INSERT or Sync items in the Cart / Order for this visit & order - $items = $this->getEcommerceItemsFromRequest(); - if ($items === false) { + $items = $this->getEcommerceItemsFromRequest($request); + + if (false === $items) { return; } $itemsCount = 0; foreach ($items as $item) { - $itemsCount += $item[self::INTERNAL_ITEM_QUANTITY]; + $itemsCount += $item[GoalManager::INTERNAL_ITEM_QUANTITY]; } + $conversion['items'] = $itemsCount; - if($this->isThereExistingCartInVisit) { - $updateWhere = array( - 'idvisit' => $visitInformation['idvisit'], - 'idgoal' => self::IDGOAL_CART, - 'buster' => 0, - ); - $recorded = $this->updateExistingConversion($conversion, $updateWhere); + if ($isThereExistingCartInVisit) { + $recorded = $this->getModel()->updateConversion( + $visitProperties->getProperty('idvisit'), self::IDGOAL_CART, $conversion); } else { - $recorded = $this->insertNewConversion($conversion, $visitInformation); + $recorded = $this->insertNewConversion($conversion, $visitProperties->getProperties(), $request); } if ($recorded) { - $this->recordEcommerceItems($conversion, $items, $visitInformation); + $this->recordEcommerceItems($conversion, $items); } /** * Triggered after successfully persisting an ecommerce conversion. - * + * * _Note: Subscribers should be wary of doing any expensive computation here as it may slow * the tracker down._ - * + * + * This event is deprecated, use [Dimensions](http://developer.piwik.org/guides/dimensions) instead. + * * @param array $conversion The conversion entity that was just persisted. See what information * it contains [here](/guides/persistence-and-the-mysql-backend#conversions). * @param array $visitInformation The visit entity that we are tracking a conversion for. See what * information it contains [here](/guides/persistence-and-the-mysql-backend#visits). + * @deprecated */ - Piwik::postEvent('Tracker.recordEcommerceGoal', array($conversion, $visitInformation)); + Piwik::postEvent('Tracker.recordEcommerceGoal', array($conversion, $visitProperties->getProperties())); } /** * Returns Items read from the request string * @return array|bool */ - protected function getEcommerceItemsFromRequest() + private function getEcommerceItemsFromRequest(Request $request) { - $items = Common::unsanitizeInputValue($this->request->getParam('ec_items')); + $items = $request->getParam('ec_items'); + if (empty($items)) { Common::printDebug("There are no Ecommerce items in the request"); // we still record an Ecommerce order without any item in it return array(); } - $items = Common::json_decode($items, $assoc = true); + if (!is_array($items)) { Common::printDebug("Error while json_decode the Ecommerce items = " . var_export($items, true)); return false; } + $items = Common::unsanitizeInputValues($items); + $cleanedItems = $this->getCleanedEcommerceItems($items); return $cleanedItems; } @@ -425,23 +400,11 @@ class GoalManager $itemInCartBySku[$item[0]] = $item; } - // Select all items currently in the Cart if any - $sql = "SELECT idaction_sku, idaction_name, idaction_category, idaction_category2, idaction_category3, idaction_category4, idaction_category5, price, quantity, deleted, idorder as idorder_original_value - FROM " . Common::prefixTable('log_conversion_item') . " - WHERE idvisit = ? - AND (idorder = ? OR idorder = ?)"; + $itemsInDb = $this->getModel()->getAllItemsCurrentlyInTheCart($goal, self::ITEM_IDORDER_ABANDONED_CART); - $bind = array($goal['idvisit'], - isset($goal['idorder']) ? $goal['idorder'] : self::ITEM_IDORDER_ABANDONED_CART, - self::ITEM_IDORDER_ABANDONED_CART - ); - - $itemsInDb = Tracker::getDatabase()->fetchAll($sql, $bind); - - Common::printDebug("Items found in current cart, for conversion_item (visit,idorder)=" . var_export($bind, true)); - Common::printDebug($itemsInDb); // Look at which items need to be deleted, which need to be added or updated, based on the SKU $skuFoundInDb = $itemsToUpdate = array(); + foreach ($itemsInDb as $itemInDb) { $skuFoundInDb[] = $itemInDb['idaction_sku']; @@ -492,27 +455,10 @@ class GoalManager $itemsToInsert[] = $item; } } + $this->insertEcommerceItems($goal, $itemsToInsert); } - // In the GET items parameter, each item has the following array of information - const INDEX_ITEM_SKU = 0; - const INDEX_ITEM_NAME = 1; - const INDEX_ITEM_CATEGORY = 2; - const INDEX_ITEM_PRICE = 3; - const INDEX_ITEM_QUANTITY = 4; - - // Used in the array of items, internally to this class - const INTERNAL_ITEM_SKU = 0; - const INTERNAL_ITEM_NAME = 1; - const INTERNAL_ITEM_CATEGORY = 2; - const INTERNAL_ITEM_CATEGORY2 = 3; - const INTERNAL_ITEM_CATEGORY3 = 4; - const INTERNAL_ITEM_CATEGORY4 = 5; - const INTERNAL_ITEM_CATEGORY5 = 6; - const INTERNAL_ITEM_PRICE = 7; - const INTERNAL_ITEM_QUANTITY = 8; - /** * Reads items from the request, then looks up the names from the lookup table * and returns a clean array of items ready for the database. @@ -520,14 +466,15 @@ class GoalManager * @param array $items * @return array $cleanedItems */ - protected function getCleanedEcommerceItems($items) + private function getCleanedEcommerceItems($items) { // Clean up the items array $cleanedItems = array(); foreach ($items as $item) { - $name = $category = $category2 = $category3 = $category4 = $category5 = false; - $price = 0; + $name = $category = $category2 = $category3 = $category4 = $category5 = false; + $price = 0; $quantity = 1; + // items are passed in the request as an array: ( $sku, $name, $category, $price, $quantity ) if (empty($item[self::INDEX_ITEM_SKU])) { continue; @@ -619,6 +566,7 @@ class GoalManager $item[5] = $actionsLookedUp[$index * $columnsInEachRow + 5]; $item[6] = $actionsLookedUp[$index * $columnsInEachRow + 6]; } + return $cleanedItems; } @@ -636,29 +584,23 @@ class GoalManager if (empty($itemsToUpdate)) { return; } + Common::printDebug("Goal data used to update ecommerce items:"); Common::printDebug($goal); foreach ($itemsToUpdate as $item) { $newRow = $this->getItemRowEnriched($goal, $item); Common::printDebug($newRow); - $updateParts = $sqlBind = array(); - foreach ($newRow AS $name => $value) { - $updateParts[] = $name . " = ?"; - $sqlBind[] = $value; - } - $sql = 'UPDATE ' . Common::prefixTable('log_conversion_item') . " - SET " . implode($updateParts, ', ') . " - WHERE idvisit = ? - AND idorder = ? - AND idaction_sku = ?"; - $sqlBind[] = $newRow['idvisit']; - $sqlBind[] = $item['idorder_original_value']; - $sqlBind[] = $newRow['idaction_sku']; - Tracker::getDatabase()->query($sql, $sqlBind); + + $this->getModel()->updateEcommerceItem($item['idorder_original_value'], $newRow); } } + private function getModel() + { + return new Model(); + } + /** * Inserts in the cart in the DB the new items * that were not previously in the cart @@ -673,27 +615,17 @@ class GoalManager if (empty($itemsToInsert)) { return; } + Common::printDebug("Ecommerce items that are added to the cart/order"); Common::printDebug($itemsToInsert); - $sql = "INSERT INTO " . Common::prefixTable('log_conversion_item') . " - (idaction_sku, idaction_name, idaction_category, idaction_category2, idaction_category3, idaction_category4, idaction_category5, price, quantity, deleted, - idorder, idsite, idvisitor, server_time, idvisit) - VALUES "; - $i = 0; - $bind = array(); + $items = array(); + foreach ($itemsToInsert as $item) { - if ($i > 0) { - $sql .= ','; - } - $newRow = array_values($this->getItemRowEnriched($goal, $item)); - $sql .= " ( " . Common::getSqlStringFieldsArray($newRow) . " ) "; - $i++; - $bind = array_merge($bind, $newRow); + $items[] = $this->getItemRowEnriched($goal, $item); } - Tracker::getDatabase()->query($sql, $bind); - Common::printDebug($sql); - Common::printDebug($bind); + + $this->getModel()->createEcommerceItems($items); } protected function getItemRowEnriched($goal, $item) @@ -706,7 +638,7 @@ class GoalManager 'idaction_category3' => (int)$item[self::INTERNAL_ITEM_CATEGORY3], 'idaction_category4' => (int)$item[self::INTERNAL_ITEM_CATEGORY4], 'idaction_category5' => (int)$item[self::INTERNAL_ITEM_CATEGORY5], - 'price' => $item[self::INTERNAL_ITEM_PRICE], + 'price' => Common::forceDotAsSeparatorForDecimalPoint($item[self::INTERNAL_ITEM_PRICE]), 'quantity' => $item[self::INTERNAL_ITEM_QUANTITY], 'deleted' => isset($item['deleted']) ? $item['deleted'] : 0, //deleted 'idorder' => isset($goal['idorder']) ? $goal['idorder'] : self::ITEM_IDORDER_ABANDONED_CART, //idorder = 0 in log_conversion_item for carts @@ -718,21 +650,34 @@ class GoalManager return $newRow; } + public function getGoalColumn($column) + { + if (array_key_exists($column, $this->currentGoal)) { + return $this->currentGoal[$column]; + } + + return false; + } + /** * Records a standard non-Ecommerce goal in the DB (URL/Title matching), * linking the conversion to the action that triggered it * @param $goal + * @param Visitor $visitor * @param Action $action * @param $visitorInformation */ - protected function recordStandardGoals($goal, $action, $visitorInformation) + protected function recordStandardGoals(VisitProperties $visitProperties, Request $request, $goal, $action) { - foreach ($this->convertedGoals as $convertedGoal) { + $visitor = Visitor::makeFromVisitProperties($visitProperties, $request); + + $convertedGoals = $request->getMetadata('Goals', 'goalsConverted') ?: array(); + foreach ($convertedGoals as $convertedGoal) { + $this->currentGoal = $convertedGoal; Common::printDebug("- Goal " . $convertedGoal['idgoal'] . " matched. Recording..."); $conversion = $goal; $conversion['idgoal'] = $convertedGoal['idgoal']; - $conversion['url'] = $convertedGoal['url']; - $conversion['revenue'] = $this->getRevenue($convertedGoal['revenue']); + $conversion['url'] = $convertedGoal['url']; if (!is_null($action)) { $conversion['idaction_url'] = $action->getIdActionUrl(); @@ -742,18 +687,24 @@ class GoalManager // If multiple Goal conversions per visit, set a cache buster $conversion['buster'] = $convertedGoal['allow_multiple'] == 0 ? '0' - : $visitorInformation['visit_last_action_time']; + : $visitProperties->getProperty('visit_last_action_time'); - $this->insertNewConversion($conversion, $visitorInformation); + $conversionDimensions = ConversionDimension::getAllDimensions(); + $conversion = $this->triggerHookOnDimensions($request, $conversionDimensions, 'onGoalConversion', $visitor, $action, $conversion); + + $this->insertNewConversion($conversion, $visitProperties->getProperties(), $request); /** * Triggered after successfully recording a non-ecommerce conversion. - * + * * _Note: Subscribers should be wary of doing any expensive computation here as it may slow * the tracker down._ - * + * + * This event is deprecated, use [Dimensions](http://developer.piwik.org/guides/dimensions) instead. + * * @param array $conversion The conversion entity that was just persisted. See what information * it contains [here](/guides/persistence-and-the-mysql-backend#conversions). + * @deprecated */ Piwik::postEvent('Tracker.recordStandardGoals', array($conversion)); } @@ -766,34 +717,31 @@ class GoalManager * @param array $visitInformation * @return bool */ - protected function insertNewConversion($conversion, $visitInformation) + protected function insertNewConversion($conversion, $visitInformation, Request $request) { /** * Triggered before persisting a new [conversion entity](/guides/persistence-and-the-mysql-backend#conversions). - * + * * This event can be used to modify conversion information or to add new information to be persisted. - * + * + * This event is deprecated, use [Dimensions](http://developer.piwik.org/guides/dimensions) instead. + * * @param array $conversion The conversion entity. Read [this](/guides/persistence-and-the-mysql-backend#conversions) * to see what it contains. * @param array $visitInformation The visit entity that we are tracking a conversion for. See what * information it contains [here](/guides/persistence-and-the-mysql-backend#visits). * @param \Piwik\Tracker\Request $request An object describing the tracking request being processed. + * @deprecated */ - Piwik::postEvent('Tracker.newConversionInformation', array(&$conversion, $visitInformation, $this->request)); + Piwik::postEvent('Tracker.newConversionInformation', array(&$conversion, $visitInformation, $request)); $newGoalDebug = $conversion; $newGoalDebug['idvisitor'] = bin2hex($newGoalDebug['idvisitor']); Common::printDebug($newGoalDebug); - $fields = implode(", ", array_keys($conversion)); - $bindFields = Common::getSqlStringFieldsArray($conversion); - $sql = 'INSERT IGNORE INTO ' . Common::prefixTable('log_conversion') . " - ($fields) VALUES ($bindFields) "; - $bind = array_values($conversion); - $result = Tracker::getDatabase()->query($sql, $bind); + $wasInserted = $this->getModel()->createConversion($conversion); - // If a record was inserted, we return true - return Tracker::getDatabase()->rowCount($result) > 0; + return $wasInserted; } /** @@ -816,47 +764,6 @@ class GoalManager ); } - protected function updateExistingConversion($newGoal, $updateWhere) - { - $updateParts = $sqlBind = $updateWhereParts = array(); - foreach ($newGoal AS $name => $value) { - $updateParts[] = $name . " = ?"; - $sqlBind[] = $value; - } - foreach ($updateWhere as $name => $value) { - $updateWhereParts[] = $name . " = ?"; - $sqlBind[] = $value; - } - $sql = 'UPDATE ' . Common::prefixTable('log_conversion') . " - SET " . implode($updateParts, ', ') . " - WHERE " . implode($updateWhereParts, ' AND '); - - try { - Tracker::getDatabase()->query($sql, $sqlBind); - } catch(Exception $e){ - Common::printDebug("There was an error while updating the Conversion: " . $e->getMessage()); - return false; - } - return true; - } - - /** - * @param $type - * @param $name - * @param $keyword - */ - protected function setCampaignValuesToLowercase($type, &$name, &$keyword) - { - if ($type === Common::REFERRER_TYPE_CAMPAIGN) { - if (!empty($name)) { - $name = Common::mb_strtolower($name); - } - if (!empty($keyword)) { - $keyword = Common::mb_strtolower($keyword); - } - } - } - /** * @param $goal * @param $pattern_type @@ -865,6 +772,79 @@ class GoalManager * @throws \Exception */ protected function isUrlMatchingGoal($goal, $pattern_type, $url) + { + $url = Common::unsanitizeInputValue($url); + $goal['pattern'] = Common::unsanitizeInputValue($goal['pattern']); + + $match = $this->isGoalPatternMatchingUrl($goal, $pattern_type, $url); + + if (!$match) { + // Users may set Goal matching URL as URL encoded + $goal['pattern'] = urldecode($goal['pattern']); + + $match = $this->isGoalPatternMatchingUrl($goal, $pattern_type, $url); + } + return $match; + } + + /** + * @param ConversionDimension[] $dimensions + * @param string $hook + * @param Visitor $visitor + * @param Action|null $action + * @param array|null $valuesToUpdate If null, $this->visitorInfo will be updated + * + * @return array|null The updated $valuesToUpdate or null if no $valuesToUpdate given + */ + private function triggerHookOnDimensions(Request $request, $dimensions, $hook, $visitor, $action, $valuesToUpdate) + { + foreach ($dimensions as $dimension) { + $value = $dimension->$hook($request, $visitor, $action, $this); + + if (false !== $value) { + if (is_float($value)) { + $value = Common::forceDotAsSeparatorForDecimalPoint($value); + } + + $fieldName = $dimension->getColumnName(); + $visitor->setVisitorColumn($fieldName, $value); + + $valuesToUpdate[$fieldName] = $value; + } + } + + return $valuesToUpdate; + } + + private function getGoalFromVisitor(VisitProperties $visitProperties, Request $request, $action) + { + $goal = array( + 'idvisit' => $visitProperties->getProperty('idvisit'), + 'idvisitor' => $visitProperties->getProperty('idvisitor'), + 'server_time' => Date::getDatetimeFromTimestamp($visitProperties->getProperty('visit_last_action_time')), + ); + + $visitDimensions = VisitDimension::getAllDimensions(); + + $visit = Visitor::makeFromVisitProperties($visitProperties, $request); + foreach ($visitDimensions as $dimension) { + $value = $dimension->onAnyGoalConversion($request, $visit, $action); + if (false !== $value) { + $goal[$dimension->getColumnName()] = $value; + } + } + + return $goal; + } + + /** + * @param $goal + * @param $pattern_type + * @param $url + * @return bool + * @throws Exception + */ + protected function isGoalPatternMatchingUrl($goal, $pattern_type, $url) { switch ($pattern_type) { case 'regex': diff --git a/www/analytics/core/Tracker/Handler.php b/www/analytics/core/Tracker/Handler.php new file mode 100644 index 00000000..ed75104e --- /dev/null +++ b/www/analytics/core/Tracker/Handler.php @@ -0,0 +1,117 @@ +setResponse(new Response()); + } + + public function setResponse($response) + { + $this->response = $response; + } + + public function init(Tracker $tracker, RequestSet $requestSet) + { + $this->response->init($tracker); + } + + public function process(Tracker $tracker, RequestSet $requestSet) + { + foreach ($requestSet->getRequests() as $request) { + $tracker->trackRequest($request); + } + } + + public function onStartTrackRequests(Tracker $tracker, RequestSet $requestSet) + { + } + + public function onAllRequestsTracked(Tracker $tracker, RequestSet $requestSet) + { + $tasks = $this->getScheduledTasksRunner(); + if ($tasks->shouldRun($tracker)) { + $tasks->runScheduledTasks(); + } + } + + private function getScheduledTasksRunner() + { + if (is_null($this->tasksRunner)) { + $this->tasksRunner = new ScheduledTasksRunner(); + } + + return $this->tasksRunner; + } + + /** + * @internal + */ + public function setScheduledTasksRunner(ScheduledTasksRunner $runner) + { + $this->tasksRunner = $runner; + } + + public function onException(Tracker $tracker, RequestSet $requestSet, Exception $e) + { + Common::printDebug("Exception: " . $e->getMessage()); + + $statusCode = 500; + if ($e instanceof UnexpectedWebsiteFoundException) { + $statusCode = 400; + } elseif ($e instanceof InvalidRequestParameterException) { + $statusCode = 400; + } + + $this->response->outputException($tracker, $e, $statusCode); + $this->redirectIfNeeded($requestSet); + } + + public function finish(Tracker $tracker, RequestSet $requestSet) + { + $this->response->outputResponse($tracker); + $this->redirectIfNeeded($requestSet); + return $this->response->getOutput(); + } + + public function getResponse() + { + return $this->response; + } + + protected function redirectIfNeeded(RequestSet $requestSet) + { + $redirectUrl = $requestSet->shouldPerformRedirectToUrl(); + + if (!empty($redirectUrl)) { + Url::redirectToUrl($redirectUrl); + } + } +} diff --git a/www/analytics/core/Tracker/Handler/Factory.php b/www/analytics/core/Tracker/Handler/Factory.php new file mode 100644 index 00000000..63333747 --- /dev/null +++ b/www/analytics/core/Tracker/Handler/Factory.php @@ -0,0 +1,42 @@ +Tracker['cookie_name']; $cookie_path = @Config::getInstance()->Tracker['cookie_path']; @@ -35,7 +35,7 @@ class IgnoreCookie * * @return Cookie */ - static public function getIgnoreCookie() + public static function getIgnoreCookie() { $cookie_name = @Config::getInstance()->Tracker['ignore_visits_cookie_name']; $cookie_path = @Config::getInstance()->Tracker['cookie_path']; @@ -46,7 +46,7 @@ class IgnoreCookie /** * Set ignore (visit) cookie or deletes it if already present */ - static public function setIgnoreCookie() + public static function setIgnoreCookie() { $ignoreCookie = self::getIgnoreCookie(); if ($ignoreCookie->isCookieFound()) { @@ -65,7 +65,7 @@ class IgnoreCookie * * @return bool True if ignore cookie found; false otherwise */ - static public function isIgnoreCookieFound() + public static function isIgnoreCookieFound() { $cookie = self::getIgnoreCookie(); return $cookie->isCookieFound() && $cookie->get('ignore') === '*'; diff --git a/www/analytics/core/Tracker/Model.php b/www/analytics/core/Tracker/Model.php new file mode 100644 index 00000000..afffd5fa --- /dev/null +++ b/www/analytics/core/Tracker/Model.php @@ -0,0 +1,464 @@ +getDb(); + $db->query($sql, $bind); + + $id = $db->lastInsertId(); + + return $id; + } + + public function createConversion($conversion) + { + $fields = implode(", ", array_keys($conversion)); + $bindFields = Common::getSqlStringFieldsArray($conversion); + $table = Common::prefixTable('log_conversion'); + + $sql = "INSERT IGNORE INTO $table ($fields) VALUES ($bindFields) "; + $bind = array_values($conversion); + + $db = $this->getDb(); + $result = $db->query($sql, $bind); + + // If a record was inserted, we return true + return $db->rowCount($result) > 0; + } + + public function updateConversion($idVisit, $idGoal, $newConversion) + { + $updateWhere = array( + 'idvisit' => $idVisit, + 'idgoal' => $idGoal, + 'buster' => 0, + ); + + $updateParts = $sqlBind = $updateWhereParts = array(); + + foreach ($newConversion as $name => $value) { + $updateParts[] = $name . " = ?"; + $sqlBind[] = $value; + } + + foreach ($updateWhere as $name => $value) { + $updateWhereParts[] = $name . " = ?"; + $sqlBind[] = $value; + } + + $parts = implode($updateParts, ', '); + $table = Common::prefixTable('log_conversion'); + + $sql = "UPDATE $table SET $parts WHERE " . implode($updateWhereParts, ' AND '); + + try { + $this->getDb()->query($sql, $sqlBind); + } catch (Exception $e) { + Common::printDebug("There was an error while updating the Conversion: " . $e->getMessage()); + + return false; + } + + return true; + } + + + /** + * Loads the Ecommerce items from the request and records them in the DB + * + * @param array $goal + * @param int $defaultIdOrder + * @throws Exception + * @return array + */ + public function getAllItemsCurrentlyInTheCart($goal, $defaultIdOrder) + { + $sql = "SELECT idaction_sku, idaction_name, idaction_category, idaction_category2, idaction_category3, idaction_category4, idaction_category5, price, quantity, deleted, idorder as idorder_original_value + FROM " . Common::prefixTable('log_conversion_item') . " + WHERE idvisit = ? AND (idorder = ? OR idorder = ?)"; + + $bind = array( + $goal['idvisit'], + isset($goal['idorder']) ? $goal['idorder'] : $defaultIdOrder, + $defaultIdOrder + ); + + $itemsInDb = $this->getDb()->fetchAll($sql, $bind); + + Common::printDebug("Items found in current cart, for conversion_item (visit,idorder)=" . var_export($bind, true)); + Common::printDebug($itemsInDb); + + return $itemsInDb; + } + + public function createEcommerceItems($ecommerceItems) + { + $sql = "INSERT INTO " . Common::prefixTable('log_conversion_item'); + $i = 0; + $bind = array(); + + foreach ($ecommerceItems as $item) { + if ($i === 0) { + $fields = implode(', ', array_keys($item)); + $sql .= ' (' . $fields . ') VALUES '; + } elseif ($i > 0) { + $sql .= ','; + } + + $newRow = array_values($item); + $sql .= " ( " . Common::getSqlStringFieldsArray($newRow) . " ) "; + $bind = array_merge($bind, $newRow); + $i++; + } + + Common::printDebug($sql); + Common::printDebug($bind); + + try { + $this->getDb()->query($sql, $bind); + } catch (Exception $e) { + if ($e->getCode() == 23000 || + false !== strpos($e->getMessage(), 'Duplicate entry') || + false !== strpos($e->getMessage(), 'Integrity constraint violation')) { + Common::printDebug('Did not create ecommerce item as item was already created'); + } else { + throw $e; + } + } + } + + /** + * Inserts a new action into the log_action table. If there is an existing action that was inserted + * due to another request pre-empting this one, the newly inserted action is deleted. + * + * @param string $name + * @param int $type + * @param int $urlPrefix + * @return int The ID of the action (can be for an existing action or new action). + */ + public function createNewIdAction($name, $type, $urlPrefix) + { + $newActionId = $this->insertNewAction($name, $type, $urlPrefix); + + $realFirstActionId = $this->getIdActionMatchingNameAndType($name, $type); + + // if the inserted action ID is not the same as the queried action ID, then that means we inserted + // a duplicate, so remove it now + if ($realFirstActionId != $newActionId) { + $this->deleteDuplicateAction($newActionId); + } + + return $realFirstActionId; + } + + private function insertNewAction($name, $type, $urlPrefix) + { + $table = Common::prefixTable('log_action'); + $sql = "INSERT INTO $table (name, hash, type, url_prefix) VALUES (?,CRC32(?),?,?)"; + + $db = $this->getDb(); + $db->query($sql, array($name, $name, $type, $urlPrefix)); + + $actionId = $db->lastInsertId(); + + return $actionId; + } + + private function getSqlSelectActionId() + { + // it is possible for multiple actions to exist in the DB (due to rare concurrency issues), so the ORDER BY and + // LIMIT are important + $sql = "SELECT idaction, type, name FROM " . Common::prefixTable('log_action') + . " WHERE " . $this->getSqlConditionToMatchSingleAction() . " " + . "ORDER BY idaction ASC LIMIT 1"; + + return $sql; + } + + public function getIdActionMatchingNameAndType($name, $type) + { + $sql = $this->getSqlSelectActionId(); + $bind = array($name, $name, $type); + + $idAction = $this->getDb()->fetchOne($sql, $bind); + + return $idAction; + } + + /** + * Returns the IDs for multiple actions based on name + type values. + * + * @param array $actionsNameAndType Array like `array( array('name' => '...', 'type' => 1), ... )` + * @return array|false Array of DB rows w/ columns: **idaction**, **type**, **name**. + */ + public function getIdsAction($actionsNameAndType) + { + $sql = "SELECT MIN(idaction) as idaction, type, name FROM " . Common::prefixTable('log_action') + . " WHERE"; + $bind = array(); + + $i = 0; + foreach ($actionsNameAndType as $actionNameType) { + $name = $actionNameType['name']; + + if (empty($name)) { + continue; + } + + if ($i > 0) { + $sql .= " OR"; + } + + $sql .= " " . $this->getSqlConditionToMatchSingleAction() . " "; + + $bind[] = $name; + $bind[] = $name; + $bind[] = $actionNameType['type']; + $i++; + } + + $sql .= " GROUP BY type, hash, name"; + + // Case URL & Title are empty + if (empty($bind)) { + return false; + } + + $actionIds = $this->getDb()->fetchAll($sql, $bind); + + return $actionIds; + } + + public function updateEcommerceItem($originalIdOrder, $newItem) + { + $updateParts = $sqlBind = array(); + foreach ($newItem as $name => $value) { + $updateParts[] = $name . " = ?"; + $sqlBind[] = $value; + } + + $parts = implode($updateParts, ', '); + $table = Common::prefixTable('log_conversion_item'); + + $sql = "UPDATE $table SET $parts WHERE idvisit = ? AND idorder = ? AND idaction_sku = ?"; + + $sqlBind[] = $newItem['idvisit']; + $sqlBind[] = $originalIdOrder; + $sqlBind[] = $newItem['idaction_sku']; + + $this->getDb()->query($sql, $sqlBind); + } + + public function createVisit($visit) + { + $fields = array_keys($visit); + $fields = implode(", ", $fields); + $values = Common::getSqlStringFieldsArray($visit); + $table = Common::prefixTable('log_visit'); + + $sql = "INSERT INTO $table ($fields) VALUES ($values)"; + $bind = array_values($visit); + + $db = $this->getDb(); + $db->query($sql, $bind); + + return $db->lastInsertId(); + } + + public function updateVisit($idSite, $idVisit, $valuesToUpdate) + { + list($updateParts, $sqlBind) = $this->fieldsToQuery($valuesToUpdate); + + $parts = implode($updateParts, ', '); + $table = Common::prefixTable('log_visit'); + + $sqlQuery = "UPDATE $table SET $parts WHERE idsite = ? AND idvisit = ?"; + + $sqlBind[] = $idSite; + $sqlBind[] = $idVisit; + + $db = $this->getDb(); + $result = $db->query($sqlQuery, $sqlBind); + $wasInserted = $db->rowCount($result) != 0; + + if (!$wasInserted) { + Common::printDebug("Visitor with this idvisit wasn't found in the DB."); + Common::printDebug("$sqlQuery --- "); + Common::printDebug($sqlBind); + } + + return $wasInserted; + } + + public function updateAction($idLinkVa, $valuesToUpdate) + { + if (empty($idLinkVa)) { + return; + } + + list($updateParts, $sqlBind) = $this->fieldsToQuery($valuesToUpdate); + + $parts = implode($updateParts, ', '); + $table = Common::prefixTable('log_link_visit_action'); + + $sqlQuery = "UPDATE $table SET $parts WHERE idlink_va = ?"; + + $sqlBind[] = $idLinkVa; + + $db = $this->getDb(); + $result = $db->query($sqlQuery, $sqlBind); + $wasInserted = $db->rowCount($result) != 0; + + if (!$wasInserted) { + Common::printDebug("Action with this idLinkVa wasn't found in the DB."); + Common::printDebug("$sqlQuery --- "); + Common::printDebug($sqlBind); + } + + return $wasInserted; + } + + public function findVisitor($idSite, $configId, $idVisitor, $fieldsToRead, $shouldMatchOneFieldOnly, $isVisitorIdToLookup, $timeLookBack, $timeLookAhead) + { + $selectCustomVariables = ''; + + $selectFields = implode(', ', $fieldsToRead); + + $select = "SELECT $selectFields $selectCustomVariables "; + $from = "FROM " . Common::prefixTable('log_visit'); + + // Two use cases: + // 1) there is no visitor ID so we try to match only on config_id (heuristics) + // Possible causes of no visitor ID: no browser cookie support, direct Tracking API request without visitor ID passed, + // importing server access logs with import_logs.py, etc. + // In this case we use config_id heuristics to try find the visitor in tahhhe past. There is a risk to assign + // this page view to the wrong visitor, but this is better than creating artificial visits. + // 2) there is a visitor ID and we trust it (config setting trust_visitors_cookies, OR it was set using &cid= in tracking API), + // and in these cases, we force to look up this visitor id + $whereCommon = "visit_last_action_time >= ? AND visit_last_action_time <= ? AND idsite = ?"; + $bindSql = array( + $timeLookBack, + $timeLookAhead, + $idSite + ); + + if ($shouldMatchOneFieldOnly && $isVisitorIdToLookup) { + $visitRow = $this->findVisitorByVisitorId($idVisitor, $select, $from, $whereCommon, $bindSql); + } elseif ($shouldMatchOneFieldOnly) { + $visitRow = $this->findVisitorByConfigId($configId, $select, $from, $whereCommon, $bindSql); + } else { + $visitRow = $this->findVisitorByVisitorId($idVisitor, $select, $from, $whereCommon, $bindSql); + + if (empty($visitRow)) { + $whereCommon .= ' AND user_id IS NULL '; + $visitRow = $this->findVisitorByConfigId($configId, $select, $from, $whereCommon, $bindSql); + } + } + + return $visitRow; + } + + private function findVisitorByVisitorId($idVisitor, $select, $from, $where, $bindSql) + { + // will use INDEX index_idsite_idvisitor (idsite, idvisitor) + $where .= ' AND idvisitor = ?'; + $bindSql[] = $idVisitor; + + return $this->fetchVisitor($select, $from, $where, $bindSql); + } + + private function findVisitorByConfigId($configId, $select, $from, $where, $bindSql) + { + // will use INDEX index_idsite_config_datetime (idsite, config_id, visit_last_action_time) + $where .= ' AND config_id = ?'; + $bindSql[] = $configId; + + return $this->fetchVisitor($select, $from, $where, $bindSql); + } + + private function fetchVisitor($select, $from, $where, $bindSql) + { + $sql = "$select $from WHERE " . $where . " + ORDER BY visit_last_action_time DESC + LIMIT 1"; + + $visitRow = $this->getDb()->fetch($sql, $bindSql); + + return $visitRow; + } + + /** + * Returns true if the site doesn't have log data. + * + * @param int $siteId + * @return bool + */ + public function isSiteEmpty($siteId) + { + $sql = sprintf('SELECT idsite FROM %s WHERE idsite = ? limit 1', Common::prefixTable('log_visit')); + + $result = \Piwik\Db::fetchOne($sql, array($siteId)); + + return $result == null; + } + + private function fieldsToQuery($valuesToUpdate) + { + $updateParts = array(); + $sqlBind = array(); + + foreach ($valuesToUpdate as $name => $value) { + // Case where bind parameters don't work + if ($value === $name . ' + 1') { + //$name = 'visit_total_events' + //$value = 'visit_total_events + 1'; + $updateParts[] = " $name = $value "; + } else { + $updateParts[] = $name . " = ?"; + $sqlBind[] = $value; + } + } + + return array($updateParts, $sqlBind); + } + + private function deleteDuplicateAction($newActionId) + { + $sql = "DELETE FROM " . Common::prefixTable('log_action') . " WHERE idaction = ?"; + + $db = $this->getDb(); + $db->query($sql, array($newActionId)); + } + + private function getDb() + { + return Tracker::getDatabase(); + } + + private function getSqlConditionToMatchSingleAction() + { + return "( hash = CRC32(?) AND name = ? AND type = ? )"; + } +} diff --git a/www/analytics/core/Tracker/PageUrl.php b/www/analytics/core/Tracker/PageUrl.php index c38bae51..1ff3e529 100644 --- a/www/analytics/core/Tracker/PageUrl.php +++ b/www/analytics/core/Tracker/PageUrl.php @@ -1,6 +1,6 @@ $posFirstSemiColon) { $originalUrl = substr_replace($originalUrl, ";", $posQuestionMark, 1); $replace = true; } + if ($replace) { $originalUrl = substr_replace($originalUrl, "?", strpos($originalUrl, ";"), 1); $originalUrl = str_replace(";", "&", $originalUrl); } + return $originalUrl; } @@ -212,10 +227,12 @@ class PageUrl { if (is_string($value)) { $decoded = urldecode($value); - if (@mb_check_encoding($decoded, $encoding)) { + if (function_exists('mb_check_encoding') + && @mb_check_encoding($decoded, $encoding)) { $value = urlencode(mb_convert_encoding($decoded, 'UTF-8', $encoding)); } } + return $value; } @@ -228,6 +245,7 @@ class PageUrl $value = PageUrl::reencodeParameterValue($value, $encoding); } } + return $queryParameters; } @@ -247,14 +265,20 @@ class PageUrl */ public static function reencodeParameters(&$queryParameters, $encoding = false) { - // if query params are encoded w/ non-utf8 characters (due to browser bug or whatever), - // encode to UTF-8. - if ($encoding !== false - && strtolower($encoding) != 'utf-8' - && function_exists('mb_check_encoding') - ) { - $queryParameters = PageUrl::reencodeParametersArray($queryParameters, $encoding); + if (function_exists('mb_check_encoding')) { + // if query params are encoded w/ non-utf8 characters (due to browser bug or whatever), + // encode to UTF-8. + if (strtolower($encoding) != 'utf-8' + && $encoding != false + ) { + Common::printDebug("Encoding page URL query parameters to $encoding."); + + $queryParameters = PageUrl::reencodeParametersArray($queryParameters, $encoding); + } + } else { + Common::printDebug("Page charset supplied in tracking request, but mbstring extension is not available."); } + return $queryParameters; } @@ -263,6 +287,7 @@ class PageUrl $url = Common::unsanitizeInputValue($url); $url = PageUrl::cleanupString($url); $url = PageUrl::convertMatrixUrl($url); + return $url; } @@ -276,6 +301,7 @@ class PageUrl public static function reconstructNormalizedUrl($url, $prefixId) { $map = array_flip(self::$urlPrefixMap); + if ($prefixId !== null && isset($map[$prefixId])) { $fullUrl = $map[$prefixId] . $url; } else { @@ -285,7 +311,8 @@ class PageUrl // Clean up host & hash tags, for URLs $parsedUrl = @parse_url($fullUrl); $parsedUrl = PageUrl::cleanupHostAndHashTag($parsedUrl); - $url = UrlHelper::getParseUrlReverse($parsedUrl); + $url = UrlHelper::getParseUrlReverse($parsedUrl); + if (!empty($url)) { return $url; } @@ -310,6 +337,7 @@ class PageUrl ); } } + return array('url' => $url, 'prefixId' => null); } @@ -319,10 +347,30 @@ class PageUrl if (!UrlHelper::isLookLikeUrl($url)) { Common::printDebug("WARNING: URL looks invalid and is discarded"); - $url = false; - return $url; + + return false; } + return $url; } -} + private static function getExcludedParametersFromWebsite($website) + { + if (isset($website['excluded_parameters'])) { + return $website['excluded_parameters']; + } + + return array(); + } + + public static function urldecodeValidUtf8($value) + { + $value = urldecode($value); + if (function_exists('mb_check_encoding') + && !@mb_check_encoding($value, 'utf-8') + ) { + return urlencode($value); + } + return $value; + } +} diff --git a/www/analytics/core/Tracker/Referrer.php b/www/analytics/core/Tracker/Referrer.php deleted file mode 100644 index 12d8ff74..00000000 --- a/www/analytics/core/Tracker/Referrer.php +++ /dev/null @@ -1,301 +0,0 @@ -idsite = $idSite; - - // default values for the referer_* fields - $referrerUrl = Common::unsanitizeInputValue($referrerUrl); - if (!empty($referrerUrl) - && !UrlHelper::isLookLikeUrl($referrerUrl) - ) { - $referrerUrl = ''; - } - - $currentUrl = PageUrl::cleanupUrl($currentUrl); - - $this->referrerUrl = $referrerUrl; - $this->referrerUrlParse = @parse_url($this->referrerUrl); - $this->currentUrlParse = @parse_url($currentUrl); - $this->typeReferrerAnalyzed = Common::REFERRER_TYPE_DIRECT_ENTRY; - $this->nameReferrerAnalyzed = ''; - $this->keywordReferrerAnalyzed = ''; - $this->referrerHost = ''; - - if (isset($this->referrerUrlParse['host'])) { - $this->referrerHost = $this->referrerUrlParse['host']; - } - - $referrerDetected = $this->detectReferrerCampaign(); - - if (!$referrerDetected) { - if ($this->detectReferrerDirectEntry() - || $this->detectReferrerSearchEngine() - ) { - $referrerDetected = true; - } - } - - if (!empty($this->referrerHost) - && !$referrerDetected - ) { - $this->typeReferrerAnalyzed = Common::REFERRER_TYPE_WEBSITE; - $this->nameReferrerAnalyzed = Common::mb_strtolower($this->referrerHost); - } - - $referrerInformation = array( - 'referer_type' => $this->typeReferrerAnalyzed, - 'referer_name' => $this->nameReferrerAnalyzed, - 'referer_keyword' => $this->keywordReferrerAnalyzed, - 'referer_url' => $this->referrerUrl, - ); - - return $referrerInformation; - } - - /** - * Search engine detection - * @return bool - */ - protected function detectReferrerSearchEngine() - { - $searchEngineInformation = UrlHelper::extractSearchEngineInformationFromUrl($this->referrerUrl); - - /** - * Triggered when detecting the search engine of a referrer URL. - * - * Plugins can use this event to provide custom search engine detection - * logic. - * - * @param array &$searchEngineInformation An array with the following information: - * - * - **name**: The search engine name. - * - **keywords**: The search keywords used. - * - * This parameter is initialized to the results - * of Piwik's default search engine detection - * logic. - * @param string referrerUrl The referrer URL from the tracking request. - */ - Piwik::postEvent('Tracker.detectReferrerSearchEngine', array(&$searchEngineInformation, $this->referrerUrl)); - if ($searchEngineInformation === false) { - return false; - } - $this->typeReferrerAnalyzed = Common::REFERRER_TYPE_SEARCH_ENGINE; - $this->nameReferrerAnalyzed = $searchEngineInformation['name']; - $this->keywordReferrerAnalyzed = $searchEngineInformation['keywords']; - return true; - } - - /** - * @param string $string - * @return bool - */ - protected function detectCampaignFromString($string) - { - foreach ($this->campaignNames as $campaignNameParameter) { - $campaignName = trim(urldecode(UrlHelper::getParameterFromQueryString($string, $campaignNameParameter))); - if (!empty($campaignName)) { - break; - } - } - - if (empty($campaignName)) { - return false; - } - $this->typeReferrerAnalyzed = Common::REFERRER_TYPE_CAMPAIGN; - $this->nameReferrerAnalyzed = $campaignName; - - foreach ($this->campaignKeywords as $campaignKeywordParameter) { - $campaignKeyword = UrlHelper::getParameterFromQueryString($string, $campaignKeywordParameter); - if (!empty($campaignKeyword)) { - $this->keywordReferrerAnalyzed = trim(urldecode($campaignKeyword)); - break; - } - } - return !empty($this->keywordReferrerAnalyzed); - } - - protected function detectReferrerCampaignFromLandingUrl() - { - if (!isset($this->currentUrlParse['query']) - && !isset($this->currentUrlParse['fragment']) - ) { - return false; - } - $campaignParameters = Common::getCampaignParameters(); - $this->campaignNames = $campaignParameters[0]; - $this->campaignKeywords = $campaignParameters[1]; - - $found = false; - - // 1) Detect campaign from query string - if (isset($this->currentUrlParse['query'])) { - $found = $this->detectCampaignFromString($this->currentUrlParse['query']); - } - - // 2) Detect from fragment #hash - if (!$found - && isset($this->currentUrlParse['fragment']) - ) { - $this->detectCampaignFromString($this->currentUrlParse['fragment']); - } - } - - /** - * We have previously tried to detect the campaign variables in the URL - * so at this stage, if the referrer host is the current host, - * or if the referrer host is any of the registered URL for this website, - * it is considered a direct entry - * @return bool - */ - protected function detectReferrerDirectEntry() - { - if (!empty($this->referrerHost)) { - // is the referrer host the current host? - if (isset($this->currentUrlParse['host'])) { - $currentHost = mb_strtolower($this->currentUrlParse['host'], 'UTF-8'); - if ($currentHost == mb_strtolower($this->referrerHost, 'UTF-8')) { - $this->typeReferrerAnalyzed = Common::REFERRER_TYPE_DIRECT_ENTRY; - return true; - } - } - if (Visit::isHostKnownAliasHost($this->referrerHost, $this->idsite)) { - $this->typeReferrerAnalyzed = Common::REFERRER_TYPE_DIRECT_ENTRY; - return true; - } - } - return false; - } - - protected function detectCampaignKeywordFromReferrerUrl() - { - if(!empty($this->nameReferrerAnalyzed) - && !empty($this->keywordReferrerAnalyzed)) { - // keyword is already set, we skip - return true; - } - - // Set the Campaign keyword to the keyword found in the Referrer URL if any - if(!empty($this->nameReferrerAnalyzed)) { - $referrerUrlInfo = UrlHelper::extractSearchEngineInformationFromUrl($this->referrerUrl); - if (!empty($referrerUrlInfo['keywords'])) { - $this->keywordReferrerAnalyzed = $referrerUrlInfo['keywords']; - } - } - - // Set the keyword, to the hostname found, in a Adsense Referrer URL '&url=' parameter - if (empty($this->keywordReferrerAnalyzed) - && !empty($this->referrerUrlParse['query']) - && !empty($this->referrerHost) - && (strpos($this->referrerHost, 'googleads') !== false || strpos($this->referrerHost, 'doubleclick') !== false) - ) { - // This parameter sometimes is found & contains the page with the adsense ad bringing visitor to our site - $value = $this->getParameterValueFromReferrerUrl('url'); - if (!empty($value)) { - $parsedAdsenseReferrerUrl = parse_url($value); - if (!empty($parsedAdsenseReferrerUrl['host'])) { - - if(empty($this->nameReferrerAnalyzed)) { - $type = $this->getParameterValueFromReferrerUrl('ad_type'); - $type = $type ? " ($type)" : ''; - $this->nameReferrerAnalyzed = self::LABEL_ADWORDS_NAME . $type; - $this->typeReferrerAnalyzed = Common::REFERRER_TYPE_CAMPAIGN; - } - $this->keywordReferrerAnalyzed = self::LABEL_PREFIX_ADWORDS_KEYWORD . $parsedAdsenseReferrerUrl['host']; - } - } - } - - } - - /** - * @return string - */ - protected function getParameterValueFromReferrerUrl($adsenseReferrerParameter) - { - $value = trim(urldecode(UrlHelper::getParameterFromQueryString($this->referrerUrlParse['query'], $adsenseReferrerParameter))); - return $value; - } - - /** - * @return bool - */ - protected function detectReferrerCampaign() - { - $this->detectReferrerCampaignFromLandingUrl(); - $this->detectCampaignKeywordFromReferrerUrl(); - - if ($this->typeReferrerAnalyzed != Common::REFERRER_TYPE_CAMPAIGN) { - return false; - } - // if we detected a campaign but there is still no keyword set, we set the keyword to the Referrer host - if(empty($this->keywordReferrerAnalyzed)) { - $this->keywordReferrerAnalyzed = $this->referrerHost; - } - - $this->keywordReferrerAnalyzed = Common::mb_strtolower($this->keywordReferrerAnalyzed); - $this->nameReferrerAnalyzed = Common::mb_strtolower($this->nameReferrerAnalyzed); - return true; - } - -} diff --git a/www/analytics/core/Tracker/Request.php b/www/analytics/core/Tracker/Request.php index df551e7d..b6f31630 100644 --- a/www/analytics/core/Tracker/Request.php +++ b/www/analytics/core/Tracker/Request.php @@ -1,6 +1,6 @@ params = $params; + $this->rawParams = $params; $this->tokenAuth = $tokenAuth; $this->timestamp = time(); - $this->enforcedIp = false; + $this->isEmptyRequest = empty($params); // When the 'url' and referrer url parameter are not given, we might be in the 'Simple Image Tracker' mode. // The URL can default to the Referrer, which will be in this case // the URL of the page containing the Simple Image beacon if (empty($this->params['urlref']) && empty($this->params['url']) + && array_key_exists('HTTP_REFERER', $_SERVER) ) { - $url = @$_SERVER['HTTP_REFERER']; + $url = $_SERVER['HTTP_REFERER']; if (!empty($url)) { $this->params['url'] = $url; } } + + // check for 4byte utf8 characters in url and replace them with � + // @TODO Remove as soon as our database tables use utf8mb4 instead of utf8 + if (array_key_exists('url', $this->params) && preg_match('/[\x{10000}-\x{10FFFF}]/u', $this->params['url'])) { + Common::printDebug("Unsupport character detected. Replacing with \xEF\xBF\xBD"); + $this->params['url'] = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $this->params['url']); + } + } + + /** + * Get the params that were originally passed to the instance. These params do not contain any params that were added + * within this object. + * @return array + */ + public function getRawParams() + { + return $this->rawParams; + } + + public function getTokenAuth() + { + return $this->tokenAuth; } /** @@ -80,21 +123,43 @@ class Request * This method allows to set custom IP + server time + visitor ID, when using Tracking API. * These two attributes can be only set by the Super User (passing token_auth). */ - protected function authenticateTrackingApi($tokenAuthFromBulkRequest) + protected function authenticateTrackingApi($tokenAuth) { - $shouldAuthenticate = Config::getInstance()->Tracker['tracking_requests_require_authentication']; + $shouldAuthenticate = TrackerConfig::getConfigValue('tracking_requests_require_authentication'); + if ($shouldAuthenticate) { - $tokenAuth = $tokenAuthFromBulkRequest ? $tokenAuthFromBulkRequest : Common::getRequestVar('token_auth', false, 'string', $this->params); try { $idSite = $this->getIdSite(); - $this->isAuthenticated = $this->authenticateSuperUserOrAdmin($tokenAuth, $idSite); } catch (Exception $e) { $this->isAuthenticated = false; - } - if (!$this->isAuthenticated) { return; } - Common::printDebug("token_auth is authenticated!"); + + if (empty($tokenAuth)) { + $tokenAuth = Common::getRequestVar('token_auth', false, 'string', $this->params); + } + + $cache = PiwikCache::getTransientCache(); + $cacheKey = 'tracker_request_authentication_' . $idSite . '_' . $tokenAuth; + + if ($cache->contains($cacheKey)) { + Common::printDebug("token_auth is authenticated in cache!"); + $this->isAuthenticated = $cache->fetch($cacheKey); + return; + } + + try { + $this->isAuthenticated = self::authenticateSuperUserOrAdmin($tokenAuth, $idSite); + $cache->save($cacheKey, $this->isAuthenticated); + } catch (Exception $e) { + Common::printDebug("could not authenticate, caught exception: " . $e->getMessage()); + + $this->isAuthenticated = false; + } + + if ($this->isAuthenticated) { + Common::printDebug("token_auth is authenticated!"); + } } else { $this->isAuthenticated = true; Common::printDebug("token_auth authentication not required"); @@ -110,9 +175,11 @@ class Request Piwik::postEvent('Request.initAuthenticationObject'); /** @var \Piwik\Auth $auth */ - $auth = Registry::get('auth'); + $auth = StaticContainer::get('Piwik\Auth'); $auth->setTokenAuth($tokenAuth); $auth->setLogin(null); + $auth->setPassword(null); + $auth->setPasswordHash(null); $access = $auth->authenticate(); if (!empty($access) && $access->hasSuperUserAccess()) { @@ -122,10 +189,12 @@ class Request // Now checking the list of admin token_auth cached in the Tracker config file if (!empty($idSite) && $idSite > 0) { $website = Cache::getCacheWebsiteAttributes($idSite); - if (array_key_exists('admin_token_auth', $website) && in_array($tokenAuth, $website['admin_token_auth'])) { + + if (array_key_exists('admin_token_auth', $website) && in_array((string) $tokenAuth, $website['admin_token_auth'])) { return true; } } + Common::printDebug("WARNING! token_auth = $tokenAuth is not valid, Super User / Admin was NOT authenticated"); return false; @@ -137,13 +206,17 @@ class Request public function getDaysSinceFirstVisit() { $cookieFirstVisitTimestamp = $this->getParam('_idts'); + if (!$this->isTimestampValid($cookieFirstVisitTimestamp)) { $cookieFirstVisitTimestamp = $this->getCurrentTimestamp(); } + $daysSinceFirstVisit = round(($this->getCurrentTimestamp() - $cookieFirstVisitTimestamp) / 86400, $precision = 0); + if ($daysSinceFirstVisit < 0) { $daysSinceFirstVisit = 0; } + return $daysSinceFirstVisit; } @@ -154,12 +227,14 @@ class Request { $daysSinceLastOrder = false; $lastOrderTimestamp = $this->getParam('_ects'); + if ($this->isTimestampValid($lastOrderTimestamp)) { $daysSinceLastOrder = round(($this->getCurrentTimestamp() - $lastOrderTimestamp) / 86400, $precision = 0); if ($daysSinceLastOrder < 0) { $daysSinceLastOrder = 0; } } + return $daysSinceLastOrder; } @@ -170,12 +245,14 @@ class Request { $daysSinceLastVisit = 0; $lastVisitTimestamp = $this->getParam('_viewts'); + if ($this->isTimestampValid($lastVisitTimestamp)) { $daysSinceLastVisit = round(($this->getCurrentTimestamp() - $lastVisitTimestamp) / 86400, $precision = 0); if ($daysSinceLastVisit < 0) { $daysSinceLastVisit = 0; } } + return $daysSinceLastVisit; } @@ -211,6 +288,15 @@ class Request 'i' => (string)Common::getRequestVar('m', $this->getCurrentDate("i"), 'int', $this->params), 's' => (string)Common::getRequestVar('s', $this->getCurrentDate("s"), 'int', $this->params) ); + if($localTimes['h'] < 0 || $localTimes['h'] > 23) { + $localTimes['h'] = 0; + } + if($localTimes['i'] < 0 || $localTimes['i'] > 59) { + $localTimes['i'] = 0; + } + if($localTimes['s'] < 0 || $localTimes['s'] > 59) { + $localTimes['s'] = 0; + } foreach ($localTimes as $k => $time) { if (strlen($time) == 1) { $localTimes[$k] = '0' . $time; @@ -252,75 +338,176 @@ class Request 'urlref' => array('', 'string'), 'res' => array(self::UNKNOWN_RESOLUTION, 'string'), 'idgoal' => array(-1, 'int'), + 'ping' => array(0, 'int'), // other 'bots' => array(0, 'int'), 'dp' => array(0, 'int'), - 'rec' => array(false, 'int'), + 'rec' => array(0, 'int'), 'new_visit' => array(0, 'int'), // Ecommerce - 'ec_id' => array(false, 'string'), + 'ec_id' => array('', 'string'), 'ec_st' => array(false, 'float'), 'ec_tx' => array(false, 'float'), 'ec_sh' => array(false, 'float'), 'ec_dt' => array(false, 'float'), - 'ec_items' => array('', 'string'), + 'ec_items' => array('', 'json'), // Events - 'e_c' => array(false, 'string'), - 'e_a' => array(false, 'string'), - 'e_n' => array(false, 'string'), + 'e_c' => array('', 'string'), + 'e_a' => array('', 'string'), + 'e_n' => array('', 'string'), 'e_v' => array(false, 'float'), // some visitor attributes can be overwritten - 'cip' => array(false, 'string'), - 'cdt' => array(false, 'string'), - 'cid' => array(false, 'string'), + 'cip' => array('', 'string'), + 'cdt' => array('', 'string'), + 'cid' => array('', 'string'), + 'uid' => array('', 'string'), // Actions / pages - 'cs' => array(false, 'string'), + 'cs' => array('', 'string'), 'download' => array('', 'string'), 'link' => array('', 'string'), 'action_name' => array('', 'string'), 'search' => array('', 'string'), - 'search_cat' => array(false, 'string'), + 'search_cat' => array('', 'string'), 'search_count' => array(-1, 'int'), 'gt_ms' => array(-1, 'int'), + + // Content + 'c_p' => array('', 'string'), + 'c_n' => array('', 'string'), + 'c_t' => array('', 'string'), + 'c_i' => array('', 'string'), ); + if (isset($this->paramsCache[$name])) { + return $this->paramsCache[$name]; + } + if (!isset($supportedParams[$name])) { throw new Exception("Requested parameter $name is not a known Tracking API Parameter."); } + $paramDefaultValue = $supportedParams[$name][0]; $paramType = $supportedParams[$name][1]; - $value = Common::getRequestVar($name, $paramDefaultValue, $paramType, $this->params); + if ($this->hasParam($name)) { + $this->paramsCache[$name] = Common::getRequestVar($name, $paramDefaultValue, $paramType, $this->params); + } else { + $this->paramsCache[$name] = $paramDefaultValue; + } - return $value; + return $this->paramsCache[$name]; + } + + public function setParam($name, $value) + { + $this->params[$name] = $value; + unset($this->paramsCache[$name]); + + if ($name === 'cdt') { + $this->cdtCache = null; + } + } + + private function hasParam($name) + { + return isset($this->params[$name]); + } + + public function getParams() + { + return $this->params; } public function getCurrentTimestamp() { + if (!isset($this->cdtCache)) { + $this->cdtCache = $this->getCustomTimestamp(); + } + + if (!empty($this->cdtCache)) { + return $this->cdtCache; + } + return $this->timestamp; } - protected function isTimestampValid($time) + public function setCurrentTimestamp($timestamp) { - return $time <= $this->getCurrentTimestamp() - && $time > $this->getCurrentTimestamp() - 10 * 365 * 86400; + $this->timestamp = $timestamp; + } + + protected function getCustomTimestamp() + { + if (!$this->hasParam('cdt')) { + return false; + } + + $cdt = $this->getParam('cdt'); + + if (empty($cdt)) { + return false; + } + + if (!is_numeric($cdt)) { + $cdt = strtotime($cdt); + } + + if (!$this->isTimestampValid($cdt, $this->timestamp)) { + Common::printDebug(sprintf("Datetime %s is not valid", date("Y-m-d H:i:m", $cdt))); + return false; + } + + // If timestamp in the past, token_auth is required + $timeFromNow = $this->timestamp - $cdt; + $isTimestampRecent = $timeFromNow < self::CUSTOM_TIMESTAMP_DOES_NOT_REQUIRE_TOKENAUTH_WHEN_NEWER_THAN; + + if (!$isTimestampRecent) { + if (!$this->isAuthenticated()) { + Common::printDebug(sprintf("Custom timestamp is %s seconds old, requires &token_auth...", $timeFromNow)); + Common::printDebug("WARN: Tracker API 'cdt' was used with invalid token_auth"); + return false; + } + } + + return $cdt; + } + + /** + * Returns true if the timestamp is valid ie. timestamp is sometime in the last 10 years and is not in the future. + * + * @param $time int Timestamp to test + * @param $now int Current timestamp + * @return bool + */ + protected function isTimestampValid($time, $now = null) + { + if (empty($now)) { + $now = $this->getCurrentTimestamp(); + } + + return $time <= $now + && $time > $now - 10 * 365 * 86400; } public function getIdSite() { + if (isset($this->idSiteCache)) { + return $this->idSiteCache; + } + $idSite = Common::getRequestVar('idsite', 0, 'int', $this->params); /** * Triggered when obtaining the ID of the site we are tracking a visit for. - * + * * This event can be used to change the site ID so data is tracked for a different * website. - * + * * @param int &$idSite Initialized to the value of the **idsite** query parameter. If a * subscriber sets this variable, the value it uses must be greater * than 0. @@ -328,18 +515,41 @@ class Request * request. */ Piwik::postEvent('Tracker.Request.getIdSite', array(&$idSite, $this->params)); + if ($idSite <= 0) { - throw new Exception('Invalid idSite: \'' . $idSite . '\''); + throw new UnexpectedWebsiteFoundException('Invalid idSite: \'' . $idSite . '\''); } + + $this->idSiteCache = $idSite; + return $idSite; } public function getUserAgent() { - $default = @$_SERVER['HTTP_USER_AGENT']; - return Common::getRequestVar('ua', is_null($default) ? false : $default, 'string', $this->params); + $default = false; + + if (array_key_exists('HTTP_USER_AGENT', $_SERVER)) { + $default = $_SERVER['HTTP_USER_AGENT']; + } + + return Common::getRequestVar('ua', $default, 'string', $this->params); } + public function getCustomVariablesInVisitScope() + { + return $this->getCustomVariables('visit'); + } + + public function getCustomVariablesInPageScope() + { + return $this->getCustomVariables('page'); + } + + /** + * @deprecated since Piwik 2.10.0. Use Request::getCustomVariablesInPageScope() or Request::getCustomVariablesInVisitScope() instead. + * When we "remove" this method we will only set visibility to "private" and pass $parameter = _cvar|cvar as an argument instead of $scope + */ public function getCustomVariables($scope) { if ($scope == 'visit') { @@ -348,14 +558,19 @@ class Request $parameter = 'cvar'; } - $customVar = Common::unsanitizeInputValues(Common::getRequestVar($parameter, '', 'json', $this->params)); + $cvar = Common::getRequestVar($parameter, '', 'json', $this->params); + $customVar = Common::unsanitizeInputValues($cvar); + if (!is_array($customVar)) { return array(); } + $customVariables = array(); - $maxCustomVars = CustomVariables::getMaxCustomVariables(); + $maxCustomVars = CustomVariables::getNumUsableCustomVariables(); + foreach ($customVar as $id => $keyValue) { $id = (int)$id; + if ($id < 1 || $id > $maxCustomVars || count($keyValue) != 2 @@ -364,16 +579,15 @@ class Request Common::printDebug("Invalid custom variables detected (id=$id)"); continue; } + if (strlen($keyValue[1]) == 0) { $keyValue[1] = ""; } // We keep in the URL when Custom Variable have empty names // and values, as it means they can be deleted server side - $key = self::truncateCustomVariable($keyValue[0]); - $value = self::truncateCustomVariable($keyValue[1]); - $customVariables['custom_var_k' . $id] = $key; - $customVariables['custom_var_v' . $id] = $value; + $customVariables['custom_var_k' . $id] = self::truncateCustomVariable($keyValue[0]); + $customVariables['custom_var_v' . $id] = self::truncateCustomVariable($keyValue[1]); } return $customVariables; @@ -397,6 +611,7 @@ class Request if (!$this->shouldUseThirdPartyCookie()) { return; } + Common::printDebug("We manage the cookie..."); $cookie = $this->makeThirdPartyCookie(); @@ -417,37 +632,53 @@ class Request protected function getCookieName() { - return Config::getInstance()->Tracker['cookie_name']; + return TrackerConfig::getConfigValue('cookie_name'); } protected function getCookieExpire() { - return $this->getCurrentTimestamp() + Config::getInstance()->Tracker['cookie_expire']; + return $this->getCurrentTimestamp() + TrackerConfig::getConfigValue('cookie_expire'); } protected function getCookiePath() { - return Config::getInstance()->Tracker['cookie_path']; + return TrackerConfig::getConfigValue('cookie_path'); } /** - * Is the request for a known VisitorId, based on 1st party, 3rd party (optional) cookies or Tracking API forced Visitor ID + * Returns the ID from the request in this order: + * return from a given User ID, + * or from a Tracking API forced Visitor ID, + * or from a Visitor ID from 3rd party (optional) cookies, + * or from a given Visitor Id from 1st party? + * * @throws Exception */ public function getVisitorId() { $found = false; - // Was a Visitor ID "forced" (@see Tracking API setVisitorId()) for this request? - $idVisitor = $this->getForcedVisitorId(); - if (!empty($idVisitor)) { - if (strlen($idVisitor) != Tracker::LENGTH_HEX_ID_STRING) { - throw new Exception("Visitor ID (cid) $idVisitor must be " . Tracker::LENGTH_HEX_ID_STRING . " characters long"); - } - Common::printDebug("Request will be recorded for this idvisitor = " . $idVisitor); + // If User ID is set it takes precedence + $userId = $this->getForcedUserId(); + if ($userId) { + $userIdHashed = $this->getUserIdHashed($userId); + $idVisitor = $this->truncateIdAsVisitorId($userIdHashed); + Common::printDebug("Request will be recorded for this user_id = " . $userId . " (idvisitor = $idVisitor)"); $found = true; } + // Was a Visitor ID "forced" (@see Tracking API setVisitorId()) for this request? + if (!$found) { + $idVisitor = $this->getForcedVisitorId(); + if (!empty($idVisitor)) { + if (strlen($idVisitor) != Tracker::LENGTH_HEX_ID_STRING) { + throw new InvalidRequestParameterException("Visitor ID (cid) $idVisitor must be " . Tracker::LENGTH_HEX_ID_STRING . " characters long"); + } + Common::printDebug("Request will be recorded for this idvisitor = " . $idVisitor); + $found = true; + } + } + // - If set to use 3rd party cookies for Visit ID, read the cookie if (!$found) { // - By default, reads the first party cookie ID @@ -462,6 +693,7 @@ class Request } } } + // If a third party cookie was not found, we default to the first party cookie if (!$found) { $idVisitor = Common::getRequestVar('_id', '', 'string', $this->params); @@ -469,78 +701,34 @@ class Request } if ($found) { - $truncated = substr($idVisitor, 0, Tracker::LENGTH_HEX_ID_STRING); + $truncated = $this->truncateIdAsVisitorId($idVisitor); $binVisitorId = @Common::hex2bin($truncated); if (!empty($binVisitorId)) { return $binVisitorId; } } + return false; } public function getIp() { - if (!empty($this->enforcedIp)) { - $ipString = $this->enforcedIp; - } else { - $ipString = IP::getIpFromHeader(); - } - $ip = IP::P2N($ipString); - return $ip; + return IPUtils::stringToBinaryIP($this->getIpString()); } - public function setForceIp($ip) + public function getForcedUserId() { - if (!empty($ip)) { - $this->enforcedIp = $ip; + $userId = $this->getParam('uid'); + if (strlen($userId) > 0) { + return $userId; } - } - public function setForceDateTime($dateTime) - { - if (!is_numeric($dateTime)) { - $dateTime = strtotime($dateTime); - } - if (!empty($dateTime)) { - $this->timestamp = $dateTime; - } - } - - public function setForcedVisitorId($visitorId) - { - if (!empty($visitorId)) { - $this->forcedVisitorId = $visitorId; - } + return false; } public function getForcedVisitorId() { - return $this->forcedVisitorId; - } - - public function overrideLocation(&$visitorInfo) - { - if (!$this->isAuthenticated()) { - return; - } - - // check for location override query parameters (ie, lat, long, country, region, city) - static $locationOverrideParams = array( - 'country' => array('string', 'location_country'), - 'region' => array('string', 'location_region'), - 'city' => array('string', 'location_city'), - 'lat' => array('float', 'location_latitude'), - 'long' => array('float', 'location_longitude'), - ); - foreach ($locationOverrideParams as $queryParamName => $info) { - list($type, $visitorInfoKey) = $info; - - $value = Common::getRequestVar($queryParamName, false, $type, $this->params); - if (!empty($value)) { - $visitorInfo[$visitorInfoKey] = $value; - } - } - return; + return $this->getParam('cid'); } public function getPlugins() @@ -553,9 +741,9 @@ class Request return $plugins; } - public function getParamsCount() + public function isEmptyRequest() { - return count($this->params); + return $this->isEmptyRequest; } const GENERATION_TIME_MS_MAXIMUM = 3600000; // 1 hour @@ -568,6 +756,71 @@ class Request ) { return (int)$generationTime; } + return false; } + + /** + * @param $idVisitor + * @return string + */ + private function truncateIdAsVisitorId($idVisitor) + { + return substr($idVisitor, 0, Tracker::LENGTH_HEX_ID_STRING); + } + + /** + * Matches implementation of PiwikTracker::getUserIdHashed + * + * @param $userId + * @return string + */ + public function getUserIdHashed($userId) + { + return substr(sha1($userId), 0, 16); + } + + /** + * @return mixed|string + * @throws Exception + */ + public function getIpString() + { + $cip = $this->getParam('cip'); + + if (empty($cip)) { + return IP::getIpFromHeader(); + } + + if (!$this->isAuthenticated()) { + Common::printDebug("WARN: Tracker API 'cip' was used with invalid token_auth"); + return IP::getIpFromHeader(); + } + + return $cip; + } + + /** + * Set a request metadata value. + * + * @param string $pluginName eg, `'Actions'`, `'Goals'`, `'YourPlugin'` + * @param string $key + * @param mixed $value + */ + public function setMetadata($pluginName, $key, $value) + { + $this->requestMetadata[$pluginName][$key] = $value; + } + + /** + * Get a request metadata value. Returns `null` if none exists. + * + * @param string $pluginName eg, `'Actions'`, `'Goals'`, `'YourPlugin'` + * @param string $key + * @return mixed + */ + public function getMetadata($pluginName, $key) + { + return isset($this->requestMetadata[$pluginName][$key]) ? $this->requestMetadata[$pluginName][$key] : null; + } } diff --git a/www/analytics/core/Tracker/RequestProcessor.php b/www/analytics/core/Tracker/RequestProcessor.php new file mode 100644 index 00000000..8d50d3b2 --- /dev/null +++ b/www/analytics/core/Tracker/RequestProcessor.php @@ -0,0 +1,174 @@ +visitorInfo` will be empty. + * + * @param VisitProperties $visitProperties + * @param Request $request + * @return bool If `true` the tracking request will be aborted. + */ + public function processRequestParams(VisitProperties $visitProperties, Request $request) + { + return false; + } + + /** + * This is the third method called when processing a tracker request. + * + * Derived classes should use this method to set request metadata that needs request metadata + * from other plugins, or to override request metadata from other plugins to change + * tracking behavior. + * + * When this method is called, you can assume all available request metadata from all plugins + * will be initialized (but not at their final value). Also, `$visitProperties->visitorInfo` + * will contain the values of the visitor's last known visit (if any). + * + * @param VisitProperties $visitProperties + * @param Request $request + * @return bool If `true` the tracking request will be aborted. + */ + public function afterRequestProcessed(VisitProperties $visitProperties, Request $request) + { + return false; + } + + /** + * This method is called before recording a new visit. You can set/change visit information here + * to change what gets inserted into `log_visit`. + * + * Only implement this method if you cannot use a Dimension for the same thing. + * + * @param VisitProperties $visitProperties + * @param Request $request + */ + public function onNewVisit(VisitProperties $visitProperties, Request $request) + { + // empty + } + + /** + * This method is called before updating an existing visit. You can set/change visit information + * here to change what gets recorded in `log_visit`. + * + * Only implement this method if you cannot use a Dimension for the same thing. + * + * @param array &$valuesToUpdate + * @param VisitProperties $visitProperties + * @param Request $request + */ + public function onExistingVisit(&$valuesToUpdate, VisitProperties $visitProperties, Request $request) + { + // empty + } + + /** + * This method is called last. Derived classes should use this method to insert log data. They + * should also only read request metadata, and not set it. + * + * When this method is called, you can assume all request metadata have their final values. Also, + * `$visitProperties->visitorInfo` will contain the properties of the visitor's current visit (in + * other words, the values in the array were persisted to the DB before this method was called). + * + * @param VisitProperties $visitProperties + * @param Request $request + */ + public function recordLogs(VisitProperties $visitProperties, Request $request) + { + // empty + } +} diff --git a/www/analytics/core/Tracker/RequestSet.php b/www/analytics/core/Tracker/RequestSet.php new file mode 100644 index 00000000..3d3c626e --- /dev/null +++ b/www/analytics/core/Tracker/RequestSet.php @@ -0,0 +1,255 @@ +requests = array(); + + foreach ($requests as $request) { + if (empty($request) && !is_array($request)) { + continue; + } + + if (!$request instanceof Request) { + $request = new Request($request, $this->getTokenAuth()); + } + + $this->requests[] = $request; + } + } + + public function setTokenAuth($tokenAuth) + { + $this->tokenAuth = $tokenAuth; + } + + public function getNumberOfRequests() + { + if (is_array($this->requests)) { + return count($this->requests); + } + + return 0; + } + + public function getRequests() + { + if (!$this->areRequestsInitialized()) { + return array(); + } + + return $this->requests; + } + + public function getTokenAuth() + { + if (!is_null($this->tokenAuth)) { + return $this->tokenAuth; + } + + return Common::getRequestVar('token_auth', false); + } + + private function areRequestsInitialized() + { + return !is_null($this->requests); + } + + public function initRequestsAndTokenAuth() + { + if ($this->areRequestsInitialized()) { + return; + } + + /** + * Triggered when detecting tracking requests. A plugin can use this event to set + * requests that should be tracked by calling the {@link RequestSet::setRequests()} method. + * For example the BulkTracking plugin uses this event to detect tracking requests and auth token based on + * a sent JSON instead of default $_GET+$_POST. It would allow you for example to track requests based on + * XML or you could import tracking requests stored in a file. + * + * @param \Piwik\Tracker\RequestSet &$requestSet Call {@link setRequests()} to initialize requests and + * {@link setTokenAuth()} to set a detected auth token. + * + * @ignore This event is not public yet as the RequestSet API is not really stable yet + */ + Piwik::postEvent('Tracker.initRequestSet', array($this)); + + if (!$this->areRequestsInitialized()) { + $this->requests = array(); + + if (!empty($_GET) || !empty($_POST)) { + $this->setRequests(array($_GET + $_POST)); + } + } + } + + public function hasRequests() + { + return !empty($this->requests); + } + + protected function getRedirectUrl() + { + return Common::getRequestVar('redirecturl', false, 'string'); + } + + protected function hasRedirectUrl() + { + $redirectUrl = $this->getRedirectUrl(); + + return !empty($redirectUrl); + } + + protected function getAllSiteIdsWithinRequest() + { + if (empty($this->requests)) { + return array(); + } + + $siteIds = array(); + foreach ($this->requests as $request) { + $siteIds[] = (int) $request->getIdSite(); + } + + return array_values(array_unique($siteIds)); + } + + // TODO maybe move to reponse? or somewhere else? not sure where! + public function shouldPerformRedirectToUrl() + { + if (!$this->hasRedirectUrl()) { + return false; + } + + if (!$this->hasRequests()) { + return false; + } + + $redirectUrl = $this->getRedirectUrl(); + $host = Url::getHostFromUrl($redirectUrl); + + if (empty($host)) { + return false; + } + + $urls = new SiteUrls(); + $siteUrls = $urls->getAllCachedSiteUrls(); + $siteIds = $this->getAllSiteIdsWithinRequest(); + + foreach ($siteIds as $siteId) { + if (empty($siteUrls[$siteId])) { + $siteUrls[$siteId] = array(); + } + + if (Url::isHostInUrls($host, $siteUrls[$siteId])) { + return $redirectUrl; + } + } + + return false; + } + + public function getState() + { + $requests = array( + 'requests' => array(), + 'env' => $this->getEnvironment(), + 'tokenAuth' => $this->getTokenAuth(), + 'time' => time() + ); + + foreach ($this->getRequests() as $request) { + $requests['requests'][] = $request->getRawParams(); + } + + return $requests; + } + + public function restoreState($state) + { + $backupEnv = $this->getCurrentEnvironment(); + + $this->setEnvironment($state['env']); + $this->setTokenAuth($state['tokenAuth']); + + $this->restoreEnvironment(); + $this->setRequests($state['requests']); + + foreach ($this->getRequests() as $request) { + $request->setCurrentTimestamp($state['time']); + } + + $this->resetEnvironment($backupEnv); + } + + public function rememberEnvironment() + { + $this->setEnvironment($this->getEnvironment()); + } + + public function setEnvironment($env) + { + $this->env = $env; + } + + protected function getEnvironment() + { + if (!empty($this->env)) { + return $this->env; + } + + return $this->getCurrentEnvironment(); + } + + public function restoreEnvironment() + { + if (empty($this->env)) { + return; + } + + $this->resetEnvironment($this->env); + } + + private function resetEnvironment($env) + { + $_SERVER = $env['server']; + } + + private function getCurrentEnvironment() + { + return array( + 'server' => $_SERVER + ); + } +} diff --git a/www/analytics/core/Tracker/Response.php b/www/analytics/core/Tracker/Response.php new file mode 100644 index 00000000..2b4a6f3b --- /dev/null +++ b/www/analytics/core/Tracker/Response.php @@ -0,0 +1,182 @@ +isDebugModeEnabled()) { + $this->timer = new Timer(); + + TrackerDb::enableProfiling(); + } + } + + public function getOutput() + { + $this->outputAccessControlHeaders(); + + if (is_null($this->content) && ob_get_level() > 0) { + $this->content = ob_get_clean(); + } + + return $this->content; + } + + /** + * Echos an error message & other information, then exits. + * + * @param Tracker $tracker + * @param Exception $e + * @param int $statusCode eg 500 + */ + public function outputException(Tracker $tracker, Exception $e, $statusCode) + { + Common::sendResponseCode($statusCode); + $this->logExceptionToErrorLog($e); + + if ($tracker->isDebugModeEnabled()) { + Common::sendHeader('Content-Type: text/html; charset=utf-8'); + $trailer = 'Backtrace:
' . $e->getTraceAsString() . '
'; + $headerPage = file_get_contents(PIWIK_INCLUDE_PATH . '/plugins/Morpheus/templates/simpleLayoutHeader.tpl'); + $footerPage = file_get_contents(PIWIK_INCLUDE_PATH . '/plugins/Morpheus/templates/simpleLayoutFooter.tpl'); + $headerPage = str_replace('{$HTML_TITLE}', 'Piwik › Error', $headerPage); + + echo $headerPage . '

' . $this->getMessageFromException($e) . '

' . $trailer . $footerPage; + } else { + $this->outputApiResponse($tracker); + } + } + + public function outputResponse(Tracker $tracker) + { + if (!$tracker->shouldRecordStatistics()) { + $this->outputApiResponse($tracker); + Common::printDebug("Logging disabled, display transparent logo"); + } elseif (!$tracker->hasLoggedRequests()) { + if (!$this->isHttpGetRequest() || !empty($_GET) || !empty($_POST)) { + Common::sendResponseCode(400); + } + Common::printDebug("Empty request => Piwik page"); + echo "Piwik is a free/libre web analytics that lets you keep control of your data."; + } else { + $this->outputApiResponse($tracker); + Common::printDebug("Nothing to notice => default behaviour"); + } + + Common::printDebug("End of the page."); + + if ($tracker->isDebugModeEnabled() + && $tracker->isDatabaseConnected() + && TrackerDb::isProfilingEnabled()) { + $db = Tracker::getDatabase(); + $db->recordProfiling(); + Profiler::displayDbTrackerProfile($db); + } + + if ($tracker->isDebugModeEnabled()) { + Common::printDebug($_COOKIE); + Common::printDebug((string)$this->timer); + } + } + + private function outputAccessControlHeaders() + { + if (!$this->isHttpGetRequest()) { + $origin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '*'; + Common::sendHeader('Access-Control-Allow-Origin: ' . $origin); + Common::sendHeader('Access-Control-Allow-Credentials: true'); + } + } + + private function isHttpGetRequest() + { + $requestMethod = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET'; + + return strtoupper($requestMethod) === 'GET'; + } + + private function getOutputBuffer() + { + return ob_get_contents(); + } + + protected function hasAlreadyPrintedOutput() + { + return strlen($this->getOutputBuffer()) > 0; + } + + private function outputApiResponse(Tracker $tracker) + { + if ($tracker->isDebugModeEnabled()) { + return; + } + + if ($this->hasAlreadyPrintedOutput()) { + return; + } + + $request = $_GET + $_POST; + + if (array_key_exists('send_image', $request) && $request['send_image'] === '0') { + Common::sendResponseCode(204); + return; + } + + $this->outputTransparentGif(); + } + + private function outputTransparentGif() + { + $transGifBase64 = "R0lGODlhAQABAIAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="; + Common::sendHeader('Content-Type: image/gif'); + + echo base64_decode($transGifBase64); + } + + /** + * Gets the error message to output when a tracking request fails. + * + * @param Exception $e + * @return string + */ + protected function getMessageFromException($e) + { + // Note: duplicated from FormDatabaseSetup.isAccessDenied + // Avoid leaking the username/db name when access denied + if ($e->getCode() == 1044 || $e->getCode() == 42000) { + return "Error while connecting to the Piwik database - please check your credentials in config/config.ini.php file"; + } + + if (Common::isPhpCliMode()) { + return $e->getMessage() . "\n" . $e->getTraceAsString(); + } + + return $e->getMessage(); + } + + protected function logExceptionToErrorLog(Exception $e) + { + error_log(sprintf("Error in Piwik (tracker): %s", str_replace("\n", " ", $this->getMessageFromException($e)))); + } +} diff --git a/www/analytics/core/Tracker/ScheduledTasksRunner.php b/www/analytics/core/Tracker/ScheduledTasksRunner.php new file mode 100644 index 00000000..6c1f230b --- /dev/null +++ b/www/analytics/core/Tracker/ScheduledTasksRunner.php @@ -0,0 +1,86 @@ +shouldRecordStatistics(); + } + + /** + * Tracker requests will automatically trigger the Scheduled tasks. + * This is useful for users who don't setup the cron, + * but still want daily/weekly/monthly PDF reports emailed automatically. + * + * This is similar to calling the API CoreAdminHome.runScheduledTasks + */ + public function runScheduledTasks() + { + $now = time(); + + // Currently, there are no hourly tasks. When there are some, + // this could be too aggressive minimum interval (some hours would be skipped in case of low traffic) + $minimumInterval = TrackerConfig::getConfigValue('scheduled_tasks_min_interval'); + + // If the user disabled browser archiving, he has already setup a cron + // To avoid parallel requests triggering the Scheduled Tasks, + // Get last time tasks started executing + $cache = Cache::getCacheGeneral(); + + if ($minimumInterval <= 0 + || empty($cache['isBrowserTriggerEnabled']) + ) { + Common::printDebug("-> Scheduled tasks not running in Tracker: Browser archiving is disabled."); + return; + } + + $nextRunTime = $cache['lastTrackerCronRun'] + $minimumInterval; + + if ((defined('DEBUG_FORCE_SCHEDULED_TASKS') && DEBUG_FORCE_SCHEDULED_TASKS) + || $cache['lastTrackerCronRun'] === false + || $nextRunTime < $now + ) { + $cache['lastTrackerCronRun'] = $now; + Cache::setCacheGeneral($cache); + + Option::set('lastTrackerCronRun', $cache['lastTrackerCronRun']); + Common::printDebug('-> Scheduled Tasks: Starting...'); + + $invokeScheduledTasksUrl = "?module=API&format=csv&convertToUnicode=0&method=CoreAdminHome.runScheduledTasks&trigger=archivephp"; + + $cliMulti = new CliMulti(); + $cliMulti->runAsSuperUser(); + $responses = $cliMulti->request(array($invokeScheduledTasksUrl)); + $resultTasks = reset($responses); + + Common::printDebug($resultTasks); + + Common::printDebug('Finished Scheduled Tasks.'); + } else { + Common::printDebug("-> Scheduled tasks not triggered."); + } + + Common::printDebug("Next run will be from: " . date('Y-m-d H:i:s', $nextRunTime) . ' UTC'); + } +} diff --git a/www/analytics/core/Tracker/Settings.php b/www/analytics/core/Tracker/Settings.php new file mode 100644 index 00000000..36aeb917 --- /dev/null +++ b/www/analytics/core/Tracker/Settings.php @@ -0,0 +1,126 @@ +isSameFingerprintsAcrossWebsites = $isSameFingerprintsAcrossWebsites; + } + + public function getConfigId(Request $request, $ipAddress) + { + list($plugin_Flash, $plugin_Java, $plugin_Director, $plugin_Quicktime, $plugin_RealPlayer, $plugin_PDF, + $plugin_WindowsMedia, $plugin_Gears, $plugin_Silverlight, $plugin_Cookie) = $request->getPlugins(); + + $userAgent = $request->getUserAgent(); + + $deviceDetector = DeviceDetectorFactory::getInstance($userAgent); + $aBrowserInfo = $deviceDetector->getClient(); + + if ($aBrowserInfo['type'] != 'browser') { + // for now only track browsers + unset($aBrowserInfo); + } + + $browserName = !empty($aBrowserInfo['short_name']) ? $aBrowserInfo['short_name'] : 'UNK'; + $browserVersion = !empty($aBrowserInfo['version']) ? $aBrowserInfo['version'] : ''; + + if ($deviceDetector->isBot()) { + $os = self::OS_BOT; + } else { + $os = $deviceDetector->getOS(); + $os = empty($os['short_name']) ? 'UNK' : $os['short_name']; + } + + $browserLang = substr($request->getBrowserLanguage(), 0, 20); // limit the length of this string to match db + + return $this->getConfigHash( + $request, + $os, + $browserName, + $browserVersion, + $plugin_Flash, + $plugin_Java, + $plugin_Director, + $plugin_Quicktime, + $plugin_RealPlayer, + $plugin_PDF, + $plugin_WindowsMedia, + $plugin_Gears, + $plugin_Silverlight, + $plugin_Cookie, + $ipAddress, + $browserLang); + } + + /** + * Returns a 64-bit hash that attemps to identify a user. + * Maintaining some privacy by default, eg. prevents the merging of several Piwik serve together for matching across instances.. + * + * @param $os + * @param $browserName + * @param $browserVersion + * @param $plugin_Flash + * @param $plugin_Java + * @param $plugin_Director + * @param $plugin_Quicktime + * @param $plugin_RealPlayer + * @param $plugin_PDF + * @param $plugin_WindowsMedia + * @param $plugin_Gears + * @param $plugin_Silverlight + * @param $plugin_Cookie + * @param $ip + * @param $browserLang + * @return string + */ + protected function getConfigHash(Request $request, $os, $browserName, $browserVersion, $plugin_Flash, $plugin_Java, + $plugin_Director, $plugin_Quicktime, $plugin_RealPlayer, $plugin_PDF, + $plugin_WindowsMedia, $plugin_Gears, $plugin_Silverlight, $plugin_Cookie, $ip, + $browserLang) + { + // prevent the config hash from being the same, across different Piwik instances + // (limits ability of different Piwik instances to cross-match users) + $salt = SettingsPiwik::getSalt(); + + $configString = + $os + . $browserName . $browserVersion + . $plugin_Flash . $plugin_Java . $plugin_Director . $plugin_Quicktime . $plugin_RealPlayer . $plugin_PDF + . $plugin_WindowsMedia . $plugin_Gears . $plugin_Silverlight . $plugin_Cookie + . $ip + . $browserLang + . $salt; + + if (!$this->isSameFingerprintsAcrossWebsites) { + $configString .= $request->getIdSite(); + } + + $hash = md5($configString, $raw_output = true); + + return substr($hash, 0, Tracker::LENGTH_BINARY_ID); + } +} diff --git a/www/analytics/core/Tracker/SettingsStorage.php b/www/analytics/core/Tracker/SettingsStorage.php new file mode 100644 index 00000000..a69e0355 --- /dev/null +++ b/www/analytics/core/Tracker/SettingsStorage.php @@ -0,0 +1,58 @@ +getOptionKey(); + $cache = $this->getCache(); + + if ($cache->contains($cacheId)) { + $settings = $cache->fetch($cacheId); + } else { + $settings = parent::loadSettings(); + + $cache->save($cacheId, $settings); + } + + return $settings; + } + + public function save() + { + parent::save(); + self::clearCache(); + } + + private function getCache() + { + return self::buildCache($this->getOptionKey()); + } + + public static function clearCache() + { + Cache::deleteTrackerCache(); + self::buildCache()->flushAll(); + } + + private static function buildCache() + { + return PiwikCache::getEagerCache(); + } +} diff --git a/www/analytics/core/Tracker/TableLogAction.php b/www/analytics/core/Tracker/TableLogAction.php index 1eb237d3..9552e7ae 100644 --- a/www/analytics/core/Tracker/TableLogAction.php +++ b/www/analytics/core/Tracker/TableLogAction.php @@ -1,6 +1,6 @@ query($sql, array($name, $name, $type, $urlPrefix)); - $actionId = Tracker::getDatabase()->lastInsertId(); - - $inserted[$fieldName] = $actionId; + $actionId = self::getModel()->createNewIdAction($name, $type, $urlPrefix); Common::printDebug("Recorded a new action (" . Action::getTypeAsString($type) . ") in the lookup table: " . $name . " (idaction = " . $actionId . ")"); + + $inserted[$fieldName] = $actionId; } + return $inserted; } + private static function getModel() + { + return new Model(); + } + private static function queryIdsAction($actionsNameAndType) { - $sql = TableLogAction::getSqlSelectActionId(); - $bind = array(); - $i = 0; + $toQuery = array(); foreach ($actionsNameAndType as &$actionNameType) { list($name, $type, $urlPrefix) = $actionNameType; - if (empty($name)) { - continue; - } - if ($i > 0) { - $sql .= " OR ( hash = CRC32(?) AND name = ? AND type = ? ) "; - } - $bind[] = $name; - $bind[] = $name; - $bind[] = $type; - $i++; + $toQuery[] = array('name' => $name, 'type' => $type); } - // Case URL & Title are empty - if (empty($bind)) { - return false; - } - $actionIds = Tracker::getDatabase()->fetchAll($sql, $bind); + + $actionIds = self::getModel()->getIdsAction($toQuery); + return $actionIds; } @@ -151,6 +131,7 @@ class TableLogAction // For the Actions found in the lookup table, add the idaction in the array, // If not found in lookup table, queue for INSERT $fieldNamesToInsert = $fieldNameToActionId = array(); + foreach ($actionsNameAndType as $fieldName => &$actionNameType) { @list($name, $type, $urlPrefix) = $actionNameType; if (empty($name)) { @@ -173,10 +154,10 @@ class TableLogAction $fieldNamesToInsert[] = $fieldName; } } + return array($fieldNameToActionId, $fieldNamesToInsert); } - /** * Convert segment expression to an action ID or an SQL expression. * @@ -193,34 +174,37 @@ class TableLogAction */ public static function getIdActionFromSegment($valueToMatch, $sqlField, $matchType, $segmentName) { - $actionType = self::guessActionTypeFromSegment($segmentName); - - if ($actionType == Action::TYPE_PAGE_URL) { - // for urls trim protocol and www because it is not recorded in the db - $valueToMatch = preg_replace('@^http[s]?://(www\.)?@i', '', $valueToMatch); - } - $valueToMatch = Common::sanitizeInputValue(Common::unsanitizeInputValue($valueToMatch)); - - if ($matchType == SegmentExpression::MATCH_EQUAL - || $matchType == SegmentExpression::MATCH_NOT_EQUAL - ) { - $idAction = self::getIdActionMatchingNameAndType($valueToMatch, $actionType); - // if the action is not found, we hack -100 to ensure it tries to match against an integer - // otherwise binding idaction_name to "false" returns some rows for some reasons (in case &segment=pageTitle==Větrnásssssss) - if (empty($idAction)) { - $idAction = -100; + if ($segmentName === 'actionType') { + $actionType = (int) $valueToMatch; + $valueToMatch = array(); + $sql = 'SELECT idaction FROM ' . Common::prefixTable('log_action') . ' WHERE type = ' . $actionType . ' )'; + } else { + $actionType = self::guessActionTypeFromSegment($segmentName); + if ($actionType == Action::TYPE_PAGE_URL) { + // for urls trim protocol and www because it is not recorded in the db + $valueToMatch = preg_replace('@^http[s]?://(www\.)?@i', '', $valueToMatch); } - return $idAction; + + $valueToMatch = self::normaliseActionString($actionType, $valueToMatch); + if ($matchType == SegmentExpression::MATCH_EQUAL + || $matchType == SegmentExpression::MATCH_NOT_EQUAL + ) { + $idAction = self::getModel()->getIdActionMatchingNameAndType($valueToMatch, $actionType); + // Action is not found (eg. &segment=pageTitle==Větrnásssssss) + if (empty($idAction)) { + $idAction = null; + } + return $idAction; + } + + // "name contains $string" match can match several idaction so we cannot return yet an idaction + // special case + $sql = self::getSelectQueryWhereNameContains($matchType, $actionType); } - // "name contains $string" match can match several idaction so we cannot return yet an idaction - // special case - $sql = TableLogAction::getSelectQueryWhereNameContains($matchType, $actionType); - return array( - // mark that the returned value is an sql-expression instead of a literal value - 'SQL' => $sql, - 'bind' => $valueToMatch, - ); + + $cache = StaticContainer::get('Piwik\Tracker\TableLogAction\Cache'); + return $cache->getIdActionFromSegment($valueToMatch, $sql); } /** @@ -231,11 +215,18 @@ class TableLogAction private static function guessActionTypeFromSegment($segmentName) { $exactMatch = array( - 'eventAction' => Action::TYPE_EVENT_ACTION, - 'eventCategory' => Action::TYPE_EVENT_CATEGORY, - 'eventName' => Action::TYPE_EVENT_NAME, + 'outlinkUrl' => Action::TYPE_OUTLINK, + 'downloadUrl' => Action::TYPE_DOWNLOAD, + 'eventAction' => Action::TYPE_EVENT_ACTION, + 'eventCategory' => Action::TYPE_EVENT_CATEGORY, + 'eventName' => Action::TYPE_EVENT_NAME, + 'contentPiece' => Action::TYPE_CONTENT_PIECE, + 'contentTarget' => Action::TYPE_CONTENT_TARGET, + 'contentName' => Action::TYPE_CONTENT_NAME, + 'contentInteraction' => Action::TYPE_CONTENT_INTERACTION, ); - if(!empty($exactMatch[$segmentName])) { + + if (!empty($exactMatch[$segmentName])) { return $exactMatch[$segmentName]; } @@ -253,5 +244,40 @@ class TableLogAction } } -} + /** + * This function will sanitize or not if it's needed for the specified action type + * + * URLs (Download URL, Outlink URL) are stored raw (unsanitized) + * while other action types are stored Sanitized + * + * @param $actionType + * @param $actionString + * @return string + */ + private static function normaliseActionString($actionType, $actionString) + { + $actionString = Common::unsanitizeInputValue($actionString); + if (self::isActionTypeStoredUnsanitized($actionType)) { + return $actionString; + } + + return Common::sanitizeInputValue($actionString); + } + + /** + * @param $actionType + * @return bool + */ + private static function isActionTypeStoredUnsanitized($actionType) + { + $actionsTypesStoredUnsanitized = array( + $actionType == Action::TYPE_DOWNLOAD, + $actionType == Action::TYPE_OUTLINK, + $actionType == Action::TYPE_PAGE_URL, + $actionType == Action::TYPE_CONTENT, + ); + + return in_array($actionType, $actionsTypesStoredUnsanitized); + } +} diff --git a/www/analytics/core/Tracker/TableLogAction/Cache.php b/www/analytics/core/Tracker/TableLogAction/Cache.php new file mode 100644 index 00000000..ba078c57 --- /dev/null +++ b/www/analytics/core/Tracker/TableLogAction/Cache.php @@ -0,0 +1,160 @@ +isEnabled = (bool)$config->General['enable_segments_subquery_cache']; + $this->limitActionIds = $config->General['segments_subquery_cache_limit']; + $this->lifetime = $config->General['segments_subquery_cache_ttl']; + $this->logger = $logger; + $this->cache = $cache; + } + + /** + * @param $valueToMatch + * @param $sql + * @return array|null + * @throws \Exception + */ + public function getIdActionFromSegment($valueToMatch, $sql) + { + if (!$this->isEnabled) { + return array( + // mark that the returned value is an sql-expression instead of a literal value + 'SQL' => $sql, + 'bind' => $valueToMatch, + ); + } + + $ids = self::getIdsFromCache($valueToMatch, $sql); + + if(is_null($ids)) { + // Too Big To Cache, issue SQL as subquery instead + return array( + 'SQL' => $sql, + 'bind' => $valueToMatch, + ); + } + + if(count($ids) == 0) { + return null; + } + + + $sql = Common::getSqlStringFieldsArray($ids); + $bind = $ids; + + return array( + // mark that the returned value is an sql-expression instead of a literal value + 'SQL' => $sql, + 'bind' => $bind, + ); + } + + + /** + * @param $valueToMatch + * @param $sql + * @return array of IDs, or null if the returnset is too big to cache + */ + private function getIdsFromCache($valueToMatch, $sql) + { + $cacheKey = $this->getCacheKey($valueToMatch, $sql); + + if ($this->cache->contains($cacheKey) === true) { // TODO: hits + $this->logger->debug("Segment subquery cache HIT (for '$valueToMatch' and SQL '$sql)"); + return $this->cache->fetch($cacheKey); + } + + $ids = $this->fetchActionIdsFromDb($valueToMatch, $sql); + + if($this->isTooBigToCache($ids)) { + $this->logger->debug("Segment subquery cache SKIPPED SAVE (too many IDs returned by subquery: %s ids)'", array(count($ids))); + $this->cache->save($cacheKey, $ids = null, $this->lifetime); + return null; + } + + $this->cache->save($cacheKey, $ids, $this->lifetime); + $this->logger->debug("Segment subquery cache SAVE (for '$valueToMatch' and SQL '$sql')'"); + + return $ids; + } + + /** + * @param $valueToMatch + * @param $sql + * @return string + * @throws + */ + private function getCacheKey($valueToMatch, $sql) + { + if(is_array($valueToMatch)) { + throw new \Exception("value to match is an array: this is not expected"); + } + + $uniqueKey = md5($sql . $valueToMatch); + $cacheKey = 'TableLogAction.getIdActionFromSegment.' . $uniqueKey; + return $cacheKey; + } + + /** + * @param $valueToMatch + * @param $sql + * @return array|null + * @throws \Exception + */ + private function fetchActionIdsFromDb($valueToMatch, $sql) + { + $idActions = \Piwik\Db::fetchAll($sql, $valueToMatch); + + $ids = array(); + foreach ($idActions as $idAction) { + $ids[] = $idAction['idaction']; + } + + return $ids; + } + + /** + * @param $ids + * @return bool + */ + private function isTooBigToCache($ids) + { + return count($ids) > $this->limitActionIds; + } +} \ No newline at end of file diff --git a/www/analytics/core/Tracker/TrackerCodeGenerator.php b/www/analytics/core/Tracker/TrackerCodeGenerator.php new file mode 100644 index 00000000..c985db88 --- /dev/null +++ b/www/analytics/core/Tracker/TrackerCodeGenerator.php @@ -0,0 +1,199 @@ +getJavascriptTagOptions($idSite, $mergeSubdomains, $mergeAliasUrls); + } + $maxCustomVars = CustomVariables::getNumUsableCustomVariables(); + + if ($visitorCustomVariables && count($visitorCustomVariables) > 0) { + $options .= ' // you can set up to ' . $maxCustomVars . ' custom variables for each visitor' . "\n"; + $index = 1; + foreach ($visitorCustomVariables as $visitorCustomVariable) { + if (empty($visitorCustomVariable)) { + continue; + } + + $options .= sprintf( + ' _paq.push(["setCustomVariable", %d, %s, %s, "visit"]);%s', + $index++, + json_encode($visitorCustomVariable[0]), + json_encode($visitorCustomVariable[1]), + "\n" + ); + } + } + if ($pageCustomVariables && count($pageCustomVariables) > 0) { + $options .= ' // you can set up to ' . $maxCustomVars . ' custom variables for each action (page view, download, click, site search)' . "\n"; + $index = 1; + foreach ($pageCustomVariables as $pageCustomVariable) { + if (empty($pageCustomVariable)) { + continue; + } + $options .= sprintf( + ' _paq.push(["setCustomVariable", %d, %s, %s, "page"]);%s', + $index++, + json_encode($pageCustomVariable[0]), + json_encode($pageCustomVariable[1]), + "\n" + ); + } + } + if ($customCampaignNameQueryParam) { + $options .= ' _paq.push(["setCampaignNameKey", ' + . json_encode($customCampaignNameQueryParam) . ']);' . "\n"; + } + if ($customCampaignKeywordParam) { + $options .= ' _paq.push(["setCampaignKeywordKey", ' + . json_encode($customCampaignKeywordParam) . ']);' . "\n"; + } + if ($doNotTrack) { + $options .= ' _paq.push(["setDoNotTrack", true]);' . "\n"; + } + if ($disableCookies) { + $options .= ' _paq.push(["disableCookies"]);' . "\n"; + } + + $codeImpl = array( + 'idSite' => $idSite, + // TODO why sanitizeInputValue() and not json_encode? + 'piwikUrl' => Common::sanitizeInputValue($piwikUrl), + 'options' => $options, + 'optionsBeforeTrackerUrl' => $optionsBeforeTrackerUrl, + 'protocol' => '//' + ); + $parameters = compact('mergeSubdomains', 'groupPageTitlesByDomain', 'mergeAliasUrls', 'visitorCustomVariables', + 'pageCustomVariables', 'customCampaignNameQueryParam', 'customCampaignKeywordParam', + 'doNotTrack'); + + /** + * Triggered when generating JavaScript tracking code server side. Plugins can use + * this event to customise the JavaScript tracking code that is displayed to the + * user. + * + * @param array &$codeImpl An array containing snippets of code that the event handler + * can modify. Will contain the following elements: + * + * - **idSite**: The ID of the site being tracked. + * - **piwikUrl**: The tracker URL to use. + * - **options**: A string of JavaScript code that customises + * the JavaScript tracker. + * - **optionsBeforeTrackerUrl**: A string of Javascript code that customises + * the JavaScript tracker inside of anonymous function before + * adding setTrackerUrl into paq. + * - **protocol**: Piwik url protocol. + * + * The **httpsPiwikUrl** element can be set if the HTTPS + * domain is different from the normal domain. + * @param array $parameters The parameters supplied to `TrackerCodeGenerator::generate()`. + */ + Piwik::postEvent('Piwik.getJavascriptCode', array(&$codeImpl, $parameters)); + + $setTrackerUrl = 'var u="' . $codeImpl['protocol'] . '{$piwikUrl}/";'; + + if (!empty($codeImpl['httpsPiwikUrl'])) { + $setTrackerUrl = 'var u=((document.location.protocol === "https:") ? "https://{$httpsPiwikUrl}/" : "http://{$piwikUrl}/");'; + $codeImpl['httpsPiwikUrl'] = rtrim($codeImpl['httpsPiwikUrl'], "/"); + } + $codeImpl = array('setTrackerUrl' => htmlentities($setTrackerUrl)) + $codeImpl; + + foreach ($codeImpl as $keyToReplace => $replaceWith) { + $jsCode = str_replace('{$' . $keyToReplace . '}', $replaceWith, $jsCode); + } + + return $jsCode; + } + + private function getJavascriptTagOptions($idSite, $mergeSubdomains, $mergeAliasUrls) + { + try { + $websiteUrls = APISitesManager::getInstance()->getSiteUrlsFromId($idSite); + } catch (\Exception $e) { + return ''; + } + // We need to parse_url to isolate hosts + $websiteHosts = array(); + $firstHost = null; + foreach ($websiteUrls as $site_url) { + $referrerParsed = parse_url($site_url); + + if (!isset($firstHost)) { + $firstHost = $referrerParsed['host']; + } + + $url = $referrerParsed['host']; + if (!empty($referrerParsed['path'])) { + $url .= $referrerParsed['path']; + } + $websiteHosts[] = $url; + } + $options = ''; + if ($mergeSubdomains && !empty($firstHost)) { + $options .= ' _paq.push(["setCookieDomain", "*.' . $firstHost . '"]);' . "\n"; + } + if ($mergeAliasUrls && !empty($websiteHosts)) { + $urls = '["*.' . implode('","*.', $websiteHosts) . '"]'; + $options .= ' _paq.push(["setDomains", ' . $urls . ']);' . "\n"; + } + return $options; + } +} diff --git a/www/analytics/core/Tracker/TrackerConfig.php b/www/analytics/core/Tracker/TrackerConfig.php new file mode 100644 index 00000000..537dc8f0 --- /dev/null +++ b/www/analytics/core/Tracker/TrackerConfig.php @@ -0,0 +1,39 @@ +Tracker = $section; + } + + public static function getConfigValue($name) + { + $config = self::getConfig(); + return $config[$name]; + } + + private static function getConfig() + { + return Config::getInstance()->Tracker; + } +} diff --git a/www/analytics/core/Tracker/Visit.php b/www/analytics/core/Tracker/Visit.php index 1cd7d7c9..80d8d136 100644 --- a/www/analytics/core/Tracker/Visit.php +++ b/www/analytics/core/Tracker/Visit.php @@ -1,6 +1,6 @@ requestProcessors = $requestProcessors->getRequestProcessors(); + $this->visitorRecognizer = StaticContainer::get('Piwik\Tracker\VisitorRecognizer'); + $this->visitProperties = null; + $this->userSettings = StaticContainer::get('Piwik\Tracker\Settings'); + $this->invalidator = StaticContainer::get('Piwik\Archive\ArchiveInvalidator'); + } /** * @param Request $request @@ -78,73 +115,42 @@ class Visit implements VisitInterface */ public function handle() { - // the IP is needed by isExcluded() and GoalManager->recordGoals() - $ip = $this->request->getIp(); - $this->visitorInfo['location_ip'] = $ip; + foreach ($this->requestProcessors as $processor) { + Common::printDebug("Executing " . get_class($processor) . "::manipulateRequest()..."); - $excluded = new VisitExcluded($this->request, $ip); - if ($excluded->isExcluded()) { - return; + $processor->manipulateRequest($this->request); } - /** - * Triggered after visits are tested for exclusion so plugins can modify the IP address - * persisted with a visit. - * - * This event is primarily used by the **PrivacyManager** plugin to anonymize IP addresses. - * - * @param string &$ip The visitor's IP address. - */ - Piwik::postEvent('Tracker.setVisitorIp', array(&$this->visitorInfo['location_ip'])); + $this->visitProperties = new VisitProperties(); - $this->visitorCustomVariables = $this->request->getCustomVariables($scope = 'visit'); - if (!empty($this->visitorCustomVariables)) { - Common::printDebug("Visit level Custom Variables: "); - Common::printDebug($this->visitorCustomVariables); - } + foreach ($this->requestProcessors as $processor) { + Common::printDebug("Executing " . get_class($processor) . "::processRequestParams()..."); - $this->goalManager = new GoalManager($this->request); - - $visitIsConverted = false; - $action = null; - - $requestIsManualGoalConversion = ($this->goalManager->idGoal > 0); - $requestIsEcommerce = $this->goalManager->requestIsEcommerce; - if ($requestIsEcommerce) { - $someGoalsConverted = true; - - // Mark the visit as Converted only if it is an order (not for a Cart update) - if ($this->goalManager->isGoalAnOrder) { - $visitIsConverted = true; + $abort = $processor->processRequestParams($this->visitProperties, $this->request); + if ($abort) { + Common::printDebug("-> aborting due to processRequestParams method"); + return; } - } // this request is from the JS call to piwikTracker.trackGoal() - elseif ($requestIsManualGoalConversion) { - $someGoalsConverted = $this->goalManager->detectGoalId($this->request->getIdSite()); - $visitIsConverted = $someGoalsConverted; - // if we find a idgoal in the URL, but then the goal is not valid, this is most likely a fake request - if (!$someGoalsConverted) { - throw new \Exception('Invalid goal tracking request for goal id = ' . $this->goalManager->idGoal); + } + + $isNewVisit = $this->request->getMetadata('CoreHome', 'isNewVisit'); + if (!$isNewVisit) { + $isNewVisit = $this->triggerPredicateHookOnDimensions($this->getAllVisitDimensions(), 'shouldForceNewVisit'); + $this->request->setMetadata('CoreHome', 'isNewVisit', $isNewVisit); + } + + foreach ($this->requestProcessors as $processor) { + Common::printDebug("Executing " . get_class($processor) . "::afterRequestProcessed()..."); + + $abort = $processor->afterRequestProcessed($this->visitProperties, $this->request); + if ($abort) { + Common::printDebug("-> aborting due to afterRequestProcessed method"); + return; } - } // normal page view, potentially triggering a URL matching goal - else { - $action = Action::factory($this->request); - - $action->writeDebugInfo(); - - $someGoalsConverted = $this->goalManager->detectGoalsMatchingUrl($this->request->getIdSite(), $action); - $visitIsConverted = $someGoalsConverted; - - $action->loadIdsFromLogActionTable(); } - // the visitor and session - $this->recognizeTheVisitor(); + $isNewVisit = $this->request->getMetadata('CoreHome', 'isNewVisit'); - $isLastActionInTheSameVisit = $this->isLastActionInTheSameVisit(); - - if (!$isLastActionInTheSameVisit) { - Common::printDebug("Visitor detected, but last action was more than 30 minutes ago..."); - } // Known visit when: // ( - the visitor has the Piwik cookie with the idcookie ID used by Piwik to match the visitor // OR @@ -152,38 +158,11 @@ class Visit implements VisitInterface // ) // AND // - the last page view for this visitor was less than 30 minutes ago @see isLastActionInTheSameVisit() - if ($this->isVisitorKnown() - && $isLastActionInTheSameVisit - ) { - $idReferrerActionUrl = $this->visitorInfo['visit_exit_idaction_url']; - $idReferrerActionName = $this->visitorInfo['visit_exit_idaction_name']; + if (!$isNewVisit) { try { - $this->handleExistingVisit($action, $visitIsConverted); - if (!is_null($action)) { - $action->record($this->visitorInfo['idvisit'], - $this->visitorInfo['idvisitor'], - $idReferrerActionUrl, - $idReferrerActionName, - $this->visitorInfo['time_spent_ref_action'] - ); - } + $this->handleExistingVisit($this->request->getMetadata('Goals', 'visitIsConverted')); } catch (VisitorNotFoundInDb $e) { - - // There is an edge case when: - // - two manual goal conversions happen in the same second - // - which result in handleExistingVisit throwing the exception - // because the UPDATE didn't affect any rows (one row was found, but not updated since no field changed) - // - the exception is caught here and will result in a new visit incorrectly - // In this case, we cancel the current conversion to be recorded: - if ($requestIsManualGoalConversion - || $requestIsEcommerce - ) { - $someGoalsConverted = $visitIsConverted = false; - } // When the row wasn't found in the logs, and this is a pageview or - // goal matching URL, we force a new visitor - else { - $this->visitorKnown = false; - } + $this->request->setMetadata('CoreHome', 'visitorNotFoundInDb', true); // TODO: perhaps we should just abort here? } } @@ -191,29 +170,20 @@ class Visit implements VisitInterface // - the visitor has the Piwik cookie but the last action was performed more than 30 min ago @see isLastActionInTheSameVisit() // - the visitor doesn't have the Piwik cookie, and couldn't be matched in @see recognizeTheVisitor() // - the visitor does have the Piwik cookie but the idcookie and idvisit found in the cookie didn't match to any existing visit in the DB - if (!$this->isVisitorKnown() - || !$isLastActionInTheSameVisit - ) { - $this->handleNewVisit($action, $visitIsConverted); - if (!is_null($action)) { - $action->record($this->visitorInfo['idvisit'], $this->visitorInfo['idvisitor'], 0, 0, 0); - } + if ($isNewVisit) { + $this->handleNewVisit($this->request->getMetadata('Goals', 'visitIsConverted')); } // update the cookie with the new visit information - $this->request->setThirdPartyCookie($this->visitorInfo['idvisitor']); + $this->request->setThirdPartyCookie($this->visitProperties->getProperty('idvisitor')); - // record the goals if applicable - if ($someGoalsConverted) { - $this->goalManager->recordGoals( - $this->request->getIdSite(), - $this->visitorInfo, - $this->visitorCustomVariables, - $action - ); + foreach ($this->requestProcessors as $processor) { + Common::printDebug("Executing " . get_class($processor) . "::recordLogs()..."); + + $processor->recordLogs($this->visitProperties, $this->request); } - unset($this->goalManager); - unset($action); + + $this->markArchivedReportsAsInvalidIfArchiveAlreadyFinished(); } /** @@ -222,39 +192,48 @@ class Visit implements VisitInterface * 1) Insert the new action * 2) Update the visit information * + * @param Visitor $visitor * @param Action $action * @param $visitIsConverted * @throws VisitorNotFoundInDb */ - protected function handleExistingVisit($action, $visitIsConverted) + protected function handleExistingVisit($visitIsConverted) { - Common::printDebug("Visit is known (IP = " . IP::N2P($this->getVisitorIp()) . ")"); + Common::printDebug("Visit is known (IP = " . IPUtils::binaryToStringIP($this->getVisitorIp()) . ")"); - $valuesToUpdate = $this->getExistingVisitFieldsToUpdate($action, $visitIsConverted); + // TODO it should be its own dimension + $this->visitProperties->setProperty('time_spent_ref_action', $this->getTimeSpentReferrerAction()); - $this->visitorInfo['time_spent_ref_action'] = $this->getTimeSpentReferrerAction(); - - $this->request->overrideLocation($valuesToUpdate); + $valuesToUpdate = $this->getExistingVisitFieldsToUpdate($visitIsConverted); // update visitorInfo - foreach ($valuesToUpdate AS $name => $value) { - $this->visitorInfo[$name] = $value; + foreach ($valuesToUpdate as $name => $value) { + $this->visitProperties->setProperty($name, $value); } /** * Triggered before a [visit entity](/guides/persistence-and-the-mysql-backend#visits) is updated when * tracking an action for an existing visit. - * + * * This event can be used to modify the visit properties that will be updated before the changes * are persisted. - * + * + * This event is deprecated, use [Dimensions](http://developer.piwik.org/guides/dimensions) instead. + * * @param array &$valuesToUpdate Visit entity properties that will be updated. * @param array $visit The entire visit entity. Read [this](/guides/persistence-and-the-mysql-backend#visits) * to see what it contains. + * @deprecated */ - Piwik::postEvent('Tracker.existingVisitInformation', array(&$valuesToUpdate, $this->visitorInfo)); + Piwik::postEvent('Tracker.existingVisitInformation', array(&$valuesToUpdate, $this->visitProperties->getProperties())); + + foreach ($this->requestProcessors as $processor) { + $processor->onExistingVisit($valuesToUpdate, $this->visitProperties, $this->request); + } $this->updateExistingVisit($valuesToUpdate); + + $this->visitProperties->setProperty('visit_last_action_time', $this->request->getCurrentTimestamp()); } /** @@ -262,12 +241,15 @@ class Visit implements VisitInterface */ protected function getTimeSpentReferrerAction() { - $timeSpent = $this->request->getCurrentTimestamp() - $this->visitorInfo['visit_last_action_time']; - if ($timeSpent < 0 - || $timeSpent > Config::getInstance()->Tracker['visit_standard_length'] - ) { + $timeSpent = $this->request->getCurrentTimestamp() - + $this->visitProperties->getProperty('visit_last_action_time'); + if ($timeSpent < 0) { $timeSpent = 0; } + $visitStandardLength = $this->getVisitStandardLength(); + if ($timeSpent > $visitStandardLength) { + $timeSpent = $visitStandardLength; + } return $timeSpent; } @@ -278,58 +260,58 @@ class Visit implements VisitInterface * * 2) Insert the visit information * + * @param Visitor $visitor * @param Action $action * @param bool $visitIsConverted */ - protected function handleNewVisit($action, $visitIsConverted) + protected function handleNewVisit($visitIsConverted) { - Common::printDebug("New Visit (IP = " . IP::N2P($this->getVisitorIp()) . ")"); + Common::printDebug("New Visit (IP = " . IPUtils::binaryToStringIP($this->getVisitorIp()) . ")"); - $this->visitorInfo = $this->getNewVisitorInformation($action); + $this->setNewVisitorInformation(); - // Add Custom variable key,value to the visitor array - $this->visitorInfo = array_merge($this->visitorInfo, $this->visitorCustomVariables); + $dimensions = $this->getAllVisitDimensions(); - $this->visitorInfo['visit_goal_converted'] = $visitIsConverted ? 1 : 0; + $this->triggerHookOnDimensions($dimensions, 'onNewVisit'); - $this->visitorInfo['referer_name'] = substr($this->visitorInfo['referer_name'], 0, 70); - $this->visitorInfo['referer_keyword'] = substr($this->visitorInfo['referer_keyword'], 0, 255); - $this->visitorInfo['config_resolution'] = substr($this->visitorInfo['config_resolution'], 0, 9); + if ($visitIsConverted) { + $this->triggerHookOnDimensions($dimensions, 'onConvertedVisit'); + } + + $properties = &$this->visitProperties->getProperties(); /** * Triggered before a new [visit entity](/guides/persistence-and-the-mysql-backend#visits) is persisted. - * + * * This event can be used to modify the visit entity or add new information to it before it is persisted. * The UserCountry plugin, for example, uses this event to add location information for each visit. * + * This event is deprecated, use [Dimensions](http://developer.piwik.org/guides/dimensions) instead. + * * @param array &$visit The visit entity. Read [this](/guides/persistence-and-the-mysql-backend#visits) to see * what information it contains. * @param \Piwik\Tracker\Request $request An object describing the tracking request being processed. + * + * @deprecated */ - Piwik::postEvent('Tracker.newVisitorInformation', array(&$this->visitorInfo, $this->request)); + Piwik::postEvent('Tracker.newVisitorInformation', array(&$properties, $this->request)); + + foreach ($this->requestProcessors as $processor) { + $processor->onNewVisit($this->visitProperties, $this->request); + } - $this->request->overrideLocation($this->visitorInfo); $this->printVisitorInformation(); - $idVisit = $this->insertNewVisit( $this->visitorInfo ); - - $this->visitorInfo['idvisit'] = $idVisit; - $this->visitorInfo['visit_first_action_time'] = $this->request->getCurrentTimestamp(); - $this->visitorInfo['visit_last_action_time'] = $this->request->getCurrentTimestamp(); + $idVisit = $this->insertNewVisit($this->visitProperties->getProperties()); + $this->visitProperties->setProperty('idvisit', $idVisit); + $this->visitProperties->setProperty('visit_first_action_time', $this->request->getCurrentTimestamp()); + $this->visitProperties->setProperty('visit_last_action_time', $this->request->getCurrentTimestamp()); } - static private function cleanupVisitTotalTime($t) + private function getModel() { - $t = (int)$t; - if ($t < 0) { - $t = 0; - } - $smallintMysqlLimit = 65534; - if ($t > $smallintMysqlLimit) { - $t = $smallintMysqlLimit; - } - return $t; + return new Model(); } /** @@ -339,25 +321,28 @@ class Visit implements VisitInterface */ protected function getVisitorIdcookie() { - if ($this->isVisitorKnown()) { - return $this->visitorInfo['idvisitor']; + $isKnown = $this->request->getMetadata('CoreHome', 'isVisitorKnown'); + if ($isKnown) { + return $this->visitProperties->getProperty('idvisitor'); } + // If the visitor had a first party ID cookie, then we use this value - if (!empty($this->visitorInfo['idvisitor']) - && strlen($this->visitorInfo['idvisitor']) == Tracker::LENGTH_BINARY_ID + $idVisitor = $this->visitProperties->getProperty('idvisitor'); + if (!empty($idVisitor) + && Tracker::LENGTH_BINARY_ID == strlen($this->visitProperties->getProperty('idvisitor')) ) { - return $this->visitorInfo['idvisitor']; + return $this->visitProperties->getProperty('idvisitor'); } + return Common::hex2bin($this->generateUniqueVisitorId()); } /** * @return string returns random 16 chars hex string */ - static public function generateUniqueVisitorId() + public static function generateUniqueVisitorId() { - $uniqueId = substr(Common::generateUniqId(), 0, Tracker::LENGTH_HEX_ID_STRING); - return $uniqueId; + return substr(Common::generateUniqId(), 0, Tracker::LENGTH_HEX_ID_STRING); } /** @@ -367,378 +352,43 @@ class Visit implements VisitInterface */ protected function getVisitorIp() { - return $this->visitorInfo['location_ip']; + return $this->visitProperties->getProperty('location_ip'); } /** - * This methods tries to see if the visitor has visited the website before. + * Gets the UserSettings object * - * We have to split the visitor into one of the category - * - Known visitor - * - New visitor + * @return Settings */ - protected function recognizeTheVisitor() + protected function getSettingsObject() { - $this->visitorKnown = false; - - $userInfo = $this->getUserSettingsInformation(); - $configId = $userInfo['config_id']; - - $idVisitor = $this->request->getVisitorId(); - $isVisitorIdToLookup = !empty($idVisitor); - - if ($isVisitorIdToLookup) { - $this->visitorInfo['idvisitor'] = $idVisitor; - Common::printDebug("Matching visitors with: visitorId=" . bin2hex($this->visitorInfo['idvisitor']) . " OR configId=" . bin2hex($configId)); - } else { - Common::printDebug("Visitor doesn't have the piwik cookie..."); - } - - $selectCustomVariables = ''; - // No custom var were found in the request, so let's copy the previous one in a potential conversion later - if (!$this->visitorCustomVariables) { - $maxCustomVariables = CustomVariables::getMaxCustomVariables(); - - for ($index = 1; $index <= $maxCustomVariables; $index++) { - $selectCustomVariables .= ', custom_var_k' . $index . ', custom_var_v' . $index; - } - } - - $persistedVisitAttributes = $this->getVisitFieldsPersist(); - - $selectFields = implode(", ", $persistedVisitAttributes); - - $select = "SELECT - visit_last_action_time, - visit_first_action_time, - $selectFields - $selectCustomVariables - "; - $from = "FROM " . Common::prefixTable('log_visit'); - - list($timeLookBack, $timeLookAhead) = $this->getWindowLookupThisVisit(); - - $shouldMatchOneFieldOnly = $this->shouldLookupOneVisitorFieldOnly($isVisitorIdToLookup); - - // Two use cases: - // 1) there is no visitor ID so we try to match only on config_id (heuristics) - // Possible causes of no visitor ID: no browser cookie support, direct Tracking API request without visitor ID passed, - // importing server access logs with import_logs.py, etc. - // In this case we use config_id heuristics to try find the visitor in tahhhe past. There is a risk to assign - // this page view to the wrong visitor, but this is better than creating artificial visits. - // 2) there is a visitor ID and we trust it (config setting trust_visitors_cookies, OR it was set using &cid= in tracking API), - // and in these cases, we force to look up this visitor id - $whereCommon = "visit_last_action_time >= ? AND visit_last_action_time <= ? AND idsite = ?"; - $bindSql = array( - $timeLookBack, - $timeLookAhead, - $this->request->getIdSite() - ); - - if ($shouldMatchOneFieldOnly) { - if ($isVisitorIdToLookup) { - $whereCommon .= ' AND idvisitor = ?'; - $bindSql[] = $this->visitorInfo['idvisitor']; - } else { - $whereCommon .= ' AND config_id = ?'; - $bindSql[] = $configId; - } - - $sql = "$select - $from - WHERE " . $whereCommon . " - ORDER BY visit_last_action_time DESC - LIMIT 1"; - } // We have a config_id AND a visitor_id. We match on either of these. - // Why do we also match on config_id? - // we do not trust the visitor ID only. Indeed, some browsers, or browser addons, - // cause the visitor id from the 1st party cookie to be different on each page view! - // It is not acceptable to create a new visit every time such browser does a page view, - // so we also backup by searching for matching config_id. - // We use a UNION here so that each sql query uses its own INDEX - else { - // will use INDEX index_idsite_config_datetime (idsite, config_id, visit_last_action_time) - $where = ' AND config_id = ?'; - $bindSql[] = $configId; - $sqlConfigId = "$select , - 0 as priority - $from - WHERE $whereCommon $where - ORDER BY visit_last_action_time DESC - LIMIT 1 - "; - - // will use INDEX index_idsite_idvisitor (idsite, idvisitor) - $bindSql[] = $timeLookBack; - $bindSql[] = $timeLookAhead; - $bindSql[] = $this->request->getIdSite(); - $where = ' AND idvisitor = ?'; - $bindSql[] = $this->visitorInfo['idvisitor']; - $sqlVisitorId = "$select , - 1 as priority - $from - WHERE $whereCommon $where - ORDER BY visit_last_action_time DESC - LIMIT 1 - "; - - // We join both queries and favor the one matching the visitor_id if it did match - $sql = " ( $sqlConfigId ) - UNION - ( $sqlVisitorId ) - ORDER BY priority DESC - LIMIT 1"; - } - - $visitRow = Tracker::getDatabase()->fetch($sql, $bindSql); - - $isNewVisitForced = $this->request->getParam('new_visit'); - $isNewVisitForced = !empty($isNewVisitForced); - $newVisitEnforcedAPI = $isNewVisitForced - && ($this->request->isAuthenticated() - || !Config::getInstance()->Tracker['new_visit_api_requires_admin']); - $enforceNewVisit = $newVisitEnforcedAPI || Config::getInstance()->Debug['tracker_always_new_visitor']; - - if (!$enforceNewVisit - && $visitRow - && count($visitRow) > 0 - ) { - // These values will be used throughout the request - $this->visitorInfo['visit_last_action_time'] = strtotime($visitRow['visit_last_action_time']); - $this->visitorInfo['visit_first_action_time'] = strtotime($visitRow['visit_first_action_time']); - - foreach($persistedVisitAttributes as $field) { - $this->visitorInfo[$field] = $visitRow[$field]; - } - - // Custom Variables copied from Visit in potential later conversion - if (!empty($selectCustomVariables)) { - $maxCustomVariables = CustomVariables::getMaxCustomVariables(); - for ($i = 1; $i <= $maxCustomVariables; $i++) { - if (isset($visitRow['custom_var_k' . $i]) - && strlen($visitRow['custom_var_k' . $i]) - ) { - $this->visitorInfo['custom_var_k' . $i] = $visitRow['custom_var_k' . $i]; - } - if (isset($visitRow['custom_var_v' . $i]) - && strlen($visitRow['custom_var_v' . $i]) - ) { - $this->visitorInfo['custom_var_v' . $i] = $visitRow['custom_var_v' . $i]; - } - } - } - - $this->visitorKnown = true; - Common::printDebug("The visitor is known (idvisitor = " . bin2hex($this->visitorInfo['idvisitor']) . ", - config_id = " . bin2hex($configId) . ", - idvisit = {$this->visitorInfo['idvisit']}, - last action = " . date("r", $this->visitorInfo['visit_last_action_time']) . ", - first action = " . date("r", $this->visitorInfo['visit_first_action_time']) . ", - visit_goal_buyer' = " . $this->visitorInfo['visit_goal_buyer'] . ")"); - //Common::printDebug($this->visitorInfo); - } else { - Common::printDebug("The visitor was not matched with an existing visitor..."); - } - } - - /** - * By default, we look back 30 minutes to find a previous visitor (for performance reasons). - * In some cases, it is useful to look back and count unique visitors more accurately. You can set custom lookback window in - * [Tracker] window_look_back_for_visitor - * - * The returned value is the window range (Min, max) that the matched visitor should fall within - * - * @return array( datetimeMin, datetimeMax ) - */ - protected function getWindowLookupThisVisit() - { - $visitStandardLength = Config::getInstance()->Tracker['visit_standard_length']; - $lookBackNSecondsCustom = Config::getInstance()->Tracker['window_look_back_for_visitor']; - - $lookAheadNSeconds = $visitStandardLength; - $lookBackNSeconds = $visitStandardLength; - if ($lookBackNSecondsCustom > $lookBackNSeconds) { - $lookBackNSeconds = $lookBackNSecondsCustom; - } - - $timeLookBack = date('Y-m-d H:i:s', $this->request->getCurrentTimestamp() - $lookBackNSeconds); - $timeLookAhead = date('Y-m-d H:i:s', $this->request->getCurrentTimestamp() + $lookAheadNSeconds); - - return array($timeLookBack, $timeLookAhead); - } - - protected function shouldLookupOneVisitorFieldOnly($isVisitorIdToLookup) - { - // This setting would be enabled for Intranet websites, to ensure that visitors using all the same computer config, same IP - // are not counted as 1 visitor. In this case, we want to enforce and trust the visitor ID from the cookie. - $trustCookiesOnly = Config::getInstance()->Tracker['trust_visitors_cookies']; - - // If a &cid= was set, we force to select this visitor (or create a new one) - $isForcedVisitorIdMustMatch = ($this->request->getForcedVisitorId() != null); - - $shouldMatchOneFieldOnly = (($isVisitorIdToLookup && $trustCookiesOnly) - || $isForcedVisitorIdMustMatch - || !$isVisitorIdToLookup); - return $shouldMatchOneFieldOnly; - } - - /** - * Gets the UserSettings information and returns them in an array of name => value - * - * @return array - */ - protected function getUserSettingsInformation() - { - // we already called this method before, simply returns the result - if (is_array($this->userSettingsInformation)) { - return $this->userSettingsInformation; - } - - list($plugin_Flash, $plugin_Java, $plugin_Director, $plugin_Quicktime, $plugin_RealPlayer, $plugin_PDF, - $plugin_WindowsMedia, $plugin_Gears, $plugin_Silverlight, $plugin_Cookie) = $this->request->getPlugins(); - - $resolution = $this->request->getParam('res'); - $userAgent = $this->request->getUserAgent(); - - $deviceDetector = new DeviceDetector($userAgent); - $deviceDetector->parse(); - $aBrowserInfo = $deviceDetector->getBrowser(); - - $browserName = !empty($aBrowserInfo['short_name']) ? $aBrowserInfo['short_name'] : 'UNK'; - $browserVersion = !empty($aBrowserInfo['version']) ? $aBrowserInfo['version'] : ''; - - $os = $deviceDetector->getOS(); - $os = empty($os['short_name']) ? 'UNK' : $os['short_name']; - - $browserLang = substr($this->request->getBrowserLanguage(), 0, 20); // limit the length of this string to match db - $configurationHash = $this->getConfigHash( - $os, - $browserName, - $browserVersion, - $plugin_Flash, - $plugin_Java, - $plugin_Director, - $plugin_Quicktime, - $plugin_RealPlayer, - $plugin_PDF, - $plugin_WindowsMedia, - $plugin_Gears, - $plugin_Silverlight, - $plugin_Cookie, - $this->getVisitorIp(), - $browserLang); - - $this->userSettingsInformation = array( - 'config_id' => $configurationHash, - 'config_os' => $os, - 'config_browser_name' => $browserName, - 'config_browser_version' => $browserVersion, - 'config_resolution' => $resolution, - 'config_pdf' => $plugin_PDF, - 'config_flash' => $plugin_Flash, - 'config_java' => $plugin_Java, - 'config_director' => $plugin_Director, - 'config_quicktime' => $plugin_Quicktime, - 'config_realplayer' => $plugin_RealPlayer, - 'config_windowsmedia' => $plugin_WindowsMedia, - 'config_gears' => $plugin_Gears, - 'config_silverlight' => $plugin_Silverlight, - 'config_cookie' => $plugin_Cookie, - 'location_browser_lang' => $browserLang, - ); - return $this->userSettingsInformation; - } - - /** - * Returns true if the last action was done during the last 30 minutes - * @return bool - */ - protected function isLastActionInTheSameVisit() - { - return isset($this->visitorInfo['visit_last_action_time']) - && ($this->visitorInfo['visit_last_action_time'] - > ($this->request->getCurrentTimestamp() - Config::getInstance()->Tracker['visit_standard_length'])); - } - - /** - * Returns true if the recognizeTheVisitor() method did recognize the visitor - * @return bool - */ - protected function isVisitorKnown() - { - return $this->visitorKnown === true; - } - - /** - * Returns a 64-bit hash of all the configuration settings - * @param $os - * @param $browserName - * @param $browserVersion - * @param $plugin_Flash - * @param $plugin_Java - * @param $plugin_Director - * @param $plugin_Quicktime - * @param $plugin_RealPlayer - * @param $plugin_PDF - * @param $plugin_WindowsMedia - * @param $plugin_Gears - * @param $plugin_Silverlight - * @param $plugin_Cookie - * @param $ip - * @param $browserLang - * @return string - */ - protected function getConfigHash($os, $browserName, $browserVersion, $plugin_Flash, $plugin_Java, $plugin_Director, $plugin_Quicktime, $plugin_RealPlayer, $plugin_PDF, $plugin_WindowsMedia, $plugin_Gears, $plugin_Silverlight, $plugin_Cookie, $ip, $browserLang) - { - $hash = md5($os . $browserName . $browserVersion . $plugin_Flash . $plugin_Java . $plugin_Director . $plugin_Quicktime . $plugin_RealPlayer . $plugin_PDF . $plugin_WindowsMedia . $plugin_Gears . $plugin_Silverlight . $plugin_Cookie . $ip . $browserLang, $raw_output = true); - return substr($hash, 0, Tracker::LENGTH_BINARY_ID); - } - - /** - * Returns either - * - "-1" for a known visitor - * - at least 16 char identifier in hex @see Common::generateUniqId() - * @return int|string - */ - protected function getVisitorUniqueId() - { - if ($this->isVisitorKnown()) { - return -1; - } - return Common::generateUniqId(); + return $this->userSettings; } // is the referrer host any of the registered URLs for this website? - static public function isHostKnownAliasHost($urlHost, $idSite) + public static function isHostKnownAliasHost($urlHost, $idSite) { $websiteData = Cache::getCacheWebsiteAttributes($idSite); + if (isset($websiteData['hosts'])) { $canonicalHosts = array(); foreach ($websiteData['hosts'] as $host) { - $canonicalHosts[] = str_replace('www.', '', mb_strtolower($host, 'UTF-8')); + $canonicalHosts[] = self::toCanonicalHost($host); } - $canonicalHost = str_replace('www.', '', mb_strtolower($urlHost, 'UTF-8')); + + $canonicalHost = self::toCanonicalHost($urlHost); if (in_array($canonicalHost, $canonicalHosts)) { return true; } } + return false; } - /** - * @return mixed - */ - protected function insertNewVisit($visit) + private static function toCanonicalHost($host) { - $fields = implode(", ", array_keys($visit)); - $values = Common::getSqlStringFieldsArray($visit); - - $sql = "INSERT INTO " . Common::prefixTable('log_visit') . " ($fields) VALUES ($values)"; - $bind = array_values($visit); - Tracker::getDatabase()->query($sql, $bind); - - $idVisit = Tracker::getDatabase()->lastInsertId(); - return $idVisit; + $hostLower = Common::mb_strtolower($host); + return str_replace('www.', '', $hostLower); } /** @@ -747,264 +397,208 @@ class Visit implements VisitInterface */ protected function updateExistingVisit($valuesToUpdate) { - $sqlQuery = "UPDATE " . Common::prefixTable('log_visit') . " - SET %s - WHERE idsite = ? - AND idvisit = ?"; - // build sql query - $updateParts = $sqlBind = array(); - foreach ($valuesToUpdate AS $name => $value) { - // Case where bind parameters don't work - if(strpos($value, $name) !== false) { - //$name = 'visit_total_events' - //$value = 'visit_total_events + 1'; - $updateParts[] = " $name = $value "; - } else { - $updateParts[] = $name . " = ?"; - $sqlBind[] = $value; - } - } - $sqlQuery = sprintf($sqlQuery, implode($updateParts, ', ') ); - array_push($sqlBind, $this->request->getIdSite(), (int)$this->visitorInfo['idvisit']); + $idSite = $this->request->getIdSite(); + $idVisit = (int)$this->visitProperties->getProperty('idvisit'); - $result = Tracker::getDatabase()->query($sqlQuery, $sqlBind); - - $this->visitorInfo['visit_last_action_time'] = $this->request->getCurrentTimestamp(); + $wasInserted = $this->getModel()->updateVisit($idSite, $idVisit, $valuesToUpdate); // Debug output if (isset($valuesToUpdate['idvisitor'])) { $valuesToUpdate['idvisitor'] = bin2hex($valuesToUpdate['idvisitor']); } - Common::printDebug('Updating existing visit: ' . var_export($valuesToUpdate, true)); - if (Tracker::getDatabase()->rowCount($result) == 0) { - Common::printDebug("Visitor with this idvisit wasn't found in the DB."); - Common::printDebug("$sqlQuery --- "); - Common::printDebug($sqlBind); + if ($wasInserted) { + Common::printDebug('Updated existing visit: ' . var_export($valuesToUpdate, true)); + } else { throw new VisitorNotFoundInDb( - "The visitor with idvisitor=" . bin2hex($this->visitorInfo['idvisitor']) . " and idvisit=" . $this->visitorInfo['idvisit'] + "The visitor with idvisitor=" . bin2hex($this->visitProperties->getProperty('idvisitor')) + . " and idvisit=" . @$this->visitProperties->getProperty('idvisit') . " wasn't found in the DB, we fallback to a new visitor"); } } - protected function printVisitorInformation() + private function printVisitorInformation() { - $debugVisitInfo = $this->visitorInfo; + $debugVisitInfo = $this->visitProperties->getProperties(); $debugVisitInfo['idvisitor'] = bin2hex($debugVisitInfo['idvisitor']); $debugVisitInfo['config_id'] = bin2hex($debugVisitInfo['config_id']); + $debugVisitInfo['location_ip'] = IPUtils::binaryToStringIP($debugVisitInfo['location_ip']); Common::printDebug($debugVisitInfo); } - protected function getNewVisitorInformation($action) + private function setNewVisitorInformation() { - $actionType = $idActionName = $idActionUrl = false; - if($action) { - $idActionUrl = $action->getIdActionUrlForEntryAndExitIds(); - $idActionName = $action->getIdActionNameForEntryAndExitIds(); - $actionType = $action->getActionType(); - } + $idVisitor = $this->getVisitorIdcookie(); + $visitorIp = $this->getVisitorIp(); + $configId = $this->request->getMetadata('CoreHome', 'visitorId'); - $daysSinceFirstVisit = $this->request->getDaysSinceFirstVisit(); - $visitCount = $this->request->getVisitCount(); - $daysSinceLastVisit = $this->request->getDaysSinceLastVisit(); + $this->visitProperties->clearProperties(); - $daysSinceLastOrder = $this->request->getDaysSinceLastOrder(); - $isReturningCustomer = ($daysSinceLastOrder !== false); - - if ($daysSinceLastOrder === false) { - $daysSinceLastOrder = 0; - } - - // User settings - $userInfo = $this->getUserSettingsInformation(); - - // Referrer data - $referrer = new Referrer(); - $referrerUrl = $this->request->getParam('urlref'); - $currentUrl = $this->request->getParam('url'); - $referrerInfo = $referrer->getReferrerInformation($referrerUrl, $currentUrl, $this->request->getIdSite()); - - $visitorReturning = $isReturningCustomer - ? 2 /* Returning customer */ - : ($visitCount > 1 || $this->isVisitorKnown() || $daysSinceLastVisit > 0 - ? 1 /* Returning */ - : 0 /* New */); - $defaultTimeOnePageVisit = Config::getInstance()->Tracker['default_time_one_page_visit']; - - return array( - 'idsite' => $this->request->getIdSite(), - 'visitor_localtime' => $this->request->getLocalTime(), - 'idvisitor' => $this->getVisitorIdcookie(), - 'visitor_returning' => $visitorReturning, - 'visitor_count_visits' => $visitCount, - 'visitor_days_since_last' => $daysSinceLastVisit, - 'visitor_days_since_order' => $daysSinceLastOrder, - 'visitor_days_since_first' => $daysSinceFirstVisit, - 'visit_first_action_time' => Tracker::getDatetimeFromTimestamp($this->request->getCurrentTimestamp()), - 'visit_last_action_time' => Tracker::getDatetimeFromTimestamp($this->request->getCurrentTimestamp()), - 'visit_entry_idaction_url' => (int)$idActionUrl, - 'visit_entry_idaction_name' => (int)$idActionName, - 'visit_exit_idaction_url' => (int)$idActionUrl, - 'visit_exit_idaction_name' => (int)$idActionName, - 'visit_total_actions' => in_array($actionType, - array(Action::TYPE_PAGE_URL, - Action::TYPE_DOWNLOAD, - Action::TYPE_OUTLINK, - Action::TYPE_SITE_SEARCH, - Action::TYPE_EVENT)) - ? 1 : 0, // if visit starts with something else (e.g. ecommerce order), don't record as an action - 'visit_total_searches' => $actionType == Action::TYPE_SITE_SEARCH ? 1 : 0, - 'visit_total_events' => $actionType == Action::TYPE_EVENT ? 1 : 0, - 'visit_total_time' => self::cleanupVisitTotalTime($defaultTimeOnePageVisit), - 'visit_goal_buyer' => $this->goalManager->getBuyerType(), - 'referer_type' => $referrerInfo['referer_type'], - 'referer_name' => $referrerInfo['referer_name'], - 'referer_url' => $referrerInfo['referer_url'], - 'referer_keyword' => $referrerInfo['referer_keyword'], - 'config_id' => $userInfo['config_id'], - 'config_os' => $userInfo['config_os'], - 'config_browser_name' => $userInfo['config_browser_name'], - 'config_browser_version' => $userInfo['config_browser_version'], - 'config_resolution' => $userInfo['config_resolution'], - 'config_pdf' => $userInfo['config_pdf'], - 'config_flash' => $userInfo['config_flash'], - 'config_java' => $userInfo['config_java'], - 'config_director' => $userInfo['config_director'], - 'config_quicktime' => $userInfo['config_quicktime'], - 'config_realplayer' => $userInfo['config_realplayer'], - 'config_windowsmedia' => $userInfo['config_windowsmedia'], - 'config_gears' => $userInfo['config_gears'], - 'config_silverlight' => $userInfo['config_silverlight'], - 'config_cookie' => $userInfo['config_cookie'], - 'location_ip' => $this->getVisitorIp(), - 'location_browser_lang' => $userInfo['location_browser_lang'], - ); + $this->visitProperties->setProperty('idvisitor', $idVisitor); + $this->visitProperties->setProperty('config_id', $configId); + $this->visitProperties->setProperty('location_ip', $visitorIp); } /** * Gather fields=>values that needs to be updated for the existing visit in log_visit * - * @param $action * @param $visitIsConverted * @return array */ - protected function getExistingVisitFieldsToUpdate($action, $visitIsConverted) + private function getExistingVisitFieldsToUpdate($visitIsConverted) { $valuesToUpdate = array(); - if ($action) { - $idActionUrl = $action->getIdActionUrlForEntryAndExitIds(); - $idActionName = $action->getIdActionNameForEntryAndExitIds(); - $actionType = $action->getActionType(); + $valuesToUpdate = $this->setIdVisitorForExistingVisit($valuesToUpdate); - if ($idActionName !== false) { - $valuesToUpdate['visit_exit_idaction_name'] = $idActionName; - } + $dimensions = $this->getAllVisitDimensions(); + $valuesToUpdate = $this->triggerHookOnDimensions($dimensions, 'onExistingVisit', $valuesToUpdate); - $incrementActions = false; - if ($idActionUrl !== false) { - $valuesToUpdate['visit_exit_idaction_url'] = $idActionUrl; - $incrementActions = true; - } - if ($actionType == Action::TYPE_SITE_SEARCH) { - $valuesToUpdate['visit_total_searches'] = 'visit_total_searches + 1'; - $incrementActions = true; - } else if ($actionType == Action::TYPE_EVENT) { - $valuesToUpdate['visit_total_events'] = 'visit_total_events + 1'; - $incrementActions = true; - } - - if ($incrementActions) { - $valuesToUpdate['visit_total_actions'] = 'visit_total_actions + 1'; - } - } - - $datetimeServer = Tracker::getDatetimeFromTimestamp($this->request->getCurrentTimestamp()); - $valuesToUpdate['visit_last_action_time'] = $datetimeServer; - - // Add 1 so it's always > 0 - $visitTotalTime = 1 + $this->request->getCurrentTimestamp() - $this->visitorInfo['visit_first_action_time']; - $valuesToUpdate['visit_total_time'] = self::cleanupVisitTotalTime($visitTotalTime); - - // Goal conversion if ($visitIsConverted) { - $valuesToUpdate['visit_goal_converted'] = 1; - // If a pageview and goal conversion in the same second, with previously a goal conversion recorded - // the request would not "update" the row since all values are the same as previous - // therefore the request below throws exception, instead we make sure the UPDATE will affect the row - $valuesToUpdate['visit_total_time'] = self::cleanupVisitTotalTime( - $valuesToUpdate['visit_total_time'] - + $this->goalManager->idGoal - // +2 to offset idgoal=-1 and idgoal=0 - + 2); - } - - // Might update the idvisitor when it was forced or overwritten for this visit - if (strlen($this->visitorInfo['idvisitor']) == Tracker::LENGTH_BINARY_ID) { - $valuesToUpdate['idvisitor'] = $this->visitorInfo['idvisitor']; - } - - // Ecommerce buyer status - $visitEcommerceStatus = $this->goalManager->getBuyerType($this->visitorInfo['visit_goal_buyer']); - - if($visitEcommerceStatus != GoalManager::TYPE_BUYER_NONE - // only update if the value has changed (prevents overwriting the value in case a request has updated it in the meantime) - && $visitEcommerceStatus != $this->visitorInfo['visit_goal_buyer']) { - $valuesToUpdate['visit_goal_buyer'] = $visitEcommerceStatus; + $valuesToUpdate = $this->triggerHookOnDimensions($dimensions, 'onConvertedVisit', $valuesToUpdate); } // Custom Variables overwrite previous values on each page view - $valuesToUpdate = array_merge($valuesToUpdate, $this->visitorCustomVariables); return $valuesToUpdate; } /** - * @return array + * @param VisitDimension[] $dimensions + * @param string $hook + * @param Visitor $visitor + * @param Action|null $action + * @param array|null $valuesToUpdate If null, $this->visitorInfo will be updated + * + * @return array|null The updated $valuesToUpdate or null if no $valuesToUpdate given */ - public static function getVisitFieldsPersist() + private function triggerHookOnDimensions($dimensions, $hook, $valuesToUpdate = null) { - $fields = array( - 'idvisitor', - 'idvisit', - 'visit_exit_idaction_url', - 'visit_exit_idaction_name', - 'visitor_returning', - 'visitor_days_since_first', - 'visitor_days_since_order', - 'visitor_count_visits', - 'visit_goal_buyer', + $visitor = $this->makeVisitorFacade(); - 'location_country', - 'location_region', - 'location_city', - 'location_latitude', - 'location_longitude', + /** @var Action $action */ + $action = $this->request->getMetadata('Actions', 'action'); - 'referer_name', - 'referer_keyword', - 'referer_type', - ); + foreach ($dimensions as $dimension) { + $value = $dimension->$hook($this->request, $visitor, $action); - /** - * Triggered when checking if the current action being tracked belongs to an existing visit. - * - * This event collects a list of [visit entity]() properties that should be loaded when reading - * the existing visit. Properties that appear in this list will be available in other tracking - * events such as {@hook Tracker.newConversionInformation} and {@hook Tracker.newVisitorInformation}. - * - * Plugins can use this event to load additional visit entity properties for later use during tracking. - * When you add fields to this $fields array, they will be later available in Tracker.newConversionInformation - * - * **Example** - * - * Piwik::addAction('Tracker.getVisitFieldsToPersist', function (&$fields) { - * $fields[] = 'custom_visit_property'; - * }); - * - * @param array &$fields The list of visit properties to load. - */ - Piwik::postEvent('Tracker.getVisitFieldsToPersist', array(&$fields)); + if ($value !== false) { + $fieldName = $dimension->getColumnName(); + $visitor->setVisitorColumn($fieldName, $value); - return $fields; + if (is_float($value)) { + $value = Common::forceDotAsSeparatorForDecimalPoint($value); + } + + if ($valuesToUpdate !== null) { + $valuesToUpdate[$fieldName] = $value; + } else { + $this->visitProperties->setProperty($fieldName, $value); + } + } + } + + return $valuesToUpdate; + } + + private function triggerPredicateHookOnDimensions($dimensions, $hook) + { + $visitor = $this->makeVisitorFacade(); + + /** @var Action $action */ + $action = $this->request->getMetadata('Actions', 'action'); + + foreach ($dimensions as $dimension) { + if ($dimension->$hook($this->request, $visitor, $action)) { + return true; + } + } + return false; + } + + protected function getAllVisitDimensions() + { + if (is_null(self::$dimensions)) { + self::$dimensions = VisitDimension::getAllDimensions(); + + $dimensionNames = array(); + foreach (self::$dimensions as $dimension) { + $dimensionNames[] = $dimension->getColumnName(); + } + + Common::printDebug("Following dimensions have been collected from plugins: " . implode(", ", + $dimensionNames)); + } + + return self::$dimensions; + } + + private function getVisitStandardLength() + { + return Config::getInstance()->Tracker['visit_standard_length']; + } + + /** + * @param $visitor + * @param $valuesToUpdate + * @return mixed + */ + private function setIdVisitorForExistingVisit($valuesToUpdate) + { + // Might update the idvisitor when it was forced or overwritten for this visit + if (strlen($this->visitProperties->getProperty('idvisitor')) == Tracker::LENGTH_BINARY_ID) { + $binIdVisitor = $this->visitProperties->getProperty('idvisitor'); + $valuesToUpdate['idvisitor'] = $binIdVisitor; + } + + // User ID takes precedence and overwrites idvisitor value + $userId = $this->request->getForcedUserId(); + if ($userId) { + $userIdHash = $this->request->getUserIdHashed($userId); + $binIdVisitor = Common::hex2bin($userIdHash); + $this->visitProperties->setProperty('idvisitor', $binIdVisitor); + $valuesToUpdate['idvisitor'] = $binIdVisitor; + } + return $valuesToUpdate; + } + + protected function insertNewVisit($visit) + { + return $this->getModel()->createVisit($visit); + } + + private function markArchivedReportsAsInvalidIfArchiveAlreadyFinished() + { + $idSite = (int)$this->request->getIdSite(); + $time = $this->request->getCurrentTimestamp(); + + $timezone = $this->getTimezoneForSite($idSite); + + if (!isset($timezone)) { + return; + } + + $date = Date::factory((int)$time, $timezone); + + if (!$date->isToday()) { // we don't have to handle in case date is in future as it is not allowed by tracker + $this->invalidator->rememberToInvalidateArchivedReportsLater($idSite, $date); + } + } + + private function getTimezoneForSite($idSite) + { + try { + $site = Cache::getCacheWebsiteAttributes($idSite); + } catch (UnexpectedWebsiteFoundException $e) { + return null; + } + + if (!empty($site['timezone'])) { + return $site['timezone']; + } + } + + private function makeVisitorFacade() + { + return Visitor::makeFromVisitProperties($this->visitProperties, $this->request); } } diff --git a/www/analytics/core/Tracker/Visit/Factory.php b/www/analytics/core/Tracker/Visit/Factory.php new file mode 100644 index 00000000..9dbb8632 --- /dev/null +++ b/www/analytics/core/Tracker/Visit/Factory.php @@ -0,0 +1,48 @@ +getSpammerListFromCache(); + + $referrerUrl = $request->getParam('urlref'); + + foreach ($spammers as $spammerHost) { + if (stripos($referrerUrl, $spammerHost) !== false) { + Common::printDebug('Referrer URL is a known spam: ' . $spammerHost); + return true; + } + } + + return false; + } + + private function getSpammerListFromCache() + { + $cache = Cache::getEagerCache(); + $cacheId = 'ReferrerSpamFilter-' . self::OPTION_STORAGE_NAME; + + if ($cache->contains($cacheId)) { + $list = $cache->fetch($cacheId); + } else { + $list = $this->loadSpammerList(); + $cache->save($cacheId, $list); + } + + if(!is_array($list)) { + Common::printDebug('Warning: could not read list of spammers from cache.'); + return array(); + } + return $list; + } + + private function loadSpammerList() + { + if ($this->spammerList !== null) { + return $this->spammerList; + } + + // Read first from the auto-updated list in database + $list = Option::get(self::OPTION_STORAGE_NAME); + + if ($list) { + $this->spammerList = unserialize($list); + } else { + // Fallback to reading the bundled list + $file = PIWIK_VENDOR_PATH . '/piwik/referrer-spam-blacklist/spammers.txt'; + $this->spammerList = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + } + + return $this->spammerList; + } +} diff --git a/www/analytics/core/Tracker/Visit/VisitProperties.php b/www/analytics/core/Tracker/Visit/VisitProperties.php new file mode 100644 index 00000000..567d9bd0 --- /dev/null +++ b/www/analytics/core/Tracker/Visit/VisitProperties.php @@ -0,0 +1,73 @@ +visitInfo[$name]) ? $this->visitInfo[$name] : null; + } + + /** + * Returns all visit properties by reference. + * + * @return array + */ + public function &getProperties() + { + return $this->visitInfo; + } + + /** + * Sets a visit property. + * + * @param string $name The property name. + * @param mixed $value The property value. + */ + public function setProperty($name, $value) + { + $this->visitInfo[$name] = $value; + } + + /** + * Unsets all visit properties. + */ + public function clearProperties() + { + $this->visitInfo = array(); + } + + /** + * Sets all visit properties. + * + * @param array $properties + */ + public function setProperties($properties) + { + $this->visitInfo = $properties; + } +} diff --git a/www/analytics/core/Tracker/VisitExcluded.php b/www/analytics/core/Tracker/VisitExcluded.php index 25d05250..a644d447 100644 --- a/www/analytics/core/Tracker/VisitExcluded.php +++ b/www/analytics/core/Tracker/VisitExcluded.php @@ -1,6 +1,6 @@ spamFilter = new ReferrerSpamFilter(); + + if (false === $ip) { $ip = $request->getIp(); } - if ($userAgent === false) { + if (false === $userAgent) { $userAgent = $request->getUserAgent(); } - $this->request = $request; - $this->idSite = $request->getIdSite(); + $this->request = $request; + $this->idSite = $request->getIdSite(); $this->userAgent = $userAgent; $this->ip = $ip; } @@ -72,9 +83,9 @@ class VisitExcluded /** * Triggered on every tracking request. - * + * * This event can be used to tell the Tracker not to record this particular action or visit. - * + * * @param bool &$excluded Whether the request should be excluded or not. Initialized * to `false`. Event subscribers should set it to `true` in * order to exclude the request. @@ -110,6 +121,22 @@ class VisitExcluded } } + // Check if Referrer URL is a known spam + if (!$excluded) { + $excluded = $this->isReferrerSpamExcluded(); + if ($excluded) { + Common::printDebug("Referrer URL is blacklisted as spam."); + } + } + + // Check if request URL is excluded + if (!$excluded) { + $excluded = $this->isUrlExcluded(); + if ($excluded) { + Common::printDebug("Unknown URL is not allowed to track."); + } + } + if (!$excluded) { if ($this->isPrefetchDetected()) { $excluded = true; @@ -138,37 +165,53 @@ class VisitExcluded * As a result, these sophisticated bots exhibit characteristics of * browsers (cookies enabled, executing JavaScript, etc). * + * @see \DeviceDetector\Parser\Bot + * * @return boolean */ protected function isNonHumanBot() { $allowBots = $this->request->getParam('bots'); - return !$allowBots - // Seen in the wild - && (strpos($this->userAgent, 'Googlebot') !== false // Googlebot - || strpos($this->userAgent, 'Google Web Preview') !== false // Google Instant - || strpos($this->userAgent, 'AdsBot-Google') !== false // Google Adwords landing pages - || strpos($this->userAgent, 'Google Page Speed Insights') !== false // #4049 - || strpos($this->userAgent, 'Google (+https://developers.google.com') !== false // Google Snippet https://developers.google.com/+/web/snippet/ - || strpos($this->userAgent, 'facebookexternalhit') !== false // http://www.facebook.com/externalhit_uatext.php - || strpos($this->userAgent, 'baidu') !== false // Baidu - || strpos($this->userAgent, 'bingbot') !== false // Bingbot - || strpos($this->userAgent, 'YottaaMonitor') !== false // Yottaa - || strpos($this->userAgent, 'CloudFlare') !== false // CloudFlare-AlwaysOnline + $deviceDetector = DeviceDetectorFactory::getInstance($this->userAgent); - // Added as they are popular bots - || strpos($this->userAgent, 'pingdom') !== false // pingdom - || strpos($this->userAgent, 'yandex') !== false // yandex - || strpos($this->userAgent, 'exabot') !== false // Exabot - || strpos($this->userAgent, 'sogou') !== false // Sogou - || strpos($this->userAgent, 'soso') !== false // Soso - || IP::isIpInRange($this->ip, $this->getBotIpRanges())); + return !$allowBots + && ($deviceDetector->isBot() || $this->isIpInRange()); } - protected function getBotIpRanges() + private function isIpInRange() { - return array( + $cache = PiwikCache::getTransientCache(); + + $ip = IP::fromBinaryIP($this->ip); + $key = 'VisitExcludedIsIpInRange' . $ip->toString(); + + if ($cache->contains($key)) { + $isInRanges = $cache->fetch($key); + } else { + if ($this->isChromeDataSaverUsed($ip)) { + $isInRanges = false; + } else { + $isInRanges = $ip->isInRanges($this->getBotIpRanges()); + } + + $cache->save($key, $isInRanges); + } + + return $isInRanges; + } + + private function isChromeDataSaverUsed(IP $ip) + { + // see https://github.com/piwik/piwik/issues/7733 + return !empty($_SERVER['HTTP_VIA']) + && false !== strpos(strtolower($_SERVER['HTTP_VIA']), 'chrome-compression-proxy') + && $ip->isInRanges($this->getGoogleBotIpRanges()); + } + + protected function getBotIpRanges() + { + return array_merge($this->getGoogleBotIpRanges(), array( // Live/Bing/MSN '64.4.0.0/18', '65.52.0.0/14', @@ -180,12 +223,29 @@ class VisitExcluded '207.68.192.0/20', '131.253.26.0/20', '131.253.24.0/20', + // Yahoo '72.30.198.0/20', '72.30.196.0/20', '98.137.207.0/20', // Chinese bot hammering websites '1.202.218.8' + )); + } + + private function getGoogleBotIpRanges() + { + return array( + '216.239.32.0/19', + '64.233.160.0/19', + '66.249.80.0/20', + '72.14.192.0/18', + '209.85.128.0/17', + '66.102.0.0/20', + '74.125.0.0/16', + '64.18.0.0/20', + '207.126.144.0/20', + '173.194.0.0/16' ); } @@ -199,6 +259,7 @@ class VisitExcluded Common::printDebug('Piwik ignore cookie was found, visit not tracked.'); return true; } + return false; } @@ -210,12 +271,39 @@ class VisitExcluded protected function isVisitorIpExcluded() { $websiteAttributes = Cache::getCacheWebsiteAttributes($this->idSite); + if (!empty($websiteAttributes['excluded_ips'])) { - if (IP::isIpInRange($this->ip, $websiteAttributes['excluded_ips'])) { - Common::printDebug('Visitor IP ' . IP::N2P($this->ip) . ' is excluded from being tracked'); + $ip = IP::fromBinaryIP($this->ip); + if ($ip->isInRanges($websiteAttributes['excluded_ips'])) { + Common::printDebug('Visitor IP ' . $ip->toString() . ' is excluded from being tracked'); return true; } } + + return false; + } + + /** + * Checks if request URL is excluded + * @return bool + */ + protected function isUrlExcluded() + { + $site = Cache::getCacheWebsiteAttributes($this->idSite); + + if (!empty($site['exclude_unknown_urls']) && !empty($site['urls'])) { + $url = $this->request->getParam('url'); + $parsedUrl = parse_url($url); + + $trackingUrl = new SiteUrls(); + $urls = $trackingUrl->groupUrlsByHost(array($this->idSite => $site['urls'])); + + $idSites = $trackingUrl->getIdSitesMatchingUrl($parsedUrl, $urls); + $isUrlExcluded = !isset($idSites) || !in_array($this->idSite, $idSites); + + return $isUrlExcluded; + } + return false; } @@ -231,6 +319,7 @@ class VisitExcluded protected function isUserAgentExcluded() { $websiteAttributes = Cache::getCacheWebsiteAttributes($this->idSite); + if (!empty($websiteAttributes['excluded_user_agents'])) { foreach ($websiteAttributes['excluded_user_agents'] as $excludedUserAgent) { // if the excluded user agent string part is in this visit's user agent, this visit should be excluded @@ -239,6 +328,17 @@ class VisitExcluded } } } + return false; } + + /** + * Returns true if the Referrer is a known spammer. + * + * @return bool + */ + protected function isReferrerSpamExcluded() + { + return $this->spamFilter->isSpam($this->request); + } } diff --git a/www/analytics/core/Tracker/VisitInterface.php b/www/analytics/core/Tracker/VisitInterface.php index 250d5565..a7022072 100644 --- a/www/analytics/core/Tracker/VisitInterface.php +++ b/www/analytics/core/Tracker/VisitInterface.php @@ -1,6 +1,6 @@ visitProperties = $visitProperties; + $this->setIsVisitorKnown($isVisitorKnown); + } + + public static function makeFromVisitProperties(VisitProperties $visitProperties, Request $request) + { + $isKnown = $request->getMetadata('CoreHome', 'isVisitorKnown'); + return new Visitor($visitProperties, $isKnown); + } + + public function setVisitorColumn($column, $value) + { + $this->visitProperties->setProperty($column, $value); + } + + public function getVisitorColumn($column) + { + if (array_key_exists($column, $this->visitProperties->getProperties())) { + return $this->visitProperties->getProperty($column); + } + + return false; + } + + public function isVisitorKnown() + { + return $this->visitorKnown === true; + } + + public function isNewVisit() + { + return !$this->isVisitorKnown(); + } + + private function setIsVisitorKnown($isVisitorKnown) + { + return $this->visitorKnown = $isVisitorKnown; + } +} \ No newline at end of file diff --git a/www/analytics/core/Tracker/VisitorNotFoundInDb.php b/www/analytics/core/Tracker/VisitorNotFoundInDb.php index e3dfce50..5cbf7919 100644 --- a/www/analytics/core/Tracker/VisitorNotFoundInDb.php +++ b/www/analytics/core/Tracker/VisitorNotFoundInDb.php @@ -1,6 +1,6 @@ trustCookiesOnly = $trustCookiesOnly; + $this->visitStandardLength = $visitStandardLength; + $this->lookBackNSecondsCustom = $lookbackNSecondsCustom; + $this->trackerAlwaysNewVisitor = $trackerAlwaysNewVisitor; + + $this->model = $model; + $this->eventDispatcher = $eventDispatcher; + } + + public function findKnownVisitor($configId, VisitProperties $visitProperties, Request $request) + { + $idSite = $request->getIdSite(); + $idVisitor = $request->getVisitorId(); + + $isVisitorIdToLookup = !empty($idVisitor); + + if ($isVisitorIdToLookup) { + $visitProperties->setProperty('idvisitor', $idVisitor); + Common::printDebug("Matching visitors with: visitorId=" . bin2hex($idVisitor) . " OR configId=" . bin2hex($configId)); + } else { + Common::printDebug("Visitor doesn't have the piwik cookie..."); + } + + $persistedVisitAttributes = $this->getVisitFieldsPersist(); + + $shouldMatchOneFieldOnly = $this->shouldLookupOneVisitorFieldOnly($isVisitorIdToLookup, $request); + list($timeLookBack, $timeLookAhead) = $this->getWindowLookupThisVisit($request); + + $visitRow = $this->model->findVisitor($idSite, $configId, $idVisitor, $persistedVisitAttributes, $shouldMatchOneFieldOnly, $isVisitorIdToLookup, $timeLookBack, $timeLookAhead); + + $isNewVisitForced = $request->getParam('new_visit'); + $isNewVisitForced = !empty($isNewVisitForced); + $enforceNewVisit = $isNewVisitForced || $this->trackerAlwaysNewVisitor; + + if (!$enforceNewVisit + && $visitRow + && count($visitRow) > 0 + ) { + + // These values will be used throughout the request + foreach ($persistedVisitAttributes as $field) { + $visitProperties->setProperty($field, $visitRow[$field]); + } + + $visitProperties->setProperty('visit_last_action_time', strtotime($visitRow['visit_last_action_time'])); + $visitProperties->setProperty('visit_first_action_time', strtotime($visitRow['visit_first_action_time'])); + + // Custom Variables copied from Visit in potential later conversion + if (!empty($numCustomVarsToRead)) { + for ($i = 1; $i <= $numCustomVarsToRead; $i++) { + if (isset($visitRow['custom_var_k' . $i]) + && strlen($visitRow['custom_var_k' . $i]) + ) { + $visitProperties->setProperty('custom_var_k' . $i, $visitRow['custom_var_k' . $i]); + } + if (isset($visitRow['custom_var_v' . $i]) + && strlen($visitRow['custom_var_v' . $i]) + ) { + $visitProperties->setProperty('custom_var_v' . $i, $visitRow['custom_var_v' . $i]); + } + } + } + + Common::printDebug("The visitor is known (idvisitor = " . bin2hex($visitProperties->getProperty('idvisitor')) . ", + config_id = " . bin2hex($configId) . ", + idvisit = {$visitProperties->getProperty('idvisit')}, + last action = " . date("r", $visitProperties->getProperty('visit_last_action_time')) . ", + first action = " . date("r", $visitProperties->getProperty('visit_first_action_time')) . ", + visit_goal_buyer' = " . $visitProperties->getProperty('visit_goal_buyer') . ")"); + + return true; + } else { + Common::printDebug("The visitor was not matched with an existing visitor..."); + + return false; + } + } + + protected function shouldLookupOneVisitorFieldOnly($isVisitorIdToLookup, Request $request) + { + $isForcedUserIdMustMatch = (false !== $request->getForcedUserId()); + + if ($isForcedUserIdMustMatch) { + // if &iud was set, we must try and match both idvisitor and config_id + return false; + } + + // This setting would be enabled for Intranet websites, to ensure that visitors using all the same computer config, same IP + // are not counted as 1 visitor. In this case, we want to enforce and trust the visitor ID from the cookie. + if ($isVisitorIdToLookup && $this->trustCookiesOnly) { + return true; + } + + // If a &cid= was set, we force to select this visitor (or create a new one) + $isForcedVisitorIdMustMatch = ($request->getForcedVisitorId() != null); + + if ($isForcedVisitorIdMustMatch) { + return true; + } + + if (!$isVisitorIdToLookup) { + return true; + } + + return false; + } + + /** + * By default, we look back 30 minutes to find a previous visitor (for performance reasons). + * In some cases, it is useful to look back and count unique visitors more accurately. You can set custom lookback window in + * [Tracker] window_look_back_for_visitor + * + * The returned value is the window range (Min, max) that the matched visitor should fall within + * + * @return array( datetimeMin, datetimeMax ) + */ + protected function getWindowLookupThisVisit(Request $request) + { + $lookAheadNSeconds = $this->visitStandardLength; + $lookBackNSeconds = $this->visitStandardLength; + if ($this->lookBackNSecondsCustom > $lookBackNSeconds) { + $lookBackNSeconds = $this->lookBackNSecondsCustom; + } + + $timeLookBack = date('Y-m-d H:i:s', $request->getCurrentTimestamp() - $lookBackNSeconds); + $timeLookAhead = date('Y-m-d H:i:s', $request->getCurrentTimestamp() + $lookAheadNSeconds); + + return array($timeLookBack, $timeLookAhead); + } + + /** + * @return array + */ + private function getVisitFieldsPersist() + { + if (is_null($this->visitFieldsToSelect)) { + $fields = array( + 'idvisitor', + 'idvisit', + 'user_id', + + 'visit_exit_idaction_url', + 'visit_exit_idaction_name', + 'visitor_returning', + 'visitor_days_since_first', + 'visitor_days_since_order', + 'visitor_count_visits', + 'visit_goal_buyer', + + 'location_country', + 'location_region', + 'location_city', + 'location_latitude', + 'location_longitude', + + 'referer_name', + 'referer_keyword', + 'referer_type', + ); + + $dimensions = VisitDimension::getAllDimensions(); + + foreach ($dimensions as $dimension) { + if ($dimension->hasImplementedEvent('onExistingVisit')) { + $fields[] = $dimension->getColumnName(); + } + + foreach ($dimension->getRequiredVisitFields() as $field) { + $fields[] = $field; + } + } + + /** + * This event collects a list of [visit entity](/guides/persistence-and-the-mysql-backend#visits) properties that should be loaded when reading + * the existing visit. Properties that appear in this list will be available in other tracking + * events such as 'onExistingVisit'. + * + * Plugins can use this event to load additional visit entity properties for later use during tracking. + * + * This event is deprecated, use [Dimensions](http://developer.piwik.org/guides/dimensions) instead. + * + * @deprecated + */ + $this->eventDispatcher->postEvent('Tracker.getVisitFieldsToPersist', array(&$fields)); + + array_unshift($fields, 'visit_first_action_time'); + array_unshift($fields, 'visit_last_action_time'); + + for ($index = 1; $index <= CustomVariables::getNumUsableCustomVariables(); $index++) { + $fields[] = 'custom_var_k' . $index; + $fields[] = 'custom_var_v' . $index; + } + + $this->visitFieldsToSelect = array_unique($fields); + } + + return $this->visitFieldsToSelect; + } +} \ No newline at end of file diff --git a/www/analytics/core/Translate.php b/www/analytics/core/Translate.php index fecfbdf9..5b46a2e7 100644 --- a/www/analytics/core/Translate.php +++ b/www/analytics/core/Translate.php @@ -1,6 +1,6 @@ loadPluginTranslations($language); } /** @@ -57,41 +61,14 @@ class Translate */ public static function loadCoreTranslation($language = false) { - if (empty($language)) { - $language = self::getLanguageToLoad(); - } - if (self::$loadedLanguage == $language) { - return; - } - self::loadCoreTranslationFile($language); - } - - private static function loadCoreTranslationFile($language) - { - if(empty($language)) { - return; - } - $path = PIWIK_INCLUDE_PATH . '/lang/' . $language . '.json'; - if (!Filesystem::isValidFilename($language) || !is_readable($path)) { - throw new Exception(Piwik::translate('General_ExceptionLanguageFileNotFound', array($language))); - } - $data = file_get_contents($path); - $translations = json_decode($data, true); - self::mergeTranslationArray($translations); - self::setLocale(); - self::$loadedLanguage = $language; + self::getTranslator()->addDirectory(PIWIK_INCLUDE_PATH . '/lang'); } + /** + * @deprecated + */ public static function mergeTranslationArray($translation) { - if (!isset($GLOBALS['Piwik_translations'])) { - $GLOBALS['Piwik_translations'] = array(); - } - if (empty($translation)) { - return; - } - // we could check that no string overlap here - $GLOBALS['Piwik_translations'] = array_replace_recursive($GLOBALS['Piwik_translations'], $translation); } /** @@ -100,52 +77,27 @@ class Translate */ public static function getLanguageToLoad() { - if (is_null(self::$languageToLoad)) { - $lang = Common::getRequestVar('language', '', 'string'); - - /** - * Triggered when the current user's language is requested. - * - * By default the current language is determined by the **language** query - * parameter. Plugins can override this logic by subscribing to this event. - * - * **Example** - * - * public function getLanguage(&$lang) - * { - * $client = new My3rdPartyAPIClient(); - * $thirdPartyLang = $client->getLanguageForUser(Piwik::getCurrentUserLogin()); - * - * if (!empty($thirdPartyLang)) { - * $lang = $thirdPartyLang; - * } - * } - * - * @param string &$lang The language that should be used for the current user. Will be - * initialized to the value of the **language** query parameter. - */ - Piwik::postEvent('User.getLanguage', array(&$lang)); - - self::$languageToLoad = $lang; - } - - return self::$languageToLoad; + return self::getTranslator()->getCurrentLanguage(); } /** Reset the cached language to load. Used in tests. */ public static function reset() { - self::$languageToLoad = null; + self::getTranslator()->reset(); } + /** + * Either the name of the currently loaded language such as 'en' or 'de' or null if no language is loaded at all. + * @return bool|string + */ public static function getLanguageLoaded() { - return self::$loadedLanguage; + return self::getTranslator()->getCurrentLanguage(); } public static function getLanguageDefault() { - return Config::getInstance()->General['default_language']; + return self::getTranslator()->getDefaultLanguage(); } /** @@ -153,63 +105,25 @@ class Translate */ public static function getJavascriptTranslations() { - $translations = & $GLOBALS['Piwik_translations']; + return self::getTranslator()->getJavascriptTranslations(); + } - $clientSideTranslations = array(); - foreach (self::getClientSideTranslationKeys() as $key) { - list($plugin, $stringName) = explode("_", $key, 2); - $clientSideTranslations[$key] = $translations[$plugin][$stringName]; - } - - $js = 'var translations = ' . Common::json_encode($clientSideTranslations) . ';'; - $js .= "\n" . 'if(typeof(piwik_translations) == \'undefined\') { var piwik_translations = new Object; }' . - 'for(var i in translations) { piwik_translations[i] = translations[i];} '; - return $js; + public static function findTranslationKeyForTranslation($translation) + { + return self::getTranslator()->findTranslationKeyForTranslation($translation); } /** - * Returns the list of client side translations by key. These translations will be outputted - * to the translation JavaScript. + * @return Translator */ - private static function getClientSideTranslationKeys() + private static function getTranslator() { - $result = array(); - - /** - * Triggered before generating the JavaScript code that allows i18n strings to be used - * in the browser. - * - * Plugins should subscribe to this event to specify which translations - * should be available to JavaScript. - * - * Event handlers should add whole translation keys, ie, keys that include the plugin name. - * - * **Example** - * - * public function getClientSideTranslationKeys(&$result) - * { - * $result[] = "MyPlugin_MyTranslation"; - * } - * - * @param array &$result The whole list of client side translation keys. - */ - Piwik::postEvent('Translate.getClientSideTranslationKeys', array(&$result)); - - $result = array_unique($result); - - return $result; + return StaticContainer::get('Piwik\Translation\Translator'); } - /** - * Set locale - * - * @see http://php.net/setlocale - */ - private static function setLocale() + public static function loadAllTranslations() { - $locale = $GLOBALS['Piwik_translations']['General']['Locale']; - $locale_variant = str_replace('UTF-8', 'UTF8', $locale); - setlocale(LC_ALL, $locale, $locale_variant); - setlocale(LC_CTYPE, ''); + self::loadCoreTranslation(); + Manager::getInstance()->loadPluginTranslations(); } } diff --git a/www/analytics/core/Translate/Validate/CoreTranslations.php b/www/analytics/core/Translate/Validate/CoreTranslations.php deleted file mode 100644 index 96ea1da7..00000000 --- a/www/analytics/core/Translate/Validate/CoreTranslations.php +++ /dev/null @@ -1,101 +0,0 @@ -baseTranslations = $baseTranslations; - } - - /** - * Validates the given translations - * * There need to be more than 250 translations presen - * * Locale, TranslatorName and TranslatorEmail needs to be set in plugin General - * * LayoutDirection needs to be ltr or rtl if present - * * Locale must be valid (format, language & country) - * - * @param array $translations - * - * @return boolean - */ - public function isValid($translations) - { - $this->message = null; - - if (250 > count($translations, COUNT_RECURSIVE)) { - $this->message = self::ERRORSTATE_MINIMUMTRANSLATIONS; - return false; - } - - if (empty($translations['General']['Locale'])) { - $this->message = self::ERRORSTATE_LOCALEREQUIRED; - return false; - } - - if (empty($translations['General']['TranslatorName'])) { - $this->message = self::ERRORSTATE_TRANSLATORINFOREQUIRED; - return false; - } - - if (empty($translations['General']['TranslatorEmail'])) { - $this->message = self::ERRORSTATE_TRANSLATOREMAILREQUIRED; - return false; - } - - if (!empty($translations['General']['LayoutDirection']) && - !in_array($translations['General']['LayoutDirection'], array('ltr', 'rtl')) - ) { - $this->message = self::ERRORSTATE_LAYOUTDIRECTIONINVALID; - return false; - } - - $allLanguages = Common::getLanguagesList(); - $allCountries = Common::getCountriesList(); - - if (!preg_match('/^([a-z]{2})_([A-Z]{2})\.UTF-8$/', $translations['General']['Locale'], $matches)) { - $this->message = self::ERRORSTATE_LOCALEINVALID; - return false; - } else if (!array_key_exists($matches[1], $allLanguages)) { - $this->message = self::ERRORSTATE_LOCALEINVALIDLANGUAGE; - return false; - } else if (!array_key_exists(strtolower($matches[2]), $allCountries)) { - $this->message = self::ERRORSTATE_LOCALEINVALIDCOUNTRY; - return false; - } - - return true; - } -} diff --git a/www/analytics/core/Translation/Loader/DevelopmentLoader.php b/www/analytics/core/Translation/Loader/DevelopmentLoader.php new file mode 100644 index 00000000..a75af4bd --- /dev/null +++ b/www/analytics/core/Translation/Loader/DevelopmentLoader.php @@ -0,0 +1,72 @@ +loader = $loader; + } + + /** + * {@inheritdoc} + */ + public function load($language, array $directories) + { + if ($language !== self::LANGUAGE_ID) { + return $this->loader->load($language, $directories); + } + + return $this->getDevelopmentTranslations($directories); + } + + private function getDevelopmentTranslations(array $directories) + { + $fallbackTranslations = $this->loader->load($this->fallbackLanguage, $directories); + + $translations = array(); + + foreach ($fallbackTranslations as $section => $sectionFallbackTranslations) { + $translationIds = array_keys($sectionFallbackTranslations); + $sectionTranslations = $this->prefixTranslationsWithSection($section, $translationIds); + + $translations[$section] = array_combine($translationIds, $sectionTranslations); + } + + return $translations; + } + + private function prefixTranslationsWithSection($section, $translationIds) + { + return array_map(function ($translation) use ($section) { + return $section . '_' . $translation; + }, $translationIds); + } +} diff --git a/www/analytics/core/Translation/Loader/JsonFileLoader.php b/www/analytics/core/Translation/Loader/JsonFileLoader.php new file mode 100644 index 00000000..802bcc62 --- /dev/null +++ b/www/analytics/core/Translation/Loader/JsonFileLoader.php @@ -0,0 +1,64 @@ +loadFile($filename) + ); + } + + return $translations; + } + + private function loadFile($filename) + { + $data = file_get_contents($filename); + $translations = json_decode($data, true); + + if (is_null($translations) && Common::hasJsonErrorOccurred()) { + throw new \Exception(sprintf( + 'Not able to load translation file %s: %s', + $filename, + Common::getLastJsonError() + )); + } + + if (!is_array($translations)) { + return array(); + } + + return $translations; + } +} diff --git a/www/analytics/core/Translation/Loader/LoaderCache.php b/www/analytics/core/Translation/Loader/LoaderCache.php new file mode 100644 index 00000000..5448e1ae --- /dev/null +++ b/www/analytics/core/Translation/Loader/LoaderCache.php @@ -0,0 +1,65 @@ +loader = $loader; + $this->cache = $cache; + } + + /** + * {@inheritdoc} + */ + public function load($language, array $directories) + { + if (empty($language)) { + return array(); + } + + $cacheKey = $this->getCacheKey($language, $directories); + + $translations = $this->cache->fetch($cacheKey); + + if (empty($translations) || !is_array($translations)) { + $translations = $this->loader->load($language, $directories); + + $this->cache->save($cacheKey, $translations, 43200); // ttl=12hours + } + + return $translations; + } + + private function getCacheKey($language, array $directories) + { + $cacheKey = 'Translations-' . $language . '-'; + + // in case loaded plugins change (ie Tests vs Tracker vs UI etc) + $cacheKey .= sha1(implode('', $directories)); + + return $cacheKey; + } +} diff --git a/www/analytics/core/Translation/Loader/LoaderInterface.php b/www/analytics/core/Translation/Loader/LoaderInterface.php new file mode 100644 index 00000000..9aae71d4 --- /dev/null +++ b/www/analytics/core/Translation/Loader/LoaderInterface.php @@ -0,0 +1,23 @@ +username = $username; + $this->password = $password; + $this->projectSlug = $project; + } + + /** + * Returns all resources available on Transifex project + * + * @return array + */ + public function getAvailableResources() + { + $cache = Cache::getTransientCache(); + $cacheId = 'transifex_resources_' . $this->projectSlug; + $resources = $cache->fetch($cacheId); + + if (empty($resources)) { + $apiPath = 'project/' . $this->projectSlug . '/resources'; + $resources = $this->getApiResults($apiPath); + $cache->save($cacheId, $resources); + } + + return $resources; + } + + /** + * Checks if the given resource exists in Transifex project + * + * @param string $resource + * @return bool + */ + public function resourceExists($resource) + { + $resources = $this->getAvailableResources(); + foreach ($resources as $res) { + if ($res->slug == $resource) { + return true; + } + } + return false; + } + + /** + * Returns all language codes the transifex project is available for + * + * @return array + * @throws AuthenticationFailedException + * @throws Exception + */ + public function getAvailableLanguageCodes() + { + $cache = Cache::getTransientCache(); + $cacheId = 'transifex_languagescodes_' . $this->projectSlug; + $languageCodes = $cache->fetch($cacheId); + + if (empty($languageCodes)) { + $apiData = $this->getApiResults('project/' . $this->projectSlug . '/languages'); + foreach ($apiData as $languageData) { + $languageCodes[] = $languageData->language_code; + } + $cache->save($cacheId, $languageCodes); + } + return $languageCodes; + } + + /** + * Returns statistic data for the given resource + * + * @param string $resource e.g. piwik-base, piwik-plugin-api,... + * @return array + * @throws AuthenticationFailedException + * @throws Exception + */ + public function getStatistics($resource) + { + return $this->getApiResults('project/' . $this->projectSlug . '/resource/' . $resource . '/stats/'); + } + + /** + * Return the translations for the given resource and language + * + * @param string $resource e.g. piwik-base, piwik-plugin-api,... + * @param string $language e.g. de, pt_BR, hy,... + * @param bool $raw if true plain response wil be returned (unparsed json) + * @return mixed + * @throws AuthenticationFailedException + * @throws Exception + */ + public function getTranslations($resource, $language, $raw = false) + { + if ($this->resourceExists($resource)) { + $apiPath = 'project/' . $this->projectSlug . '/resource/' . $resource . '/translation/' . $language . '/?mode=onlytranslated&file'; + return $this->getApiResults($apiPath, $raw); + } + return null; + } + + /** + * Returns response for API request with given path + * + * @param $apiPath + * @param bool $raw + * @return mixed + * @throws AuthenticationFailedException + * @throws Exception + */ + protected function getApiResults($apiPath, $raw = false) + { + $apiUrl = $this->apiUrl . $apiPath; + + $response = Http::sendHttpRequest($apiUrl, 1000, null, null, 5, false, false, true, 'GET', $this->username, $this->password); + + $httpStatus = $response['status']; + $response = $response['data']; + + if ($httpStatus == 401) { + throw new AuthenticationFailedException(); + } elseif ($httpStatus != 200) { + throw new Exception('Error while getting API results', $httpStatus); + } + + return $raw ? $response : json_decode($response); + } +} diff --git a/www/analytics/core/Translation/Translator.php b/www/analytics/core/Translation/Translator.php new file mode 100644 index 00000000..af2ae597 --- /dev/null +++ b/www/analytics/core/Translation/Translator.php @@ -0,0 +1,271 @@ +loader = $loader; + $this->currentLanguage = $this->getDefaultLanguage(); + + if ($directories === null) { + // TODO should be moved out of this class + $directories = array(PIWIK_INCLUDE_PATH . '/lang'); + } + $this->directories = $directories; + } + + /** + * Returns an internationalized string using a translation ID. If a translation + * cannot be found for the ID, the ID is returned. + * + * @param string $translationId Translation ID, eg, `General_Date`. + * @param array|string|int $args `sprintf` arguments to be applied to the internationalized + * string. + * @param string|null $language Optionally force the language. + * @return string The translated string or `$translationId`. + * @api + */ + public function translate($translationId, $args = array(), $language = null) + { + $args = is_array($args) ? $args : array($args); + + if (strpos($translationId, "_") !== false) { + list($plugin, $key) = explode("_", $translationId, 2); + $language = is_string($language) ? $language : $this->currentLanguage; + + $translationId = $this->getTranslation($translationId, $language, $plugin, $key); + } + + if (count($args) == 0) { + return $translationId; + } + return vsprintf($translationId, $args); + } + + /** + * @return string + */ + public function getCurrentLanguage() + { + return $this->currentLanguage; + } + + /** + * @param string $language + */ + public function setCurrentLanguage($language) + { + if (!$language) { + $language = $this->getDefaultLanguage(); + } + + $this->currentLanguage = $language; + } + + /** + * @return string The default configured language. + */ + public function getDefaultLanguage() + { + $generalSection = Config::getInstance()->General; + + // the config may not be available (for example, during environment setup), so we default to 'en' + // if the config cannot be found. + return @$generalSection['default_language'] ?: 'en'; + } + + /** + * Generate javascript translations array + */ + public function getJavascriptTranslations() + { + $clientSideTranslations = array(); + foreach ($this->getClientSideTranslationKeys() as $id) { + list($plugin, $key) = explode('_', $id, 2); + $clientSideTranslations[$id] = $this->getTranslation($id, $this->currentLanguage, $plugin, $key); + } + + $js = 'var translations = ' . json_encode($clientSideTranslations) . ';'; + $js .= "\n" . 'if (typeof(piwik_translations) == \'undefined\') { var piwik_translations = new Object; }' . + 'for(var i in translations) { piwik_translations[i] = translations[i];} '; + return $js; + } + + /** + * Returns the list of client side translations by key. These translations will be outputted + * to the translation JavaScript. + */ + private function getClientSideTranslationKeys() + { + $result = array(); + + /** + * Triggered before generating the JavaScript code that allows i18n strings to be used + * in the browser. + * + * Plugins should subscribe to this event to specify which translations + * should be available to JavaScript. + * + * Event handlers should add whole translation keys, ie, keys that include the plugin name. + * + * **Example** + * + * public function getClientSideTranslationKeys(&$result) + * { + * $result[] = "MyPlugin_MyTranslation"; + * } + * + * @param array &$result The whole list of client side translation keys. + */ + Piwik::postEvent('Translate.getClientSideTranslationKeys', array(&$result)); + + $result = array_unique($result); + + return $result; + } + + /** + * Add a directory containing translations. + * + * @param string $directory + */ + public function addDirectory($directory) + { + if (isset($this->directories[$directory])) { + return; + } + // index by name to avoid duplicates + $this->directories[$directory] = $directory; + + // clear currently loaded translations to force reloading them + $this->translations = array(); + } + + /** + * Should be used by tests only, and this method should eventually be removed. + */ + public function reset() + { + $this->currentLanguage = $this->getDefaultLanguage(); + $this->directories = array(); + $this->translations = array(); + } + + /** + * @param string $translation + * @return null|string + */ + public function findTranslationKeyForTranslation($translation) + { + foreach ($this->getAllTranslations() as $key => $translations) { + $possibleKey = array_search($translation, $translations); + if (!empty($possibleKey)) { + return $key . '_' . $possibleKey; + } + } + + return null; + } + + /** + * Returns all the translation messages loaded. + * + * @return array + */ + public function getAllTranslations() + { + $this->loadTranslations($this->currentLanguage); + + if (!isset($this->translations[$this->currentLanguage])) { + return array(); + } + + return $this->translations[$this->currentLanguage]; + } + + private function getTranslation($id, $lang, $plugin, $key) + { + $this->loadTranslations($lang); + + if (isset($this->translations[$lang][$plugin]) + && isset($this->translations[$lang][$plugin][$key]) + ) { + return $this->translations[$lang][$plugin][$key]; + } + + /** + * Fallback for keys moved to new Intl plugin to avoid untranslated string in non core plugins + * @todo remove this in Piwik 3.0 + */ + if ($plugin != 'Intl') { + if (isset($this->translations[$lang]['Intl']) + && isset($this->translations[$lang]['Intl'][$key]) + ) { + return $this->translations[$lang]['Intl'][$key]; + } + } + + // fallback + if ($lang !== $this->fallback) { + return $this->getTranslation($id, $this->fallback, $plugin, $key); + } + + return $id; + } + + private function loadTranslations($language) + { + if (empty($language) || isset($this->translations[$language])) { + return; + } + + $this->translations[$language] = $this->loader->load($language, $this->directories); + } +} diff --git a/www/analytics/core/Twig.php b/www/analytics/core/Twig.php old mode 100644 new mode 100755 index 16348e52..c3b6ba74 --- a/www/analytics/core/Twig.php +++ b/www/analytics/core/Twig.php @@ -1,6 +1,6 @@ getDefaultThemeLoader(); - $this->addPluginNamespaces($loader); - // If theme != default we need to chain - $chainLoader = new Twig_Loader_Chain(array($loader)); + //get current theme + $manager = Plugin\Manager::getInstance(); + $theme = $manager->getThemeEnabled(); + $loaders = array(); + + $this->formatter = new Formatter(); + + //create loader for custom theme to overwrite twig templates + if ($theme && $theme->getPluginName() != \Piwik\Plugin\Manager::DEFAULT_THEME) { + $customLoader = $this->getCustomThemeLoader($theme); + if ($customLoader) { + //make it possible to overwrite plugin templates + $this->addCustomPluginNamespaces($customLoader, $theme->getPluginName()); + $loaders[] = $customLoader; + } + } + + $loaders[] = $loader; + + $chainLoader = new Twig_Loader_Chain($loaders); // Create new Twig Environment and set cache dir - $templatesCompiledPath = PIWIK_USER_PATH . '/tmp/templates_c'; - $templatesCompiledPath = SettingsPiwik::rewriteTmpPathWithHostname($templatesCompiledPath); + $templatesCompiledPath = StaticContainer::get('path.tmp') . '/templates_c'; $this->twig = new Twig_Environment($chainLoader, array( @@ -62,11 +83,19 @@ class Twig $this->addFilter_sumTime(); $this->addFilter_money(); $this->addFilter_truncate(); - $this->addFilter_notificiation(); + $this->addFilter_notification(); $this->addFilter_percentage(); + $this->addFilter_percent(); + $this->addFilter_percentEvolution(); + $this->addFilter_piwikProAdLink(); + $this->addFilter_piwikProOnPremisesAdLink(); + $this->addFilter_piwikProCloudAdLink(); $this->addFilter_prettyDate(); + $this->addFilter_safeDecodeRaw(); + $this->addFilter_number(); $this->twig->addFilter(new Twig_SimpleFilter('implode', 'implode')); $this->twig->addFilter(new Twig_SimpleFilter('ucwords', 'ucwords')); + $this->twig->addFilter(new Twig_SimpleFilter('lcfirst', 'lcfirst')); $this->addFunction_includeAssets(); $this->addFunction_linkTo(); @@ -76,6 +105,43 @@ class Twig $this->addFunction_getJavascriptTranslations(); $this->twig->addTokenParser(new RenderTokenParser()); + + $this->addTest_false(); + $this->addTest_true(); + $this->addTest_emptyString(); + } + + private function addTest_false() + { + $test = new Twig_SimpleTest( + 'false', + function ($value) { + return false === $value; + } + ); + $this->twig->addTest($test); + } + + private function addTest_true() + { + $test = new Twig_SimpleTest( + 'true', + function ($value) { + return true === $value; + } + ); + $this->twig->addTest($test); + } + + private function addTest_emptyString() + { + $test = new Twig_SimpleTest( + 'emptyString', + function ($value) { + return '' === $value; + } + ); + $this->twig->addTest($test); } protected function addFunction_getJavascriptTranslations() @@ -126,7 +192,7 @@ class Twig // make the first value the string that will get output in the template // plugins can modify this string $str = ''; - $params = array_merge( array( &$str ), $params); + $params = array_merge(array( &$str ), $params); Piwik::postEvent($eventName, $params); return $str; @@ -164,12 +230,29 @@ class Twig return $themeLoader; } + /** + * create template loader for a custom theme + * @param \Piwik\Plugin $theme + * @return \Twig_Loader_Filesystem + */ + protected function getCustomThemeLoader(Plugin $theme) + { + if (!file_exists(sprintf("%s/plugins/%s/templates/", PIWIK_INCLUDE_PATH, $theme->getPluginName()))) { + return false; + } + $themeLoader = new Twig_Loader_Filesystem(array( + sprintf("%s/plugins/%s/templates/", PIWIK_INCLUDE_PATH, $theme->getPluginName()) + )); + + return $themeLoader; + } + public function getTwigEnvironment() { return $this->twig; } - protected function addFilter_notificiation() + protected function addFilter_notification() { $twigEnv = $this->getTwigEnvironment(); $notificationFunction = new Twig_SimpleFilter('notification', function ($message, $options) use ($twigEnv) { @@ -198,10 +281,22 @@ class Twig $this->twig->addFilter($notificationFunction); } + protected function addFilter_safeDecodeRaw() + { + $rawSafeDecoded = new Twig_SimpleFilter('rawSafeDecoded', function ($string) { + $string = str_replace('+', '%2B', $string); + $string = str_replace(' ', html_entity_decode(' '), $string); + + return SafeDecodeLabel::decodeLabelSafe($string); + + }, array('is_safe' => array('all'))); + $this->twig->addFilter($rawSafeDecoded); + } + protected function addFilter_prettyDate() { $prettyDate = new Twig_SimpleFilter('prettyDate', function ($dateString, $period) { - return Range::factory($period, $dateString)->getLocalizedShortString(); + return Period\Factory::build($period, $dateString)->getLocalizedShortString(); }); $this->twig->addFilter($prettyDate); } @@ -209,11 +304,78 @@ class Twig protected function addFilter_percentage() { $percentage = new Twig_SimpleFilter('percentage', function ($string, $totalValue, $precision = 1) { - return Piwik::getPercentageSafe($string, $totalValue, $precision) . '%'; + return NumberFormatter::getInstance()->formatPercent(Piwik::getPercentageSafe($string, $totalValue, $precision), $precision); }); $this->twig->addFilter($percentage); } + protected function addFilter_percent() + { + $percentage = new Twig_SimpleFilter('percent', function ($string, $precision = 1) { + return NumberFormatter::getInstance()->formatPercent($string, $precision); + }); + $this->twig->addFilter($percentage); + } + + protected function addFilter_percentEvolution() + { + $percentage = new Twig_SimpleFilter('percentEvolution', function ($string) { + return NumberFormatter::getInstance()->formatPercentEvolution($string); + }); + $this->twig->addFilter($percentage); + } + + protected function addFilter_piwikProAdLink() + { + $ads = $this->getPiwikProAdvertising(); + $piwikProAd = new Twig_SimpleFilter('piwikProCampaignParameters', function ($url, $campaignName, $campaignMedium, $campaignContent = '') use ($ads) { + $url = $ads->addPromoCampaignParametersToUrl($url, $campaignName, $campaignMedium, $campaignContent); + return $url; + }); + $this->twig->addFilter($piwikProAd); + } + + protected function addFilter_piwikProOnPremisesAdLink() + { + $twigEnv = $this->getTwigEnvironment(); + $ads = $this->getPiwikProAdvertising(); + $piwikProAd = new Twig_SimpleFilter('piwikProOnPremisesPromoUrl', function ($medium, $content = '') use ($twigEnv, $ads) { + + $url = $ads->getPromoUrlForOnPremises($medium, $content); + + return twig_escape_filter($twigEnv, $url, 'html_attr'); + + }, array('is_safe' => array('html_attr'))); + $this->twig->addFilter($piwikProAd); + } + + protected function addFilter_piwikProCloudAdLink() + { + $twigEnv = $this->getTwigEnvironment(); + $ads = $this->getPiwikProAdvertising(); + $piwikProAd = new Twig_SimpleFilter('piwikProCloudPromoUrl', function ($medium, $content = '') use ($twigEnv, $ads) { + + $url = $ads->getPromoUrlForCloud($medium, $content); + + return twig_escape_filter($twigEnv, $url, 'html_attr'); + + }, array('is_safe' => array('html_attr'))); + $this->twig->addFilter($piwikProAd); + } + + private function getPiwikProAdvertising() + { + return StaticContainer::get('Piwik\PiwikPro\Advertising'); + } + + protected function addFilter_number() + { + $formatter = new Twig_SimpleFilter('number', function ($string, $minFractionDigits = 0, $maxFractionDigits = 0) { + return NumberFormatter::getInstance()->format($string, $minFractionDigits, $maxFractionDigits); + }); + $this->twig->addFilter($formatter); + } + protected function addFilter_truncate() { $truncateFilter = new Twig_SimpleFilter('truncate', function ($string, $size) { @@ -229,21 +391,24 @@ class Twig protected function addFilter_money() { - $moneyFilter = new Twig_SimpleFilter('money', function ($amount) { + $formatter = $this->formatter; + $moneyFilter = new Twig_SimpleFilter('money', function ($amount) use ($formatter) { if (func_num_args() != 2) { throw new Exception('the money modifier expects one parameter: the idSite.'); } $idSite = func_get_args(); $idSite = $idSite[1]; - return MetricsFormatter::getPrettyMoney($amount, $idSite); + $currencySymbol = Site::getCurrencySymbolFor($idSite); + return NumberFormatter::getInstance()->formatCurrency($amount, $currencySymbol, GoalManager::REVENUE_PRECISION); }); $this->twig->addFilter($moneyFilter); } protected function addFilter_sumTime() { - $sumtimeFilter = new Twig_SimpleFilter('sumtime', function ($numberOfSeconds) { - return MetricsFormatter::getPrettyTimeFromSeconds($numberOfSeconds); + $formatter = $this->formatter; + $sumtimeFilter = new Twig_SimpleFilter('sumtime', function ($numberOfSeconds) use ($formatter) { + return $formatter->getPrettyTimeFromSeconds($numberOfSeconds, true); }); $this->twig->addFilter($sumtimeFilter); } @@ -289,6 +454,22 @@ class Twig } } + /** + * + * Plugin-Templates can be overwritten by putting identically named templates in plugins/[theme]/templates/plugins/[plugin]/ + * + */ + private function addCustomPluginNamespaces(Twig_Loader_Filesystem $loader, $pluginName) + { + $plugins = \Piwik\Plugin\Manager::getInstance()->getAllPluginsNames(); + foreach ($plugins as $name) { + $path = sprintf("%s/plugins/%s/templates/plugins/%s/", PIWIK_INCLUDE_PATH, $pluginName, $name); + if (is_dir($path)) { + $loader->addPath(PIWIK_INCLUDE_PATH . '/plugins/' . $pluginName . '/templates/plugins/'. $name, $name); + } + } + } + /** * Prepend relative paths with absolute Piwik path * diff --git a/www/analytics/core/Unzip.php b/www/analytics/core/Unzip.php index 2ba8cfcd..ffef2add 100644 --- a/www/analytics/core/Unzip.php +++ b/www/analytics/core/Unzip.php @@ -1,22 +1,20 @@ tarArchive = new Archive_Tar($filename, $compression); - } - - /** - * Extracts the contents of the tar file to $pathExtracted. - * - * @param string $pathExtracted Directory to extract into. - * @return bool true if successful, false if otherwise. - */ - public function extract($pathExtracted) - { - return $this->tarArchive->extract($pathExtracted); - } - - /** - * Extracts one file held in a tar archive and returns the deflated file - * as a string. - * - * @param string $inArchivePath Path to file in the tar archive. - * @return bool true if successful, false if otherwise. - */ - public function extractInString($inArchivePath) - { - return $this->tarArchive->extractInString($inArchivePath); - } - - /** - * Lists the files held in the tar archive. - * - * @return array List of paths describing everything held in the tar archive. - */ - public function listContent() - { - return $this->tarArchive->listContent(); - } - - /** - * Get error status string for the latest error. - * - * @return string - */ - public function errorInfo() - { - return $this->tarArchive->error_object->getMessage(); - } -} diff --git a/www/analytics/core/Unzip/UncompressInterface.php b/www/analytics/core/Unzip/UncompressInterface.php deleted file mode 100644 index d3dccf20..00000000 --- a/www/analytics/core/Unzip/UncompressInterface.php +++ /dev/null @@ -1,39 +0,0 @@ -filename = $filename; - $this->ziparchive = new \ZipArchive; - if ($this->ziparchive->open($filename) !== true) { - throw new Exception('Error opening ' . $filename); - } - } - - /** - * Extract files from archive to target directory - * - * @param string $pathExtracted Absolute path of target directory - * @return mixed Array of filenames if successful; or 0 if an error occurred - */ - public function extract($pathExtracted) - { - if (substr_compare($pathExtracted, '/', -1)) - $pathExtracted .= '/'; - - $fileselector = array(); - $list = array(); - $count = $this->ziparchive->numFiles; - if ($count === 0) { - return 0; - } - - for ($i = 0; $i < $count; $i++) { - $entry = $this->ziparchive->statIndex($i); - - $filename = str_replace('\\', '/', $entry['name']); - $parts = explode('/', $filename); - - if (!strncmp($filename, '/', 1) || - array_search('..', $parts) !== false || - strpos($filename, ':') !== false - ) { - return 0; - } - $fileselector[] = $entry['name']; - $list[] = array( - 'filename' => $pathExtracted . $entry['name'], - 'stored_filename' => $entry['name'], - 'size' => $entry['size'], - 'compressed_size' => $entry['comp_size'], - 'mtime' => $entry['mtime'], - 'index' => $i, - 'crc' => $entry['crc'], - ); - } - - $res = $this->ziparchive->extractTo($pathExtracted, $fileselector); - if ($res === false) - return 0; - return $list; - } - - /** - * Get error status string for the latest error - * - * @return string - */ - public function errorInfo() - { - static $statusStrings = array( - \ZIPARCHIVE::ER_OK => 'No error', - \ZIPARCHIVE::ER_MULTIDISK => 'Multi-disk zip archives not supported', - \ZIPARCHIVE::ER_RENAME => 'Renaming temporary file failed', - \ZIPARCHIVE::ER_CLOSE => 'Closing zip archive failed', - \ZIPARCHIVE::ER_SEEK => 'Seek error', - \ZIPARCHIVE::ER_READ => 'Read error', - \ZIPARCHIVE::ER_WRITE => 'Write error', - \ZIPARCHIVE::ER_CRC => 'CRC error', - \ZIPARCHIVE::ER_ZIPCLOSED => 'Containing zip archive was closed', - \ZIPARCHIVE::ER_NOENT => 'No such file', - \ZIPARCHIVE::ER_EXISTS => 'File already exists', - \ZIPARCHIVE::ER_OPEN => 'Can\'t open file', - \ZIPARCHIVE::ER_TMPOPEN => 'Failure to create temporary file', - \ZIPARCHIVE::ER_ZLIB => 'Zlib error', - \ZIPARCHIVE::ER_MEMORY => 'Malloc failure', - \ZIPARCHIVE::ER_CHANGED => 'Entry has been changed', - \ZIPARCHIVE::ER_COMPNOTSUPP => 'Compression method not supported', - \ZIPARCHIVE::ER_EOF => 'Premature EOF', - \ZIPARCHIVE::ER_INVAL => 'Invalid argument', - \ZIPARCHIVE::ER_NOZIP => 'Not a zip archive', - \ZIPARCHIVE::ER_INTERNAL => 'Internal error', - \ZIPARCHIVE::ER_INCONS => 'Zip archive inconsistent', - \ZIPARCHIVE::ER_REMOVE => 'Can\'t remove file', - \ZIPARCHIVE::ER_DELETED => 'Entry has been deleted', - ); - - if (isset($statusStrings[$this->ziparchive->status])) { - $statusString = $statusStrings[$this->ziparchive->status]; - } else { - $statusString = 'Unknown status'; - } - return $statusString . '(' . $this->ziparchive->status . ')'; - } -} diff --git a/www/analytics/core/UpdateCheck.php b/www/analytics/core/UpdateCheck.php index 76b3a248..b176ed87 100644 --- a/www/analytics/core/UpdateCheck.php +++ b/www/analytics/core/UpdateCheck.php @@ -1,6 +1,6 @@ Version::VERSION, - 'php_version' => PHP_VERSION, - 'url' => Url::getCurrentUrlWithoutQueryString(), - 'trigger' => Common::getRequestVar('module', '', 'string'), - 'timezone' => API::getInstance()->getDefaultTimezone(), - ); - $url = Config::getInstance()->General['api_service_url'] - . '/1.0/getLatestVersion/' - . '?' . http_build_query($parameters, '', '&'); - $timeout = self::SOCKET_TIMEOUT; - - if (@Config::getInstance()->Debug['allow_upgrades_to_beta']) { - $url = 'http://builds.piwik.org/LATEST_BETA'; - } - - try { - $latestVersion = Http::sendHttpRequest($url, $timeout); - if (!preg_match('~^[0-9][0-9a-zA-Z_.-]*$~D', $latestVersion)) { - $latestVersion = ''; - } - } catch (Exception $e) { - // e.g., disable_functions = fsockopen; allow_url_open = Off + $latestVersion = self::getLatestAvailableVersionNumber(); + $latestVersion = trim((string) $latestVersion); + if (!preg_match('~^[0-9][0-9a-zA-Z_.-]*$~D', $latestVersion)) { $latestVersion = ''; } + Option::set(self::LATEST_VERSION, $latestVersion); } } + /** + * Get the latest available version number for the currently active release channel. Eg '2.15.0-b4' or '2.15.0'. + * Should return a semantic version number in format MAJOR.MINOR.PATCH (http://semver.org/). + * Returns an empty string in case one cannot connect to the remote server. + * @return string + */ + private static function getLatestAvailableVersionNumber() + { + $channel = StaticContainer::get('\Piwik\Plugin\ReleaseChannels')->getActiveReleaseChannel(); + $url = $channel->getUrlToCheckForLatestAvailableVersion(); + + try { + $latestVersion = Http::sendHttpRequest($url, self::SOCKET_TIMEOUT); + } catch (\Exception $e) { + // e.g., disable_functions = fsockopen; allow_url_open = Off + $latestVersion = ''; + } + + return $latestVersion; + } + /** * Returns the latest available version number. Does not perform a check whether a later version is available. * diff --git a/www/analytics/core/UpdateCheck/ReleaseChannel.php b/www/analytics/core/UpdateCheck/ReleaseChannel.php new file mode 100644 index 00000000..5f204074 --- /dev/null +++ b/www/analytics/core/UpdateCheck/ReleaseChannel.php @@ -0,0 +1,73 @@ +pathUpdateFileCore = PIWIK_INCLUDE_PATH . '/core/Updates/'; - $this->pathUpdateFilePlugins = PIWIK_INCLUDE_PATH . '/plugins/%s/Updates/'; + $this->pathUpdateFileCore = $pathUpdateFileCore ?: PIWIK_INCLUDE_PATH . '/core/Updates/'; + $this->pathUpdateFilePlugins = $pathUpdateFilePlugins ?: PIWIK_INCLUDE_PATH . '/plugins/%s/Updates/'; + $this->columnsUpdater = $columnsUpdater ?: new Columns\Updater(); + + self::$activeInstance = $this; } /** - * Add component to check + * Adds an UpdateObserver to the internal list of listeners. * - * @param string $name - * @param string $version + * @param UpdateObserver $listener */ - public function addComponentToCheck($name, $version) + public function addUpdateObserver(UpdateObserver $listener) { - $this->componentsToCheck[$name] = $version; + $this->updateObservers[] = $listener; } /** - * Record version of successfully completed component update + * Marks a component as successfully updated to a specific version in the database. Sets an option + * that looks like `"version_$componentName"`. * - * @param string $name - * @param string $version + * @param string $name The component name. Eg, a plugin name, `'core'` or dimension column name. + * @param string $version The component version (should use semantic versioning). */ - public static function recordComponentSuccessfullyUpdated($name, $version) + public function markComponentSuccessfullyUpdated($name, $version) { try { Option::set(self::getNameInOptionTable($name), $version, $autoLoad = 1); @@ -58,23 +91,58 @@ class Updater } /** - * Returns the flag name to use in the option table to record current schema version - * @param string $name - * @return string + * Marks a component as successfully uninstalled. Deletes an option + * that looks like `"version_$componentName"`. + * + * @param string $name The component name. Eg, a plugin name, `'core'` or dimension column name. */ - private static function getNameInOptionTable($name) + public function markComponentSuccessfullyUninstalled($name) { - return 'version_' . $name; + try { + Option::delete(self::getNameInOptionTable($name)); + } catch (\Exception $e) { + // case when the option table is not yet created (before 0.2.10) + } + } + + /** + * Returns the currently installed version of a Piwik component. + * + * @param string $name The component name. Eg, a plugin name, `'core'` or dimension column name. + * @return string A semantic version. + * @throws \Exception + */ + public function getCurrentComponentVersion($name) + { + try { + $currentVersion = Option::get(self::getNameInOptionTable($name)); + } catch (\Exception $e) { + // mysql error 1146: table doesn't exist + if (Db::get()->isErrNo($e, '1146')) { + // case when the option table is not yet created (before 0.2.10) + $currentVersion = false; + } else { + // failed for some other reason + throw $e; + } + } + + return $currentVersion; } /** * Returns a list of components (core | plugin) that need to run through the upgrade process. * + * @param string[] $componentsToCheck An array mapping component names to the latest locally available version. + * If the version is later than the currently installed version, the component + * must be upgraded. + * + * Example: `array('core' => '2.11.0')` * @return array( componentName => array( file1 => version1, [...]), [...]) */ - public function getComponentsWithUpdateFile() + public function getComponentsWithUpdateFile($componentsToCheck) { - $this->componentsWithNewVersion = $this->getComponentsWithNewVersion(); + $this->componentsWithNewVersion = $this->getComponentsWithNewVersion($componentsToCheck); $this->componentsWithUpdateFile = $this->loadComponentsWithUpdateFile(); return $this->componentsWithUpdateFile; } @@ -87,8 +155,7 @@ class Updater */ public function hasNewVersion($componentName) { - return isset($this->componentsWithNewVersion) && - isset($this->componentsWithNewVersion[$componentName]); + return isset($this->componentsWithNewVersion[$componentName]); } /** @@ -111,6 +178,8 @@ class Updater public function getSqlQueriesToExecute() { $queries = array(); + $classNames = array(); + foreach ($this->componentsWithUpdateFile as $componentName => $componentUpdateInfo) { foreach ($componentUpdateInfo as $file => $fileVersion) { require_once $file; // prefixed by PIWIK_INCLUDE_PATH @@ -119,21 +188,25 @@ class Updater if (!class_exists($className, false)) { throw new \Exception("The class $className was not found in $file"); } - $queriesForComponent = call_user_func(array($className, 'getSql')); + + if (in_array($className, $classNames)) { + continue; // prevent from getting updates from Piwik\Columns\Updater multiple times + } + + $classNames[] = $className; + + $update = StaticContainer::getContainer()->make($className); + $queriesForComponent = call_user_func(array($update, 'getMigrationQueries'), $this); foreach ($queriesForComponent as $query => $error) { $queries[] = $query . ';'; } $this->hasMajorDbUpdate = $this->hasMajorDbUpdate || call_user_func(array($className, 'isMajorUpdate')); } - // unfortunately had to extract this query from the Option class - $queries[] = 'UPDATE `' . Common::prefixTable('option') . '` '. - 'SET option_value = \'' . $fileVersion . '\' '. - 'WHERE option_name = \'' . self::getNameInOptionTable($componentName) . '\';'; } return $queries; } - private function getUpdateClassName($componentName, $fileVersion) + public function getUpdateClassName($componentName, $fileVersion) { $suffix = strtolower(str_replace(array('-', '.'), '_', $fileVersion)); $className = 'Updates_' . $suffix; @@ -141,6 +214,11 @@ class Updater if ($componentName == 'core') { return '\\Piwik\\Updates\\' . $className; } + + if (ColumnUpdater::isDimensionComponent($componentName)) { + return '\\Piwik\\Columns\\Updater'; + } + return '\\Piwik\\Plugins\\' . $componentName . '\\' . $className; } @@ -154,26 +232,46 @@ class Updater public function update($componentName) { $warningMessages = array(); + + $this->executeListenerHook('onComponentUpdateStarting', array($componentName)); + foreach ($this->componentsWithUpdateFile[$componentName] as $file => $fileVersion) { try { require_once $file; // prefixed by PIWIK_INCLUDE_PATH $className = $this->getUpdateClassName($componentName, $fileVersion); - if (class_exists($className, false)) { - // update() - call_user_func(array($className, 'update')); + if (!in_array($className, $this->updatedClasses) + && class_exists($className, false) + ) { + $this->executeListenerHook('onComponentUpdateFileStarting', array($componentName, $file, $className, $fileVersion)); + + $this->executeSingleUpdateClass($className); + + $this->executeListenerHook('onComponentUpdateFileFinished', array($componentName, $file, $className, $fileVersion)); + + // makes sure to call Piwik\Columns\Updater only once as one call updates all dimensions at the same + // time for better performance + $this->updatedClasses[] = $className; } - self::recordComponentSuccessfullyUpdated($componentName, $fileVersion); + $this->markComponentSuccessfullyUpdated($componentName, $fileVersion); } catch (UpdaterErrorException $e) { + $this->executeListenerHook('onError', array($componentName, $fileVersion, $e)); + throw $e; } catch (\Exception $e) { $warningMessages[] = $e->getMessage(); + + $this->executeListenerHook('onWarning', array($componentName, $fileVersion, $e)); } } - // to debug, create core/Updates/X.php, update the core/Version.php, throw an Exception in the try, and comment the following line - self::recordComponentSuccessfullyUpdated($componentName, $this->componentsWithNewVersion[$componentName][self::INDEX_NEW_VERSION]); + // to debug, create core/Updates/X.php, update the core/Version.php, throw an Exception in the try, and comment the following lines + $updatedVersion = $this->componentsWithNewVersion[$componentName][self::INDEX_NEW_VERSION]; + $this->markComponentSuccessfullyUpdated($componentName, $updatedVersion); + + $this->executeListenerHook('onComponentUpdateFinished', array($componentName, $updatedVersion, $warningMessages)); + return $warningMessages; } @@ -185,29 +283,34 @@ class Updater private function loadComponentsWithUpdateFile() { $componentsWithUpdateFile = array(); + foreach ($this->componentsWithNewVersion as $name => $versions) { $currentVersion = $versions[self::INDEX_CURRENT_VERSION]; $newVersion = $versions[self::INDEX_NEW_VERSION]; if ($name == 'core') { $pathToUpdates = $this->pathUpdateFileCore . '*.php'; + } elseif (ColumnUpdater::isDimensionComponent($name)) { + $componentsWithUpdateFile[$name][PIWIK_INCLUDE_PATH . '/core/Columns/Updater.php'] = $newVersion; } else { $pathToUpdates = sprintf($this->pathUpdateFilePlugins, $name) . '*.php'; } - $files = _glob($pathToUpdates); - if ($files == false) { - $files = array(); - } + if (!empty($pathToUpdates)) { + $files = _glob($pathToUpdates); + if ($files == false) { + $files = array(); + } - foreach ($files as $file) { - $fileVersion = basename($file, '.php'); - if ( // if the update is from a newer version - version_compare($currentVersion, $fileVersion) == -1 - // but we don't execute updates from non existing future releases - && version_compare($fileVersion, $newVersion) <= 0 - ) { - $componentsWithUpdateFile[$name][$file] = $fileVersion; + foreach ($files as $file) { + $fileVersion = basename($file, '.php'); + if (// if the update is from a newer version + version_compare($currentVersion, $fileVersion) == -1 + // but we don't execute updates from non existing future releases + && version_compare($fileVersion, $newVersion) <= 0 + ) { + $componentsWithUpdateFile[$name][$file] = $fileVersion; + } } } @@ -216,67 +319,245 @@ class Updater uasort($componentsWithUpdateFile[$name], "version_compare"); } else { // there are no update file => nothing to do, update to the new version is successful - self::recordComponentSuccessfullyUpdated($name, $newVersion); + $this->markComponentSuccessfullyUpdated($name, $newVersion); } } + return $componentsWithUpdateFile; } /** * Construct list of outdated components * + * @param string[] $componentsToCheck An array mapping component names to the latest locally available version. + * If the version is later than the currently installed version, the component + * must be upgraded. + * + * Example: `array('core' => '2.11.0')` * @throws \Exception * @return array array( componentName => array( oldVersion, newVersion), [...]) */ - public function getComponentsWithNewVersion() + public function getComponentsWithNewVersion($componentsToCheck) { $componentsToUpdate = array(); // we make sure core updates are processed before any plugin updates - if (isset($this->componentsToCheck['core'])) { - $coreVersions = $this->componentsToCheck['core']; - unset($this->componentsToCheck['core']); - $this->componentsToCheck = array_merge(array('core' => $coreVersions), $this->componentsToCheck); + if (isset($componentsToCheck['core'])) { + $coreVersions = $componentsToCheck['core']; + unset($componentsToCheck['core']); + $componentsToCheck = array_merge(array('core' => $coreVersions), $componentsToCheck); } - foreach ($this->componentsToCheck as $name => $version) { - try { - $currentVersion = Option::get(self::getNameInOptionTable($name)); - } catch (\Exception $e) { - // mysql error 1146: table doesn't exist - if (Db::get()->isErrNo($e, '1146')) { - // case when the option table is not yet created (before 0.2.10) - $currentVersion = false; - } else { - // failed for some other reason - throw $e; - } - } - if ($currentVersion === false) { - if ($name === 'core') { - // This should not happen - $currentVersion = Version::VERSION; - } else { - // When plugins have been installed since Piwik 2.0 this should not happen - // We "fix" the data for any plugin that may have been ported from Piwik 1.x - $currentVersion = $version; - } - self::recordComponentSuccessfullyUpdated($name, $currentVersion); + $recordedCoreVersion = $this->getCurrentComponentVersion('core'); + if (empty($recordedCoreVersion)) { + // This should not happen + $recordedCoreVersion = Version::VERSION; + $this->markComponentSuccessfullyUpdated('core', $recordedCoreVersion); + } + + foreach ($componentsToCheck as $name => $version) { + $currentVersion = $this->getCurrentComponentVersion($name); + + if (ColumnUpdater::isDimensionComponent($name)) { + $isComponentOutdated = $currentVersion !== $version; + } else { + // note: when versionCompare == 1, the version in the DB is newer, we choose to ignore + $isComponentOutdated = version_compare($currentVersion, $version) == -1; } - $versionCompare = version_compare($currentVersion, $version); - if ($versionCompare == -1) { + if ($isComponentOutdated || $currentVersion === false) { $componentsToUpdate[$name] = array( self::INDEX_CURRENT_VERSION => $currentVersion, self::INDEX_NEW_VERSION => $version ); - } else if ($versionCompare == 1) { - // the version in the DB is newest.. we choose to ignore } } + return $componentsToUpdate; } + /** + * Updates multiple components, while capturing & returning errors and warnings. + * + * @param string[] $componentsWithUpdateFile Component names mapped with arrays of update files. Same structure + * as the result of `getComponentsWithUpdateFile()`. + * @return array Information about the update process, including: + * + * * **warnings**: The list of warnings that occurred during the update process. + * * **errors**: The list of updater exceptions thrown during individual component updates. + * * **coreError**: True if an exception was thrown while updating core. + * * **deactivatedPlugins**: The list of plugins that were deactivated due to an error in the + * update process. + */ + public function updateComponents($componentsWithUpdateFile) + { + $warnings = array(); + $errors = array(); + $deactivatedPlugins = array(); + $coreError = false; + + if (!empty($componentsWithUpdateFile)) { + $currentAccess = Access::getInstance(); + $hasSuperUserAccess = $currentAccess->hasSuperUserAccess(); + + if (!$hasSuperUserAccess) { + $currentAccess->setSuperUserAccess(true); + } + + // if error in any core update, show message + help message + EXIT + // if errors in any plugins updates, show them on screen, disable plugins that errored + CONTINUE + // if warning in any core update or in any plugins update, show message + CONTINUE + // if no error or warning, success message + CONTINUE + foreach ($componentsWithUpdateFile as $name => $filenames) { + try { + $warnings = array_merge($warnings, $this->update($name)); + } catch (UpdaterErrorException $e) { + $errors[] = $e->getMessage(); + if ($name == 'core') { + $coreError = true; + break; + } elseif (\Piwik\Plugin\Manager::getInstance()->isPluginActivated($name)) { + \Piwik\Plugin\Manager::getInstance()->deactivatePlugin($name); + $deactivatedPlugins[] = $name; + } + } + } + + if (!$hasSuperUserAccess) { + $currentAccess->setSuperUserAccess(false); + } + } + + Filesystem::deleteAllCacheOnUpdate(); + + $result = array( + 'warnings' => $warnings, + 'errors' => $errors, + 'coreError' => $coreError, + 'deactivatedPlugins' => $deactivatedPlugins + ); + + /** + * Triggered after Piwik has been updated. + */ + Piwik::postEvent('CoreUpdater.update.end'); + + return $result; + } + + /** + * Returns any updates that should occur for core and all plugins that are both loaded and + * installed. Also includes updates required for dimensions. + * + * @return string[]|null Returns the result of `getComponentsWithUpdateFile()`. + */ + public function getComponentUpdates() + { + $componentsToCheck = array( + 'core' => Version::VERSION + ); + + $manager = \Piwik\Plugin\Manager::getInstance(); + $plugins = $manager->getLoadedPlugins(); + foreach ($plugins as $pluginName => $plugin) { + if ($manager->isPluginInstalled($pluginName)) { + $componentsToCheck[$pluginName] = $plugin->getVersion(); + } + } + + $columnsVersions = $this->columnsUpdater->getAllVersions($this); + foreach ($columnsVersions as $component => $version) { + $componentsToCheck[$component] = $version; + } + + $componentsWithUpdateFile = $this->getComponentsWithUpdateFile($componentsToCheck); + + if (count($componentsWithUpdateFile) == 0) { + $this->columnsUpdater->onNoUpdateAvailable($columnsVersions); + + if (!$this->hasNewVersion('core')) { + return null; + } + } + + return $componentsWithUpdateFile; + } + + /** + * Execute multiple migration queries from a single Update file. + * + * @param string $file The path to the Updates file. + * @param array $migrationQueries An array mapping SQL queries w/ one or more MySQL errors to ignore. + */ + public function executeMigrationQueries($file, $migrationQueries) + { + foreach ($migrationQueries as $update => $ignoreError) { + $this->executeSingleMigrationQuery($update, $ignoreError, $file); + } + } + + /** + * Execute a single migration query from an update file. + * + * @param string $migrationQuerySql The SQL to execute. + * @param int|int[]|null An optional error code or list of error codes to ignore. + * @param string $file The path to the Updates file. + */ + public function executeSingleMigrationQuery($migrationQuerySql, $errorToIgnore, $file) + { + try { + $this->executeListenerHook('onStartExecutingMigrationQuery', array($file, $migrationQuerySql)); + + Db::exec($migrationQuerySql); + } catch (\Exception $e) { + $this->handleUpdateQueryError($e, $migrationQuerySql, $errorToIgnore, $file); + } + + $this->executeListenerHook('onFinishedExecutingMigrationQuery', array($file, $migrationQuerySql)); + } + + /** + * Handle an update query error. + * + * @param \Exception $e The error that occurred. + * @param string $updateSql The SQL that was executed. + * @param int|int[]|null An optional error code or list of error codes to ignore. + * @param string $file The path to the Updates file. + * @throws \Exception + */ + public function handleUpdateQueryError(\Exception $e, $updateSql, $errorToIgnore, $file) + { + if (($errorToIgnore === false) + || !self::isDbErrorOneOf($e, $errorToIgnore) + ) { + $message = $file . ":\nError trying to execute the query '" . $updateSql . "'.\nThe error was: " . $e->getMessage(); + throw new UpdaterErrorException($message); + } + } + + private function executeListenerHook($hookName, $arguments) + { + foreach ($this->updateObservers as $listener) { + call_user_func_array(array($listener, $hookName), $arguments); + } + } + + private function executeSingleUpdateClass($className) + { + $update = StaticContainer::getContainer()->make($className); + try { + call_user_func(array($update, 'doUpdate'), $this); + } catch (\Exception $e) { + // if an Update file executes PHP statements directly, DB exceptions be handled by executeSingleMigrationQuery, so + // make sure to check for them here + if ($e instanceof Zend_Db_Exception) { + throw new UpdaterErrorException($e->getMessage(), $e->getCode(), $e); + } else { + throw $e; + } + } + } + /** * Performs database update(s) * @@ -284,11 +565,9 @@ class Updater * @param array $sqlarray An array of SQL queries to be executed * @throws UpdaterErrorException */ - static function updateDatabase($file, $sqlarray) + public static function updateDatabase($file, $sqlarray) { - foreach ($sqlarray as $update => $ignoreError) { - self::executeMigrationQuery($update, $ignoreError, $file); - } + self::$activeInstance->executeMigrationQueries($file, $sqlarray); } /** @@ -300,11 +579,7 @@ class Updater */ public static function executeMigrationQuery($updateSql, $errorToIgnore, $file) { - try { - Db::exec($updateSql); - } catch (\Exception $e) { - self::handleQueryError($e, $updateSql, $errorToIgnore, $file); - } + self::$activeInstance->executeSingleMigrationQuery($updateSql, $errorToIgnore, $file); } /** @@ -314,15 +589,61 @@ class Updater * @param string $updateSql Update SQL query. * @param int|false $errorToIgnore A MySQL error code to ignore. * @param string $file The Update file that's calling this method. + * @throws UpdaterErrorException */ public static function handleQueryError($e, $updateSql, $errorToIgnore, $file) { - if (($errorToIgnore === false) - || !Db::get()->isErrNo($e, $errorToIgnore) - ) { - $message = $file . ":\nError trying to execute the query '" . $updateSql . "'.\nThe error was: " . $e->getMessage(); - throw new UpdaterErrorException($message); + self::$activeInstance->handleQueryError($e, $updateSql, $errorToIgnore, $file); + } + + /** + * Record version of successfully completed component update + * + * @param string $name + * @param string $version + */ + public static function recordComponentSuccessfullyUpdated($name, $version) + { + self::$activeInstance->markComponentSuccessfullyUpdated($name, $version); + } + + /** + * Retrieve the current version of a recorded component + * @param string $name + * @return false|string + * @throws \Exception + */ + public static function getCurrentRecordedComponentVersion($name) + { + return self::$activeInstance->getCurrentComponentVersion($name); + } + + /** + * Returns whether an exception is a DB error with a code in the $errorCodesToIgnore list. + * + * @param int $error + * @param int|int[] $errorCodesToIgnore + * @return boolean + */ + public static function isDbErrorOneOf($error, $errorCodesToIgnore) + { + $errorCodesToIgnore = is_array($errorCodesToIgnore) ? $errorCodesToIgnore : array($errorCodesToIgnore); + foreach ($errorCodesToIgnore as $code) { + if (Db::get()->isErrNo($error, $code)) { + return true; + } } + return false; + } + + /** + * Returns the flag name to use in the option table to record current schema version + * @param string $name + * @return string + */ + private static function getNameInOptionTable($name) + { + return 'version_' . $name; } } diff --git a/www/analytics/core/Updater/UpdateObserver.php b/www/analytics/core/Updater/UpdateObserver.php new file mode 100644 index 00000000..e639ecdb --- /dev/null +++ b/www/analytics/core/Updater/UpdateObserver.php @@ -0,0 +1,114 @@ +executeMigrationQueries(__FILE__, $this->getMigrationQueries()); + * } * * @example core/Updates/0.4.2.php */ abstract class Updates { /** - * Return SQL to be executed in this update - * - * @return array( - * 'ALTER .... ' => '1234', // if the query fails, it will be ignored if the error code is 1234 - * 'ALTER .... ' => false, // if an error occurs, the update will stop and fail - * // and user will have to manually run the query - * ) + * @deprecated since v2.12.0 use getMigrationQueries() instead */ - static function getSql() + public static function getSql() { return array(); } /** - * Incremental version update + * @deprecated since v2.12.0 use doUpdate() instead */ - static function update() + public static function update() { } + /** + * Return SQL to be executed in this update. + * + * SQL queries should be defined here, instead of in `doUpdate()`, since this method is used + * in the `core:update` command when displaying the queries an update will run. If you execute + * queries directly in `doUpdate()`, they won't be displayed to the user. + * + * @param Updater $updater + * @return array ``` + * array( + * 'ALTER .... ' => '1234', // if the query fails, it will be ignored if the error code is 1234 + * 'ALTER .... ' => false, // if an error occurs, the update will stop and fail + * // and user will have to manually run the query + * ) + * ``` + * @api + */ + public function getMigrationQueries(Updater $updater) + { + return static::getSql(); + } + + /** + * Perform the incremental version update. + * + * This method should preform all updating logic. If you define queries in an overridden `getMigrationQueries()` + * method, you must call {@link Updater::executeMigrationQueries()} here. + * + * See {@link Updates} for an example. + * + * @param Updater $updater + * @api + */ + public function doUpdate(Updater $updater) + { + static::update(); + } + /** * Tell the updater that this is a major update. * Leads to a more visible notice. * + * NOTE to release manager: Remember to mention in the Changelog + * that this update contains major DB upgrades and will take some time! + * * @return bool */ - static function isMajorUpdate() + public static function isMajorUpdate() { return false; } /** - * Helper method to enable maintenance mode during large updates + * Enables maintenance mode. Should be used for updates where Piwik will be unavailable + * for a large amount of time. */ - static function enableMaintenanceMode() + public static function enableMaintenanceMode() { $config = Config::getInstance(); - $config->init(); $tracker = $config->Tracker; $tracker['record_statistics'] = 0; @@ -67,12 +118,11 @@ abstract class Updates } /** - * Helper method to disable maintenance mode after large updates + * Helper method to disable maintenance mode after large updates. */ - static function disableMaintenanceMode() + public static function disableMaintenanceMode() { $config = Config::getInstance(); - $config->init(); $tracker = $config->Tracker; $tracker['record_statistics'] = 1; @@ -88,7 +138,6 @@ abstract class Updates public static function deletePluginFromConfigFile($pluginToDelete) { $config = Config::getInstance(); - $config->init(); if (isset($config->Plugins['Plugins'])) { $plugins = $config->Plugins['Plugins']; if (($key = array_search($pluginToDelete, $plugins)) !== false) { diff --git a/www/analytics/core/Updates/0.2.10.php b/www/analytics/core/Updates/0.2.10.php index bb3bba97..a7ae47c5 100644 --- a/www/analytics/core/Updates/0.2.10.php +++ b/www/analytics/core/Updates/0.2.10.php @@ -1,6 +1,6 @@ false, + )' => 1050, // 0.1.7 [463] - 'ALTER IGNORE TABLE `' . Common::prefixTable('log_visit') . '` - CHANGE `location_provider` `location_provider` VARCHAR( 100 ) DEFAULT NULL' => '1054', + 'ALTER TABLE `' . Common::prefixTable('log_visit') . '` + CHANGE `location_provider` `location_provider` VARCHAR( 100 ) DEFAULT NULL' => 1054, // 0.1.7 [470] 'ALTER TABLE `' . Common::prefixTable('logger_api_call') . '` CHANGE `parameter_names_default_values` `parameter_names_default_values` TEXT, CHANGE `parameter_values` `parameter_values` TEXT, - CHANGE `returned_value` `returned_value` TEXT' => false, + CHANGE `returned_value` `returned_value` TEXT' => array(1054, 1146), 'ALTER TABLE `' . Common::prefixTable('logger_error') . '` - CHANGE `message` `message` TEXT' => false, + CHANGE `message` `message` TEXT' => array(1054, 1146), 'ALTER TABLE `' . Common::prefixTable('logger_exception') . '` - CHANGE `message` `message` TEXT' => false, + CHANGE `message` `message` TEXT' => array(1054, 1146), 'ALTER TABLE `' . Common::prefixTable('logger_message') . '` - CHANGE `message` `message` TEXT' => false, + CHANGE `message` `message` TEXT' => 1054, // 0.2.2 [489] - 'ALTER IGNORE TABLE `' . Common::prefixTable('site') . '` - CHANGE `feedburnerName` `feedburnerName` VARCHAR( 100 ) DEFAULT NULL' => '1054', + 'ALTER TABLE `' . Common::prefixTable('site') . '` + CHANGE `feedburnerName` `feedburnerName` VARCHAR( 100 ) DEFAULT NULL' => 1054, ); } - static function update() + public function doUpdate(Updater $updater) { - Updater::updateDatabase(__FILE__, self::getSql()); - - $obsoleteFile = '/plugins/ExamplePlugin/API.php'; - if (file_exists(PIWIK_INCLUDE_PATH . $obsoleteFile)) { - @unlink(PIWIK_INCLUDE_PATH . $obsoleteFile); - } + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); $obsoleteDirectories = array( '/plugins/AdminHome', diff --git a/www/analytics/core/Updates/0.2.12.php b/www/analytics/core/Updates/0.2.12.php index ff8fa18b..de9eb2a0 100644 --- a/www/analytics/core/Updates/0.2.12.php +++ b/www/analytics/core/Updates/0.2.12.php @@ -1,6 +1,6 @@ false, 'ALTER TABLE `' . Common::prefixTable('log_visit') . '` - DROP `config_color_depth`' => false, + DROP `config_color_depth`' => 1091, // 0.2.12 [673] // Note: requires INDEX privilege - 'DROP INDEX index_idaction ON `' . Common::prefixTable('log_action') . '`' => '1091', + 'DROP INDEX index_idaction ON `' . Common::prefixTable('log_action') . '`' => 1091, ); } - static function update() + public function doUpdate(Updater $updater) { - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/0.2.13.php b/www/analytics/core/Updates/0.2.13.php index 93228ad6..6f13d753 100644 --- a/www/analytics/core/Updates/0.2.13.php +++ b/www/analytics/core/Updates/0.2.13.php @@ -1,6 +1,6 @@ false, @@ -27,12 +27,12 @@ class Updates_0_2_13 extends Updates option_value LONGTEXT NOT NULL , autoload TINYINT NOT NULL DEFAULT '1', PRIMARY KEY ( option_name ) - )" => false, + )" => 1050, ); } - static function update() + public function doUpdate(Updater $updater) { - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/0.2.24.php b/www/analytics/core/Updates/0.2.24.php index e627f64f..970f961f 100644 --- a/www/analytics/core/Updates/0.2.24.php +++ b/www/analytics/core/Updates/0.2.24.php @@ -1,6 +1,6 @@ false, + ON ' . Common::prefixTable('log_action') . ' (type, name(15))' => 1072, 'CREATE INDEX index_idsite_date - ON ' . Common::prefixTable('log_visit') . ' (idsite, visit_server_date)' => false, - 'DROP INDEX index_idsite ON ' . Common::prefixTable('log_visit') => false, - 'DROP INDEX index_visit_server_date ON ' . Common::prefixTable('log_visit') => false, + ON ' . Common::prefixTable('log_visit') . ' (idsite, visit_server_date)' => 1072, + 'DROP INDEX index_idsite ON ' . Common::prefixTable('log_visit') => 1091, + 'DROP INDEX index_visit_server_date ON ' . Common::prefixTable('log_visit') => 1091, ); } - static function update() + public function doUpdate(Updater $updater) { - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/0.2.27.php b/www/analytics/core/Updates/0.2.27.php index 4b505194..b18ea37e 100644 --- a/www/analytics/core/Updates/0.2.27.php +++ b/www/analytics/core/Updates/0.2.27.php @@ -1,6 +1,6 @@ false, + ADD `visit_goal_converted` VARCHAR( 1 ) NOT NULL AFTER `visit_total_time`' => 1060, // 0.2.27 [826] - 'ALTER IGNORE TABLE `' . Common::prefixTable('log_visit') . '` - CHANGE `visit_goal_converted` `visit_goal_converted` TINYINT(1) NOT NULL' => false, + 'ALTER TABLE `' . Common::prefixTable('log_visit') . '` + CHANGE `visit_goal_converted` `visit_goal_converted` TINYINT(1) NOT NULL' => 1060, 'CREATE TABLE `' . Common::prefixTable('goal') . "` ( `idsite` int(11) NOT NULL, @@ -38,7 +38,7 @@ class Updates_0_2_27 extends Updates `revenue` float NOT NULL, `deleted` tinyint(4) NOT NULL default '0', PRIMARY KEY (`idsite`,`idgoal`) - )" => false, + )" => 1050, 'CREATE TABLE `' . Common::prefixTable('log_conversion') . '` ( `idvisit` int(10) unsigned NOT NULL, @@ -49,7 +49,6 @@ class Updates_0_2_27 extends Updates `idaction` int(11) NOT NULL, `idlink_va` int(11) NOT NULL, `referer_idvisit` int(10) unsigned default NULL, - `referer_visit_server_date` date default NULL, `referer_type` int(10) unsigned default NULL, `referer_name` varchar(70) default NULL, `referer_keyword` varchar(255) default NULL, @@ -61,21 +60,21 @@ class Updates_0_2_27 extends Updates `revenue` float default NULL, PRIMARY KEY (`idvisit`,`idgoal`), KEY `index_idsite_date` (`idsite`,`visit_server_date`) - )' => false, + )' => 1050, ); $tables = DbHelper::getTablesInstalled(); foreach ($tables as $tableName) { if (preg_match('/archive_/', $tableName) == 1) { - $sqlarray['CREATE INDEX index_all ON ' . $tableName . ' (`idsite`,`date1`,`date2`,`name`,`ts_archived`)'] = false; + $sqlarray['CREATE INDEX index_all ON ' . $tableName . ' (`idsite`,`date1`,`date2`,`name`,`ts_archived`)'] = 1072; } } return $sqlarray; } - static function update() + public function doUpdate(Updater $updater) { - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/0.2.32.php b/www/analytics/core/Updates/0.2.32.php index 7f697cc4..9d844224 100644 --- a/www/analytics/core/Updates/0.2.32.php +++ b/www/analytics/core/Updates/0.2.32.php @@ -1,6 +1,6 @@ executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/0.2.33.php b/www/analytics/core/Updates/0.2.33.php index 0b4d281f..40cb9251 100644 --- a/www/analytics/core/Updates/0.2.33.php +++ b/www/analytics/core/Updates/0.2.33.php @@ -1,6 +1,6 @@ executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/0.2.34.php b/www/analytics/core/Updates/0.2.34.php deleted file mode 100644 index 49c128f1..00000000 --- a/www/analytics/core/Updates/0.2.34.php +++ /dev/null @@ -1,28 +0,0 @@ -getAllSitesId(); - Cache::regenerateCacheWebsiteAttributes($allSiteIds); - } -} diff --git a/www/analytics/core/Updates/0.2.35.php b/www/analytics/core/Updates/0.2.35.php index 4c0c53ff..01d5854b 100644 --- a/www/analytics/core/Updates/0.2.35.php +++ b/www/analytics/core/Updates/0.2.35.php @@ -1,6 +1,6 @@ executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/0.2.37.php b/www/analytics/core/Updates/0.2.37.php index 47c8e78d..8fbcbc00 100644 --- a/www/analytics/core/Updates/0.2.37.php +++ b/www/analytics/core/Updates/0.2.37.php @@ -1,6 +1,6 @@ executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/0.4.1.php b/www/analytics/core/Updates/0.4.1.php index b4562d4f..077e5ef6 100644 --- a/www/analytics/core/Updates/0.4.1.php +++ b/www/analytics/core/Updates/0.4.1.php @@ -1,6 +1,6 @@ executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/0.4.2.php b/www/analytics/core/Updates/0.4.2.php index 15039367..d567fe04 100644 --- a/www/analytics/core/Updates/0.4.2.php +++ b/www/analytics/core/Updates/0.4.2.php @@ -1,6 +1,6 @@ '1060', + ADD `config_java` TINYINT(1) NOT NULL AFTER `config_flash`' => 1060, 'ALTER TABLE `' . Common::prefixTable('log_visit') . '` - ADD `config_quicktime` TINYINT(1) NOT NULL AFTER `config_director`' => '1060', + ADD `config_quicktime` TINYINT(1) NOT NULL AFTER `config_director`' => 1060, 'ALTER TABLE `' . Common::prefixTable('log_visit') . '` ADD `config_gears` TINYINT(1) NOT NULL AFTER `config_windowsmedia`, - ADD `config_silverlight` TINYINT(1) NOT NULL AFTER `config_gears`' => false, + ADD `config_silverlight` TINYINT(1) NOT NULL AFTER `config_gears`' => 1060, ); } // when restoring (possibly) previousy dropped columns, ignore mysql code error 1060: duplicate column - static function update() + public function doUpdate(Updater $updater) { - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/0.4.4.php b/www/analytics/core/Updates/0.4.4.php index 44cc1690..4aff5254 100644 --- a/www/analytics/core/Updates/0.4.4.php +++ b/www/analytics/core/Updates/0.4.4.php @@ -1,6 +1,6 @@ false, + SET location_ip=location_ip+CAST(POW(2,32) AS UNSIGNED) WHERE location_ip < 0' => false, 'ALTER TABLE `' . Common::prefixTable('log_visit') . '` - CHANGE `location_ip` `location_ip` BIGINT UNSIGNED NOT NULL' => false, + CHANGE `location_ip` `location_ip` BIGINT UNSIGNED NOT NULL' => 1054, 'UPDATE `' . Common::prefixTable('logger_api_call') . '` - SET caller_ip=caller_ip+CAST(POW(2,32) AS UNSIGNED) WHERE caller_ip < 0' => false, + SET caller_ip=caller_ip+CAST(POW(2,32) AS UNSIGNED) WHERE caller_ip < 0' => 1146, 'ALTER TABLE `' . Common::prefixTable('logger_api_call') . '` - CHANGE `caller_ip` `caller_ip` BIGINT UNSIGNED' => false, + CHANGE `caller_ip` `caller_ip` BIGINT UNSIGNED' => 1146, ); } - static function update() + public function doUpdate(Updater $updater) { - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/0.5.4.php b/www/analytics/core/Updates/0.5.4.php index 235391db..59c787c1 100644 --- a/www/analytics/core/Updates/0.5.4.php +++ b/www/analytics/core/Updates/0.5.4.php @@ -1,6 +1,6 @@ executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/0.5.5.php b/www/analytics/core/Updates/0.5.5.php index aaacf0e9..4334a34d 100644 --- a/www/analytics/core/Updates/0.5.5.php +++ b/www/analytics/core/Updates/0.5.5.php @@ -1,6 +1,6 @@ '1091', - 'CREATE INDEX index_idsite_date_config ON ' . Common::prefixTable('log_visit') . ' (idsite, visit_server_date, config_md5config(8))' => '1061', + 'DROP INDEX index_idsite_date ON ' . Common::prefixTable('log_visit') => 1091, + 'CREATE INDEX index_idsite_date_config ON ' . Common::prefixTable('log_visit') . ' (idsite, visit_server_date, config_md5config(8))' => array(1061,1072), ); $tables = DbHelper::getTablesInstalled(); foreach ($tables as $tableName) { if (preg_match('/archive_/', $tableName) == 1) { - $sqlarray['DROP INDEX index_all ON ' . $tableName] = '1091'; + $sqlarray['DROP INDEX index_all ON ' . $tableName] = 1091; } if (preg_match('/archive_numeric_/', $tableName) == 1) { - $sqlarray['CREATE INDEX index_idsite_dates_period ON ' . $tableName . ' (idsite, date1, date2, period)'] = '1061'; + $sqlarray['CREATE INDEX index_idsite_dates_period ON ' . $tableName . ' (idsite, date1, date2, period)'] = 1061; } } return $sqlarray; } - static function update() + public function doUpdate(Updater $updater) { - Updater::updateDatabase(__FILE__, self::getSql()); - + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/0.5.php b/www/analytics/core/Updates/0.5.php index f90f7f48..00c6ed7a 100644 --- a/www/analytics/core/Updates/0.5.php +++ b/www/analytics/core/Updates/0.5.php @@ -1,6 +1,6 @@ '1060', - 'ALTER TABLE ' . Common::prefixTable('log_visit') . ' CHANGE visit_exit_idaction visit_exit_idaction_url INTEGER(11) NOT NULL;' => '1054', - 'ALTER TABLE ' . Common::prefixTable('log_visit') . ' CHANGE visit_entry_idaction visit_entry_idaction_url INTEGER(11) NOT NULL;' => '1054', - 'ALTER TABLE ' . Common::prefixTable('log_link_visit_action') . ' CHANGE `idaction_ref` `idaction_url_ref` INTEGER(10) UNSIGNED NOT NULL;' => '1054', - 'ALTER TABLE ' . Common::prefixTable('log_link_visit_action') . ' CHANGE `idaction` `idaction_url` INTEGER(10) UNSIGNED NOT NULL;' => '1054', - 'ALTER TABLE ' . Common::prefixTable('log_link_visit_action') . ' ADD COLUMN `idaction_name` INTEGER(10) UNSIGNED AFTER `idaction_url_ref`;' => '1060', - 'ALTER TABLE ' . Common::prefixTable('log_conversion') . ' CHANGE `idaction` `idaction_url` INTEGER(11) UNSIGNED NOT NULL;' => '1054', + 'ALTER TABLE ' . Common::prefixTable('log_action') . ' ADD COLUMN `hash` INTEGER(10) UNSIGNED NOT NULL AFTER `name`;' => 1060, + 'ALTER TABLE ' . Common::prefixTable('log_visit') . ' CHANGE visit_exit_idaction visit_exit_idaction_url INTEGER(11) NOT NULL;' => 1054, + 'ALTER TABLE ' . Common::prefixTable('log_visit') . ' CHANGE visit_entry_idaction visit_entry_idaction_url INTEGER(11) NOT NULL;' => 1054, + 'ALTER TABLE ' . Common::prefixTable('log_link_visit_action') . ' CHANGE `idaction_ref` `idaction_url_ref` INTEGER(10) UNSIGNED NOT NULL;' => 1054, + 'ALTER TABLE ' . Common::prefixTable('log_link_visit_action') . ' CHANGE `idaction` `idaction_url` INTEGER(10) UNSIGNED NOT NULL;' => 1054, + 'ALTER TABLE ' . Common::prefixTable('log_link_visit_action') . ' ADD COLUMN `idaction_name` INTEGER(10) UNSIGNED AFTER `idaction_url_ref`;' => 1060, + 'ALTER TABLE ' . Common::prefixTable('log_conversion') . ' CHANGE `idaction` `idaction_url` INTEGER(11) UNSIGNED NOT NULL;' => 1054, 'UPDATE ' . Common::prefixTable('log_action') . ' SET `hash` = CRC32(name);' => false, - 'CREATE INDEX index_type_hash ON ' . Common::prefixTable('log_action') . ' (type, hash);' => '1061', - 'DROP INDEX index_type_name ON ' . Common::prefixTable('log_action') . ';' => '1091', + 'CREATE INDEX index_type_hash ON ' . Common::prefixTable('log_action') . ' (type, hash);' => 1061, + 'DROP INDEX index_type_name ON ' . Common::prefixTable('log_action') . ';' => 1091, ); } - static function update() + public function doUpdate(Updater $updater) { - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/0.6-rc1.php b/www/analytics/core/Updates/0.6-rc1.php index 43ab8e4e..1c6a4f31 100644 --- a/www/analytics/core/Updates/0.6-rc1.php +++ b/www/analytics/core/Updates/0.6-rc1.php @@ -1,6 +1,6 @@ false, - 'ALTER TABLE ' . Common::prefixTable('site') . ' CHANGE ts_created ts_created TIMESTAMP NULL' => false, - 'ALTER TABLE ' . Common::prefixTable('site') . ' ADD `timezone` VARCHAR( 50 ) NOT NULL AFTER `ts_created` ;' => false, - 'UPDATE ' . Common::prefixTable('site') . ' SET `timezone` = "' . $defaultTimezone . '";' => false, - 'ALTER TABLE ' . Common::prefixTable('site') . ' ADD currency CHAR( 3 ) NOT NULL AFTER `timezone` ;' => false, - 'UPDATE ' . Common::prefixTable('site') . ' SET `currency` = "' . $defaultCurrency . '";' => false, - 'ALTER TABLE ' . Common::prefixTable('site') . ' ADD `excluded_ips` TEXT NOT NULL AFTER `currency` ;' => false, - 'ALTER TABLE ' . Common::prefixTable('site') . ' ADD excluded_parameters VARCHAR( 255 ) NOT NULL AFTER `excluded_ips` ;' => false, - 'ALTER TABLE ' . Common::prefixTable('log_visit') . ' ADD INDEX `index_idsite_datetime_config` ( `idsite` , `visit_last_action_time` , `config_md5config` ( 8 ) ) ;' => false, - 'ALTER TABLE ' . Common::prefixTable('log_visit') . ' ADD INDEX index_idsite_idvisit (idsite, idvisit) ;' => false, - 'ALTER TABLE ' . Common::prefixTable('log_conversion') . ' DROP INDEX index_idsite_date' => false, - 'ALTER TABLE ' . Common::prefixTable('log_conversion') . ' DROP visit_server_date;' => false, - 'ALTER TABLE ' . Common::prefixTable('log_conversion') . ' ADD INDEX index_idsite_datetime ( `idsite` , `server_time` )' => false, + 'ALTER TABLE ' . Common::prefixTable('user') . ' CHANGE date_registered date_registered TIMESTAMP NULL' => 1054, + 'ALTER TABLE ' . Common::prefixTable('site') . ' CHANGE ts_created ts_created TIMESTAMP NULL' => 1054, + 'ALTER TABLE ' . Common::prefixTable('site') . ' ADD `timezone` VARCHAR( 50 ) NOT NULL AFTER `ts_created` ;' => 1060, + 'UPDATE ' . Common::prefixTable('site') . ' SET `timezone` = "' . $defaultTimezone . '";' => 1060, + 'ALTER TABLE ' . Common::prefixTable('site') . ' ADD currency CHAR( 3 ) NOT NULL AFTER `timezone` ;' => 1060, + 'UPDATE ' . Common::prefixTable('site') . ' SET `currency` = "' . $defaultCurrency . '";' => 1060, + 'ALTER TABLE ' . Common::prefixTable('site') . ' ADD `excluded_ips` TEXT NOT NULL AFTER `currency` ;' => 1060, + 'ALTER TABLE ' . Common::prefixTable('site') . ' ADD excluded_parameters VARCHAR( 255 ) NOT NULL AFTER `excluded_ips` ;' => 1060, + 'ALTER TABLE ' . Common::prefixTable('log_visit') . ' ADD INDEX `index_idsite_datetime_config` ( `idsite` , `visit_last_action_time` , `config_md5config` ( 8 ) ) ;' => array(1061, 1072), + 'ALTER TABLE ' . Common::prefixTable('log_visit') . ' ADD INDEX index_idsite_idvisit (idsite, idvisit) ;' => array(1061, 1072), + 'ALTER TABLE ' . Common::prefixTable('log_conversion') . ' DROP INDEX index_idsite_date' => 1091, + 'ALTER TABLE ' . Common::prefixTable('log_conversion') . ' DROP visit_server_date;' => 1091, + 'ALTER TABLE ' . Common::prefixTable('log_conversion') . ' ADD INDEX index_idsite_datetime ( `idsite` , `server_time` )' => array(1072, 1061), ); } - static function update() + public function doUpdate(Updater $updater) { // first we disable the plugins and keep an array of warnings messages $pluginsToDisableMessage = array( - 'SearchEnginePosition' => "SearchEnginePosition plugin was disabled, because it is not compatible with the new Piwik 0.6. \n You can download the latest version of the plugin, compatible with Piwik 0.6.\nClick here.", - 'GeoIP' => "GeoIP plugin was disabled, because it is not compatible with the new Piwik 0.6. \nYou can download the latest version of the plugin, compatible with Piwik 0.6.\nClick here." + 'SearchEnginePosition' => "SearchEnginePosition plugin was disabled, because it is not compatible with the new Piwik 0.6. \n You can download the latest version of the plugin, compatible with Piwik 0.6.\nClick here.", + 'GeoIP' => "GeoIP plugin was disabled, because it is not compatible with the new Piwik 0.6. \nYou can download the latest version of the plugin, compatible with Piwik 0.6.\nClick here." ); $disabledPlugins = array(); foreach ($pluginsToDisableMessage as $pluginToDisable => $warningMessage) { @@ -54,7 +54,7 @@ class Updates_0_6_rc1 extends Updates } // Run the SQL - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); // Outputs warning message, pointing users to the plugin download page if (!empty($disabledPlugins)) { diff --git a/www/analytics/core/Updates/0.6.2.php b/www/analytics/core/Updates/0.6.2.php deleted file mode 100644 index 15236ed0..00000000 --- a/www/analytics/core/Updates/0.6.2.php +++ /dev/null @@ -1,47 +0,0 @@ -getAllSitesId(); - Cache::regenerateCacheWebsiteAttributes($allSiteIds); - } -} diff --git a/www/analytics/core/Updates/0.6.3.php b/www/analytics/core/Updates/0.6.3.php index 87ef2d27..7716fb7c 100644 --- a/www/analytics/core/Updates/0.6.3.php +++ b/www/analytics/core/Updates/0.6.3.php @@ -1,6 +1,6 @@ false, + CHANGE `location_ip` `location_ip` INT UNSIGNED NOT NULL' => 1054, 'ALTER TABLE `' . Common::prefixTable('logger_api_call') . '` - CHANGE `caller_ip` `caller_ip` INT UNSIGNED' => false, + CHANGE `caller_ip` `caller_ip` INT UNSIGNED' => array(1054, 1146), ); } - static function update() + public function doUpdate(Updater $updater) { $config = Config::getInstance(); $dbInfos = $config->database; @@ -44,6 +44,6 @@ class Updates_0_6_3 extends Updates } } - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/0.7.php b/www/analytics/core/Updates/0.7.php index 3c6daad6..212ab2d1 100644 --- a/www/analytics/core/Updates/0.7.php +++ b/www/analytics/core/Updates/0.7.php @@ -1,6 +1,6 @@ executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/0.9.1.php b/www/analytics/core/Updates/0.9.1.php index 5b11099b..323ef219 100644 --- a/www/analytics/core/Updates/0.9.1.php +++ b/www/analytics/core/Updates/0.9.1.php @@ -1,6 +1,6 @@ false, 'UPDATE `' . Common::prefixTable('option') . '` - SET option_value = "UTC" - WHERE option_name = "SitesManager_DefaultTimezone" + SET option_value = "UTC" + WHERE option_name = "SitesManager_DefaultTimezone" AND option_value IN (' . $timezoneList . ')' => false, ); } - static function update() + public function doUpdate(Updater $updater) { if (SettingsServer::isTimezoneSupportEnabled()) { - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } } diff --git a/www/analytics/core/Updates/1.1.php b/www/analytics/core/Updates/1.1.php index df58645c..e977099d 100644 --- a/www/analytics/core/Updates/1.1.php +++ b/www/analytics/core/Updates/1.1.php @@ -1,6 +1,6 @@ activatePlugin('MobileMessaging'); diff --git a/www/analytics/core/Updates/1.10.1.php b/www/analytics/core/Updates/1.10.1.php index e6051a7b..d6cc58b1 100755 --- a/www/analytics/core/Updates/1.10.1.php +++ b/www/analytics/core/Updates/1.10.1.php @@ -1,6 +1,6 @@ activatePlugin('Overlay'); diff --git a/www/analytics/core/Updates/1.10.2-b1.php b/www/analytics/core/Updates/1.10.2-b1.php index bf58008b..fcf35d96 100755 --- a/www/analytics/core/Updates/1.10.2-b1.php +++ b/www/analytics/core/Updates/1.10.2-b1.php @@ -1,6 +1,6 @@ executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/1.10.2-b2.php b/www/analytics/core/Updates/1.10.2-b2.php index de177432..9a2b1c34 100644 --- a/www/analytics/core/Updates/1.10.2-b2.php +++ b/www/analytics/core/Updates/1.10.2-b2.php @@ -1,6 +1,6 @@ executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/1.11-b1.php b/www/analytics/core/Updates/1.11-b1.php index dc118714..ffc222ad 100644 --- a/www/analytics/core/Updates/1.11-b1.php +++ b/www/analytics/core/Updates/1.11-b1.php @@ -1,6 +1,6 @@ activatePlugin('UserCountryMap'); diff --git a/www/analytics/core/Updates/1.12-b1.php b/www/analytics/core/Updates/1.12-b1.php index 5318a7cb..9c2271be 100644 --- a/www/analytics/core/Updates/1.12-b1.php +++ b/www/analytics/core/Updates/1.12-b1.php @@ -1,6 +1,6 @@ executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } - } diff --git a/www/analytics/core/Updates/1.12-b15.php b/www/analytics/core/Updates/1.12-b15.php index 96a0696c..8769f981 100644 --- a/www/analytics/core/Updates/1.12-b15.php +++ b/www/analytics/core/Updates/1.12-b15.php @@ -1,6 +1,6 @@ activatePlugin('SegmentEditor'); diff --git a/www/analytics/core/Updates/1.12-b16.php b/www/analytics/core/Updates/1.12-b16.php index d1090674..26fde0ba 100644 --- a/www/analytics/core/Updates/1.12-b16.php +++ b/www/analytics/core/Updates/1.12-b16.php @@ -1,6 +1,6 @@ executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/1.2-rc1.php b/www/analytics/core/Updates/1.2-rc1.php index cb46b767..db09aae1 100644 --- a/www/analytics/core/Updates/1.2-rc1.php +++ b/www/analytics/core/Updates/1.2-rc1.php @@ -1,6 +1,6 @@ array(1054, 1091), + 'ALTER TABLE `' . Common::prefixTable('log_visit') . '` ADD `visit_entry_idaction_name` INT UNSIGNED NOT NULL AFTER `visit_entry_idaction_url`, ADD `visit_exit_idaction_name` INT UNSIGNED NOT NULL AFTER `visit_exit_idaction_url`, - CHANGE `visit_exit_idaction_url` `visit_exit_idaction_url` INT UNSIGNED NOT NULL, + CHANGE `visit_exit_idaction_url` `visit_exit_idaction_url` INT UNSIGNED NOT NULL, CHANGE `visit_entry_idaction_url` `visit_entry_idaction_url` INT UNSIGNED NOT NULL, CHANGE `referer_type` `referer_type` TINYINT UNSIGNED NULL DEFAULT NULL, - ADD `idvisitor` BINARY(8) NOT NULL AFTER `idsite`, ADD visitor_count_visits SMALLINT(5) UNSIGNED NOT NULL AFTER `visitor_returning`, ADD visitor_days_since_last SMALLINT(5) UNSIGNED NOT NULL, - ADD visitor_days_since_first SMALLINT(5) UNSIGNED NOT NULL, - ADD `config_id` BINARY(8) NOT NULL AFTER `config_md5config` - ' => false, + ADD visitor_days_since_first SMALLINT(5) UNSIGNED NOT NULL + ' => 1060, 'ALTER TABLE `' . Common::prefixTable('log_visit') . '` ADD custom_var_k1 VARCHAR(100) DEFAULT NULL, ADD custom_var_v1 VARCHAR(100) DEFAULT NULL, @@ -49,19 +51,23 @@ class Updates_1_2_rc1 extends Updates ADD custom_var_v5 VARCHAR(100) DEFAULT NULL ' => 1060, 'ALTER TABLE `' . Common::prefixTable('log_link_visit_action') . '` - ADD `idsite` INT( 10 ) UNSIGNED NOT NULL AFTER `idlink_va` , - ADD `server_time` DATETIME AFTER `idsite`, + ADD `idsite` INT( 10 ) UNSIGNED NOT NULL AFTER `idlink_va` , ADD `idvisitor` BINARY(8) NOT NULL AFTER `idsite`, - ADD `idaction_name_ref` INT UNSIGNED NOT NULL AFTER `idaction_name`, + ADD `idaction_name_ref` INT UNSIGNED NOT NULL AFTER `idaction_name` + ' => 1060, + 'ALTER TABLE `' . Common::prefixTable('log_link_visit_action') . '` + ADD `server_time` DATETIME AFTER `idsite`, ADD INDEX `index_idsite_servertime` ( `idsite` , `server_time` ) - ' => false, + ' => 1060, 'ALTER TABLE `' . Common::prefixTable('log_conversion') . '` DROP `referer_idvisit`, - ADD `idvisitor` BINARY(8) NOT NULL AFTER `idsite`, + ADD `idvisitor` BINARY(8) NOT NULL AFTER `idsite` + ' => array(1060, 1091), + 'ALTER TABLE `' . Common::prefixTable('log_conversion') . '` ADD visitor_count_visits SMALLINT(5) UNSIGNED NOT NULL, ADD visitor_days_since_first SMALLINT(5) UNSIGNED NOT NULL - ' => false, + ' => 1060, 'ALTER TABLE `' . Common::prefixTable('log_conversion') . '` ADD custom_var_k1 VARCHAR(100) DEFAULT NULL, ADD custom_var_v1 VARCHAR(100) DEFAULT NULL, @@ -73,36 +79,36 @@ class Updates_1_2_rc1 extends Updates ADD custom_var_v4 VARCHAR(100) DEFAULT NULL, ADD custom_var_k5 VARCHAR(100) DEFAULT NULL, ADD custom_var_v5 VARCHAR(100) DEFAULT NULL - ' => 1060, + ' => array(1060, 1061), // Migrate 128bits IDs inefficiently stored as 8bytes (256 bits) into 64bits 'UPDATE ' . Common::prefixTable('log_visit') . ' SET idvisitor = binary(unhex(substring(visitor_idcookie,1,16))), config_id = binary(unhex(substring(config_md5config,1,16))) - ' => false, + ' => 1054, 'UPDATE ' . Common::prefixTable('log_conversion') . ' SET idvisitor = binary(unhex(substring(visitor_idcookie,1,16))) - ' => false, + ' => 1054, // Drop migrated fields 'ALTER TABLE `' . Common::prefixTable('log_visit') . '` - DROP visitor_idcookie, + DROP visitor_idcookie, DROP config_md5config - ' => false, + ' => 1091, 'ALTER TABLE `' . Common::prefixTable('log_conversion') . '` DROP visitor_idcookie - ' => false, + ' => 1091, // Recreate INDEX on new field 'ALTER TABLE `' . Common::prefixTable('log_visit') . '` ADD INDEX `index_idsite_datetime_config` (idsite, visit_last_action_time, config_id) - ' => false, + ' => 1061, // Backfill action logs as best as we can 'UPDATE ' . Common::prefixTable('log_link_visit_action') . ' as action, ' . Common::prefixTable('log_visit') . ' as visit - SET action.idsite = visit.idsite, - action.server_time = visit.visit_last_action_time, + SET action.idsite = visit.idsite, + action.server_time = visit.visit_last_action_time, action.idvisitor = visit.idvisitor WHERE action.idvisit=visit.idvisit ' => false, @@ -112,18 +118,18 @@ class Updates_1_2_rc1 extends Updates ' => false, // New index used max once per request, in case this table grows significantly in the future - 'ALTER TABLE `' . Common::prefixTable('option') . '` ADD INDEX ( `autoload` ) ' => false, + 'ALTER TABLE `' . Common::prefixTable('option') . '` ADD INDEX ( `autoload` ) ' => 1061, // new field for websites - 'ALTER TABLE `' . Common::prefixTable('site') . '` ADD `group` VARCHAR( 250 ) NOT NULL' => false, + 'ALTER TABLE `' . Common::prefixTable('site') . '` ADD `group` VARCHAR( 250 ) NOT NULL' => 1060, ); } - static function update() + public function doUpdate(Updater $updater) { // first we disable the plugins and keep an array of warnings messages $pluginsToDisableMessage = array( - 'GeoIP' => "GeoIP plugin was disabled, because it is not compatible with the new Piwik 1.2. \nYou can download the latest version of the plugin, compatible with Piwik 1.2.\nClick here.", + 'GeoIP' => "GeoIP plugin was disabled, because it is not compatible with the new Piwik 1.2. \nYou can download the latest version of the plugin, compatible with Piwik 1.2.\nClick here.", 'EntryPage' => "EntryPage plugin is not compatible with this version of Piwik, it was disabled.", ); $disabledPlugins = array(); @@ -135,7 +141,7 @@ class Updates_1_2_rc1 extends Updates } // Run the SQL - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); // Outputs warning message, pointing users to the plugin download page if (!empty($disabledPlugins)) { @@ -144,7 +150,5 @@ class Updates_1_2_rc1 extends Updates implode('
  • ', $disabledPlugins) . "
  • "); } - } } - diff --git a/www/analytics/core/Updates/1.2-rc2.php b/www/analytics/core/Updates/1.2-rc2.php index 02cb3208..3d15a5a5 100644 --- a/www/analytics/core/Updates/1.2-rc2.php +++ b/www/analytics/core/Updates/1.2-rc2.php @@ -1,6 +1,6 @@ activatePlugin('CustomVariables'); @@ -23,4 +24,3 @@ class Updates_1_2_rc2 extends Updates } } } - diff --git a/www/analytics/core/Updates/1.2.3.php b/www/analytics/core/Updates/1.2.3.php index cf5c4559..3f640d20 100644 --- a/www/analytics/core/Updates/1.2.3.php +++ b/www/analytics/core/Updates/1.2.3.php @@ -1,6 +1,6 @@ database['dbname'] . '` DEFAULT CHARACTER SET utf8' => false, + 'ALTER DATABASE `' . Config::getInstance()->database['dbname'] . '` DEFAULT CHARACTER SET utf8' => false, // Various performance improvements schema updates 'ALTER TABLE `' . Common::prefixTable('log_visit') . '` DROP INDEX index_idsite_datetime_config, DROP INDEX index_idsite_idvisit, ADD INDEX index_idsite_config_datetime (idsite, config_id, visit_last_action_time), - ADD INDEX index_idsite_datetime (idsite, visit_last_action_time)' => false, + ADD INDEX index_idsite_datetime (idsite, visit_last_action_time)' => array(1061, 1091), ); } - static function update() + public function doUpdate(Updater $updater) { - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } - diff --git a/www/analytics/core/Updates/1.2.5-rc1.php b/www/analytics/core/Updates/1.2.5-rc1.php index 09c4dea6..d58c772c 100644 --- a/www/analytics/core/Updates/1.2.5-rc1.php +++ b/www/analytics/core/Updates/1.2.5-rc1.php @@ -1,6 +1,6 @@ false, + ADD `allow_multiple` tinyint(4) NOT NULL AFTER case_sensitive' => 1060, 'ALTER TABLE `' . Common::prefixTable('log_conversion') . '` ADD buster int unsigned NOT NULL AFTER revenue, DROP PRIMARY KEY, - ADD PRIMARY KEY (idvisit, idgoal, buster)' => false, + ADD PRIMARY KEY (idvisit, idgoal, buster)' => 1060, ); } - static function update() + public function doUpdate(Updater $updater) { - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } - diff --git a/www/analytics/core/Updates/1.2.5-rc7.php b/www/analytics/core/Updates/1.2.5-rc7.php index c4a51afc..79c5b0a5 100644 --- a/www/analytics/core/Updates/1.2.5-rc7.php +++ b/www/analytics/core/Updates/1.2.5-rc7.php @@ -1,6 +1,6 @@ false, + ADD INDEX index_idsite_idvisitor (idsite, idvisitor)' => 1061, ); } - static function update() + public function doUpdate(Updater $updater) { - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } - - diff --git a/www/analytics/core/Updates/1.4-rc1.php b/www/analytics/core/Updates/1.4-rc1.php index d8cc1397..6218f3cf 100644 --- a/www/analytics/core/Updates/1.4-rc1.php +++ b/www/analytics/core/Updates/1.4-rc1.php @@ -1,6 +1,6 @@ '42S22', + SET format = "pdf"' => '42S22', 'ALTER TABLE `' . Common::prefixTable('pdf') . '` ADD COLUMN `format` VARCHAR(10)' => '42S22', ); } - static function update() + public function doUpdate(Updater $updater) { try { - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } catch (\Exception $e) { } } diff --git a/www/analytics/core/Updates/1.4-rc2.php b/www/analytics/core/Updates/1.4-rc2.php index 1f2ba56b..aa0b12cc 100644 --- a/www/analytics/core/Updates/1.4-rc2.php +++ b/www/analytics/core/Updates/1.4-rc2.php @@ -1,6 +1,6 @@ false, + "SET sql_mode=''" => false, // this converts the 32-bit UNSIGNED INT column to a 16 byte VARBINARY; // _but_ MySQL does string conversion! (e.g., integer 1 is converted to 49 -- the ASCII code for "1") 'ALTER TABLE ' . Common::prefixTable('log_visit') . ' MODIFY location_ip VARBINARY(16) NOT NULL' => false, 'ALTER TABLE ' . Common::prefixTable('logger_api_call') . ' - MODIFY caller_ip VARBINARY(16) NOT NULL' => false, + MODIFY caller_ip VARBINARY(16) NOT NULL' => 1146, // fortunately, 2^32 is 10 digits long and fits in the VARBINARY(16) without truncation; // to fix this, we cast to an integer, convert to hex, pad out leading zeros, and unhex it 'UPDATE ' . Common::prefixTable('log_visit') . " SET location_ip = UNHEX(LPAD(HEX(CONVERT(location_ip, UNSIGNED)), 8, '0'))" => false, 'UPDATE ' . Common::prefixTable('logger_api_call') . " - SET caller_ip = UNHEX(LPAD(HEX(CONVERT(caller_ip, UNSIGNED)), 8, '0'))" => false, + SET caller_ip = UNHEX(LPAD(HEX(CONVERT(caller_ip, UNSIGNED)), 8, '0'))" => 1146, ); } - static function update() + public function doUpdate(Updater $updater) { - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/1.5-b1.php b/www/analytics/core/Updates/1.5-b1.php index 0ccd81e0..e481d1e8 100644 --- a/www/analytics/core/Updates/1.5-b1.php +++ b/www/analytics/core/Updates/1.5-b1.php @@ -1,6 +1,6 @@ false, + ) DEFAULT CHARSET=utf8 ' => 1050, - 'ALTER IGNORE TABLE `' . Common::prefixTable('log_visit') . '` + 'ALTER TABLE `' . Common::prefixTable('log_visit') . '` ADD visitor_days_since_order SMALLINT(5) UNSIGNED NOT NULL AFTER visitor_days_since_last, - ADD visit_goal_buyer TINYINT(1) NOT NULL AFTER visit_goal_converted' => false, + ADD visit_goal_buyer TINYINT(1) NOT NULL AFTER visit_goal_converted' => 1060, - 'ALTER IGNORE TABLE `' . Common::prefixTable('log_conversion') . '` - ADD visitor_days_since_order SMALLINT(5) UNSIGNED NOT NULL AFTER visitor_days_since_first, + 'ALTER TABLE `' . $logConversionTable . '` + ADD visitor_days_since_order SMALLINT(5) UNSIGNED NOT NULL AFTER visitor_days_since_first' => 1060, + 'ALTER TABLE `' . $logConversionTable . '` ADD idorder varchar(100) default NULL AFTER buster, ADD items SMALLINT UNSIGNED DEFAULT NULL, ADD revenue_subtotal float default NULL, ADD revenue_tax float default NULL, ADD revenue_shipping float default NULL, ADD revenue_discount float default NULL, - ADD UNIQUE KEY unique_idsite_idorder (idsite, idorder), - MODIFY idgoal int(10) NOT NULL' => false, + MODIFY idgoal int(10) NOT NULL' => 1060, + 'ALTER TABLE `' . Common::prefixTable('log_conversion') . '` + ADD UNIQUE KEY unique_idsite_idorder (idsite, idorder)' => 1061, ); } - static function update() + public function doUpdate(Updater $updater) { - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/1.5-b2.php b/www/analytics/core/Updates/1.5-b2.php index 00c748f8..affb2e61 100644 --- a/www/analytics/core/Updates/1.5-b2.php +++ b/www/analytics/core/Updates/1.5-b2.php @@ -1,6 +1,6 @@ executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/1.5-b3.php b/www/analytics/core/Updates/1.5-b3.php index 781056db..02a8fd16 100644 --- a/www/analytics/core/Updates/1.5-b3.php +++ b/www/analytics/core/Updates/1.5-b3.php @@ -1,6 +1,6 @@ executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/1.5-b4.php b/www/analytics/core/Updates/1.5-b4.php index 32c37bf2..359dd55b 100644 --- a/www/analytics/core/Updates/1.5-b4.php +++ b/www/analytics/core/Updates/1.5-b4.php @@ -1,6 +1,6 @@ false, + ADD ecommerce TINYINT DEFAULT 0' => 1060, ); } - static function update() + public function doUpdate(Updater $updater) { - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/1.5-b5.php b/www/analytics/core/Updates/1.5-b5.php index 5e4b10fa..d79f18b3 100644 --- a/www/analytics/core/Updates/1.5-b5.php +++ b/www/analytics/core/Updates/1.5-b5.php @@ -1,6 +1,6 @@ false, + ) DEFAULT CHARSET=utf8' => 1050, ); } - static function update() + public function doUpdate(Updater $updater) { - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/1.5-rc6.php b/www/analytics/core/Updates/1.5-rc6.php index 56e87e64..9a588745 100644 --- a/www/analytics/core/Updates/1.5-rc6.php +++ b/www/analytics/core/Updates/1.5-rc6.php @@ -1,6 +1,6 @@ activatePlugin('PrivacyManager'); @@ -23,4 +24,3 @@ class Updates_1_5_rc6 extends Updates } } } - diff --git a/www/analytics/core/Updates/1.6-b1.php b/www/analytics/core/Updates/1.6-b1.php index 2b73ed95..69e9e724 100644 --- a/www/analytics/core/Updates/1.6-b1.php +++ b/www/analytics/core/Updates/1.6-b1.php @@ -1,6 +1,6 @@ false, + ADD idaction_category5 INTEGER(10) UNSIGNED NOT NULL' => 1060, 'ALTER TABLE `' . Common::prefixTable('log_visit') . '` CHANGE custom_var_k1 custom_var_k1 VARCHAR(200) DEFAULT NULL, CHANGE custom_var_v1 custom_var_v1 VARCHAR(200) DEFAULT NULL, @@ -61,8 +61,8 @@ class Updates_1_6_b1 extends Updates ); } - static function update() + public function doUpdate(Updater $updater) { - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/1.6-rc1.php b/www/analytics/core/Updates/1.6-rc1.php index 8255786c..44e1dab8 100644 --- a/www/analytics/core/Updates/1.6-rc1.php +++ b/www/analytics/core/Updates/1.6-rc1.php @@ -1,6 +1,6 @@ activatePlugin('ImageGraph'); @@ -23,4 +24,3 @@ class Updates_1_6_rc1 extends Updates } } } - diff --git a/www/analytics/core/Updates/1.7-b1.php b/www/analytics/core/Updates/1.7-b1.php index 04e45c9c..fb10fca8 100644 --- a/www/analytics/core/Updates/1.7-b1.php +++ b/www/analytics/core/Updates/1.7-b1.php @@ -1,6 +1,6 @@ false, + ADD COLUMN `aggregate_reports_format` TINYINT(1) NOT NULL AFTER `reports`' => 1060, 'UPDATE `' . Common::prefixTable('pdf') . '` SET `aggregate_reports_format` = 1' => false, ); } - static function update() + public function doUpdate(Updater $updater) { try { - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } catch (\Exception $e) { } } diff --git a/www/analytics/core/Updates/1.7.2-rc5.php b/www/analytics/core/Updates/1.7.2-rc5.php index 19388cc9..69359d95 100644 --- a/www/analytics/core/Updates/1.7.2-rc5.php +++ b/www/analytics/core/Updates/1.7.2-rc5.php @@ -1,6 +1,6 @@ executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } catch (\Exception $e) { } } diff --git a/www/analytics/core/Updates/1.7.2-rc7.php b/www/analytics/core/Updates/1.7.2-rc7.php index ac1871de..0a731d21 100755 --- a/www/analytics/core/Updates/1.7.2-rc7.php +++ b/www/analytics/core/Updates/1.7.2-rc7.php @@ -1,6 +1,6 @@ false, + ADD `name` VARCHAR( 100 ) NULL DEFAULT NULL AFTER `iddashboard`' => 1060, ); } - static function update() + public function doUpdate(Updater $updater) { try { $dashboards = Db::fetchAll('SELECT * FROM `' . Common::prefixTable('user_dashboard') . '`'); - foreach ($dashboards AS $dashboard) { + foreach ($dashboards as $dashboard) { $idDashboard = $dashboard['iddashboard']; $login = $dashboard['login']; $layout = $dashboard['layout']; @@ -38,7 +38,7 @@ class Updates_1_7_2_rc7 extends Updates $layout = str_replace("\\\"", "\"", $layout); Db::query('UPDATE `' . Common::prefixTable('user_dashboard') . '` SET layout = ? WHERE iddashboard = ? AND login = ?', array($layout, $idDashboard, $login)); } - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } catch (\Exception $e) { } } diff --git a/www/analytics/core/Updates/1.8.3-b1.php b/www/analytics/core/Updates/1.8.3-b1.php index 85f9f669..fb2bfaa4 100644 --- a/www/analytics/core/Updates/1.8.3-b1.php +++ b/www/analytics/core/Updates/1.8.3-b1.php @@ -1,6 +1,6 @@ executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); if (!\Piwik\Plugin\Manager::getInstance()->isPluginLoaded('ScheduledReports')) { return; } @@ -60,8 +60,7 @@ class Updates_1_8_3_b1 extends Updates // - delete Common::prefixTable('pdf') $reports = Db::fetchAll('SELECT * FROM `' . Common::prefixTable('pdf') . '`'); - foreach ($reports AS $report) { - + foreach ($reports as $report) { $idreport = $report['idreport']; $idsite = $report['idsite']; $login = $report['login']; @@ -98,8 +97,8 @@ class Updates_1_8_3_b1 extends Updates is_null($period) ? ScheduledReports::DEFAULT_PERIOD : $period, ScheduledReports::EMAIL_TYPE, is_null($format) ? ScheduledReports::DEFAULT_REPORT_FORMAT : $format, - Common::json_encode(preg_split('/,/', $reports)), - Common::json_encode($parameters), + json_encode(preg_split('/,/', $reports)), + json_encode($parameters), $ts_created, $ts_last_sent, $deleted @@ -110,6 +109,5 @@ class Updates_1_8_3_b1 extends Updates Db::query('DROP TABLE `' . Common::prefixTable('pdf') . '`'); } catch (\Exception $e) { } - } } diff --git a/www/analytics/core/Updates/1.8.4-b1.php b/www/analytics/core/Updates/1.8.4-b1.php index 97e70daa..05fa418e 100644 --- a/www/analytics/core/Updates/1.8.4-b1.php +++ b/www/analytics/core/Updates/1.8.4-b1.php @@ -1,6 +1,6 @@ executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); self::disableMaintenanceMode(); } catch (\Exception $e) { self::disableMaintenanceMode(); diff --git a/www/analytics/core/Updates/1.9-b16.php b/www/analytics/core/Updates/1.9-b16.php index 3c82828d..ccb324e1 100755 --- a/www/analytics/core/Updates/1.9-b16.php +++ b/www/analytics/core/Updates/1.9-b16.php @@ -1,6 +1,6 @@ false, - 'ALTER TABLE `' . Common::prefixTable('log_visit') . '` ADD visit_total_searches SMALLINT(5) UNSIGNED NOT NULL AFTER `visit_total_actions`' => 1060, @@ -46,9 +45,8 @@ class Updates_1_9_b16 extends Updates ); } - static function update() + public function doUpdate(Updater $updater) { - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } - diff --git a/www/analytics/core/Updates/1.9-b19.php b/www/analytics/core/Updates/1.9-b19.php index d2496cbb..5e6a67c3 100755 --- a/www/analytics/core/Updates/1.9-b19.php +++ b/www/analytics/core/Updates/1.9-b19.php @@ -1,6 +1,6 @@ executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); try { \Piwik\Plugin\Manager::getInstance()->activatePlugin('Transitions'); @@ -40,4 +39,3 @@ class Updates_1_9_b19 extends Updates } } } - diff --git a/www/analytics/core/Updates/1.9-b9.php b/www/analytics/core/Updates/1.9-b9.php index dce8c9e2..84c8abf3 100755 --- a/www/analytics/core/Updates/1.9-b9.php +++ b/www/analytics/core/Updates/1.9-b9.php @@ -1,6 +1,6 @@ 1091, + "ALTER TABLE `$logVisit` $dropColumns" => 1091, + "ALTER TABLE `$logConversion` $dropColumns" => 1091, + + // add geoip columns to log_visit + "ALTER TABLE `$logVisit` $addColumns" => 1060, // add geoip columns to log_conversion - "ALTER TABLE `$logConversion` $addColumns" => 1091, + "ALTER TABLE `$logConversion` $addColumns" => 1060, ); } - static function update() + public function doUpdate(Updater $updater) { try { self::enableMaintenanceMode(); - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); self::disableMaintenanceMode(); } catch (\Exception $e) { self::disableMaintenanceMode(); @@ -54,4 +57,3 @@ class Updates_1_9_b9 extends Updates } } } - diff --git a/www/analytics/core/Updates/1.9.1-b2.php b/www/analytics/core/Updates/1.9.1-b2.php index d79dd992..794cd518 100644 --- a/www/analytics/core/Updates/1.9.1-b2.php +++ b/www/analytics/core/Updates/1.9.1-b2.php @@ -1,6 +1,6 @@ 1091 ); } - static function update() + public function doUpdate(Updater $updater) { // manually remove ExampleFeedburner column - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); // remove ExampleFeedburner plugin $pluginToDelete = 'ExampleFeedburner'; diff --git a/www/analytics/core/Updates/1.9.3-b10.php b/www/analytics/core/Updates/1.9.3-b10.php index 22583103..0a64fe6a 100755 --- a/www/analytics/core/Updates/1.9.3-b10.php +++ b/www/analytics/core/Updates/1.9.3-b10.php @@ -1,6 +1,6 @@ activatePlugin('Annotations'); diff --git a/www/analytics/core/Updates/1.9.3-b3.php b/www/analytics/core/Updates/1.9.3-b3.php index 0a511b14..77dcb2bb 100644 --- a/www/analytics/core/Updates/1.9.3-b3.php +++ b/www/analytics/core/Updates/1.9.3-b3.php @@ -1,6 +1,6 @@ executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/2.0-a12.php b/www/analytics/core/Updates/2.0-a12.php index cc202d4e..97a1087c 100644 --- a/www/analytics/core/Updates/2.0-a12.php +++ b/www/analytics/core/Updates/2.0-a12.php @@ -1,6 +1,6 @@ false @@ -40,9 +40,9 @@ class Updates_2_0_a12 extends Updates return $result; } - public static function update() + public function doUpdate(Updater $updater) { // change level column in logger_message table to string & remove other logging tables if empty - Updater::updateDatabase(__FILE__, self::getSql()); + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/2.0-a13.php b/www/analytics/core/Updates/2.0-a13.php index 70ca0590..43c292aa 100644 --- a/www/analytics/core/Updates/2.0-a13.php +++ b/www/analytics/core/Updates/2.0-a13.php @@ -1,6 +1,6 @@ executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); // old plugins deleted in 2.0-a17 update file @@ -61,6 +60,5 @@ class Updates_2_0_a13 extends Updates \Piwik\Plugin\Manager::getInstance()->activatePlugin('ScheduledReports'); } catch (\Exception $e) { } - } } diff --git a/www/analytics/core/Updates/2.0-a17.php b/www/analytics/core/Updates/2.0-a17.php index ec00c2ab..c4e2afe5 100644 --- a/www/analytics/core/Updates/2.0-a17.php +++ b/www/analytics/core/Updates/2.0-a17.php @@ -1,6 +1,6 @@ " . implode("
    ", $errors)); } - } + } } diff --git a/www/analytics/core/Updates/2.0-a7.php b/www/analytics/core/Updates/2.0-a7.php index cdbbc7e2..57544a07 100644 --- a/www/analytics/core/Updates/2.0-a7.php +++ b/www/analytics/core/Updates/2.0-a7.php @@ -1,6 +1,6 @@ executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/2.0-b10.php b/www/analytics/core/Updates/2.0-b10.php index 273c4d08..743c800c 100644 --- a/www/analytics/core/Updates/2.0-b10.php +++ b/www/analytics/core/Updates/2.0-b10.php @@ -1,6 +1,6 @@ " . implode("
    ", $errors)); } - } + } } diff --git a/www/analytics/core/Updates/2.0-b3.php b/www/analytics/core/Updates/2.0-b3.php index ae30df77..31e1e8f1 100644 --- a/www/analytics/core/Updates/2.0-b3.php +++ b/www/analytics/core/Updates/2.0-b3.php @@ -1,6 +1,6 @@ executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); try { \Piwik\Plugin\Manager::getInstance()->activatePlugin('Events'); diff --git a/www/analytics/core/Updates/2.0-b9.php b/www/analytics/core/Updates/2.0-b9.php index 612c79d1..13f887f9 100644 --- a/www/analytics/core/Updates/2.0-b9.php +++ b/www/analytics/core/Updates/2.0-b9.php @@ -1,6 +1,6 @@ executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); } } diff --git a/www/analytics/core/Updates/2.0-rc1.php b/www/analytics/core/Updates/2.0-rc1.php index beda1cda..414f48e7 100644 --- a/www/analytics/core/Updates/2.0-rc1.php +++ b/www/analytics/core/Updates/2.0-rc1.php @@ -1,6 +1,6 @@ activatePlugin('Morpheus'); - } catch(\Exception $e) { + } catch (\Exception $e) { } } } diff --git a/www/analytics/core/Updates/2.0.3-b7.php b/www/analytics/core/Updates/2.0.3-b7.php index 2e9b593a..5771002b 100644 --- a/www/analytics/core/Updates/2.0.3-b7.php +++ b/www/analytics/core/Updates/2.0.3-b7.php @@ -1,6 +1,6 @@ isPluginActivated('DoNotTrack')) { - DoNotTrackHeaderChecker::activate(); + $checker->activate(); } // enable IP anonymization if AnonymizeIP plugin was enabled @@ -45,8 +44,7 @@ class Updates_2_0_3_b7 extends Updates foreach ($oldPlugins as $plugin) { try { \Piwik\Plugin\Manager::getInstance()->deactivatePlugin($plugin); - } catch(\Exception $e) { - + } catch (\Exception $e) { } $dir = PIWIK_INCLUDE_PATH . "/plugins/$plugin"; @@ -58,9 +56,8 @@ class Updates_2_0_3_b7 extends Updates if (file_exists($dir)) { $errors[] = "Please delete this directory manually (eg. using your FTP software): $dir \n"; } - } - if(!empty($errors)) { + if (!empty($errors)) { throw new \Exception("Warnings during the update:
    " . implode("
    ", $errors)); } } diff --git a/www/analytics/core/Updates/2.0.4-b5.php b/www/analytics/core/Updates/2.0.4-b5.php index 78c8ab2b..aadafa32 100644 --- a/www/analytics/core/Updates/2.0.4-b5.php +++ b/www/analytics/core/Updates/2.0.4-b5.php @@ -1,6 +1,6 @@ executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); try { self::migrateConfigSuperUserToDb(); @@ -56,7 +56,10 @@ class Updates_2_0_4_b5 extends Updates $superUser = null; } - if (!empty($superUser['bridge']) || empty($superUser)) { + if (!empty($superUser['bridge']) + || empty($superUser) + || empty($superUser['login']) + ) { // there is a super user which is not from the config but from the bridge, that means we already have // a super user in the database return; @@ -75,7 +78,7 @@ class Updates_2_0_4_b5 extends Updates 'superuser_access' => 1 ) ); - } catch(\Exception $e) { + } catch (\Exception $e) { echo "There was an issue, but we proceed: " . $e->getMessage(); } diff --git a/www/analytics/core/Updates/2.0.4-b7.php b/www/analytics/core/Updates/2.0.4-b7.php index 83b19903..61ab3ef7 100644 --- a/www/analytics/core/Updates/2.0.4-b7.php +++ b/www/analytics/core/Updates/2.0.4-b7.php @@ -1,6 +1,6 @@ forceSave(); - } catch (\Exception $e) { throw new UpdaterErrorException($e->getMessage()); } diff --git a/www/analytics/core/Updates/2.1.1-b11.php b/www/analytics/core/Updates/2.1.1-b11.php index 4eb924d3..543ae284 100644 --- a/www/analytics/core/Updates/2.1.1-b11.php +++ b/www/analytics/core/Updates/2.1.1-b11.php @@ -1,27 +1,27 @@ ", false, __FILE__); } diff --git a/www/analytics/core/Updates/2.10.0-b10.php b/www/analytics/core/Updates/2.10.0-b10.php new file mode 100644 index 00000000..1b0454ad --- /dev/null +++ b/www/analytics/core/Updates/2.10.0-b10.php @@ -0,0 +1,47 @@ +activatePlugin('DevicePlugins'); + } catch (\Exception $e) { + } + + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); + } +} diff --git a/www/analytics/core/Updates/2.10.0-b4.php b/www/analytics/core/Updates/2.10.0-b4.php new file mode 100644 index 00000000..1f7cee00 --- /dev/null +++ b/www/analytics/core/Updates/2.10.0-b4.php @@ -0,0 +1,30 @@ +activatePlugin('BulkTracking'); + } catch (\Exception $e) { + } + } +} diff --git a/www/analytics/core/Updates/2.10.0-b5.php b/www/analytics/core/Updates/2.10.0-b5.php new file mode 100644 index 00000000..fb77fc20 --- /dev/null +++ b/www/analytics/core/Updates/2.10.0-b5.php @@ -0,0 +1,208 @@ + false); + + // update scheduled reports to use new plugin + $reportsToReplace = array( + 'UserSettings_getBrowserVersion' => 'DevicesDetection_getBrowserVersions', + 'UserSettings_getBrowser' => 'DevicesDetection_getBrowsers', + 'UserSettings_getOSFamily' => 'DevicesDetection_getOsFamilies', + 'UserSettings_getOS' => 'DevicesDetection_getOsVersions', + 'UserSettings_getMobileVsDesktop' => 'DevicesDetection_getType', + 'UserSettings_getBrowserType' => 'DevicesDetection_getBrowserEngines', + 'UserSettings_getWideScreen' => 'UserSettings_getScreenType', + ); + + foreach ($reportsToReplace as $old => $new) { + $sqls["UPDATE " . Common::prefixTable('report') . " SET reports = REPLACE(reports, '".$old."', '".$new."')"] = false; + } + + // update dashboard to use new widgets + $oldWidgets = array( + array('module' => 'UserSettings', 'action' => 'getBrowserVersion', 'params' => array()), + array('module' => 'UserSettings', 'action' => 'getBrowser', 'params' => array()), + array('module' => 'UserSettings', 'action' => 'getOSFamily', 'params' => array()), + array('module' => 'UserSettings', 'action' => 'getOS', 'params' => array()), + array('module' => 'UserSettings', 'action' => 'getMobileVsDesktop', 'params' => array()), + array('module' => 'UserSettings', 'action' => 'getBrowserType', 'params' => array()), + array('module' => 'UserSettings', 'action' => 'getWideScreen', 'params' => array()), + ); + + $newWidgets = array( + array('module' => 'DevicesDetection', 'action' => 'getBrowserVersions', 'params' => array()), + array('module' => 'DevicesDetection', 'action' => 'getBrowsers', 'params' => array()), + array('module' => 'DevicesDetection', 'action' => 'getOsFamilies', 'params' => array()), + array('module' => 'DevicesDetection', 'action' => 'getOsVersions', 'params' => array()), + array('module' => 'DevicesDetection', 'action' => 'getType', 'params' => array()), + array('module' => 'DevicesDetection', 'action' => 'getBrowserEngines', 'params' => array()), + array('module' => 'UserSettings', 'action' => 'getScreenType', 'params' => array()), + ); + + $allDashboards = Db::get()->fetchAll(sprintf("SELECT * FROM %s", Common::prefixTable('user_dashboard'))); + + foreach ($allDashboards as $dashboard) { + $dashboardLayout = json_decode($dashboard['layout']); + + $dashboardLayout = DashboardModel::replaceDashboardWidgets($dashboardLayout, $oldWidgets, $newWidgets); + + $newLayout = json_encode($dashboardLayout); + if ($newLayout != $dashboard['layout']) { + $sqls["UPDATE " . Common::prefixTable('user_dashboard') . " SET layout = '".addslashes($newLayout)."' WHERE iddashboard = ".$dashboard['iddashboard']] = false; + } + } + + return $sqls; + } + + public function doUpdate(Updater $updater) + { + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); + + // DeviceDetection upgrade in beta1 timed out on demo #6750 + $archiveBlobTables = self::getAllArchiveBlobTables(); + + foreach ($archiveBlobTables as $table) { + self::updateBrowserArchives($table); + self::updateOsArchives($table); + } + } + + /** + * Returns all available archive blob tables + * + * @return array + */ + public static function getAllArchiveBlobTables() + { + if (empty(self::$archiveBlobTables)) { + $archiveTables = ArchiveTableCreator::getTablesArchivesInstalled(); + + self::$archiveBlobTables = array_filter($archiveTables, function ($name) { + return ArchiveTableCreator::getTypeFromTableName($name) == ArchiveTableCreator::BLOB_TABLE; + }); + + // sort tables so we have them in order of their date + rsort(self::$archiveBlobTables); + } + + return (array) self::$archiveBlobTables; + } + + /** + * Find the first day on which DevicesDetection archives were generated + * + * @return int Timestamp + */ + public static function getFirstDayOfArchivedDeviceDetectorData() + { + static $deviceDetectionBlobAvailableDate; + + if (empty($deviceDetectionBlobAvailableDate)) { + $archiveBlobTables = self::getAllArchiveBlobTables(); + + $deviceDetectionBlobAvailableDate = null; + foreach ($archiveBlobTables as $table) { + + // Look for all day archives and try to find that with the lowest date + $deviceDetectionBlobAvailableDate = Db::get()->fetchOne(sprintf("SELECT date1 FROM %s WHERE name = 'DevicesDetection_browserVersions' AND period = 1 ORDER BY date1 ASC LIMIT 1", $table)); + + if (!empty($deviceDetectionBlobAvailableDate)) { + break; + } + } + + $deviceDetectionBlobAvailableDate = strtotime($deviceDetectionBlobAvailableDate); + } + + return $deviceDetectionBlobAvailableDate; + } + + /** + * Updates all browser archives to new structure + * @param string $table + * @throws \Exception + */ + public static function updateBrowserArchives($table) + { + // rename old UserSettings archives where no DeviceDetection archives exists + Db::exec(sprintf("UPDATE IGNORE %s SET name='DevicesDetection_browserVersions' WHERE name = 'UserSettings_browser'", $table)); + + /* + * check dates of remaining (non-day) archives with calculated safe date + * archives before or within that week/month/year of that date will be replaced + */ + $oldBrowserBlobs = Db::get()->fetchAll(sprintf("SELECT * FROM %s WHERE name = 'UserSettings_browser' AND `period` > 1", $table)); + foreach ($oldBrowserBlobs as $blob) { + + // if start date of blob is before calculated date us old usersettings archive instead of already existing DevicesDetection archive + if (strtotime($blob['date1']) < self::getFirstDayOfArchivedDeviceDetectorData()) { + Db::get()->query(sprintf("DELETE FROM %s WHERE idarchive = ? AND name = ?", $table), array($blob['idarchive'], 'DevicesDetection_browserVersions')); + Db::get()->query(sprintf("UPDATE %s SET name = ? WHERE idarchive = ? AND name = ?", $table), array('DevicesDetection_browserVersions', $blob['idarchive'], 'UserSettings_browser')); + } + } + } + + public static function updateOsArchives($table) + { + Db::exec(sprintf("UPDATE IGNORE %s SET name='DevicesDetection_osVersions' WHERE name = 'UserSettings_os'", $table)); + + /* + * check dates of remaining (non-day) archives with calculated safe date + * archives before or within that week/month/year of that date will be replaced + */ + $oldOsBlobs = Db::get()->fetchAll(sprintf("SELECT * FROM %s WHERE name = 'UserSettings_os' AND `period` > 1", $table)); + foreach ($oldOsBlobs as $blob) { + + // if start date of blob is before calculated date us old usersettings archive instead of already existing DevicesDetection archive + if (strtotime($blob['date1']) < self::getFirstDayOfArchivedDeviceDetectorData()) { + Db::get()->query(sprintf("DELETE FROM %s WHERE idarchive = ? AND name = ?", $table), array($blob['idarchive'], 'DevicesDetection_osVersions')); + Db::get()->query(sprintf("UPDATE %s SET name = ? WHERE idarchive = ? AND name = ?", $table), array('DevicesDetection_osVersions', $blob['idarchive'], 'UserSettings_os')); + } + } + } +} diff --git a/www/analytics/core/Updates/2.10.0-b7.php b/www/analytics/core/Updates/2.10.0-b7.php new file mode 100644 index 00000000..50bc120a --- /dev/null +++ b/www/analytics/core/Updates/2.10.0-b7.php @@ -0,0 +1,41 @@ +executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); + } +} diff --git a/www/analytics/core/Updates/2.10.0-b8.php b/www/analytics/core/Updates/2.10.0-b8.php new file mode 100644 index 00000000..885d7482 --- /dev/null +++ b/www/analytics/core/Updates/2.10.0-b8.php @@ -0,0 +1,26 @@ +activatePlugin('Resolution'); + } catch (\Exception $e) { + } + } +} diff --git a/www/analytics/core/Updates/2.11.0-b2.php b/www/analytics/core/Updates/2.11.0-b2.php new file mode 100644 index 00000000..47430800 --- /dev/null +++ b/www/analytics/core/Updates/2.11.0-b2.php @@ -0,0 +1,65 @@ + 'Goals', 'action' => 'getEcommerceLog', 'params' => array()), + array('module' => 'Goals', 'action' => 'widgetGoalReport', 'params' => array('idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER)), + ); + + $newWidgets = array( + array('module' => 'Ecommerce', 'action' => 'getEcommerceLog', 'params' => array()), + array('module' => 'Ecommerce', 'action' => 'widgetGoalReport', 'params' => array('idGoal' => Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER)), + ); + + $allDashboards = Db::get()->fetchAll(sprintf("SELECT * FROM %s", Common::prefixTable('user_dashboard'))); + + foreach ($allDashboards as $dashboard) { + $dashboardLayout = json_decode($dashboard['layout']); + $dashboardLayout = DashboardModel::replaceDashboardWidgets($dashboardLayout, $oldWidgets, $newWidgets); + + $newLayout = json_encode($dashboardLayout); + if ($newLayout != $dashboard['layout']) { + $sqls["UPDATE " . Common::prefixTable('user_dashboard') . " SET layout = '".addslashes($newLayout)."' WHERE iddashboard = ".$dashboard['iddashboard']] = false; + } + } + + return $sqls; + } + + public function doUpdate(Updater $updater) + { + $pluginManager = \Piwik\Plugin\Manager::getInstance(); + + try { + $pluginManager->activatePlugin('Ecommerce'); + } catch (\Exception $e) { + } + + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); + } +} diff --git a/www/analytics/core/Updates/2.11.0-b4.php b/www/analytics/core/Updates/2.11.0-b4.php new file mode 100644 index 00000000..e35025f3 --- /dev/null +++ b/www/analytics/core/Updates/2.11.0-b4.php @@ -0,0 +1,47 @@ +activatePlugin('UserLanguage'); + } catch (\Exception $e) { + } + + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); + } +} diff --git a/www/analytics/core/Updates/2.11.0-b5.php b/www/analytics/core/Updates/2.11.0-b5.php new file mode 100644 index 00000000..37921e96 --- /dev/null +++ b/www/analytics/core/Updates/2.11.0-b5.php @@ -0,0 +1,24 @@ +activatePlugin('Monolog'); + } catch (\Exception $e) { + } + } +} diff --git a/www/analytics/core/Updates/2.11.1-b4.php b/www/analytics/core/Updates/2.11.1-b4.php new file mode 100644 index 00000000..3091445b --- /dev/null +++ b/www/analytics/core/Updates/2.11.1-b4.php @@ -0,0 +1,40 @@ +database_tests; + + if ($dbTests['username'] === '@USERNAME@') { + $dbTests['username'] = 'root'; + } + + $config->database_tests = $dbTests; + + $config->forceSave(); + } +} diff --git a/www/analytics/core/Updates/2.13.0-b3.php b/www/analytics/core/Updates/2.13.0-b3.php new file mode 100644 index 00000000..f0950c64 --- /dev/null +++ b/www/analytics/core/Updates/2.13.0-b3.php @@ -0,0 +1,26 @@ +activatePlugin('Diagnostics'); + } catch (\Exception $e) { + } + } +} diff --git a/www/analytics/core/Updates/2.13.1.php b/www/analytics/core/Updates/2.13.1.php new file mode 100644 index 00000000..73b01dc8 --- /dev/null +++ b/www/analytics/core/Updates/2.13.1.php @@ -0,0 +1,43 @@ + false + ); + } + + /** + * Here you can define any action that should be performed during the update. For instance executing SQL statements, + * renaming config entries, updating files, etc. + */ + public function doUpdate(Updater $updater) + { + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); + } +} diff --git a/www/analytics/core/Updates/2.14.0-b1.php b/www/analytics/core/Updates/2.14.0-b1.php new file mode 100644 index 00000000..e414bc3a --- /dev/null +++ b/www/analytics/core/Updates/2.14.0-b1.php @@ -0,0 +1,43 @@ +uninstallPlugin('UserSettings'); + } + + private function uninstallPlugin($plugin) + { + $pluginManager = Manager::getInstance(); + + if ($pluginManager->isPluginInstalled($plugin)) { + if ($pluginManager->isPluginActivated($plugin)) { + $pluginManager->deactivatePlugin($plugin); + } + + $pluginManager->unloadPlugin($plugin); + $pluginManager->uninstallPlugin($plugin); + } else { + $this->makeSurePluginIsRemovedFromFilesystem($plugin); + } + } + + private function makeSurePluginIsRemovedFromFilesystem($plugin) + { + Manager::deletePluginFromFilesystem($plugin); + } +} diff --git a/www/analytics/core/Updates/2.14.0-b2.php b/www/analytics/core/Updates/2.14.0-b2.php new file mode 100644 index 00000000..dfa8c3f5 --- /dev/null +++ b/www/analytics/core/Updates/2.14.0-b2.php @@ -0,0 +1,43 @@ +getEngine(); + + $table = Common::prefixTable('site_setting'); + + $sqlarray = array( + "DROP TABLE IF EXISTS `$table`" => false, + "CREATE TABLE `$table` ( + idsite INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `setting_name` VARCHAR(255) NOT NULL, + `setting_value` LONGTEXT NOT NULL, + PRIMARY KEY(idsite, setting_name) + ) ENGINE=$engine DEFAULT CHARSET=utf8" => 1050, + ); + + return $sqlarray; + } + + public function doUpdate(Updater $updater) + { + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); + } +} diff --git a/www/analytics/core/Updates/2.14.2.php b/www/analytics/core/Updates/2.14.2.php new file mode 100644 index 00000000..de416527 --- /dev/null +++ b/www/analytics/core/Updates/2.14.2.php @@ -0,0 +1,124 @@ +executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); + } +} diff --git a/www/analytics/core/Updates/2.15.0-b12.php b/www/analytics/core/Updates/2.15.0-b12.php new file mode 100644 index 00000000..f780e616 --- /dev/null +++ b/www/analytics/core/Updates/2.15.0-b12.php @@ -0,0 +1,44 @@ +migrateBetaUpgradesToReleaseChannel(); + } + + private function migrateBetaUpgradesToReleaseChannel() + { + $config = Config::getInstance(); + $debug = $config->Debug; + + if (array_key_exists('allow_upgrades_to_beta', $debug)) { + $allowUpgradesToBeta = 1 == $debug['allow_upgrades_to_beta']; + unset($debug['allow_upgrades_to_beta']); + + $general = $config->General; + if ($allowUpgradesToBeta) { + $general['release_channel'] = 'latest_beta'; + } else { + $general['release_channel'] = 'latest_stable'; + } + + $config->Debug = $debug; + $config->General = $general; + $config->forceSave(); + } + } +} diff --git a/www/analytics/core/Updates/2.15.0-b16.php b/www/analytics/core/Updates/2.15.0-b16.php new file mode 100644 index 00000000..a37e6211 --- /dev/null +++ b/www/analytics/core/Updates/2.15.0-b16.php @@ -0,0 +1,45 @@ +uninstallPlugin('LeftMenu'); + $this->uninstallPlugin('ZenMode'); + } + + private function uninstallPlugin($plugin) + { + $pluginManager = Manager::getInstance(); + + if ($pluginManager->isPluginInstalled($plugin)) { + if ($pluginManager->isPluginActivated($plugin)) { + $pluginManager->deactivatePlugin($plugin); + } + + $pluginManager->unloadPlugin($plugin); + $pluginManager->uninstallPlugin($plugin); + } else { + $this->makeSurePluginIsRemovedFromFilesystem($plugin); + } + } + + private function makeSurePluginIsRemovedFromFilesystem($plugin) + { + Manager::deletePluginFromFilesystem($plugin); + } +} diff --git a/www/analytics/core/Updates/2.15.0-b17.php b/www/analytics/core/Updates/2.15.0-b17.php new file mode 100644 index 00000000..86e3decf --- /dev/null +++ b/www/analytics/core/Updates/2.15.0-b17.php @@ -0,0 +1,44 @@ +removeDeprecatedDebugConfig('enable_measure_piwik_usage_in_idsite'); + } + + private function removeDeprecatedDebugConfig($name) + { + $config = Config::getInstance(); + $debug = $config->Debug; + unset($debug[$name]); + $config->Debug = $debug; + $config->forceSave(); + } +} diff --git a/www/analytics/core/Updates/2.15.0-b20.php b/www/analytics/core/Updates/2.15.0-b20.php new file mode 100644 index 00000000..79b4703f --- /dev/null +++ b/www/analytics/core/Updates/2.15.0-b20.php @@ -0,0 +1,42 @@ +makeSurePluginIsRemovedFromFilesystem('ZenMode'); + $this->makeSurePluginIsRemovedFromFilesystem('LeftMenu'); + } + + private function makeSurePluginIsRemovedFromFilesystem($plugin) + { + Plugin\Manager::deletePluginFromFilesystem($plugin); + } +} diff --git a/www/analytics/core/Updates/2.15.0-b3.php b/www/analytics/core/Updates/2.15.0-b3.php new file mode 100644 index 00000000..374563c6 --- /dev/null +++ b/www/analytics/core/Updates/2.15.0-b3.php @@ -0,0 +1,32 @@ + array(1060) + ); + return $updateSql; + } + + public function doUpdate(Updater $updater) + { + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); + } +} diff --git a/www/analytics/core/Updates/2.15.0-b4.php b/www/analytics/core/Updates/2.15.0-b4.php new file mode 100644 index 00000000..132c3319 --- /dev/null +++ b/www/analytics/core/Updates/2.15.0-b4.php @@ -0,0 +1,25 @@ +activatePlugin('Heartbeat'); + } catch (\Exception $e) { + } + } +} \ No newline at end of file diff --git a/www/analytics/core/Updates/2.16.0-rc2.php b/www/analytics/core/Updates/2.16.0-rc2.php new file mode 100644 index 00000000..2bddb6b5 --- /dev/null +++ b/www/analytics/core/Updates/2.16.0-rc2.php @@ -0,0 +1,28 @@ +isPluginActivated($pluginName)) { + $pluginManager->activatePlugin($pluginName); + } + } catch (\Exception $e) { + } + } +} \ No newline at end of file diff --git a/www/analytics/core/Updates/2.2.0-b15.php b/www/analytics/core/Updates/2.2.0-b15.php index 8437a83d..e161b8cb 100644 --- a/www/analytics/core/Updates/2.2.0-b15.php +++ b/www/analytics/core/Updates/2.2.0-b15.php @@ -1,21 +1,20 @@ activatePlugin('ZenMode'); + } catch (\Exception $e) { + } + } +} diff --git a/www/analytics/core/Updates/2.3.0-rc2.php b/www/analytics/core/Updates/2.3.0-rc2.php new file mode 100644 index 00000000..c24cb257 --- /dev/null +++ b/www/analytics/core/Updates/2.3.0-rc2.php @@ -0,0 +1,25 @@ +activatePlugin('Morpheus'); + } catch (\Exception $e) { + } + + try { + \Piwik\Plugin\Manager::getInstance()->deactivatePlugin('Zeitgeist'); + self::deletePluginFromConfigFile('Zeitgeist'); + } catch (\Exception $e) { + } + } +} diff --git a/www/analytics/core/Updates/2.4.0-b2.php b/www/analytics/core/Updates/2.4.0-b2.php new file mode 100644 index 00000000..16d09d03 --- /dev/null +++ b/www/analytics/core/Updates/2.4.0-b2.php @@ -0,0 +1,27 @@ +activatePlugin('LeftMenu'); + } catch (\Exception $e) { + } + + try { + $pluginManager->deactivatePlugin('Zeitgeist'); + } catch (\Exception $e) { + } + + try { + $pluginManager->uninstallPlugin('Zeitgeist'); + } catch (\Exception $e) { + } + } +} diff --git a/www/analytics/core/Updates/2.4.0-b4.php b/www/analytics/core/Updates/2.4.0-b4.php new file mode 100644 index 00000000..ec613ab0 --- /dev/null +++ b/www/analytics/core/Updates/2.4.0-b4.php @@ -0,0 +1,35 @@ +getAllPluginsNames(); + + if (!in_array('Zeitgeist', $pluginNames)) { + return; + } + + try { + $pluginManager->deactivatePlugin('Zeitgeist'); + } catch (\Exception $e) { + } + + try { + $pluginManager->uninstallPlugin('Zeitgeist'); + } catch (\Exception $e) { + } + } +} diff --git a/www/analytics/core/Updates/2.4.0-b6.php b/www/analytics/core/Updates/2.4.0-b6.php new file mode 100644 index 00000000..d8c4674b --- /dev/null +++ b/www/analytics/core/Updates/2.4.0-b6.php @@ -0,0 +1,25 @@ +activatePlugin('DevicesDetection'); + } catch (\Exception $e) { + } + } +} diff --git a/www/analytics/core/Updates/2.4.0-b8.php b/www/analytics/core/Updates/2.4.0-b8.php new file mode 100644 index 00000000..225a2aa9 --- /dev/null +++ b/www/analytics/core/Updates/2.4.0-b8.php @@ -0,0 +1,29 @@ + false, + ); + } + + public function doUpdate(Updater $updater) + { + $updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); + } +} diff --git a/www/analytics/core/Updates/2.5.0-b1.php b/www/analytics/core/Updates/2.5.0-b1.php new file mode 100644 index 00000000..bd1863e4 --- /dev/null +++ b/www/analytics/core/Updates/2.5.0-b1.php @@ -0,0 +1,37 @@ +Debug; + + if (array_key_exists('disable_merged_assets', $debug)) { + $development = $config->Development; + $development['disable_merged_assets'] = $debug['disable_merged_assets']; + unset($debug['disable_merged_assets']); + + $config->Debug = $debug; + $config->Development = $development; + $config->forceSave(); + } + } +} diff --git a/www/analytics/core/Updates/2.5.0-rc2.php b/www/analytics/core/Updates/2.5.0-rc2.php new file mode 100644 index 00000000..1cac2a92 --- /dev/null +++ b/www/analytics/core/Updates/2.5.0-rc2.php @@ -0,0 +1,69 @@ +Plugins_Tracker = array(); + $config->forceSave(); + } +} diff --git a/www/analytics/core/Updates/2.7.0-b2.php b/www/analytics/core/Updates/2.7.0-b2.php new file mode 100644 index 00000000..050de34e --- /dev/null +++ b/www/analytics/core/Updates/2.7.0-b2.php @@ -0,0 +1,28 @@ +activatePlugin('Contents'); + } catch (\Exception $e) { + } + } +} diff --git a/www/analytics/core/Updates/2.7.0-b4.php b/www/analytics/core/Updates/2.7.0-b4.php new file mode 100644 index 00000000..4ef52795 --- /dev/null +++ b/www/analytics/core/Updates/2.7.0-b4.php @@ -0,0 +1,33 @@ +activatePlugin('Contents'); + } catch (\Exception $e) { + } + } + + public static function isMajorUpdate() + { + return true; + } +} diff --git a/www/analytics/core/Updates/2.9.0-b1.php b/www/analytics/core/Updates/2.9.0-b1.php new file mode 100644 index 00000000..d22b9355 --- /dev/null +++ b/www/analytics/core/Updates/2.9.0-b1.php @@ -0,0 +1,89 @@ +executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); + + self::updateIPAnonymizationSettings(); + + try { + Manager::getInstance()->activatePlugin('TestRunner'); + } catch (\Exception $e) { + } + } + + private static function updateBrowserEngine($sql) + { + $sql[sprintf("ALTER TABLE `%s` ADD COLUMN `config_browser_engine` VARCHAR(10) NOT NULL", Common::prefixTable('log_visit'))] = 1060; + + $browserEngineMatch = array( + 'Trident' => array('IE'), + 'Gecko' => array('NS', 'PX', 'FF', 'FB', 'CA', 'GA', 'KM', 'MO', 'SM', 'CO', 'FE', 'KP', 'KZ', 'TB'), + 'KHTML' => array('KO'), + 'WebKit' => array('SF', 'CH', 'OW', 'AR', 'EP', 'FL', 'WO', 'AB', 'IR', 'CS', 'FD', 'HA', 'MI', 'GE', 'DF', 'BB', 'BP', 'TI', 'CF', 'RK', 'B2', 'NF'), + 'Presto' => array('OP'), + ); + + // Update visits, fill in now missing engine + $engineUpdate = "''"; + $ifFragment = "IF (`config_browser_name` IN ('%s'), '%s', %s)"; + + foreach ($browserEngineMatch as $engine => $browsers) { + $engineUpdate = sprintf($ifFragment, implode("','", $browsers), $engine, $engineUpdate); + } + + $engineUpdate = sprintf("UPDATE %s SET `config_browser_engine` = %s", Common::prefixTable('log_visit'), $engineUpdate); + $sql[$engineUpdate] = false; + + $archiveBlobTables = Db::get()->fetchCol("SHOW TABLES LIKE '%archive_blob%'"); + + // for each blob archive table, rename UserSettings_browserType to DevicesDetection_browserEngines + foreach ($archiveBlobTables as $table) { + + // try to rename old archives + $sql[sprintf("UPDATE IGNORE %s SET name='DevicesDetection_browserEngines' WHERE name = 'UserSettings_browserType'", $table)] = false; + } + + return $sql; + } + + private static function updateIPAnonymizationSettings() + { + $optionName = 'PrivacyManager.ipAnonymizerEnabled'; + + $value = Option::get($optionName); + + if ($value !== false) { + // If the config is defined, nothing to do + return; + } + + // We disable IP anonymization if it wasn't configured (because by default it has gone from disabled to enabled) + Option::set($optionName, '0'); + } +} diff --git a/www/analytics/core/Updates/2.9.0-b7.php b/www/analytics/core/Updates/2.9.0-b7.php new file mode 100644 index 00000000..bc0338ef --- /dev/null +++ b/www/analytics/core/Updates/2.9.0-b7.php @@ -0,0 +1,90 @@ +executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater)); + } + + private static function addArchivingIdMigrationQueries($sql) + { + $tables = ArchiveTableCreator::getTablesArchivesInstalled(); + + foreach ($tables as $table) { + $type = ArchiveTableCreator::getTypeFromTableName($table); + + if ($type === ArchiveTableCreator::NUMERIC_TABLE) { + $maxId = Db::fetchOne('SELECT MAX(idarchive) FROM ' . $table); + + if (!empty($maxId)) { + $maxId = (int) $maxId + 500; + } else { + $maxId = 1; + } + + $query = self::getQueryToCreateSequence($table, $maxId); + // refs #6696, ignores Integrity constraint violation: 1062 Duplicate entry 'piwik_archive_numeric_2010_01' for key 'PRIMARY' + $sql[$query] = '1062'; + } + } + + return $sql; + } + + private static function getQueryToCreateSequence($name, $initialValue) + { + $table = self::getSequenceTableName(); + $query = sprintf("INSERT INTO %s (name, value) VALUES ('%s', %d)", $table, $name, $initialValue); + + return $query; + } + + /** + * @return string + */ + private static function addCreateSequenceTableQuery($sql) + { + $dbSettings = new Db\Settings(); + $engine = $dbSettings->getEngine(); + $table = self::getSequenceTableName(); + + $query = "CREATE TABLE `$table` ( + `name` VARCHAR(120) NOT NULL, + `value` BIGINT(20) UNSIGNED NOT NULL, + PRIMARY KEY(`name`) + ) ENGINE=$engine DEFAULT CHARSET=utf8"; + + $sql[$query] = 1050; + + return $sql; + } + + private static function getSequenceTableName() + { + return Common::prefixTable('sequence'); + } +} diff --git a/www/analytics/core/Url.php b/www/analytics/core/Url.php index 1fe58a96..2a301a9b 100644 --- a/www/analytics/core/Url.php +++ b/www/analytics/core/Url.php @@ -1,6 +1,6 @@ 'UserSettings', + * 'module' => 'DevicesDetection', * 'action' => 'index' * )); * Url::redirectToUrl($url); * } - * + * * **Link to a different controller action in a template** - * + * * public function myControllerAction() * { * $url = Url::getCurrentQueryStringWithParametersModified(array( @@ -45,43 +46,38 @@ use Exception; * $view->realtimeMapUrl = $url; * return $view->render(); * } - * + * */ class Url { - /** - * List of hosts that are never checked for validity. - */ - private static $alwaysTrustedHosts = array('localhost', '127.0.0.1', '::1', '[::1]'); - /** * Returns the current URL. * * @return string eg, `"http://example.org/dir1/dir2/index.php?param1=value1¶m2=value2"` * @api */ - static public function getCurrentUrl() + public static function getCurrentUrl() { return self::getCurrentScheme() . '://' . self::getCurrentHost() - . self::getCurrentScriptName() + . self::getCurrentScriptName(false) . self::getCurrentQueryString(); } /** * Returns the current URL without the query string. - * + * * @param bool $checkTrustedHost Whether to do trusted host check. Should ALWAYS be true, * except in {@link Piwik\Plugin\Controller}. * @return string eg, `"http://example.org/dir1/dir2/index.php"` if the current URL is * `"http://example.org/dir1/dir2/index.php?param1=value1¶m2=value2"`. * @api */ - static public function getCurrentUrlWithoutQueryString($checkTrustedHost = true) + public static function getCurrentUrlWithoutQueryString($checkTrustedHost = true) { return self::getCurrentScheme() . '://' . self::getCurrentHost($default = 'unknown', $checkTrustedHost) - . self::getCurrentScriptName(); + . self::getCurrentScriptName(false); } /** @@ -92,7 +88,7 @@ class Url * `"http://example.org/dir1/dir2/index.php?param1=value1¶m2=value2"`. * @api */ - static public function getCurrentUrlWithoutFileName() + public static function getCurrentUrlWithoutFileName() { return self::getCurrentScheme() . '://' . self::getCurrentHost() @@ -106,7 +102,7 @@ class Url * `"http://example.org/dir1/dir2/index.php?param1=value1¶m2=value2"` * @api */ - static public function getCurrentScriptPath() + public static function getCurrentScriptPath() { $queryString = self::getCurrentScriptName(); @@ -123,11 +119,12 @@ class Url /** * Returns the path to the script being executed. Includes the script file name. * + * @param bool $removePathInfo If true (default value) then the PATH_INFO will be stripped. * @return string eg, `"/dir1/dir2/index.php"` if the current URL is * `"http://example.org/dir1/dir2/index.php?param1=value1¶m2=value2"` * @api */ - static public function getCurrentScriptName() + public static function getCurrentScriptName($removePathInfo = true) { $url = ''; @@ -145,7 +142,7 @@ class Url } // strip path_info - if (isset($_SERVER['PATH_INFO'])) { + if ($removePathInfo && isset($_SERVER['PATH_INFO'])) { $url = substr($url, 0, -strlen($_SERVER['PATH_INFO'])); } } @@ -177,21 +174,12 @@ class Url * @return string `'https'` or `'http'` * @api */ - static public function getCurrentScheme() + public static function getCurrentScheme() { - try { - $assume_secure_protocol = @Config::getInstance()->General['assume_secure_protocol']; - } catch (Exception $e) { - $assume_secure_protocol = false; - } - if ($assume_secure_protocol - || (isset($_SERVER['HTTPS']) - && ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] === true)) - || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') - ) { + if (self::isPiwikConfiguredToAssumeSecureConnection()) { return 'https'; } - return 'http'; + return self::getCurrentSchemeFromRequestHeader(); } /** @@ -202,7 +190,7 @@ class Url * value from the request. * @return bool `true` if valid; `false` otherwise. */ - static public function isValidHost($host = false) + public static function isValidHost($host = false) { // only do trusted host check if it's enabled if (isset(Config::getInstance()->General['enable_trusted_host_check']) @@ -213,33 +201,38 @@ class Url if ($host === false) { $host = @$_SERVER['HTTP_HOST']; - if (empty($host)) // if no current host, assume valid - { + if (empty($host)) { + // if no current host, assume valid + return true; } } + // if host is in hardcoded whitelist, assume it's valid - if (in_array($host, self::$alwaysTrustedHosts)) { + if (in_array($host, self::getAlwaysTrustedHosts())) { return true; } $trustedHosts = self::getTrustedHosts(); + // Only punctuation we allow is '[', ']', ':', '.', '_' and '-' + $hostLength = strlen($host); + if ($hostLength !== strcspn($host, '`~!@#$%^&*()+={}\\|;"\'<>,?/ ')) { + return false; + } + // if no trusted hosts, just assume it's valid if (empty($trustedHosts)) { self::saveTrustedHostnameInConfig($host); return true; } - // Only punctuation we allow is '[', ']', ':', '.' and '-' - $hostLength = strlen($host); - if ($hostLength !== strcspn($host, '`~!@#$%^&*()_+={}\\|;"\'<>,?/ ')) { - return false; - } - + // Escape trusted hosts for preg_match call below foreach ($trustedHosts as &$trustedHost) { $trustedHost = preg_quote($trustedHost); } + $trustedHosts = str_replace("/", "\\/", $trustedHosts); + $untrustedHost = Common::mb_strtolower($host); $untrustedHost = rtrim($untrustedHost, '.'); @@ -258,11 +251,21 @@ class Url * @return bool */ public static function saveTrustedHostnameInConfig($host) + { + return self::saveHostsnameInConfig($host, 'General', 'trusted_hosts'); + } + + public static function saveCORSHostnameInConfig($host) + { + return self::saveHostsnameInConfig($host, 'General', 'cors_domains'); + } + + protected static function saveHostsnameInConfig($host, $domain, $key) { if (Piwik::hasUserSuperUserAccess() && file_exists(Config::getLocalConfigPath()) ) { - $general = Config::getInstance()->General; + $config = Config::getInstance()->$domain; if (!is_array($host)) { $host = array($host); } @@ -270,8 +273,8 @@ class Url if (empty($host)) { return false; } - $general['trusted_hosts'] = $host; - Config::getInstance()->General = $general; + $config[$key] = $host; + Config::getInstance()->$domain = $config; Config::getInstance()->forceSave(); return true; } @@ -285,7 +288,7 @@ class Url * except in Controller. * @return string|bool eg, `"demo.piwik.org"` or false if no host found. */ - static public function getHost($checkIfTrusted = true) + public static function getHost($checkIfTrusted = true) { // HTTP/1.1 request if (isset($_SERVER['HTTP_HOST']) @@ -305,11 +308,11 @@ class Url } /** - * Sets the host. Useful for CLI scripts, eg. archive.php - * + * Sets the host. Useful for CLI scripts, eg. core:archive command + * * @param $host string */ - static public function setHost($host) + public static function setHost($host) { $_SERVER['HTTP_HOST'] = $host; } @@ -324,12 +327,12 @@ class Url * `"http://example.org/dir1/dir2/index.php?param1=value1¶m2=value2"` * @api */ - static public function getCurrentHost($default = 'unknown', $checkTrustedHost = true) + public static function getCurrentHost($default = 'unknown', $checkTrustedHost = true) { $hostHeaders = array(); $config = Config::getInstance()->General; - if(isset($config['proxy_host_headers'])) { + if (isset($config['proxy_host_headers'])) { $hostHeaders = $config['proxy_host_headers']; } @@ -350,7 +353,7 @@ class Url * `"http://example.org/dir1/dir2/index.php?param1=value1¶m2=value2"` * @api */ - static public function getCurrentQueryString() + public static function getCurrentQueryString() { $url = ''; if (isset($_SERVER['QUERY_STRING']) @@ -367,14 +370,14 @@ class Url * * @return array If current URL is `"http://example.org/dir1/dir2/index.php?param1=value1¶m2=value2"` * this will return: - * + * * array( * 'param1' => string 'value1', * 'param2' => string 'value2' * ) * @api */ - static public function getArrayFromCurrentQueryString() + public static function getArrayFromCurrentQueryString() { $queryString = self::getCurrentQueryString(); $urlValues = UrlHelper::getArrayFromQueryString($queryString); @@ -392,7 +395,7 @@ class Url * @return string eg, `"?param2=value2¶m3=value3"` * @api */ - static function getCurrentQueryStringWithParametersModified($params) + public static function getCurrentQueryStringWithParametersModified($params) { $urlValues = self::getArrayFromCurrentQueryString(); foreach ($params as $key => $value) { @@ -407,13 +410,13 @@ class Url /** * Converts an array of parameters name => value mappings to a query - * string. - * + * string. Values must already be URL encoded before you call this function. + * * @param array $parameters eg. `array('param1' => 10, 'param2' => array(1,2))` * @return string eg. `"param1=10¶m2[]=1¶m2[]=2"` * @api */ - static public function getQueryStringFromParameters($parameters) + public static function getQueryStringFromParameters($parameters) { $query = ''; foreach ($parameters as $name => $value) { @@ -432,7 +435,7 @@ class Url return $query; } - static public function getQueryStringFromUrl($url) + public static function getQueryStringFromUrl($url) { return parse_url($url, PHP_URL_QUERY); } @@ -440,10 +443,10 @@ class Url /** * Redirects the user to the referrer. If no referrer exists, the user is redirected * to the current URL without query string. - * + * * @api */ - static public function redirectToReferrer() + public static function redirectToReferrer() { $referrer = self::getReferrer(); if ($referrer !== false) { @@ -452,34 +455,47 @@ class Url self::redirectToUrl(self::getCurrentUrlWithoutQueryString()); } - /** - * Redirects the user to the specified URL. - * - * @param string $url - * @api - */ - static public function redirectToUrl($url) + private static function redirectToUrlNoExit($url) { if (UrlHelper::isLookLikeUrl($url) || strpos($url, 'index.php') === 0 ) { - @header("Location: $url"); + Common::sendResponseCode(302); + Common::sendHeader("Location: $url"); } else { echo "Invalid URL to redirect to."; } - if(Common::isPhpCliMode()) { - die("If you were using a browser, Piwik would redirect you to this URL: $url \n\n"); + if (Common::isPhpCliMode()) { + throw new Exception("If you were using a browser, Piwik would redirect you to this URL: $url \n\n"); } + } + + /** + * Redirects the user to the specified URL. + * + * @param string $url + * @throws Exception + * @api + */ + public static function redirectToUrl($url) + { + // Close the session manually. + // We should not have to call this because it was registered via register_shutdown_function, + // but it is not always called fast enough + Session::close(); + + self::redirectToUrlNoExit($url); + exit; } /** * If the page is using HTTP, redirect to the same page over HTTPS */ - static public function redirectToHttps() + public static function redirectToHttps() { - if(ProxyHttp::isHttps()) { + if (ProxyHttp::isHttps()) { return; } $url = self::getCurrentUrl(); @@ -493,7 +509,7 @@ class Url * @return string|false * @api */ - static public function getReferrer() + public static function getReferrer() { if (!empty($_SERVER['HTTP_REFERER'])) { return $_SERVER['HTTP_REFERER']; @@ -508,7 +524,7 @@ class Url * @return bool True if local; false otherwise. * @api */ - static public function isLocalUrl($url) + public static function isLocalUrl($url) { if (empty($url)) { return true; @@ -523,40 +539,198 @@ class Url } // drop port numbers from hostnames and IP addresses - $hosts = array_map(array('Piwik\IP', 'sanitizeIp'), $hosts); + $hosts = array_map(array('self', 'getHostSanitized'), $hosts); $disableHostCheck = Config::getInstance()->General['enable_trusted_host_check'] == 0; // compare scheme and host $parsedUrl = @parse_url($url); - $host = IP::sanitizeIp(@$parsedUrl['host']); + $host = IPUtils::sanitizeIp(@$parsedUrl['host']); return !empty($host) - && ($disableHostCheck || in_array($host, $hosts)) - && !empty($parsedUrl['scheme']) - && in_array($parsedUrl['scheme'], array('http', 'https')); + && ($disableHostCheck || in_array($host, $hosts)) + && !empty($parsedUrl['scheme']) + && in_array($parsedUrl['scheme'], array('http', 'https')); + } + + /** + * Checks whether the given host is a local host like `127.0.0.1` or `localhost`. + * + * @param string $host + * @return bool + */ + public static function isLocalHost($host) + { + if (empty($host)) { + return false; + } + + return in_array($host, Url::getLocalHostnames(), true); } public static function getTrustedHostsFromConfig() { - $trustedHosts = @Config::getInstance()->General['trusted_hosts']; - if (!is_array($trustedHosts)) { - return array(); - } - foreach ($trustedHosts as &$trustedHost) { - // Case user wrote in the config, http://example.com/test instead of example.com - if (UrlHelper::isLookLikeUrl($trustedHost)) { - $trustedHost = parse_url($trustedHost, PHP_URL_HOST); + $hosts = self::getHostsFromConfig('General', 'trusted_hosts'); + + // Case user wrote in the config, http://example.com/test instead of example.com + foreach ($hosts as &$host) { + if (UrlHelper::isLookLikeUrl($host)) { + $host = parse_url($host, PHP_URL_HOST); } } - return $trustedHosts; + return $hosts; } public static function getTrustedHosts() { - $trustedHosts = self::getTrustedHostsFromConfig(); + return self::getTrustedHostsFromConfig(); + } - /* used by Piwik PRO */ - Piwik::postEvent('Url.filterTrustedHosts', array(&$trustedHosts)); + public static function getCorsHostsFromConfig() + { + return self::getHostsFromConfig('General', 'cors_domains'); + } - return $trustedHosts; + /** + * Returns hostname, without port numbers + * + * @param $host + * @return array + */ + public static function getHostSanitized($host) + { + if (!class_exists("Piwik\\Network\\IPUtils")) { + throw new Exception("Piwik\\Network\\IPUtils could not be found, maybe you are using Piwik from git and need to update Composer. $ php composer.phar update"); + } + return IPUtils::sanitizeIp($host); + } + + protected static function getHostsFromConfig($domain, $key) + { + $config = @Config::getInstance()->$domain; + + if (!isset($config[$key])) { + return array(); + } + + $hosts = $config[$key]; + if (!is_array($hosts)) { + return array(); + } + return $hosts; + } + + /** + * Returns the host part of any valid URL. + * + * @param string $url Any fully qualified URL + * @return string|null The actual host in lower case or null if $url is not a valid fully qualified URL. + */ + public static function getHostFromUrl($url) + { + $parsedUrl = parse_url($url); + + if (empty($parsedUrl['host'])) { + return; + } + + return Common::mb_strtolower($parsedUrl['host']); + } + + /** + * Checks whether any of the given URLs has the given host. If not, we will also check whether any URL uses a + * subdomain of the given host. For instance if host is "example.com" and a URL is "http://www.example.com" we + * consider this as valid and return true. The always trusted hosts such as "127.0.0.1" are considered valid as well. + * + * @param $host + * @param $urls + * @return bool + */ + public static function isHostInUrls($host, $urls) + { + if (empty($host)) { + return false; + } + + $host = Common::mb_strtolower($host); + + if (!empty($urls)) { + foreach ($urls as $url) { + if (Common::mb_strtolower($url) === $host) { + return true; + } + + $siteHost = self::getHostFromUrl($url); + + if ($siteHost === $host) { + return true; + } + + if (Common::stringEndsWith($siteHost, '.' . $host)) { + // allow subdomains + return true; + } + } + } + + return in_array($host, self::getAlwaysTrustedHosts()); + } + + /** + * List of hosts that are never checked for validity. + * + * @return array + */ + private static function getAlwaysTrustedHosts() + { + return self::getLocalHostnames(); + } + + /** + * @return array + */ + public static function getLocalHostnames() + { + return array('localhost', '127.0.0.1', '::1', '[::1]'); + } + + /** + * @return bool + */ + public static function isSecureConnectionAssumedByPiwikButNotForcedYet() + { + $isSecureConnectionLikelyNotUsed = Url::isSecureConnectionLikelyNotUsed(); + $hasSessionCookieSecureFlag = ProxyHttp::isHttps(); + $isSecureConnectionAssumedByPiwikButNotForcedYet = Url::isPiwikConfiguredToAssumeSecureConnection() && !SettingsPiwik::isHttpsForced(); + + return $isSecureConnectionLikelyNotUsed + && $hasSessionCookieSecureFlag + && $isSecureConnectionAssumedByPiwikButNotForcedYet; + } + + /** + * @return string + */ + protected static function getCurrentSchemeFromRequestHeader() + { + if ((isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] === true)) + || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') + ) { + + return 'https'; + } + return 'http'; + } + + protected static function isSecureConnectionLikelyNotUsed() + { + return Url::getCurrentSchemeFromRequestHeader() == 'http'; + } + + /** + * @return bool + */ + protected static function isPiwikConfiguredToAssumeSecureConnection() + { + $assume_secure_protocol = @Config::getInstance()->General['assume_secure_protocol']; + return (bool) $assume_secure_protocol; } } diff --git a/www/analytics/core/UrlHelper.php b/www/analytics/core/UrlHelper.php index 2ce26ba1..66a0e64e 100644 --- a/www/analytics/core/UrlHelper.php +++ b/www/analytics/core/UrlHelper.php @@ -1,6 +1,6 @@ getCountryList(true))); } return preg_replace( @@ -96,21 +101,22 @@ class UrlHelper * We don't need a precise test here because the value comes from the website * tracked source code and the URLs may look very strange. * + * @api * @param string $url * @return bool */ public static function isLookLikeUrl($url) { - return preg_match('~^(ftp|news|http|https)?://(.*)$~D', $url, $matches) !== 0 - && strlen($matches[2]) > 0; + return preg_match('~^(([[:alpha:]][[:alnum:]+.-]*)?:)?//(.*)$~D', $url, $matches) !== 0 + && strlen($matches[3]) > 0; } /** * Returns a URL created from the result of the [parse_url](http://php.net/manual/en/function.parse-url.php) * function. - * + * * Copied from the PHP comments at [http://php.net/parse_url](http://php.net/parse_url). - * + * * @param array $parsed Result of [parse_url](http://php.net/manual/en/function.parse-url.php). * @return false|string The URL or `false` if `$parsed` isn't an array. * @api @@ -149,6 +155,17 @@ class UrlHelper if (strlen($urlQuery) == 0) { return array(); } + + // TODO: this method should not use a cache. callers should instead have their own cache, configured through DI. + // one undesirable side effect of using a cache here, is that this method can now init the StaticContainer, which makes setting + // test environment for RequestCommand more complicated. + $cache = Cache::getTransientCache(); + $cacheKey = 'arrayFromQuery' . $urlQuery; + + if ($cache->contains($cacheKey)) { + return $cache->fetch($cacheKey); + } + if ($urlQuery[0] == '?') { $urlQuery = substr($urlQuery, 1); } @@ -190,10 +207,13 @@ class UrlHelper $nameToValue[$name] = array(); } array_push($nameToValue[$name], $value); - } else if (!empty($name)) { + } elseif (!empty($name)) { $nameToValue[$name] = $value; } } + + $cache->save($cacheKey, $nameToValue); + return $nameToValue; } @@ -208,6 +228,7 @@ class UrlHelper public static function getParameterFromQueryString($urlQuery, $parameter) { $nameToValue = self::getArrayFromQueryString($urlQuery); + if (isset($nameToValue[$parameter])) { return $nameToValue[$parameter]; } @@ -226,7 +247,10 @@ class UrlHelper $parsedUrl = parse_url($url); $result = ''; if (isset($parsedUrl['path'])) { - $result .= substr($parsedUrl['path'], 1); + if (substr($parsedUrl['path'], 0, 1) == '/') { + $parsedUrl['path'] = substr($parsedUrl['path'], 1); + } + $result .= $parsedUrl['path']; } if (isset($parsedUrl['query'])) { $result .= '?' . $parsedUrl['query']; @@ -234,229 +258,6 @@ class UrlHelper return $result; } - - /** - * Extracts a keyword from a raw not encoded URL. - * Will only extract keyword if a known search engine has been detected. - * Returns the keyword: - * - in UTF8: automatically converted from other charsets when applicable - * - strtolowered: "QUErY test!" will return "query test!" - * - trimmed: extra spaces before and after are removed - * - * Lists of supported search engines can be found in /core/DataFiles/SearchEngines.php - * The function returns false when a keyword couldn't be found. - * eg. if the url is "http://www.google.com/partners.html" this will return false, - * as the google keyword parameter couldn't be found. - * - * @see unit tests in /tests/core/Common.test.php - * @param string $referrerUrl URL referrer URL, eg. $_SERVER['HTTP_REFERER'] - * @return array|bool false if a keyword couldn't be extracted, - * or array( - * 'name' => 'Google', - * 'keywords' => 'my searched keywords') - */ - public static function extractSearchEngineInformationFromUrl($referrerUrl) - { - $referrerParsed = @parse_url($referrerUrl); - $referrerHost = ''; - if (isset($referrerParsed['host'])) { - $referrerHost = $referrerParsed['host']; - } - if (empty($referrerHost)) { - return false; - } - // some search engines (eg. Bing Images) use the same domain - // as an existing search engine (eg. Bing), we must also use the url path - $referrerPath = ''; - if (isset($referrerParsed['path'])) { - $referrerPath = $referrerParsed['path']; - } - - // no search query - if (!isset($referrerParsed['query'])) { - $referrerParsed['query'] = ''; - } - $query = $referrerParsed['query']; - - // Google Referrers URLs sometimes have the fragment which contains the keyword - if (!empty($referrerParsed['fragment'])) { - $query .= '&' . $referrerParsed['fragment']; - } - - $searchEngines = Common::getSearchEngineUrls(); - - $hostPattern = self::getLossyUrl($referrerHost); - if (array_key_exists($referrerHost . $referrerPath, $searchEngines)) { - $referrerHost = $referrerHost . $referrerPath; - } elseif (array_key_exists($hostPattern . $referrerPath, $searchEngines)) { - $referrerHost = $hostPattern . $referrerPath; - } elseif (array_key_exists($hostPattern, $searchEngines)) { - $referrerHost = $hostPattern; - } elseif (!array_key_exists($referrerHost, $searchEngines)) { - if (!strncmp($query, 'cx=partner-pub-', 15)) { - // Google custom search engine - $referrerHost = 'google.com/cse'; - } elseif (!strncmp($referrerPath, '/pemonitorhosted/ws/results/', 28)) { - // private-label search powered by InfoSpace Metasearch - $referrerHost = 'wsdsold.infospace.com'; - } elseif (strpos($referrerHost, '.images.search.yahoo.com') != false) { - // Yahoo! Images - $referrerHost = 'images.search.yahoo.com'; - } elseif (strpos($referrerHost, '.search.yahoo.com') != false) { - // Yahoo! - $referrerHost = 'search.yahoo.com'; - } else { - return false; - } - } - $searchEngineName = $searchEngines[$referrerHost][0]; - $variableNames = null; - if (isset($searchEngines[$referrerHost][1])) { - $variableNames = $searchEngines[$referrerHost][1]; - } - if (!$variableNames) { - $searchEngineNames = Common::getSearchEngineNames(); - $url = $searchEngineNames[$searchEngineName]; - $variableNames = $searchEngines[$url][1]; - } - if (!is_array($variableNames)) { - $variableNames = array($variableNames); - } - - $key = null; - if ($searchEngineName === 'Google Images' - || ($searchEngineName === 'Google' && strpos($referrerUrl, '/imgres') !== false) - ) { - if (strpos($query, '&prev') !== false) { - $query = urldecode(trim(self::getParameterFromQueryString($query, 'prev'))); - $query = str_replace('&', '&', strstr($query, '?')); - } - $searchEngineName = 'Google Images'; - } else if ($searchEngineName === 'Google' - && (strpos($query, '&as_') !== false || strpos($query, 'as_') === 0) - ) { - $keys = array(); - $key = self::getParameterFromQueryString($query, 'as_q'); - if (!empty($key)) { - array_push($keys, $key); - } - $key = self::getParameterFromQueryString($query, 'as_oq'); - if (!empty($key)) { - array_push($keys, str_replace('+', ' OR ', $key)); - } - $key = self::getParameterFromQueryString($query, 'as_epq'); - if (!empty($key)) { - array_push($keys, "\"$key\""); - } - $key = self::getParameterFromQueryString($query, 'as_eq'); - if (!empty($key)) { - array_push($keys, "-$key"); - } - $key = trim(urldecode(implode(' ', $keys))); - } - - if ($searchEngineName === 'Google') { - // top bar menu - $tbm = self::getParameterFromQueryString($query, 'tbm'); - switch ($tbm) { - case 'isch': - $searchEngineName = 'Google Images'; - break; - case 'vid': - $searchEngineName = 'Google Video'; - break; - case 'shop': - $searchEngineName = 'Google Shopping'; - break; - } - } - - if (empty($key)) { - foreach ($variableNames as $variableName) { - if ($variableName[0] == '/') { - // regular expression match - if (preg_match($variableName, $referrerUrl, $matches)) { - $key = trim(urldecode($matches[1])); - break; - } - } else { - // search for keywords now &vname=keyword - $key = self::getParameterFromQueryString($query, $variableName); - $key = trim(urldecode($key)); - - // Special case: Google & empty q parameter - if (empty($key) - && $variableName == 'q' - - && ( - // Google search with no keyword - ($searchEngineName == 'Google' - && ( // First, they started putting an empty q= parameter - strpos($query, '&q=') !== false - || strpos($query, '?q=') !== false - // then they started sending the full host only (no path/query string) - || (empty($query) && (empty($referrerPath) || $referrerPath == '/') && empty($referrerParsed['fragment'])) - ) - ) - // search engines with no keyword - || $searchEngineName == 'Google Images' - || $searchEngineName == 'DuckDuckGo') - ) { - $key = false; - } - if (!empty($key) - || $key === false - ) { - break; - } - } - } - } - - // $key === false is the special case "No keyword provided" which is a Search engine match - if ($key === null - || $key === '' - ) { - return false; - } - - if (!empty($key)) { - if (function_exists('iconv') - && isset($searchEngines[$referrerHost][3]) - ) { - // accepts string, array, or comma-separated list string in preferred order - $charsets = $searchEngines[$referrerHost][3]; - if (!is_array($charsets)) { - $charsets = explode(',', $charsets); - } - - if (!empty($charsets)) { - $charset = $charsets[0]; - if (count($charsets) > 1 - && function_exists('mb_detect_encoding') - ) { - $charset = mb_detect_encoding($key, $charsets); - if ($charset === false) { - $charset = $charsets[0]; - } - } - - $newkey = @iconv($charset, 'UTF-8//IGNORE', $key); - if (!empty($newkey)) { - $key = $newkey; - } - } - } - - $key = Common::mb_strtolower($key); - } - - return array( - 'name' => $searchEngineName, - 'keywords' => $key, - ); - } - /** * Returns the query part from any valid url and adds additional parameters to the query part if needed. * diff --git a/www/analytics/core/Version.php b/www/analytics/core/Version.php index d187b41b..7725230d 100644 --- a/www/analytics/core/Version.php +++ b/www/analytics/core/Version.php @@ -1,6 +1,6 @@ isStableVersion($version) || $this->isNonStableVersion($version); + } + + private function isNonStableVersion($version) + { + return (bool) preg_match('/^(\d+)\.(\d+)\.(\d+)-.{1,4}(\d+)$/', $version); + } } diff --git a/www/analytics/core/View.php b/www/analytics/core/View.php index 311b58e3..b57a7f55 100644 --- a/www/analytics/core/View.php +++ b/www/analytics/core/View.php @@ -1,6 +1,6 @@ property2 = "another view property"; * return $view->render(); * } - * + * * * @api */ @@ -118,7 +119,7 @@ class View implements ViewInterface /** * Constructor. - * + * * @param string $templateFile The template file to load. Must be in the following format: * `"@MyPlugin/templateFileName"`. Note the absence of .twig * from the end of the name. @@ -190,6 +191,17 @@ class View implements ViewInterface return $this->templateVars[$key]; } + /** + * Returns true if a template variable has been set or not. + * + * @param string $name The name of the template variable. + * @return bool + */ + public function __isset($name) + { + return isset($this->templateVars[$name]); + } + private function initializeTwig() { $piwikTwig = new Twig(); @@ -211,39 +223,54 @@ class View implements ViewInterface $this->url = Common::sanitizeInputValue(Url::getCurrentUrl()); $this->token_auth = Piwik::getCurrentUserTokenAuth(); $this->userHasSomeAdminAccess = Piwik::isUserHasSomeAdminAccess(); + $this->userIsAnonymous = Piwik::isUserIsAnonymous(); $this->userIsSuperUser = Piwik::hasUserSuperUserAccess(); $this->latest_version_available = UpdateCheck::isNewestVersionAvailable(); $this->disableLink = Common::getRequestVar('disableLink', 0, 'int'); $this->isWidget = Common::getRequestVar('widget', 0, 'int'); - $this->cacheBuster = UIAssetCacheBuster::getInstance()->piwikVersionBasedCacheBuster(); + $piwikProAds = StaticContainer::get('Piwik\PiwikPro\Advertising'); + $this->arePiwikProAdsEnabled = $piwikProAds->arePiwikProAdsEnabled(); + + if (Development::isEnabled()) { + $cacheBuster = rand(0, 10000); + } else { + $cacheBuster = UIAssetCacheBuster::getInstance()->piwikVersionBasedCacheBuster(); + } + + $this->cacheBuster = $cacheBuster; + $this->loginModule = Piwik::getLoginPluginName(); $user = APIUsersManager::getInstance()->getUser($this->userLogin); $this->userAlias = $user['alias']; } catch (Exception $e) { - // can fail, for example at installation (no plugin loaded yet) - } + Log::debug($e); - try { - $this->totalTimeGeneration = Registry::get('timer')->getTime(); - $this->totalNumberOfQueries = Profiler::getQueryCount(); - } catch (Exception $e) { - $this->totalNumberOfQueries = 0; + // can fail, for example at installation (no plugin loaded yet) } ProxyHttp::overrideCacheControlHeaders('no-store'); - @header('Content-Type: ' . $this->contentType); + Common::sendHeader('Content-Type: ' . $this->contentType); // always sending this header, sometimes empty, to ensure that Dashboard embed loads (which could call this header() multiple times, the last one will prevail) - @header('X-Frame-Options: ' . (string)$this->xFrameOptions); + Common::sendHeader('X-Frame-Options: ' . (string)$this->xFrameOptions); return $this->renderTwigTemplate(); } protected function renderTwigTemplate() { - $output = $this->twig->render($this->getTemplateFile(), $this->getTemplateVars()); + try { + $output = $this->twig->render($this->getTemplateFile(), $this->getTemplateVars()); + } catch (Exception $ex) { + // twig does not rethrow exceptions, it wraps them so we log the cause if we can find it + $cause = $ex->getPrevious(); + Log::debug($cause === null ? $ex : $cause); + + throw $ex; + } + $output = $this->applyFilter_cacheBuster($output); $helper = new Theme; @@ -253,8 +280,18 @@ class View implements ViewInterface protected function applyFilter_cacheBuster($output) { - $cacheBuster = UIAssetCacheBuster::getInstance()->piwikVersionBasedCacheBuster(); - $tag = 'cb=' . $cacheBuster; + $assetManager = AssetManager::getInstance(); + + $stylesheet = $assetManager->getMergedStylesheetAsset(); + if ($stylesheet->exists()) { + $content = $stylesheet->getContent(); + } else { + $content = $assetManager->getMergedStylesheet()->getContent(); + } + + $cacheBuster = UIAssetCacheBuster::getInstance(); + $tagJs = 'cb=' . $cacheBuster->piwikVersionBasedCacheBuster(); + $tagCss = 'cb=' . $cacheBuster->md5BasedCacheBuster($content); $pattern = array( '~