From e6764e9249c59523feb093149774cc74a19400d2 Mon Sep 17 00:00:00 2001 From: Christopher Butler Date: Thu, 3 Jul 2025 13:24:32 -0400 Subject: [PATCH] Add New Premium Plugin Why these changes are being introduced: A third premium plugin was purchased from InnoCraft. At the same time, one of the other premium plugins had a pending update. Per the documentation in the HOWTO-premium-plugins.md, this pass through the repository just installs the plugin directory and files but does not activate the plugin. Additionally, we revert the copying of the config.ini.php file back to the /var/www/html folder instead into the source folder. Now that we have established the persistent storage for the config.ini file, we don't need to worry about forcing it into the source. How this addresses that need: * Add the source code for the SearchEngineKeywordsPerformance plugin to the files/ directory * Add the new version for the HeatmapSessionRecording plugin to the files/ directory * Update the Dockerfile to copy the new plugin sources to the plugins/ directory * Update the Dockerfile to copy the config.ini.php to /var/www/html instead of to /usr/src/matomo * Update the backup-data.sh script to make it a little cleaner * Update the HOWTO-premium-plugins.md documentation * Update the HOWTO-miscellaneous.md documentation to address a TooManyRedirectsException Relevant ticket(s): * https://mitlibraries.atlassian.net/browse/INFRA-558 --- Dockerfile | 12 +- docs/HowTos/HOWTO-miscellaneous.md | 9 +- docs/HowTos/HOWTO-premium-plugins.md | 18 +- files/backup-data.sh | 30 +- .../API.php | 999 +++ .../Actions/ActionHsr.php | 61 + .../Activity/BaseActivity.php | 114 + .../Activity/HeatmapAdded.php | 41 + .../Activity/HeatmapDeleted.php | 46 + .../Activity/HeatmapEnded.php | 44 + .../Activity/HeatmapPaused.php | 46 + .../Activity/HeatmapResumed.php | 46 + .../Activity/HeatmapScreenshotDeleted.php | 46 + .../Activity/HeatmapUpdated.php | 42 + .../Activity/RecordedPageviewDeleted.php | 46 + .../Activity/RecordedSessionDeleted.php | 46 + .../Activity/SessionRecordingAdded.php | 41 + .../Activity/SessionRecordingDeleted.php | 46 + .../Activity/SessionRecordingEnded.php | 44 + .../Activity/SessionRecordingPaused.php | 46 + .../Activity/SessionRecordingResumed.php | 46 + .../Activity/SessionRecordingUpdated.php | 42 + .../Archiver/Aggregator.php | 476 ++ .../CHANGELOG.md | 452 ++ .../Categories/HeatmapCategory.php | 26 + .../Categories/ManageHeatmapSubcategory.php | 32 + .../ManageSessionRecordingSubcategory.php | 32 + .../Categories/SessionRecordingsCategory.php | 26 + .../Columns/Metrics/BaseMetric.php | 27 + .../Columns/Metrics/Browser.php | 67 + .../Columns/Metrics/Device.php | 78 + .../Columns/Metrics/Location.php | 86 + .../Columns/Metrics/OperatingSystem.php | 67 + .../Columns/Metrics/SessionTime.php | 97 + .../Columns/Metrics/TimeOnPage.php | 65 + .../Columns/Metrics/TimeOnSite.php | 37 + .../Columns/Metrics/TotalEvents.php | 48 + .../Commands/RemoveHeatmapScreenshot.php | 101 + .../Configuration.php | 145 + .../Controller.php | 463 ++ .../Dao/LogHsr.php | 375 ++ .../Dao/LogHsrBlob.php | 180 + .../Dao/LogHsrEvent.php | 166 + .../Dao/LogHsrSite.php | 141 + .../Dao/SiteHsrDao.php | 422 ++ .../Filter/EnrichRecordedSessions.php | 73 + .../Diagnostic/ConfigsPhpCheck.php | 112 + .../HeatmapSessionRecording.php | 886 +++ .../Input/Breakpoint.php | 66 + .../Input/CaptureKeystrokes.php | 40 + .../Input/ExcludedElements.php | 50 + .../Input/MinSessionTime.php | 57 + .../Input/Name.php | 51 + .../Input/PageRule.php | 80 + .../Input/PageRules.php | 61 + .../Input/RequiresActivity.php | 40 + .../Input/SampleLimit.php | 57 + .../Input/SampleRate.php | 62 + .../Input/ScreenshotUrl.php | 58 + .../Input/Validator.php | 176 + .../Install/HtAccess.php | 66 + .../Install/htaccessTemplate | 23 + .../LEGALNOTICE | 46 + .../LICENSE | 49 + .../Menu.php | 50 + .../Model/SiteHsrModel.php | 489 ++ .../MutationManipulator.php | 226 + .../README.md | 120 + .../Reports/GetRecordedSessions.php | 344 + .../Settings/TrackingDisableDefault.php | 36 + .../SystemSettings.php | 296 + .../Tasks.php | 77 + .../Tracker/Configs.php | 160 + .../Tracker/HsrMatcher.php | 224 + .../Tracker/LogTable/LogHsr.php | 42 + .../Tracker/LogTable/LogHsrBlob.php | 37 + .../Tracker/LogTable/LogHsrEvent.php | 37 + .../Tracker/PageRuleMatcher.php | 305 + .../Tracker/RequestProcessor.php | 312 + .../Updates/3.0.10.php | 51 + .../Updates/3.0.11.php | 51 + .../Updates/3.0.3.php | 30 + .../Updates/4.0.0.php | 55 + .../Updates/5.1.0.php | 54 + .../VisitorDetails.php | 125 + .../Widgets/GetManageHeatmaps.php | 77 + .../Widgets/GetManageSessionRecordings.php | 66 + .../Widgets/GettingStartedHeatmap.php | 75 + .../Widgets/GettingStartedSessions.php | 75 + .../config/config.php | 21 + .../configs.php | 161 + .../docs/index.md | 3 + .../javascripts/recording.js | 636 ++ .../javascripts/rowaction.js | 211 + .../lang/bg.json | 7 + .../lang/cs.json | 190 + .../lang/da.json | 1 + .../lang/de.json | 80 + .../lang/en.json | 237 + .../lang/es.json | 183 + .../lang/fi.json | 1 + .../lang/fr.json | 237 + .../lang/hi.json | 1 + .../lang/it.json | 190 + .../lang/ja.json | 1 + .../lang/nb.json | 35 + .../lang/nl.json | 185 + .../lang/pl.json | 185 + .../lang/pt.json | 185 + .../lang/ro.json | 6 + .../lang/ru.json | 1 + .../lang/sq.json | 237 + .../lang/sv.json | 80 + .../lang/tr.json | 237 + .../lang/uk.json | 6 + .../lang/zh-cn.json | 1 + .../lang/zh-tw.json | 185 + .../MutationObserver.js/MutationObserver.js | 624 ++ .../libs/MutationObserver.js/README.md | 68 + .../libs/MutationObserver.js/dist/README.md | 7 + .../dist/mutationobserver.min.js | 10 + .../libs/MutationObserver.js/license | 11 + .../libs/MutationObserver.js/package.json | 49 + .../libs/mutation-summary/COPYING | 202 + .../libs/mutation-summary/README.md | 59 + .../libs/mutation-summary/package.json | 30 + .../mutation-summary/src/mutation-summary.js | 1406 +++++ .../mutation-summary/src/mutation-summary.ts | 1750 ++++++ .../libs/mutation-summary/util/tree-mirror.js | 268 + .../libs/mutation-summary/util/tree-mirror.ts | 375 ++ .../libs/svg.js/CHANGELOG.md | 642 ++ .../libs/svg.js/LICENSE.txt | 21 + .../libs/svg.js/README.md | 29 + .../libs/svg.js/dist/svg.js | 5518 +++++++++++++++++ .../libs/svg.js/dist/svg.min.js | 3 + .../libs/svg.js/package.json | 84 + .../package-lock.json | 71 + .../package.json | 27 + .../phpcs.xml | 37 + .../plugin.json | 41 + .../pull_request_template.md | 26 + .../stylesheets/edit-entities.less | 55 + .../stylesheets/list-entities.less | 35 + .../stylesheets/recordings.less | 154 + .../templates/_detectAdBlocker.twig | 26 + .../templates/embedPage.twig | 43 + .../templates/gettingStartedHeatmaps.twig | 2 + .../templates/gettingStartedSessions.twig | 2 + .../templates/manageHeatmap.twig | 22 + .../templates/manageSessions.twig | 20 + .../templates/replayRecording.twig | 60 + .../templates/showHeatmap.twig | 21 + .../tracker.min.js | 125 + .../tsconfig.json | 14 + .../vue/dist/HeatmapSessionRecording.umd.js | 5480 ++++++++++++++++ .../dist/HeatmapSessionRecording.umd.min.js | 73 + .../vue/dist/umd.metadata.json | 6 + .../vue/src/HeatmapVis/HeatmapVis.less | 99 + .../vue/src/HeatmapVis/HeatmapVis.vue | 1102 ++++ .../vue/src/HeatmapVis/HeatmapVisPage.vue | 184 + .../vue/src/HsrStore/HsrStore.store.ts | 213 + .../vue/src/HsrTargetTest/HsrTargetTest.less | 20 + .../vue/src/HsrTargetTest/HsrTargetTest.vue | 181 + .../AvailableTargetPageRules.store.ts | 57 + .../vue/src/HsrUrlTarget/HsrUrlTarget.less | 17 + .../vue/src/HsrUrlTarget/HsrUrlTarget.vue | 199 + .../src/ListOfPageviews/ListOfPageviews.vue | 85 + .../vue/src/ManageHeatmap/Edit.vue | 522 ++ .../vue/src/ManageHeatmap/List.vue | 283 + .../vue/src/ManageHeatmap/Manage.vue | 121 + .../vue/src/ManageSessionRecording/Edit.vue | 488 ++ .../vue/src/ManageSessionRecording/List.vue | 286 + .../vue/src/ManageSessionRecording/Manage.vue | 105 + .../MatomoJsNotWritableAlert.vue | 47 + .../SessionRecordingVis.less | 131 + .../SessionRecordingVis.vue | 1222 ++++ .../vue/src/Tooltip/Tooltip.less | 59 + .../vue/src/Tooltip/Tooltip.vue | 112 + .../vue/src/getIframeWindow.ts | 27 + .../vue/src/index.ts | 29 + .../vue/src/oneAtATime.ts | 43 + .../vue/src/types.ts | 131 + .../API.php | 410 ++ .../Activity/AccountAdded.php | 46 + .../Activity/AccountRemoved.php | 46 + .../Activity/GoogleClientConfigChanged.php | 46 + .../Activity/YandexClientConfigChanged.php | 46 + .../Archiver.php | 35 + .../CHANGELOG.md | 276 + .../CrawlingOverviewSubcategory.php | 31 + .../Categories/SearchKeywordsSubcategory.php | 26 + .../Client/Bing.php | 310 + .../Configuration/BaseConfiguration.php | 49 + .../Client/Configuration/Bing.php | 77 + .../Client/Configuration/Google.php | 178 + .../Client/Configuration/Yandex.php | 130 + .../Client/Google.php | 489 ++ .../Client/Yandex.php | 545 ++ .../Columns/Keyword.php | 28 + .../Commands/ImportBing.php | 51 + .../Commands/ImportGoogle.php | 55 + .../Commands/ImportYandex.php | 55 + .../Controller.php | 1125 ++++ .../Diagnostic/BingAccountDiagnostic.php | 74 + .../Diagnostic/GoogleAccountDiagnostic.php | 71 + .../Diagnostic/YandexAccountDiagnostic.php | 76 + .../InvalidClientConfigException.php | 21 + .../InvalidCredentialsException.php | 21 + .../MissingClientConfigException.php | 21 + .../MissingOAuthConfigException.php | 21 + .../Exceptions/RateLimitApiException.php | 21 + .../Exceptions/UnknownAPIException.php | 21 + .../Importer/Bing.php | 288 + .../Importer/Google.php | 361 ++ .../Importer/Yandex.php | 292 + .../LICENSE | 49 + .../MeasurableSettings.php | 182 + .../Menu.php | 30 + .../Metrics.php | 144 + .../Model/Bing.php | 168 + .../Model/Google.php | 131 + .../Model/Yandex.php | 147 + .../Monolog/Handler/SEKPSystemLogHandler.php | 33 + .../Provider/Bing.php | 155 + .../Provider/Google.php | 147 + .../Provider/Helper/MeasurableHelper.php | 53 + .../Provider/ProviderAbstract.php | 151 + .../Provider/Yandex.php | 147 + .../README.md | 87 + .../RecordBuilders/Base.php | 32 + .../RecordBuilders/Bing.php | 204 + .../RecordBuilders/Google.php | 155 + .../RecordBuilders/Yandex.php | 196 + .../Reports/Base.php | 205 + .../Reports/GetCrawlingErrorExamplesBing.php | 100 + .../Reports/GetCrawlingOverviewBing.php | 132 + .../Reports/GetCrawlingOverviewYandex.php | 107 + .../Reports/GetKeywords.php | 53 + .../Reports/GetKeywordsBing.php | 90 + .../Reports/GetKeywordsGoogleImage.php | 44 + .../Reports/GetKeywordsGoogleNews.php | 44 + .../Reports/GetKeywordsGoogleVideo.php | 44 + .../Reports/GetKeywordsGoogleWeb.php | 43 + .../Reports/GetKeywordsImported.php | 58 + .../Reports/GetKeywordsReferrers.php | 46 + .../Reports/GetKeywordsYandex.php | 62 + .../SearchEngineKeywordsPerformance.php | 962 +++ .../SystemSettings.php | 41 + .../Tasks.php | 101 + .../Updates/3.5.0.php | 45 + .../Updates/4.1.0.php | 30 + .../Updates/4.2.0.php | 29 + .../config/config.php | 29 + .../docs/index.md | 3 + .../images/Bing.png | Bin 0 -> 14323 bytes .../images/Google.png | Bin 0 -> 18767 bytes .../images/Yahoo.png | Bin 0 -> 7776 bytes .../images/Yandex.png | Bin 0 -> 18422 bytes .../lang/bg.json | 6 + .../lang/cs.json | 1 + .../lang/da.json | 1 + .../lang/de.json | 224 + .../lang/en.json | 240 + .../lang/es.json | 170 + .../lang/fi.json | 1 + .../lang/fr.json | 241 + .../lang/hi.json | 1 + .../lang/it.json | 225 + .../lang/ja.json | 1 + .../lang/nb.json | 1 + .../lang/nl.json | 183 + .../lang/pl.json | 178 + .../lang/pt.json | 178 + .../lang/ro.json | 1 + .../lang/ru.json | 81 + .../lang/sq.json | 240 + .../lang/sv.json | 37 + .../lang/tr.json | 241 + .../lang/uk.json | 1 + .../lang/zh-cn.json | 1 + .../lang/zh-tw.json | 178 + .../phpcs.xml | 41 + .../plugin.json | 31 + .../pull_request_template.md | 26 + .../scoper.inc.php | 141 + .../stylesheets/styles.less | 262 + .../templates/bing/configuration.twig | 24 + .../templates/google/configuration.twig | 32 + .../templates/index.twig | 12 + .../messageReferrerKeywordsReport.twig | 10 + .../templates/yandex/configuration.twig | 31 + .../vendor/autoload.php | 10 + .../vendor/autoload_original.php | 12 + .../vendor/composer/ClassLoader.php | 572 ++ .../vendor/composer/InstalledVersions.php | 352 ++ .../vendor/composer/LICENSE | 21 + .../vendor/composer/autoload_classmap.php | 10 + .../vendor/composer/autoload_files.php | 16 + .../vendor/composer/autoload_namespaces.php | 9 + .../vendor/composer/autoload_psr4.php | 21 + .../vendor/composer/autoload_real.php | 55 + .../vendor/composer/autoload_static.php | 105 + .../vendor/composer/installed.json | 1156 ++++ .../vendor/composer/installed.php | 197 + .../vendor/prefixed/firebase/php-jwt/LICENSE | 30 + .../php-jwt/src/BeforeValidException.php | 19 + .../firebase/php-jwt/src/CachedKeySet.php | 226 + .../firebase/php-jwt/src/ExpiredException.php | 19 + .../prefixed/firebase/php-jwt/src/JWK.php | 267 + .../prefixed/firebase/php-jwt/src/JWT.php | 572 ++ .../src/JWTExceptionWithPayloadInterface.php | 20 + .../prefixed/firebase/php-jwt/src/Key.php | 50 + .../php-jwt/src/SignatureInvalidException.php | 7 + .../google/apiclient-services/LICENSE | 203 + .../google/apiclient-services/autoload.php | 28 + .../google/apiclient-services/renovate.json | 7 + .../google/apiclient-services/src/Oauth2.php | 82 + .../src/Oauth2/Resource/Userinfo.php | 45 + .../src/Oauth2/Resource/UserinfoV2.php | 32 + .../src/Oauth2/Resource/UserinfoV2Me.php | 45 + .../src/Oauth2/Tokeninfo.php | 88 + .../src/Oauth2/Userinfo.php | 124 + .../apiclient-services/src/SearchConsole.php | 67 + .../src/SearchConsole/ApiDataRow.php | 70 + .../src/SearchConsole/ApiDimensionFilter.php | 51 + .../SearchConsole/ApiDimensionFilterGroup.php | 50 + .../src/SearchConsole/BlockedResource.php | 33 + .../src/SearchConsole/Image.php | 42 + .../src/SearchConsole/MobileFriendlyIssue.php | 33 + .../Resource/Searchanalytics.php | 54 + .../src/SearchConsole/Resource/Sitemaps.php | 99 + .../src/SearchConsole/Resource/Sites.php | 86 + .../Resource/UrlTestingTools.php | 32 + .../UrlTestingToolsMobileFriendlyTest.php | 47 + .../src/SearchConsole/ResourceIssue.php | 40 + .../RunMobileFriendlyTestRequest.php | 42 + .../RunMobileFriendlyTestResponse.php | 98 + .../SearchAnalyticsQueryRequest.php | 122 + .../SearchAnalyticsQueryResponse.php | 50 + .../SearchConsole/SitemapsListResponse.php | 41 + .../src/SearchConsole/SitesListResponse.php | 41 + .../src/SearchConsole/TestStatus.php | 42 + .../src/SearchConsole/WmxSite.php | 42 + .../src/SearchConsole/WmxSitemap.php | 113 + .../src/SearchConsole/WmxSitemapContent.php | 51 + .../google/apiclient-services/synth.metadata | 18 + .../google/apiclient-services/synth.py | 119 + .../vendor/prefixed/google/apiclient/LICENSE | 203 + .../apiclient/src/AccessToken/Revoke.php | 65 + .../apiclient/src/AccessToken/Verify.php | 217 + .../src/AuthHandler/AuthHandlerFactory.php | 47 + .../src/AuthHandler/Guzzle6AuthHandler.php | 76 + .../src/AuthHandler/Guzzle7AuthHandler.php | 25 + .../prefixed/google/apiclient/src/Client.php | 1032 +++ .../google/apiclient/src/Collection.php | 104 + .../google/apiclient/src/Exception.php | 23 + .../google/apiclient/src/Http/Batch.php | 190 + .../apiclient/src/Http/MediaFileUpload.php | 273 + .../google/apiclient/src/Http/REST.php | 153 + .../prefixed/google/apiclient/src/Model.php | 301 + .../prefixed/google/apiclient/src/Service.php | 63 + .../apiclient/src/Service/Exception.php | 65 + .../google/apiclient/src/Service/Resource.php | 214 + .../google/apiclient/src/Task/Composer.php | 77 + .../google/apiclient/src/Task/Exception.php | 23 + .../google/apiclient/src/Task/Retryable.php | 26 + .../google/apiclient/src/Task/Runner.php | 235 + .../apiclient/src/Utils/UriTemplate.php | 264 + .../prefixed/google/apiclient/src/aliases.php | 80 + .../vendor/prefixed/google/auth/COPYING | 202 + .../vendor/prefixed/google/auth/LICENSE | 203 + .../vendor/prefixed/google/auth/VERSION | 1 + .../vendor/prefixed/google/auth/autoload.php | 35 + .../prefixed/google/auth/src/AccessToken.php | 430 ++ .../src/ApplicationDefaultCredentials.php | 301 + .../src/Cache/InvalidArgumentException.php | 23 + .../prefixed/google/auth/src/Cache/Item.php | 146 + .../auth/src/Cache/MemoryCacheItemPool.php | 161 + .../auth/src/Cache/SysVCacheItemPool.php | 207 + .../google/auth/src/Cache/TypedItem.php | 152 + .../prefixed/google/auth/src/CacheTrait.php | 96 + .../src/CredentialSource/AwsNativeSource.php | 287 + .../auth/src/CredentialSource/FileSource.php | 69 + .../auth/src/CredentialSource/UrlSource.php | 83 + .../Credentials/AppIdentityCredentials.php | 209 + .../ExternalAccountCredentials.php | 177 + .../auth/src/Credentials/GCECredentials.php | 493 ++ .../auth/src/Credentials/IAMCredentials.php | 77 + .../ImpersonatedServiceAccountCredentials.php | 120 + .../src/Credentials/InsecureCredentials.php | 62 + .../Credentials/ServiceAccountCredentials.php | 312 + .../ServiceAccountJwtAccessCredentials.php | 171 + .../Credentials/UserRefreshCredentials.php | 130 + .../google/auth/src/CredentialsLoader.php | 242 + ...ternalAccountCredentialSourceInterface.php | 23 + .../google/auth/src/FetchAuthTokenCache.php | 238 + .../auth/src/FetchAuthTokenInterface.php | 52 + .../prefixed/google/auth/src/GCECache.php | 70 + .../auth/src/GetQuotaProjectInterface.php | 32 + .../auth/src/GetUniverseDomainInterface.php | 34 + .../src/HttpHandler/Guzzle6HttpHandler.php | 59 + .../src/HttpHandler/Guzzle7HttpHandler.php | 22 + .../auth/src/HttpHandler/HttpClientCache.php | 51 + .../src/HttpHandler/HttpHandlerFactory.php | 62 + .../vendor/prefixed/google/auth/src/Iam.php | 79 + .../google/auth/src/IamSignerTrait.php | 58 + .../src/Middleware/AuthTokenMiddleware.php | 138 + .../Middleware/ProxyAuthTokenMiddleware.php | 130 + .../ScopedAccessTokenMiddleware.php | 140 + .../auth/src/Middleware/SimpleMiddleware.php | 86 + .../prefixed/google/auth/src/OAuth2.php | 1515 +++++ .../auth/src/ProjectIdProviderInterface.php | 32 + .../auth/src/ServiceAccountSignerTrait.php | 53 + .../google/auth/src/SignBlobInterface.php | 43 + .../auth/src/UpdateMetadataInterface.php | 36 + .../google/auth/src/UpdateMetadataTrait.php | 62 + .../vendor/prefixed/guzzlehttp/guzzle/LICENSE | 27 + .../guzzlehttp/guzzle/src/BodySummarizer.php | 23 + .../guzzle/src/BodySummarizerInterface.php | 12 + .../prefixed/guzzlehttp/guzzle/src/Client.php | 402 ++ .../guzzlehttp/guzzle/src/ClientInterface.php | 78 + .../guzzlehttp/guzzle/src/ClientTrait.php | 227 + .../guzzle/src/Cookie/CookieJar.php | 240 + .../guzzle/src/Cookie/CookieJarInterface.php | 74 + .../guzzle/src/Cookie/FileCookieJar.php | 92 + .../guzzle/src/Cookie/SessionCookieJar.php | 71 + .../guzzle/src/Cookie/SetCookie.php | 407 ++ .../src/Exception/BadResponseException.php | 31 + .../guzzle/src/Exception/ClientException.php | 10 + .../guzzle/src/Exception/ConnectException.php | 47 + .../guzzle/src/Exception/GuzzleException.php | 8 + .../Exception/InvalidArgumentException.php | 7 + .../guzzle/src/Exception/RequestException.php | 124 + .../guzzle/src/Exception/ServerException.php | 10 + .../Exception/TooManyRedirectsException.php | 7 + .../src/Exception/TransferException.php | 7 + .../guzzle/src/Handler/CurlFactory.php | 496 ++ .../src/Handler/CurlFactoryInterface.php | 23 + .../guzzle/src/Handler/CurlHandler.php | 43 + .../guzzle/src/Handler/CurlMultiHandler.php | 220 + .../guzzle/src/Handler/EasyHandle.php | 91 + .../guzzle/src/Handler/HeaderProcessor.php | 36 + .../guzzle/src/Handler/MockHandler.php | 174 + .../guzzlehttp/guzzle/src/Handler/Proxy.php | 49 + .../guzzle/src/Handler/StreamHandler.php | 455 ++ .../guzzlehttp/guzzle/src/HandlerStack.php | 238 + .../guzzle/src/MessageFormatter.php | 168 + .../guzzle/src/MessageFormatterInterface.php | 17 + .../guzzlehttp/guzzle/src/Middleware.php | 227 + .../prefixed/guzzlehttp/guzzle/src/Pool.php | 116 + .../guzzle/src/PrepareBodyMiddleware.php | 86 + .../guzzle/src/RedirectMiddleware.php | 162 + .../guzzlehttp/guzzle/src/RequestOptions.php | 244 + .../guzzlehttp/guzzle/src/RetryMiddleware.php | 91 + .../guzzlehttp/guzzle/src/TransferStats.php | 114 + .../prefixed/guzzlehttp/guzzle/src/Utils.php | 339 + .../guzzlehttp/guzzle/src/functions.php | 158 + .../guzzle/src/functions_include.php | 8 + .../prefixed/guzzlehttp/promises/LICENSE | 24 + .../promises/src/AggregateException.php | 14 + .../promises/src/CancellationException.php | 10 + .../guzzlehttp/promises/src/Coroutine.php | 151 + .../guzzlehttp/promises/src/Create.php | 75 + .../prefixed/guzzlehttp/promises/src/Each.php | 66 + .../guzzlehttp/promises/src/EachPromise.php | 200 + .../promises/src/FulfilledPromise.php | 69 + .../prefixed/guzzlehttp/promises/src/Is.php | 43 + .../guzzlehttp/promises/src/Promise.php | 237 + .../promises/src/PromiseInterface.php | 87 + .../promises/src/PromisorInterface.php | 16 + .../promises/src/RejectedPromise.php | 75 + .../promises/src/RejectionException.php | 40 + .../guzzlehttp/promises/src/TaskQueue.php | 62 + .../promises/src/TaskQueueInterface.php | 22 + .../guzzlehttp/promises/src/Utils.php | 239 + .../guzzlehttp/promises/src/functions.php | 334 + .../promises/src/functions_include.php | 8 + .../vendor/prefixed/guzzlehttp/psr7/LICENSE | 26 + .../guzzlehttp/psr7/src/AppendStream.php | 205 + .../guzzlehttp/psr7/src/BufferStream.php | 123 + .../guzzlehttp/psr7/src/CachingStream.php | 125 + .../guzzlehttp/psr7/src/DroppingStream.php | 40 + .../src/Exception/MalformedUriException.php | 12 + .../prefixed/guzzlehttp/psr7/src/FnStream.php | 150 + .../prefixed/guzzlehttp/psr7/src/Header.php | 117 + .../guzzlehttp/psr7/src/HttpFactory.php | 76 + .../guzzlehttp/psr7/src/InflateStream.php | 33 + .../guzzlehttp/psr7/src/LazyOpenStream.php | 41 + .../guzzlehttp/psr7/src/LimitStream.php | 128 + .../prefixed/guzzlehttp/psr7/src/Message.php | 189 + .../guzzlehttp/psr7/src/MessageTrait.php | 212 + .../prefixed/guzzlehttp/psr7/src/MimeType.php | 27 + .../guzzlehttp/psr7/src/MultipartStream.php | 124 + .../guzzlehttp/psr7/src/NoSeekStream.php | 23 + .../guzzlehttp/psr7/src/PumpStream.php | 151 + .../prefixed/guzzlehttp/psr7/src/Query.php | 104 + .../prefixed/guzzlehttp/psr7/src/Request.php | 124 + .../prefixed/guzzlehttp/psr7/src/Response.php | 78 + .../prefixed/guzzlehttp/psr7/src/Rfc7230.php | 22 + .../guzzlehttp/psr7/src/ServerRequest.php | 270 + .../prefixed/guzzlehttp/psr7/src/Stream.php | 237 + .../psr7/src/StreamDecoratorTrait.php | 133 + .../guzzlehttp/psr7/src/StreamWrapper.php | 114 + .../guzzlehttp/psr7/src/UploadedFile.php | 152 + .../prefixed/guzzlehttp/psr7/src/Uri.php | 570 ++ .../guzzlehttp/psr7/src/UriComparator.php | 43 + .../guzzlehttp/psr7/src/UriNormalizer.php | 175 + .../guzzlehttp/psr7/src/UriResolver.php | 180 + .../prefixed/guzzlehttp/psr7/src/Utils.php | 375 ++ .../constant_time_encoding/LICENSE.txt | 48 + .../constant_time_encoding/src/Base32.php | 404 ++ .../constant_time_encoding/src/Base32Hex.php | 98 + .../constant_time_encoding/src/Base64.php | 257 + .../src/Base64DotSlash.php | 78 + .../src/Base64DotSlashOrdered.php | 74 + .../src/Base64UrlSafe.php | 82 + .../constant_time_encoding/src/Binary.php | 85 + .../src/EncoderInterface.php | 51 + .../constant_time_encoding/src/Encoding.php | 244 + .../constant_time_encoding/src/Hex.php | 124 + .../constant_time_encoding/src/RFC4648.php | 176 + .../prefixed/paragonie/random_compat/LICENSE | 22 + .../paragonie/random_compat/build-phar.sh | 5 + .../dist/random_compat.phar.pubkey | 5 + .../dist/random_compat.phar.pubkey.asc | 11 + .../paragonie/random_compat/lib/random.php | 34 + .../random_compat/other/build_phar.php | 44 + .../random_compat/psalm-autoload.php | 10 + .../paragonie/random_compat/psalm.xml | 19 + .../prefixed/phpseclib/phpseclib/AUTHORS | 7 + .../prefixed/phpseclib/phpseclib/LICENSE | 20 + .../phpseclib/Common/Functions/Strings.php | 454 ++ .../phpseclib/phpseclib/Crypt/AES.php | 112 + .../phpseclib/phpseclib/Crypt/Blowfish.php | 660 ++ .../phpseclib/phpseclib/Crypt/ChaCha20.php | 999 +++ .../phpseclib/Crypt/Common/AsymmetricKey.php | 511 ++ .../phpseclib/Crypt/Common/BlockCipher.php | 23 + .../Crypt/Common/Formats/Keys/JWK.php | 62 + .../Crypt/Common/Formats/Keys/OpenSSH.php | 195 + .../Crypt/Common/Formats/Keys/PKCS.php | 67 + .../Crypt/Common/Formats/Keys/PKCS1.php | 187 + .../Crypt/Common/Formats/Keys/PKCS8.php | 599 ++ .../Crypt/Common/Formats/Keys/PuTTY.php | 324 + .../Crypt/Common/Formats/Signature/Raw.php | 53 + .../phpseclib/Crypt/Common/PrivateKey.php | 29 + .../phpseclib/Crypt/Common/PublicKey.php | 24 + .../phpseclib/Crypt/Common/StreamCipher.php | 51 + .../phpseclib/Crypt/Common/SymmetricKey.php | 3096 +++++++++ .../Crypt/Common/Traits/Fingerprint.php | 55 + .../Crypt/Common/Traits/PasswordProtected.php | 44 + .../phpseclib/phpseclib/Crypt/DES.php | 522 ++ .../phpseclib/phpseclib/Crypt/DH.php | 295 + .../phpseclib/Crypt/DH/Formats/Keys/PKCS1.php | 65 + .../phpseclib/Crypt/DH/Formats/Keys/PKCS8.php | 115 + .../phpseclib/Crypt/DH/Parameters.php | 33 + .../phpseclib/Crypt/DH/PrivateKey.php | 64 + .../phpseclib/Crypt/DH/PublicKey.php | 44 + .../phpseclib/phpseclib/Crypt/DSA.php | 292 + .../Crypt/DSA/Formats/Keys/OpenSSH.php | 102 + .../Crypt/DSA/Formats/Keys/PKCS1.php | 115 + .../Crypt/DSA/Formats/Keys/PKCS8.php | 125 + .../Crypt/DSA/Formats/Keys/PuTTY.php | 98 + .../phpseclib/Crypt/DSA/Formats/Keys/Raw.php | 78 + .../phpseclib/Crypt/DSA/Formats/Keys/XML.php | 123 + .../Crypt/DSA/Formats/Signature/ASN1.php | 57 + .../Crypt/DSA/Formats/Signature/Raw.php | 23 + .../Crypt/DSA/Formats/Signature/SSH2.php | 61 + .../phpseclib/Crypt/DSA/Parameters.php | 33 + .../phpseclib/Crypt/DSA/PrivateKey.php | 131 + .../phpseclib/Crypt/DSA/PublicKey.php | 74 + .../phpseclib/phpseclib/Crypt/EC.php | 414 ++ .../phpseclib/Crypt/EC/BaseCurves/Base.php | 192 + .../phpseclib/Crypt/EC/BaseCurves/Binary.php | 324 + .../Crypt/EC/BaseCurves/KoblitzPrime.php | 273 + .../Crypt/EC/BaseCurves/Montgomery.php | 246 + .../phpseclib/Crypt/EC/BaseCurves/Prime.php | 695 +++ .../Crypt/EC/BaseCurves/TwistedEdwards.php | 190 + .../phpseclib/Crypt/EC/Curves/Curve25519.php | 73 + .../phpseclib/Crypt/EC/Curves/Curve448.php | 76 + .../phpseclib/Crypt/EC/Curves/Ed25519.php | 295 + .../phpseclib/Crypt/EC/Curves/Ed448.php | 222 + .../Crypt/EC/Curves/brainpoolP160r1.php | 26 + .../Crypt/EC/Curves/brainpoolP160t1.php | 43 + .../Crypt/EC/Curves/brainpoolP192r1.php | 26 + .../Crypt/EC/Curves/brainpoolP192t1.php | 30 + .../Crypt/EC/Curves/brainpoolP224r1.php | 26 + .../Crypt/EC/Curves/brainpoolP224t1.php | 30 + .../Crypt/EC/Curves/brainpoolP256r1.php | 26 + .../Crypt/EC/Curves/brainpoolP256t1.php | 30 + .../Crypt/EC/Curves/brainpoolP320r1.php | 26 + .../Crypt/EC/Curves/brainpoolP320t1.php | 30 + .../Crypt/EC/Curves/brainpoolP384r1.php | 26 + .../Crypt/EC/Curves/brainpoolP384t1.php | 30 + .../Crypt/EC/Curves/brainpoolP512r1.php | 26 + .../Crypt/EC/Curves/brainpoolP512t1.php | 30 + .../phpseclib/Crypt/EC/Curves/nistb233.php | 17 + .../phpseclib/Crypt/EC/Curves/nistb409.php | 17 + .../phpseclib/Crypt/EC/Curves/nistk163.php | 17 + .../phpseclib/Crypt/EC/Curves/nistk233.php | 17 + .../phpseclib/Crypt/EC/Curves/nistk283.php | 17 + .../phpseclib/Crypt/EC/Curves/nistk409.php | 17 + .../phpseclib/Crypt/EC/Curves/nistp192.php | 17 + .../phpseclib/Crypt/EC/Curves/nistp224.php | 17 + .../phpseclib/Crypt/EC/Curves/nistp256.php | 17 + .../phpseclib/Crypt/EC/Curves/nistp384.php | 17 + .../phpseclib/Crypt/EC/Curves/nistp521.php | 17 + .../phpseclib/Crypt/EC/Curves/nistt571.php | 17 + .../phpseclib/Crypt/EC/Curves/prime192v1.php | 17 + .../phpseclib/Crypt/EC/Curves/prime192v2.php | 26 + .../phpseclib/Crypt/EC/Curves/prime192v3.php | 26 + .../phpseclib/Crypt/EC/Curves/prime239v1.php | 26 + .../phpseclib/Crypt/EC/Curves/prime239v2.php | 26 + .../phpseclib/Crypt/EC/Curves/prime239v3.php | 26 + .../phpseclib/Crypt/EC/Curves/prime256v1.php | 17 + .../phpseclib/Crypt/EC/Curves/secp112r1.php | 26 + .../phpseclib/Crypt/EC/Curves/secp112r2.php | 27 + .../phpseclib/Crypt/EC/Curves/secp128r1.php | 26 + .../phpseclib/Crypt/EC/Curves/secp128r2.php | 27 + .../phpseclib/Crypt/EC/Curves/secp160k1.php | 31 + .../phpseclib/Crypt/EC/Curves/secp160r1.php | 26 + .../phpseclib/Crypt/EC/Curves/secp160r2.php | 27 + .../phpseclib/Crypt/EC/Curves/secp192k1.php | 30 + .../phpseclib/Crypt/EC/Curves/secp192r1.php | 68 + .../phpseclib/Crypt/EC/Curves/secp224k1.php | 30 + .../phpseclib/Crypt/EC/Curves/secp224r1.php | 26 + .../phpseclib/Crypt/EC/Curves/secp256k1.php | 34 + .../phpseclib/Crypt/EC/Curves/secp256r1.php | 26 + .../phpseclib/Crypt/EC/Curves/secp384r1.php | 26 + .../phpseclib/Crypt/EC/Curves/secp521r1.php | 26 + .../phpseclib/Crypt/EC/Curves/sect113r1.php | 26 + .../phpseclib/Crypt/EC/Curves/sect113r2.php | 26 + .../phpseclib/Crypt/EC/Curves/sect131r1.php | 26 + .../phpseclib/Crypt/EC/Curves/sect131r2.php | 26 + .../phpseclib/Crypt/EC/Curves/sect163k1.php | 26 + .../phpseclib/Crypt/EC/Curves/sect163r1.php | 26 + .../phpseclib/Crypt/EC/Curves/sect163r2.php | 26 + .../phpseclib/Crypt/EC/Curves/sect193r1.php | 26 + .../phpseclib/Crypt/EC/Curves/sect193r2.php | 26 + .../phpseclib/Crypt/EC/Curves/sect233k1.php | 26 + .../phpseclib/Crypt/EC/Curves/sect233r1.php | 26 + .../phpseclib/Crypt/EC/Curves/sect239k1.php | 26 + .../phpseclib/Crypt/EC/Curves/sect283k1.php | 26 + .../phpseclib/Crypt/EC/Curves/sect283r1.php | 26 + .../phpseclib/Crypt/EC/Curves/sect409k1.php | 26 + .../phpseclib/Crypt/EC/Curves/sect409r1.php | 26 + .../phpseclib/Crypt/EC/Curves/sect571k1.php | 26 + .../phpseclib/Crypt/EC/Curves/sect571r1.php | 26 + .../Crypt/EC/Formats/Keys/Common.php | 489 ++ .../phpseclib/Crypt/EC/Formats/Keys/JWK.php | 155 + .../EC/Formats/Keys/MontgomeryPrivate.php | 93 + .../EC/Formats/Keys/MontgomeryPublic.php | 65 + .../Crypt/EC/Formats/Keys/OpenSSH.php | 163 + .../phpseclib/Crypt/EC/Formats/Keys/PKCS1.php | 154 + .../phpseclib/Crypt/EC/Formats/Keys/PKCS8.php | 186 + .../phpseclib/Crypt/EC/Formats/Keys/PuTTY.php | 115 + .../phpseclib/Crypt/EC/Formats/Keys/XML.php | 373 ++ .../Crypt/EC/Formats/Keys/libsodium.php | 106 + .../Crypt/EC/Formats/Signature/ASN1.php | 57 + .../Crypt/EC/Formats/Signature/IEEE.php | 60 + .../Crypt/EC/Formats/Signature/Raw.php | 23 + .../Crypt/EC/Formats/Signature/SSH2.php | 83 + .../phpseclib/Crypt/EC/Parameters.php | 33 + .../phpseclib/Crypt/EC/PrivateKey.php | 226 + .../phpseclib/Crypt/EC/PublicKey.php | 136 + .../phpseclib/phpseclib/Crypt/Hash.php | 1134 ++++ .../phpseclib/Crypt/PublicKeyLoader.php | 102 + .../phpseclib/phpseclib/Crypt/RC2.php | 478 ++ .../phpseclib/phpseclib/Crypt/RC4.php | 258 + .../phpseclib/phpseclib/Crypt/RSA.php | 824 +++ .../phpseclib/Crypt/RSA/Formats/Keys/JWK.php | 116 + .../Crypt/RSA/Formats/Keys/MSBLOB.php | 207 + .../Crypt/RSA/Formats/Keys/OpenSSH.php | 101 + .../Crypt/RSA/Formats/Keys/PKCS1.php | 120 + .../Crypt/RSA/Formats/Keys/PKCS8.php | 111 + .../phpseclib/Crypt/RSA/Formats/Keys/PSS.php | 193 + .../Crypt/RSA/Formats/Keys/PuTTY.php | 107 + .../phpseclib/Crypt/RSA/Formats/Keys/Raw.php | 153 + .../phpseclib/Crypt/RSA/Formats/Keys/XML.php | 140 + .../phpseclib/Crypt/RSA/PrivateKey.php | 441 ++ .../phpseclib/Crypt/RSA/PublicKey.php | 439 ++ .../phpseclib/phpseclib/Crypt/Random.php | 202 + .../phpseclib/phpseclib/Crypt/Rijndael.php | 1048 ++++ .../phpseclib/phpseclib/Crypt/Salsa20.php | 454 ++ .../phpseclib/phpseclib/Crypt/TripleDES.php | 384 ++ .../phpseclib/phpseclib/Crypt/Twofish.php | 506 ++ .../Exception/BadConfigurationException.php | 22 + .../Exception/BadDecryptionException.php | 22 + .../phpseclib/Exception/BadModeException.php | 22 + .../Exception/ConnectionClosedException.php | 22 + .../Exception/FileNotFoundException.php | 22 + .../Exception/InconsistentSetupException.php | 22 + .../Exception/InsufficientSetupException.php | 22 + .../Exception/NoKeyLoadedException.php | 22 + .../NoSupportedAlgorithmsException.php | 22 + .../Exception/UnableToConnectException.php | 22 + .../UnsupportedAlgorithmException.php | 22 + .../Exception/UnsupportedCurveException.php | 22 + .../Exception/UnsupportedFormatException.php | 22 + .../UnsupportedOperationException.php | 22 + .../phpseclib/phpseclib/File/ANSI.php | 553 ++ .../phpseclib/phpseclib/File/ASN1.php | 1398 +++++ .../phpseclib/phpseclib/File/ASN1/Element.php | 41 + .../File/ASN1/Maps/AccessDescription.php | 24 + .../ASN1/Maps/AdministrationDomainName.php | 31 + .../File/ASN1/Maps/AlgorithmIdentifier.php | 24 + .../phpseclib/File/ASN1/Maps/AnotherName.php | 24 + .../phpseclib/File/ASN1/Maps/Attribute.php | 24 + .../File/ASN1/Maps/AttributeType.php | 24 + .../File/ASN1/Maps/AttributeTypeAndValue.php | 24 + .../File/ASN1/Maps/AttributeValue.php | 24 + .../phpseclib/File/ASN1/Maps/Attributes.php | 24 + .../ASN1/Maps/AuthorityInfoAccessSyntax.php | 24 + .../File/ASN1/Maps/AuthorityKeyIdentifier.php | 24 + .../phpseclib/File/ASN1/Maps/BaseDistance.php | 24 + .../File/ASN1/Maps/BasicConstraints.php | 24 + .../Maps/BuiltInDomainDefinedAttribute.php | 24 + .../Maps/BuiltInDomainDefinedAttributes.php | 30 + .../ASN1/Maps/BuiltInStandardAttributes.php | 24 + .../phpseclib/File/ASN1/Maps/CPSuri.php | 24 + .../File/ASN1/Maps/CRLDistributionPoints.php | 24 + .../phpseclib/File/ASN1/Maps/CRLNumber.php | 24 + .../phpseclib/File/ASN1/Maps/CRLReason.php | 36 + .../phpseclib/File/ASN1/Maps/CertPolicyId.php | 24 + .../phpseclib/File/ASN1/Maps/Certificate.php | 24 + .../File/ASN1/Maps/CertificateIssuer.php | 23 + .../File/ASN1/Maps/CertificateList.php | 24 + .../File/ASN1/Maps/CertificatePolicies.php | 24 + .../ASN1/Maps/CertificateSerialNumber.php | 24 + .../File/ASN1/Maps/CertificationRequest.php | 24 + .../ASN1/Maps/CertificationRequestInfo.php | 24 + .../File/ASN1/Maps/Characteristic_two.php | 29 + .../phpseclib/File/ASN1/Maps/CountryName.php | 31 + .../phpseclib/File/ASN1/Maps/Curve.php | 24 + .../phpseclib/File/ASN1/Maps/DHParameter.php | 26 + .../phpseclib/File/ASN1/Maps/DSAParams.php | 24 + .../File/ASN1/Maps/DSAPrivateKey.php | 24 + .../phpseclib/File/ASN1/Maps/DSAPublicKey.php | 24 + .../phpseclib/File/ASN1/Maps/DigestInfo.php | 26 + .../File/ASN1/Maps/DirectoryString.php | 24 + .../phpseclib/File/ASN1/Maps/DisplayText.php | 24 + .../File/ASN1/Maps/DistributionPoint.php | 24 + .../File/ASN1/Maps/DistributionPointName.php | 24 + .../phpseclib/File/ASN1/Maps/DssSigValue.php | 24 + .../phpseclib/File/ASN1/Maps/ECParameters.php | 36 + .../phpseclib/File/ASN1/Maps/ECPoint.php | 24 + .../phpseclib/File/ASN1/Maps/ECPrivateKey.php | 26 + .../phpseclib/File/ASN1/Maps/EDIPartyName.php | 29 + .../File/ASN1/Maps/EcdsaSigValue.php | 24 + .../File/ASN1/Maps/EncryptedData.php | 24 + .../ASN1/Maps/EncryptedPrivateKeyInfo.php | 24 + .../File/ASN1/Maps/ExtKeyUsageSyntax.php | 24 + .../phpseclib/File/ASN1/Maps/Extension.php | 30 + .../File/ASN1/Maps/ExtensionAttribute.php | 24 + .../File/ASN1/Maps/ExtensionAttributes.php | 30 + .../phpseclib/File/ASN1/Maps/Extensions.php | 31 + .../phpseclib/File/ASN1/Maps/FieldElement.php | 24 + .../phpseclib/File/ASN1/Maps/FieldID.php | 24 + .../phpseclib/File/ASN1/Maps/GeneralName.php | 24 + .../phpseclib/File/ASN1/Maps/GeneralNames.php | 24 + .../File/ASN1/Maps/GeneralSubtree.php | 24 + .../File/ASN1/Maps/GeneralSubtrees.php | 24 + .../File/ASN1/Maps/HashAlgorithm.php | 23 + .../File/ASN1/Maps/HoldInstructionCode.php | 24 + .../File/ASN1/Maps/InvalidityDate.php | 24 + .../File/ASN1/Maps/IssuerAltName.php | 23 + .../ASN1/Maps/IssuingDistributionPoint.php | 24 + .../File/ASN1/Maps/KeyIdentifier.php | 24 + .../phpseclib/File/ASN1/Maps/KeyPurposeId.php | 24 + .../phpseclib/File/ASN1/Maps/KeyUsage.php | 24 + .../File/ASN1/Maps/MaskGenAlgorithm.php | 23 + .../phpseclib/File/ASN1/Maps/Name.php | 24 + .../File/ASN1/Maps/NameConstraints.php | 24 + .../File/ASN1/Maps/NetworkAddress.php | 24 + .../File/ASN1/Maps/NoticeReference.php | 24 + .../File/ASN1/Maps/NumericUserIdentifier.php | 24 + .../phpseclib/File/ASN1/Maps/ORAddress.php | 24 + .../File/ASN1/Maps/OneAsymmetricKey.php | 26 + .../File/ASN1/Maps/OrganizationName.php | 24 + .../ASN1/Maps/OrganizationalUnitNames.php | 30 + .../File/ASN1/Maps/OtherPrimeInfo.php | 31 + .../File/ASN1/Maps/OtherPrimeInfos.php | 25 + .../phpseclib/File/ASN1/Maps/PBEParameter.php | 26 + .../phpseclib/File/ASN1/Maps/PBES2params.php | 26 + .../phpseclib/File/ASN1/Maps/PBKDF2params.php | 33 + .../phpseclib/File/ASN1/Maps/PBMAC1params.php | 26 + .../phpseclib/File/ASN1/Maps/PKCS9String.php | 24 + .../phpseclib/File/ASN1/Maps/Pentanomial.php | 30 + .../phpseclib/File/ASN1/Maps/PersonalName.php | 24 + .../File/ASN1/Maps/PolicyInformation.php | 24 + .../File/ASN1/Maps/PolicyMappings.php | 24 + .../File/ASN1/Maps/PolicyQualifierId.php | 24 + .../File/ASN1/Maps/PolicyQualifierInfo.php | 24 + .../File/ASN1/Maps/PostalAddress.php | 24 + .../phpseclib/File/ASN1/Maps/Prime_p.php | 24 + .../File/ASN1/Maps/PrivateDomainName.php | 24 + .../phpseclib/File/ASN1/Maps/PrivateKey.php | 24 + .../File/ASN1/Maps/PrivateKeyInfo.php | 24 + .../File/ASN1/Maps/PrivateKeyUsagePeriod.php | 24 + .../phpseclib/File/ASN1/Maps/PublicKey.php | 24 + .../File/ASN1/Maps/PublicKeyAndChallenge.php | 24 + .../File/ASN1/Maps/PublicKeyInfo.php | 27 + .../File/ASN1/Maps/RC2CBCParameter.php | 26 + .../phpseclib/File/ASN1/Maps/RDNSequence.php | 36 + .../File/ASN1/Maps/RSAPrivateKey.php | 44 + .../phpseclib/File/ASN1/Maps/RSAPublicKey.php | 24 + .../File/ASN1/Maps/RSASSA_PSS_params.php | 26 + .../phpseclib/File/ASN1/Maps/ReasonFlags.php | 24 + .../ASN1/Maps/RelativeDistinguishedName.php | 30 + .../File/ASN1/Maps/RevokedCertificate.php | 24 + .../ASN1/Maps/SignedPublicKeyAndChallenge.php | 24 + .../File/ASN1/Maps/SpecifiedECDomain.php | 26 + .../File/ASN1/Maps/SubjectAltName.php | 23 + .../ASN1/Maps/SubjectDirectoryAttributes.php | 24 + .../ASN1/Maps/SubjectInfoAccessSyntax.php | 24 + .../File/ASN1/Maps/SubjectPublicKeyInfo.php | 24 + .../phpseclib/File/ASN1/Maps/TBSCertList.php | 24 + .../File/ASN1/Maps/TBSCertificate.php | 41 + .../File/ASN1/Maps/TerminalIdentifier.php | 24 + .../phpseclib/File/ASN1/Maps/Time.php | 24 + .../phpseclib/File/ASN1/Maps/Trinomial.php | 24 + .../File/ASN1/Maps/UniqueIdentifier.php | 24 + .../phpseclib/File/ASN1/Maps/UserNotice.php | 24 + .../phpseclib/File/ASN1/Maps/Validity.php | 24 + .../File/ASN1/Maps/netscape_ca_policy_url.php | 24 + .../File/ASN1/Maps/netscape_cert_type.php | 26 + .../File/ASN1/Maps/netscape_comment.php | 24 + .../phpseclib/phpseclib/File/X509.php | 3505 +++++++++++ .../phpseclib/phpseclib/Math/BigInteger.php | 802 +++ .../Math/BigInteger/Engines/BCMath.php | 601 ++ .../Math/BigInteger/Engines/BCMath/Base.php | 102 + .../BigInteger/Engines/BCMath/BuiltIn.php | 37 + .../Engines/BCMath/DefaultEngine.php | 23 + .../BigInteger/Engines/BCMath/OpenSSL.php | 23 + .../Engines/BCMath/Reductions/Barrett.php | 157 + .../Engines/BCMath/Reductions/EvalBarrett.php | 96 + .../Math/BigInteger/Engines/Engine.php | 1160 ++++ .../phpseclib/Math/BigInteger/Engines/GMP.php | 612 ++ .../BigInteger/Engines/GMP/DefaultEngine.php | 37 + .../Math/BigInteger/Engines/OpenSSL.php | 58 + .../phpseclib/Math/BigInteger/Engines/PHP.php | 1110 ++++ .../Math/BigInteger/Engines/PHP/Base.php | 133 + .../BigInteger/Engines/PHP/DefaultEngine.php | 23 + .../BigInteger/Engines/PHP/Montgomery.php | 78 + .../Math/BigInteger/Engines/PHP/OpenSSL.php | 23 + .../Engines/PHP/Reductions/Barrett.php | 239 + .../Engines/PHP/Reductions/Classic.php | 40 + .../Engines/PHP/Reductions/EvalBarrett.php | 412 ++ .../Engines/PHP/Reductions/Montgomery.php | 113 + .../Engines/PHP/Reductions/MontgomeryMult.php | 68 + .../Engines/PHP/Reductions/PowerOfTwo.php | 54 + .../Math/BigInteger/Engines/PHP32.php | 341 + .../Math/BigInteger/Engines/PHP64.php | 342 + .../phpseclib/phpseclib/Math/BinaryField.php | 183 + .../phpseclib/Math/BinaryField/Integer.php | 442 ++ .../phpseclib/Math/Common/FiniteField.php | 21 + .../Math/Common/FiniteField/Integer.php | 42 + .../phpseclib/phpseclib/Math/PrimeField.php | 106 + .../phpseclib/Math/PrimeField/Integer.php | 370 ++ .../phpseclib/phpseclib/Net/SFTP.php | 3058 +++++++++ .../phpseclib/phpseclib/Net/SFTP/Stream.php | 697 +++ .../phpseclib/phpseclib/Net/SSH2.php | 4594 ++++++++++++++ .../phpseclib/phpseclib/System/SSH/Agent.php | 253 + .../phpseclib/System/SSH/Agent/Identity.php | 280 + .../System/SSH/Common/Traits/ReadBytes.php | 36 + .../phpseclib/phpseclib/bootstrap.php | 20 + .../phpseclib/phpseclib/phpseclib/openssl.cnf | 6 + .../vendor/prefixed/psr/cache/LICENSE.txt | 19 + .../prefixed/psr/cache/src/CacheException.php | 10 + .../psr/cache/src/CacheItemInterface.php | 100 + .../psr/cache/src/CacheItemPoolInterface.php | 129 + .../cache/src/InvalidArgumentException.php | 13 + .../vendor/prefixed/psr/http-client/LICENSE | 19 + .../src/ClientExceptionInterface.php | 10 + .../psr/http-client/src/ClientInterface.php | 19 + .../src/NetworkExceptionInterface.php | 23 + .../src/RequestExceptionInterface.php | 23 + .../vendor/prefixed/psr/http-factory/LICENSE | 21 + .../src/RequestFactoryInterface.php | 18 + .../src/ResponseFactoryInterface.php | 18 + .../src/ServerRequestFactoryInterface.php | 24 + .../src/StreamFactoryInterface.php | 43 + .../src/UploadedFileFactoryInterface.php | 28 + .../http-factory/src/UriFactoryInterface.php | 17 + .../vendor/prefixed/psr/http-message/LICENSE | 19 + .../psr/http-message/src/MessageInterface.php | 177 + .../psr/http-message/src/RequestInterface.php | 124 + .../http-message/src/ResponseInterface.php | 66 + .../src/ServerRequestInterface.php | 249 + .../psr/http-message/src/StreamInterface.php | 144 + .../src/UploadedFileInterface.php | 118 + .../psr/http-message/src/UriInterface.php | 309 + .../prefixed/ralouphie/getallheaders/LICENSE | 21 + .../getallheaders/src/getallheaders.php | 38 + .../symfony/deprecation-contracts/LICENSE | 19 + .../deprecation-contracts/function.php | 28 + .../vendor/prefixed/vendor/autoload.php | 12 + .../prefixed/vendor/composer/ClassLoader.php | 572 ++ .../vendor/prefixed/vendor/composer/LICENSE | 21 + .../vendor/composer/autoload_classmap.php | 581 ++ .../vendor/composer/autoload_files.php | 16 + .../vendor/composer/autoload_namespaces.php | 9 + .../vendor/composer/autoload_psr4.php | 9 + .../vendor/composer/autoload_real.php | 55 + .../vendor/composer/autoload_static.php | 601 ++ .../vendor/scoper-autoload.php | 26 + .../vendor/yiisoft/extensions.php | 3 + .../SearchEngineKeywordsPerformance.umd.js | 2336 +++++++ ...SearchEngineKeywordsPerformance.umd.js.map | 1 + ...SearchEngineKeywordsPerformance.umd.min.js | 30 + ...chEngineKeywordsPerformance.umd.min.js.map | 1 + .../vue/dist/umd.metadata.json | 6 + .../vue/src/Admin/AdminPage.vue | 54 + .../vue/src/Admin/Provider.vue | 103 + .../vue/src/Bing/Configuration.vue | 431 ++ .../src/Configure/ConfigureConnection.less | 3 + .../vue/src/Configure/ConfigureConnection.vue | 125 + .../vue/src/Google/Configuration.vue | 637 ++ .../vue/src/Yandex/Configuration.vue | 578 ++ .../vue/src/index.ts | 20 + .../vue/src/utilities.ts | 18 + 920 files changed, 139711 insertions(+), 22 deletions(-) create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/API.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Actions/ActionHsr.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Activity/BaseActivity.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapAdded.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapDeleted.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapEnded.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapPaused.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapResumed.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapScreenshotDeleted.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapUpdated.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Activity/RecordedPageviewDeleted.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Activity/RecordedSessionDeleted.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Activity/SessionRecordingAdded.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Activity/SessionRecordingDeleted.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Activity/SessionRecordingEnded.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Activity/SessionRecordingPaused.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Activity/SessionRecordingResumed.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Activity/SessionRecordingUpdated.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Archiver/Aggregator.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/CHANGELOG.md create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Categories/HeatmapCategory.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Categories/ManageHeatmapSubcategory.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Categories/ManageSessionRecordingSubcategory.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Categories/SessionRecordingsCategory.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/BaseMetric.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/Browser.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/Device.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/Location.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/OperatingSystem.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/SessionTime.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/TimeOnPage.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/TimeOnSite.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/TotalEvents.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Commands/RemoveHeatmapScreenshot.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Configuration.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Controller.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Dao/LogHsr.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Dao/LogHsrBlob.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Dao/LogHsrEvent.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Dao/LogHsrSite.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Dao/SiteHsrDao.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/DataTable/Filter/EnrichRecordedSessions.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Diagnostic/ConfigsPhpCheck.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/HeatmapSessionRecording.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Input/Breakpoint.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Input/CaptureKeystrokes.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Input/ExcludedElements.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Input/MinSessionTime.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Input/Name.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Input/PageRule.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Input/PageRules.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Input/RequiresActivity.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Input/SampleLimit.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Input/SampleRate.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Input/ScreenshotUrl.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Input/Validator.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Install/HtAccess.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Install/htaccessTemplate create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/LEGALNOTICE create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/LICENSE create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Menu.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Model/SiteHsrModel.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/MutationManipulator.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/README.md create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Reports/GetRecordedSessions.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Settings/TrackingDisableDefault.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/SystemSettings.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Tasks.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Tracker/Configs.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Tracker/HsrMatcher.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Tracker/LogTable/LogHsr.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Tracker/LogTable/LogHsrBlob.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Tracker/LogTable/LogHsrEvent.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Tracker/PageRuleMatcher.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Tracker/RequestProcessor.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Updates/3.0.10.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Updates/3.0.11.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Updates/3.0.3.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Updates/4.0.0.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Updates/5.1.0.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/VisitorDetails.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Widgets/GetManageHeatmaps.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Widgets/GetManageSessionRecordings.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Widgets/GettingStartedHeatmap.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/Widgets/GettingStartedSessions.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/config/config.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/configs.php create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/docs/index.md create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/javascripts/recording.js create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/javascripts/rowaction.js create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/lang/bg.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/lang/cs.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/lang/da.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/lang/de.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/lang/en.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/lang/es.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/lang/fi.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/lang/fr.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/lang/hi.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/lang/it.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/lang/ja.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/lang/nb.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/lang/nl.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/lang/pl.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/lang/pt.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/lang/ro.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/lang/ru.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/lang/sq.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/lang/sv.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/lang/tr.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/lang/uk.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/lang/zh-cn.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/lang/zh-tw.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/libs/MutationObserver.js/MutationObserver.js create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/libs/MutationObserver.js/README.md create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/libs/MutationObserver.js/dist/README.md create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/libs/MutationObserver.js/dist/mutationobserver.min.js create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/libs/MutationObserver.js/license create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/libs/MutationObserver.js/package.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/COPYING create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/README.md create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/package.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/src/mutation-summary.js create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/src/mutation-summary.ts create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/util/tree-mirror.js create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/util/tree-mirror.ts create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/libs/svg.js/CHANGELOG.md create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/libs/svg.js/LICENSE.txt create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/libs/svg.js/README.md create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/libs/svg.js/dist/svg.js create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/libs/svg.js/dist/svg.min.js create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/libs/svg.js/package.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/package-lock.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/package.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/phpcs.xml create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/plugin.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/pull_request_template.md create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/stylesheets/edit-entities.less create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/stylesheets/list-entities.less create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/stylesheets/recordings.less create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/templates/_detectAdBlocker.twig create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/templates/embedPage.twig create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/templates/gettingStartedHeatmaps.twig create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/templates/gettingStartedSessions.twig create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/templates/manageHeatmap.twig create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/templates/manageSessions.twig create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/templates/replayRecording.twig create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/templates/showHeatmap.twig create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/tracker.min.js create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/tsconfig.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/dist/HeatmapSessionRecording.umd.js create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/dist/HeatmapSessionRecording.umd.min.js create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/dist/umd.metadata.json create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/HeatmapVis/HeatmapVis.less create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/HeatmapVis/HeatmapVis.vue create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/HeatmapVis/HeatmapVisPage.vue create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/HsrStore/HsrStore.store.ts create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/HsrTargetTest/HsrTargetTest.less create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/HsrTargetTest/HsrTargetTest.vue create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/HsrUrlTarget/AvailableTargetPageRules.store.ts create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/HsrUrlTarget/HsrUrlTarget.less create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/HsrUrlTarget/HsrUrlTarget.vue create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/ListOfPageviews/ListOfPageviews.vue create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/ManageHeatmap/Edit.vue create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/ManageHeatmap/List.vue create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/ManageHeatmap/Manage.vue create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/ManageSessionRecording/Edit.vue create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/ManageSessionRecording/List.vue create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/ManageSessionRecording/Manage.vue create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/MatomoJsNotWritable/MatomoJsNotWritableAlert.vue create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/SessionRecordingVis/SessionRecordingVis.less create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/SessionRecordingVis/SessionRecordingVis.vue create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/Tooltip/Tooltip.less create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/Tooltip/Tooltip.vue create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/getIframeWindow.ts create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/index.ts create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/oneAtATime.ts create mode 100644 files/plugin-HeatmapSessionRecording-5.2.4/vue/src/types.ts create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/API.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Activity/AccountAdded.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Activity/AccountRemoved.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Activity/GoogleClientConfigChanged.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Activity/YandexClientConfigChanged.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Archiver.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/CHANGELOG.md create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Categories/CrawlingOverviewSubcategory.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Categories/SearchKeywordsSubcategory.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Client/Bing.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Client/Configuration/BaseConfiguration.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Client/Configuration/Bing.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Client/Configuration/Google.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Client/Configuration/Yandex.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Client/Google.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Client/Yandex.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Columns/Keyword.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Commands/ImportBing.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Commands/ImportGoogle.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Commands/ImportYandex.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Controller.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Diagnostic/BingAccountDiagnostic.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Diagnostic/GoogleAccountDiagnostic.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Diagnostic/YandexAccountDiagnostic.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Exceptions/InvalidClientConfigException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Exceptions/InvalidCredentialsException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Exceptions/MissingClientConfigException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Exceptions/MissingOAuthConfigException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Exceptions/RateLimitApiException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Exceptions/UnknownAPIException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Importer/Bing.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Importer/Google.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Importer/Yandex.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/LICENSE create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/MeasurableSettings.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Menu.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Metrics.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Model/Bing.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Model/Google.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Model/Yandex.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Monolog/Handler/SEKPSystemLogHandler.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Provider/Bing.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Provider/Google.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Provider/Helper/MeasurableHelper.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Provider/ProviderAbstract.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Provider/Yandex.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/README.md create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/RecordBuilders/Base.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/RecordBuilders/Bing.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/RecordBuilders/Google.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/RecordBuilders/Yandex.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Reports/Base.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Reports/GetCrawlingErrorExamplesBing.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Reports/GetCrawlingOverviewBing.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Reports/GetCrawlingOverviewYandex.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Reports/GetKeywords.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Reports/GetKeywordsBing.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Reports/GetKeywordsGoogleImage.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Reports/GetKeywordsGoogleNews.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Reports/GetKeywordsGoogleVideo.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Reports/GetKeywordsGoogleWeb.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Reports/GetKeywordsImported.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Reports/GetKeywordsReferrers.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Reports/GetKeywordsYandex.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/SearchEngineKeywordsPerformance.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/SystemSettings.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Tasks.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Updates/3.5.0.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Updates/4.1.0.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/Updates/4.2.0.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/config/config.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/docs/index.md create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/images/Bing.png create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/images/Google.png create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/images/Yahoo.png create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/images/Yandex.png create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/bg.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/cs.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/da.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/de.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/en.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/es.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/fi.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/fr.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/hi.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/it.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/ja.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/nb.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/nl.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/pl.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/pt.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/ro.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/ru.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/sq.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/sv.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/tr.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/uk.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/zh-cn.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/lang/zh-tw.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/phpcs.xml create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/plugin.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/pull_request_template.md create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/scoper.inc.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/stylesheets/styles.less create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/templates/bing/configuration.twig create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/templates/google/configuration.twig create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/templates/index.twig create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/templates/messageReferrerKeywordsReport.twig create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/templates/yandex/configuration.twig create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/autoload.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/autoload_original.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/composer/ClassLoader.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/composer/InstalledVersions.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/composer/LICENSE create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/composer/autoload_classmap.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/composer/autoload_files.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/composer/autoload_namespaces.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/composer/autoload_psr4.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/composer/autoload_real.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/composer/autoload_static.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/composer/installed.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/composer/installed.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/firebase/php-jwt/LICENSE create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/firebase/php-jwt/src/BeforeValidException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/firebase/php-jwt/src/CachedKeySet.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/firebase/php-jwt/src/ExpiredException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/firebase/php-jwt/src/JWK.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/firebase/php-jwt/src/JWT.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/firebase/php-jwt/src/JWTExceptionWithPayloadInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/firebase/php-jwt/src/Key.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/firebase/php-jwt/src/SignatureInvalidException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/LICENSE create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/autoload.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/renovate.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/Oauth2.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/Oauth2/Resource/Userinfo.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/Oauth2/Resource/UserinfoV2.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/Oauth2/Resource/UserinfoV2Me.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/Oauth2/Tokeninfo.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/Oauth2/Userinfo.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/SearchConsole.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/SearchConsole/ApiDataRow.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/SearchConsole/ApiDimensionFilter.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/SearchConsole/ApiDimensionFilterGroup.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/SearchConsole/BlockedResource.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/SearchConsole/Image.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/SearchConsole/MobileFriendlyIssue.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/SearchConsole/Resource/Searchanalytics.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/SearchConsole/Resource/Sitemaps.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/SearchConsole/Resource/Sites.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/SearchConsole/Resource/UrlTestingTools.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/SearchConsole/Resource/UrlTestingToolsMobileFriendlyTest.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/SearchConsole/ResourceIssue.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/SearchConsole/RunMobileFriendlyTestRequest.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/SearchConsole/RunMobileFriendlyTestResponse.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/SearchConsole/SearchAnalyticsQueryRequest.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/SearchConsole/SearchAnalyticsQueryResponse.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/SearchConsole/SitemapsListResponse.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/SearchConsole/SitesListResponse.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/SearchConsole/TestStatus.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/SearchConsole/WmxSite.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/SearchConsole/WmxSitemap.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/src/SearchConsole/WmxSitemapContent.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/synth.metadata create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient-services/synth.py create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient/LICENSE create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient/src/AccessToken/Revoke.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient/src/AccessToken/Verify.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient/src/AuthHandler/AuthHandlerFactory.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient/src/AuthHandler/Guzzle6AuthHandler.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient/src/AuthHandler/Guzzle7AuthHandler.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient/src/Client.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient/src/Collection.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient/src/Exception.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient/src/Http/Batch.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient/src/Http/MediaFileUpload.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient/src/Http/REST.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient/src/Model.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient/src/Service.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient/src/Service/Exception.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient/src/Service/Resource.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient/src/Task/Composer.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient/src/Task/Exception.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient/src/Task/Retryable.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient/src/Task/Runner.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient/src/Utils/UriTemplate.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/apiclient/src/aliases.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/COPYING create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/LICENSE create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/VERSION create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/autoload.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/AccessToken.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/ApplicationDefaultCredentials.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/Cache/InvalidArgumentException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/Cache/Item.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/Cache/MemoryCacheItemPool.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/Cache/SysVCacheItemPool.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/Cache/TypedItem.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/CacheTrait.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/CredentialSource/AwsNativeSource.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/CredentialSource/FileSource.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/CredentialSource/UrlSource.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/Credentials/AppIdentityCredentials.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/Credentials/ExternalAccountCredentials.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/Credentials/GCECredentials.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/Credentials/IAMCredentials.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/Credentials/ImpersonatedServiceAccountCredentials.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/Credentials/InsecureCredentials.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/Credentials/ServiceAccountCredentials.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/Credentials/ServiceAccountJwtAccessCredentials.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/Credentials/UserRefreshCredentials.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/CredentialsLoader.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/ExternalAccountCredentialSourceInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/FetchAuthTokenCache.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/FetchAuthTokenInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/GCECache.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/GetQuotaProjectInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/GetUniverseDomainInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/HttpHandler/Guzzle6HttpHandler.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/HttpHandler/Guzzle7HttpHandler.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/HttpHandler/HttpClientCache.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/HttpHandler/HttpHandlerFactory.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/Iam.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/IamSignerTrait.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/Middleware/AuthTokenMiddleware.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/Middleware/ProxyAuthTokenMiddleware.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/Middleware/ScopedAccessTokenMiddleware.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/Middleware/SimpleMiddleware.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/OAuth2.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/ProjectIdProviderInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/ServiceAccountSignerTrait.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/SignBlobInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/UpdateMetadataInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/google/auth/src/UpdateMetadataTrait.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/LICENSE create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/BodySummarizer.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/BodySummarizerInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Client.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/ClientInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/ClientTrait.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Cookie/CookieJar.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Cookie/SetCookie.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Exception/BadResponseException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Exception/ClientException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Exception/ConnectException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Exception/GuzzleException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Exception/InvalidArgumentException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Exception/RequestException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Exception/ServerException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Exception/TransferException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Handler/CurlFactory.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Handler/CurlHandler.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Handler/EasyHandle.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Handler/MockHandler.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Handler/Proxy.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Handler/StreamHandler.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/HandlerStack.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/MessageFormatter.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/MessageFormatterInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Middleware.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Pool.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/RedirectMiddleware.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/RequestOptions.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/RetryMiddleware.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/TransferStats.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/Utils.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/functions.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/guzzle/src/functions_include.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/promises/LICENSE create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/promises/src/AggregateException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/promises/src/CancellationException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/promises/src/Coroutine.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/promises/src/Create.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/promises/src/Each.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/promises/src/EachPromise.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/promises/src/FulfilledPromise.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/promises/src/Is.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/promises/src/Promise.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/promises/src/PromiseInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/promises/src/PromisorInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/promises/src/RejectedPromise.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/promises/src/RejectionException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/promises/src/TaskQueue.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/promises/src/TaskQueueInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/promises/src/Utils.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/promises/src/functions.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/promises/src/functions_include.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/LICENSE create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/AppendStream.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/BufferStream.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/CachingStream.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/DroppingStream.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/Exception/MalformedUriException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/FnStream.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/Header.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/HttpFactory.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/InflateStream.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/LazyOpenStream.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/LimitStream.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/Message.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/MessageTrait.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/MimeType.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/MultipartStream.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/NoSeekStream.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/PumpStream.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/Query.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/Request.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/Response.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/Rfc7230.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/ServerRequest.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/Stream.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/StreamDecoratorTrait.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/StreamWrapper.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/UploadedFile.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/Uri.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/UriComparator.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/UriNormalizer.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/UriResolver.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/guzzlehttp/psr7/src/Utils.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/paragonie/constant_time_encoding/LICENSE.txt create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/paragonie/constant_time_encoding/src/Base32.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/paragonie/constant_time_encoding/src/Base32Hex.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/paragonie/constant_time_encoding/src/Base64.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/paragonie/constant_time_encoding/src/Base64DotSlash.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/paragonie/constant_time_encoding/src/Base64DotSlashOrdered.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/paragonie/constant_time_encoding/src/Base64UrlSafe.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/paragonie/constant_time_encoding/src/Binary.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/paragonie/constant_time_encoding/src/EncoderInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/paragonie/constant_time_encoding/src/Encoding.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/paragonie/constant_time_encoding/src/Hex.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/paragonie/constant_time_encoding/src/RFC4648.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/paragonie/random_compat/LICENSE create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/paragonie/random_compat/build-phar.sh create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/paragonie/random_compat/dist/random_compat.phar.pubkey create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/paragonie/random_compat/dist/random_compat.phar.pubkey.asc create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/paragonie/random_compat/lib/random.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/paragonie/random_compat/other/build_phar.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/paragonie/random_compat/psalm-autoload.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/paragonie/random_compat/psalm.xml create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/AUTHORS create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/LICENSE create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Common/Functions/Strings.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/AES.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/Blowfish.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/ChaCha20.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/Common/AsymmetricKey.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/Common/BlockCipher.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/JWK.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/OpenSSH.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS8.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PuTTY.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Signature/Raw.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/Common/PrivateKey.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/Common/PublicKey.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/Common/StreamCipher.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/Common/SymmetricKey.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/Common/Traits/Fingerprint.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/Common/Traits/PasswordProtected.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/DES.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/DH.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/DH/Formats/Keys/PKCS1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/DH/Formats/Keys/PKCS8.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/DH/Parameters.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/DH/PrivateKey.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/DH/PublicKey.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/DSA.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/OpenSSH.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PKCS1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PKCS8.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PuTTY.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/Raw.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/XML.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/ASN1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/Raw.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/SSH2.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/DSA/Parameters.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/DSA/PrivateKey.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/DSA/PublicKey.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Base.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Binary.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/KoblitzPrime.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Montgomery.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Prime.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/TwistedEdwards.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Curve25519.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Curve448.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Ed25519.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Ed448.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP160r1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP160t1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP192r1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP192t1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP224r1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP224t1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP256r1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP256t1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP320r1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP320t1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP384r1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP384t1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP512r1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP512t1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistb233.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistb409.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk163.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk233.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk283.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk409.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp192.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp224.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp256.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp384.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp521.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistt571.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v2.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v3.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v2.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v3.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime256v1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp112r1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp112r2.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp128r1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp128r2.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160k1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160r1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160r2.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp192k1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp192r1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp224k1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp224r1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp256k1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp256r1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp384r1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp521r1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect113r1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect113r2.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect131r1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect131r2.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163k1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163r1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163r2.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect193r1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect193r2.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect233k1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect233r1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect239k1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect283k1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect283r1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect409k1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect409r1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect571k1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect571r1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/Common.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/JWK.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/MontgomeryPrivate.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/MontgomeryPublic.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/OpenSSH.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PKCS1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PKCS8.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PuTTY.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/XML.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/libsodium.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/ASN1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/IEEE.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/Raw.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/SSH2.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/Parameters.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/PrivateKey.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/EC/PublicKey.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/Hash.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/PublicKeyLoader.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/RC2.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/RC4.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/RSA.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/JWK.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/MSBLOB.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/OpenSSH.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PKCS1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PKCS8.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PSS.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PuTTY.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/Raw.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/XML.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/RSA/PrivateKey.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/RSA/PublicKey.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/Random.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/Rijndael.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/Salsa20.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/TripleDES.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Crypt/Twofish.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Exception/BadConfigurationException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Exception/BadDecryptionException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Exception/BadModeException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Exception/ConnectionClosedException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Exception/FileNotFoundException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Exception/InconsistentSetupException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Exception/InsufficientSetupException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Exception/NoKeyLoadedException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Exception/NoSupportedAlgorithmsException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Exception/UnableToConnectException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Exception/UnsupportedAlgorithmException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Exception/UnsupportedCurveException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Exception/UnsupportedFormatException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Exception/UnsupportedOperationException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ANSI.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Element.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AccessDescription.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AdministrationDomainName.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AlgorithmIdentifier.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AnotherName.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Attribute.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeType.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeTypeAndValue.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeValue.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Attributes.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AuthorityInfoAccessSyntax.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AuthorityKeyIdentifier.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BaseDistance.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BasicConstraints.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttribute.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttributes.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInStandardAttributes.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CPSuri.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLDistributionPoints.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLNumber.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLReason.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertPolicyId.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Certificate.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateIssuer.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateList.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificatePolicies.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateSerialNumber.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificationRequest.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificationRequestInfo.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Characteristic_two.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CountryName.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Curve.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DHParameter.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAParams.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAPrivateKey.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAPublicKey.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DigestInfo.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DirectoryString.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DisplayText.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DistributionPoint.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DistributionPointName.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DssSigValue.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECParameters.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECPoint.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECPrivateKey.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EDIPartyName.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EcdsaSigValue.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EncryptedData.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EncryptedPrivateKeyInfo.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtKeyUsageSyntax.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Extension.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtensionAttribute.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtensionAttributes.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Extensions.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/FieldElement.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/FieldID.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralName.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralNames.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralSubtree.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralSubtrees.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/HashAlgorithm.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/HoldInstructionCode.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/InvalidityDate.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/IssuerAltName.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/IssuingDistributionPoint.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyIdentifier.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyPurposeId.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyUsage.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/MaskGenAlgorithm.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Name.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NameConstraints.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NetworkAddress.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NoticeReference.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NumericUserIdentifier.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ORAddress.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OneAsymmetricKey.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OrganizationName.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OrganizationalUnitNames.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OtherPrimeInfo.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OtherPrimeInfos.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBEParameter.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBES2params.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBKDF2params.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBMAC1params.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PKCS9String.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Pentanomial.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PersonalName.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyInformation.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyMappings.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyQualifierId.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyQualifierInfo.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PostalAddress.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Prime_p.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateDomainName.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKey.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKeyInfo.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKeyUsagePeriod.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKey.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKeyAndChallenge.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKeyInfo.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RC2CBCParameter.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RDNSequence.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSAPrivateKey.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSAPublicKey.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSASSA_PSS_params.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ReasonFlags.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RelativeDistinguishedName.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RevokedCertificate.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SignedPublicKeyAndChallenge.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SpecifiedECDomain.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectAltName.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectDirectoryAttributes.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectInfoAccessSyntax.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectPublicKeyInfo.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TBSCertList.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TBSCertificate.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TerminalIdentifier.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Time.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Trinomial.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/UniqueIdentifier.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/UserNotice.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Validity.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_ca_policy_url.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_cert_type.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_comment.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/File/X509.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Base.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/BuiltIn.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/DefaultEngine.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/OpenSSL.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Reductions/Barrett.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Reductions/EvalBarrett.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/Engine.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/GMP.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/GMP/DefaultEngine.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/OpenSSL.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Base.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/DefaultEngine.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Montgomery.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/OpenSSL.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Barrett.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Classic.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/EvalBarrett.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Montgomery.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/MontgomeryMult.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/PowerOfTwo.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP32.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP64.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BinaryField.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/BinaryField/Integer.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/Common/FiniteField.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/Common/FiniteField/Integer.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/PrimeField.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Math/PrimeField/Integer.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Net/SFTP.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Net/SFTP/Stream.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/Net/SSH2.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/System/SSH/Agent.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/System/SSH/Agent/Identity.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/System/SSH/Common/Traits/ReadBytes.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/bootstrap.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/phpseclib/phpseclib/phpseclib/openssl.cnf create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/cache/LICENSE.txt create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/cache/src/CacheException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/cache/src/CacheItemInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/cache/src/CacheItemPoolInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/cache/src/InvalidArgumentException.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/http-client/LICENSE create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/http-client/src/ClientExceptionInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/http-client/src/ClientInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/http-client/src/NetworkExceptionInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/http-client/src/RequestExceptionInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/http-factory/LICENSE create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/http-factory/src/RequestFactoryInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/http-factory/src/ResponseFactoryInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/http-factory/src/ServerRequestFactoryInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/http-factory/src/StreamFactoryInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/http-factory/src/UploadedFileFactoryInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/http-factory/src/UriFactoryInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/http-message/LICENSE create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/http-message/src/MessageInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/http-message/src/RequestInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/http-message/src/ResponseInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/http-message/src/ServerRequestInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/http-message/src/StreamInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/http-message/src/UploadedFileInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/psr/http-message/src/UriInterface.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/ralouphie/getallheaders/LICENSE create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/ralouphie/getallheaders/src/getallheaders.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/symfony/deprecation-contracts/LICENSE create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/symfony/deprecation-contracts/function.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/vendor/autoload.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/vendor/composer/ClassLoader.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/vendor/composer/LICENSE create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/vendor/composer/autoload_classmap.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/vendor/composer/autoload_files.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/vendor/composer/autoload_namespaces.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/vendor/composer/autoload_psr4.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/vendor/composer/autoload_real.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/prefixed/vendor/composer/autoload_static.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/scoper-autoload.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vendor/yiisoft/extensions.php create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vue/dist/SearchEngineKeywordsPerformance.umd.js create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vue/dist/SearchEngineKeywordsPerformance.umd.js.map create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vue/dist/SearchEngineKeywordsPerformance.umd.min.js create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vue/dist/SearchEngineKeywordsPerformance.umd.min.js.map create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vue/dist/umd.metadata.json create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vue/src/Admin/AdminPage.vue create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vue/src/Admin/Provider.vue create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vue/src/Bing/Configuration.vue create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vue/src/Configure/ConfigureConnection.less create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vue/src/Configure/ConfigureConnection.vue create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vue/src/Google/Configuration.vue create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vue/src/Yandex/Configuration.vue create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vue/src/index.ts create mode 100644 files/plugin-SearchEngineKeywordsPerformance-5.0.22/vue/src/utilities.ts diff --git a/Dockerfile b/Dockerfile index 6d5bdf8..df603e7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,16 +9,16 @@ COPY ./files/plugin-EnvironmentVariables-5.0.3/ /var/www/html/plugins/Environmen COPY ./files/plugin-CustomVariables-5.0.4/ /var/www/html/plugins/CustomVariables # Add the HeatmapSessionRecording plugin -COPY ./files/plugin-HeatmapSessionRecording-5.2.3/ /var/www/html/plugins/HeatmapSessionRecording +COPY ./files/plugin-HeatmapSessionRecording-5.2.4/ /var/www/html/plugins/HeatmapSessionRecording # Add the UsersFlow plugin COPY ./files/plugin-UsersFlow-5.0.5/ /var/www/html/plugins/UsersFlow -# Our custom configuration settings. We put it in /usr/src because the -# entrypoint.sh builds the /var/www/html folder from the /usr/src/matomo -# folder. This ensures that the config file from our updated container is the -# one that is pushed to the persistent EFS storage. -COPY ./files/config.ini.php /usr/src/matomo/config/config.ini.php +# Add the SearchEngineKeywordsPerformance plugin +COPY ./files/plugin-SearchEngineKeywordsPerformance-5.0.22/ /var/www/html/plugins/SearchEngineKeywordsPerformance + +# Our custom configuration settings. +COPY ./files/config.ini.php /var/www/html/config/config.ini.php # The HeatmapSessionRecording and UsersFlow update the matomo.js and piwik.js # files when they are activated. Those updates have been captured and we diff --git a/docs/HowTos/HOWTO-miscellaneous.md b/docs/HowTos/HOWTO-miscellaneous.md index 2985213..272bfd6 100644 --- a/docs/HowTos/HOWTO-miscellaneous.md +++ b/docs/HowTos/HOWTO-miscellaneous.md @@ -34,8 +34,13 @@ To retrieve the **task number** value for the command: OR ```bash -aws ecs list-clusters --output text | grep matomo | cut -d'/' -f2 -aws ecs list-tasks --cluster $(aws ecs list-clusters --output text | grep matomo | cut -d'/' -f2) --query "taskArns[*]" --output text | cut -d'/' -f3 +aws ecs execute-command --region us-east-1 --cluster $(aws ecs list-clusters --output text | grep matomo | cut -d'/' -f2) --task $(aws ecs list-tasks --cluster $(aws ecs list-clusters --output text | grep matomo | cut -d'/' -f2) --query "taskArns[*]" --output text | cut -d'/' -f3) --command "/bin/bash" --interactive +``` + +If you need to force a redeployment of the task for the service, this one-liner will work: + +```bash +aws ecs update-service --cluster $(aws ecs list-clusters --output text | grep matomo | cut -d'/' -f2) --service $(aws ecs list-services --cluster $(aws ecs list-clusters --output text | grep matomo | cut -d'/' -f2) --output text | grep matomo | cut -d'/' -f3) --force-new-deployment ``` ## Reset 2-Factor auth diff --git a/docs/HowTos/HOWTO-premium-plugins.md b/docs/HowTos/HOWTO-premium-plugins.md index aaaff53..5be77ac 100644 --- a/docs/HowTos/HOWTO-premium-plugins.md +++ b/docs/HowTos/HOWTO-premium-plugins.md @@ -8,7 +8,7 @@ After some initial testing in Dev1, it's not as simple as just dumping the new p 1. Some plugins require changes to the database tables or just new tables. This requires that the plugin installation process is triggered to kick off the script that updates the tables. 1. The *Marketplace* plugin must be active for license keys to work. -## The config.ini.php file +## A note about the config.ini.php file The `config.ini.php` file has two lists of plugins under two different headings. @@ -22,8 +22,8 @@ In the end, the premium plugin installation is a two-pass process. ### High level overview -1. Install license key (via UI or CLI) so that it is in the database. -2. Go through a dev -> stage -> prod deployment cycle of the container to install the plugin folder(s) into the container. +1. Install license key (via UI or CLI) so that it is in the database (this apparently only needs to be done once as all future premium plugins get linked to the same license key). +2. Go through a dev -> stage -> prod deployment cycle of the container to install the plugin folder(s) into the container 3. Activate the new plugin(s) (via UI or CLI) so that any database changes are properly executed. 4. Go through a dev -> stage -> prod deployment cycle of the container to match the updated `config.ini.php` file on the server. @@ -31,7 +31,7 @@ In the end, the premium plugin installation is a two-pass process. #### 1. Install the license key -Before installing the license key, the *Marketplace* plugin must be activated. This is a one-time update to the `config.ini.php` file to add the *Marketplace* pluging to the `[Plugins]` section. +Before installing the license key, the *Marketplace* plugin must be activated. This is a one-time update to the `config.ini.php` file to add the *Marketplace* pluging to the `[Plugins]` section - all new premium plugin purchases are linked to the same license key. According to the support team at Matomo, the premium license key can be installed in two instances of Matomo, "stage" and "prod." So, we can do some initial validation of a license key in Dev1, but the key cannot remain installed in the Dev1 instance. The license key installation can either be done by a user with "superuser" privileges in the Matomo web UI or it can be done by a member of InfraEng who has ssh access to the running container task/service. The CLI command is @@ -39,11 +39,13 @@ According to the support team at Matomo, the premium license key can be installe ./console marketplace:set-license-key --license-key=LICENSE-KEY "" ``` -This needs to be done on each of the stage & prod instances of Matomo. +This needs to be done once on each of the stage & prod instances of Matomo. #### 2. Install the plugin files -In this phase, the files are installed in the container *but no changes are made to the `config.ini.php` file. This will **not** activate the plugins, it will just make them visible in the UI. +In this phase, the files are installed in the container **but** no changes are made to the `config.ini.php` file. This will **not** activate the plugins, it will just make them visible in the UI. + +**Note**: It is possible to do this with the `/var/www/html/console` utility when logged in to the cli of the running conatiner. However, that method introduces potential file permission errors since the command is run as `root` and the content in the `/var/www/html` folder needs to be owned by `www-data`. #### 3. Activate the plugin @@ -53,7 +55,9 @@ Once the plugin files are installed in the container, it's time to activate the ./console plugin:activate [...] ``` -This will change the `config.ini.php` file on the container. It is **very** important to capture these changes and put them back in the `config.ini.php` in the container (see step 4). +This will change the `config/config.ini.php` file -- which is actually persisted on the EFS filesystem linked to the container. It is important to capture any changes that happen in this file so that we can back-fill this repository in case we need to redeploy in a DR scenario. + +It's also important to note that this `plugin:activate` command very likely makes changes to the database (adding/removing tables/columns or other changes). #### 4. Backfill this repo diff --git a/files/backup-data.sh b/files/backup-data.sh index 5567a35..7dfe4b8 100755 --- a/files/backup-data.sh +++ b/files/backup-data.sh @@ -1,15 +1,31 @@ #!/bin/bash -target_dir="/mnt/efs" +# Define source directories +source_dirs=( + "/var/www/html/config" + "/var/www/html/misc" + "/var/www/html/js" +) -mkdir -p "$target_dir/config" -tar -cf - -C "/var/www/html/config" . | tar -xf - -C "$target_dir/config" +# Define target directory +target_dir="/mnt/efs/backups" -mkdir -p "$target_dir/misc" -tar -cf - -C "/var/www/html/misc" . | tar -xf - -C "$target_dir/misc" +# Loop through each source directory and duplicate it to the target directory +for src in "${source_dirs[@]}"; do + # Extract the directory name from the source path + dir_name=$(basename "$src") + + # Create the target directory if it doesn't exist + mkdir -p "$target_dir/$dir_name" + + # Use tar to duplicate the directory + tar -cf - -C "$src" . | tar -xf - -C "$target_dir/$dir_name" +done -mkdir -p "$target_dir/js" -tar -cf - -C "/var/www/html/js" . | tar -xf - -C "$target_dir/js" +echo "Directories have been successfully duplicated to $target_dir." cp -a "/var/www/html/matomo.js" "$target_dir/matomo.js" cp -a "/var/www/html/piwik.js" "$target_dir/piwik.js" + +# finally, make sure everything is www-data:www-data +chown -R www-data:www-data "$target_dir" diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/API.php b/files/plugin-HeatmapSessionRecording-5.2.4/API.php new file mode 100644 index 0000000..8dd9ecc --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/API.php @@ -0,0 +1,999 @@ +validator = $validator; + $this->aggregator = $aggregator; + $this->siteHsr = $siteHsr; + $this->logHsr = $logHsr; + $this->logEvent = $logEvent; + $this->logHsrSite = $logHsrSite; + $this->systemSettings = $settings; + $this->configuration = $configuration; + + $dir = Plugin\Manager::getPluginDirectory('UserCountry'); + require_once $dir . '/functions.php'; + } + + /** + * Adds a new heatmap. + * + * Once added, the system will start recording activities for this heatmap. + * + * @param int $idSite + * @param string $name The name of heatmap which will be visible in the reporting UI. + * @param array $matchPageRules Eg. array(array('attribute' => 'url', 'type' => 'equals_simple', 'inverted' => 0, 'value' => 'http://example.com/directory')) + * For a list of available attribute and type values call {@link getAvailableTargetPageRules()}. + * "inverted" should be "0" or "1". + * @param int $sampleLimit The number of page views you want to record. Once the sample limit has been reached, the heatmap will be ended automatically. + * @param float $sampleRate Needs to be between 0 and 100 where 100 means => 100%, 10 => 10%, 0.1 => 0.1%. + * Defines how often a visitor will be actually recorded when they match the page rules, also known as "traffic". Currently max one decimal is supported. + * @param string $excludedElements Optional, a comma separated list of CSS selectors to exclude elements from being shown in the heatmap. For example to disable popups etc. + * @param string $screenshotUrl Optional, a URL to define on which page a screenshot should be taken. + * @param int $breakpointMobile If the device type cannot be detected, we will put any device having a lower width than this value into the mobile category. Useful if your website is responsive. + * @param int $breakpointTablet If the device type cannot be detected, we will put any device having a lower width than this value into the tablet category. Useful if your website is responsive. + * @return int + */ + public function addHeatmap($idSite, $name, $matchPageRules, $sampleLimit = 1000, $sampleRate = 5, $excludedElements = false, $screenshotUrl = false, $breakpointMobile = false, $breakpointTablet = false, $captureDomManually = false) + { + $this->validator->checkHeatmapReportWritePermission($idSite); + + if ($breakpointMobile === false || $breakpointMobile === null) { + $breakpointMobile = $this->systemSettings->breakpointMobile->getValue(); + } + + if ($breakpointTablet === false || $breakpointTablet === null) { + $breakpointTablet = $this->systemSettings->breakpointTablet->getValue(); + } + + $createdDate = Date::now()->getDatetime(); + + $matchPageRules = $this->unsanitizePageRules($matchPageRules); + $screenshotUrl = $this->unsanitizeScreenshotUrl($screenshotUrl); + + return $this->siteHsr->addHeatmap($idSite, $name, $matchPageRules, $sampleLimit, $sampleRate, $excludedElements, $screenshotUrl, $breakpointMobile, $breakpointTablet, $captureDomManually, $createdDate); + } + + private function unsanitizeScreenshotUrl($screenshotUrl) + { + if (!empty($screenshotUrl) && is_string($screenshotUrl)) { + $screenshotUrl = Common::unsanitizeInputValue($screenshotUrl); + } + + return $screenshotUrl; + } + + private function unsanitizePageRules($matchPageRules) + { + if (!empty($matchPageRules) && is_array($matchPageRules)) { + foreach ($matchPageRules as $index => $matchPageRule) { + if (is_array($matchPageRule) && !empty($matchPageRule['value'])) { + $matchPageRules[$index]['value'] = Common::unsanitizeInputValue($matchPageRule['value']); + } + } + } + return $matchPageRules; + } + + /** + * Updates an existing heatmap. + * + * All fields need to be set in order to update a heatmap. Easiest way is to get all values for a heatmap via + * "HeatmapSessionRecording.getHeatmap", make the needed changes on the heatmap, and send all values back to + * "HeatmapSessionRecording.updateHeatmap". + * + * @param int $idSite + * @param int $idSiteHsr The id of the heatmap you want to update. + * @param string $name The name of heatmap which will be visible in the reporting UI. + * @param array $matchPageRules Eg. array(array('attribute' => 'url', 'type' => 'equals_simple', 'inverted' => 0, 'value' => 'http://example.com/directory')) + * For a list of available attribute and type values call {@link getAvailableTargetPageRules()}. + * "inverted" should be "0" or "1". + * @param int $sampleLimit The number of page views you want to record. Once the sample limit has been reached, the heatmap will be ended automatically. + * @param float $sampleRate Needs to be between 0 and 100 where 100 means => 100%, 10 => 10%, 0.1 => 0.1%. + * Defines how often a visitor will be actually recorded when they match the page rules, also known as "traffic". Currently max one decimal is supported. + * @param string $excludedElements Optional, a comma separated list of CSS selectors to exclude elements from being shown in the heatmap. For example to disable popups etc. + * @param string $screenshotUrl Optional, a URL to define on which page a screenshot should be taken. + * @param int $breakpointMobile If the device type cannot be detected, we will put any device having a lower width than this value into the mobile category. Useful if your website is responsive. + * @param int $breakpointTablet If the device type cannot be detected, we will put any device having a lower width than this value into the tablet category. Useful if your website is responsive. + */ + public function updateHeatmap($idSite, $idSiteHsr, $name, $matchPageRules, $sampleLimit = 1000, $sampleRate = 5, $excludedElements = false, $screenshotUrl = false, $breakpointMobile = false, $breakpointTablet = false, $captureDomManually = false) + { + $this->validator->checkHeatmapReportWritePermission($idSite); + $this->siteHsr->checkHeatmapExists($idSite, $idSiteHsr); + + if ($breakpointMobile === false || $breakpointMobile === null) { + $breakpointMobile = $this->systemSettings->breakpointMobile->getValue(); + } + + if ($breakpointTablet === false || $breakpointTablet === null) { + $breakpointTablet = $this->systemSettings->breakpointTablet->getValue(); + } + + $updatedDate = Date::now()->getDatetime(); + + $matchPageRules = $this->unsanitizePageRules($matchPageRules); + $screenshotUrl = $this->unsanitizeScreenshotUrl($screenshotUrl); + + $this->siteHsr->updateHeatmap($idSite, $idSiteHsr, $name, $matchPageRules, $sampleLimit, $sampleRate, $excludedElements, $screenshotUrl, $breakpointMobile, $breakpointTablet, $captureDomManually, $updatedDate); + } + + /** + * Deletes / removes the screenshot from a heatmap + * @param int $idSite + * @param int $idSiteHsr + * @return bool + * @throws Exception + */ + public function deleteHeatmapScreenshot($idSite, $idSiteHsr) + { + $this->validator->checkHeatmapReportWritePermission($idSite); + $this->siteHsr->checkHeatmapExists($idSite, $idSiteHsr); + + $heatmap = $this->siteHsr->getHeatmap($idSite, $idSiteHsr); + if (!empty($heatmap['status']) && $heatmap['status'] === SiteHsrDao::STATUS_ACTIVE) { + $this->siteHsr->setPageTreeMirror($idSite, $idSiteHsr, null, null); + + if (!empty($heatmap['page_treemirror'])) { + // only needed when a screenshot existed before that + Cache::deleteCacheWebsiteAttributes($idSite); + } + return true; + } elseif (!empty($heatmap['status'])) { + throw new Exception('The screenshot can be only removed from active heatmaps'); + } + } + + /** + * Adds a new session recording. + * + * Once added, the system will start recording sessions. + * + * @param int $idSite + * @param string $name The name of session recording which will be visible in the reporting UI. + * @param array $matchPageRules Eg. array(array('attribute' => 'url', 'type' => 'equals_simple', 'inverted' => 0, 'value' => 'http://example.com/directory')) + * For a list of available attribute and type values call {@link getAvailableTargetPageRules()}. + * "inverted" should be "0" or "1". Leave it empty to record any page. + * If page rules are set, a session will be only recorded as soon as a visitor has reached a page that matches these rules. + * @param int $sampleLimit The number of sessions you want to record. Once the sample limit has been reached, the session recording will be ended automatically. + * @param float $sampleRate Needs to be between 0 and 100 where 100 means => 100%, 10 => 10%, 0.1 => 0.1%. + * Defines how often a visitor will be actually recorded when they match the page rules, also known as "traffic". Currently max one decimal is supported. + * @param int $minSessionTime If defined, will only record sessions when the visitor has spent more than this many seconds on the current page. + * @param int $requiresActivity If enabled (default), the session will be only recorded if the visitor has at least scrolled and clicked once. + * @param int $captureKeystrokes If enabled (default), any text that a user enters into text form elements will be recorded. + * Password fields will be automatically masked and you can mask other elements with sensitive data using a data-matomo-mask attribute. + * @return int + */ + public function addSessionRecording($idSite, $name, $matchPageRules = array(), $sampleLimit = 1000, $sampleRate = 10, $minSessionTime = 0, $requiresActivity = true, $captureKeystrokes = true) + { + $this->validator->checkSessionReportWritePermission($idSite); + + $createdDate = Date::now()->getDatetime(); + + $matchPageRules = $this->unsanitizePageRules($matchPageRules); + + return $this->siteHsr->addSessionRecording($idSite, $name, $matchPageRules, $sampleLimit, $sampleRate, $minSessionTime, $requiresActivity, $captureKeystrokes, $createdDate); + } + + /** + * Updates an existing session recording. + * + * All fields need to be set in order to update a session recording. Easiest way is to get all values for a + * session recording via "HeatmapSessionRecording.getSessionRecording", make the needed changes on the recording, + * and send all values back to "HeatmapSessionRecording.updateSessionRecording". + * + * @param int $idSite + * @param int $idSiteHsr The id of the session recording you want to update. + * @param string $name The name of session recording which will be visible in the reporting UI. + * @param array $matchPageRules Eg. array(array('attribute' => 'url', 'type' => 'equals_simple', 'inverted' => 0, 'value' => 'http://example.com/directory')) + * For a list of available attribute and type values call {@link getAvailableTargetPageRules()}. + * "inverted" should be "0" or "1". Leave it empty to record any page. + * If page rules are set, a session will be only recorded as soon as a visitor has reached a page that matches these rules. + * @param int $sampleLimit The number of sessions you want to record. Once the sample limit has been reached, the session recording will be ended automatically. + * @param float $sampleRate Needs to be between 0 and 100 where 100 means => 100%, 10 => 10%, 0.1 => 0.1%. + * Defines how often a visitor will be actually recorded when they match the page rules, also known as "traffic". Currently max one decimal is supported. + * @param int $minSessionTime If defined, will only record sessions when the visitor has spent more than this many seconds on the current page. + * @param int $requiresActivity If enabled (default), the session will be only recorded if the visitor has at least scrolled and clicked once. + * @param int $captureKeystrokes If enabled (default), any text that a user enters into text form elements will be recorded. + * Password fields will be automatically masked and you can mask other elements with sensitive data using a data-matomo-mask attribute. + */ + public function updateSessionRecording($idSite, $idSiteHsr, $name, $matchPageRules = array(), $sampleLimit = 1000, $sampleRate = 10, $minSessionTime = 0, $requiresActivity = true, $captureKeystrokes = true) + { + $this->validator->checkSessionReportWritePermission($idSite); + $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr); + + $updatedDate = Date::now()->getDatetime(); + + $matchPageRules = $this->unsanitizePageRules($matchPageRules); + + $this->siteHsr->updateSessionRecording($idSite, $idSiteHsr, $name, $matchPageRules, $sampleLimit, $sampleRate, $minSessionTime, $requiresActivity, $captureKeystrokes, $updatedDate); + } + + /** + * Get a specific heatmap by its ID. + * + * @param int $idSite + * @param int $idSiteHsr The id of the heatmap. + * @return array|false + */ + public function getHeatmap($idSite, $idSiteHsr) + { + $this->validator->checkHeatmapReportViewPermission($idSite); + $this->siteHsr->checkHeatmapExists($idSite, $idSiteHsr); + + $heatmap = $this->siteHsr->getHeatmap($idSite, $idSiteHsr); + + return $heatmap; + } + + /** + * Get a specific session recording by its ID. + * + * @param int $idSite + * @param int $idSiteHsr The id of the heatmap. + * @return array|false + */ + public function getSessionRecording($idSite, $idSiteHsr) + { + $this->validator->checkSessionReportViewPermission($idSite); + $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr); + + return $this->siteHsr->getSessionRecording($idSite, $idSiteHsr); + } + + /** + * Pauses the given heatmap. + * + * When a heatmap is paused, all the tracking will be paused until its resumed again. + * + * @param int $idSite + * @param int $idSiteHsr The id of the heatmap + */ + public function pauseHeatmap($idSite, $idSiteHsr) + { + $this->validator->checkHeatmapReportWritePermission($idSite); + $this->siteHsr->checkHeatmapExists($idSite, $idSiteHsr); + + $this->siteHsr->pauseHeatmap($idSite, $idSiteHsr); + + Cache::deleteCacheWebsiteAttributes($idSite); + } + + /** + * Resumes the given heatmap. + * + * When a heatmap is resumed, all the tracking will be enabled. + * + * @param int $idSite + * @param int $idSiteHsr The id of the heatmap + */ + public function resumeHeatmap($idSite, $idSiteHsr) + { + $this->validator->checkHeatmapReportWritePermission($idSite); + $this->siteHsr->checkHeatmapExists($idSite, $idSiteHsr); + + $this->siteHsr->resumeHeatmap($idSite, $idSiteHsr); + + Cache::deleteCacheWebsiteAttributes($idSite); + } + + /** + * Deletes the given heatmap. + * + * When a heatmap is deleted, the report will be no longer available in the API and tracked data for this + * heatmap might be removed. + * + * @param int $idSite + * @param int $idSiteHsr The id of the heatmap + */ + public function deleteHeatmap($idSite, $idSiteHsr) + { + $this->validator->checkHeatmapReportWritePermission($idSite); + + $this->siteHsr->deactivateHeatmap($idSite, $idSiteHsr); + } + + /** + * Ends / finishes the given heatmap. + * + * When you end a heatmap, the heatmap reports will be still available via API and UI but no new heatmap activity + * will be recorded for this heatmap. + * + * @param int $idSite + * @param int $idSiteHsr The id of the heatmap. + */ + public function endHeatmap($idSite, $idSiteHsr) + { + $this->validator->checkHeatmapReportWritePermission($idSite); + $this->siteHsr->checkHeatmapExists($idSite, $idSiteHsr); + + $this->siteHsr->endHeatmap($idSite, $idSiteHsr); + } + + /** + * Pauses the given session recording. + * + * When a session recording is paused, all the tracking will be paused until its resumed again. + * + * @param int $idSite + * @param int $idSiteHsr The id of the heatmap + */ + public function pauseSessionRecording($idSite, $idSiteHsr) + { + $this->validator->checkSessionReportWritePermission($idSite); + $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr); + + $this->siteHsr->pauseSessionRecording($idSite, $idSiteHsr); + + Cache::deleteCacheWebsiteAttributes($idSite); + } + + /** + * Resumes the given session recording. + * + * When a session recording is resumed, all the tracking will be enabled. + * + * @param int $idSite + * @param int $idSiteHsr The id of the heatmap + */ + public function resumeSessionRecording($idSite, $idSiteHsr) + { + $this->validator->checkSessionReportWritePermission($idSite); + $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr); + + $this->siteHsr->resumeSessionRecording($idSite, $idSiteHsr); + + Cache::deleteCacheWebsiteAttributes($idSite); + } + + /** + * Deletes the given session recording. + * + * When a session recording is deleted, any related recordings be no longer available in the API and tracked data + * for this session recording might be removed. + * + * @param int $idSite + * @param int $idSiteHsr The id of the session recording. + */ + public function deleteSessionRecording($idSite, $idSiteHsr) + { + + $this->validator->checkSessionReportWritePermission($idSite); + + $this->siteHsr->deactivateSessionRecording($idSite, $idSiteHsr); + } + + /** + * Ends / finishes the given session recording. + * + * When you end a session recording, the session recording reports will be still available via API and UI but no new + * session will be recorded anymore. + * + * @param int $idSite + * @param int $idSiteHsr The id of the session recording. + */ + public function endSessionRecording($idSite, $idSiteHsr) + { + $this->validator->checkSessionReportWritePermission($idSite); + $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr); + + $this->siteHsr->endSessionRecording($idSite, $idSiteHsr); + } + + /** + * Get all available heatmaps for a specific website or app. + * + * It will return active as well as ended heatmaps but not any deleted heatmaps. + * + * @param int $idSite + * @param bool|int $includePageTreeMirror set to 0 if you don't need the page tree mirror for heatmaps (improves performance) + * @return array + */ + public function getHeatmaps($idSite, $includePageTreeMirror = true) + { + $this->validator->checkHeatmapReportViewPermission($idSite); + + return $this->siteHsr->getHeatmaps($idSite, !empty($includePageTreeMirror)); + } + + /** + * Get all available session recordings for a specific website or app. + * + * It will return active as well as ended session recordings but not any deleted session recordings. + * + * @param int $idSite + * @return array + */ + public function getSessionRecordings($idSite) + { + $this->validator->checkSessionReportViewPermission($idSite); + + return $this->siteHsr->getSessionRecordings($idSite); + } + + /** + * Returns all page views that were recorded during a particular session / visit. We do not apply segments as it is + * used for video player when replaying sessions etc. + * + * @param int $idSite + * @param int $idSiteHsr The id of a session recording + * @param int $idVisit The visit / session id + * @return array + */ + private function getRecordedPageViewsInSession($idSite, $idSiteHsr, $idVisit, $period, $date) + { + $timezone = Site::getTimezoneFor($idSite); + + // ideally we would also check if idSiteHsr is actually linked to idLogHsr but not really needed for security reasons + $pageviews = $this->aggregator->getRecordedPageViewsInSession($idSite, $idSiteHsr, $idVisit, $period, $date, $segment = false); + + $isAnonymous = Piwik::isUserIsAnonymous(); + + foreach ($pageviews as &$pageview) { + $pageview['server_time_pretty'] = Date::factory($pageview['server_time'], $timezone)->getLocalized(DateTimeFormatProvider::DATETIME_FORMAT_SHORT); + + if ($isAnonymous) { + unset($pageview['idvisitor']); + } else { + $pageview['idvisitor'] = bin2hex($pageview['idvisitor']); + } + + $formatter = new Formatter(); + $pageview['time_on_page_pretty'] = $formatter->getPrettyTimeFromSeconds(intval($pageview['time_on_page'] / 1000), $asSentence = true); + } + + return $pageviews; + } + + /** + * Returns all recorded sessions for a specific session recording. + * + * To get the actual recorded data for any of the recorded sessions, call {@link getRecordedSession()}. + * + * @param int $idSite + * @param string $period + * @param string $date + * @param int $idSiteHsr The id of the session recording you want to retrieve all the recorded sessions for. + * @param bool $segment + * @param int $idSubtable Optional visit id if you want to get all recorded pageviews of a specific visitor + * @return DataTable + */ + public function getRecordedSessions($idSite, $period, $date, $idSiteHsr, $segment = false, $idSubtable = false) + { + $this->validator->checkSessionReportViewPermission($idSite); + $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr); + + $idVisit = $idSubtable; + + try { + PeriodFactory::checkPeriodIsEnabled($period); + } catch (\Exception $e) { + throw new Exception(Piwik::translate('HeatmapSessionRecording_PeriodDisabledErrorMessage', $period)); + } + + if (!empty($idVisit)) { + $recordings = $this->aggregator->getRecordedPageViewsInSession($idSite, $idSiteHsr, $idVisit, $period, $date, $segment); + } else { + $recordings = $this->aggregator->getRecordedSessions($idSite, $idSiteHsr, $period, $date, $segment); + } + + $table = new DataTable(); + $table->disableFilter('AddColumnsProcessedMetrics'); + $table->setMetadata('idSiteHsr', $idSiteHsr); + + if (!empty($recordings)) { + $table->addRowsFromSimpleArray($recordings); + } + + if (empty($idVisit)) { + $table->queueFilter(function (DataTable $table) { + foreach ($table->getRowsWithoutSummaryRow() as $row) { + if ($idVisit = $row->getColumn('idvisit')) { + $row->setNonLoadedSubtableId($idVisit); + } + } + }); + } else { + $table->disableFilter('Sort'); + } + + if (!method_exists(SettingsServer::class, 'isMatomoForWordPress') || !SettingsServer::isMatomoForWordPress()) { + $table->queueFilter(function (DataTable $table) use ($idSite, $idSiteHsr, $period, $date) { + foreach ($table->getRowsWithoutSummaryRow() as $row) { + $idLogHsr = $row->getColumn('idloghsr'); + $row->setMetadata('sessionReplayUrl', SiteHsrModel::completeWidgetUrl('replayRecording', 'idSiteHsr=' . (int) $idSiteHsr . '&idLogHsr=' . (int) $idLogHsr, $idSite, $period, $date)); + } + }); + } + + $table->filter('Piwik\Plugins\HeatmapSessionRecording\DataTable\Filter\EnrichRecordedSessions'); + + return $table; + } + + /** + * Get all activities of a specific recorded session. + * + * This includes events such as clicks, mouse moves, scrolls, resizes, page / HTML DOM changes, form changed. + * It is recommended to call this API method with filter_limit = -1 to retrieve all results. It also returns + * metadata like the viewport size the user had when it was recorded, the browser, operating system, and more. + * + * To see what each event type in the events property means, call {@link getEventTypes()}. + * + * @param int $idSite + * @param int $idSiteHsr The id of the session recording you want to retrieve the data for. + * @param int $idLogHsr The id of the recorded session you want to retrieve the data for. + * @return array + * @throws Exception + */ + public function getRecordedSession($idSite, $idSiteHsr, $idLogHsr) + { + $this->validator->checkSessionReportViewPermission($idSite); + $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr); + + // ideally we would also check if idSiteHsr is actually linked to idLogHsr but not really needed for security reasons + $session = $this->aggregator->getRecordedSession($idLogHsr); + + if (empty($session['idsite']) || empty($idSite)) { + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorSessionRecordingDoesNotExist')); + } + + if ($session['idsite'] != $idSite) { + // important otherwise can fetch any log entry! + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorSessionRecordingDoesNotExist')); + } + + $session['idvisitor'] = !empty($session['idvisitor']) ? bin2hex($session['idvisitor']) : ''; + + if (Piwik::isUserIsAnonymous()) { + foreach (EnrichRecordedSessions::getBlockedFields() as $blockedField) { + if (isset($session[$blockedField])) { + $session[$blockedField] = null; + } + } + } + + + $configBrowserName = !empty($session['config_browser_name']) ? $session['config_browser_name'] : ''; + $session['browser_name'] = \Piwik\Plugins\DevicesDetection\getBrowserName($configBrowserName); + $session['browser_logo'] = \Piwik\Plugins\DevicesDetection\getBrowserLogo($configBrowserName); + $configOs = !empty($session['config_os']) ? $session['config_os'] : ''; + $session['os_name'] = \Piwik\Plugins\DevicesDetection\getOsFullName($configOs); + $session['os_logo'] = \Piwik\Plugins\DevicesDetection\getOsLogo($configOs); + $session['device_name'] = \Piwik\Plugins\DevicesDetection\getDeviceTypeLabel($session['config_device_type']); + $session['device_logo'] = \Piwik\Plugins\DevicesDetection\getDeviceTypeLogo($session['config_device_type']); + + if (!empty($session['config_device_model'])) { + $session['device_name'] .= ', ' . $session['config_device_model']; + } + + $session['location_name'] = ''; + $session['location_logo'] = ''; + + if (!empty($session['location_country'])) { + $session['location_name'] = \Piwik\Plugins\UserCountry\countryTranslate($session['location_country']); + $session['location_logo'] = \Piwik\Plugins\UserCountry\getFlagFromCode($session['location_country']); + + if (!empty($session['location_region']) && $session['location_region'] != Visit::UNKNOWN_CODE) { + $session['location_name'] .= ', ' . \Piwik\Plugins\UserCountry\getRegionNameFromCodes($session['location_country'], $session['location_region']); + } + + if (!empty($session['location_city'])) { + $session['location_name'] .= ', ' . $session['location_city']; + } + } + + $timezone = Site::getTimezoneFor($idSite); + $session['server_time_pretty'] = Date::factory($session['server_time'], $timezone)->getLocalized(DateTimeFormatProvider::DATETIME_FORMAT_SHORT); + + $formatter = new Formatter(); + $session['time_on_page_pretty'] = $formatter->getPrettyTimeFromSeconds(intval($session['time_on_page'] / 1000), $asSentence = true); + + // we make sure to get all recorded pageviews in this session + $serverTime = Date::factory($session['server_time']); + $from = $serverTime->subDay(1)->toString(); + $to = $serverTime->addDay(1)->toString(); + + $period = 'range'; + $dateRange = $from . ',' . $to; + + $session['events'] = $this->logEvent->getEventsForPageview($idLogHsr); + $session['pageviews'] = $this->getRecordedPageViewsInSession($idSite, $idSiteHsr, $session['idvisit'], $period, $dateRange); + $session['numPageviews'] = count($session['pageviews']); + + return $session; + } + + /** + * Deletes all recorded page views within a recorded session. + * + * Once a recorded session has been deleted, the replay video will no longer be available in the UI and no data + * can be retrieved anymore via the API. + * + * @param int $idSite + * @param int $idSiteHsr The id of the session recording you want to delete the data. + * @param int $idVisit The visitId of the recorded session you want to delete. + */ + public function deleteRecordedSession($idSite, $idSiteHsr, $idVisit) + { + $this->validator->checkSessionReportWritePermission($idSite); + // make sure the recording actually belongs to that site, otherwise could delete any recording for any other site + $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr); + + // we also need to make sure the visit actually belongs to that site + $idLogHsrs = $this->logHsr->findLogHsrIdsInVisit($idSite, $idVisit); + + foreach ($idLogHsrs as $idLogHsr) { + $this->logHsrSite->unlinkRecord($idLogHsr, $idSiteHsr); + } + } + + /** + * Deletes an individual page view within a recorded session. + * + * It only deletes one recorded session of one page view, not all recorded sessions. + * Once a recorded page view has been deleted, the replay video will no longer be available in the UI and no data + * can be retrieved anymore via the API for this page view. + * + * @param int $idSite + * @param int $idSiteHsr The id of the session recording you want to delete the data. + * @param int $idLogHsr The id of the recorded session you want to delete. + */ + public function deleteRecordedPageview($idSite, $idSiteHsr, $idLogHsr) + { + $this->validator->checkWritePermission($idSite); + // make sure the recording actually belongs to that site, otherwise could delete any recording for any other site + $this->siteHsr->checkSessionRecordingExists($idSite, $idSiteHsr); + + $this->logHsrSite->unlinkRecord($idLogHsr, $idSiteHsr); + } + + /** + * Get metadata for a specific heatmap like the number of samples / pageviews that were recorded or the + * average above the fold per device type. + * + * @param int $idSite + * @param string $period + * @param string $date + * @param int $idSiteHsr The id of the heatmap you want to retrieve the meta data for. + * @param bool|string $segment + * @return array + */ + public function getRecordedHeatmapMetadata($idSite, $period, $date, $idSiteHsr, $segment = false) + { + $this->validator->checkHeatmapReportViewPermission($idSite); + $this->siteHsr->checkHeatmapExists($idSite, $idSiteHsr); + + $samples = $this->aggregator->getRecordedHeatmapMetadata($idSiteHsr, $idSite, $period, $date, $segment); + + $result = array('nb_samples_device_all' => 0); + + foreach ($samples as $sample) { + $result['nb_samples_device_' . $sample['device_type']] = $sample['value']; + $result['avg_fold_device_' . $sample['device_type']] = round(($sample['avg_fold'] / LogHsr::SCROLL_ACCURACY) * 100, 1); + $result['nb_samples_device_all'] += $sample['value']; + } + + return $result; + } + + /** + * Get all activities of a heatmap. + * + * For example retrieve all mouse movements made by desktop visitors, or all clicks made my tablet visitors, or + * all scrolls by mobile users. It is recommended to call this method with filter_limit = -1 to retrieve all + * results. As there can be many results, you may want to call this method several times using filter_limit and + * filter_offset. + * + * @param int $idSite + * @param string $period + * @param string $date + * @param int $idSiteHsr The id of the heatmap you want to retrieve the data for. + * @param int $heatmapType To see which heatmap types can be used, call {@link getAvailableHeatmapTypes()} + * @param int $deviceType To see which device types can be used, call {@link getAvailableDeviceTypes()} + * @param bool|string $segment + * @return array + */ + public function getRecordedHeatmap($idSite, $period, $date, $idSiteHsr, $heatmapType, $deviceType, $segment = false) + { + $this->validator->checkHeatmapReportViewPermission($idSite); + $this->siteHsr->checkHeatmapExists($idSite, $idSiteHsr); + + if ($heatmapType == RequestProcessor::EVENT_TYPE_SCROLL) { + $heatmap = $this->aggregator->aggregateScrollHeatmap($idSiteHsr, $deviceType, $idSite, $period, $date, $segment); + } else { + $heatmap = $this->aggregator->aggregateHeatmap($idSiteHsr, $heatmapType, $deviceType, $idSite, $period, $date, $segment); + } + + // we do not return dataTable here as it doubles the time it takes to call this method (eg 4s vs 7s when heaps of data) + // datatable is not really needed here as we don't want to sort it or so + return $heatmap; + } + + /** + * @param $idSite + * @param $idSiteHsr + * @param $idLogHsr + * @return array + * @hide + */ + public function getEmbedSessionInfo($idSite, $idSiteHsr, $idLogHsr) + { + $this->validator->checkSessionReportViewPermission($idSite); + + $aggregator = new Aggregator(); + return $aggregator->getEmbedSessionInfo($idSite, $idSiteHsr, $idLogHsr); + } + + /** + * Tests, checks whether the given URL matches the given page rules. + * + * This can be used before configuring a heatmap or session recording to make sure the configured target page(s) + * will match a specific URL. + * + * @param string $url + * @param array $matchPageRules + * @return array + * @throws Exception + */ + public function testUrlMatchPages($url, $matchPageRules = array()) + { + $this->validator->checkHasSomeWritePermission(); + + if ($url === '' || $url === false || $url === null) { + return array('url' => '', 'matches' => false); + } + + if (!empty($matchPageRules) && !is_array($matchPageRules)) { + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorNotAnArray', 'matchPageRules')); + } + + $url = Common::unsanitizeInputValue($url); + + if (!empty($matchPageRules)) { + $pageRules = new PageRules($matchPageRules, '', $needsOneEntry = false); + $pageRules->check(); + } + + $matchPageRules = $this->unsanitizePageRules($matchPageRules); + + $allMatch = HsrMatcher::matchesAllPageRules($matchPageRules, $url); + + return array('url' => $url, 'matches' => $allMatch); + } + + /** + * Get a list of valid heatmap and session recording statuses (eg "active", "ended") + * + * @return array + */ + public function getAvailableStatuses() + { + $this->validator->checkHasSomeWritePermission(); + + return array( + array('value' => SiteHsrDao::STATUS_ACTIVE, 'name' => Piwik::translate('HeatmapSessionRecording_StatusActive')), + array('value' => SiteHsrDao::STATUS_ENDED, 'name' => Piwik::translate('HeatmapSessionRecording_StatusEnded')), + ); + } + + /** + * Get a list of all available target attributes and target types for "pageTargets" / "page rules". + * + * For example URL, URL Parameter, Path, simple comparison, contains, starts with, and more. + * + * @return array + */ + public function getAvailableTargetPageRules() + { + $this->validator->checkHasSomeWritePermission(); + + return PageRuleMatcher::getAvailableTargetTypes(); + } + + /** + * Get a list of available device types that can be used when fetching a heatmap report. + * + * For example desktop, tablet, mobile. + * + * @return array + */ + public function getAvailableDeviceTypes() + { + Piwik::checkUserHasSomeViewAccess(); + + return array( + array('name' => Piwik::translate('General_Desktop'), + 'key' => LogHsr::DEVICE_TYPE_DESKTOP, + 'logo' => 'plugins/Morpheus/icons/dist/devices/desktop.png'), + array('name' => Piwik::translate('DevicesDetection_Tablet'), + 'key' => LogHsr::DEVICE_TYPE_TABLET, + 'logo' => 'plugins/Morpheus/icons/dist/devices/tablet.png'), + array('name' => Piwik::translate('General_Mobile'), + 'key' => LogHsr::DEVICE_TYPE_MOBILE, + 'logo' => 'plugins/Morpheus/icons/dist/devices/smartphone.png'), + ); + } + + /** + * Get a list of available heatmap types that can be used when fetching a heatmap report. + * + * For example click, mouse move, scroll. + * + * @return array + */ + public function getAvailableHeatmapTypes() + { + Piwik::checkUserHasSomeViewAccess(); + + return array( + array( + 'name' => Piwik::translate('HeatmapSessionRecording_ActivityClick'), + 'key' => RequestProcessor::EVENT_TYPE_CLICK), + array( + 'name' => Piwik::translate('HeatmapSessionRecording_ActivityMove'), + 'key' => RequestProcessor::EVENT_TYPE_MOVEMENT), + array( + 'name' => Piwik::translate('HeatmapSessionRecording_ActivityScroll'), + 'key' => RequestProcessor::EVENT_TYPE_SCROLL), + ); + } + + /** + * Get a list of available session recording sample limits. + * + * Note: This is only a suggested list of sample limits that should be shown in the UI when creating or editing a + * session recording. When you configure a session recording via the API directly, any limit can be used. + * + * For example 50, 100, 200, 500 + * + * @return array + */ + public function getAvailableSessionRecordingSampleLimits() + { + $this->validator->checkHasSomeWritePermission(); + $this->validator->checkSessionRecordingEnabled(); + + return $this->configuration->getSessionRecordingSampleLimits(); + } + + /** + * Get a list of available event types that may be returned eg when fetching a recorded session. + * + * @return array + */ + public function getEventTypes() + { + Piwik::checkUserHasSomeViewAccess(); + + return array( + array( + 'name' => Piwik::translate('HeatmapSessionRecording_ActivityMove'), + 'key' => RequestProcessor::EVENT_TYPE_MOVEMENT), + array( + 'name' => Piwik::translate('HeatmapSessionRecording_ActivityClick'), + 'key' => RequestProcessor::EVENT_TYPE_CLICK), + array( + 'name' => Piwik::translate('HeatmapSessionRecording_ActivityScroll'), + 'key' => RequestProcessor::EVENT_TYPE_SCROLL), + array( + 'name' => Piwik::translate('HeatmapSessionRecording_ActivityResize'), + 'key' => RequestProcessor::EVENT_TYPE_RESIZE), + array( + 'name' => Piwik::translate('HeatmapSessionRecording_ActivityInitialDom'), + 'key' => RequestProcessor::EVENT_TYPE_INITIAL_DOM), + array( + 'name' => Piwik::translate('HeatmapSessionRecording_ActivityPageChange'), + 'key' => RequestProcessor::EVENT_TYPE_MUTATION), + array( + 'name' => Piwik::translate('HeatmapSessionRecording_ActivityFormText'), + 'key' => RequestProcessor::EVENT_TYPE_FORM_TEXT), + array( + 'name' => Piwik::translate('HeatmapSessionRecording_ActivityFormValue'), + 'key' => RequestProcessor::EVENT_TYPE_FORM_VALUE), + array( + 'name' => Piwik::translate('HeatmapSessionRecording_ActivityScrollElement'), + 'key' => RequestProcessor::EVENT_TYPE_SCROLL_ELEMENT), + ); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Actions/ActionHsr.php b/files/plugin-HeatmapSessionRecording-5.2.4/Actions/ActionHsr.php new file mode 100644 index 0000000..8eb7998 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Actions/ActionHsr.php @@ -0,0 +1,61 @@ +getParam('url'); + + $this->setActionUrl($url); + } + + public static function shouldHandle(Request $request) + { + $params = $request->getParams(); + $isHsrRequest = Common::getRequestVar(RequestProcessor::TRACKING_PARAM_HSR_ID_VIEW, '', 'string', $params); + + return !empty($isHsrRequest); + } + + protected function getActionsToLookup() + { + return array(); + } + + // 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; + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Activity/BaseActivity.php b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/BaseActivity.php new file mode 100644 index 0000000..daac776 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/BaseActivity.php @@ -0,0 +1,114 @@ + $this->getSiteData($idSite), + 'version' => 'v1', + 'hsr' => $this->getHsrData($idSiteHsr, $idSite), + ); + } + + private function getSiteData($idSite) + { + return array( + 'site_id' => $idSite, + 'site_name' => Site::getNameFor($idSite) + ); + } + + private function getHsrData($idSiteHsr, $idSite) + { + $dao = $this->getDao(); + $hsr = $dao->getRecord($idSite, $idSiteHsr, SiteHsrDao::RECORD_TYPE_HEATMAP); + if (empty($hsr)) { + // maybe it is a session? we could make this faster by adding a new method to DAO that returns hsr independent of type + $hsr = $dao->getRecord($idSite, $idSiteHsr, SiteHsrDao::RECORD_TYPE_SESSION); + } + + $hsrName = ''; + if (!empty($hsr['name'])) { + // hsr name might not be set when we are handling deleteExperiment activity + $hsrName = $hsr['name']; + } + + return array( + 'id' => $idSiteHsr, + 'name' => $hsrName + ); + } + + public function getPerformingUser($eventData = null) + { + $login = Piwik::getCurrentUserLogin(); + + if ($login === self::USER_ANONYMOUS || empty($login)) { + // anonymous cannot change an experiment, in this case the system changed it, eg during tracking it started + // an experiment + return self::USER_SYSTEM; + } + + return $login; + } + + private function getDao() + { + // we do not get it via DI as it would slow down creation of all activities on all requests. Instead only + // create instance when needed + return StaticContainer::get('Piwik\Plugins\HeatmapSessionRecording\Dao\SiteHsrDao'); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapAdded.php b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapAdded.php new file mode 100644 index 0000000..ad04966 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapAdded.php @@ -0,0 +1,41 @@ +formatActivityData($idSiteHsr, $idSite); + } + + public function getTranslatedDescription($activityData, $performingUser) + { + $siteName = $this->getSiteNameFromActivityData($activityData); + $hsrName = $this->getHsrNameFromActivityData($activityData); + + return Piwik::translate('HeatmapSessionRecording_HeatmapAddedActivity', [$hsrName, $siteName]); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapDeleted.php b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapDeleted.php new file mode 100644 index 0000000..fae6d25 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapDeleted.php @@ -0,0 +1,46 @@ +formatActivityData($idSiteHsr, $idSite); + } + + public function getTranslatedDescription($activityData, $performingUser) + { + $siteName = $this->getSiteNameFromActivityData($activityData); + $hsrName = $this->getHsrNameFromActivityData($activityData); + + return Piwik::translate('HeatmapSessionRecording_HeatmapDeletedActivity', [$hsrName, $siteName]); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapEnded.php b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapEnded.php new file mode 100644 index 0000000..28afe54 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapEnded.php @@ -0,0 +1,44 @@ +formatActivityData($idSiteHsr, $idSite); + } + + public function getTranslatedDescription($activityData, $performingUser) + { + $siteName = $this->getSiteNameFromActivityData($activityData); + $hsrName = $this->getHsrNameFromActivityData($activityData); + + return Piwik::translate('HeatmapSessionRecording_HeatmapEndedActivity', [$hsrName, $siteName]); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapPaused.php b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapPaused.php new file mode 100644 index 0000000..ddacfb5 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapPaused.php @@ -0,0 +1,46 @@ +formatActivityData($idSiteHsr, $idSite); + } + + public function getTranslatedDescription($activityData, $performingUser) + { + $siteName = $this->getSiteNameFromActivityData($activityData); + $hsrName = $this->getHsrNameFromActivityData($activityData); + + return Piwik::translate('HeatmapSessionRecording_HeatmapPausedActivity', [$hsrName, $siteName]); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapResumed.php b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapResumed.php new file mode 100644 index 0000000..9062306 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapResumed.php @@ -0,0 +1,46 @@ +formatActivityData($idSiteHsr, $idSite); + } + + public function getTranslatedDescription($activityData, $performingUser) + { + $siteName = $this->getSiteNameFromActivityData($activityData); + $hsrName = $this->getHsrNameFromActivityData($activityData); + + return Piwik::translate('HeatmapSessionRecording_HeatmapResumedActivity', [$hsrName, $siteName]); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapScreenshotDeleted.php b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapScreenshotDeleted.php new file mode 100644 index 0000000..4275d16 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapScreenshotDeleted.php @@ -0,0 +1,46 @@ +formatActivityData($idSiteHsr, $idSite); + } + + public function getTranslatedDescription($activityData, $performingUser) + { + $siteName = $this->getSiteNameFromActivityData($activityData); + $hsrName = $this->getHsrNameFromActivityData($activityData); + + return Piwik::translate('HeatmapSessionRecording_HeatmapScreenshotDeletedActivity', [$hsrName, $siteName]); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapUpdated.php b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapUpdated.php new file mode 100644 index 0000000..16b0636 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/HeatmapUpdated.php @@ -0,0 +1,42 @@ +formatActivityData($idSiteHsr, $idSite); + } + + public function getTranslatedDescription($activityData, $performingUser) + { + $siteName = $this->getSiteNameFromActivityData($activityData); + $hsrName = $this->getHsrNameFromActivityData($activityData); + + return Piwik::translate('HeatmapSessionRecording_HeatmapUpdatedActivity', [$hsrName, $siteName]); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Activity/RecordedPageviewDeleted.php b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/RecordedPageviewDeleted.php new file mode 100644 index 0000000..0c608b3 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/RecordedPageviewDeleted.php @@ -0,0 +1,46 @@ +formatActivityData($idSiteHsr, $idSite); + } + + public function getTranslatedDescription($activityData, $performingUser) + { + $siteName = $this->getSiteNameFromActivityData($activityData); + $hsrName = $this->getHsrNameFromActivityData($activityData); + + return Piwik::translate('HeatmapSessionRecording_RecordedPageviewDeletedActivity', [$hsrName, $siteName]); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Activity/RecordedSessionDeleted.php b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/RecordedSessionDeleted.php new file mode 100644 index 0000000..0eb581a --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/RecordedSessionDeleted.php @@ -0,0 +1,46 @@ +formatActivityData($idSiteHsr, $idSite); + } + + public function getTranslatedDescription($activityData, $performingUser) + { + $siteName = $this->getSiteNameFromActivityData($activityData); + $hsrName = $this->getHsrNameFromActivityData($activityData); + + return Piwik::translate('HeatmapSessionRecording_RecordedSessionDeletedActivity', [$hsrName, $siteName]); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Activity/SessionRecordingAdded.php b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/SessionRecordingAdded.php new file mode 100644 index 0000000..6178a0e --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/SessionRecordingAdded.php @@ -0,0 +1,41 @@ +formatActivityData($idSiteHsr, $idSite); + } + + public function getTranslatedDescription($activityData, $performingUser) + { + $siteName = $this->getSiteNameFromActivityData($activityData); + $hsrName = $this->getHsrNameFromActivityData($activityData); + + return Piwik::translate('HeatmapSessionRecording_SessionRecordingAddedActivity', [$hsrName, $siteName]); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Activity/SessionRecordingDeleted.php b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/SessionRecordingDeleted.php new file mode 100644 index 0000000..2f35f7f --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/SessionRecordingDeleted.php @@ -0,0 +1,46 @@ +formatActivityData($idSiteHsr, $idSite); + } + + public function getTranslatedDescription($activityData, $performingUser) + { + $siteName = $this->getSiteNameFromActivityData($activityData); + $hsrName = $this->getHsrNameFromActivityData($activityData); + + return Piwik::translate('HeatmapSessionRecording_SessionRecordingDeletedActivity', [$hsrName, $siteName]); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Activity/SessionRecordingEnded.php b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/SessionRecordingEnded.php new file mode 100644 index 0000000..dfd0df0 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/SessionRecordingEnded.php @@ -0,0 +1,44 @@ +formatActivityData($idSiteHsr, $idSite); + } + + public function getTranslatedDescription($activityData, $performingUser) + { + $siteName = $this->getSiteNameFromActivityData($activityData); + $hsrName = $this->getHsrNameFromActivityData($activityData); + + return Piwik::translate('HeatmapSessionRecording_SessionRecordingEndedActivity', [$hsrName, $siteName]); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Activity/SessionRecordingPaused.php b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/SessionRecordingPaused.php new file mode 100644 index 0000000..7b376fd --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/SessionRecordingPaused.php @@ -0,0 +1,46 @@ +formatActivityData($idSiteHsr, $idSite); + } + + public function getTranslatedDescription($activityData, $performingUser) + { + $siteName = $this->getSiteNameFromActivityData($activityData); + $hsrName = $this->getHsrNameFromActivityData($activityData); + + return Piwik::translate('HeatmapSessionRecording_SessionRecordingPausedActivity', [$hsrName, $siteName]); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Activity/SessionRecordingResumed.php b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/SessionRecordingResumed.php new file mode 100644 index 0000000..b89500d --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/SessionRecordingResumed.php @@ -0,0 +1,46 @@ +formatActivityData($idSiteHsr, $idSite); + } + + public function getTranslatedDescription($activityData, $performingUser) + { + $siteName = $this->getSiteNameFromActivityData($activityData); + $hsrName = $this->getHsrNameFromActivityData($activityData); + + return Piwik::translate('HeatmapSessionRecording_SessionRecordingResumedActivity', [$hsrName, $siteName]); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Activity/SessionRecordingUpdated.php b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/SessionRecordingUpdated.php new file mode 100644 index 0000000..fd35fa1 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Activity/SessionRecordingUpdated.php @@ -0,0 +1,42 @@ +formatActivityData($idSiteHsr, $idSite); + } + + public function getTranslatedDescription($activityData, $performingUser) + { + $siteName = $this->getSiteNameFromActivityData($activityData); + $hsrName = $this->getHsrNameFromActivityData($activityData); + + return Piwik::translate('HeatmapSessionRecording_SessionRecordingUpdatedActivity', [$hsrName, $siteName]); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Archiver/Aggregator.php b/files/plugin-HeatmapSessionRecording-5.2.4/Archiver/Aggregator.php new file mode 100644 index 0000000..bc15cef --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Archiver/Aggregator.php @@ -0,0 +1,476 @@ +forceSleepInQuery) { + $extraWhere = 'SLEEP(1) AND'; + } + + $query = sprintf( + 'SELECT /* HeatmapSessionRecording.findRecording */ hsrsite.idsitehsr, + min(hsr.idloghsr) as idloghsr + FROM %s hsr + LEFT JOIN %s hsrsite ON hsr.idloghsr = hsrsite.idloghsr + LEFT JOIN %s hsrevent ON hsrevent.idloghsr = hsr.idloghsr and hsrevent.event_type = %s + LEFT JOIN %s sitehsr ON hsrsite.idsitehsr = sitehsr.idsitehsr + WHERE %s hsr.idvisit = ? and sitehsr.record_type = ? and hsrevent.idhsrblob is not null and hsrsite.idsitehsr is not null + GROUP BY hsrsite.idsitehsr + LIMIT 1', + Common::prefixTable('log_hsr'), + Common::prefixTable('log_hsr_site'), + Common::prefixTable('log_hsr_event'), + RequestProcessor::EVENT_TYPE_INITIAL_DOM, + Common::prefixTable('site_hsr'), + $extraWhere + ); + + $readerDb = $this->getDbReader(); + $query = DbHelper::addMaxExecutionTimeHintToQuery($query, $this->getLiveQueryMaxExecutionTime()); + + try { + return $readerDb->fetchRow($query, array($idVisit, SiteHsrDao::RECORD_TYPE_SESSION)); + } catch (\Exception $e) { + Model::handleMaxExecutionTimeError($readerDb, $e, '', Date::now(), Date::now(), null, 0, ['sql' => $query]); + throw $e; + } + } + + private function getDbReader() + { + if (method_exists(Db::class, 'getReader')) { + return Db::getReader(); + } else { + return Db::get(); + } + } + + public function findRecordings($visitIds) + { + if (empty($visitIds)) { + return array(); + } + + $visitIds = array_map('intval', $visitIds); + + $extraWhere = ''; + if ($this->forceSleepInQuery) { + $extraWhere = 'SLEEP(1) AND'; + } + + $query = sprintf( + 'SELECT /* HeatmapSessionRecording.findRecordings */ hsrsite.idsitehsr, + min(hsr.idloghsr) as idloghsr, + hsr.idvisit + FROM %s hsr + LEFT JOIN %s hsrsite ON hsr.idloghsr = hsrsite.idloghsr + LEFT JOIN %s hsrevent ON hsrevent.idloghsr = hsr.idloghsr and hsrevent.event_type = %s + LEFT JOIN %s sitehsr ON hsrsite.idsitehsr = sitehsr.idsitehsr + WHERE %s hsr.idvisit IN ("%s") and sitehsr.record_type = ? and hsrevent.idhsrblob is not null and hsrsite.idsitehsr is not null + GROUP BY hsr.idvisit, hsrsite.idsitehsr', + Common::prefixTable('log_hsr'), + Common::prefixTable('log_hsr_site'), + Common::prefixTable('log_hsr_event'), + RequestProcessor::EVENT_TYPE_INITIAL_DOM, + Common::prefixTable('site_hsr'), + $extraWhere, + implode('","', $visitIds) + ); + + $readerDb = $this->getDbReader(); + $query = DbHelper::addMaxExecutionTimeHintToQuery($query, $this->getLiveQueryMaxExecutionTime()); + + try { + return $readerDb->fetchAll($query, array(SiteHsrDao::RECORD_TYPE_SESSION)); + } catch (\Exception $e) { + Model::handleMaxExecutionTimeError($readerDb, $e, '', Date::now(), Date::now(), null, 0, ['sql' => $query]); + throw $e; + } + } + + private function getLiveQueryMaxExecutionTime() + { + return Config::getInstance()->General['live_query_max_execution_time']; + } + + public function getEmbedSessionInfo($idSite, $idSiteHsr, $idLogHsr) + { + $logHsr = Common::prefixTable('log_hsr'); + $logHsrSite = Common::prefixTable('log_hsr_site'); + $logAction = Common::prefixTable('log_action'); + $logEvent = Common::prefixTable('log_hsr_event'); + $logBlob = Common::prefixTable('log_hsr_blob'); + + $query = sprintf( + 'SELECT laction.name as base_url, + laction.url_prefix, hsrblob.`value` as initial_mutation, hsrblob.compressed + FROM %s hsr + LEFT JOIN %s laction ON laction.idaction = hsr.idaction_url + LEFT JOIN %s hsr_site ON hsr_site.idloghsr = hsr.idloghsr + LEFT JOIN %s hsrevent ON hsrevent.idloghsr = hsr.idloghsr and hsrevent.event_type = %s + LEFT JOIN %s hsrblob ON hsrevent.idhsrblob = hsrblob.idhsrblob + WHERE hsr.idloghsr = ? and hsr.idsite = ? and hsr_site.idsitehsr = ? + and hsrevent.idhsrblob is not null and `hsrblob`.`value` is not null + LIMIT 1', + $logHsr, + $logAction, + $logHsrSite, + $logEvent, + RequestProcessor::EVENT_TYPE_INITIAL_DOM, + $logBlob + ); + + $row = $this->getDbReader()->fetchRow($query, array($idLogHsr, $idSite, $idSiteHsr)); + + if (!empty($row['compressed'])) { + $row['initial_mutation'] = gzuncompress($row['initial_mutation']); + } + + return $row; + } + + public function getRecordedSession($idLogHsr) + { + $select = 'log_action.name as url, + log_visit.idvisit, + log_visit.idvisitor, + log_hsr.idsite, + log_visit.location_country, + log_visit.location_region, + log_visit.location_city, + log_visit.config_os, + log_visit.config_device_type, + log_visit.config_device_model, + log_visit.config_browser_name, + log_hsr.time_on_page, + log_hsr.server_time, + log_hsr.viewport_w_px, + log_hsr.viewport_h_px, + log_hsr.scroll_y_max_relative, + log_hsr.fold_y_relative'; + + $logHsr = Common::prefixTable('log_hsr'); + $logVisit = Common::prefixTable('log_visit'); + $logAction = Common::prefixTable('log_action'); + + $query = sprintf('SELECT %s + FROM %s log_hsr + LEFT JOIN %s log_visit ON log_hsr.idvisit = log_visit.idvisit + LEFT JOIN %s log_action ON log_action.idaction = log_hsr.idaction_url + WHERE log_hsr.idloghsr = ?', $select, $logHsr, $logVisit, $logAction); + + return $this->getDbReader()->fetchRow($query, array($idLogHsr)); + } + + public function getRecordedSessions($idSite, $idSiteHsr, $period, $date, $segment) + { + $period = Period\Factory::build($period, $date); + $segment = new Segment($segment, array($idSite)); + $site = new Site($idSite); + + $from = array( + 'log_hsr', + array( + 'table' => 'log_hsr_site', + 'joinOn' => 'log_hsr_site.idloghsr = log_hsr.idloghsr' + ), + array( + 'table' => 'log_visit', + 'joinOn' => 'log_visit.idvisit = log_hsr.idvisit' + ), + array( + 'table' => 'log_action', + 'joinOn' => 'log_action.idaction = log_hsr.idaction_url' + ), + array( + 'table' => 'log_hsr_event', + 'joinOn' => 'log_hsr_event.idloghsr = log_hsr.idloghsr and log_hsr_event.event_type = ' . RequestProcessor::EVENT_TYPE_INITIAL_DOM + ) + ); + + // we need to make sure to show only sessions that have an initial mutation with time_since_load = 0, otherwise + // the recording won't work. + $logHsrEventTable = Common::prefixTable('log_hsr_event'); + + $actionQuery = sprintf('SELECT count(*) FROM %1$s as hsr_ev + WHERE hsr_ev.idloghsr = log_hsr_site.idloghsr and hsr_ev.event_type not in (%2$s, %3$s)', $logHsrEventTable, RequestProcessor::EVENT_TYPE_CSS, RequestProcessor::EVENT_TYPE_INITIAL_DOM); + + $select = 'log_hsr.idvisit as label, + count(*) as nb_pageviews, + log_hsr.idvisit, + SUBSTRING_INDEX(GROUP_CONCAT(CAST(log_action.name AS CHAR) ORDER BY log_hsr.server_time ASC SEPARATOR \'##\'), \'##\', 1) as first_url, + SUBSTRING_INDEX(GROUP_CONCAT(CAST(log_action.name AS CHAR) ORDER BY log_hsr.server_time DESC SEPARATOR \'##\'), \'##\', 1) as last_url, + sum(log_hsr.time_on_page) as time_on_site, + (' . $actionQuery . ') as total_events, + min(log_hsr_site.idloghsr) as idloghsr, + log_visit.idvisitor, + log_visit.location_country, + log_visit.location_region, + log_visit.location_city, + log_visit.config_os, + log_visit.config_device_type, + log_visit.config_device_model, + log_visit.config_browser_name, + min(log_hsr.server_time) as server_time'; + + $params = new ArchiveProcessor\Parameters($site, $period, $segment); + $logAggregator = new LogAggregator($params); + + $where = $logAggregator->getWhereStatement('log_hsr', 'server_time'); + $where .= sprintf(" and log_hsr_site.idsitehsr = %d and log_hsr_event.idhsrblob is not null", (int) $idSiteHsr); + $groupBy = 'log_hsr.idvisit'; + $orderBy = 'log_hsr.server_time DESC'; + + $revertSubselect = $this->applyForceSubselect($segment, 'log_hsr.idvisit,log_hsr_site.idloghsr'); + + $query = $logAggregator->generateQuery($select, $from, $where, $groupBy, $orderBy); + + if (!empty($revertSubselect) && is_callable($revertSubselect)) { + call_user_func($revertSubselect); + } + + $dbReader = $this->getDbReader(); + $query['sql'] = DbHelper::addMaxExecutionTimeHintToQuery($query['sql'], $this->getLiveQueryMaxExecutionTime()); + + try { + return $dbReader->fetchAll($query['sql'], $query['bind']); + } catch (\Exception $e) { + Model::handleMaxExecutionTimeError($dbReader, $e, '', Date::now(), Date::now(), null, 0, $query); + throw $e; + } + } + + private function applyForceSubselect($segment, $subselectForced) + { + // for performance reasons we use this and not `LogAggregator->allowUsageSegmentCache()` + // That's because this is a LIVE query and not archived... and HSR tables usually have few entries < 5000 + // so segmentation should be fairly fast using this method compared to allowUsageSegmentCache + // which would query the entire log_visit over several days with the applied query and then create the temp table + // and only then apply the log_hsr query. + // it should be a lot faster this way + if (class_exists('Piwik\DataAccess\LogQueryBuilder') && !$segment->isEmpty()) { + $logQueryBuilder = StaticContainer::get('Piwik\DataAccess\LogQueryBuilder'); + if ( + method_exists($logQueryBuilder, 'getForcedInnerGroupBySubselect') && + method_exists($logQueryBuilder, 'forceInnerGroupBySubselect') + ) { + $forceGroupByBackup = $logQueryBuilder->getForcedInnerGroupBySubselect(); + $logQueryBuilder->forceInnerGroupBySubselect($subselectForced); + + return function () use ($forceGroupByBackup, $logQueryBuilder) { + $logQueryBuilder->forceInnerGroupBySubselect($forceGroupByBackup); + }; + } + } + } + + public function getRecordedPageViewsInSession($idSite, $idSiteHsr, $idVisit, $period, $date, $segment) + { + $period = Period\Factory::build($period, $date); + $segment = new Segment($segment, array($idSite)); + $site = new Site($idSite); + + $from = array( + 'log_hsr', + array( + 'table' => 'log_hsr_site', + 'joinOn' => 'log_hsr_site.idloghsr = log_hsr.idloghsr' + ), + array( + 'table' => 'log_visit', + 'joinOn' => 'log_visit.idvisit = log_hsr.idvisit' + ), + array( + 'table' => 'log_action', + 'joinOn' => 'log_action.idaction = log_hsr.idaction_url' + ), + array( + 'table' => 'log_hsr_event', + 'joinOn' => 'log_hsr_event.idloghsr = log_hsr.idloghsr and log_hsr_event.event_type = ' . RequestProcessor::EVENT_TYPE_INITIAL_DOM + ) + ); + + // we need to make sure to show only sessions that have an initial mutation with time_since_load = 0, otherwise + // the recording won't work. If this happens often, we might "end / finish" a configured session recording + // earlier since we have eg recorded 1000 sessions, but user sees only 950 which will be confusing but we can + // for now not take this into consideration during tracking when we get number of available samples only using + // log_hsr_site to detect if the number of configured sessions have been reached. ideally we would at some point + // also make sure to include this check there but will be slower. + + $select = 'log_action.name as label, + log_visit.idvisitor, + log_hsr_site.idloghsr, + log_hsr.time_on_page as time_on_page, + CONCAT(log_hsr.viewport_w_px, "x", log_hsr.viewport_h_px) as resolution, + log_hsr.server_time, + log_hsr.scroll_y_max_relative, + log_hsr.fold_y_relative'; + + $params = new ArchiveProcessor\Parameters($site, $period, $segment); + $logAggregator = new LogAggregator($params); + + $where = $logAggregator->getWhereStatement('log_hsr', 'server_time'); + $where .= sprintf(" and log_hsr_site.idsitehsr = %d and log_hsr.idvisit = %d and log_hsr_event.idhsrblob is not null ", (int) $idSiteHsr, (int) $idVisit); + $groupBy = ''; + $orderBy = 'log_hsr.server_time ASC'; + + $revertSubselect = $this->applyForceSubselect($segment, 'log_hsr.idvisit,log_hsr_site.idloghsr'); + + $query = $logAggregator->generateQuery($select, $from, $where, $groupBy, $orderBy); + + if (!empty($revertSubselect) && is_callable($revertSubselect)) { + call_user_func($revertSubselect); + } + + return $this->getDbReader()->fetchAll($query['sql'], $query['bind']); + } + + public function aggregateHeatmap($idSiteHsr, $heatmapType, $deviceType, $idSite, $period, $date, $segment) + { + $heatmapTypeWhere = ''; + if ($heatmapType == RequestProcessor::EVENT_TYPE_CLICK) { + $heatmapTypeWhere .= 'log_hsr_event.event_type = ' . (int) $heatmapType; + } elseif ($heatmapType == RequestProcessor::EVENT_TYPE_MOVEMENT) { + $heatmapTypeWhere .= 'log_hsr_event.event_type IN(' . (int) RequestProcessor::EVENT_TYPE_MOVEMENT . ',' . (int) RequestProcessor::EVENT_TYPE_CLICK . ')'; + } else { + throw new \Exception('Heatmap type not supported'); + } + + $period = Period\Factory::build($period, $date); + $segment = new Segment($segment, array($idSite)); + $site = new Site($idSite); + + $from = array( + 'log_hsr', + array( + 'table' => 'log_hsr_site', + 'joinOn' => 'log_hsr_site.idloghsr = log_hsr.idloghsr' + ), + array( + 'table' => 'log_hsr_event', + 'joinOn' => 'log_hsr_site.idloghsr = log_hsr_event.idloghsr' + ), + array( + 'table' => 'log_action', + 'joinOn' => 'log_action.idaction = log_hsr_event.idselector' + ) + ); + + $select = 'log_action.name as selector, + log_hsr_event.x as offset_x, + log_hsr_event.y as offset_y, + count(*) as value'; + + $params = new ArchiveProcessor\Parameters($site, $period, $segment); + $logAggregator = new LogAggregator($params); + + $where = $logAggregator->getWhereStatement('log_hsr', 'server_time'); + $where .= ' and log_hsr_site.idsitehsr = ' . (int) $idSiteHsr . ' and log_hsr_event.idselector is not null and ' . $heatmapTypeWhere; + $where .= ' and log_hsr.device_type = ' . (int) $deviceType; + + $groupBy = 'log_hsr_event.idselector, log_hsr_event.x, log_hsr_event.y'; + $orderBy = ''; + + $query = $logAggregator->generateQuery($select, $from, $where, $groupBy, $orderBy); + + return $this->getDbReader()->fetchAll($query['sql'], $query['bind']); + } + + public function getRecordedHeatmapMetadata($idSiteHsr, $idSite, $period, $date, $segment) + { + $period = Period\Factory::build($period, $date); + $segment = new Segment($segment, array($idSite)); + $site = new Site($idSite); + + $from = array( + 'log_hsr', + array( + 'table' => 'log_hsr_site', + 'joinOn' => 'log_hsr_site.idloghsr = log_hsr.idloghsr' + ) + ); + + $select = 'log_hsr.device_type, count(*) as value, avg(log_hsr.fold_y_relative) as avg_fold'; + + $params = new ArchiveProcessor\Parameters($site, $period, $segment); + $logAggregator = new LogAggregator($params); + + $where = $logAggregator->getWhereStatement('log_hsr', 'server_time'); + $where .= ' and log_hsr_site.idsitehsr = ' . (int) $idSiteHsr; + $groupBy = 'log_hsr.device_type'; + $orderBy = ''; + + $query = $logAggregator->generateQuery($select, $from, $where, $groupBy, $orderBy); + + return $this->getDbReader()->fetchAll($query['sql'], $query['bind']); + } + + public function aggregateScrollHeatmap($idSiteHsr, $deviceType, $idSite, $period, $date, $segment) + { + $period = Period\Factory::build($period, $date); + $segment = new Segment($segment, array($idSite)); + $site = new Site($idSite); + + $from = array('log_hsr', + array( + 'table' => 'log_hsr_site', + 'joinOn' => 'log_hsr_site.idloghsr = log_hsr.idloghsr' + ), + ); + + $select = 'log_hsr.scroll_y_max_relative as label, + count(*) as value'; + + $params = new ArchiveProcessor\Parameters($site, $period, $segment); + $logAggregator = new LogAggregator($params); + $where = $logAggregator->getWhereStatement('log_hsr', 'server_time'); + $where .= ' and log_hsr_site.idsitehsr = ' . (int) $idSiteHsr; + $where .= ' and log_hsr.device_type = ' . (int) $deviceType; + + $groupBy = 'log_hsr.scroll_y_max_relative'; + $orderBy = 'label ASC'; // labels are no from 0-1000 i.e page from top to bottom, so top label should always come first 0..100..500..1000 + + $query = $logAggregator->generateQuery($select, $from, $where, $groupBy, $orderBy); + + return $this->getDbReader()->fetchAll($query['sql'], $query['bind']); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/CHANGELOG.md b/files/plugin-HeatmapSessionRecording-5.2.4/CHANGELOG.md new file mode 100644 index 0000000..a32f012 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/CHANGELOG.md @@ -0,0 +1,452 @@ +## Changelog + +5.2.4 - 2025-06-09 +- Started showing the troubleshooting link even when no heatmap sample has been recorded +- Do not crash when displaying a heatmap for a page with invalid HTML + +5.2.3 - 2025-01-20 +- Added an activity for pause and resume action +- Added a troubleshooting FAQ link for heatmaps + +5.2.2 - 2024-12-16 +- Fixes PHP deprecation warnings + +5.2.1 - 2024-12-02 +- Added activities to track deleting recorded sessions and page views + +5.2.0 - 2024-11-04 +- Implemented a tooltip which displays click count and rate + +5.1.8 - 2024-10-17 +- Fixes excluded_elements not working for escaped values for a heatmap + +5.1.7 - 2024-10-11 +- Fixes classes with word script being removed due to xss filtering + +5.1.6 - 2024-08-26 +- Pricing updated + +5.1.5 +- Added cover image for marketplace + +5.1.4 +- Fixes captureInitialDom not working for single heatmap + +5.1.3 +- Added code to disable matomo.js file writable check code for Matomo Cloud + +5.1.2 +- Added code to alert if matomo.js is not writable + +5.1.1 +- Fixed applying segment returns error for SessionRecording + +5.1.0 +- Added an option to capture Heatmap DOM on demand + +5.0.10 +- Added total actions column in Session Recording listing page + +5.0.9 +- Added code to keep playing on resize event +- Added code to update Translation keys via event + +5.0.8 +- Changes for README.md +- Fixed an error that occurs when viewing posts that have heatmaps associated in WordPress. + +5.0.7 +- Fixed issue where form fields that were supposed to be unmasked weren't +- Added code to pause/resume heatmap for Matomo Cloud + +5.0.6 +- Fixed input[type="button"] background being ignored +- Added code to display AdBlocker banner when detected + +5.0.5 +- Fixed regression where good configs were disabled + +5.0.4 +- Fixed location provider not loading for cloud customers + +5.0.3 +- Fixed error when location provider is null + +5.0.2 +- Added option to fire heatmap/session recording only for certain geographies + +5.0.1 +- Compatibility with Matomo 5.0.0-b4 + +5.0.0 +- Compatibility with Matomo 5 + +4.5.10 +- Started skipping deletion of heatmap and session recordings for proxysite + +4.5.9 +- Started hiding period selector when viewing heatmaps + +4.5.8 +- Fixed scroll data not displaying correctly due to sort missing + +4.5.7 +- Fixed deprecation warnings for PHP 8.1 + +4.5.6 +- Changed time_on_page column to BIGINT for new installation for log_hsr and log_hsr_event table + +4.5.5 +- Fixed session recording not masking image with `data-matomo-mask` attribute set on parent node + +4.5.4 +- Fixed unmasking issue for text-node elements +- Fixed recording to not end on tabs switch + +4.5.3 +- Added support to pass media attribute if present for external stylesheets + +4.5.2 +- Made regex to work consistently, #PG-373 +- Added examples of possible xss from portswigger.net + +4.5.1 +- Fixed mutation id bug to load css from DB + +4.5.0 +- Starting migrating AngularJS to Vue. +- Migrated view code to VueJs +- Updated code to respect max execution time during Archiving + +4.4.3 +- Added code to remove attributes with possible XSS values + +4.4.2 +- Added support for lazy loaded images + +4.4.1 +- Fixed masking issue for dynamically added DOM elements + +4.4.0 +- Added option to disable heatmap independently +- Stopped showing visitor profile icon in session recording when visitor profile is disabled + +4.3.1 +- Fixed recorded session link not working for segmented logs in visit action + +4.3.0 +- Started storing CSS content in DB +- Fixed range error when range is disabled + +4.2.1 +- Fixed double encoded segments + +4.2.0 +- Fixed heatmap not triggering when tracker configured directly. +- Added masking for images with height and width +- Added masking for [input type="image"] +- Fixed non-masking bug for child elements with data-matomo-unmask + +4.1.2 +- Fix to record inputs with data-matomo-unmask + +4.1.1 +- Removed masking for input type button, submit and reset + +4.1.0 +- Added option to disable session recording independently + +4.0.14 +- Support Matomo's new content security policy header + +4.0.13 +- Fix sharing a session might not work anymore with latest Matomo version + +4.0.12 +- Ensure configs.php is loaded correctly with multiple trackers +- Translation updates + +4.0.11 +- Improve handling of attribute changes +- Add translations for Czech, Dutch & Portuguese + +4.0.10 +- Further improvements for loading for iframes + +4.0.9 +- Improve loading for iframes + +4.0.8 +- Improve tracking react pages + +4.0.7 +- Add category help texts +- Increase possible sample limit +- jQuery 3 compatibility for WP + +4.0.6 +- Performance improvements + +4.0.4 +- Compatibility with Matomo 4.X + +4.0.3 +- Compatibility with Matomo 4.X + +4.0.2 +- Compatibility with Matomo 4.X + +4.0.1 +- Handle base URLs better + +4.0.0 +- Compatibility with Matomo 4.X + +3.2.39 +- Better handling for base URL + +3.2.38 +- Improve SPA tracking + +3.2.37 +- Improve sorting of server time + +3.2.36 +- Fix number of recorded pages may be wrong when a segment is applied + +3.2.35 +- Improve widgetize feature when embedded as iframe + +3.2.34 +- Further improvements for WordPress + +3.2.33 +- Improve compatibilty with WordPress + +3.2.32 +- Improve checking for number of previously recorded sessions + +3.2.31 +- Matomo for WordPress support + +3.2.30 +- Send less tracking requests by queueing more requests together + +3.2.29 +- Use DB reader in Aggregator for better compatibility with Matomo 3.12 + +3.2.28 +- Improvements for Matomo 3.12 to support faster segment archiving +- Better support for single page applications + +3.2.27 + - Show search box for entities + - Support usage of a reader DB when configured + +3.2.26 + - Tracker improvements + +3.2.25 + - Tracker improvements + +3.2.24 + - Generate correct session recording link when a visitor matches multiple recordings in the visitor log + +3.2.23 + - Internal tracker performance improvements + +3.2.22 + - Add more translations + - Tracker improvements + - Internal changes + +3.2.21 + - title-text of JavaScript Tracking option help box shows HTML + - Add primary key to log_event table for new installs (existing users should receive the update with Matomo 4) + +3.2.20 + - Fix tracker may under circumstances not enable tracking after disabling it manually + +3.2.19 + - Add possibility to delete an already taken heatmap screenshot so it can be re-taken + +3.2.18 + - Performance improvements for high traffic websites + +3.2.17 + - Add possibility to define alternative CSS file through `data-matomo-href` + - Added new API method `HeatmapSessionRecording.deleteHeatmapScreenshot` to delete an already taken heatmap screenshot + - Add possibility to delete an already taken heatmap screenshot so it can be re-taken + +3.2.16 + - Add useDateUrl=0 to default Heatmap export URL so it can be used easier + +3.2.15 + - Support a URL parameter &useDateUrl=1 in exported heatmaps to fetch heatmaps only for a specific date range + +3.2.14 + - Improve compatibility with tag manager + - Fix possible notice when matching url array parameters + - Add command to remove a stored heatmap + +3.2.13 + - Fix some coordinate cannot be calculated for SVG elements + - Added more languages + - Use new brand colors + - If time on page is too high, abort the tracking request + +3.2.12 + - Update tracker file + +3.2.11 + - Add possibility to mask images + +3.2.10 + - Make sure to replay scrolling in element correctly + +3.2.9 + - Change min height of heatmaps to 400 pixels. + +3.2.8 + - When widgetizing the session player it bursts out of the iframe + - Log more debug information in tracker + - Use API calls instead of model + +3.2.7 + - Support new "Write" role + +3.2.6 + - Improve compatibility with styled-components and similar projects + - Add possibility to not record mouse and touch movements. + +3.2.5 + - Compatibility with SiteUrlTrackingID plugin + - Ensure selectors are generated correctly + +3.2.4 + - Allow users to pass sample limit of zero for unlimited recordings + - Show which page view within a session is currently being replayed + +3.2.3 + - In configs.php return a 403 if Matomo is not installed yet + +3.2.2 + - Validate an entered regular expression when configuring a heatmap or session recording + - Improve heatmap rendering of sharepoint sites + +3.2.1 + - Improve the rendering of heatmaps and session recordings + +3.2.0 + - Optimize tracker cache file + - Prevent recording injected CSS resources that only work on a visitors' computer such as Kaspersky Antivirus CSS. + - For better GDPR compliance disable capture keystroke in sessions by default. + - Added logic to support Matomo GDPR features + - Only specifically whitelisted form fields can now be recorded in plain text + - Some form fields that could potentially include personal information such as an address will be always masked and anonymized + - Trim any whitespace when configuring target pages + +3.1.9 + - Support new attribute `data-matomo-mask` which works similar to `data-piwik-mask` but additionally allows to mask content of elements. + +3.1.8 + - Support new CSS rendering classes matomoHsr, matomoHeatmap and matomoSessionRecording + - For input text fields prefer a set value on the element directly + - Differentiate between scrolling of the window and scrolling within an element (part of the window) + - Replay in the recorded session when a user is scrolling within an element + +3.1.7 + - Make sure validating URL works correctly with HTML entities + - Prevent possible fatal error when opening manage screen for all websites + +3.1.6 + - Renamed Piwik to Matomo + +3.1.5 + - Fix requested stylesheet URLs were requested lowercase when using a relative base href in the recorded page + - Show more accurate time on page and record pageviews for a longer period in case a user is not active right away. + +3.1.4 + - Prevent target rules in heatmap or session recording to visually disappear under circumstances when not using the cancel or back button. + - Respect URL prefix (eg www.) when replaying a session recording, may fix some displaying issues if website does not work without www. + - Improved look of widgetized session recording + +3.1.3 + - Make Heatmap & Session Recording compatible with canvas and webgl libraries like threejs and earcut + - Better detected of the embedded heatmap height + - Fix scroll heatmap did not paint the last scroll section correctly + - It is now possible to configure the sample limits in the config via `[HeatmapSessionRecording] session_recording_sample_limits = 50,100,...` + +3.1.2 + - Added URL to view heatmap and to replay a session recording to the API response + - Fix widgetized URL for heatmaps and sessions redirected to another page when authenticated via token_auth + +3.1.1 + - Better error code when a site does not exist + - Fix configs.php may fail if plugins directory is a symlink + - Available sessions are now also displayed in the visitor profile + +3.1.0 + - Added autoplay feature for page views within a visit + - Added possibility to change replay speed + - Added possibility to skip long pauses in a session recording automatically + - Better base URL detection in case a relative base URL is used + +3.0.15 + - Fix only max 100 heatmaps or session recordings were shown when managing them for a specific site. + - Mask closing body in embedded page so it won't be replaced by some server logic + +3.0.14 + - Make sure to find all matches for a root folder when "equals simple" is used + +3.0.13 + - Fix a custom set based URL was ignored. + +3.0.12 + - Fix session recording stops when a user changes a file form field because form value is not allowed to be changed. + +3.0.11 + - Improve the performance of a DB query of a daily task when cleaning up blob entries. + +3.0.10 + - Improve the performance of a DB query of a daily task + - Respect the new config setting `enable_internet_features` in the system check + +3.0.9 + - Make sure page rules work fine when using HTML entities + +3.0.8 + - Fix possible notice when tracking + - Avoid some logs in chrome when viewing a heatmaps or session recordings + - Always prefer same protocol when replaying sessions as currently used + +3.0.7 + - When using an "equals exactly" comparison, ignore a trailing slash when there is no path set + - Let users customize if the tracking code should be included only when active records are configured + +3.0.6 + - Fix link to replay session in visitor log may not work under circumstances + +3.0.5 + - More detailed "no data message" when nothing has been recorded yet + - Fix select fields were not recorded + +3.0.4 + - Only add tracker code when heatmap or sessions are actually active in any site + - Added index on site_hsr table + - Add custom stylesheets for custom styling + +3.0.3 + - Add system check for configs.php + - On install, if .htaccess was not created, create the file manually + +3.0.2 + - Enrich system summary widget + - Show an arrow instead of a dash between entry and exit url + - Added some German translations + +3.0.1 + - Updated translations + +3.0.0 + - Heatmap & Session Recording for Piwik 3 diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Categories/HeatmapCategory.php b/files/plugin-HeatmapSessionRecording-5.2.4/Categories/HeatmapCategory.php new file mode 100644 index 0000000..946c2ea --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Categories/HeatmapCategory.php @@ -0,0 +1,26 @@ +' . Piwik::translate('HeatmapSessionRecording_ManageHeatmapSubcategoryHelp') . '

'; + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Categories/ManageSessionRecordingSubcategory.php b/files/plugin-HeatmapSessionRecording-5.2.4/Categories/ManageSessionRecordingSubcategory.php new file mode 100644 index 0000000..fa5c615 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Categories/ManageSessionRecordingSubcategory.php @@ -0,0 +1,32 @@ +' . Piwik::translate('HeatmapSessionRecording_ManageSessionRecordingSubcategoryHelp') . '

'; + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Categories/SessionRecordingsCategory.php b/files/plugin-HeatmapSessionRecording-5.2.4/Categories/SessionRecordingsCategory.php new file mode 100644 index 0000000..f376af1 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Categories/SessionRecordingsCategory.php @@ -0,0 +1,26 @@ +getMetric($row, 'config_browser_name'); + } + + public function getDependentMetrics() + { + return array( + 'config_browser_name', + ); + } + + public function showsHtml() + { + return true; + } + + public function format($value, Formatter $formatter) + { + if (empty($value) || $value === 'UNK') { + return false; + } + + $title = \Piwik\Plugins\DevicesDetection\getBrowserName($value); + + return ''; + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/Device.php b/files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/Device.php new file mode 100644 index 0000000..c0c0c6b --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/Device.php @@ -0,0 +1,78 @@ + $this->getMetric($row, 'config_device_type'), + 'model' => $this->getMetric($row, 'config_device_model') + ); + } + + public function getDependentMetrics() + { + return array( + 'config_device_type', + 'config_device_model', + ); + } + + public function showsHtml() + { + return true; + } + + public function format($value, Formatter $formatter) + { + if (empty($value['type']) && $value['type'] !== 0 && $value['type'] !== '0') { + return false; + } + + $title = \Piwik\Plugins\DevicesDetection\getDeviceTypeLabel($value['type']); + + if (!empty($value['model'])) { + $title .= ', ' . SafeDecodeLabel::decodeLabelSafe($value['model']); + } + + return ''; + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/Location.php b/files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/Location.php new file mode 100644 index 0000000..671a658 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/Location.php @@ -0,0 +1,86 @@ + $this->getMetric($row, 'location_country'), + 'region' => $this->getMetric($row, 'location_region'), + 'city' => $this->getMetric($row, 'location_city'), + ); + } + + public function getDependentMetrics() + { + return array( + 'location_country', + 'location_region', + 'location_city' + ); + } + + public function showsHtml() + { + return true; + } + + public function format($value, Formatter $formatter) + { + if (empty($value['country']) || $value['country'] === Visit::UNKNOWN_CODE) { + return false; + } + + $title = \Piwik\Plugins\UserCountry\countryTranslate($value['country']); + + if (!empty($value['region']) && $value['region'] !== Visit::UNKNOWN_CODE) { + $title .= ', ' . \Piwik\Plugins\UserCountry\getRegionNameFromCodes($value['country'], $value['region']); + } + + if (!empty($value['city'])) { + $title .= ', ' . SafeDecodeLabel::decodeLabelSafe($value['city']); + } + + return ''; + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/OperatingSystem.php b/files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/OperatingSystem.php new file mode 100644 index 0000000..d78691b --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/OperatingSystem.php @@ -0,0 +1,67 @@ +getMetric($row, 'config_os'); + } + + public function getDependentMetrics() + { + return array( + 'config_os', + ); + } + + public function showsHtml() + { + return true; + } + + public function format($value, Formatter $formatter) + { + if (empty($value) || $value === 'UNK') { + return false; + } + + $title = \Piwik\Plugins\DevicesDetection\getOsFullName($value); + + return ''; + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/SessionTime.php b/files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/SessionTime.php new file mode 100644 index 0000000..0de98a4 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/SessionTime.php @@ -0,0 +1,97 @@ +dateFormat = $dateFormat; + } + + public function getName() + { + return 'server_time'; + } + + public function getTranslatedName() + { + return Piwik::translate('HeatmapSessionRecording_ColumnTime'); + } + + public function getDocumentation() + { + return Piwik::translate('HeatmapSessionRecording_ColumnTimeDocumentation'); + } + + public function compute(Row $row) + { + return $this->getMetric($row, 'server_time'); + } + + public function getDependentMetrics() + { + return array($this->getName()); + } + + public function format($value, Formatter $formatter) + { + $date = Date::factory($value, $this->timezone); + + $dateTimeFormatProvider = StaticContainer::get('Piwik\Intl\Data\Provider\DateTimeFormatProvider'); + + $template = $dateTimeFormatProvider->getFormatPattern($this->dateFormat); + $template = str_replace(array(' y ', '.y '), ' ', $template); + + return $date->getLocalized($template); + } + + public function beforeFormat($report, DataTable $table) + { + $this->idSite = DataTableFactory::getSiteIdFromMetadata($table); + if (empty($this->idSite)) { + $this->idSite = Common::getRequestVar('idSite', 0, 'int'); + } + if (!empty($this->idSite)) { + $this->timezone = Site::getTimezoneFor($this->idSite); + return true; + } + return false; // skip formatting if there is no site to get currency info from + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/TimeOnPage.php b/files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/TimeOnPage.php new file mode 100644 index 0000000..506472d --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/TimeOnPage.php @@ -0,0 +1,65 @@ +getMetric($row, $this->getName()); + } + + public function getDependentMetrics() + { + return array($this->getName()); + } + + public function format($value, Formatter $formatter) + { + if (!empty($value)) { + $value = round($value / 1000, 1); // convert ms to seconds + $value = (int) round($value); + } + + $time = $formatter->getPrettyTimeFromSeconds($value, $asSentence = false); + + if (strpos($time, '00:') === 0) { + $time = substr($time, 3); + } + + return $time; + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/TimeOnSite.php b/files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/TimeOnSite.php new file mode 100644 index 0000000..2ba27d2 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Columns/Metrics/TimeOnSite.php @@ -0,0 +1,37 @@ +getMetric($row, 'total_events'); + } + + public function getDependentMetrics() + { + return []; + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Commands/RemoveHeatmapScreenshot.php b/files/plugin-HeatmapSessionRecording-5.2.4/Commands/RemoveHeatmapScreenshot.php new file mode 100644 index 0000000..86783de --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Commands/RemoveHeatmapScreenshot.php @@ -0,0 +1,101 @@ +setName('heatmapsessionrecording:remove-heatmap-screenshot'); + $this->setDescription('Removes a saved heatmap screenshot which can be useful if you want Matomo to re-take this screenshot. If the heatmap is currently ended, it will automatically restart it.'); + $this->addRequiredValueOption('idsite', null, 'The ID of the site the heatmap belongs to'); + $this->addRequiredValueOption('idheatmap', null, 'The ID of the heatamp'); + } + + /** + * @return int + */ + protected function doExecute(): int + { + $this->checkAllRequiredOptionsAreNotEmpty(); + $input = $this->getInput(); + $output = $this->getOutput(); + $idSite = $input->getOption('idsite'); + $idHeatmap = $input->getOption('idheatmap'); + + $heatmap = Request::processRequest('HeatmapSessionRecording.getHeatmap', array( + 'idSite' => $idSite, + 'idSiteHsr' => $idHeatmap + )); + + if ($heatmap['status'] === SiteHsrDao::STATUS_ENDED) { + $logHsrSite = new LogHsrSite(); + $numSamplesTakenSoFar = $logHsrSite->getNumPageViews($idHeatmap); + + $currentSampleLimit = $heatmap['sample_limit']; + $newSampleLimit = $numSamplesTakenSoFar + 50; // 50 heatmaps should be enough to collect at least once the dom. + + $update = array('status' => SiteHsrDao::STATUS_ACTIVE); + if ($currentSampleLimit >= $newSampleLimit) { + $output->writeln('Sample limit remains unchanged at ' . $currentSampleLimit); + if ($currentSampleLimit - $numSamplesTakenSoFar > 75) { + $output->writeln('make sure to end the heatmap again as soon as a screenshot has been taken!'); + } + } else { + $output->writeln('Going to increase sample limit from ' . $currentSampleLimit . ' to ' . $newSampleLimit . ' so a screenshot can be retaken. The heatmap will be automatically ended after about 50 new recordings have been recorded.'); + $output->writeln('Note: This means when you manage this heatmap the selected sample wont be shown correctly in the select field'); + $update['sample_limit'] = $newSampleLimit; + } + + $output->writeln('Going to change status of heatmap from ended to active'); + + $siteHsr = StaticContainer::get(SiteHsrDao::class); + $siteHsr->updateHsrColumns($idSite, $idHeatmap, array( + 'status' => SiteHsrDao::STATUS_ACTIVE, + 'sample_limit' => $newSampleLimit + )); + $output->writeln('Done'); + } + + $success = Request::processRequest('HeatmapSessionRecording.deleteHeatmapScreenshot', array( + 'idSite' => $idSite, + 'idSiteHsr' => $idHeatmap + )); + + if ($success) { + Filesystem::deleteAllCacheOnUpdate(); + /** @var HeatmapSessionRecording $hsr */ + $hsr = Plugin\Manager::getInstance()->getLoadedPlugin('HeatmapSessionRecording'); + $hsr->updatePiwikTracker(); + $output->writeln('Screenhot removed'); + + return self::SUCCESS; + } + + $output->writeln('Heatmap not found'); + return self::FAILURE; + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Configuration.php b/files/plugin-HeatmapSessionRecording-5.2.4/Configuration.php new file mode 100644 index 0000000..b8dd31a --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Configuration.php @@ -0,0 +1,145 @@ +getConfig(); + $config->HeatmapSessionRecording = array( + self::KEY_OPTIMIZE_TRACKING_CODE => self::DEFAULT_OPTIMIZE_TRACKING_CODE, + self::KEY_SESSION_RECORDING_SAMPLE_LIMITS => self::DEFAULT_SESSION_RECORDING_SAMPLE_LIMITS, + self::KEY_ENABLE_LOAD_CSS_FROM_DB => self::DEFAULT_ENABLE_LOAD_CSS_FROM_DB, + self::MAX_ALLOWED_TIME_ON_PAGE_COLUMN_LIMIT => pow(2, 63), + self::KEY_DEFAULT_HEATMAP_WIDTH => self::DEFAULT_HEATMAP_WIDTH + + ); + $config->forceSave(); + } + + public function uninstall() + { + $config = $this->getConfig(); + $config->HeatmapSessionRecording = array(); + $config->forceSave(); + } + + public function shouldOptimizeTrackingCode() + { + $value = $this->getConfigValue(self::KEY_OPTIMIZE_TRACKING_CODE, self::DEFAULT_OPTIMIZE_TRACKING_CODE); + + return !empty($value); + } + + public function isAnonymousSessionRecordingAccessEnabled($idSite) + { + $value = $this->getDiValue(self::KEY_ENABLE_ANONYMOUS_SESSION_RECORDING_ACCESS, self::DEFAULT_ENABLE_ANONYMOUS_SESSION_RECORDING_ACCESS); + $idSites = explode(',', $value); + $idSites = array_map('trim', $idSites); + $idSites = array_filter($idSites); + return in_array($idSite, $idSites); + } + + public function getSessionRecordingSampleLimits() + { + $value = $this->getConfigValue(self::KEY_SESSION_RECORDING_SAMPLE_LIMITS, self::DEFAULT_SESSION_RECORDING_SAMPLE_LIMITS); + + if (empty($value)) { + $value = self::DEFAULT_SESSION_RECORDING_SAMPLE_LIMITS; + } + + $value = explode(',', $value); + $value = array_filter($value, function ($val) { + return !empty($val); + }); + $value = array_map(function ($val) { + return intval(trim($val)); + }, $value); + natsort($value); + + if (empty($value)) { + // just a fallback in case config is completely misconfigured + $value = explode(',', self::DEFAULT_SESSION_RECORDING_SAMPLE_LIMITS); + } + + return array_values($value); + } + + public function isLoadCSSFromDBEnabled() + { + return $this->getConfigValue(self::KEY_ENABLE_LOAD_CSS_FROM_DB, self::DEFAULT_ENABLE_LOAD_CSS_FROM_DB); + } + + public function getMaximumAllowedPageTime() + { + return $this->getConfigValue(self::MAX_ALLOWED_TIME_ON_PAGE_COLUMN_LIMIT, ''); + } + + public function getDefaultHeatmapWidth() + { + $width = $this->getConfigValue(self::KEY_DEFAULT_HEATMAP_WIDTH, 1280); + if (!in_array($width, self::HEATMAP_ALLOWED_WIDTHS)) { + $width = self::DEFAULT_HEATMAP_WIDTH; + } + + return $width; + } + + private function getConfig() + { + return Config::getInstance(); + } + + private function getConfigValue($name, $default) + { + $config = $this->getConfig(); + $values = $config->HeatmapSessionRecording; + if (isset($values[$name])) { + return $values[$name]; + } + return $default; + } + + private function getDiValue($name, $default) + { + $value = $default; + try { + $value = StaticContainer::get('HeatmapSessionRecording.' . $name); + } catch (NotFoundException $ex) { + // ignore + } + return $value; + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Controller.php b/files/plugin-HeatmapSessionRecording-5.2.4/Controller.php new file mode 100644 index 0000000..2fd7c93 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Controller.php @@ -0,0 +1,463 @@ +validator = $validator; + $this->siteHsrModel = $model; + $this->systemSettings = $settings; + $this->mutationManipulator = $mutationManipulator; + $this->mutationManipulator->generateNonce(); + $this->configuration = $configuration; + } + + public function manageHeatmap() + { + $idSite = Common::getRequestVar('idSite'); + + if (strtolower($idSite) === 'all') { + // prevent fatal error... redirect to a specific site as it is not possible to manage for all sites + $this->validator->checkHasSomeWritePermission(); + $this->redirectToIndex('HeatmapSessionRecording', 'manageHeatmap'); + exit; + } + + $this->checkSitePermission(); + $this->validator->checkHeatmapReportWritePermission($this->idSite); + + return $this->renderTemplate('manageHeatmap', array( + 'breakpointMobile' => (int) $this->systemSettings->breakpointMobile->getValue(), + 'breakpointTablet' => (int) $this->systemSettings->breakpointTablet->getValue(), + 'pauseReason' => Piwik::translate(HeatmapSessionRecording::getTranslationKey('pause'), [Piwik::translate('HeatmapSessionRecording_Heatmap')]), + 'isMatomoJsWritable' => HeatmapSessionRecording::isMatomoJsWritable() + )); + } + + public function manageSessions() + { + $idSite = Common::getRequestVar('idSite'); + + if (strtolower($idSite) === 'all') { + // prevent fatal error... redirect to a specific site as it is not possible to manage for all sites + $this->validator->checkHasSomeWritePermission(); + $this->redirectToIndex('HeatmapSessionRecording', 'manageSessions'); + exit; + } + + $this->checkSitePermission(); + $this->validator->checkSessionReportWritePermission($this->idSite); + + return $this->renderTemplate('manageSessions', array( + 'pauseReason' => Piwik::translate(HeatmapSessionRecording::getTranslationKey('pause'), [Piwik::translate('HeatmapSessionRecording_SessionRecording')]), + 'isMatomoJsWritable' => HeatmapSessionRecording::isMatomoJsWritable() + )); + } + + private function checkNotInternetExplorerWhenUsingToken() + { + if (Common::getRequestVar('token_auth', '', 'string') && !empty($_SERVER['HTTP_USER_AGENT'])) { + // we want to detect device type only once for faster performance + $ddFactory = StaticContainer::get(\Piwik\DeviceDetector\DeviceDetectorFactory::class); + $deviceDetector = $ddFactory->makeInstance($_SERVER['HTTP_USER_AGENT']); + $client = $deviceDetector->getClient(); + + if ( + (!empty($client['short_name']) && $client['short_name'] === 'IE') + || (!empty($client['name']) && $client['name'] === 'Internet Explorer') + || (!empty($client['name']) && $client['name'] === 'Opera Mini') + ) { + // see https://caniuse.com/?search=noreferrer + // and https://caniuse.com/?search=referrerpolicy + throw new \Exception('For security reasons this feature doesn\'t work in this browser when using authentication using token_auth. Please try a different browser or log in to view this.'); + } + } + } + + public function replayRecording() + { + $this->validator->checkSessionReportViewPermission($this->idSite); + $this->checkNotInternetExplorerWhenUsingToken(); + + $idLogHsr = Common::getRequestVar('idLogHsr', null, 'int'); + $idSiteHsr = Common::getRequestVar('idSiteHsr', null, 'int'); + + $_GET['period'] = 'year'; // setting it randomly to not having to pass it in the URL + $_GET['date'] = 'today'; // date is ignored anyway + + $recording = Request::processRequest('HeatmapSessionRecording.getRecordedSession', array( + 'idSite' => $this->idSite, + 'idLogHsr' => $idLogHsr, + 'idSiteHsr' => $idSiteHsr, + 'filter_limit' => '-1' + ), $default = []); + + $currentPage = null; + if (!empty($recording['pageviews']) && is_array($recording['pageviews'])) { + $allPageviews = array_values($recording['pageviews']); + foreach ($allPageviews as $index => $pageview) { + if (!empty($pageview['idloghsr']) && $idLogHsr == $pageview['idloghsr']) { + $currentPage = $index + 1; + break; + } + } + } + + $settings = $this->getPluginSettings(); + $settings = $settings->load(); + $skipPauses = !empty($settings['skip_pauses']); + $autoPlayEnabled = !empty($settings['autoplay_pageviews']); + $replaySpeed = !empty($settings['replay_speed']) ? (int) $settings['replay_speed'] : 1; + $isVisitorProfileEnabled = Manager::getInstance()->isPluginActivated('Live') && Live::isVisitorProfileEnabled(); + + if (!empty($recording['events'])) { + foreach ($recording['events'] as $recordingEventIndex => $recordingEventValue) { + if ( + !empty($recordingEventValue['event_type']) && + ( + $recordingEventValue['event_type'] == RequestProcessor::EVENT_TYPE_INITIAL_DOM || + $recordingEventValue['event_type'] == RequestProcessor::EVENT_TYPE_MUTATION + ) && + !empty( + $recordingEventValue['text'] + ) + ) { + $recording['events'][$recordingEventIndex]['text'] = $this->mutationManipulator->manipulate($recordingEventValue['text'], $idSiteHsr, $idLogHsr); + break; + } + } + } + + return $this->renderTemplate('replayRecording', array( + 'idLogHsr' => $idLogHsr, + 'idSiteHsr' => $idSiteHsr, + 'recording' => $recording, + 'scrollAccuracy' => LogHsr::SCROLL_ACCURACY, + 'offsetAccuracy' => LogHsrEvent::OFFSET_ACCURACY, + 'autoPlayEnabled' => $autoPlayEnabled, + 'visitorProfileEnabled' => $isVisitorProfileEnabled, + 'skipPausesEnabled' => $skipPauses, + 'replaySpeed' => $replaySpeed, + 'currentPage' => $currentPage + )); + } + + protected function setBasicVariablesView($view) + { + parent::setBasicVariablesView($view); + + if ( + Common::getRequestVar('module', '', 'string') === 'Widgetize' + && Common::getRequestVar('action', '', 'string') === 'iframe' + && Common::getRequestVar('moduleToWidgetize', '', 'string') === 'HeatmapSessionRecording' + ) { + $action = Common::getRequestVar('actionToWidgetize', '', 'string'); + if (in_array($action, array('replayRecording', 'showHeatmap'), true)) { + $view->enableFrames = true; + } + } + } + + private function getPluginSettings() + { + $login = Piwik::getCurrentUserLogin(); + + $settings = new PluginSettingsTable('HeatmapSessionRecording', $login); + return $settings; + } + + public function saveSessionRecordingSettings() + { + Piwik::checkUserHasSomeViewAccess(); + $this->validator->checkSessionRecordingEnabled(); + // there is no nonce for this action but that should also not be needed here. as it is just replay settings + + $autoPlay = Common::getRequestVar('autoplay', '0', 'int'); + $replaySpeed = Common::getRequestVar('replayspeed', '1', 'int'); + $skipPauses = Common::getRequestVar('skippauses', '0', 'int'); + + $settings = $this->getPluginSettings(); + $settings->save(array('autoplay_pageviews' => $autoPlay, 'replay_speed' => $replaySpeed, 'skip_pauses' => $skipPauses)); + } + + private function initHeatmapAuth() + { + // todo remove in Matomo 5 when we hopefully no longer support IE 11. + // This is mostly there to prevent forwarding tokens through referrer to third parties + // most browsers support this except IE11 + // we said we're technically OK with IE11 forwarding a view token in worst case but we still have this here for now + $token_auth = Common::getRequestVar('token_auth', '', 'string'); + + if (!empty($token_auth)) { + $auth = StaticContainer::get('Piwik\Auth'); + $auth->setTokenAuth($token_auth); + $auth->setPassword(null); + $auth->setPasswordHash(null); + $auth->setLogin(null); + + Session::start(); + $sessionInitializer = new SessionInitializer(); + $sessionInitializer->initSession($auth); + + $url = preg_replace('/&token_auth=[^&]{20,38}|$/i', '', Url::getCurrentUrl()); + if ($url) { + Url::redirectToUrl($url); + return; + } + } + + // if no token_auth, we just rely on an existing session auth check + } + + protected function setBasicVariablesNoneAdminView($view) + { + parent::setBasicVariablesNoneAdminView($view); + if (Piwik::getAction() === 'embedPage' && Piwik::getModule() === 'HeatmapSessionRecording') { + $view->setXFrameOptions('allow'); + } + } + + public function embedPage() + { + $this->checkNotInternetExplorerWhenUsingToken(); + $this->initHeatmapAuth(); + $nonceRandom = ''; + + if ( + property_exists($this, 'securityPolicy') && + method_exists($this->securityPolicy, 'allowEmbedPage') + ) { + $toSearch = array("'unsafe-inline' ", "'unsafe-eval' ", "'unsafe-inline'", "'unsafe-eval'"); + $nonceRandom = $this->mutationManipulator->getNonce(); + $this->securityPolicy->overridePolicy('default-src', $this->securityPolicy::RULE_EMBEDDED_FRAME); + $this->securityPolicy->overridePolicy('img-src', $this->securityPolicy::RULE_EMBEDDED_FRAME); + $this->securityPolicy->addPolicy('script-src', str_replace($toSearch, '', $this->securityPolicy::RULE_DEFAULT) . "'nonce-$nonceRandom'"); + } + + $pathPrefix = HeatmapSessionRecording::getPathPrefix(); + $jQueryPath = 'node_modules/jquery/dist/jquery.min.js'; + if (HeatmapSessionRecording::isMatomoForWordPress()) { + $jQueryPath = includes_url('js/jquery/jquery.js'); + } + + $idLogHsr = Common::getRequestVar('idLogHsr', 0, 'int'); + $idSiteHsr = Common::getRequestVar('idSiteHsr', null, 'int'); + + $_GET['period'] = 'year'; // setting it randomly to not having to pass it in the URL + $_GET['date'] = 'today'; // date is ignored anyway + + if (empty($idLogHsr)) { + $this->validator->checkHeatmapReportViewPermission($this->idSite); + + $heatmap = $this->getHeatmap($this->idSite, $idSiteHsr); + + if (isset($heatmap[0])) { + $heatmap = $heatmap[0]; + } + + $baseUrl = $heatmap['screenshot_url']; + $initialMutation = $heatmap['page_treemirror']; + } else { + $this->validator->checkSessionReportViewPermission($this->idSite); + $this->checkSessionRecordingExists($this->idSite, $idSiteHsr); + + $recording = Request::processRequest('HeatmapSessionRecording.getEmbedSessionInfo', [ + 'idSite' => $this->idSite, + 'idSiteHsr' => $idSiteHsr, + 'idLogHsr' => $idLogHsr, + ], $default = []); + + if (empty($recording)) { + throw new \Exception(Piwik::translate('HeatmapSessionRecording_ErrorSessionRecordingDoesNotExist')); + } + + $baseUrl = $recording['base_url']; + $map = array_flip(PageUrl::$urlPrefixMap); + + if (isset($recording['url_prefix']) !== null && isset($map[$recording['url_prefix']])) { + $baseUrl = $map[$recording['url_prefix']] . $baseUrl; + } + + if (!empty($recording['initial_mutation'])) { + $initialMutation = $recording['initial_mutation']; + } else { + $initialMutation = ''; + } + } + + $initialMutation = $this->mutationManipulator->manipulate($initialMutation, $idSiteHsr, $idLogHsr); + + return $this->renderTemplate('embedPage', array( + 'idLogHsr' => $idLogHsr, + 'idSiteHsr' => $idSiteHsr, + 'initialMutation' => $initialMutation, + 'baseUrl' => $baseUrl, + 'pathPrefix' => $pathPrefix, + 'jQueryPath' => $jQueryPath, + 'nonceRandom' => $nonceRandom + )); + } + + public function showHeatmap() + { + $this->validator->checkHeatmapReportViewPermission($this->idSite); + $this->checkNotInternetExplorerWhenUsingToken(); + + $idSiteHsr = Common::getRequestVar('idSiteHsr', null, 'int'); + $heatmapType = Common::getRequestVar('heatmapType', RequestProcessor::EVENT_TYPE_CLICK, 'int'); + $deviceType = Common::getRequestVar('deviceType', LogHsr::DEVICE_TYPE_DESKTOP, 'int'); + + $heatmap = Request::processRequest('HeatmapSessionRecording.getHeatmap', array( + 'idSite' => $this->idSite, + 'idSiteHsr' => $idSiteHsr + ), $default = []); + + if (isset($heatmap[0])) { + $heatmap = $heatmap[0]; + } + + $requestDate = $this->siteHsrModel->getPiwikRequestDate($heatmap); + $period = $requestDate['period']; + $dateRange = $requestDate['date']; + + if ( + !PeriodFactory::isPeriodEnabledForAPI($period) || + Common::getRequestVar('useDateUrl', 0, 'int') + ) { + $period = Common::getRequestVar('period', null, 'string'); + $dateRange = Common::getRequestVar('date', null, 'string'); + } + + try { + PeriodFactory::checkPeriodIsEnabled($period); + } catch (\Exception $e) { + $periodEscaped = Common::sanitizeInputValue(Piwik::translate('HeatmapSessionRecording_PeriodDisabledErrorMessage', $period)); + return '
' . $periodEscaped . '
'; + } + + $metadata = Request::processRequest('HeatmapSessionRecording.getRecordedHeatmapMetadata', array( + 'idSite' => $this->idSite, + 'idSiteHsr' => $idSiteHsr, + 'period' => $period, + 'date' => $dateRange + ), $default = []); + + if (isset($metadata[0])) { + $metadata = $metadata[0]; + } + + $editUrl = 'index.php' . Url::getCurrentQueryStringWithParametersModified(array( + 'module' => 'HeatmapSessionRecording', + 'action' => 'manageHeatmap' + )) . '#?idSiteHsr=' . (int)$idSiteHsr; + + $reportDocumentation = ''; + if ($heatmap['status'] == SiteHsrDao::STATUS_ACTIVE) { + $reportDocumentation = Piwik::translate('HeatmapSessionRecording_RecordedHeatmapDocStatusActive', array($heatmap['sample_limit'], $heatmap['sample_rate'] . '%')); + } elseif ($heatmap['status'] == SiteHsrDao::STATUS_ENDED) { + $reportDocumentation = Piwik::translate('HeatmapSessionRecording_RecordedHeatmapDocStatusEnded'); + } + + $includedCountries = $this->systemSettings->getIncludedCountries(); + + return $this->renderTemplate('showHeatmap', array( + 'idSiteHsr' => $idSiteHsr, + 'editUrl' => $editUrl, + 'heatmapType' => $heatmapType, + 'deviceType' => $deviceType, + 'heatmapPeriod' => $period, + 'heatmapDate' => $dateRange, + 'heatmap' => $heatmap, + 'isActive' => $heatmap['status'] == SiteHsrDao::STATUS_ACTIVE, + 'heatmapMetadata' => $metadata, + 'reportDocumentation' => $reportDocumentation, + 'isScroll' => $heatmapType == RequestProcessor::EVENT_TYPE_SCROLL, + 'offsetAccuracy' => LogHsrEvent::OFFSET_ACCURACY, + 'heatmapTypes' => API::getInstance()->getAvailableHeatmapTypes(), + 'deviceTypes' => API::getInstance()->getAvailableDeviceTypes(), + 'includedCountries' => !empty($includedCountries) ? implode(', ', $includedCountries) : '', + 'desktopPreviewSize' => $this->configuration->getDefaultHeatmapWidth(), + 'allowedWidth' => Configuration::HEATMAP_ALLOWED_WIDTHS, + 'noDataMessageKey' => HeatmapSessionRecording::getTranslationKey('noDataHeatmap'), + 'isMatomoJsWritable' => HeatmapSessionRecording::isMatomoJsWritable(), + )); + } + + private function getHeatmap($idSite, $idSiteHsr) + { + $heatmap = Request::processRequest('HeatmapSessionRecording.getHeatmap', [ + 'idSite' => $idSite, + 'idSiteHsr' => $idSiteHsr, + ], $default = []); + if (empty($heatmap)) { + throw new \Exception(Piwik::translate('HeatmapSessionRecording_ErrorHeatmapDoesNotExist')); + } + return $heatmap; + } + + private function checkSessionRecordingExists($idSite, $idSiteHsr) + { + $sessionRecording = Request::processRequest('HeatmapSessionRecording.getSessionRecording', [ + 'idSite' => $idSite, + 'idSiteHsr' => $idSiteHsr, + ], $default = []); + if (empty($sessionRecording)) { + throw new \Exception(Piwik::translate('HeatmapSessionRecording_ErrorSessionRecordingDoesNotExist')); + } + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Dao/LogHsr.php b/files/plugin-HeatmapSessionRecording-5.2.4/Dao/LogHsr.php new file mode 100644 index 0000000..8851782 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Dao/LogHsr.php @@ -0,0 +1,375 @@ +tablePrefixed = Common::prefixTable($this->table); + $this->logHsrSite = $logHsrSite; + } + + private function getDb() + { + if (!isset($this->db)) { + $this->db = Db::get(); + } + return $this->db; + } + + public function install() + { + DbHelper::createTable($this->table, " + `idloghsr` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `idsite` INT UNSIGNED NOT NULL, + `idvisit` BIGINT UNSIGNED NOT NULL, + `idhsrview` CHAR(6) NOT NULL, + `idpageview` CHAR(6) NULL, + `idaction_url` INT(10) UNSIGNED NOT NULL DEFAULT 0, + `device_type` TINYINT(1) NOT NULL DEFAULT 1, + `server_time` DATETIME NOT NULL, + `time_on_page` BIGINT(8) UNSIGNED NOT NULL, + `viewport_w_px` SMALLINT(5) UNSIGNED DEFAULT 0, + `viewport_h_px` SMALLINT(5) UNSIGNED DEFAULT 0, + `scroll_y_max_relative` SMALLINT(5) UNSIGNED DEFAULT 0, + `fold_y_relative` SMALLINT(5) UNSIGNED DEFAULT 0, + PRIMARY KEY(`idloghsr`), + UNIQUE KEY idvisit_idhsrview (`idvisit`,`idhsrview`), + KEY idsite_servertime (`idsite`,`server_time`)"); + + // idpageview is only there so we can add it to visitor log later. Please note that idpageview is only set on + // the first tracking request. As the user may track a new pageview during the recording, the pageview may + // change over time. This is why we need the idhsrview. + + // we need the idhsrview as there can be many recordings during one visit and this way we can control when + // to trigger a new recording / heatmap in the tracker by changing this id + } + + public function uninstall() + { + Db::query(sprintf('DROP TABLE IF EXISTS `%s`', $this->tablePrefixed)); + } + + protected function getDeviceWidth($resolution) + { + if (!empty($resolution)) { + $parts = explode('x', $resolution); + if (count($parts) === 2 && $parts[0] > 1 && $parts[1] > 1) { + $width = $parts[0]; + return (int) $width; + } + } + + return 1280; // default desktop + } + + protected function getDeviceType($hsrSiteIds, $idSite, $userAgent, $deviceWidth) + { + $deviceType = null; + + // we want to detect device type only once for faster performance + $ddFactory = StaticContainer::get(\Piwik\DeviceDetector\DeviceDetectorFactory::class); + $deviceDetector = $ddFactory->makeInstance($userAgent); + $device = $deviceDetector->getDevice(); + + $checkWidth = false; + if ( + in_array( + $device, + array( + AbstractDeviceParser::DEVICE_TYPE_FEATURE_PHONE, + AbstractDeviceParser::DEVICE_TYPE_PHABLET, + AbstractDeviceParser::DEVICE_TYPE_SMARTPHONE, + AbstractDeviceParser::DEVICE_TYPE_CAMERA, + AbstractDeviceParser::DEVICE_TYPE_CAR_BROWSER + ), + $strict = true + ) + ) { + $deviceType = self::DEVICE_TYPE_MOBILE; + } elseif (in_array($device, array(AbstractDeviceParser::DEVICE_TYPE_TABLET), $strict = true)) { + $deviceType = self::DEVICE_TYPE_TABLET; + } elseif ($deviceType === AbstractDeviceParser::DEVICE_TYPE_DESKTOP) { + $deviceType = LogHsr::DEVICE_TYPE_DESKTOP; + $checkWidth = true; + } else { + $checkWidth = true; + } + + if ($checkWidth && !empty($deviceWidth)) { + $hsrs = $this->getCachedHsrs($idSite); + + foreach ($hsrs as $hsr) { + // the device type is only relevant for heatmaps so we only look for breakpoints in heatmaps + if ( + $hsr['record_type'] == SiteHsrDao::RECORD_TYPE_HEATMAP + && in_array($hsr['idsitehsr'], $hsrSiteIds) + ) { + if ($deviceWidth < $hsr['breakpoint_mobile']) { + // resolution has to be lower than this + $deviceType = self::DEVICE_TYPE_MOBILE; + } elseif ($deviceWidth < $hsr['breakpoint_tablet']) { + $deviceType = self::DEVICE_TYPE_TABLET; + } else { + $deviceType = self::DEVICE_TYPE_DESKTOP; + } + + break; + } + } + } + + if (empty($deviceType)) { + $deviceType = LogHsr::DEVICE_TYPE_DESKTOP; + } + + return $deviceType; + } + + protected function getCachedHsrs($idSite) + { + $cache = Tracker\Cache::getCacheWebsiteAttributes($idSite); + + if (!empty($cache['hsr'])) { + return $cache['hsr']; + } + + return array(); + } + + public function findIdLogHsr($idVisit, $idHsrView) + { + $query = sprintf('SELECT idloghsr FROM %s WHERE idvisit = ? and idhsrview = ? LIMIT 1', $this->tablePrefixed); + + return $this->getDb()->fetchOne($query, array($idVisit, $idHsrView)); + } + + public function hasRecordedIdVisit($idVisit, $idSiteHsr) + { + $siteTable = Common::prefixTable('log_hsr_site'); + $query = sprintf('SELECT lhsr.idvisit + FROM %s lhsr + LEFT JOIN %s lhsrsite ON lhsr.idloghsr=lhsrsite.idloghsr + WHERE lhsr.idvisit = ? and lhsrsite.idsitehsr = ? + LIMIT 1', $this->tablePrefixed, $siteTable); + $id = $this->getDb()->fetchOne($query, array($idVisit, $idSiteHsr)); + return !empty($id); + } + + // $hsrSiteIds => one recording may be long to several actual recordings. + public function record($hsrSiteIds, $idSite, $idVisit, $idHsrView, $idPageview, $url, $serverTime, $userAgent, $resolution, $timeOnPage, $viewportW, $viewportH, $scrollYMaxRelative, $foldYRelative) + { + if ($foldYRelative > self::SCROLL_ACCURACY) { + $foldYRelative = self::SCROLL_ACCURACY; + } + + if ($scrollYMaxRelative > self::SCROLL_ACCURACY) { + $scrollYMaxRelative = self::SCROLL_ACCURACY; + } + + $idLogHsr = $this->findIdLogHsr($idVisit, $idHsrView); + + if (empty($idLogHsr)) { + // to prevent race conditions we use atomic insert. It may lead to more gaps in auto increment but there is + // no way around it + + Piwik::postEvent('HeatmapSessionRecording.trackNewHsrSiteIds', array(&$hsrSiteIds, array('idSite' => $idSite, 'serverTime' => $serverTime, 'idVisit' => $idVisit))); + + if (empty($hsrSiteIds)) { + throw new \Exception('No hsrSiteIds'); + } + + $values = array( + 'idvisit' => $idVisit, + 'idsite' => $idSite, + 'idhsrview' => $idHsrView, + 'idpageview' => $idPageview, + 'server_time' => $serverTime, + 'time_on_page' => $timeOnPage, + 'viewport_w_px' => $viewportW, + 'viewport_h_px' => $viewportH, + 'scroll_y_max_relative' => (int)$scrollYMaxRelative, + 'fold_y_relative' => (int) $foldYRelative, + ); + + $columns = implode('`,`', array_keys($values)); + $bind = array_values($values); + $sql = sprintf('INSERT INTO %s (`%s`) VALUES(?,?,?,?,?,?,?,?,?,?)', $this->tablePrefixed, $columns); + + try { + $result = $this->getDb()->query($sql, $bind); + } catch (\Exception $e) { + if (Db::get()->isErrNo($e, \Piwik\Updater\Migration\Db::ERROR_CODE_DUPLICATE_ENTRY)) { + // race condition where two tried to insert at same time... we need to update instead + + $idLogHsr = $this->findIdLogHsr($idVisit, $idHsrView); + $this->updateRecord($idLogHsr, $timeOnPage, $scrollYMaxRelative); + return $idLogHsr; + } + throw $e; + } + + $all = $this->getDb()->rowCount($result); + + $idLogHsr = $this->getDb()->lastInsertId(); + + if ($all === 1 || $all === '1') { + // was inserted, resolve idaction! would be 2 or 0 on update + // to be efficient we want to resolve idaction only once + $url = PageUrl::normalizeUrl($url); + $ids = TableLogAction::loadIdsAction(array('idaction_url' => array($url['url'], Action::TYPE_PAGE_URL, $url['prefixId']))); + + if (!empty($viewportW)) { + $deviceWidth = (int) $viewportW; + } else { + $deviceWidth = $this->getDeviceWidth($resolution); + } + $deviceType = $this->getDeviceType($hsrSiteIds, $idSite, $userAgent, $deviceWidth); + + $idaction = $ids['idaction_url']; + $this->getDb()->query( + sprintf('UPDATE %s set idaction_url = ?, device_type = ? where idloghsr = ?', $this->tablePrefixed), + array($idaction, $deviceType, $idLogHsr) + ); + + foreach ($hsrSiteIds as $hsrId) { + // for performance reasons we check the limit only on hsr start and we make this way sure to still + // accept all following requests to that hsr + $this->logHsrSite->linkRecord($idLogHsr, $hsrId); + } + } + } else { + $this->updateRecord($idLogHsr, $timeOnPage, $scrollYMaxRelative); + } + + return $idLogHsr; + } + + public function updateRecord($idLogHsr, $timeOnPage, $scrollYMaxRelative) + { + $sql = sprintf( + 'UPDATE %s SET + time_on_page = if(? > time_on_page, ?, time_on_page), + scroll_y_max_relative = if(? > scroll_y_max_relative, ?, scroll_y_max_relative) + WHERE idloghsr = ?', + $this->tablePrefixed + ); + + $bind = array(); + $bind[] = $timeOnPage; + $bind[] = $timeOnPage; + $bind[] = $scrollYMaxRelative; + $bind[] = $scrollYMaxRelative; + $bind[] = $idLogHsr; + + $this->getDb()->query($sql, $bind); + } + + public function getAllRecords() + { + return $this->getDb()->fetchAll('SELECT * FROM ' . $this->tablePrefixed); + } + + public function findLogHsrIdsInVisit($idSite, $idVisit) + { + $rows = Db::fetchAll(sprintf('SELECT idloghsr FROM %s WHERE idvisit = ? and idsite = ?', $this->tablePrefixed), array($idVisit, $idSite)); + + $idLogHsrs = array(); + foreach ($rows as $row) { + $idLogHsrs[] = (int) $row['idloghsr']; + } + + return $idLogHsrs; + } + + public function findDeletedLogHsrIds() + { + // DELETE ALL LOG ENTRIES WHOSE IDSITEHSR DOES NO LONGER EXIST + $rows = Db::fetchAll(sprintf( + 'SELECT DISTINCT log_hsr.idloghsr FROM %s log_hsr LEFT OUTER JOIN %s log_hsr_site ON log_hsr.idloghsr = log_hsr_site.idloghsr WHERE log_hsr_site.idsitehsr IS NULL', + $this->tablePrefixed, + Common::prefixTable('log_hsr_site') + )); + + $idLogHsrsToDelete = array(); + foreach ($rows as $row) { + $idLogHsrsToDelete[] = (int) $row['idloghsr']; + } + + return $idLogHsrsToDelete; + } + + public function deleteIdLogHsrsFromAllTables($idLogHsrsToDelete) + { + if (!is_array($idLogHsrsToDelete)) { + throw new \Exception('idLogHsrsToDelete is not an array'); + } + + if (empty($idLogHsrsToDelete)) { + return; + } + + // we delete them in chunks of 2500 + $idLogHsrsToDelete = array_chunk($idLogHsrsToDelete, 2500); + + $tablesToDelete = array( + Common::prefixTable('log_hsr_event'), + Common::prefixTable('log_hsr_site'), + Common::prefixTable('log_hsr'), + ); + foreach ($idLogHsrsToDelete as $idsToDelete) { + $idsToDelete = array_map('intval', $idsToDelete); + $idsToDelete = implode(',', $idsToDelete); + foreach ($tablesToDelete as $tableToDelete) { + $sql = sprintf('DELETE FROM %s WHERE idloghsr IN(%s)', $tableToDelete, $idsToDelete); + Db::query($sql); + } + } + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Dao/LogHsrBlob.php b/files/plugin-HeatmapSessionRecording-5.2.4/Dao/LogHsrBlob.php new file mode 100644 index 0000000..9945d50 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Dao/LogHsrBlob.php @@ -0,0 +1,180 @@ +tablePrefixed = Common::prefixTable($this->table); + } + + private function getDb() + { + if (!isset($this->db)) { + $this->db = Db::get(); + } + return $this->db; + } + + public function install() + { + DbHelper::createTable($this->table, " + `idhsrblob` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `hash` INT(10) UNSIGNED NOT NULL, + `compressed` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, + `value` MEDIUMBLOB NULL DEFAULT NULL, + PRIMARY KEY (`idhsrblob`), + INDEX (`hash`)"); + + // we always build the hash on the raw text for simplicity + } + + public function uninstall() + { + Db::query(sprintf('DROP TABLE IF EXISTS `%s`', $this->tablePrefixed)); + } + + public function findEntry($textHash, $text, $textCompressed) + { + $sql = sprintf('SELECT idhsrblob FROM %s WHERE `hash` = ? and (`value` = ? or `value` = ?) LIMIT 1', $this->tablePrefixed); + $id = $this->getDb()->fetchOne($sql, array($textHash, $text, $textCompressed)); + + return $id; + } + + public function createEntry($textHash, $text, $isCompressed) + { + $sql = sprintf('INSERT INTO %s (`hash`, `compressed`, `value`) VALUES(?,?,?) ', $this->tablePrefixed); + $this->getDb()->query($sql, array($textHash, (int) $isCompressed, $text)); + + return $this->getDb()->lastInsertId(); + } + + public function record($text) + { + if ($text === null || $text === false) { + return null; + } + + $textHash = abs(crc32($text)); + $textCompressed = $this->compress($text); + + $id = $this->findEntry($textHash, $text, $textCompressed); + + if (!empty($id)) { + return $id; + } + + $isCompressed = 0; + if ($text !== $textCompressed && strlen($textCompressed) < strlen($text)) { + // detect if it is more efficient to store compressed or raw text + $text = $textCompressed; + $isCompressed = 1; + } + + return $this->createEntry($textHash, $text, $isCompressed); + } + + public function deleteUnusedBlobEntries() + { + $eventTable = Common::prefixTable('log_hsr_event'); + $blobTable = Common::prefixTable('log_hsr_blob'); + + $blobEntries = Db::fetchAll('SELECT distinct idhsrblob FROM ' . $eventTable . ' LIMIT 2'); + $blobEntries = array_filter($blobEntries, function ($val) { + return $val['idhsrblob'] !== null; + }); // remove null values. + + if (empty($blobEntries)) { + // no longer any blobs in use... delete all blobs + $sql = 'DELETE FROM ' . $blobTable; + Db::query($sql); + return $sql; + } + + $indexes = Db::fetchAll('SHOW INDEX FROM ' . $eventTable); + $indexSql = ''; + foreach ($indexes as $index) { + if ( + (!empty($index['Column_name']) && !empty($index['Key_name']) && $index['Column_name'] === 'idhsrblob') + || (!empty($index['Key_name']) && $index['Key_name'] === 'idhsrblob') + || (!empty($index['Key_name']) && $index['Key_name'] === 'index_idhsrblob') + ) { + $indexSql = 'FORCE INDEX FOR JOIN (' . $index['Key_name'] . ')'; + break; + } + } + + $sql = sprintf('DELETE hsrblob + FROM %s hsrblob + LEFT JOIN %s hsrevent %s on hsrblob.idhsrblob = hsrevent.idhsrblob + WHERE hsrevent.idloghsr is null', $blobTable, $eventTable, $indexSql); + + Db::query($sql); + return $sql; + } + + public function getAllRecords() + { + $blobs = $this->getDb()->fetchAll('SELECT * FROM ' . $this->tablePrefixed); + return $this->enrichRecords($blobs); + } + + private function enrichRecords($blobs) + { + if (!empty($blobs)) { + foreach ($blobs as $index => &$blob) { + if (!empty($blob['compressed'])) { + $blob['value'] = $this->uncompress($blob['value']); + } + } + } + + return $blobs; + } + + private function compress($data) + { + if (!empty($data)) { + return gzcompress($data); + } + + return $data; + } + + private function uncompress($data) + { + if (!empty($data)) { + return gzuncompress($data); + } + + return $data; + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Dao/LogHsrEvent.php b/files/plugin-HeatmapSessionRecording-5.2.4/Dao/LogHsrEvent.php new file mode 100644 index 0000000..6a64065 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Dao/LogHsrEvent.php @@ -0,0 +1,166 @@ +tablePrefixed = Common::prefixTable($this->table); + $this->logBlobHsr = $logBlobHsr; + } + + private function getDb() + { + if (!isset($this->db)) { + $this->db = Db::get(); + } + return $this->db; + } + + public function install() + { + DbHelper::createTable($this->table, " + `idhsrevent` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + `idloghsr` INT(10) UNSIGNED NOT NULL, + `time_since_load` BIGINT(8) UNSIGNED NOT NULL DEFAULT 0, + `event_type` TINYINT UNSIGNED NOT NULL DEFAULT 0, + `idselector` INT(10) UNSIGNED NULL DEFAULT NULL, + `x` SMALLINT(5) NOT NULL DEFAULT 0, + `y` SMALLINT(5) NOT NULL DEFAULT 0, + `idhsrblob` INT(10) UNSIGNED DEFAULT NULL, + PRIMARY KEY(`idhsrevent`), + INDEX idloghsr (`idloghsr`), + INDEX idhsrblob (`idhsrblob`)"); + // x and y is not unsigned on purpose as it may hold rarely a negative value + } + + public function uninstall() + { + Db::query(sprintf('DROP TABLE IF EXISTS `%s`', $this->tablePrefixed)); + } + + public function record($idloghsr, $timeSinceLoad, $eventType, $idSelector, $x, $y, $text) + { + if ($x > self::MAX_SIZE) { + $x = self::MAX_SIZE; + } + + if ($y > self::MAX_SIZE) { + $y = self::MAX_SIZE; + } + + if ($x === null || $x === false) { + $x = 0; + } + + if ($y === null || $y === false) { + $y = 0; + } + + $idHsrBlob = $this->logBlobHsr->record($text); + + $values = array( + 'idloghsr' => $idloghsr, + 'time_since_load' => $timeSinceLoad, + 'event_type' => $eventType, + 'idselector' => $idSelector, + 'x' => $x, + 'y' => $y, + 'idhsrblob' => $idHsrBlob, + ); + + $columns = implode('`,`', array_keys($values)); + + $sql = sprintf('INSERT INTO %s (`%s`) VALUES(?,?,?,?,?,?,?) ', $this->tablePrefixed, $columns); + + $bind = array_values($values); + + $this->getDb()->query($sql, $bind); + } + + public function getEventsForPageview($idLogHsr) + { + $sql = sprintf('SELECT %1$s.time_since_load, %1$s.event_type, %1$s.x, %1$s.y, %2$s.name as selector, %3$s.value as text, %3$s.compressed + FROM %1$s + LEFT JOIN %2$s ON %1$s.idselector = %2$s.idaction + LEFT JOIN %3$s ON %1$s.idhsrblob = %3$s.idhsrblob + WHERE %1$s.idloghsr = ? and %1$s.event_type != ? + ORDER BY time_since_load ASC', $this->tablePrefixed, Common::prefixTable('log_action'), Common::prefixTable('log_hsr_blob')); + + $rows = $this->getDb()->fetchAll($sql, array($idLogHsr, RequestProcessor::EVENT_TYPE_CSS)); + foreach ($rows as $index => $row) { + if (!empty($row['compressed'])) { + $rows[$index]['text'] = gzuncompress($row['text']); + } + unset($rows[$index]['compressed']); + } + return $rows; + } + + public function getCssEvents($idSiteHsr, $idLoghsr = '') + { + //idLogHsr will be empty in case of heatmaps, we cannot use it in where clause to resolve that, when its heatmap the where condition is `AND 1=1` and for session recording its `AND x.idloghsr=$idLoghsr` + $idLogHsrLhs = '1'; + $idLogHsrRhs = '1'; + if (!empty($idLoghsr)) { + $idLogHsrLhs = 'x.idloghsr'; + $idLogHsrRhs = $idLoghsr; + } + $sql = sprintf('SELECT distinct z.idhsrblob,a.name as url, z.value as text, z.compressed + FROM %2$s x,%3$s y,%4$s z,%5$s a + WHERE x.idsitehsr=? AND %1$s=? and y.event_type=? and x.idloghsr=y.idloghsr and y.idhsrblob = z.idhsrblob and a.idaction=y.idselector + order by z.idhsrblob ASC', $idLogHsrLhs, Common::prefixTable('log_hsr_site'), Common::prefixTable('log_hsr_event'), Common::prefixTable('log_hsr_blob'), Common::prefixTable('log_action')); + + $rows = $this->getDb()->fetchAll($sql, array($idSiteHsr, $idLogHsrRhs, RequestProcessor::EVENT_TYPE_CSS)); + foreach ($rows as $index => $row) { + if (!empty($row['compressed'])) { + $rows[$index]['text'] = gzuncompress($row['text']); + } + unset($rows[$index]['compressed']); + } + return $rows; + } + + public function getAllRecords() + { + return $this->getDb()->fetchAll('SELECT * FROM ' . $this->tablePrefixed); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Dao/LogHsrSite.php b/files/plugin-HeatmapSessionRecording-5.2.4/Dao/LogHsrSite.php new file mode 100644 index 0000000..708aba3 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Dao/LogHsrSite.php @@ -0,0 +1,141 @@ +tablePrefixed = Common::prefixTable($this->table); + } + + private function getDb() + { + if (!isset($this->db)) { + $this->db = Db::get(); + } + return $this->db; + } + + public function install() + { + // it actually also has the advantage that removing an entry will be fast because when a user clicks on + // "delete heatmap" we could only remove this entry, and then have a daily cronjob to delete all entries that are + // no longer linked. instead of having to directly delete all data. Also it is more efficient to track when eg + // a session and a heatmap is being recording at the same time or when several heatmaps are being recorded at once + DbHelper::createTable($this->table, " + `idsitehsr` INT(10) UNSIGNED NOT NULL, + `idloghsr` INT(10) UNSIGNED NOT NULL, + PRIMARY KEY(`idsitehsr`, `idloghsr`), + INDEX index_idloghsr (`idloghsr`)"); + } + + public function uninstall() + { + Db::query(sprintf('DROP TABLE IF EXISTS `%s`', $this->tablePrefixed)); + } + + public function linkRecord($idLogHsr, $idSiteHsr) + { + $bind = array($idLogHsr,$idSiteHsr); + $sql = sprintf('INSERT INTO %s (`idloghsr`, `idsitehsr`) VALUES(?,?)', $this->tablePrefixed); + + try { + $this->getDb()->query($sql, $bind); + } catch (\Exception $e) { + if (Db::get()->isErrNo($e, \Piwik\Updater\Migration\Db::ERROR_CODE_DUPLICATE_ENTRY)) { + return; + } + throw $e; + } + } + + // should be fast as covered index + public function getNumPageViews($idSiteHsr) + { + $sql = sprintf('SELECT count(*) as numsamples FROM %s WHERE idsitehsr = ?', $this->tablePrefixed); + + return (int) $this->getDb()->fetchOne($sql, array($idSiteHsr)); + } + + // should be fast as covered index + public function getNumSessions($idSiteHsr) + { + $sql = sprintf( + 'SELECT count(distinct idvisit) + FROM %s loghsrsite + left join %s loghsr on loghsr.idloghsr = loghsrsite.idloghsr + left join %s loghsrevent on loghsr.idloghsr = loghsrevent.idloghsr and loghsrevent.event_type = %s + WHERE loghsrsite.idsitehsr = ? and loghsrevent.idhsrblob is not null', + $this->tablePrefixed, + Common::prefixTable('log_hsr'), + Common::prefixTable('log_hsr_event'), + RequestProcessor::EVENT_TYPE_INITIAL_DOM + ); + + return (int) $this->getDb()->fetchOne($sql, array($idSiteHsr)); + } + + public function unlinkRecord($idLogHsr, $idSiteHsr) + { + $sql = sprintf('DELETE FROM %s WHERE idsitehsr = ? and idloghsr = ?', $this->tablePrefixed); + + return $this->getDb()->query($sql, array($idSiteHsr, $idLogHsr)); + } + + public function unlinkSiteRecords($idSiteHsr) + { + $sql = sprintf('DELETE FROM %s WHERE idsitehsr = ?', $this->tablePrefixed); + + return $this->getDb()->query($sql, array($idSiteHsr)); + } + + public function getAllRecords() + { + return $this->getDb()->fetchAll('SELECT * FROM ' . $this->tablePrefixed); + } + + public function deleteNoLongerNeededRecords() + { + // DELETE ALL linked LOG ENTRIES WHOSE idsite does no longer exist or was removed + // we delete links for removed site_hsr entries, and for site_hsr entries with status deleted + // this query should only delete entries when they were deleted manually in the database basically. + // otherwise the application takes already care of removing the needed links + $sql = sprintf( + 'DELETE FROM %1$s WHERE %1$s.idsitehsr NOT IN (select site_hsr.idsitehsr from %2$s site_hsr where site_hsr.status = "%3$s" or site_hsr.status = "%4$s")', + Common::prefixTable('log_hsr_site'), + Common::prefixTable('site_hsr'), + SiteHsrDao::STATUS_ACTIVE, + SiteHsrDao::STATUS_ENDED + ); + + Db::query($sql); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Dao/SiteHsrDao.php b/files/plugin-HeatmapSessionRecording-5.2.4/Dao/SiteHsrDao.php new file mode 100644 index 0000000..037d00c --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Dao/SiteHsrDao.php @@ -0,0 +1,422 @@ +tablePrefixed = Common::prefixTable($this->table); + } + + private function getDb() + { + if (!isset($this->db)) { + $this->db = Db::get(); + } + return $this->db; + } + + public function install() + { + DbHelper::createTable($this->table, " + `idsitehsr` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `idsite` INT(10) UNSIGNED NOT NULL, + `name` VARCHAR(" . Name::MAX_LENGTH . ") NOT NULL, + `sample_rate` DECIMAL(4,1) UNSIGNED NOT NULL DEFAULT " . SampleRate::MAX_RATE . ", + `sample_limit` MEDIUMINT(8) UNSIGNED NOT NULL DEFAULT 1000, + `match_page_rules` TEXT DEFAULT '', + `excluded_elements` TEXT DEFAULT '', + `record_type` TINYINT(1) UNSIGNED DEFAULT 0, + `page_treemirror` MEDIUMBLOB NULL DEFAULT NULL, + `screenshot_url` VARCHAR(300) NULL DEFAULT NULL, + `breakpoint_mobile` SMALLINT(5) UNSIGNED NOT NULL DEFAULT 0, + `breakpoint_tablet` SMALLINT(5) UNSIGNED NOT NULL DEFAULT 0, + `min_session_time` SMALLINT(5) UNSIGNED NOT NULL DEFAULT 0, + `requires_activity` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, + `capture_keystrokes` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, + `created_date` DATETIME NOT NULL, + `updated_date` DATETIME NOT NULL, + `status` VARCHAR(10) NOT NULL DEFAULT '" . self::STATUS_ACTIVE . "', + `capture_manually` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY(`idsitehsr`), + INDEX index_status_idsite (`status`, `idsite`), + INDEX index_idsite_record_type (`idsite`, `record_type`)"); + } + + public function createHeatmapRecord($idSite, $name, $sampleLimit, $sampleRate, $matchPageRules, $excludedElements, $screenshotUrl, $breakpointMobile, $breakpointTablet, $status, $captureDomManually, $createdDate) + { + $columns = array( + 'idsite' => $idSite, + 'name' => $name, + 'sample_limit' => $sampleLimit, + 'match_page_rules' => $matchPageRules, + 'sample_rate' => $sampleRate, + 'status' => $status, + 'record_type' => self::RECORD_TYPE_HEATMAP, + 'created_date' => $createdDate, + 'updated_date' => $createdDate, + 'capture_manually' => !empty($captureDomManually) ? 1 : 0, + ); + + if (!empty($excludedElements)) { + $columns['excluded_elements'] = $excludedElements; + } + + if (!empty($screenshotUrl)) { + $columns['screenshot_url'] = $screenshotUrl; + } + if ($breakpointMobile !== false && $breakpointMobile !== null) { + $columns['breakpoint_mobile'] = $breakpointMobile; + } + + if ($breakpointTablet !== false && $breakpointTablet !== null) { + $columns['breakpoint_tablet'] = $breakpointTablet; + } + + return $this->insertColumns($columns); + } + + public function createSessionRecord($idSite, $name, $sampleLimit, $sampleRate, $matchPageRules, $minSessionTime, $requiresActivity, $captureKeystrokes, $status, $createdDate) + { + $columns = array( + 'idsite' => $idSite, + 'name' => $name, + 'sample_limit' => $sampleLimit, + 'match_page_rules' => $matchPageRules, + 'sample_rate' => $sampleRate, + 'status' => $status, + 'record_type' => self::RECORD_TYPE_SESSION, + 'min_session_time' => !empty($minSessionTime) ? $minSessionTime : 0, + 'requires_activity' => !empty($requiresActivity) ? 1 : 0, + 'capture_keystrokes' => !empty($captureKeystrokes) ? 1 : 0, + 'created_date' => $createdDate, + 'updated_date' => $createdDate, + ); + + return $this->insertColumns($columns); + } + + private function insertColumns($columns) + { + $columns = $this->encodeFieldsWhereNeeded($columns); + + $bind = array_values($columns); + $placeholder = Common::getSqlStringFieldsArray($columns); + + $sql = sprintf( + 'INSERT INTO %s (`%s`) VALUES(%s)', + $this->tablePrefixed, + implode('`,`', array_keys($columns)), + $placeholder + ); + + $this->getDb()->query($sql, $bind); + + $idSiteHsr = $this->getDb()->lastInsertId(); + + return (int) $idSiteHsr; + } + + protected function getCurrentTime() + { + return Date::now()->getDatetime(); + } + + public function updateHsrColumns($idSite, $idSiteHsr, $columns) + { + $columns = $this->encodeFieldsWhereNeeded($columns); + + if (!empty($columns)) { + if (!isset($columns['updated_date'])) { + $columns['updated_date'] = $this->getCurrentTime(); + } + + if (!empty($columns['page_treemirror'])) { + $columns['capture_manually'] = 0; + } elseif (!empty($columns['capture_manually'])) { + $columns['page_treemirror'] = null; + } + + $fields = array(); + $bind = array(); + foreach ($columns as $key => $value) { + $fields[] = ' ' . $key . ' = ?'; + $bind[] = $value; + } + $fields = implode(',', $fields); + + $query = sprintf('UPDATE %s SET %s WHERE idsitehsr = ? AND idsite = ?', $this->tablePrefixed, $fields); + $bind[] = (int) $idSiteHsr; + $bind[] = (int) $idSite; + + // we do not use $db->update() here as this method is as well used in Tracker mode and the tracker DB does not + // support "->update()". Therefore we use the query method where we know it works with tracker and regular DB + $this->getDb()->query($query, $bind); + } + } + + public function hasRecords($idSite, $recordType) + { + $sql = sprintf('SELECT idsite FROM %s WHERE record_type = ? and `status` IN(?,?) and idsite = ? LIMIT 1', $this->tablePrefixed); + $records = $this->getDb()->fetchRow($sql, array($recordType, self::STATUS_ENDED, self::STATUS_ACTIVE, $idSite)); + + return !empty($records); + } + + public function deleteRecord($idSite, $idSiteHsr) + { + // now we delete the heatmap manually and it should notice all log entries for that heatmap are no longer needed + $sql = sprintf('DELETE FROM %s WHERE idsitehsr = ? and idsite = ?', $this->tablePrefixed); + Db::query($sql, array($idSiteHsr, $idSite)); + } + + private function getAllFieldNames($includePageTreeMirror) + { + $fields = '`idsitehsr`,`idsite`,`name`, `sample_rate`, `sample_limit`, `match_page_rules`, `excluded_elements`, `record_type`, '; + if (!empty($includePageTreeMirror)) { + $fields .= '`page_treemirror`,'; + } + $fields .= '`screenshot_url`, `breakpoint_mobile`, `breakpoint_tablet`, `min_session_time` , `requires_activity`, `capture_keystrokes`, `created_date`, `updated_date`, `status`, `capture_manually`'; + return $fields; + } + + public function getRecords($idSite, $recordType, $includePageTreeMirror) + { + $fields = $this->getAllFieldNames($includePageTreeMirror); + $sql = sprintf('SELECT ' . $fields . ' FROM %s WHERE record_type = ? and `status` IN(?,?,?) and idsite = ? order by created_date desc', $this->tablePrefixed); + $records = $this->getDb()->fetchAll($sql, array($recordType, self::STATUS_ENDED, self::STATUS_ACTIVE, self::STATUS_PAUSED, $idSite)); + + return $this->enrichRecords($records); + } + + public function getRecord($idSite, $idSiteHsr, $recordType) + { + $sql = sprintf('SELECT * FROM %s WHERE record_type = ? and `status` IN(?,?,?) and idsite = ? and idsitehsr = ? LIMIT 1', $this->tablePrefixed); + $record = $this->getDb()->fetchRow($sql, array($recordType, self::STATUS_ENDED, self::STATUS_ACTIVE, self::STATUS_PAUSED, $idSite, $idSiteHsr)); + + return $this->enrichRecord($record); + } + + public function getNumRecordsTotal($recordType) + { + $sql = sprintf('SELECT count(*) as total FROM %s WHERE record_type = ? and `status` IN(?,?,?)', $this->tablePrefixed); + return $this->getDb()->fetchOne($sql, array($recordType, self::STATUS_ENDED, self::STATUS_ACTIVE, self::STATUS_PAUSED)); + } + + public function hasActiveRecordsAcrossSites() + { + $query = $this->getQueryActiveRequests(); + + $sql = sprintf("SELECT count(*) as numrecords FROM %s WHERE %s LIMIT 1", $this->tablePrefixed, $query['where']); + $numRecords = $this->getDb()->fetchOne($sql, $query['bind']); + + return !empty($numRecords); + } + + private function getQueryActiveRequests() + { + // for sessions we also need to return ended sessions to make sure to record all page views once a user takes part in + // a session recording. Otherwise as soon as the limit of sessions has reached, it would stop recording any further page views in already started session recordings + + // we only fetch recorded sessions with status ended for the last 24 hours to not expose any potential config and for faster processing etc + $oneDayAgo = Date::now()->subDay(1)->getDatetime(); + + return array( + 'where' => '(status = ? or (record_type = ? and status = ? and updated_date > ?))', + 'bind' => array(self::STATUS_ACTIVE, self::RECORD_TYPE_SESSION, self::STATUS_ENDED, $oneDayAgo) + ); + } + + /** + * For performance reasons the page_treemirror will be read only partially! + * @param $idSite + * @return mixed + * @throws \Piwik\Tracker\Db\DbException + */ + public function getActiveRecords($idSite) + { + $query = $this->getQueryActiveRequests(); + + $bind = $query['bind']; + $bind[] = $idSite; + + $fields = $this->getAllFieldNames(false); + // we want to avoid needing to read all the entire treemirror every time the tracking cache will be updated + // as in worst case every treemirror can be 16MB or in rare cases even more. Most of the time it's only like 50KB or so + // but we want to avoid fetching heaps of unneeded data + $fields .= ', SUBSTRING(page_treemirror, 1, 10) as page_treemirror'; + + // NOTE: If you adjust this query, you might also + $sql = sprintf("SELECT " . $fields . " FROM %s WHERE %s and idsite = ? ORDER BY idsitehsr asc", $this->tablePrefixed, $query['where']); + $records = $this->getDb()->fetchAll($sql, $bind); + + foreach ($records as $index => $record) { + if (!empty($record['page_treemirror'])) { + // avoids an error when it tries to uncompress + $records[$index]['page_treemirror'] = $this->compress($record['page_treemirror']); + } + } + + return $this->enrichRecords($records); + } + + private function enrichRecords($records) + { + if (empty($records)) { + return $records; + } + + foreach ($records as $index => $record) { + $records[$index] = $this->enrichRecord($record); + } + + return $records; + } + + private function enrichRecord($record) + { + if (empty($record)) { + return $record; + } + + $record['idsitehsr'] = (int) $record['idsitehsr']; + $record['idsite'] = (int) $record['idsite']; + $record['sample_rate'] = number_format($record['sample_rate'], 1, '.', ''); + $record['record_type'] = (int) $record['record_type']; + $record['sample_limit'] = (int) $record['sample_limit']; + $record['min_session_time'] = (int) $record['min_session_time']; + $record['breakpoint_mobile'] = (int) $record['breakpoint_mobile']; + $record['breakpoint_tablet'] = (int) $record['breakpoint_tablet']; + $record['match_page_rules'] = $this->decodeField($record['match_page_rules']); + $record['requires_activity'] = !empty($record['requires_activity']); + $record['capture_keystrokes'] = !empty($record['capture_keystrokes']); + $record['capture_manually'] = !empty($record['capture_manually']) ? 1 : 0; + + if (!empty($record['page_treemirror'])) { + $record['page_treemirror'] = $this->uncompress($record['page_treemirror']); + } else { + $record['page_treemirror'] = ''; + } + + return $record; + } + + public function uninstall() + { + Db::query(sprintf('DROP TABLE IF EXISTS `%s`', $this->tablePrefixed)); + } + + public function getAllEntities() + { + $records = $this->getDb()->fetchAll('SELECT * FROM ' . $this->tablePrefixed); + + return $this->enrichRecords($records); + } + + private function encodeFieldsWhereNeeded($columns) + { + foreach ($columns as $column => $value) { + if ($column === 'match_page_rules') { + $columns[$column] = $this->encodeField($value); + } elseif ($column === 'page_treemirror') { + if (!empty($value)) { + $columns[$column] = $this->compress($value); + } else { + $columns[$column] = null; + } + } elseif (in_array($column, array('breakpoint_mobile', 'breakpoint_tablet', 'min_session_time', 'sample_rate'), $strict = true)) { + if ($value > self::MAX_SMALLINT) { + $columns[$column] = self::MAX_SMALLINT; + } + } elseif (in_array($column, array('requires_activity', 'capture_keystrokes'), $strict = true)) { + if (!empty($value)) { + $columns[$column] = 1; + } else { + $columns[$column] = 0; + } + } + } + + return $columns; + } + + private function compress($data) + { + if (!empty($data)) { + return gzcompress($data); + } + + return $data; + } + + private function uncompress($data) + { + if (!empty($data)) { + return gzuncompress($data); + } + + return $data; + } + + private function encodeField($field) + { + if (empty($field) || !is_array($field)) { + $field = array(); + } + + return json_encode($field); + } + + private function decodeField($field) + { + if (!empty($field)) { + $field = @json_decode($field, true); + } + + if (empty($field) || !is_array($field)) { + $field = array(); + } + + return $field; + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/DataTable/Filter/EnrichRecordedSessions.php b/files/plugin-HeatmapSessionRecording-5.2.4/DataTable/Filter/EnrichRecordedSessions.php new file mode 100644 index 0000000..0a306e7 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/DataTable/Filter/EnrichRecordedSessions.php @@ -0,0 +1,73 @@ +getRowsWithoutSummaryRow() as $row) { + if ($isAnonymous) { + foreach (self::getBlockedFields() as $blockedField) { + if ($row->getColumn($blockedField) !== false) { + $row->setColumn($blockedField, false); + } + } + } else { + $row->setColumn('idvisitor', bin2hex($row->getColumn('idvisitor'))); + } + } + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Diagnostic/ConfigsPhpCheck.php b/files/plugin-HeatmapSessionRecording-5.2.4/Diagnostic/ConfigsPhpCheck.php new file mode 100644 index 0000000..c436154 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Diagnostic/ConfigsPhpCheck.php @@ -0,0 +1,112 @@ +translator = $translator; + } + + public function execute() + { + $label = $this->translator->translate('Heatmap & Session Recording Tracking'); + + $site = new Model(); + $idSites = $site->getSitesId(); + $idSite = array_shift($idSites); + + $baseUrl = SettingsPiwik::getPiwikUrl(); + if (!Common::stringEndsWith($baseUrl, '/')) { + $baseUrl .= '/'; + } + + $baseUrl .= HeatmapSessionRecording::getPathPrefix() . '/'; + $baseUrl .= 'HeatmapSessionRecording/configs.php'; + $testUrl = $baseUrl . '?idsite=' . (int) $idSite . '&trackerid=5lX6EM&url=http%3A%2F%2Ftest.test%2F'; + + $error = null; + $response = null; + + $errorResult = $this->translator->translate('HeatmapSessionRecording_ConfigsPhpErrorResult'); + $manualCheck = $this->translator->translate('HeatmapSessionRecording_ConfigsPhpManualCheck'); + + if (method_exists('\Piwik\SettingsPiwik', 'isInternetEnabled')) { + $isInternetEnabled = SettingsPiwik::isInternetEnabled(); + if (!$isInternetEnabled) { + $unknown = $this->translator->translate('HeatmapSessionRecording_ConfigsInternetDisabled', $testUrl) . ' ' . $manualCheck; + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $unknown)); + } + } + + try { + $response = Http::sendHttpRequest($testUrl, $timeout = 2); + } catch (\Exception $e) { + $error = $e->getMessage(); + } + + if (!empty($response)) { + $response = Common::mb_strtolower($response); + if (strpos($response, 'piwik.heatmapsessionrecording') !== false) { + $message = $this->translator->translate('HeatmapSessionRecording_ConfigsPhpSuccess', $baseUrl); + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK, $message)); + } elseif (strpos($response, 'forbidden') !== false || strpos($response, ' forbidden') !== false || strpos($response, ' denied ') !== false || strpos($response, '403 ') !== false || strpos($response, '404 ') !== false) { + // Likely the server returned eg a 403 HTML + $message = $this->translator->translate('HeatmapSessionRecording_ConfigsPhpNotAccessible', array($testUrl)) . ' ' . $errorResult; + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_ERROR, $message)); + } + } + + if (!empty($error)) { + $error = Common::mb_strtolower($error); + + if (strpos($error, 'forbidden ') !== false || strpos($error, ' forbidden') !== false || strpos($error, 'denied ') !== false || strpos($error, '403 ') !== false || strpos($error, '404 ') !== false) { + $message = $this->translator->translate('HeatmapSessionRecording_ConfigsPhpNotAccessible', array($testUrl)) . ' ' . $errorResult; + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_ERROR, $message)); + } + + if (strpos($error, 'ssl ') !== false || strpos($error, ' ssl') !== false || strpos($error, 'self signed') !== false || strpos($error, 'certificate ') !== false) { + $message = $this->translator->translate('HeatmapSessionRecording_ConfigsPhpSelfSignedError', array($testUrl)) . ' ' . $manualCheck; + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $message)); + } + + $unknownError = $this->translator->translate('HeatmapSessionRecording_ConfigsPhpUnknownError', array($testUrl, $error)) . ' ' . $errorResult; + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $unknownError)); + } + + $unknown = $this->translator->translate('HeatmapSessionRecording_ConfigsPhpUnknown', $testUrl) . $manualCheck; + return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $unknown)); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/HeatmapSessionRecording.php b/files/plugin-HeatmapSessionRecording-5.2.4/HeatmapSessionRecording.php new file mode 100644 index 0000000..f9f7059 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/HeatmapSessionRecording.php @@ -0,0 +1,886 @@ +register_route('HeatmapSessionRecording', 'getHeatmap'); + $api->register_route('HeatmapSessionRecording', 'getHeatmaps'); + $api->register_route('HeatmapSessionRecording', 'getRecordedHeatmapMetadata'); + $api->register_route('HeatmapSessionRecording', 'getRecordedHeatmap'); + + $api->register_route('HeatmapSessionRecording', 'getSessionRecording'); + $api->register_route('HeatmapSessionRecording', 'getSessionRecordings'); + $api->register_route('HeatmapSessionRecording', 'getRecordedSessions'); + $api->register_route('HeatmapSessionRecording', 'getRecordedSession'); + }); + + /** + * @param array $actions + * @param \WP_Post $post + * + * @return mixed + */ + function add_new_heat_map_link($actions, $post) + { + if ( + !$post + || !is_plugin_active('matomo/matomo.php') + || !current_user_can('write_matomo') + ) { + return $actions; + } + + if ($post->post_status !== 'publish') { + // the permalink url wouldn't be correct yet for unpublished post + return $actions; + } + + $postUrl = get_permalink($post); + $rules = array(array( + 'attribute' => 'url', + 'type' => 'equals_simple', + 'inverted' => 0, + 'value' => $postUrl + )); + + $hsrParams = array( + 'idSite' => 1, + 'idSiteHsr' => 0, + 'name' => $post->post_title, + // Encoded to avoid pitfalls of decoding multi-dimensional array URL params in JavaScript + 'matchPageRules' => json_encode($rules) + ); + + $url = Menu::get_matomo_reporting_url( + 'HeatmapSessionRecording_Heatmaps', + 'HeatmapSessionRecording_ManageHeatmaps', + $hsrParams + ); + + $actions['create_heatmap'] = 'Create Heatmap'; + return $actions; + } + + function get_matomo_heatmaps() + { + static $heatmaps_cached; + + global $wpdb; + + if (!isset($heatmaps_cached)) { + $site = new Site(); + $idsite = $site->get_current_matomo_site_id(); + + if (!$idsite) { + $heatmaps_cached = array(); // prevent it not being executed again + } else { + $wpDbSettings = new \WpMatomo\Db\Settings(); + $tableName = $wpDbSettings->prefix_table_name('site_hsr'); + $idsite = (int) $idsite;// needed cause we don't bind parameters below + + $heatmaps_cached = $wpdb->get_results( + "select * from $tableName WHERE record_type = 1 AND idsite = $idsite AND status != 'deleted'", + ARRAY_A + ); + } + } + return $heatmaps_cached; + } + + /** + * @param array $actions + * @param \WP_Post $post + * + * @return mixed + */ + function add_view_heat_map_link($actions, $post) + { + if ( + !$post + || !is_plugin_active('matomo/matomo.php') + || !current_user_can('write_matomo') + ) { + return $actions; + } + + $heatmaps = get_matomo_heatmaps(); + + if (empty($heatmaps)) { + return $actions; + } + + $postUrl = get_permalink($post); + + if (!$postUrl) { + return $actions; + } + + if (class_exists(Bootstrap::class)) { + Bootstrap::do_bootstrap(); + } + + require_once('Tracker/PageRuleMatcher.php'); + require_once('Tracker/HsrMatcher.php'); + + $heatmaps = array_values(array_filter($heatmaps, function ($heatmap) use ($postUrl) { + $systemSettings = StaticContainer::get(SystemSettings::class); + $includedCountries = $systemSettings->getIncludedCountries(); + return HsrMatcher::matchesAllPageRules(json_decode($heatmap['match_page_rules'], true), $postUrl) && HsrMatcher::isIncludedCountry($includedCountries); + })); + + $numMatches = count($heatmaps); + foreach ($heatmaps as $i => $heatmap) { + $url = Menu::get_matomo_reporting_url( + 'HeatmapSessionRecording_Heatmaps', + $heatmap['idsitehsr'], + array() + ); + $linkText = 'View Heatmap'; + if ($numMatches > 1) { + $linkText .= ' #' . ($i + 1); + } + $actions['view_heatmap_' . $i] = + '' . esc_html($linkText) . ''; + } + + return $actions; + } +} + +class HeatmapSessionRecording extends \Piwik\Plugin +{ + public const EMBED_SESSION_TIME = 43200; // half day in seconds + public const ULR_PARAM_FORCE_SAMPLE = 'pk_hsr_forcesample'; + public const ULR_PARAM_FORCE_CAPTURE_SCREEN = 'pk_hsr_capturescreen'; + public const EMBED_SESSION_NAME = 'HSR_EMBED_SESSID'; + + public const TRACKER_READY_HOOK_NAME = '/*!! hsrTrackerReadyHook */'; + public const TRACKER_READY_HOOK_NAME_WHEN_MINIFIED = '/*!!! hsrTrackerReadyHook */'; + + public function registerEvents() + { + return array( + 'Db.getActionReferenceColumnsByTable' => 'addActionReferenceColumnsByTable', + 'Tracker.Cache.getSiteAttributes' => 'addSiteTrackerCache', + 'AssetManager.getStylesheetFiles' => 'getStylesheetFiles', + 'AssetManager.getJavaScriptFiles' => 'getJsFiles', + 'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys', + 'Template.jsGlobalVariables' => 'addJsGlobalVariables', + 'Category.addSubcategories' => 'addSubcategories', + 'SitesManager.deleteSite.end' => 'onDeleteSite', + 'Tracker.PageUrl.getQueryParametersToExclude' => 'getQueryParametersToExclude', + 'Widget.addWidgetConfigs' => 'addWidgetConfigs', + 'System.addSystemSummaryItems' => 'addSystemSummaryItems', + 'API.HeatmapSessionRecording.addHeatmap.end' => 'updatePiwikTracker', + 'API.HeatmapSessionRecording.addSessionRecording.end' => 'updatePiwikTracker', + 'CustomJsTracker.shouldAddTrackerFile' => 'shouldAddTrackerFile', + 'Updater.componentUpdated' => 'installHtAccess', + 'Live.visitorLogViewBeforeActionsInfo' => 'visitorLogViewBeforeActionsInfo', + 'Widgetize.shouldEmbedIframeEmpty' => 'shouldEmbedIframeEmpty', + 'Session.beforeSessionStart' => 'changeSessionLengthIfEmbedPage', + 'TwoFactorAuth.requiresTwoFactorAuthentication' => 'requiresTwoFactorAuthentication', + 'API.getPagesComparisonsDisabledFor' => 'getPagesComparisonsDisabledFor', + 'CustomJsTracker.manipulateJsTracker' => 'disableHeatmapsDefaultIfNeeded', + 'AssetManager.addStylesheets' => [ + 'function' => 'addStylesheets', + 'after' => true, + ], + 'Db.getTablesInstalled' => 'getTablesInstalled' + ); + } + + public function disableHeatmapsDefaultIfNeeded(&$content) + { + $settings = StaticContainer::get(SystemSettings::class); + if ($settings->disableTrackingByDefault->getValue()) { + $replace = 'Matomo.HeatmapSessionRecording._setDisabled();'; + } else { + $replace = ''; + } + + $content = str_replace(array(self::TRACKER_READY_HOOK_NAME_WHEN_MINIFIED, self::TRACKER_READY_HOOK_NAME), $replace, $content); + } + + /** + * Register the new tables, so Matomo knows about them. + * + * @param array $allTablesInstalled + */ + public function getTablesInstalled(&$allTablesInstalled) + { + $allTablesInstalled[] = Common::prefixTable('log_hsr'); + $allTablesInstalled[] = Common::prefixTable('log_hsr_blob'); + $allTablesInstalled[] = Common::prefixTable('log_hsr_event'); + $allTablesInstalled[] = Common::prefixTable('log_hsr_site'); + $allTablesInstalled[] = Common::prefixTable('site_hsr'); + } + + public static function getPathPrefix() + { + $webRootDirs = Manager::getInstance()->getWebRootDirectoriesForCustomPluginDirs(); + if (!empty($webRootDirs['HeatmapSessionRecording'])) { + $baseUrl = trim($webRootDirs['HeatmapSessionRecording'], '/'); + } else { + $baseUrl = 'plugins'; + } + return $baseUrl; + } + + public static function isMatomoForWordPress() + { + return defined('ABSPATH') && function_exists('add_action'); + } + + public function addStylesheets(&$mergedContent) + { + if (self::isMatomoForWordPress()) { + // we hide this icon since it uses the widgetize feature which is disabled in WordPress + $mergedContent .= '.manageHsr .action .icon-show { display: none; }'; + } + } + public function getPagesComparisonsDisabledFor(&$pages) + { + $pages[] = 'HeatmapSessionRecording_Heatmaps.*'; + $pages[] = 'HeatmapSessionRecording_SessionRecordings.*'; + } + + public function addJsGlobalVariables() + { + $idSite = Common::getRequestVar('idSite', 0, 'int'); + + if ($idSite > 0 && Piwik::isUserHasWriteAccess($idSite)) { + echo 'piwik.heatmapWriteAccess = true;'; + } else { + echo 'piwik.heatmapWriteAccess = false;'; + } + } + + public function requiresTwoFactorAuthentication(&$requiresAuth, $module, $action, $parameters) + { + if ($module == 'HeatmapSessionRecording' && $action === 'embedPage') { + $requiresAuth = false; + } + } + + public function shouldEmbedIframeEmpty(&$shouldEmbedEmpty, $controllerName, $actionName) + { + if ($controllerName == 'HeatmapSessionRecording' && ($actionName == 'replayRecording' || $actionName == 'embedPage')) { + $shouldEmbedEmpty = true; + } + } + + /** + * Fallback to add play session link for Matomo < 3.1.0 + * + * NOTE: TO BE REMOVED EG FROM FEBRUARY OR MARCH 2018 + * + * @param string $out + * @param Row $visitor + */ + public function visitorLogViewBeforeActionsInfo(&$out, $visitor) + { + if (class_exists('\\Piwik\\Plugins\\Live\\VisitorDetailsAbstract')) { + return; + } + + $idVisit = $visitor->getColumn('idVisit'); + $idSite = (int) $visitor->getColumn('idSite'); + + if (empty($idSite) || empty($idVisit) || !$this->getValidator()->canViewSessionReport($idSite)) { + return; + } + + $aggregator = new Aggregator(); + $recording = $aggregator->findRecording($idVisit); + if (!empty($recording['idsitehsr'])) { + $title = Piwik::translate('HeatmapSessionRecording_ReplayRecordedSession'); + $out .= ' ' . $title . '
'; + } + } + + public function shouldAddTrackerFile(&$shouldAdd, $pluginName) + { + if ($pluginName === 'HeatmapSessionRecording') { + $config = new Configuration(); + + $siteHsrDao = $this->getSiteHsrDao(); + if ($config->shouldOptimizeTrackingCode() && !$siteHsrDao->hasActiveRecordsAcrossSites()) { + // saves requests to configs.php while no heatmap or session recording configured. + $shouldAdd = false; + } + } + } + + public function updatePiwikTracker() + { + if (Plugin\Manager::getInstance()->isPluginActivated('CustomJsTracker')) { + $trackerUpdater = StaticContainer::get('Piwik\Plugins\CustomJsTracker\TrackerUpdater'); + if (!empty($trackerUpdater)) { + $trackerUpdater->update(); + } + } + } + + public function addSystemSummaryItems(&$systemSummary) + { + $dao = $this->getSiteHsrDao(); + $numHeatmaps = $dao->getNumRecordsTotal(SiteHsrDao::RECORD_TYPE_HEATMAP); + $numSessions = $dao->getNumRecordsTotal(SiteHsrDao::RECORD_TYPE_SESSION); + + $systemSummary[] = new SystemSummary\Item( + $key = 'heatmaps', + Piwik::translate('HeatmapSessionRecording_NHeatmaps', $numHeatmaps), + $value = null, + array('module' => 'HeatmapSessionRecording', 'action' => 'manageHeatmap'), + $icon = 'icon-drop', + $order = 6 + ); + $systemSummary[] = new SystemSummary\Item( + $key = 'sessionrecordings', + Piwik::translate('HeatmapSessionRecording_NSessionRecordings', $numSessions), + $value = null, + array('module' => 'HeatmapSessionRecording', 'action' => 'manageSessions'), + $icon = 'icon-play', + $order = 7 + ); + } + + public function getQueryParametersToExclude(&$parametersToExclude) + { + // these are used by the tracker + $parametersToExclude[] = self::ULR_PARAM_FORCE_CAPTURE_SCREEN; + $parametersToExclude[] = self::ULR_PARAM_FORCE_SAMPLE; + } + + public function onDeleteSite($idSite) + { + $model = $this->getSiteHsrModel(); + $model->deactivateRecordsForSite($idSite); + } + + private function getSiteHsrModel() + { + return StaticContainer::get('Piwik\Plugins\HeatmapSessionRecording\Model\SiteHsrModel'); + } + + private function getValidator() + { + return StaticContainer::get('Piwik\Plugins\HeatmapSessionRecording\Input\Validator'); + } + + public function addWidgetConfigs(&$configs) + { + $idSite = Common::getRequestVar('idSite', 0, 'int'); + + if (!$this->getValidator()->canViewHeatmapReport($idSite)) { + return; + } + + $heatmaps = $this->getHeatmaps($idSite); + + foreach ($heatmaps as $heatmap) { + $widget = new WidgetConfig(); + $widget->setCategoryId('HeatmapSessionRecording_Heatmaps'); + $widget->setSubcategoryId($heatmap['idsitehsr']); + $widget->setModule('HeatmapSessionRecording'); + $widget->setAction('showHeatmap'); + $widget->setParameters(array('idSiteHsr' => $heatmap['idsitehsr'])); + $widget->setIsNotWidgetizable(); + $configs[] = $widget; + } + } + + public function addSubcategories(&$subcategories) + { + $idSite = Common::getRequestVar('idSite', 0, 'int'); + + if (empty($idSite)) { + // fallback for eg API.getReportMetadata which uses idSites + $idSite = Common::getRequestVar('idSites', 0, 'int'); + + if (empty($idSite)) { + return; + } + } + + if ($this->getValidator()->canViewHeatmapReport($idSite)) { + $heatmaps = $this->getHeatmaps($idSite); + + // we list recently created heatmaps first + $order = 20; + foreach ($heatmaps as $heatmap) { + $subcategory = new Subcategory(); + $subcategory->setName($heatmap['name']); + $subcategory->setCategoryId('HeatmapSessionRecording_Heatmaps'); + $subcategory->setId($heatmap['idsitehsr']); + $subcategory->setOrder($order++); + $subcategories[] = $subcategory; + } + } + + if ($this->getValidator()->canViewSessionReport($idSite)) { + $recordings = $this->getSessionRecordings($idSite); + + // we list recently created recordings first + $order = 20; + foreach ($recordings as $recording) { + $subcategory = new Subcategory(); + $subcategory->setName($recording['name']); + $subcategory->setCategoryId('HeatmapSessionRecording_SessionRecordings'); + $subcategory->setId($recording['idsitehsr']); + $subcategory->setOrder($order++); + $subcategories[] = $subcategory; + } + } + } + + public function getClientSideTranslationKeys(&$result) + { + $result[] = 'General_Save'; + $result[] = 'General_Done'; + $result[] = 'General_Actions'; + $result[] = 'General_Yes'; + $result[] = 'General_No'; + $result[] = 'General_Add'; + $result[] = 'General_Remove'; + $result[] = 'General_Id'; + $result[] = 'General_Ok'; + $result[] = 'General_Cancel'; + $result[] = 'General_Name'; + $result[] = 'General_Loading'; + $result[] = 'General_LoadingData'; + $result[] = 'General_Mobile'; + $result[] = 'General_All'; + $result[] = 'General_Search'; + $result[] = 'CorePluginsAdmin_Status'; + $result[] = 'DevicesDetection_Tablet'; + $result[] = 'CoreUpdater_UpdateTitle'; + $result[] = 'DevicesDetection_Device'; + $result[] = 'Installation_Legend'; + $result[] = 'HeatmapSessionRecording_DeleteScreenshot'; + $result[] = 'HeatmapSessionRecording_DeleteHeatmapScreenshotConfirm'; + $result[] = 'HeatmapSessionRecording_enable'; + $result[] = 'HeatmapSessionRecording_disable'; + $result[] = 'HeatmapSessionRecording_ChangeReplaySpeed'; + $result[] = 'HeatmapSessionRecording_ClickToSkipPauses'; + $result[] = 'HeatmapSessionRecording_AutoPlayNextPageview'; + $result[] = 'HeatmapSessionRecording_XSamples'; + $result[] = 'HeatmapSessionRecording_StatusActive'; + $result[] = 'HeatmapSessionRecording_StatusEnded'; + $result[] = 'HeatmapSessionRecording_StatusPaused'; + $result[] = 'HeatmapSessionRecording_RequiresActivity'; + $result[] = 'HeatmapSessionRecording_RequiresActivityHelp'; + $result[] = 'HeatmapSessionRecording_CaptureKeystrokes'; + $result[] = 'HeatmapSessionRecording_CaptureKeystrokesHelp'; + $result[] = 'HeatmapSessionRecording_SessionRecording'; + $result[] = 'HeatmapSessionRecording_Heatmap'; + $result[] = 'HeatmapSessionRecording_ActivityClick'; + $result[] = 'HeatmapSessionRecording_ActivityMove'; + $result[] = 'HeatmapSessionRecording_ActivityScroll'; + $result[] = 'HeatmapSessionRecording_ActivityResize'; + $result[] = 'HeatmapSessionRecording_ActivityFormChange'; + $result[] = 'HeatmapSessionRecording_ActivityPageChange'; + $result[] = 'HeatmapSessionRecording_HeatmapWidth'; + $result[] = 'HeatmapSessionRecording_Width'; + $result[] = 'HeatmapSessionRecording_Action'; + $result[] = 'HeatmapSessionRecording_DeviceType'; + $result[] = 'HeatmapSessionRecording_PlayerDurationXofY'; + $result[] = 'HeatmapSessionRecording_PlayerPlay'; + $result[] = 'HeatmapSessionRecording_PlayerPause'; + $result[] = 'HeatmapSessionRecording_PlayerRewindFast'; + $result[] = 'HeatmapSessionRecording_PlayerForwardFast'; + $result[] = 'HeatmapSessionRecording_PlayerReplay'; + $result[] = 'HeatmapSessionRecording_PlayerPageViewPrevious'; + $result[] = 'HeatmapSessionRecording_PlayerPageViewNext'; + $result[] = 'HeatmapSessionRecording_SessionRecordingsUsageBenefits'; + $result[] = 'HeatmapSessionRecording_ManageSessionRecordings'; + $result[] = 'HeatmapSessionRecording_ManageHeatmaps'; + $result[] = 'HeatmapSessionRecording_NoSessionRecordingsFound'; + $result[] = 'HeatmapSessionRecording_FieldIncludedTargetsHelpSessions'; + $result[] = 'HeatmapSessionRecording_NoHeatmapsFound'; + $result[] = 'HeatmapSessionRecording_AvgAboveFoldTitle'; + $result[] = 'HeatmapSessionRecording_AvgAboveFoldDescription'; + $result[] = 'HeatmapSessionRecording_TargetPage'; + $result[] = 'HeatmapSessionRecording_TargetPages'; + $result[] = 'HeatmapSessionRecording_ViewReport'; + $result[] = 'HeatmapSessionRecording_SampleLimit'; + $result[] = 'HeatmapSessionRecording_SessionNameHelp'; + $result[] = 'HeatmapSessionRecording_HeatmapSampleLimit'; + $result[] = 'HeatmapSessionRecording_SessionSampleLimit'; + $result[] = 'HeatmapSessionRecording_HeatmapSampleLimitHelp'; + $result[] = 'HeatmapSessionRecording_SessionSampleLimitHelp'; + $result[] = 'HeatmapSessionRecording_MinSessionTime'; + $result[] = 'HeatmapSessionRecording_MinSessionTimeHelp'; + $result[] = 'HeatmapSessionRecording_EditX'; + $result[] = 'HeatmapSessionRecording_StopX'; + $result[] = 'HeatmapSessionRecording_HeatmapUsageBenefits'; + $result[] = 'HeatmapSessionRecording_AdvancedOptions'; + $result[] = 'HeatmapSessionRecording_SampleRate'; + $result[] = 'HeatmapSessionRecording_HeatmapSampleRateHelp'; + $result[] = 'HeatmapSessionRecording_SessionSampleRateHelp'; + $result[] = 'HeatmapSessionRecording_ExcludedElements'; + $result[] = 'HeatmapSessionRecording_ExcludedElementsHelp'; + $result[] = 'HeatmapSessionRecording_ScreenshotUrl'; + $result[] = 'HeatmapSessionRecording_ScreenshotUrlHelp'; + $result[] = 'HeatmapSessionRecording_BreakpointX'; + $result[] = 'HeatmapSessionRecording_BreakpointGeneralHelp'; + $result[] = 'HeatmapSessionRecording_Rule'; + $result[] = 'HeatmapSessionRecording_UrlParameterValueToMatchPlaceholder'; + $result[] = 'HeatmapSessionRecording_EditHeatmapX'; + $result[] = 'HeatmapSessionRecording_TargetTypeIsAny'; + $result[] = 'HeatmapSessionRecording_TargetTypeIsNot'; + $result[] = 'HeatmapSessionRecording_PersonalInformationNote'; + $result[] = 'HeatmapSessionRecording_UpdatingData'; + $result[] = 'HeatmapSessionRecording_FieldIncludedTargetsHelp'; + $result[] = 'HeatmapSessionRecording_DeleteX'; + $result[] = 'HeatmapSessionRecording_DeleteHeatmapConfirm'; + $result[] = 'HeatmapSessionRecording_BreakpointGeneralHelpManage'; + $result[] = 'HeatmapSessionRecording_TargetPageTestTitle'; + $result[] = 'HeatmapSessionRecording_TargetPageTestErrorInvalidUrl'; + $result[] = 'HeatmapSessionRecording_TargetPageTestUrlMatches'; + $result[] = 'HeatmapSessionRecording_TargetPageTestUrlNotMatches'; + $result[] = 'HeatmapSessionRecording_TargetPageTestLabel'; + $result[] = 'HeatmapSessionRecording_ErrorXNotProvided'; + $result[] = 'HeatmapSessionRecording_ErrorPageRuleRequired'; + $result[] = 'HeatmapSessionRecording_CreationDate'; + $result[] = 'HeatmapSessionRecording_HeatmapCreated'; + $result[] = 'HeatmapSessionRecording_HeatmapUpdated'; + $result[] = 'HeatmapSessionRecording_FieldNamePlaceholder'; + $result[] = 'HeatmapSessionRecording_HeatmapNameHelp'; + $result[] = 'HeatmapSessionRecording_CreateNewHeatmap'; + $result[] = 'HeatmapSessionRecording_CreateNewSessionRecording'; + $result[] = 'HeatmapSessionRecording_EditSessionRecordingX'; + $result[] = 'HeatmapSessionRecording_DeleteSessionRecordingConfirm'; + $result[] = 'HeatmapSessionRecording_EndHeatmapConfirm'; + $result[] = 'HeatmapSessionRecording_EndSessionRecordingConfirm'; + $result[] = 'HeatmapSessionRecording_SessionRecordingCreated'; + $result[] = 'HeatmapSessionRecording_SessionRecordingUpdated'; + $result[] = 'HeatmapSessionRecording_Filter'; + $result[] = 'HeatmapSessionRecording_PlayRecordedSession'; + $result[] = 'HeatmapSessionRecording_DeleteRecordedSession'; + $result[] = 'HeatmapSessionRecording_DeleteRecordedPageview'; + $result[] = 'Live_ViewVisitorProfile'; + $result[] = 'HeatmapSessionRecording_HeatmapXRecordedSamplesSince'; + $result[] = 'HeatmapSessionRecording_PageviewsInVisit'; + $result[] = 'HeatmapSessionRecording_ColumnTime'; + $result[] = 'General_TimeOnPage'; + $result[] = 'Goals_URL'; + $result[] = 'General_Close'; + $result[] = 'HeatmapSessionRecording_HeatmapX'; + $result[] = 'HeatmapSessionRecording_NoHeatmapSamplesRecordedYet'; + $result[] = 'HeatmapSessionRecording_NoHeatmapScreenshotRecordedYet'; + $result[] = 'HeatmapSessionRecording_NoHeatmapSamplesRecordedYetWithoutSystemConfiguration'; + $result[] = 'HeatmapSessionRecording_NoHeatmapScreenshotRecordedYetWithoutSystemConfiguration'; + $result[] = 'HeatmapSessionRecording_HeatmapInfoTrackVisitsFromCountries'; + $result[] = 'HeatmapSessionRecording_SessionRecordingInfoTrackVisitsFromCountries'; + $result[] = 'HeatmapSessionRecording_AdBlockerDetected'; + $result[] = 'HeatmapSessionRecording_CaptureDomTitle'; + $result[] = 'HeatmapSessionRecording_CaptureDomInlineHelp'; + $result[] = 'HeatmapSessionRecording_MatomoJSNotWritableErrorMessage'; + $result[] = 'HeatmapSessionRecording_SessionRecordings'; + $result[] = 'HeatmapSessionRecording_Heatmaps'; + $result[] = 'HeatmapSessionRecording_Clicks'; + $result[] = 'HeatmapSessionRecording_ClickRate'; + $result[] = 'HeatmapSessionRecording_Moves'; + $result[] = 'HeatmapSessionRecording_MoveRate'; + $result[] = 'HeatmapSessionRecording_HeatmapTroubleshoot'; + } + + public function getJsFiles(&$jsFiles) + { + $jsFiles[] = "plugins/HeatmapSessionRecording/javascripts/rowaction.js"; + } + + public function getStylesheetFiles(&$stylesheets) + { + $stylesheets[] = "plugins/HeatmapSessionRecording/stylesheets/list-entities.less"; + $stylesheets[] = "plugins/HeatmapSessionRecording/stylesheets/edit-entities.less"; + $stylesheets[] = "plugins/HeatmapSessionRecording/vue/src/HsrTargetTest/HsrTargetTest.less"; + $stylesheets[] = "plugins/HeatmapSessionRecording/vue/src/HsrUrlTarget/HsrUrlTarget.less"; + $stylesheets[] = "plugins/HeatmapSessionRecording/stylesheets/recordings.less"; + $stylesheets[] = "plugins/HeatmapSessionRecording/vue/src/SessionRecordingVis/SessionRecordingVis.less"; + $stylesheets[] = "plugins/HeatmapSessionRecording/vue/src/HeatmapVis/HeatmapVis.less"; + $stylesheets[] = "plugins/HeatmapSessionRecording/vue/src/Tooltip/Tooltip.less"; + } + + public function activate() + { + $this->installHtAccess(); + } + + public function install() + { + $siteHsr = new SiteHsrDao(); + $siteHsr->install(); + + $hsrSite = new LogHsrSite(); + $hsrSite->install(); + + $hsr = new LogHsr($hsrSite); + $hsr->install(); + + $blobHsr = new LogHsrBlob(); + $blobHsr->install(); + + $event = new LogHsrEvent($blobHsr); + $event->install(); + + $this->installHtAccess(); + + $configuration = new Configuration(); + $configuration->install(); + } + + public function installHtAccess() + { + $htaccess = new HtAccess(); + $htaccess->install(); + } + + public function uninstall() + { + $siteHsr = new SiteHsrDao(); + $siteHsr->uninstall(); + + $hsrSite = new LogHsrSite(); + $hsrSite->uninstall(); + + $hsr = new LogHsr($hsrSite); + $hsr->uninstall(); + + $blobHsr = new LogHsrBlob(); + $blobHsr->uninstall(); + + $event = new LogHsrEvent($blobHsr); + $event->uninstall(); + + $configuration = new Configuration(); + $configuration->uninstall(); + } + + public function isTrackerPlugin() + { + return true; + } + + private function getSiteHsrDao() + { + return StaticContainer::get('Piwik\Plugins\HeatmapSessionRecording\Dao\SiteHsrDao'); + } + + public function addSiteTrackerCache(&$content, $idSite) + { + $hsr = $this->getSiteHsrDao(); + $hsrs = $hsr->getActiveRecords($idSite); + + foreach ($hsrs as $index => $hsr) { + // we make sure to keep the cache file small as this is not needed in the cache + $hsrs[$index]['page_treemirror'] = !empty($hsr['page_treemirror']) ? '1' : null; + } + + $content['hsr'] = $hsrs; + } + + public function addActionReferenceColumnsByTable(&$result) + { + $result['log_hsr'] = array('idaction_url'); + $result['log_hsr_event'] = array('idselector'); + } + + public function changeSessionLengthIfEmbedPage() + { + if ( + SettingsServer::isTrackerApiRequest() + || Common::isPhpCliMode() + ) { + return; + } + + // if there's no token_auth=... in the URL and there's no existing HSR session, then + // we don't change the session options and try to use the normal matomo session. + if ( + Common::getRequestVar('token_auth', false) === false + && empty($_COOKIE[self::EMBED_SESSION_NAME]) + ) { + return; + } + + $module = Common::getRequestVar('module', '', 'string'); + $action = Common::getRequestVar('action', '', 'string'); + if ( + $module == 'HeatmapSessionRecording' + && $action == 'embedPage' + ) { + Config::getInstance()->General['login_cookie_expire'] = self::EMBED_SESSION_TIME; + + Session::$sessionName = self::EMBED_SESSION_NAME; + Session::rememberMe(Config::getInstance()->General['login_cookie_expire']); + } + } + + public static function getTranslationKey($type) + { + $key = ''; + switch ($type) { + case 'pause': + $key = 'HeatmapSessionRecording_PauseReason'; + break; + case 'noDataSession': + $key = 'HeatmapSessionRecording_NoSessionRecordedYetWithoutSystemConfiguration'; + break; + case 'noDataHeatmap': + $key = 'HeatmapSessionRecording_NoHeatmapSamplesRecordedYetWithoutSystemConfiguration'; + break; + } + + if (!$key) { + return null; + } + + Piwik::postEvent('HeatmapSessionRecording.updateTranslationKey', [&$key]); + return $key; + } + + public static function isMatomoJsWritable($checkSpecificFile = '') + { + if (Manager::getInstance()->isPluginActivated('Cloud')) { + return true; + } + + $updater = StaticContainer::get('Piwik\Plugins\CustomJsTracker\TrackerUpdater'); + $filePath = $updater->getToFile()->getPath(); + $filesToCheck = array($filePath); + $jsCodeGenerator = new TrackerCodeGenerator(); + if (SettingsPiwik::isMatomoInstalled() && $jsCodeGenerator->shouldPreferPiwikEndpoint()) { + // if matomo is not installed yet, we definitely prefer matomo.js... check for isMatomoInstalled is needed + // cause otherwise it would perform a db query before matomo DB is configured + $filesToCheck[] = str_replace('matomo.js', 'piwik.js', $filePath); + } + + if (!empty($checkSpecificFile)) { + $filesToCheck = [$checkSpecificFile]; // mostly used for testing isMatomoJsWritable functionality + } + + if (!Manager::getInstance()->isPluginActivated('CustomJsTracker')) { + return false; + } + + foreach ($filesToCheck as $fileToCheck) { + $file = new File($fileToCheck); + + if (!$file->hasWriteAccess()) { + return false; + } + } + + return true; + } + + private function getHeatmaps($idSite) + { + return Request::processRequest('HeatmapSessionRecording.getHeatmaps', [ + 'idSite' => $idSite, 'filter_limit' => -1, + 'includePageTreeMirror' => 0 // IMPORTANT for performance and IO. If you need page tree mirror please add another method and don't remove this parameter + ], $default = []); + } + + private function getSessionRecordings($idSite) + { + return Request::processRequest('HeatmapSessionRecording.getSessionRecordings', [ + 'idSite' => $idSite, 'filter_limit' => -1 + ], $default = []); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Input/Breakpoint.php b/files/plugin-HeatmapSessionRecording-5.2.4/Input/Breakpoint.php new file mode 100644 index 0000000..bb0eb6a --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Input/Breakpoint.php @@ -0,0 +1,66 @@ +breakpoint = $breakpoint; + $this->name = $name; + } + + public function check() + { + $title = Piwik::translate('HeatmapSessionRecording_BreakpointX', array($this->name)); + + // zero is a valid value! + if ($this->breakpoint === false || $this->breakpoint === null || $this->breakpoint === '') { + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXNotProvided', $title)); + } + + if (!is_numeric($this->breakpoint)) { + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXNotANumber', array($title))); + } + + if ($this->breakpoint < 0) { + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXTooLow', array($title, 0))); + } + + if ($this->breakpoint > self::MAX_LIMIT) { + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXTooHigh', array($title, self::MAX_LIMIT))); + } + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Input/CaptureKeystrokes.php b/files/plugin-HeatmapSessionRecording-5.2.4/Input/CaptureKeystrokes.php new file mode 100644 index 0000000..b6004f9 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Input/CaptureKeystrokes.php @@ -0,0 +1,40 @@ +value = $value; + } + + public function check() + { + $allowedValues = array('0', '1', 0, 1, true, false); + + if (!in_array($this->value, $allowedValues, $strict = true)) { + $message = Piwik::translate('HeatmapSessionRecording_ErrorXNotWhitelisted', array('captureKeystrokes', '"1", "0"')); + throw new Exception($message); + } + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Input/ExcludedElements.php b/files/plugin-HeatmapSessionRecording-5.2.4/Input/ExcludedElements.php new file mode 100644 index 0000000..2395013 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Input/ExcludedElements.php @@ -0,0 +1,50 @@ +selector = $name; + } + + public function check() + { + if ($this->selector === null || $this->selector === false || $this->selector === '') { + // selecto may not be set + return; + } + + $title = Piwik::translate('HeatmapSessionRecording_ExcludedElements'); + + if (Common::mb_strlen($this->selector) > static::MAX_LENGTH) { + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXTooLong', array($title, static::MAX_LENGTH))); + } + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Input/MinSessionTime.php b/files/plugin-HeatmapSessionRecording-5.2.4/Input/MinSessionTime.php new file mode 100644 index 0000000..22dd6d4 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Input/MinSessionTime.php @@ -0,0 +1,57 @@ +minSessionTime = $minSessionTime; + } + + public function check() + { + $title = 'HeatmapSessionRecording_MinSessionTime'; + + if ($this->minSessionTime === false || $this->minSessionTime === null || $this->minSessionTime === '') { + $title = Piwik::translate($title); + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXNotProvided', $title)); + } + + if (!is_numeric($this->minSessionTime)) { + $title = Piwik::translate($title); + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXNotANumber', array($title))); + } + + if ($this->minSessionTime < 0) { + $title = Piwik::translate($title); + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXTooLow', array($title, 0))); + } + + if ($this->minSessionTime > self::MAX_LIMIT) { + $title = Piwik::translate($title); + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXTooHigh', array($title, self::MAX_LIMIT))); + } + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Input/Name.php b/files/plugin-HeatmapSessionRecording-5.2.4/Input/Name.php new file mode 100644 index 0000000..f03692d --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Input/Name.php @@ -0,0 +1,51 @@ +name = $name; + } + + public function check() + { + $title = 'General_Name'; + + if (empty($this->name)) { + $title = Piwik::translate($title); + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXNotProvided', $title)); + } + + if (Common::mb_strlen($this->name) > static::MAX_LENGTH) { + $title = Piwik::translate($title); + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXTooLong', array($title, static::MAX_LENGTH))); + } + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Input/PageRule.php b/files/plugin-HeatmapSessionRecording-5.2.4/Input/PageRule.php new file mode 100644 index 0000000..3c09d15 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Input/PageRule.php @@ -0,0 +1,80 @@ +target = $targets; + $this->parameterName = $parameterName; + $this->index = $index; + } + + public function check() + { + $titleSingular = 'HeatmapSessionRecording_PageRule'; + + if (!is_array($this->target)) { + $titleSingular = Piwik::translate($titleSingular); + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorInnerIsNotAnArray', array($titleSingular, $this->parameterName))); + } + + if (empty($this->target['attribute'])) { + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorArrayMissingKey', array('attribute', $this->parameterName, $this->index))); + } + + if (empty($this->target['type'])) { + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorArrayMissingKey', array('type', $this->parameterName, $this->index))); + } + + if (!array_key_exists('inverted', $this->target)) { + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorArrayMissingKey', array('inverted', $this->parameterName, $this->index))); + } + + if (empty($this->target['value']) && Tracker\PageRuleMatcher::doesTargetTypeRequireValue($this->target['type'])) { + // any is the only target type that may have an empty value + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorArrayMissingValue', array('value', $this->parameterName, $this->index))); + } + + if ($this->target['type'] === Tracker\PageRuleMatcher::TYPE_REGEXP && isset($this->target['value'])) { + $pattern = Tracker\PageRuleMatcher::completeRegexpPattern($this->target['value']); + if (@preg_match($pattern, '') === false) { + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorInvalidRegExp', array($this->target['value']))); + } + } + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Input/PageRules.php b/files/plugin-HeatmapSessionRecording-5.2.4/Input/PageRules.php new file mode 100644 index 0000000..562c871 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Input/PageRules.php @@ -0,0 +1,61 @@ +targets = $targets; + $this->parameterName = $parameterName; + $this->needsAtLeastOneEntry = $needsAtLeastOneEntry; + } + + public function check() + { + if ($this->needsAtLeastOneEntry && empty($this->targets)) { + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXNotProvided', $this->parameterName)); + } + + if (!is_array($this->targets)) { + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorNotAnArray', $this->parameterName)); + } + + foreach ($this->targets as $index => $target) { + $target = new PageRule($target, $this->parameterName, $index); + $target->check(); + } + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Input/RequiresActivity.php b/files/plugin-HeatmapSessionRecording-5.2.4/Input/RequiresActivity.php new file mode 100644 index 0000000..61ff6a6 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Input/RequiresActivity.php @@ -0,0 +1,40 @@ +value = $value; + } + + public function check() + { + $allowedValues = array('0', '1', 0, 1, true, false); + + if (!in_array($this->value, $allowedValues, $strict = true)) { + $message = Piwik::translate('HeatmapSessionRecording_ErrorXNotWhitelisted', array('activated', '"1", "0"')); + throw new Exception($message); + } + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Input/SampleLimit.php b/files/plugin-HeatmapSessionRecording-5.2.4/Input/SampleLimit.php new file mode 100644 index 0000000..daa8686 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Input/SampleLimit.php @@ -0,0 +1,57 @@ +sampleLimit = $sampleLimit; + } + + public function check() + { + $title = 'HeatmapSessionRecording_SampleLimit'; + + if ($this->sampleLimit === false || $this->sampleLimit === null || $this->sampleLimit === '') { + $title = Piwik::translate($title); + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXNotProvided', $title)); + } + + if (!is_numeric($this->sampleLimit)) { + $title = Piwik::translate($title); + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXNotANumber', array($title))); + } + + if ($this->sampleLimit < 0) { + $title = Piwik::translate($title); + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXTooLow', array($title, 0))); + } + + if ($this->sampleLimit > self::MAX_LIMIT) { + $title = Piwik::translate($title); + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXTooHigh', array($title, self::MAX_LIMIT))); + } + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Input/SampleRate.php b/files/plugin-HeatmapSessionRecording-5.2.4/Input/SampleRate.php new file mode 100644 index 0000000..b0f6261 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Input/SampleRate.php @@ -0,0 +1,62 @@ +sampleRate = $sampleRate; + } + + public function check() + { + $title = 'HeatmapSessionRecording_SampleRate'; + + if ($this->sampleRate === false || $this->sampleRate === null || $this->sampleRate === '') { + $title = Piwik::translate($title); + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXNotProvided', $title)); + } + + if (!is_numeric($this->sampleRate)) { + $title = Piwik::translate($title); + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXNotANumber', array($title))); + } + + if ($this->sampleRate < 0) { + $title = Piwik::translate($title); + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXTooLow', array($title, 0))); + } + + if ($this->sampleRate > self::MAX_RATE) { + $title = Piwik::translate($title); + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXTooHigh', array($title, self::MAX_RATE))); + } + + if (!preg_match('/^\d{1,3}\.?\d?$/', (string) $this->sampleRate)) { + $title = Piwik::translate($title); + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXNotANumber', array($title))); + } + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Input/ScreenshotUrl.php b/files/plugin-HeatmapSessionRecording-5.2.4/Input/ScreenshotUrl.php new file mode 100644 index 0000000..2b40332 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Input/ScreenshotUrl.php @@ -0,0 +1,58 @@ +url = $name; + } + + public function check() + { + if ($this->url === null || $this->url === false || $this->url === '') { + // url may not be set + return; + } + + $title = Piwik::translate('HeatmapSessionRecording_ScreenshotUrl'); + + if (preg_match('/\s/', $this->url)) { + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXContainsWhitespace', $title)); + } + + if (strpos($this->url, '//') === false) { + throw new Exception(Piwik::translate('HeatmapSessionRecording_UrlXDoesNotLookLikeUrl', array($title, static::MAX_LENGTH))); + } + + if (Common::mb_strlen($this->url) > static::MAX_LENGTH) { + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorXTooLong', array($title, static::MAX_LENGTH))); + } + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Input/Validator.php b/files/plugin-HeatmapSessionRecording-5.2.4/Input/Validator.php new file mode 100644 index 0000000..e1b99c3 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Input/Validator.php @@ -0,0 +1,176 @@ +configuration = new Configuration(); + $this->systemSettings = $systemSettings; + } + + private function supportsMethod($method) + { + return method_exists('Piwik\Piwik', $method); + } + + public function checkHasSomeWritePermission() + { + if ($this->supportsMethod('checkUserHasSomeWriteAccess')) { + // since Matomo 3.6.0 + Piwik::checkUserHasSomeWriteAccess(); + return; + } + + Piwik::checkUserHasSomeAdminAccess(); + } + + public function checkWritePermission($idSite) + { + $this->checkSiteExists($idSite); + Piwik::checkUserIsNotAnonymous(); + + if ($this->supportsMethod('checkUserHasWriteAccess')) { + // since Matomo 3.6.0 + Piwik::checkUserHasWriteAccess($idSite); + return; + } + + Piwik::checkUserHasAdminAccess($idSite); + } + + public function checkHeatmapReportViewPermission($idSite) + { + $this->checkSiteExists($idSite); + Piwik::checkUserHasViewAccess($idSite); + $this->checkHeatmapRecordingEnabled(); + } + + public function checkSessionReportViewPermission($idSite) + { + $this->checkSiteExists($idSite); + $this->checkUserIsNotAnonymousForView($idSite); + Piwik::checkUserHasViewAccess($idSite); + $this->checkSessionRecordingEnabled(); + } + + public function checkSessionReportWritePermission($idSite) + { + $this->checkWritePermission($idSite); + $this->checkSessionRecordingEnabled(); + } + + public function checkHeatmapReportWritePermission($idSite) + { + $this->checkWritePermission($idSite); + $this->checkHeatmapRecordingEnabled(); + } + + public function checkSessionRecordingEnabled() + { + if ($this->isSessionRecordingDisabled()) { + throw new \Exception(Piwik::translate('HeatmapSessionRecording_ErrorSessionRecordingDisabled')); + } + } + + public function checkHeatmapRecordingEnabled() + { + if ($this->isHeatmapRecordingDisabled()) { + throw new \Exception(Piwik::translate('HeatmapSessionRecording_ErrorHeatmapRecordingDisabled')); + } + } + + private function checkUserIsNotAnonymousForView($idSite) + { + if ($this->configuration->isAnonymousSessionRecordingAccessEnabled($idSite)) { + Piwik::checkUserHasViewAccess($idSite); + return; + } + + Piwik::checkUserIsNotAnonymous(); + } + + private function checkSiteExists($idSite) + { + new Site($idSite); + } + + public function canViewSessionReport($idSite) + { + if (empty($idSite) || $this->isSessionRecordingDisabled()) { + return false; + } + + if ( + !$this->configuration->isAnonymousSessionRecordingAccessEnabled($idSite) + && Piwik::isUserIsAnonymous() + ) { + return false; + } + + return Piwik::isUserHasViewAccess($idSite); + } + + public function canViewHeatmapReport($idSite) + { + if (empty($idSite) || $this->isHeatmapRecordingDisabled()) { + return false; + } + + return Piwik::isUserHasViewAccess($idSite); + } + + public function canWrite($idSite) + { + if (empty($idSite)) { + return false; + } + + if ($this->supportsMethod('isUserHasWriteAccess')) { + // since Matomo 3.6.0 + return Piwik::isUserHasWriteAccess($idSite); + } + + return Piwik::isUserHasAdminAccess($idSite); + } + + public function isSessionRecordingDisabled() + { + return $this->systemSettings->disableSessionRecording->getValue(); + } + + public function isHeatmapRecordingDisabled() + { + return $this->systemSettings->disableHeatmapRecording->getValue(); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Install/HtAccess.php b/files/plugin-HeatmapSessionRecording-5.2.4/Install/HtAccess.php new file mode 100644 index 0000000..dc7017d --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Install/HtAccess.php @@ -0,0 +1,66 @@ +getPluginDir() . '/.htaccess'; + } + + private function getSourcePath() + { + return $this->getPluginDir() . '/Install/htaccessTemplate'; + } + + private function exists() + { + $path = $this->getTargetPath(); + return file_exists($path); + } + + private function canCreate() + { + return is_writable($this->getPluginDir()); + } + + private function isContentDifferent() + { + $templateContent = trim(file_get_contents($this->getSourcePath())); + $fileContent = trim(file_get_contents($this->getTargetPath())); + + return $templateContent !== $fileContent; + } + + public function install() + { + if ( + $this->canCreate() && (!$this->exists() || (is_readable($this->getTargetPath()) && $this->isContentDifferent())) + ) { + Filesystem::copy($this->getSourcePath(), $this->getTargetPath()); + } + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Install/htaccessTemplate b/files/plugin-HeatmapSessionRecording-5.2.4/Install/htaccessTemplate new file mode 100644 index 0000000..c302274 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Install/htaccessTemplate @@ -0,0 +1,23 @@ +# This file is generated by InnoCraft - Piwik, do not edit directly +# Please report any issue or improvement directly to the InnoCraft team. +# Allow to serve configs.php which is safe + + + + Order Allow,Deny + Allow from All + + = 2.4> + Require all granted + + + + + Order Allow,Deny + Allow from All + + + Require all granted + + + \ No newline at end of file diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/LEGALNOTICE b/files/plugin-HeatmapSessionRecording-5.2.4/LEGALNOTICE new file mode 100644 index 0000000..b8a6bb2 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/LEGALNOTICE @@ -0,0 +1,46 @@ +COPYRIGHT + + The software package is: + + Copyright (C) 2017 InnoCraft Ltd (NZBN 6106769) + + +SOFTWARE LICENSE + + This software is licensed under the InnoCraft EULA and the license has been included in this + software package in the file LICENSE. + + +THIRD-PARTY COMPONENTS AND LIBRARIES + + The following components/libraries are redistributed in this package, + and subject to their respective licenses. + + Name: heatmap.js + Link: https://www.patrick-wied.at/static/heatmapjs/ + License: MIT + License File: ibs/heatmap.js/LICENSE + + Name: mutation-summary + Link: https://github.com/rafaelw/mutation-summary/ + License: Apache 2.0 + License File: libs/mutation-summary/COPYING + + Name: MutationObserver.js + Link: https://github.com/megawac/MutationObserver.js/tree/master + License: WTFPL, Version 2 + License File: libs/MutationObserver.js/license + + Name: svg.js + Link: http://tkyk.github.com/jquery-history-plugin/ + License: http://svgjs.com/ + License File: libs/svg.js/LICENSE.txt + + Name: Get Element CSS Selector + Link: https://gist.github.com/asfaltboy/8aea7435b888164e8563 + License: MIT + License File: included in tracker.min.js + + Name: Material icons ("repeat", "looks_one", "looks_two", "looks_four", "looks_six") in angularjs/sessionvis/sessionvis.directive.html + Link: https://design.google.com/icons/ + License: Apache License Version 2.0 diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/LICENSE b/files/plugin-HeatmapSessionRecording-5.2.4/LICENSE new file mode 100644 index 0000000..4686f35 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/LICENSE @@ -0,0 +1,49 @@ +InnoCraft License + +This InnoCraft End User License Agreement (the "InnoCraft EULA") is between you and InnoCraft Ltd (NZBN 6106769) ("InnoCraft"). If you are agreeing to this Agreement not as an individual but on behalf of your company, then "Customer" or "you" means your company, and you are binding your company to this Agreement. InnoCraft may modify this Agreement from time to time, subject to the terms in Section (xii) below. + +By clicking on the "I’ve read and accept the terms & conditions (https://shop.matomo.org/terms-conditions/)" (or similar button) that is presented to you at the time of your Order, or by using or accessing InnoCraft products, you indicate your assent to be bound by this Agreement. + + +InnoCraft EULA + +(i) InnoCraft is the licensor of the Plugin for Matomo Analytics (the "Software"). + +(ii) Subject to the terms and conditions of this Agreement, InnoCraft grants you a limited, worldwide, non-exclusive, non-transferable and non-sublicensable license to install and use the Software only on hardware systems owned, leased or controlled by you, during the applicable License Term. The term of each Software license ("License Term") will be specified in your Order. Your License Term will end upon any breach of this Agreement. + +(iii) Unless otherwise specified in your Order, for each Software license that you purchase, you may install one production instance of the Software in a Matomo Analytics instance owned or operated by you, and accessible via one URL ("Matomo instance"). Additional licenses must be purchased in order to deploy the Software in multiple Matomo instances, including when these multiple Matomo instances are hosted on a single hardware system. + +(iv) Licenses granted by InnoCraft are granted subject to the condition that you must ensure the maximum number of Authorized Users and Authorized Sites that are able to access and use the Software is equal to the number of User and Site Licenses for which the necessary fees have been paid to InnoCraft for the Subscription period. You may upgrade your license at any time on payment of the appropriate fees to InnoCraft in order to increase the maximum number of authorized users or sites. The number of User and Site Licenses granted to you is dependent on the fees paid by you. “User License” means a license granted under this EULA to you to permit an Authorized User to use the Software. “Authorized User” means a person who has an account in the Matomo instance and for which the necessary fees (“Subscription fees”) have been paid to InnoCraft for the current license term. "Site License" means a license granted under this EULA to you to permit an Authorized Site to use the Matomo Marketplace Plugin. “Authorized Sites” means a website or a measurable within Matomo instance and for which the necessary fees (“Subscription fees”) have been paid to InnoCraft for the current license term. These restrictions also apply if you install the Matomo Analytics Platform as part of your WordPress. + +(v) Piwik Analytics was renamed to Matomo Analytics in January 2018. The same terms and conditions as well as any restrictions or grants apply if you are using any version of Piwik. + +(vi) The Software requires a license key in order to operate, which will be delivered to the email addresses specified in your Order when we have received payment of the applicable fees. + +(vii) Any information that InnoCraft may collect from you or your device will be subject to InnoCraft Privacy Policy (https://www.innocraft.com/privacy). + +(viii) You are bound by the Matomo Marketplace Terms and Conditions (https://shop.matomo.org/terms-conditions/). + +(ix) You may not reverse engineer or disassemble or re-distribute the Software in whole or in part, or create any derivative works from or sublicense any rights in the Software, unless otherwise expressly authorized in writing by InnoCraft. + +(x) The Software is protected by copyright and other intellectual property laws and treaties. InnoCraft own all title, copyright and other intellectual property rights in the Software, and the Software is licensed to you directly by InnoCraft, not sold. + +(xi) The Software is provided under an "as is" basis and without any support or maintenance. Nothing in this Agreement shall require InnoCraft to provide you with support or fixes to any bug, failure, mis-performance or other defect in The Software. InnoCraft may provide you, from time to time, according to his sole discretion, with updates of the Software. You hereby warrant to keep the Software up-to-date and install all relevant updates. InnoCraft shall provide any update free of charge. + +(xii) The Software is provided "as is", and InnoCraft hereby disclaim all warranties, including but not limited to any implied warranties of title, non-infringement, merchantability or fitness for a particular purpose. InnoCraft shall not be liable or responsible in any way for any losses or damage of any kind, including lost profits or other indirect or consequential damages, relating to your use of or reliance upon the Software. + +(xiii) We may update or modify this Agreement from time to time, including the referenced Privacy Policy and the Matomo Marketplace Terms and Conditions. If a revision meaningfully reduces your rights, we will use reasonable efforts to notify you (by, for example, sending an email to the billing or technical contact you designate in the applicable Order). If we modify the Agreement during your License Term or Subscription Term, the modified version will be effective upon your next renewal of a License Term. + + +About InnoCraft Ltd + +At InnoCraft Ltd, we create innovating quality products to grow your business and to maximize your success. + +Our software products are built on top of Matomo Analytics: the leading open digital analytics platform used by more than one million websites worldwide. We are the creators and makers of the Matomo Analytics platform. + + +Contact + +Email: contact@innocraft.com +Contact form: https://www.innocraft.com/#contact +Website: https://www.innocraft.com/ +Buy our products: Premium Features for Matomo Analytics https://plugins.matomo.org/premium diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Menu.php b/files/plugin-HeatmapSessionRecording-5.2.4/Menu.php new file mode 100644 index 0000000..9ec1075 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Menu.php @@ -0,0 +1,50 @@ +validator = $validator; + } + + public function configureAdminMenu(MenuAdmin $menu) + { + $idSite = Common::getRequestVar('idSite', 0, 'int'); + + if (!empty($idSite) && !Piwik::isUserIsAnonymous() && $this->validator->canWrite($idSite)) { + if (!$this->validator->isHeatmapRecordingDisabled()) { + $menu->addMeasurableItem('HeatmapSessionRecording_Heatmaps', $this->urlForAction('manageHeatmap'), $orderId = 30); + } + if (!$this->validator->isSessionRecordingDisabled()) { + $menu->addMeasurableItem('HeatmapSessionRecording_SessionRecordings', $this->urlForAction('manageSessions'), $orderId = 30); + } + } + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Model/SiteHsrModel.php b/files/plugin-HeatmapSessionRecording-5.2.4/Model/SiteHsrModel.php new file mode 100644 index 0000000..f95767c --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Model/SiteHsrModel.php @@ -0,0 +1,489 @@ +dao = $dao; + $this->logHsrSite = $logHsrSite; + } + + public function addHeatmap($idSite, $name, $matchPageRules, $sampleLimit, $sampleRate, $excludedElements, $screenshotUrl, $breakpointMobile, $breakpointTablet, $captureDomManually, $createdDate) + { + $this->checkHeatmap($name, $matchPageRules, $sampleLimit, $sampleRate, $excludedElements, $screenshotUrl, $breakpointMobile, $breakpointTablet); + + $status = SiteHsrDao::STATUS_ACTIVE; + + $idSiteHsr = $this->dao->createHeatmapRecord($idSite, $name, $sampleLimit, $sampleRate, $matchPageRules, $excludedElements, $screenshotUrl, $breakpointMobile, $breakpointTablet, $status, $captureDomManually, $createdDate); + $this->clearTrackerCache($idSite); + + return (int) $idSiteHsr; + } + + public function updateHeatmap($idSite, $idSiteHsr, $name, $matchPageRules, $sampleLimit, $sampleRate, $excludedElements, $screenshotUrl, $breakpointMobile, $breakpointTablet, $captureDomManually, $updatedDate) + { + $this->checkHeatmap($name, $matchPageRules, $sampleLimit, $sampleRate, $excludedElements, $screenshotUrl, $breakpointMobile, $breakpointTablet); + + $columns = array( + 'name' => $name, + 'sample_limit' => $sampleLimit, + 'match_page_rules' => $matchPageRules, + 'sample_rate' => $sampleRate, + 'excluded_elements' => $excludedElements, + 'screenshot_url' => $screenshotUrl, + 'breakpoint_mobile' => $breakpointMobile, + 'breakpoint_tablet' => $breakpointTablet, + 'updated_date' => $updatedDate, + ); + + if (!empty($captureDomManually)) { + $columns['capture_manually'] = 1; + $columns['page_treemirror'] = null; + } else { + $columns['capture_manually'] = 0; + } + + $this->updateHsrColumns($idSite, $idSiteHsr, $columns); + $this->clearTrackerCache($idSite); + } + + private function checkHeatmap($name, $matchPageRules, $sampleLimit, $sampleRate, $excludedElements, $screenshotUrl, $breakpointMobile, $breakpointTablet) + { + $name = new Name($name); + $name->check(); + + $pageRules = new PageRules($matchPageRules, 'matchPageRules', $needsOneEntry = true); + $pageRules->check(); + + $sampleLimit = new SampleLimit($sampleLimit); + $sampleLimit->check(); + + $sampleRate = new SampleRate($sampleRate); + $sampleRate->check(); + + $screenshotUrl = new ScreenshotUrl($screenshotUrl); + $screenshotUrl->check(); + + $excludedElements = new ExcludedElements($excludedElements); + $excludedElements->check(); + + $breakpointMobile = new Breakpoint($breakpointMobile, 'Mobile'); + $breakpointMobile->check(); + + $breakpointTablet = new Breakpoint($breakpointTablet, 'Tablet'); + $breakpointTablet->check(); + } + + public function addSessionRecording($idSite, $name, $matchPageRules, $sampleLimit, $sampleRate, $minSessionTime, $requiresActivity, $captureKeystrokes, $createdDate) + { + $this->checkSession($name, $matchPageRules, $sampleLimit, $sampleRate, $minSessionTime, $requiresActivity, $captureKeystrokes); + $status = SiteHsrDao::STATUS_ACTIVE; + + $idSiteHsr = $this->dao->createSessionRecord($idSite, $name, $sampleLimit, $sampleRate, $matchPageRules, $minSessionTime, $requiresActivity, $captureKeystrokes, $status, $createdDate); + + $this->clearTrackerCache($idSite); + return (int) $idSiteHsr; + } + + public function updateSessionRecording($idSite, $idSiteHsr, $name, $matchPageRules, $sampleLimit, $sampleRate, $minSessionTime, $requiresActivity, $captureKeystrokes, $updatedDate) + { + $this->checkSession($name, $matchPageRules, $sampleLimit, $sampleRate, $minSessionTime, $requiresActivity, $captureKeystrokes); + + $columns = array( + 'name' => $name, + 'sample_limit' => $sampleLimit, + 'match_page_rules' => $matchPageRules, + 'sample_rate' => $sampleRate, + 'min_session_time' => $minSessionTime, + 'requires_activity' => $requiresActivity, + 'capture_keystrokes' => $captureKeystrokes, + 'updated_date' => $updatedDate, + ); + + $this->updateHsrColumns($idSite, $idSiteHsr, $columns); + $this->clearTrackerCache($idSite); + } + + private function checkSession($name, $matchPageRules, $sampleLimit, $sampleRate, $minSessionTime, $requiresActivity, $captureKeystrokes) + { + $name = new Name($name); + $name->check(); + + $pageRules = new PageRules($matchPageRules, 'matchPageRules', $needsOneEntry = false); + $pageRules->check(); + + $sampleLimit = new SampleLimit($sampleLimit); + $sampleLimit->check(); + + $sampleRate = new SampleRate($sampleRate); + $sampleRate->check(); + + $minSessionTime = new MinSessionTime($minSessionTime); + $minSessionTime->check(); + + $requiresActivity = new RequiresActivity($requiresActivity); + $requiresActivity->check(); + + $captureKeystrokes = new CaptureKeystrokes($captureKeystrokes); + $captureKeystrokes->check(); + } + + public function getHeatmap($idSite, $idSiteHsr) + { + $record = $this->dao->getRecord($idSite, $idSiteHsr, SiteHsrDao::RECORD_TYPE_HEATMAP); + + return $this->enrichHeatmap($record); + } + + public function getSessionRecording($idSite, $idSiteHsr) + { + $record = $this->dao->getRecord($idSite, $idSiteHsr, SiteHsrDao::RECORD_TYPE_SESSION); + return $this->enrichSessionRecording($record); + } + + public function pauseHeatmap($idSite, $idSiteHsr) + { + $this->updateHsrColumns($idSite, $idSiteHsr, array('status' => SiteHsrDao::STATUS_PAUSED)); + } + + public function resumeHeatmap($idSite, $idSiteHsr) + { + $this->updateHsrColumns($idSite, $idSiteHsr, array('status' => SiteHsrDao::STATUS_ACTIVE)); + } + + public function deactivateHeatmap($idSite, $idSiteHsr) + { + $heatmap = $this->getHeatmap($idSite, $idSiteHsr); + + if (!empty($heatmap)) { + $this->updateHsrColumns($idSite, $idSiteHsr, array('status' => SiteHsrDao::STATUS_DELETED)); + + // the actual recorded heatmap data will still exist but we remove the "links" which is quick. a task will later remove all entries + $this->logHsrSite->unlinkSiteRecords($idSiteHsr); + } + } + + public function checkHeatmapExists($idSite, $idSiteHsr) + { + $hsr = $this->dao->getRecord($idSite, $idSiteHsr, SiteHsrDao::RECORD_TYPE_HEATMAP); + + if (empty($hsr)) { + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorHeatmapDoesNotExist')); + } + } + + public function checkSessionRecordingExists($idSite, $idSiteHsr) + { + $hsr = $this->dao->getRecord($idSite, $idSiteHsr, SiteHsrDao::RECORD_TYPE_SESSION); + + if (empty($hsr)) { + throw new Exception(Piwik::translate('HeatmapSessionRecording_ErrorSessionRecordingDoesNotExist')); + } + } + + public function pauseSessionRecording($idSite, $idSiteHsr) + { + $this->updateHsrColumns($idSite, $idSiteHsr, array('status' => SiteHsrDao::STATUS_PAUSED)); + } + + public function resumeSessionRecording($idSite, $idSiteHsr) + { + $this->updateHsrColumns($idSite, $idSiteHsr, array('status' => SiteHsrDao::STATUS_ACTIVE)); + } + + public function deactivateSessionRecording($idSite, $idSiteHsr) + { + $session = $this->getSessionRecording($idSite, $idSiteHsr); + + if (!empty($session)) { + $this->updateHsrColumns($idSite, $idSiteHsr, array('status' => SiteHsrDao::STATUS_DELETED)); + + // the actual recording will still exist but we remove the "links" which is quick. a task will later remove all entries + $this->logHsrSite->unlinkSiteRecords($idSiteHsr); + } + } + + public function deactivateRecordsForSite($idSite) + { + foreach ($this->dao->getRecords($idSite, SiteHsrDao::RECORD_TYPE_HEATMAP, false) as $heatmap) { + $this->deactivateHeatmap($idSite, $heatmap['idsitehsr']); + } + + foreach ($this->dao->getRecords($idSite, SiteHsrDao::RECORD_TYPE_SESSION, false) as $session) { + $this->deactivateSessionRecording($idSite, $session['idsitehsr']); + } + } + + public function pauseRecordsForSite($idSite) + { + foreach ($this->dao->getRecords($idSite, SiteHsrDao::RECORD_TYPE_HEATMAP, false) as $heatmap) { + $this->pauseHeatmap($idSite, $heatmap['idsitehsr']); + } + + foreach ($this->dao->getRecords($idSite, SiteHsrDao::RECORD_TYPE_SESSION, false) as $session) { + $this->pauseSessionRecording($idSite, $session['idsitehsr']); + } + } + + public function resumeRecordsForSite($idSite) + { + foreach ($this->dao->getRecords($idSite, SiteHsrDao::RECORD_TYPE_HEATMAP, false) as $heatmap) { + $this->resumeHeatmap($idSite, $heatmap['idsitehsr']); + } + + foreach ($this->dao->getRecords($idSite, SiteHsrDao::RECORD_TYPE_SESSION, false) as $session) { + $this->resumeSessionRecording($idSite, $session['idsitehsr']); + } + } + + public function endHeatmap($idSite, $idSiteHsr) + { + $heatmap = $this->getHeatmap($idSite, $idSiteHsr); + if (!empty($heatmap)) { + $this->updateHsrColumns($idSite, $idSiteHsr, array('status' => SiteHsrDao::STATUS_ENDED)); + + Piwik::postEvent('HeatmapSessionRecording.endHeatmap', array($idSite, $idSiteHsr)); + } + } + + public function endSessionRecording($idSite, $idSiteHsr) + { + $session = $this->getSessionRecording($idSite, $idSiteHsr); + if (!empty($session)) { + $this->updateHsrColumns($idSite, $idSiteHsr, array('status' => SiteHsrDao::STATUS_ENDED)); + + Piwik::postEvent('HeatmapSessionRecording.endSessionRecording', array($idSite, $idSiteHsr)); + } + } + + /** + * @param $idSite + * @param bool $includePageTreeMirror performance and IO tweak has some heatmaps might have a 16MB or more treemirror and it would be loaded on every request causing a lot of IO etc. + * @return array + */ + public function getHeatmaps($idSite, $includePageTreeMirror) + { + $heatmaps = $this->dao->getRecords($idSite, SiteHsrDao::RECORD_TYPE_HEATMAP, $includePageTreeMirror); + + return $this->enrichHeatmaps($heatmaps); + } + + public function getSessionRecordings($idSite) + { + $sessionRecordings = $this->dao->getRecords($idSite, SiteHsrDao::RECORD_TYPE_SESSION, $includePageTreeMirror = false); + + return $this->enrichSessionRecordings($sessionRecordings); + } + + public function hasSessionRecordings($idSite) + { + $hasSession = $this->dao->hasRecords($idSite, SiteHsrDao::RECORD_TYPE_SESSION); + + return !empty($hasSession); + } + + public function hasHeatmaps($idSite) + { + $hasHeatmap = $this->dao->hasRecords($idSite, SiteHsrDao::RECORD_TYPE_HEATMAP); + + return !empty($hasHeatmap); + } + + public function setPageTreeMirror($idSite, $idSiteHsr, $treeMirror, $screenshotUrl) + { + $heatmap = $this->getHeatmap($idSite, $idSiteHsr); + if (!empty($heatmap)) { + // only supported by heatmaps + $columns = array( + 'page_treemirror' => $treeMirror, + 'screenshot_url' => $screenshotUrl + ); + if (!empty($heatmap['capture_manually']) && !empty($treeMirror)) { + $columns['capture_manually'] = 0; + } + $this->updateHsrColumns($idSite, $idSiteHsr, $columns); + } + } + + public function getPiwikRequestDate($hsr) + { + // we sub one day to make sure to include them all + $from = Date::factory($hsr['created_date'])->subDay(1)->toString(); + $to = Date::now()->addDay(1)->toString(); + + if ($from === $to) { + $dateRange = $from; + $period = 'year'; + } else { + $period = 'range'; + $dateRange = $from . ',' . $to; + } + + return array('period' => $period, 'date' => $dateRange); + } + + private function enrichHeatmaps($heatmaps) + { + if (empty($heatmaps)) { + return array(); + } + + foreach ($heatmaps as $index => $heatmap) { + $heatmaps[$index] = $this->enrichHeatmap($heatmap); + } + + return $heatmaps; + } + + private function enrichHeatmap($heatmap) + { + if (empty($heatmap)) { + return $heatmap; + } + + unset($heatmap['record_type']); + unset($heatmap['min_session_time']); + unset($heatmap['requires_activity']); + unset($heatmap['capture_keystrokes']); + $heatmap['created_date_pretty'] = Date::factory($heatmap['created_date'])->getLocalized(DateTimeFormatProvider::DATE_FORMAT_SHORT); + + if ((!method_exists(SettingsServer::class, 'isMatomoForWordPress') || !SettingsServer::isMatomoForWordPress()) && !SettingsServer::isTrackerApiRequest()) { + $heatmap['heatmapViewUrl'] = self::completeWidgetUrl('showHeatmap', 'idSiteHsr=' . (int) $heatmap['idsitehsr'] . '&useDateUrl=0', (int) $heatmap['idsite']); + } + + return $heatmap; + } + + public static function completeWidgetUrl($action, $params, $idSite, $period = null, $date = null) + { + if (!isset($date)) { + if (empty(self::$defaultDate)) { + $userPreferences = new UserPreferences(); + self::$defaultDate = $userPreferences->getDefaultDate(); + if (empty(self::$defaultDate)) { + self:: $defaultDate = 'today'; + } + } + $date = self::$defaultDate; + } + + if (!isset($period)) { + if (!isset(self::$defaultPeriod)) { + $userPreferences = new UserPreferences(); + self::$defaultPeriod = $userPreferences->getDefaultPeriod(false); + if (empty(self::$defaultPeriod)) { + self::$defaultPeriod = 'day'; + } + } + $period = self::$defaultPeriod; + } + + $token = Access::getInstance()->getTokenAuth(); + + $url = 'index.php?module=Widgetize&action=iframe&moduleToWidgetize=HeatmapSessionRecording&actionToWidgetize=' . urlencode($action) . '&' . $params . '&idSite=' . (int) $idSite . '&period=' . urlencode($period) . '&date=' . urlencode($date); + if (!empty($token)) { + $url .= '&token_auth=' . urlencode($token); + } + return $url; + } + + private function enrichSessionRecordings($sessionRecordings) + { + if (empty($sessionRecordings)) { + return array(); + } + + foreach ($sessionRecordings as $index => $sessionRecording) { + $sessionRecordings[$index] = $this->enrichSessionRecording($sessionRecording); + } + + return $sessionRecordings; + } + + private function enrichSessionRecording($session) + { + if (empty($session)) { + return $session; + } + + unset($session['record_type']); + unset($session['screenshot_url']); + unset($session['page_treemirror']); + unset($session['excluded_elements']); + unset($session['breakpoint_mobile']); + unset($session['breakpoint_tablet']); + $session['created_date_pretty'] = Date::factory($session['created_date'])->getLocalized(DateTimeFormatProvider::DATE_FORMAT_SHORT); + + return $session; + } + + protected function getCurrentDateTime() + { + return Date::now()->getDatetime(); + } + + private function updateHsrColumns($idSite, $idSiteHsr, $columns) + { + if (!isset($columns['updated_date'])) { + $columns['updated_date'] = $this->getCurrentDateTime(); + } + + $this->dao->updateHsrColumns($idSite, $idSiteHsr, $columns); + $this->clearTrackerCache($idSite); + } + + private function clearTrackerCache($idSite) + { + Tracker\Cache::deleteCacheWebsiteAttributes($idSite); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/MutationManipulator.php b/files/plugin-HeatmapSessionRecording-5.2.4/MutationManipulator.php new file mode 100644 index 0000000..5878e4f --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/MutationManipulator.php @@ -0,0 +1,226 @@ +configuration = $configuration; + $this->generateNonce(); + } + + public function manipulate($initialMutation, $idSiteHsr, $idLogHsr) + { + $parseAndSanitizeCssLinks = $this->updateCssLinks($initialMutation, $idSiteHsr, $idLogHsr); + + return $this->sanitizeNodeAttributes($parseAndSanitizeCssLinks); + } + + public function updateCssLinks($initialMutation, $idSiteHsr, $idLogHsr) + { + if ($this->configuration->isLoadCSSFromDBEnabled()) { + $blob = new LogHsrBlob(); + $dao = new LogHsrEvent($blob); + $cssEvents = $dao->getCssEvents($idSiteHsr, $idLogHsr); + if (!empty($cssEvents) && !empty($initialMutation)) { + $initialMutation = $this->updateInitialMutationWithInlineCss($initialMutation, $cssEvents); + } + } + + return $initialMutation; + } + + public function getNonce() + { + if (!$this->nonce) { + $this->generateNonce(); + } + + + return $this->nonce; + } + + public function generateNonce() + { + $this->nonce = $this->generateRandomString(); + } + + private function generateRandomString($length = 10) + { + $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $charactersLength = strlen($characters); + $randomString = ''; + for ($i = 0; $i < $length; $i++) { + $randomString .= $characters[rand(0, $charactersLength - 1)]; + } + return $randomString; + } + + public function sanitizeNodeAttributes($initialMutation) + { + $initialMutationArray = json_decode($initialMutation, true); + if (!empty($initialMutationArray['children'])) { + $this->parseMutationArrayRecursivelyToSanitizeNodes($initialMutationArray['children']); + $initialMutation = json_encode($initialMutationArray); + } + + return $initialMutation; + } + + public function updateInitialMutationWithInlineCss($initialMutation, $cssEvents) + { + $formattedCssEvents = $this->formatCssEvents($cssEvents); + $initialMutationArray = json_decode($initialMutation, true); + if (!empty($initialMutationArray['children']) && !empty($formattedCssEvents)) { + $this->parseMutationArrayRecursivelyForCssLinks($initialMutationArray['children'], $formattedCssEvents); + + $initialMutation = json_encode($initialMutationArray); + } + + return $initialMutation; + } + + public function formatCssEvents($cssEvents) + { + $formatted = array(); + foreach ($cssEvents as $cssEvent) { + if (!isset($formatted[md5(trim($cssEvent['url']))])) { //Only use the first one since the o/p is sorted by ID in ascending order + $formatted[md5(trim($cssEvent['url']))] = $cssEvent; + } + } + + return $formatted; + } + + private function parseMutationArrayRecursivelyForCssLinks(&$nodes, $cssEvents, &$id = 900000000) + { + foreach ($nodes as &$node) { + $parseChildNodes = true; + if (isset($node['tagName']) && $node['tagName'] == 'LINK' && !empty($node['attributes']['url']) && !empty($cssEvents) && !empty($cssEvents[md5(trim($node['attributes']['url']))]['text'])) { + $parseChildNodes = false; + $content = $cssEvents[md5(trim($node['attributes']['url']))]['text']; + if (!empty($content)) { + $node['tagName'] = 'STYLE'; + $media = $node['attributes']['media'] ?? ''; + if (isset($node['attributes'])) { + $node['attributes'] = []; + } + $node['attributes']['nonce'] = $this->getNonce(); + if ($media) { + $node['attributes']['media'] = $media; + } + $node['childNodes'] = [ + [ + 'nodeType' => 3, + 'id' => $id++, + 'textContent' => $content + ] + ]; + } + } + + if ($parseChildNodes && !empty($node['childNodes'])) { + $this->parseMutationArrayRecursivelyForCssLinks($node['childNodes'], $cssEvents, $id); + } + } + } + + private function parseMutationArrayRecursivelyToSanitizeNodes(&$nodes) + { + foreach ($nodes as &$node) { + if (!empty($node['attributes'])) { + // empty all the attributes with base64 and contains javascript/script/"(" + // Eg: OR + foreach ($node['attributes'] as $nodeAttributeKey => &$nodeAttributeValue) { + // had to double encode `\x09` as `\\\\x09` in MutationManipulatorTest.php to make json_decode work, else it was giving "syntax error" via json_last_error_msg() + // Due to double encoding had to add entry for both "\\x09" and "\x09" + $nodeAttributeValue = str_replace(["\\x09", "\\x0a", "\\x0d", "\\0", "\x09", "\x0a", "\x0d", "\0"], "", $nodeAttributeValue); + $htmlDecodedAttributeValue = html_entity_decode($nodeAttributeValue, ENT_COMPAT, 'UTF-8'); + if ( + $htmlDecodedAttributeValue && + ( + stripos($htmlDecodedAttributeValue, 'ecmascript') !== false || + stripos($htmlDecodedAttributeValue, 'javascript') !== false || + stripos($htmlDecodedAttributeValue, 'script:') !== false || + stripos($htmlDecodedAttributeValue, 'jscript') !== false || + stripos($htmlDecodedAttributeValue, 'vbscript') !== false + ) + ) { + $nodeAttributeValue = ''; + } elseif (stripos($nodeAttributeValue, 'base64') !== false) { + $base64KeywordMadeLowerCase = str_ireplace('base64', 'base64', $nodeAttributeValue); + //For values like data:text/javascript;base64,YWxlcnQoMSk= we split the value into 2 parts + // part1: data:text/javascript;base64 + // part2: ,YWxlcnQoMSk= we split the value into 2 parts + // we determine the position of first comma from second part and try to decode the base64 string and check fo possible XSS + // cannot assume the position of firstComma to be `0` since there can be string with spaces in beginning + $attributeExploded = explode('base64', $base64KeywordMadeLowerCase); + array_shift($attributeExploded); + if (!empty($attributeExploded)) { + foreach ($attributeExploded as $attributeExplodedValue) { + $htmlDecodedAttributeString = html_entity_decode($attributeExplodedValue, ENT_COMPAT, 'UTF-8'); + $base64DecodedString = base64_decode($attributeExplodedValue); + $base64UrlDecodedString = base64_decode(urldecode($attributeExplodedValue)); + if ( + $this->isXssString($base64DecodedString) || + $this->isXssString($base64UrlDecodedString) || + $this->isXssString($htmlDecodedAttributeString) || + $this->isXssString(urldecode($htmlDecodedAttributeString)) + ) { + $nodeAttributeValue = ''; + break; + } + } + } + } elseif ($nodeAttributeValue) { + $htmlDecodedString = html_entity_decode($nodeAttributeValue, ENT_COMPAT, 'UTF-8'); + if ( + $this->isXssString($htmlDecodedString) || + $this->isXssString(urldecode($htmlDecodedString)) + ) { + $nodeAttributeValue = ''; + } + } + } + } + + if (!empty($node['childNodes'])) { + $this->parseMutationArrayRecursivelyToSanitizeNodes($node['childNodes']); + } + } + } + + private function isXssString($value) + { + if ( + !empty($value) && + ( + stripos($value, 'script:') !== false || + stripos($value, 'javascript') !== false || + stripos($value, 'ecmascript') !== false || + stripos($value, '') !== false || + strpos($value, '&#') !== false // Wll handle decimal cases without trailing semicolon + // eg: + ) + ) { + return true; + } + + return false; + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/README.md b/files/plugin-HeatmapSessionRecording-5.2.4/README.md new file mode 100644 index 0000000..a817d91 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/README.md @@ -0,0 +1,120 @@ +# Matomo HeatmapSessionRecording Plugin + +## Description + +Dive deep into your visitors' behaviours to see where they click and why. Identify friction points to craft a more intuitive user experience. + +Buried under endless heaps of data, the true story of how your visitors experience your site remains hidden. Because it's not enough to know how many clicks you get, but why they happen. + +Unveil the narrative behind each click with Heatmaps and Session Recordings. Observe and understand every mouse scroll, movement, and click to make confident decisions rooted in real user behaviour. + +### How Heatmaps & Session Recordings Works + +#### See The Untold Story of Your Visitors + +
+
+

There's so much happening behind your click data than you realise. Discover where your visitors click, move their mouse, and scroll with Heatmaps.

+

See how your visitors interact with your website without having to create complex reports. Set your heatmaps in seconds and start analysing your user experience for every device and size screen hassle-free.

+
+
+See The Untold Story of Your Visitors +
+
+ +#### Empower Your Usability with Clarity and Buy-In + +
+
+

Improving user experience may be a priority, but without the right data or buy-in, it's easy to second-guess your design changes.

+

With Matomo's heatmaps, you can translate complex user interactions into vivid, easy-to-grasp visuals. Heatmaps don't just represent data but a complete view of your visitors' behaviours.

+
+
+Empower Your Usability with Clarity and Buy-In +
+
+ +#### Relive Every Click, Scroll, and Pause with Session Recordings + +
+
+

Ever wished you could look over your visitor's shoulder, seeing every move they make? Matomo's Session Recordings make this a reality while keeping full GDPR compliance.

+

Watch where your visitors move their mouse, click, and scroll. Discover what's stopping them from converting and boost your conversions backed with real visitor data.

+
+
+Relive Every Click, Scroll, and Pause with Session Recordings +
+
+ +#### Stitch Together the Complete User Journey + +
+
+

Traditional web analytics give you mere glimpses of visitor interactions, like where they click or how long they stay. But this alone won't tell you much of the reasons behind their actions.

+

Overlay your Session Recordings with your visitors' profiles in Matomo to reveal a holistic view of their entire journey, from the pages they explore to their on-page behaviours. See the big picture and act with absolute confidence.

+
+
+Stitch Together the Complete User Journey +
+
+ +### Try Heatmaps & Session Recordings Today + +Shine a light on user behaviours, revealing actionable insights and opportunities for improvement with Matomo's Heatmaps & Session Recordings plugin. + +Start your free 30-day trial and elevate your user experience effortlessly. + +### Heatmap visualization features +* View click, mouse move (hover) and scroll heatmaps +* View the heatmaps for desktop, tablet and mobile devices +* See how much of the content is visible on average when users open the website (above the fold) +* See how far down your visitors scroll +* Delete an already taken heatmap screenshot +* Choose between different heatmap widths +* Apply [segments](https://matomo.org/docs/segmentation/) to drill down your visitors and gain insights into specific target groups + +### Recording features +* Visitor summary shows used browser, operating system, location, viewport resolution, spent time on the page, and more. +* Video controls like play, pause, replay and seek +* Video timeline shows you when a certain event like a click, mouse move, or scroll will happen +* Replays all clicks, mouse movements, scrolls, window resizes, form interactions, and page changes (eg when a popup appears) +* Replay all recorded page views of a visitor within a session one after another +* Delete individual recordings +* Enable autoplay to replay all page views within a visit automatically +* Change the replay speed +* Optionally skip long pauses in a recording automatically +* Use shortcuts when replaying a recorded session +* View the [Visitor Profile](https://matomo.org/docs/user-profile/) to get all information about a visitor +* Replay a recorded session directly from the [Visitor Log](https://matomo.org/docs/real-time/#visitor-log) +* Apply [segments](https://matomo.org/docs/segmentation/) to find the recordings you are interested in + +### Manage Heatmap +* Create unlimited heatmaps +* Select how many page views you want to record +* Define on which page a heatmap should be recorded by applying patterns like "starts with", "contains", "regular expressions" to URL, URL path and URL parameter +* Optionally choose a sample rate +* Optionally hide certain elements in the heatmap (for example a pop-up) +* Optionally define on which URL a screenshot should be taken +* Optionally define custom mobile and tablet breakpoints + +### Manage Session Recordings +* Record unlimited sessions +* Select how many page views you want to record +* Optionally restrict on which pages a visitor should be recorded by using patterns like "starts with" based on URL, URL path and URL parameter +* Optionally choose a sample rate +* Optionally only record activities when a visitor spends at least a specific time on a page +* Optionally only record activities when a user has clicked and scrolled at least once +* Optionally define if keystrokes on text form fields should be captured or not +* It will literally take you only a few seconds to create a new session recording + +### Privacy features +* Anonymizing / Masking of personal or sensitive information that a user enters into a form field (keystrokes) to not record personal data +* Possibility to record form fields in plain text except for form fields that may contain personal information +* Lets you optionally mask any content within the website to avoid the recording of personal information. +* Supports Matomo's [privacy](https://matomo.org/docs/privacy/) and GDPR features like the right to erase data or the right to export data. GDPR stands for General Data Protection Regulation and is for example also known as RGPD in French, DS-GVO in German +* The session recording feature can be disabled while keeping the heatmap feature enabled and vice versa. + +### Export and API features +* HTTP API to manage your heatmaps and session recordings +* HTTP API to fetch and export all [Heatmap & Session Recording reports](https://developer.matomo.org/api-reference/reporting-api#HeatmapSessionRecording) +* Get access to all the raw data via MySQL for 100% data ownership \ No newline at end of file diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Reports/GetRecordedSessions.php b/files/plugin-HeatmapSessionRecording-5.2.4/Reports/GetRecordedSessions.php new file mode 100644 index 0000000..59e639f --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Reports/GetRecordedSessions.php @@ -0,0 +1,344 @@ +categoryId = 'HeatmapSessionRecording_SessionRecordings'; + $this->name = Piwik::translate('HeatmapSessionRecording_SessionRecordings'); + $this->dimension = null; + $this->documentation = Piwik::translate('HeatmapSessionRecording_ReportRecordedSessionsDocumentation'); + + $this->metrics = array( + 'server_time', + 'idloghsr', + 'time_on_site', + 'nb_pageviews', + 'config_os', + 'config_device_type', + 'config_device_model', + 'config_browser_name', + ); + $this->processedMetrics = array(); + $this->actionToLoadSubTables = $this->action; + + $this->order = 1; + } + + private function getValidator() + { + return StaticContainer::get('Piwik\Plugins\HeatmapSessionRecording\Input\Validator'); + } + + + public function configureView(ViewDataTable $view) + { + $view->requestConfig->filter_limit = 100; + + if ($view->isViewDataTableId(HtmlTable::ID)) { + $view->config->disable_row_evolution = true; + } + + $view->config->documentation = $this->documentation; + + if (!empty($_GET['filter_sort_column'])) { + // because we show html in reporting ui we cannot use processed metrics and we need to map it therefore + // manually + + $paramsToFixSort = array( + 'os' => 'config_os', + 'device' => 'config_device_type', + 'browser' => 'config_browser_name', + 'location' => 'country', + ); + $sort = $_GET['filter_sort_column']; + + if (isset($paramsToFixSort[$sort])) { + $_GET['filter_sort_column'] = $paramsToFixSort[$sort]; + } + $view->config->filters[] = function () use ($view, $sort, $paramsToFixSort) { + if (isset($paramsToFixSort[$sort])) { + // we make sure correct column will be selected (sort icon) + $_GET['filter_sort_column'] = $sort; + return; + } + $key = array_search($sort, $paramsToFixSort); + if ($key !== false) { + $_GET['filter_sort_column'] = $key; + } + }; + } + + if (property_exists($view->config, 'show_totals_row')) { + // since Matomo 3.7 for htmltables + $view->config->show_totals_row = false; + } + + $view->config->show_all_views_icons = false; + $view->config->show_exclude_low_population = false; + $view->config->show_table_all_columns = false; + $view->config->show_table = false; + $view->config->show_flatten_table = false; + $view->config->show_search = false; + $view->config->metrics_documentation['resolution'] = Piwik::translate('HeatmapSessionRecording_ColumnResolutionDocumentation'); + $view->config->addTranslation('resolution', Piwik::translate('Resolution_ColumnResolution')); + $view->config->no_data_message = Piwik::translate(HeatmapSessionRecording::getTranslationKey('noDataSession')); + + $idSubtable = Common::getRequestVar('idSubtable', 0, 'int'); + + $idSite = Common::getRequestVar('idSite', 0, 'int'); + $idSiteHsr = Common::getRequestVar('idSiteHsr', 0, 'int'); + if (!empty($idSite) && !empty($idSiteHsr)) { + $model = StaticContainer::get('Piwik\Plugins\HeatmapSessionRecording\Model\SiteHsrModel'); + $recording = Request::processRequest('HeatmapSessionRecording.getSessionRecording', [ + 'idSite' => $idSite, + 'idSiteHsr' => $idSiteHsr, + ], $default = []); + + if (!empty($recording)) { + $requestDate = $model->getPiwikRequestDate($recording); + + if (!PeriodFactory::isPeriodEnabledForAPI($requestDate['period'])) { + $requestDate['period'] = Common::getRequestVar('period', $requestDate['period'], 'string'); + $requestDate['date'] = Common::getRequestVar('date', $requestDate['date'], 'string'); + } + + // we want to fetch all recordings + $view->requestConfig->request_parameters_to_modify['period'] = $requestDate['period']; + $view->requestConfig->request_parameters_to_modify['date'] = $requestDate['date']; + $view->requestConfig->request_parameters_to_modify['idSiteHsr'] = (int)$idSiteHsr; + + if (empty($idSubtable)) { + if ($this->getValidator()->canWrite($idSite)) { + $view->config->title_edit_entity_url = 'index.php' . Url::getCurrentQueryStringWithParametersModified(array( + 'module' => 'HeatmapSessionRecording', + 'action' => 'manageSessions' + )) . '#?idSiteHsr=' . (int)$idSiteHsr; + } + + $view->config->title = Piwik::translate('HeatmapSessionRecording_SessionRecordingX', '"' . $recording['name'] . '"'); + + if ($recording['status'] == SiteHsrDao::STATUS_ACTIVE) { + $view->config->show_footer_message = Piwik::translate('HeatmapSessionRecording_RecordedSessionsDocStatusActive', array($recording['sample_limit'], $recording['sample_rate'] . '%')); + } elseif ($recording['status'] == SiteHsrDao::STATUS_ENDED) { + $view->config->show_footer_message = Piwik::translate('HeatmapSessionRecording_RecordedSessionsDocStatusEnded'); + } + } + } + } + + $report = $this; + + if (empty($idSubtable)) { + $view->config->addTranslation('label', Piwik::translate('HeatmapSessionRecording_ColumnLabelRecordedSessions')); + } else { + $view->config->addTranslation('label', 'URL'); + $view->config->enable_sort = false; + $view->requestConfig->filter_sort_column = 'server_time'; + $view->requestConfig->filter_sort_order = 'asc'; + $_GET['filter_sort_column'] = 'server_time'; + $_GET['filter_sort_order'] = 'asc'; + } + + $view->config->metrics_documentation['nb_pageviews'] = Piwik::translate('HeatmapSessionRecording_ColumnPageviewsDocumentation'); + + $validator = StaticContainer::get('Piwik\Plugins\HeatmapSessionRecording\Input\Validator'); + $view->config->custom_parameters['writeAccess'] = $validator->canWrite($idSite); + + $view->config->filters[] = function (DataTable $table) use ($report, $view, $idSiteHsr, $idSubtable) { + // we need to handle the metrics manually and cannot use processed metrics! + // this is because we "merge" columns in those metrics using arrays (compute() returns sometimes arrays) + // returning arrays in compute() wouldn't work in UI tests so we do it here manually and in API always return + // raw response. This way the API response is also more usable as the HTML those methods return is only + // interesting for us when visualizing it + + if (empty($idSubtable)) { + $metrics = array(new TotalEvents(), new TimeOnSite(), new Location(), new Browser(), + new Device(), new OperatingSystem(), new SessionTime(DateTimeFormatProvider::DATETIME_FORMAT_SHORT)); + } else { + $metrics = array(new TimeOnPage(), new SessionTime(DateTimeFormatProvider::TIME_FORMAT)); + } + + $formatter = new Formatter(); + + foreach ($table->getRowsWithoutSummaryRow() as $row) { + $row->setMetadata('idvisitor', $row->getColumn('idvisitor')); + $row->setMetadata('idsitehsr', $idSiteHsr); + $row->setMetadata('idloghsr', $row->getColumn('idloghsr')); + + if (empty($idSubtable)) { + $row->setMetadata('idvisit', $row->getColumn('idvisit')); + } + + if (!empty($idSubtable)) { + $label = $row->getColumn('label'); + $row->setMetadata('url', $label); + $row->setColumn('label', EnrichRecordedSessions::shortUrl($label)); + } elseif ($row->getColumn('nb_pageviews') == 1) { + $firstUrl = $row->getColumn('first_url'); + $row->setColumn('label', EnrichRecordedSessions::shortUrl($firstUrl)); + $row->setMetadata('url', $firstUrl); + $row->setNonLoadedSubtableId(null); // no need to have it expandable I reckon + } else { + $firstUrl = EnrichRecordedSessions::shortUrl($row->getColumn('first_url')); + $lastUrl = EnrichRecordedSessions::shortUrl($row->getColumn('last_url')); + $row->setColumn('label', $firstUrl . ' → ' . $lastUrl); + } + } + + foreach ($metrics as $processedMetric) { + /** @var BaseMetric $processedMetric */ + $name = $processedMetric->getName(); + $view->config->addTranslation($name, $processedMetric->getTranslatedName()); + + $documentation = $processedMetric->getDocumentation(); + if (!empty($documentation)) { + $view->config->metrics_documentation[$name] = $documentation; + } + + if (!$processedMetric->beforeCompute($report, $table)) { + continue; + } + + $doFormat = $processedMetric->beforeFormat($report, $table); + + foreach ($table->getRowsWithoutSummaryRow() as $row) { + $value = $row->getColumn($name); + if ($value === false) { + // only compute the metric if it has not been computed already + $value = $processedMetric->compute($row); + } + if ($doFormat) { + $value = $processedMetric->format($value, $formatter); + } + + if ($processedMetric->showsHtml() && $value !== false) { + $row->setColumn($name, ' '); + $row->setMetadata('html_column_' . $name . '_prefix', $value); + } else { + $row->setColumn($name, $value); + } + } + } + }; + + if (!empty($idSubtable)) { + $view->config->columns_to_display = array( + 'label', + 'server_time', + 'time_on_page', + 'resolution' + ); + } else { + $systemSettings = StaticContainer::get(SystemSettings::class); + $includedCountries = $systemSettings->getIncludedCountries(); + $headerMessage = ''; + if (!HeatmapSessionRecording::isMatomoJsWritable()) { + $headerMessage .= '
' . + Piwik::translate('HeatmapSessionRecording_MatomoJSNotWritableErrorMessage', [ + Piwik::translate('HeatmapSessionRecording_SessionRecordings'), + '', + '' + ]) . '
'; + } + if (!empty($includedCountries)) { + $headerMessage .= '
' . Piwik::translate('HeatmapSessionRecording_SessionRecordingInfoTrackVisitsFromCountries', [implode(', ', $includedCountries)]) . '
'; + } + $detectAdBlockerView = new View('@HeatmapSessionRecording/_detectAdBlocker'); + $detectAdBlockerView->type = 'Session recordings'; + $headerMessage .= $detectAdBlockerView->render(); + if ($headerMessage) { + $view->config->show_header_message = $headerMessage; + } + + $view->config->columns_to_display = array( + 'label', + 'nb_pageviews', + 'total_events', + 'server_time', + 'time_on_site', + 'location', + 'device', + 'os', + 'browser', + ); + } + } + + public function alwaysUseDefaultViewDataTable() + { + return true; + } + + public function configureWidgets(WidgetsList $widgetsList, ReportWidgetFactory $factory) + { + $idSite = Common::getRequestVar('idSite', $default = 0, 'int'); + + if ($this->getValidator()->canViewSessionReport($idSite)) { + $recordings = Request::processRequest('HeatmapSessionRecording.getSessionRecordings', [ + 'idSite' => $idSite, 'filter_limit' => -1 + ], $default = []); + + foreach ($recordings as $recording) { + $widget = $factory->createWidget(); + $widget->setName(sprintf('Session recording "%s"', $recording['name'])); + $widget->setSubcategoryId($recording['idsitehsr']); + $widget->setIsNotWidgetizable(); + $widget->setParameters(array('idSiteHsr' => $recording['idsitehsr'])); + $widgetsList->addWidgetConfig($widget); + } + } + } + + public function configureReportMetadata(&$availableReports, $infos) + { + // disabled for now + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Settings/TrackingDisableDefault.php b/files/plugin-HeatmapSessionRecording-5.2.4/Settings/TrackingDisableDefault.php new file mode 100644 index 0000000..716a5ed --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Settings/TrackingDisableDefault.php @@ -0,0 +1,36 @@ +oldValue; + } + + public function setValue($value) + { + $this->oldValue = $this->getValue(); + + parent::setValue($value); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/SystemSettings.php b/files/plugin-HeatmapSessionRecording-5.2.4/SystemSettings.php new file mode 100644 index 0000000..3c16e9c --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/SystemSettings.php @@ -0,0 +1,296 @@ +breakpointMobile = $this->createBreakpointMobileSetting(); + $this->breakpointTablet = $this->createBreakpointTabletSetting(); + $this->disableTrackingByDefault = $this->createDisableTrackingByDefaultSetting(); + $this->disableSessionRecording = $this->createDisableSessionRecordingSetting(); + $this->disableHeatmapRecording = $this->createDisableHeatmapRecordingSetting(); + $this->includeCountries = $this->createIncludeCountriesSetting(); + + if (Plugin\Manager::getInstance()->isPluginActivated('CustomJsTracker')) { + $trackerUpdater = StaticContainer::get(TrackerUpdater::class); + if (!$trackerUpdater || !$trackerUpdater->getToFile() || !$trackerUpdater->getToFile()->hasWriteAccess()) { + // only works if matomo file can be updated + $this->disableTrackingByDefault->setIsWritableByCurrentUser(false); + } + } else { + $this->disableTrackingByDefault->setIsWritableByCurrentUser(false); + } + } + + private function createDisableTrackingByDefaultSetting() + { + $setting = new TrackingDisableDefault('trackingDisabledDefault', false, FieldConfig::TYPE_BOOL, $this->pluginName); + $setting->setConfigureCallback(function (FieldConfig $field) { + $field->title = Piwik::translate('HeatmapSessionRecording_TrackingDisabledDefaultSettingTitle'); + $field->uiControl = FieldConfig::UI_CONTROL_CHECKBOX; + $field->description = Piwik::translate('HeatmapSessionRecording_TrackingDisabledDefaultSettingDescription'); + }); + $this->addSetting($setting); + return $setting; + } + + private function createBreakpointMobileSetting() + { + return $this->makeSetting('breakpointMobile', Breakpoint::DEFAULT_MOBILE, FieldConfig::TYPE_INT, function (FieldConfig $field) { + $field->title = Piwik::translate('HeatmapSessionRecording_BreakpointX', Piwik::translate('General_Mobile')); + $field->uiControl = FieldConfig::UI_CONTROL_TEXT; + $field->description = Piwik::translate('HeatmapSessionRecording_BreakpointGeneralHelp'); + $field->validate = function ($value) { + $breakpoint = new Breakpoint($value, Piwik::translate('General_Mobile')); + $breakpoint->check(); + }; + }); + } + + private function createBreakpointTabletSetting() + { + return $this->makeSetting('breakpointTablet', Breakpoint::DEFAULT_TABLET, FieldConfig::TYPE_INT, function (FieldConfig $field) { + $field->title = Piwik::translate('HeatmapSessionRecording_BreakpointX', Piwik::translate('DevicesDetection_Tablet')); + $field->uiControl = FieldConfig::UI_CONTROL_TEXT; + $field->description = Piwik::translate('HeatmapSessionRecording_BreakpointGeneralHelp'); + $field->validate = function ($value) { + $breakpoint = new Breakpoint($value, Piwik::translate('DevicesDetection_Tablet')); + $breakpoint->check(); + }; + }); + } + + private function createDisableSessionRecordingSetting() + { + $setting = new TrackingDisableDefault('disableSessionRecording', false, FieldConfig::TYPE_BOOL, $this->pluginName); + $setting->setConfigureCallback(function (FieldConfig $field) { + $field->title = Piwik::translate('HeatmapSessionRecording_DisableSessionRecordingTitle', Piwik::translate('General_Mobile')); + $field->description = Piwik::translate('HeatmapSessionRecording_DisableSessionRecordingDescription'); + $field->inlineHelp = Piwik::translate('HeatmapSessionRecording_DisableSessionRecordingInlineHelp', array('','')); + }); + $this->addSetting($setting); + + return $setting; + } + + private function createDisableHeatmapRecordingSetting() + { + $setting = new TrackingDisableDefault('disableHeatmapRecording', false, FieldConfig::TYPE_BOOL, $this->pluginName); + $setting->setConfigureCallback(function (FieldConfig $field) { + $field->title = Piwik::translate('HeatmapSessionRecording_DisableHeatmapRecordingTitle', Piwik::translate('General_Mobile')); + $field->description = Piwik::translate('HeatmapSessionRecording_DisableHeatmapRecordingDescription'); + $field->inlineHelp = Piwik::translate('HeatmapSessionRecording_DisableHeatmapRecordingInlineHelp', array('','')); + }); + $this->addSetting($setting); + + return $setting; + } + + public function createIncludeCountriesSetting() + { + return $this->makeSetting('included_countries', [], FieldConfig::TYPE_ARRAY, function (FieldConfig $field) { + $field->title = Piwik::translate('HeatmapSessionRecording_EnableIncludeCountriesTitle'); + $field->description = Piwik::translate('HeatmapSessionRecording_EnableIncludeCountriesDescription'); + $field->uiControl = FieldConfig::UI_CONTROL_MULTI_TUPLE; + $field1 = new FieldConfig\MultiPair(Piwik::translate('HeatmapSessionRecording_Country'), 'country', FieldConfig::UI_CONTROL_SINGLE_SELECT); + $field1->availableValues = $this->getAvailableCountries(); + $field->uiControlAttributes['field1'] = $field1->toArray(); + + $self = $this; + $field->transform = function ($value) use ($self) { + return $self->transformCountryList($value); + }; + $field->validate = function ($value) use ($field1) { + foreach ($value as $country) { + if (empty($country['country'])) { + continue; + } + if ($country['country'] === 'xx') { + continue; // valid, country not detected + } + if (!isset($field1->availableValues[$country['country']])) { + throw new \Exception('Invalid country code'); + } + } + }; + }); + } + + public function transformCountryList($value) + { + if (!empty($value) && is_array($value)) { + $newVal = []; + foreach ($value as $index => $val) { + if (empty($val['country'])) { + continue; + } + $newVal[] = ['country' => $val['country']]; + } + return $newVal; + } + return $value; + } + + public function save() + { + $this->endSessionRecordings(); + $this->endHeatmapRecordings(); + parent::save(); + + if (!empty($this->disableTrackingByDefault)) { + $oldValue = $this->disableTrackingByDefault->getOldValue(); + $newValue = $this->disableTrackingByDefault->getValue(); + if ($oldValue != $newValue) { + $plugin = Plugin\Manager::getInstance()->getLoadedPlugin($this->pluginName); + if (!empty($plugin) && $plugin instanceof HeatmapSessionRecording) { + $plugin->updatePiwikTracker(); + } + } + } + } + + public function getIncludedCountries($applyTransformation = true) + { + $includedCountries = $this->includeCountries->getValue(); + $transformedList = []; + + if (!empty($includedCountries)) { + foreach ($includedCountries as $value) { + $transformedList[] = $value['country']; + } + } + + if (!empty($transformedList) && $applyTransformation) { + $transformedList = $this->getAvailableCountries($transformedList); + } + + return $transformedList; + } + + private function getAvailableCountries($countryCodesToFilter = []) + { + $regionDataProvider = StaticContainer::get(RegionDataProvider::class); + $countryList = $regionDataProvider->getCountryList(); + array_walk($countryList, function (&$item, $key) { + $item = Piwik::translate('Intl_Country_' . strtoupper($key)); + }); + asort($countryList); //order by localized name + + if (!empty($countryCodesToFilter)) { + $filteredList = []; + foreach ($countryList as $countryCode => $countryName) { + if (in_array($countryCode, $countryCodesToFilter)) { + $filteredList[$countryCode] = $countryName; + } + } + + return $filteredList; + } + + return $countryList; + } + + private function endSessionRecordings() + { + if ( + !empty($this->disableSessionRecording->getValue()) && + $this->disableSessionRecording->getOldValue() != $this->disableSessionRecording->getValue() + ) { + $sites = SitesManagerAPI::getInstance()->getAllSitesId(); + $this->disableSessionRecording->setValue(false); //added this to fetch results, else it throws an exception + foreach ($sites as $idSite) { + if (Site::getTypeFor($idSite) === 'proxysite') { + continue; //do not delete session recording for proxySite as it will throw an exception due to read only behaviour + } + $recordings = Request::processRequest('HeatmapSessionRecording.getSessionRecordings', [ + 'idSite' => $idSite, 'filter_limit' => -1 + ]); + foreach ($recordings as $recording) { + Request::processRequest('HeatmapSessionRecording.deleteSessionRecording', [ + 'idSite' => $idSite, + 'idSiteHsr' => $recording['idsitehsr'] + ]); + } + } + + $this->disableSessionRecording->setValue(true); + Cache::deleteTrackerCache(); + } + } + + private function endHeatmapRecordings() + { + if ( + !empty($this->disableHeatmapRecording->getValue()) && + $this->disableHeatmapRecording->getOldValue() != $this->disableHeatmapRecording->getValue() + ) { + $sites = SitesManagerAPI::getInstance()->getAllSitesId(); + $this->disableHeatmapRecording->setValue(false); //added this to fetch results, else it throws an exception + foreach ($sites as $idSite) { + if (Site::getTypeFor($idSite) === 'proxysite') { + continue; //do not delete heatmap for proxySite as it will throw an exception due to read only behaviour + } + $recordings = Request::processRequest('HeatmapSessionRecording.getHeatmaps', [ + 'idSite' => $idSite, 'filter_limit' => -1 + ]); + foreach ($recordings as $recording) { + Request::processRequest('HeatmapSessionRecording.deleteHeatmap', [ + 'idSite' => $idSite, + 'idSiteHsr' => $recording['idsitehsr'] + ]); + } + } + + $this->disableHeatmapRecording->setValue(true); + Cache::deleteTrackerCache(); + } + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Tasks.php b/files/plugin-HeatmapSessionRecording-5.2.4/Tasks.php new file mode 100644 index 0000000..6ff6744 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Tasks.php @@ -0,0 +1,77 @@ +daily('removeDeletedRecordings'); + + // we are doing this rarely to avoid removing actions that might be used eg a day later or so again + $this->monthly('removeUnusedHsrBlobs'); + } + + public function __construct(LogHsr $logHsr, LogHsrSite $logHsrSite, LogHsrBlob $logHsrBlob) + { + $this->logHsr = $logHsr; + $this->logHsrSite = $logHsrSite; + $this->logHsrBlob = $logHsrBlob; + } + + /** + * To test execute the following command: + * `./console core:run-scheduled-tasks "Piwik\Plugins\HeatmapSessionRecording\Tasks.removeDeletedRecordings"` + * + * @throws \Exception + */ + public function removeDeletedRecordings() + { + $this->logHsrSite->deleteNoLongerNeededRecords(); + + $idLogHsrsToDelete = $this->logHsr->findDeletedLogHsrIds(); + $this->logHsr->deleteIdLogHsrsFromAllTables($idLogHsrsToDelete); + } + + /** + * To test execute the following command: + * `./console core:run-scheduled-tasks "Piwik\Plugins\HeatmapSessionRecording\Tasks.removeDeletedHsrBlobs"` + */ + public function removeUnusedHsrBlobs() + { + $this->logHsrBlob->deleteUnusedBlobEntries(); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Tracker/Configs.php b/files/plugin-HeatmapSessionRecording-5.2.4/Tracker/Configs.php new file mode 100644 index 0000000..d1cc86c --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Tracker/Configs.php @@ -0,0 +1,160 @@ + $validator + )); + try { + $environment->init(); + } catch (NotYetInstalledException $e) { + http_response_code(403); + exit; + } catch (Exception $e) { + if (!\Piwik\SettingsPiwik::isMatomoInstalled()) { + // the validator is disabled for performance so we would not detect a not installed matomo + http_response_code(403); + exit; + } + throw $e; + } + } + + public function sendResponse($response, $originalIdSite, $trackerId) + { + $response['idsite'] = $originalIdSite; + $response['trackerid'] = $trackerId; + header('Content-Type: application/javascript'); + echo 'Piwik.HeatmapSessionRecording.configuration.assign(' . json_encode($response) . ');'; + } + + public function loadTrackerEnvironment() + { + if (!$this->hasLoadedTrackerEnvironment) { + $this->hasLoadedTrackerEnvironment = true; + try { + Tracker::loadTrackerEnvironment(); + $GLOBALS['PIWIK_TRACKER_DEBUG'] = false; + } catch (Exception $e) { + $GLOBALS['PIWIK_TRACKER_DEBUG'] = false; + if (!\Piwik\SettingsPiwik::isMatomoInstalled()) { + // the validator is disabled for performance so we would not detect a not installed matomo + http_response_code(403); + exit; + } + throw $e; + } + } + } + + private function isNumber($value) + { + return preg_match('/^\d+$/', (string) $value); + } + + public function getIdSite($idSite) + { + if (is_string($idSite) && !$this->isNumber($idSite)) { + $this->loadTrackerEnvironment(); + + /** + * 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. + * @param array $params The entire array of request parameters in the current tracking + * request. + */ + \Piwik\Piwik::postEvent('Tracker.Request.getIdSite', array(&$idSite, array_merge($_GET, $_POST))); + + if (!$this->isNumber($idSite)) { + // the string idsite was not converted to an integer / number... + // we do not cast to int as it could result in using the wrong idsite when eg string is '5Tk3k' => could result + // in idsite=5 which may not be the correct idsite + http_response_code(400); + exit; + } + } + + $idSite = (int) abs($idSite); + return $idSite; + } + + public function getCachedWebsiteAttributes($idSite) + { + try { + $cache = Tracker\Cache::getCacheWebsiteAttributes($idSite); + } catch (\Piwik\Exception\UnexpectedWebsiteFoundException $exception) { + http_response_code(400); + exit; + } catch (Exception $e) { + if (!\Piwik\SettingsPiwik::isMatomoInstalled()) { + // the validator is disabled for performance so we would not detect a not installed matomo + http_response_code(403); + exit; + } + throw $e; + } + + if (empty($cache)) { + ob_start(); + // this is a performance improvement to bootstrap the plugins only if the cache does not exist right now + $this->loadTrackerEnvironment(); + + try { + $cache = Tracker\Cache::getCacheWebsiteAttributes($idSite); + } catch (\Piwik\Exception\UnexpectedWebsiteFoundException $exception) { + http_response_code(400); + exit; + } catch (Exception $e) { + if (!\Piwik\SettingsPiwik::isMatomoInstalled()) { + // the validator is disabled for performance so we would not detect a not installed matomo + http_response_code(403); + exit; + } + throw $e; + } + ob_end_clean(); + } + + return $cache; + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Tracker/HsrMatcher.php b/files/plugin-HeatmapSessionRecording-5.2.4/Tracker/HsrMatcher.php new file mode 100644 index 0000000..ac70179 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Tracker/HsrMatcher.php @@ -0,0 +1,224 @@ +fetch($samplesKey); + + if ($hsr['record_type'] == SiteHsrDao::RECORD_TYPE_HEATMAP) { + if (empty($numSamples)) { + $numSamples = $logHsrSite->getNumPageViews($idSiteHsr); + $lazyCache->save($samplesKey, $numSamples, $ttlInSec = 300); + } + + if ($numSamples >= $hsr['sample_limit']) { + $siteHsr = \Piwik\Container\StaticContainer::get('Piwik\Plugins\HeatmapSessionRecording\Model\SiteHsrModel'); + $siteHsr->endHeatmap($idSite, $idSiteHsr); + // we make sure in the future the completed HSR won't receive any recordings anymore afterwards + Cache::deleteCacheWebsiteAttributes($idSite); + + return false; + } + } elseif ($hsr['record_type'] == SiteHsrDao::RECORD_TYPE_SESSION) { + if (empty($numSamples)) { + $numSamples = $logHsrSite->getNumSessions($idSiteHsr); + $lazyCache->save($samplesKey, $numSamples, $ttlInSec = 300); + } + + if ($numSamples >= $hsr['sample_limit']) { + $siteHsr = \Piwik\Container\StaticContainer::get('Piwik\Plugins\HeatmapSessionRecording\Model\SiteHsrModel'); + + $siteHsr->endSessionRecording($idSite, $idSiteHsr); + // we make sure in the future the completed HSR won't receive any recordings anymore afterwards + Cache::deleteCacheWebsiteAttributes($idSite); + + return false; + } + } + + return true; + } + + public static function makeCookieSampleKey($idSiteHsr) + { + return self::QUERY_NAMESPACE . (int) $idSiteHsr; + } + + public static function isUserPartOfSampleGroup($recordType, $idSiteHsr) + { + if ($recordType == SiteHsrDao::RECORD_TYPE_SESSION) { + $key = self::makeCookieSampleKey($idSiteHsr); + if (isset($_GET[$key]) && $_GET[$key] == '1') { + return self::SAMPLE_GROUP_IS_PART; + } elseif (isset($_GET[$key]) && $_GET[$key] == '0') { + return self::SAMPLE_GROUP_NOT_PART; + } + } + + return self::SAMPLE_GROUP_TO_BE_DETECTED; + } + + /** + * @param $matchPageRules + * @param $url + * @return bool + */ + public static function matchesAllPageRules($matchPageRules, $url) + { + if (empty($matchPageRules)) { + // no restrictions, we track any page + return true; + } + + if (is_array($matchPageRules)) { + // ALL RULES NEED TO MATCH! + + foreach ($matchPageRules as $pageRule) { + $pageRule = new PageRuleMatcher($pageRule); + if (!$pageRule->matches($url)) { + return false; + } + } + + return true; + } + + return false; + } + + public static function isIncludedCountry($includedCountries) + { + if (empty($includedCountries)) { + return true; + } + $ipAddress = IP::getIpFromHeader(); + $browserLanguage = Common::getBrowserLanguage(); + + if (defined('PIWIK_TEST_MODE') && PIWIK_TEST_MODE) { + //Need to use MockLocationProvider else it will throw an error, need to do it here instead of test as `configs.php` is called via HTTP call + $visitorLocator = new MockLocationProvider(); + } else { + $visitorLocator = new VisitorGeolocator(); + } + + $info = array('lang' => $browserLanguage, 'ip' => $ipAddress); + + $provider = $visitorLocator->getProvider(); + if ($provider === null) { + // Try even without a provider because something might be cached + try { + $visitorLocator = $visitorLocator->getLocation($info, $useClassCache = true); + } catch (\Throwable $throwable) { + return false; + } + } else { + $visitorLocator = $visitorLocator->getLocation($info, $useClassCache = true); + } + + if (empty($visitorLocator[LocationProvider::COUNTRY_CODE_KEY])) { + return false; + } + + $countryCode = strtolower($visitorLocator[LocationProvider::COUNTRY_CODE_KEY]); + + if (in_array($countryCode, $includedCountries, true)) { + return true; + } + + return false; + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Tracker/LogTable/LogHsr.php b/files/plugin-HeatmapSessionRecording-5.2.4/Tracker/LogTable/LogHsr.php new file mode 100644 index 0000000..eb8a31c --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Tracker/LogTable/LogHsr.php @@ -0,0 +1,42 @@ + 'idhsrblob'); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Tracker/LogTable/LogHsrEvent.php b/files/plugin-HeatmapSessionRecording-5.2.4/Tracker/LogTable/LogHsrEvent.php new file mode 100644 index 0000000..b011f0d --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Tracker/LogTable/LogHsrEvent.php @@ -0,0 +1,37 @@ + 'idloghsr'); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Tracker/PageRuleMatcher.php b/files/plugin-HeatmapSessionRecording-5.2.4/Tracker/PageRuleMatcher.php new file mode 100644 index 0000000..427d870 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Tracker/PageRuleMatcher.php @@ -0,0 +1,305 @@ +pageRule = $pageRule; + } + + /** + * Check if the experiment matches the given target. + * + * @param string $url + * @return bool + * @throws \Exception + */ + public function matches($url) + { + if (empty($this->pageRule['type']) || empty($this->pageRule['attribute'])) { + return true; + } + + $attributeValue = $this->getValueForAttribute($url); + + switch (strtolower($this->pageRule['attribute'])) { + case self::ATTRIBUTE_URL: + case self::ATTRIBUTE_PATH: + return $this->matchesTargetValue($attributeValue, $this->pageRule['type'], $this->pageRule['inverted'], $this->pageRule['value']); + case self::ATTRIBUTE_URLPARAM: + $value2 = null; + if (isset($this->pageRule['value2'])) { + $value2 = $this->pageRule['value2']; + } + + if (is_array($attributeValue)) { + // eg when url param is `&foo=bar&foo=baz` and trying to match foo url param + if ($this->pageRule['inverted']) { + $matchesOne = true; + foreach ($attributeValue as $attrVal) { + $matchesOne = $matchesOne && $this->matchesTargetValue($attrVal, $this->pageRule['type'], $this->pageRule['inverted'], $value2); + } + return $matchesOne; + } else { + $matchesOne = false; + foreach ($attributeValue as $attrVal) { + $matchesOne = $matchesOne || $this->matchesTargetValue($attrVal, $this->pageRule['type'], $this->pageRule['inverted'], $value2); + } + return $matchesOne; + } + } + return $this->matchesTargetValue($attributeValue, $this->pageRule['type'], $this->pageRule['inverted'], $value2); + } + + return false; + } + + protected function getValueForAttribute($url) + { + if (!empty($url) && is_string($url)) { + $enabledForceSample = HeatmapSessionRecording::ULR_PARAM_FORCE_SAMPLE . '=1'; + $disabledForceSample = HeatmapSessionRecording::ULR_PARAM_FORCE_SAMPLE . '=0'; + $enabledForceScreen = HeatmapSessionRecording::ULR_PARAM_FORCE_CAPTURE_SCREEN . '=1'; + $disabledForceScreen = HeatmapSessionRecording::ULR_PARAM_FORCE_CAPTURE_SCREEN . '=0'; + + $url = str_replace(array( + '?' . $enabledForceSample, + '?' . $disabledForceSample, + '?' . $enabledForceScreen, + '?' . $disabledForceScreen + ), '?', $url); + $url = rtrim($url, '?'); // eg when https://www.example.com?pk_hsr_forcesample=1 + $url = str_replace(array( + '&' . $enabledForceSample, + '&' . $disabledForceSample, + '&' . $enabledForceScreen, + '&' . $disabledForceScreen + ), '', $url); + } + + switch (strtolower($this->pageRule['attribute'])) { + case self::ATTRIBUTE_URL: + return $url; + case self::ATTRIBUTE_PATH: + $urlParsed = parse_url($url); + if (isset($urlParsed['path'])) { + return $urlParsed['path']; + } + return ''; + case self::ATTRIBUTE_URLPARAM: + $urlParsed = parse_url($url); + $targetValue = null; + if (!empty($urlParsed['query']) && !empty($this->pageRule['value'])) { + $paramName = $this->pageRule['value']; + $params = UrlHelper::getArrayFromQueryString($urlParsed['query']); + if (isset($params[$paramName])) { + $targetValue = $params[$paramName]; + } + } + return $targetValue; + } + } + + private function removeWwwSubdomain($host) + { + return str_replace('www.', '', $host); + } + + protected function matchesTargetValue($attributeValue, $type, $invert, $valueToMatch) + { + $matches = false; + + if (is_string($attributeValue)) { + $attributeValue = strtolower($attributeValue); + } + + if (is_string($valueToMatch) && $type !== 'regexp') { + $valueToMatch = strtolower($valueToMatch); + } + + switch ($type) { + case self::TYPE_ANY: + $matches = true; + break; + case self::TYPE_EXISTS: + if ($attributeValue !== null) { + $matches = true; + } + break; + case self::TYPE_EQUALS_SIMPLE: + $parsedActual = parse_url($attributeValue); + $parsedMatch = parse_url($valueToMatch); + + if (isset($parsedActual['host'])) { + $parsedActual['host'] = $this->removeWwwSubdomain($parsedActual['host']); + } + if (isset($parsedMatch['host'])) { + $parsedMatch['host'] = $this->removeWwwSubdomain($parsedMatch['host']); + } + + if (!isset($parsedMatch['host']) || !isset($parsedActual['host']) || $parsedActual['host'] == $parsedMatch['host']) { + if (!isset($parsedActual['path']) && !isset($parsedMatch['path'])) { + $matches = true; + } elseif (isset($parsedActual['path']) && isset($parsedMatch['path'])) { + if ( + $parsedActual['path'] == $parsedMatch['path'] || + $parsedActual['path'] == $parsedMatch['path'] . '/' || + $parsedActual['path'] == '/' . $parsedMatch['path'] || + $parsedActual['path'] == '/' . $parsedMatch['path'] . '/' || + $parsedActual['path'] . '/' == $parsedMatch['path'] + ) { + $matches = true; + } + } elseif (isset($parsedActual['path']) && $parsedActual['path'] === '/' && !isset($parsedMatch['path'])) { + $matches = true; + } elseif (isset($parsedMatch['path']) && $parsedMatch['path'] === '/' && !isset($parsedActual['path'])) { + $matches = true; + } + } + + break; + case self::TYPE_EQUALS_EXACTLY: + if ($attributeValue && $attributeValue === $valueToMatch) { + $matches = true; + } + + if ($valueToMatch && @parse_url($valueToMatch, PHP_URL_PATH) === '/' && $valueToMatch === ($attributeValue . '/')) { + $matches = true; + } + + if ($attributeValue && @parse_url($attributeValue, PHP_URL_PATH) === '/' && $attributeValue === ($valueToMatch . '/')) { + $matches = true; + } + + break; + case self::TYPE_CONTAINS: + if (isset($valueToMatch) && $attributeValue && !is_array($valueToMatch) && !is_object($valueToMatch) && strlen($valueToMatch) && strpos($attributeValue, (string) $valueToMatch) !== false) { + $matches = true; + } + break; + case self::TYPE_STARTS_WITH: + if ($attributeValue && !is_array($valueToMatch) && !is_object($valueToMatch) && strlen($valueToMatch) && strpos($attributeValue, (string) $valueToMatch) === 0) { + $matches = true; + } + break; + case self::TYPE_REGEXP: + $pattern = self::completeRegexpPattern($valueToMatch); + if ($pattern && $attributeValue && preg_match($pattern, $attributeValue)) { + $matches = true; + } + break; + } + + if ($invert) { + return !$matches; + } + + return $matches; + } + + public static function completeRegexpPattern($pattern) + { + return $pattern ? ('/' . str_replace('/', '\/', stripslashes($pattern)) . '/i') : $pattern; + } + + public static function doesTargetTypeRequireValue($type) + { + return $type !== self::TYPE_ANY; + } + + public static function getAvailableTargetTypes() + { + $targetTypes = array(); + + $urlOptions = array( + self::TYPE_EQUALS_EXACTLY => Piwik::translate('HeatmapSessionRecording_TargetTypeEqualsExactly'), + self::TYPE_EQUALS_SIMPLE => Piwik::translate('HeatmapSessionRecording_TargetTypeEqualsSimple'), + self::TYPE_CONTAINS => Piwik::translate('HeatmapSessionRecording_TargetTypeContains'), + self::TYPE_STARTS_WITH => Piwik::translate('HeatmapSessionRecording_TargetTypeStartsWith'), + self::TYPE_REGEXP => Piwik::translate('HeatmapSessionRecording_TargetTypeRegExp'), + ); + + $urlAttribute = array( + 'value' => self::ATTRIBUTE_URL, + 'name' => Piwik::translate('HeatmapSessionRecording_TargetAttributeUrl'), + 'types' => array(), + 'example' => 'http://www.example.com/' . Piwik::translate('HeatmapSessionRecording_FilesystemDirectory') + ); + foreach ($urlOptions as $key => $value) { + $urlAttribute['types'][] = array('value' => $key, 'name' => $value); + } + $targetTypes[] = $urlAttribute; + + + $urlAttribute = array( + 'value' => self::ATTRIBUTE_PATH, + 'name' => Piwik::translate('HeatmapSessionRecording_TargetAttributePath'), + 'types' => array(), + 'example' => '/' . Piwik::translate('HeatmapSessionRecording_FilesystemDirectory') + ); + foreach ($urlOptions as $key => $value) { + $urlAttribute['types'][] = array('value' => $key, 'name' => $value); + } + $targetTypes[] = $urlAttribute; + + + $urlAttribute = array( + 'value' => self::ATTRIBUTE_URLPARAM, + 'name' => Piwik::translate('HeatmapSessionRecording_TargetAttributeUrlParameter'), + 'types' => array(), + 'example' => Piwik::translate('HeatmapSessionRecording_TargetAttributeUrlParameterExample') + ); + + $parameterOptions = array( + self::TYPE_EXISTS => Piwik::translate('HeatmapSessionRecording_TargetTypeExists'), + self::TYPE_EQUALS_EXACTLY => Piwik::translate('HeatmapSessionRecording_TargetTypeEqualsExactly'), + self::TYPE_CONTAINS => Piwik::translate('HeatmapSessionRecording_TargetTypeContains'), + self::TYPE_REGEXP => Piwik::translate('HeatmapSessionRecording_TargetTypeRegExp'), + ); + + foreach ($parameterOptions as $key => $value) { + $urlAttribute['types'][] = array('value' => $key, 'name' => $value); + } + + $targetTypes[] = $urlAttribute; + + return $targetTypes; + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Tracker/RequestProcessor.php b/files/plugin-HeatmapSessionRecording-5.2.4/Tracker/RequestProcessor.php new file mode 100644 index 0000000..ba94e54 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Tracker/RequestProcessor.php @@ -0,0 +1,312 @@ +logHsr = $logHsr; + $this->logEvent = $logEvent; + $this->logMutation = $logMutation; + $this->siteHsr = $siteHsr; + $this->logHsrSite = $logHsrSite; + } + + public function afterRequestProcessed(VisitProperties $visitProperties, Request $request) + { + $params = $request->getParams(); + + if (!empty($params[self::TRACKING_PARAM_HSR_ID_VIEW])) { + // we need to make sure no action will be recorded! especially since we might go into record logs! + $request->setMetadata('Actions', 'action', null); + // make sure no goals will be recorded! + $request->setMetadata('Goals', 'goalsConverted', array()); + + // for all tracking requests further down we need at least one hsrid + if (empty($params[self::TRACKING_PARAM_HSR_IDS]) || !is_array($params[self::TRACKING_PARAM_HSR_IDS])) { + // nothing to record, abort request + return true; + } + + $idVisit = $visitProperties->getProperty('idvisit'); + + if (empty($idVisit)) { + // this is a NEW VISIT request, this should usually not happen as HSR requests are usually sent after + // pageviews. We need to handle the visit during recordLogs() + // For faster performance and to reduce load on visit table etc we want to record those requests + // ideally always here in afterRequestProcessed and terminate as early as possible + return false; + } + + $this->handleHsrRequest($idVisit, $request); + + // abort anything else + return true; + } + } + + public function recordLogs(VisitProperties $visitProperties, Request $request) + { + // we try to insert the request again if it is a new visit, as we now have the idVisit (should have) + return $this->afterRequestProcessed($visitProperties, $request); + } + + private function handleHsrRequest($idVisit, Request $request) + { + $params = $request->getParams(); + $url = $request->getParam('url'); + $idPageview = $request->getParam('pv_id'); + $idSite = $request->getIdSite(); + + $serverTime = Date::getDatetimeFromTimestamp($request->getCurrentTimestamp()); + $timeOnPage = $params[self::TRACKING_PARAM_TOTAL_TIME]; + $viewportW = $params[self::TRACKING_PARAM_VIEWPORT_WIDTH]; + $viewportH = $params[self::TRACKING_PARAM_VIEWPORT_HEIGHT]; + $scrollYMaxPercent = $params[self::TRACKING_PARAM_SCROLL_MAX_PERCENTAGE]; + $foldYpercent = $params[self::TRACKING_PARAM_FOLD_Y_PERCENT]; + $hsrIds = $params[self::TRACKING_PARAM_HSR_IDS]; + $idHsrView = $params[self::TRACKING_PARAM_HSR_ID_VIEW]; + + $maxAllowedTimeOnPage = self::MYSQL_MAX_MEDIUM_UINT; + $configuration = new Configuration(); + $maxAllowedTimeOnPageConfigValue = $configuration->getMaximumAllowedPageTime(); + if (!empty($maxAllowedTimeOnPageConfigValue)) { + $maxAllowedTimeOnPage = $maxAllowedTimeOnPageConfigValue; + } + + if ($timeOnPage > $maxAllowedTimeOnPage) { + return; + } + + $hsrIds = $this->getValidHsrIds($idSite, $hsrIds); + + if (empty($hsrIds)) { + Common::printDebug('Warning! no active hsrIds provided, will ignore tracking request'); + throw new InvalidRequestParameterException('No active hsrIds provided'); + } + + $action = $request->getMetadata('Actions', 'action'); + if (!empty($action)) { + /** @var Tracker\Action $action */ + $action->loadIdsFromLogActionTable(); + $url = $action->getActionUrl(); + } + $userAgent = $request->getUserAgent(); + $resolution = $request->getParam('res'); + + $idLogHsr = $this->logHsr->record($hsrIds, $idSite, $idVisit, $idHsrView, $idPageview, $url, $serverTime, $userAgent, $resolution, $timeOnPage, $viewportW, $viewportH, $scrollYMaxPercent, $foldYpercent); + + $events = $params[self::TRACKING_PARAM_EVENTS]; + + $selectors = array(); + foreach ($events as $index => $event) { + // DO NOT USE VARIABLE REFERENCE HERE! + if (isset($event['s'])) { + $key = md5($event['s']); + $events[$index]['skey'] = $key; + $selectors[$key] = array($event['s'], self::ACTION_TYPE_HSR_SELECTOR, null); + } + } + + $selectorIds = array(); + if (!empty($selectors)) { + $selectorIds = Tracker\TableLogAction::loadIdsAction($selectors); + } + + foreach ($events as $event) { + $this->recordEvent($idLogHsr, $idSite, $event, $url, $selectorIds); + } + } + + private function recordEvent($idLogHsr, $idSite, $event, $url, $selectorIds) + { + $event['ty'] = (int) $event['ty']; + + switch ($event['ty']) { + case self::EVENT_TYPE_STOP_RECORDING: + // we don't record any event for this, only there to track accurate recording time (time when we stopped recording) + break; + case self::EVENT_TYPE_PAGE_TREEMIRROR: + if (!empty($event['dom'])) { + // we need to save the initial DOM for heatmap + $clearCache = false; + $cachedHsrs = $this->getCachedHsrs($idSite); + // we cannot overwrite simply all matching idsitehsr because someone might define a screenshot url and this + // url might be different per hsr. + foreach ($cachedHsrs as $hsr) { + if (empty($hsr['page_treemirror']) && $hsr['idsitehsr'] == $event['id'] && $hsr['record_type'] == SiteHsrDao::RECORD_TYPE_HEATMAP) { + $screenshotUrl = $hsr['screenshot_url']; + if (empty($hsr['screenshot_url'])) { + $screenshotUrl = $url; + } + + if (!empty($screenshotUrl)) { + // we need to make sure a url is set + $this->siteHsr->setPageTreeMirror($idSite, $hsr['idsitehsr'], $event['dom'], $screenshotUrl); + $clearCache = true; + } + } + } + if ($clearCache) { + // prevent tracking initial dom again + Tracker\Cache::deleteCacheWebsiteAttributes($idSite); + } + } + + break; + + case self::EVENT_TYPE_LINK_HSR: + if (isset($event['id'])) { + // we need to link a new idsitehsr to an existing recording + $idSiteHsr = (int) $event['id']; + + if (!empty($idSiteHsr)) { + $this->logHsrSite->linkRecord($idLogHsr, $idSiteHsr); + } + } + + break; + + default: + $idSelector = null; + if (isset($event['skey']) && isset($selectorIds[$event['skey']])) { + $idSelector = $selectorIds[$event['skey']]; + } + $x = null; + if (isset($event['x'])) { + $x = $event['x']; + } + $y = null; + if (isset($event['y'])) { + $y = $event['y']; + } + $text = null; + if (isset($event['te'])) { + $text = $event['te']; + } + + if ($event['ty'] == RequestProcessor::EVENT_TYPE_INITIAL_DOM) { + $event['ti'] = 0;// we make sure to force 0 time since load + } + + $this->logEvent->record($idLogHsr, $event['ti'], $event['ty'], $idSelector, $x, $y, $text); + } + } + + // we need to make sure it is only possible to track data into currently active heatmaps, and currently + // active or ended session recordings. Otherwise other users could track in advance data into any heatmap or recording + // it is still possible to track data into completed sessions because we want to make sure to include all tracking requests + // of a session + private function getValidHsrIds($idSite, $hsrIds) + { + $hsrs = $this->getCachedHsrs($idSite); + + $existingIds = array(); + foreach ($hsrs as $hsr) { + $existingIds[] = $hsr['idsitehsr']; + } + + $ids = array(); + foreach ($hsrIds as $hsrId) { + if (in_array($hsrId, $existingIds)) { + $ids[] = $hsrId; + } else { + Common::printDebug(sprintf("Notice! hsrId %d is not active anymore and will be ignored", (int) $hsrId)); + } + } + + $ids = array_unique($ids); + return $ids; + } + + private function getCachedHsrs($idSite) + { + $cache = Tracker\Cache::getCacheWebsiteAttributes($idSite); + if (!empty($cache['hsr'])) { + return $cache['hsr']; + } + return array(); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Updates/3.0.10.php b/files/plugin-HeatmapSessionRecording-5.2.4/Updates/3.0.10.php new file mode 100644 index 0000000..3a4a407 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Updates/3.0.10.php @@ -0,0 +1,51 @@ +migration = $factory; + } + + public function getMigrations(Updater $updater) + { + $migration = $this->migration->db->addIndex('log_hsr_site', 'idloghsr'); + + return array( + $migration + ); + } + + public function doUpdate(Updater $updater) + { + $updater->executeMigrations(__FILE__, $this->getMigrations($updater)); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Updates/3.0.11.php b/files/plugin-HeatmapSessionRecording-5.2.4/Updates/3.0.11.php new file mode 100644 index 0000000..f200234 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Updates/3.0.11.php @@ -0,0 +1,51 @@ +migration = $factory; + } + + public function getMigrations(Updater $updater) + { + $migration = $this->migration->db->addIndex('log_hsr_event', 'idhsrblob'); + + return array( + $migration + ); + } + + public function doUpdate(Updater $updater) + { + $updater->executeMigrations(__FILE__, $this->getMigrations($updater)); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Updates/3.0.3.php b/files/plugin-HeatmapSessionRecording-5.2.4/Updates/3.0.3.php new file mode 100644 index 0000000..c79e8f5 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Updates/3.0.3.php @@ -0,0 +1,30 @@ +install(); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Updates/4.0.0.php b/files/plugin-HeatmapSessionRecording-5.2.4/Updates/4.0.0.php new file mode 100644 index 0000000..2a66759 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Updates/4.0.0.php @@ -0,0 +1,55 @@ +migration = $factory; + } + + public function getMigrations(Updater $updater) + { + $eventTable = Common::prefixTable('log_hsr_event'); + $columns = DbHelper::getTableColumns($eventTable); + + $migrations = array(); + if (!array_key_exists('idhsrevent', $columns)) { + // if installed < 3.2.21 where column wasn't added yet + $migrations[] = $this->migration->db->addColumn('log_hsr_event', 'idhsrevent', 'BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY'); + } + + return $migrations; + } + + public function doUpdate(Updater $updater) + { + $updater->executeMigrations(__FILE__, $this->getMigrations($updater)); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Updates/5.1.0.php b/files/plugin-HeatmapSessionRecording-5.2.4/Updates/5.1.0.php new file mode 100644 index 0000000..5cd40d1 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Updates/5.1.0.php @@ -0,0 +1,54 @@ +migration = $factory; + } + + public function getMigrations(Updater $updater) + { + $siteHsrTable = Common::prefixTable('site_hsr'); + $columns = DbHelper::getTableColumns($siteHsrTable); + + $migrations = array(); + if (!array_key_exists('capture_manually', $columns)) { + $migrations[] = $this->migration->db->addColumn('site_hsr', 'capture_manually', 'TINYINT(1) UNSIGNED NOT NULL DEFAULT 0'); + } + + return $migrations; + } + + public function doUpdate(Updater $updater) + { + $updater->executeMigrations(__FILE__, $this->getMigrations($updater)); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/VisitorDetails.php b/files/plugin-HeatmapSessionRecording-5.2.4/VisitorDetails.php new file mode 100644 index 0000000..a1bf919 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/VisitorDetails.php @@ -0,0 +1,125 @@ +recordings = array_fill_keys($visitIds, null); + + $aggregator = new Aggregator(); + $recordings = $aggregator->findRecordings($visitIds); + + foreach ($recordings as $recording) { + $this->recordings[$recording['idvisit']] = $recording; + } + } + + public function extendVisitorDetails(&$visitor) + { + $visitor['sessionReplayUrl'] = null; + + $idVisit = $visitor['idVisit']; + $idSite = $visitor['idSite']; + + if (empty($idSite) || empty($idVisit) || !$this->getValidator()->canViewSessionReport($idSite)) { + return; + } + + $recording = $this->getRecoding($idVisit); + + if (!empty($recording) && !empty($recording['idsitehsr'])) { + $visitor['sessionReplayUrl'] = '?module=HeatmapSessionRecording&action=replayRecording&idSite=' . (int)$idSite . '&idLogHsr=' . (int)$recording['idloghsr'] . '&idSiteHsr=' . (int)$recording['idsitehsr']; + + $token_auth = Common::getRequestVar('token_auth', '', 'string'); + $force_api_session = Common::getRequestVar('force_api_session', 0, 'int'); + if ( + !empty($token_auth) + && ctype_xdigit($token_auth) && strlen($token_auth) > 30 && strlen($token_auth) < 81 + && !HeatmapSessionRecording::isMatomoForWordPress() + && !$force_api_session + ) { + $visitor['sessionReplayUrl'] .= '&token_auth=' . rawurlencode($token_auth); + } + } + } + + protected function getRecoding($idVisit) + { + if (empty($this->recordings) || !array_key_exists($idVisit, $this->recordings)) { + // if not prefetched + $aggregator = new Aggregator(); + return $aggregator->findRecording($idVisit); + } + + return $this->recordings[$idVisit]; + } + + /** + * @return \Piwik\Plugins\HeatmapSessionRecording\Input\Validator + */ + protected function getValidator() + { + return StaticContainer::get('Piwik\Plugins\HeatmapSessionRecording\Input\Validator'); + } + + public function renderIcons($visitorDetails) + { + if (!empty($visitorDetails['sessionReplayUrl'])) { + $title = htmlentities( + Piwik::translate('HeatmapSessionRecording_ReplayRecordedSession'), + ENT_COMPAT | ENT_HTML401, + 'UTF-8' + ); + return ''; + } + + return ''; + } + + public function renderVisitorDetails($visitorDetails) + { + if (!empty($visitorDetails['sessionReplayUrl'])) { + $title = htmlentities( + Piwik::translate('HeatmapSessionRecording_ReplayRecordedSession'), + ENT_COMPAT | ENT_HTML401, + 'UTF-8' + ); + return [ + [ + 100, + ' ' . $title . '
' + ] + ]; + } + + return []; + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Widgets/GetManageHeatmaps.php b/files/plugin-HeatmapSessionRecording-5.2.4/Widgets/GetManageHeatmaps.php new file mode 100644 index 0000000..f1215e5 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Widgets/GetManageHeatmaps.php @@ -0,0 +1,77 @@ +settings = $settings; + } + + public static function configure(WidgetConfig $config) + { + $config->setCategoryId('HeatmapSessionRecording_Heatmaps'); + $config->setSubcategoryId('HeatmapSessionRecording_ManageHeatmaps'); + $config->setName('HeatmapSessionRecording_ManageHeatmaps'); + $config->setParameters(array('showtitle' => 0)); + $config->setOrder(99); + $config->setIsNotWidgetizable(); + + $idSite = Common::getRequestVar('idSite', 0, 'int'); + if (self::getAccessValidator()->canWrite($idSite) && !self::getAccessValidator()->isHeatmapRecordingDisabled()) { + $config->enable(); + } else { + $config->disable(); + } + } + + private static function getAccessValidator() + { + return StaticContainer::get('Piwik\Plugins\HeatmapSessionRecording\Input\Validator'); + } + + public function render() + { + $idSite = Common::getRequestVar('idSite', null, 'int'); + self::getAccessValidator()->checkWritePermission($idSite); + + return sprintf( + '
', + (int)$this->settings->breakpointMobile->getValue(), + (int)$this->settings->breakpointTablet->getValue(), + Piwik::translate( + HeatmapSessionRecording::getTranslationKey('pause'), + [Piwik::translate('HeatmapSessionRecording_Heatmap')] + ), + HeatmapSessionRecording::isMatomoJsWritable() + ); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Widgets/GetManageSessionRecordings.php b/files/plugin-HeatmapSessionRecording-5.2.4/Widgets/GetManageSessionRecordings.php new file mode 100644 index 0000000..7a9908f --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Widgets/GetManageSessionRecordings.php @@ -0,0 +1,66 @@ +setCategoryId('HeatmapSessionRecording_SessionRecordings'); + $config->setSubcategoryId('HeatmapSessionRecording_ManageSessionRecordings'); + $config->setName('HeatmapSessionRecording_ManageSessionRecordings'); + $config->setParameters(array('showtitle' => 0)); + $config->setOrder(99); + $config->setIsNotWidgetizable(); + + $idSite = Common::getRequestVar('idSite', 0, 'int'); + if (self::getAccessValidator()->canWrite($idSite) && !self::getAccessValidator()->isSessionRecordingDisabled()) { + $config->enable(); + } else { + $config->disable(); + } + } + + private static function getAccessValidator() + { + return StaticContainer::get('Piwik\Plugins\HeatmapSessionRecording\Input\Validator'); + } + + public function render() + { + $idSite = Common::getRequestVar('idSite', null, 'int'); + self::getAccessValidator()->checkWritePermission($idSite); + + return sprintf( + '
', + Piwik::translate( + HeatmapSessionRecording::getTranslationKey('pause'), + [ + Piwik::translate('HeatmapSessionRecording_Heatmap') + ] + ), + HeatmapSessionRecording::isMatomoJsWritable() + ); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Widgets/GettingStartedHeatmap.php b/files/plugin-HeatmapSessionRecording-5.2.4/Widgets/GettingStartedHeatmap.php new file mode 100644 index 0000000..82b053b --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Widgets/GettingStartedHeatmap.php @@ -0,0 +1,75 @@ +setIsNotWidgetizable(); + $config->setCategoryId('HeatmapSessionRecording_Heatmaps'); + $config->setName('HeatmapSessionRecording_GettingStarted'); + $config->setOrder(5); + + $idSite = Common::getRequestVar('idSite', 0, 'int'); + + if (self::shouldEnable($idSite) && !self::getAccessValidator()->isHeatmapRecordingDisabled()) { + $config->enable(); + $hsrs = StaticContainer::get('Piwik\Plugins\HeatmapSessionRecording\Model\SiteHsrModel'); + $hsrs = $hsrs->hasSessionRecordings($idSite); + if (empty($hsrs)) { + // we only make it visible in the UI when there are no hsrs. We cannot disable/enable it + // as we otherwise would show an error message "not allowed to view widget" when suddenly + // hsrs are configured + $config->setSubcategoryId('HeatmapSessionRecording_GettingStarted'); + } + } else { + $config->disable(); + } + } + + private static function getAccessValidator() + { + return StaticContainer::get('Piwik\Plugins\HeatmapSessionRecording\Input\Validator'); + } + + private static function shouldEnable($idSite) + { + $validator = self::getAccessValidator(); + return !empty($idSite) && $validator->canViewHeatmapReport($idSite) && !$validator->canWrite($idSite); + } + + public function render() + { + $idSite = Common::getRequestVar('idSite', null, 'int'); + + self::getAccessValidator()->checkHeatmapReportViewPermission($idSite); + + if (self::shouldEnable($idSite)) { + return $this->renderTemplate('gettingStartedHeatmaps'); + } + + return ''; + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/Widgets/GettingStartedSessions.php b/files/plugin-HeatmapSessionRecording-5.2.4/Widgets/GettingStartedSessions.php new file mode 100644 index 0000000..aa15cf7 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/Widgets/GettingStartedSessions.php @@ -0,0 +1,75 @@ +setIsNotWidgetizable(); + $config->setCategoryId('HeatmapSessionRecording_SessionRecordings'); + $config->setName('HeatmapSessionRecording_GettingStarted'); + $config->setOrder(5); + + $idSite = Common::getRequestVar('idSite', 0, 'int'); + + if (self::shouldEnable($idSite) && !self::getAccessValidator()->isSessionRecordingDisabled()) { + $config->enable(); + $hsrs = StaticContainer::get('Piwik\Plugins\HeatmapSessionRecording\Model\SiteHsrModel'); + $hsrs = $hsrs->hasSessionRecordings($idSite); + if (empty($hsrs)) { + // we only make it visible in the UI when there are no hsrs. We cannot disable/enable it + // as we otherwise would show an error message "not allowed to view widget" when suddenly + // hsrs are configured + $config->setSubcategoryId('HeatmapSessionRecording_GettingStarted'); + } + } else { + $config->disable(); + } + } + + private static function getAccessValidator() + { + return StaticContainer::get('Piwik\Plugins\HeatmapSessionRecording\Input\Validator'); + } + + private static function shouldEnable($idSite) + { + $validator = self::getAccessValidator(); + // only for VIEW users, not for anonymous and not for admin users + return !empty($idSite) && $validator->canViewSessionReport($idSite) && !$validator->canWrite($idSite); + } + + public function render() + { + $idSite = Common::getRequestVar('idSite', null, 'int'); + self::getAccessValidator()->checkSessionReportViewPermission($idSite); + + if (self::shouldEnable($idSite)) { + return $this->renderTemplate('gettingStartedSessions'); + } + + return ''; + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/config/config.php b/files/plugin-HeatmapSessionRecording-5.2.4/config/config.php new file mode 100644 index 0000000..2c33444 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/config/config.php @@ -0,0 +1,21 @@ + Piwik\DI::add(array( + Piwik\DI::get('Piwik\Plugins\HeatmapSessionRecording\Diagnostic\ConfigsPhpCheck'), + )), +); diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/configs.php b/files/plugin-HeatmapSessionRecording-5.2.4/configs.php new file mode 100644 index 0000000..58cb628 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/configs.php @@ -0,0 +1,161 @@ +init(); + +$originalIdSite = $_GET['idsite']; +$trackerId = (string) $_GET['trackerid']; +$url = (string) $_GET['url']; + +$idSite = $configs->getIdSite($originalIdSite); + +if ($idSite <= 0) { + http_response_code(400); + exit; +} + +$trackerConfig = array( + 'heatmaps' => array(), // for getdom we later need to check if a screenhot url is configured array('enabled' => false, 'getdom' => []) + 'sessions' => array(), // array('enabled' => false) +); + +$cache = $configs->getCachedWebsiteAttributes($idSite); + +if (empty($cache['hsr'])) { + $configs->sendResponse($trackerConfig, $originalIdSite, $trackerId); + exit; +} + +$systemSettings = StaticContainer::get(SystemSettings::class); +$includedCountries = $systemSettings->getIncludedCountries(false); +$isIncludedCountry = true; +if (!empty($includedCountries)) { + $configs->loadTrackerEnvironment(); + $isIncludedCountry = HsrMatcher::isIncludedCountry($includedCountries); +} + +foreach ($cache['hsr'] as $hsr) { + $sessionGroup = HsrMatcher::isUserPartOfSampleGroup($hsr['record_type'], $hsr['idsitehsr']); + + if ($sessionGroup === HsrMatcher::SAMPLE_GROUP_NOT_PART) { + // user is not part of sample group + continue; + } + + $shouldForceBeIncluded = $sessionGroup === HsrMatcher::SAMPLE_GROUP_IS_PART; + // For sessions we need to record all pages, even if page rules does not match as soon as the session is being recorded. That is + // as soon as the visitor has visited at least one page that matches the page rules + + if ( + ( + $shouldForceBeIncluded || + ( + HsrMatcher::matchesAllPageRules($hsr['match_page_rules'], $url) && + HsrMatcher::checkIsNotEnded($hsr) + ) + ) && + $isIncludedCountry + ) { + // by returning the hsrid IDs here we can make sure to record all updates for this user + // also it makes request processor faster since we won't have to match the records there again + // it also prevents starting to track only partial data after a heatmap or session was created while a different + // session or heatmap is already being recorded + // it will also make the JS tracker easier since user will be able to configure manually which ids to track + // also because the sample rate is based on random number generation we cannot check sample rate and whether a user + // is in the group or not on each tracking request. We would need to send a cookie + + if ($hsr['record_type'] == \Piwik\Plugins\HeatmapSessionRecording\Dao\SiteHsrDao::RECORD_TYPE_SESSION) { + $trackerConfig['sessions'][] = array( + 'id' => $hsr['idsitehsr'], + 'sample_rate' => $hsr['sample_rate'], + 'min_time' => $hsr['min_session_time'], + 'activity' => $hsr['requires_activity'], + 'keystrokes' => $hsr['capture_keystrokes'], + ); + } elseif ($hsr['record_type'] == \Piwik\Plugins\HeatmapSessionRecording\Dao\SiteHsrDao::RECORD_TYPE_HEATMAP) { + $heatmap = array('id' => $hsr['idsitehsr'], 'getdom' => false, 'sample_rate' => $hsr['sample_rate'], 'capture_manually' => $hsr['capture_manually']); + + if ( + empty($hsr['page_treemirror']) && + empty($hsr['capture_manually']) && + \Piwik\Plugins\HeatmapSessionRecording\Tracker\HsrMatcher::doesScreenshotUrlMatch($hsr['screenshot_url'], $url) + ) { + $heatmap['getdom'] = true; + } + + $trackerConfig['heatmaps'][] = $heatmap; + } + } +} + +$configs->sendResponse($trackerConfig, $originalIdSite, $trackerId); diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/docs/index.md b/files/plugin-HeatmapSessionRecording-5.2.4/docs/index.md new file mode 100644 index 0000000..aad3729 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/docs/index.md @@ -0,0 +1,3 @@ +## Documentation + +The [Heatmap User Guide](https://matomo.org/docs/heatmaps/), [Session Recording User Guide](https://matomo.org/docs/session-recording/) and the [Heatmap & Session Recording User FAQ](https://matomo.org/faq/heatmap-session-recording/) cover how to get the most out of this plugin. The [Heatmap & Session Recording developer guides](https://developer.matomo.org/guides/heatmap-session-recording) help you customizing the tracking of your heatmaps and session recordings. \ No newline at end of file diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/javascripts/recording.js b/files/plugin-HeatmapSessionRecording-5.2.4/javascripts/recording.js new file mode 100644 index 0000000..5900cbe --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/javascripts/recording.js @@ -0,0 +1,636 @@ +/** + * Copyright (C) InnoCraft Ltd - All rights reserved. + * + * NOTICE: All information contained herein is, and remains the property of InnoCraft Ltd. + * The intellectual and technical concepts contained herein are protected by trade secret or copyright law. + * Redistribution of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from InnoCraft Ltd. + * + * You shall use this code only in accordance with the license agreement obtained from InnoCraft Ltd. + * + * @link https://www.innocraft.com/ + * @license For license details see https://www.innocraft.com/license + */ + +function HsrRecordingIframe (url) { + url = String(url); + if (url.indexOf('?') >= 0) { + url = url.substr(0, url.indexOf('?')); + } + + if (url.indexOf('#') >= 0) { + url = url.substr(0, url.indexOf('#')); + } + + if (url.indexOf('http') === -1) { + // we need to load http or at least same protocol as current piwik install + url = String(location.protocol) + '//' + url; + } + + var requireSecureProtocol = String(location.protocol).toLowerCase() === 'https:'; + + function convertUrlToSecureProtocolIfNeeded(url) + { + // otherwise we get problems re insecure content + if (requireSecureProtocol && String(url).toLowerCase().indexOf('http:') === 0) { + url = 'https' + url.substr('http'.length); + } + + return url; + } + + url = convertUrlToSecureProtocolIfNeeded(url); + + var baseUrl = url; + + this.enableDebugMode = false; + + function addRemoveSupport(window) + { + if (window && !('remove' in window.Element.prototype)) { + window.Element.prototype.remove = function() { + if (this.parentNode && this.parentNode.removeChild) { + this.parentNode.removeChild(this); + } + }; + } + } + + addRemoveSupport(window); + + this.isSupportedBrowser = function () { + if (typeof WebKitMutationObserver !== 'undefined') { + return true; + } else if (typeof MutationObserver !== 'undefined') { + return true; + } + + return false; + }; + + this.scrollTo = function (x,y) { + window.scrollTo(x,y); + }; + + this.getIframeWindow = function () { + return window; + }; + + this.findElement = function (selector) { + return $(selector); + }; + + this.makeSvg = function (width, height) { + var canvas = this.findElement('#mouseMoveCanvas'); + if (canvas.length) { + canvas.empty(); + } else { + this.appendContent('
'); + } + + this.draw = SVG('mouseMoveCanvas').size(width, height); + }; + + this.drawLine = function (x1, y1, x2, y2, color) { + if (this.draw) { + var line = this.draw.line(x1, y1, x2, y2); + line.stroke({ width: 1,color:color }); + } + }; + + this.drawCircle = function (x, y, color) { + if (this.draw) { + if (x > 4) { + x = x - 4; // because of radius of 8 we need to center it + } + if (y > 4) { + y = y - 4; // because of radius of 8 we need to center it + } + var circle = this.draw.circle(8); + circle.fill(color); + circle.move(x,y); + } + }; + + this.appendContent = function (html) { + return $('body').append(html); + }; + + this.initialMutation = function(event) { + + if (document.documentElement) { + document.documentElement.remove(); + } + + if (document && document.doctype) { + if (document.doctype && document.doctype.remove) { + document.doctype.remove(); + } else if (document.doctype) { + // fix remove is not available on IE + document.removeChild(document.doctype); + } + } + + addRemoveSupport(window); + + this.mirror = new TreeMirror(document, { + createElement: function(tagName, data) { + if (!tagName) { + return; + } + + tagName = tagName.toLowerCase().trim(); + + if (tagName === 'script') { + // prevent execution of this element! we still need to have it in the dom eg for nth-child selector etc. + var element = document.createElement('NO_SCRIPT'); + element.style.display = 'none'; + return element; + + } else if (tagName === 'form') { + var element = document.createElement('FORM'); + element.addEventListener('submit', function (event) { + event.preventDefault(); + event.stopPropagation(); + return false; + }); + return element; + + } else if (tagName === 'link' || tagName === 'img' || tagName === 'iframe') { + var element; + var isLinkHrefAttr = (tagName === 'link' && data && typeof data.attributes === 'object'); + function shouldUnresolve(href) { + var posHref = href.toLowerCase().indexOf('.scr.kaspersky-labs.com'); + if (posHref > 5 && posHref < 20) { + return true; + } + return false; + } + if (tagName === 'iframe' && data && 'src' in data.attributes && data.attributes.src.indexOf('google.com/recaptcha') !== -1) { + var element = document.createElement('NO_SCRIPT'); + element.style.display = 'none'; + return element; + } + if (isLinkHrefAttr && data && 'href' in data.attributes && data.attributes['href']) { + if (shouldUnresolve(String(data.attributes['href']))) { + data.attributes['href'] = '#not_possible_to_resolve'; + // this URL cannot be resolved and is injected dynamically + element = document.createElement('NO_LINK'); + element.style.display = 'none'; + } + } + if (isLinkHrefAttr && data && 'data-matomo-href' in data.attributes && data.attributes['data-matomo-href']) { + if (shouldUnresolve(String(data.attributes['data-matomo-href']))) { + data.attributes['href'] = '#not_possible_to_resolve'; + // this URL cannot be resolved and is injected dynamically + element = document.createElement('NO_LINK'); + element.style.display = 'none'; + } else { + data.attributes['href'] = data.attributes['data-matomo-href']; + } + } + + if (!element) { + element = document.createElement(tagName.toUpperCase()); + } + + if (element.tagName === 'IFRAME') { + element.setAttribute('sandbox', 'allow-scripts'); + } + + element.setAttribute('referrerpolicy', 'no-referrer'); + return element; + } else if (tagName === 'head') { + var element = document.createElement('HEAD'); + + // we need to add ours first, because multiple base elements may exist and their base might only + // appear after few resources are already loaded + element.appendChild(document.createElement('BASE')); + element.firstChild.href = baseUrl; + + var style = document.createElement('style'); + style.setAttribute('type', 'text/css') + style.appendChild(document.createTextNode('[data-matomo-mask] {background-image: none !important; }' + + ' img[data-matomo-mask][src^="http"] {visibility:hidden !important;opacity: 0; }' + + ' img[data-matomo-mask][src^="/"] {visibility:hidden !important;opacity: 0; }' + + ' img[data-matomo-mask][src*=".png"] {visibility:hidden !important;opacity: 0; }' + + ' img[data-matomo-mask][src*=".jpg"] {visibility:hidden !important;opacity: 0; }' + + ' img[data-matomo-mask][src*=".webp"] {visibility:hidden !important;opacity: 0; }' + + ' img[data-matomo-mask][src*=".jepg"] {visibility:hidden !important;opacity: 0; }' + + ' img[data-matomo-mask][src*=".gif"] {visibility:hidden !important;opacity: 0; }' + + ' input:not([type="submit"]):not([type="button"]):not([value=""]){ background: unset !important; }' + )); + element.appendChild(style); + element.appendChild(document.createElement('BASE')); + + var metaElement = document.createElement('META'); + metaElement.name = 'referrer'; + metaElement.content = 'no-referrer'; + element.appendChild(metaElement); + + if (typeof data === 'object' && 'childNodes' in data && data.childNodes && data.childNodes.length) { + for (var k = 0; k < data.childNodes.length; k++) { + if (k in data.childNodes && 'object' === typeof data.childNodes[k] && 'tagName' in data.childNodes[k] && data.childNodes[k].tagName && data.childNodes[k].tagName === 'BASE') { + if ('attributes' in data.childNodes[k] && data.childNodes[k].attributes && data.childNodes[k].attributes.href) { + // no need to add a BASE ourselves, we prefer to use existing base set by user + + var thisBaseUrl = data.childNodes[k].attributes.href; + + var lowerThisBaseUrl = ('' + thisBaseUrl).toLowerCase(); + if (lowerThisBaseUrl.indexOf('http') === 0 || lowerThisBaseUrl.indexOf('//') === 0) { + // absolute base URL is set, we can simply use that URL + continue; // there might be multiple base URLs so need to continue + } + + // it has to be a relative URL, trying to resolve it + if ('function' === typeof URL) { + var theUrl = new URL(thisBaseUrl, baseUrl); + if (theUrl && theUrl.href) { + baseUrl = theUrl.href; + } else if (theUrl) { + baseUrl = '' + theUrl; + } + } else { + // browser does not support URL api... won't work in IE11 or lower + if ('undefined' !== typeof console && 'undefined' !== typeof console.log){ + console.log('browser does not support URL api, cannot resolve relative base URL'); + } + } + + // make sure to use this absolute base url + data.childNodes[k].attributes.href = baseUrl; + continue; // there might be multiple base URLs so need to continue + } + } + } + } + + return element; + + } else if (tagName === 'a') { + var element = document.createElement('A'); + element.addEventListener('click', function (event) { + event.preventDefault(); + event.stopPropagation(); + return false; + }); + return element; + + } else if (['svg', 'path', 'g', 'polygon', 'polyline', 'rect', 'text', 'circle', 'line'].indexOf(tagName) !== -1) { + return document.createElementNS('http://www.w3.org/2000/svg', tagName) + } else if (tagName === 'meta') { + if (data && typeof data.attributes === 'object') { + if ('http-equiv' in data.attributes && data.attributes['http-equiv']) { + var httpEquiv = String(data.attributes['http-equiv']).toLowerCase(); + + if (httpEquiv === 'content-security-policy' || httpEquiv === 'refresh') { + return document.createElement('NO_META'); + } + } + if ('name' in data.attributes && data.attributes['name']) { + var metaName = String(data.attributes['name']).toLowerCase(); + if (metaName === 'csrf-token') { + return document.createElement('NO_META'); + } + if (metaName === 'referrer') { + // we want to apply our own policy + return document.createElement('NO_META'); + } + } + } + } + }, + setAttribute: function(node, name, value) { + if (!name) { + return node; + } + + var nameLower = String(name).trim().toLowerCase(); + + if (nameLower === 'src' && value && (String(value).indexOf('/piwik.js') >= 0 || String(value).indexOf('/matomo.js') >= 0)) { + // we do not want to set piwik.js + return node; + } + + if (nameLower === 'srcdoc') { + // we ignore srcdoc + return node; + } + + if (nameLower === 'referrerpolicy') { + // we always set our value + node.setAttribute(nameLower, 'no-referrer'); + return node; + } + + if (nameLower === 'src' && value && String(value).indexOf('/HeatmapSessionRecording/') > 0) { + // we do not want to set configs.php etc + return node; + } + + if (value + && (String(value).toLowerCase().replace(/\x09|\x0a|\x0d/g, '').indexOf('javascript') >= 0 + || String(value).toLowerCase().replace(/\x09|\x0a|\x0d/g, '').indexOf('ecmascript') >= 0 + || String(value).toLowerCase().replace(/\x09|\x0a|\x0d/g, '').indexOf('vbscript') >= 0 + || String(value).toLowerCase().replace(/\x09|\x0a|\x0d/g, '').indexOf('jscript') >= 0)) { + // we do not want to set any javascript URL, eg href and src and attribute + return node; + } + if (value && String(value).toLowerCase().indexOf('xmlhttprequest') >= 0) { + // prevent simple input of xmlhttprequest + return node; + } + if (value && /fetch\s*\(/.test(String(value).toLowerCase())) { + // prevent simple input of fetch( + return node; + } + + var blockedAttributes = ['onchange', 'onload', 'onshow', 'onhashchange', 'onstorage', 'onchecking', 'ondownloading', 'onnoupdate', 'onupdateready', 'onabort', 'oncopy','ondrop','onwheel', 'onpaste', 'oncut', 'onbeforeunload', 'onreset','onsubmit', 'onunload', 'onerror', 'onclose','onopen','onpagehide','onpageshow','onpopstate','onmessage', 'onclick', 'ondblclick', 'oncontextmenu', 'onauxclick', 'onfocus', 'onfocusin', 'onfocusout', 'onblur', 'onselect', 'onplay', 'onpause', 'onended', 'onsuspend', 'onwaiting', 'onprogress', 'ontimeout', 'onchange', 'ontimeupdate', 'onstalled', 'onseeking', 'onplaying', 'onloadeddata', 'onended', 'onemptied', 'ondurationchange', 'oncanplay', 'oncomplete', 'onaudioprocess'] + // we block any on... per regex but adding few other checks just in case the regex fails + if (/^on([a-zA-Z])+$/.test(nameLower) + || blockedAttributes.indexOf(nameLower) > -1 + || nameLower.indexOf('onmouse') === 0 + || nameLower.indexOf('onkey') === 0 + || nameLower.indexOf('onanimation') === 0 + || nameLower.indexOf('ondrag') === 0 + || nameLower.indexOf('onload') === 0 + || nameLower.indexOf('ontransition') === 0 + || nameLower.indexOf('oncomposition') === 0 + || nameLower.indexOf('ontouch') === 0) { + // do not execute any onload method or when we set form element values + return node; + } + + if (node.tagName === 'LINK') { + if (nameLower === 'crossorigin') { + // cross origin relevant for images only, not for scripts as we rename them anyway + return node + } + + if (nameLower === 'integrity') { + // hash of a file should be ignored as file fetched later might have different hash etc + return node + } + + if (nameLower === 'referrerpolicy') { + // do not overwrite our policy + return node + } + + if (requireSecureProtocol) { + if (nameLower === 'href' && value && String(value).indexOf('http:') === 0) { + value = convertUrlToSecureProtocolIfNeeded(value); + node.setAttribute(name, value); + return node; + } + } + } + + if (node.tagName === 'IMG') { + var isHeatmap = window.location.search.indexOf('idLogHsr') === -1; + //To support images rendered using lazy load, we check if allowed dataset attributes are set and no src attributes are present replace it with available data attributes + if (isHeatmap && (typeof node.attributes.src === "undefined" || node.getAttribute('src') === '') && Object.keys(node.dataset).length) { + var allowedDatasetsToReplaceImageSrc = ['src', 'original', 'lazy']; + var newSrcValue = ''; + for (var i = 0; i < allowedDatasetsToReplaceImageSrc.length; i++) { + if (typeof node.dataset[allowedDatasetsToReplaceImageSrc[i]] !== "undefined" && node.dataset[allowedDatasetsToReplaceImageSrc[i]]) { + newSrcValue = node.dataset[allowedDatasetsToReplaceImageSrc[i]]; + break; + } + } + + //srcset is also used to lazy load with responsive images + //the value of srcset is "{imagePath} screenSize1,{imagePath} screenSize2" for responsive lazy load + //Eg data-scrset="images/400.jpg 400w, images/400.webp 400w, images/600.jpg 600w" + //we pick the last size and to determine it by checking the last character has w + // if we cannot find last character as 'w' we just set the src else we replace with the last size determined + if (newSrcValue === '' && typeof node.dataset.srcset !== "undefined" && node.dataset.srcset) { + var srcSetValue = node.dataset.srcset; + var srcSetLength = srcSetValue.length; + if (srcSetValue[srcSetLength-1] === 'w') { + var splitSrcSetSizes = srcSetValue.split('w,'); + var lastSizeValue = splitSrcSetSizes[splitSrcSetSizes.length-1]; + newSrcValue = lastSizeValue.replace( / \d+w/g,'').trim(); + } else { + newSrcValue = srcSetValue; + } + } + if (newSrcValue) { + node.setAttribute('src', convertUrlToSecureProtocolIfNeeded(newSrcValue)); + } + } + + if (requireSecureProtocol) { + if (nameLower === 'src' && value && String(value).indexOf('http:') === 0) { + value = convertUrlToSecureProtocolIfNeeded(value); + node.setAttribute(name, value); + return node; + } + } + + if (nameLower === 'referrerpolicy') { + // do not overwrite our policy + return node + } + } + + if (node.tagName === 'FORM') { + if (requireSecureProtocol) { + if (nameLower === 'action' && value && String(value).indexOf('http:') === 0) { + value = convertUrlToSecureProtocolIfNeeded(value); + node.setAttribute(name, value); + return node; + } + } + } + + if (node.tagName === 'IFRAME') { + var youtubeRegex = /^((?:https?:)\/\/)((?:www|m)\.)?((?:youtube\.com|youtube-nocookie\.com|youtu\.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)([a-zA-Z_=&]*)?$/; + if (node.src && youtubeRegex.test(node.src.toLowerCase())) { + node.setAttribute('sandbox', 'allow-scripts allow-same-origin'); + } else { + node.setAttribute('sandbox', 'allow-scripts'); + } + + if (requireSecureProtocol) { + if (nameLower === 'src' && value && String(value).indexOf('http:') === 0) { + value = convertUrlToSecureProtocolIfNeeded(value); + node.setAttribute(name, value); + return node; + } + } + + if (nameLower === 'src' && value) { + if (youtubeRegex.test(String(value).toLowerCase())) { + node.setAttribute('sandbox', 'allow-scripts allow-same-origin'); + } + } + + if (nameLower === 'referrerpolicy') { + // do not overwrite our policy + return node + } + if (nameLower === 'sandbox') { + // do not overwrite our policy + return node + } + } + + if (node.tagName === 'BASE') { + if (requireSecureProtocol) { + if (nameLower === 'href' && value && String(value).indexOf('http:') === 0) { + value = convertUrlToSecureProtocolIfNeeded(value); + node.setAttribute(name, value); + return node; + } + } + } + } + }); + + if (event) { + this.mirror.initialize(event.rootId, event.children); + + this.addClass('html', 'piwikHsr'); + this.addClass('html', 'matomoHsr'); + } + }; + + this.addClass = function (selector, className) { + $(selector).addClass(className); + }; + + this.addWorkaroundForSharepointHeatmaps = function () { + // only needed for heatmaps... instead of having a scrollable element we show all content + var doc = document.getElementById('s4-workspace'); + if (doc && doc.tagName === 'DIV' && doc.className && String(doc.className).indexOf('ms-core-overlay') >= 0) { + doc.style += ';height: auto !important;width: auto !important'; + } + }; + + this.applyMutation = function (event) { + if (event) { + this.mirror.applyChanged(event.rem || [], event.adOrMo || [], event.att || [], event.text || []); + } + }; + this.trim = function(text) + { + if (text && String(text) === text) { + return text.replace(/^\s+|\s+$/g, ''); + } + + return text; + }; + + this.parseExcludedElementSelectors = function (excludedElements) { + if (!excludedElements) { + return []; + } + + excludedElements = String(excludedElements); + excludedElements = this.trim(excludedElements); + + if (!excludedElements) { + return []; + } + + excludedElements = excludedElements.split(','); + + if (!excludedElements || !excludedElements.length) { + return []; + } + + var selectors = []; + for (var i = 0; i < excludedElements.length; i++) { + var selector = this.trim(excludedElements[i]); + selectors.push(selector); + } + return selectors; + }; + + this.excludeElements = function (excludedElements) { + excludedElements = this.parseExcludedElementSelectors(excludedElements); + if (!excludedElements || !excludedElements.length) { + return; + } + + var self = this; + + var style = (function() { + var style = document.createElement('style'); + style.appendChild(document.createTextNode('')); + document.head.appendChild(style); + + return style; + })(); + + for (var i = 0; i < excludedElements.length; i++) { + var selector = this.decodeHTMLEntities(excludedElements[i]); // decodeHTMLEntities fixes cases like p>a[href="test.html"] or a[href="test.html"] as the quotes are escaped and results in error + if (selector && style && style.sheet) { + if('insertRule' in style.sheet) { + style.sheet.insertRule(selector + "{ visibility: hidden; }", i); + } else if('addRule' in sheet) { + style.sheet.addRule(selector, 'visibility: hidden; ', i); + } + + } + } + }; + + this.decodeHTMLEntities = function (text) { + var textArea = document.createElement('textarea'); + textArea.innerHTML = text; + return textArea.value; + }; + + this.getCoordinatesInFrame = function (selector, offsetx, offsety, offsetAccuracy, ignoreHiddenElement) { + var $node = $(selector); + + if (!$node.length) { + if (this.enableDebugMode) { + if ('undefined' !== typeof console && 'undefined' !== typeof console.log){ + console.log(selector, 'selector not found'); + } + } + return; + } + + var width = $node.outerWidth(); + var height = $node.outerHeight(); + + if (ignoreHiddenElement && ignoreHiddenElement === true && width === 0 || height === 0 || !$node.is(':visible')) { + // not visible + return; + } + + var width = width / offsetAccuracy; + var height = height / offsetAccuracy; + var coordinates = $node.offset(); + + var dataPoint = { + x: parseInt(coordinates.left, 10) + parseInt(offsetx * width, 10), + y: parseInt(coordinates.top, 10) + parseInt(offsety * height, 10), + } + + return dataPoint; + }; + + this.getScrollTop = function () { + return $(window).scrollTop(); + }; + + this.getScrollLeft = function () { + return $(window).scrollLeft(); + }; + + this.getIframeHeight = function () { + var documentHeight = Math.max(document.body ? document.body.offsetHeight : 0, document.body ? document.body.scrollHeight : 0, document.documentElement ? document.documentElement.offsetHeight : 0, document.documentElement ? document.documentElement.clientHeight : 0, document.documentElement ? document.documentElement.scrollHeight : 0); + return documentHeight; + }; + + this.getIframeWidth = function () { + var documentHeight = Math.max(document.body ? document.body.offsetWidth : 0, document.body ? document.body.scrollWidth : 0, document.documentElement ? document.documentElement.offsetWidth : 0, document.documentElement ? document.documentElement.clientWidth : 0, document.documentElement ? document.documentElement.scrollWidth : 0); + return documentHeight; + }; + +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/javascripts/rowaction.js b/files/plugin-HeatmapSessionRecording-5.2.4/javascripts/rowaction.js new file mode 100644 index 0000000..a9149f0 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/javascripts/rowaction.js @@ -0,0 +1,211 @@ +/*! + * Copyright (C) InnoCraft Ltd - All rights reserved. + * + * NOTICE: All information contained herein is, and remains the property of InnoCraft Ltd. + * The intellectual and technical concepts contained herein are protected by trade secret or copyright law. + * Redistribution of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from InnoCraft Ltd. + * + * You shall use this code only in accordance with the license agreement obtained from InnoCraft Ltd. + * + * @link https://www.innocraft.com/ + * @license For license details see https://www.innocraft.com/license + */ + +/** + * This file registers the Overlay row action on the pages report. + */ + +(function () { + + var actionNameProfile = 'HsrVisitorProfile'; + var actionNamePlay = 'HsrPlayRecording'; + var actionNameDelete = 'HsrDeleteRecording'; + + function getMetadataFromRow(tr) + { + if (tr) { + return JSON.parse($(tr).attr('data-row-metadata') || '{}'); + } + } + + function DataTable_RowActions_HsrVisitorProfile(dataTable) { + this.dataTable = dataTable; + this.actionName = actionNameProfile; + } + + DataTable_RowActions_HsrVisitorProfile.prototype = new DataTable_RowAction(); + + DataTable_RowActions_HsrVisitorProfile.prototype.trigger = function (tr, e, subTableLabel) { + var metadata = getMetadataFromRow(tr); + broadcast.propagateNewPopoverParameter('visitorProfile', metadata.idvisitor); + }; + + function DataTable_RowActions_HsrPlayRecording(dataTable) { + this.dataTable = dataTable; + this.actionName = actionNamePlay; + } + + DataTable_RowActions_HsrPlayRecording.prototype = new DataTable_RowAction(); + + DataTable_RowActions_HsrPlayRecording.prototype.trigger = function (tr, e, subTableLabel) { + var metadata = getMetadataFromRow(tr); + var idsite = parseInt(this.dataTable.param.idSite, 10); + var idLogHsr = parseInt(metadata.idloghsr, 10); + var idSiteHsr = parseInt(metadata.idsitehsr, 10); + window.open('?module=HeatmapSessionRecording&action=replayRecording&idSite=' + idsite + '&idLogHsr=' + idLogHsr + '&idSiteHsr=' + idSiteHsr); + }; + + function DataTable_RowActions_HsrDeleteRecording(dataTable) { + this.dataTable = dataTable; + this.actionName = actionNameDelete; + } + + DataTable_RowActions_HsrDeleteRecording.prototype = new DataTable_RowAction(); + + DataTable_RowActions_HsrDeleteRecording.prototype.trigger = function (tr, e, subTableLabel) { + var metadata = getMetadataFromRow(tr); + + var idloghsr = metadata.idloghsr; + var idsitehsr = metadata.idsitehsr; + + var params = { + module: 'API', + method: 'HeatmapSessionRecording.deleteRecordedPageview', + idLogHsr: idloghsr, + idSiteHsr: idsitehsr, + format: 'json' + }; + + if (metadata.idvisit) { + params.method = 'HeatmapSessionRecording.deleteRecordedSession'; + params.idVisit = metadata.idvisit; + } + + var ajaxRequest = new ajaxHelper(); + ajaxRequest.withTokenInUrl(); + ajaxRequest.addParams(params, 'get'); + ajaxRequest.setCallback(function (id) { }); + ajaxRequest.send(); + + // we directly remove the node for better UX and assume the removal works + $(tr).remove(); + }; + + DataTable_RowActions_Registry.register({ + name: actionNameProfile, + + dataTableIcon: 'icon-visitor-profile', + + order: 53, + + dataTableIconTooltip: [ + _pk_translate('Live_ViewVisitorProfile'), + '' + ], + + isAvailableOnReport: function (dataTableParams, undefined) { + return dataTableParams && dataTableParams.module === 'HeatmapSessionRecording' && dataTableParams.action === 'getRecordedSessions' && piwik.visitorProfileEnabled; + }, + + isAvailableOnRow: function (dataTableParams, tr) { + return true; + }, + + createInstance: function (dataTable, param) { + if (dataTable !== null && typeof dataTable.hsrVisitorProfileInstance !== 'undefined') { + return dataTable.hsrVisitorProfileInstance; + } + + var instance = new DataTable_RowActions_HsrVisitorProfile(dataTable); + if (dataTable !== null) { + dataTable.hsrVisitorProfileInstance = instance; + } + + return instance; + } + }); + + + DataTable_RowActions_Registry.register({ + name: actionNamePlay, + + dataTableIcon: 'icon-play', + + order: 51, + + dataTableIconTooltip: [ + _pk_translate('HeatmapSessionRecording_PlayRecordedSession'), + '' + ], + + isAvailableOnReport: function (dataTableParams, undefined) { + return dataTableParams && dataTableParams.module === 'HeatmapSessionRecording' && dataTableParams.action === 'getRecordedSessions'; + }, + + isAvailableOnRow: function (dataTableParams, tr) { + return true; + }, + + createInstance: function (dataTable, param) { + if (dataTable !== null && typeof dataTable.hsrPlayRecordingInstance !== 'undefined') { + return dataTable.hsrPlayRecordingInstance; + } + + var instance = new DataTable_RowActions_HsrPlayRecording(dataTable); + if (dataTable !== null) { + dataTable.hsrPlayRecordingInstance = instance; + } + + return instance; + } + }); + + + DataTable_RowActions_Registry.register({ + name: actionNameDelete, + + dataTableIcon: 'icon-delete', + + order: 55, + + dataTableIconTooltip: [ + _pk_translate('HeatmapSessionRecording_DeleteRecordedSession'), + '' + ], + + isAvailableOnReport: function (dataTableParams, undefined) { + if (!dataTableParams || !dataTableParams.writeAccess) { + return false; + } + + return dataTableParams && dataTableParams.module === 'HeatmapSessionRecording' && dataTableParams.action === 'getRecordedSessions'; + }, + + isAvailableOnRow: function (dataTableParams, tr) { + var metadata = getMetadataFromRow(tr); + + if (metadata.idvisit) { + this.dataTableIconTooltip[0] = _pk_translate('HeatmapSessionRecording_DeleteRecordedSession'); + } else { + this.dataTableIconTooltip[0] = _pk_translate('HeatmapSessionRecording_DeleteRecordedPageview'); + } + + return true; + }, + + createInstance: function (dataTable, param) { + if (dataTable !== null && typeof dataTable.hsrDeleteRecordingInstance !== 'undefined') { + return dataTable.hsrDeleteRecordingInstance; + } + + var instance = new DataTable_RowActions_HsrDeleteRecording(dataTable); + if (dataTable !== null) { + dataTable.hsrDeleteRecordingInstance = instance; + } + + return instance; + } + }); + +})(); diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/bg.json b/files/plugin-HeatmapSessionRecording-5.2.4/lang/bg.json new file mode 100644 index 0000000..71d6990 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/lang/bg.json @@ -0,0 +1,7 @@ +{ + "HeatmapSessionRecording": { + "Heatmap": "Топлинна карта", + "Heatmaps": "Топлинни карти", + "Recordings": "Записи" + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/cs.json b/files/plugin-HeatmapSessionRecording-5.2.4/lang/cs.json new file mode 100644 index 0000000..5d9e0c0 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/lang/cs.json @@ -0,0 +1,190 @@ +{ + "HeatmapSessionRecording": { + "ActivityClick": "Kliknutí", + "ActivityFormChange": "Změna formuláře", + "ActivityFormText": "Změna textu formuláře", + "ActivityFormValue": "Změna hodnoty formuláře", + "ActivityInitialDom": "Počáteční stránka", + "ActivityMove": "Pohyb", + "ActivityPageChange": "Změnit na stránce", + "ActivityResize": "Změna velikosti", + "ActivityScroll": "Posouování", + "ActivityScrollElement": "Prvek posouvání", + "AdvancedOptions": "Pokročilé možnosti", + "AutoPlayNextPageview": "Klepnutím na %1$s se automaticky přehraje další zobrazení stránky (zástupce %2$s)", + "AvgAboveFoldDescription": "Průměrně návštěvníci vidí obsah nad tímto řádkem bez posouvání", + "AvgAboveFoldTitle": "Prům. nad hranou %spx", + "BreakpointGeneralHelp": "Typ zařízení je automaticky detekován, když si návštěvník prohlíží váš web. Někdy však nemusí být možné zjistit typ zařízení. Pokud vaše webové stránky reagují a vaše rozvržení se v určitém okamžiku rozpadlo, umístíme uživatele stolních počítačů s malým rozlišením do tohoto typu zařízení pro přesnější heatmapy. Do tohoto typu zařízení bude vložena jakákoli šířka menší než tato hodnota. Pokud váš web nereaguje, nastavte jej na nulu.", + "BreakpointGeneralHelpManage": "Uživatel s přístupem Super User může změnit výchozí hodnoty pro tyto body zlomu v Administration => General Settings.", + "BreakpointX": "Bod zlomu %s", + "CaptureKeystrokes": "Zachyťte stisknutí kláves", + "CaptureKeystrokesHelp": "Pokud je povoleno, zaznamená se jakýkoli text, který je zadán do textových polí formuláře. Během záznamu textu je jakýkoli znak, který uživatel zadá, nahrazen hvězdičkou ('*'). Určitá pole, která mají být zaznamenána jako prostý text, můžete povolit zadáním atributu 'data-matomo-unmask' do pole formuláře. Pole hesel a mnoho dalších polí, která by případně mohla obsahovat osobní údaje (adresa, e-mail, informace o kreditní kartě, uživatelské jméno, telefonní číslo, …), však budou automaticky maskována, pokud nás jako takové detekuje (%1$slearn%2$s) . Upozorňujeme, že když tuto funkci povolíte, můžete ukládat osobní údaje, které mohou mít vliv na GDPR.", + "ChangeReplaySpeed": "Změna rychlosti přehrávání (zástupce %s)", + "ClickToSkipPauses": "Kliknutím na %1$s přeskočíte pauzy (zástupce %2$s)", + "ColumnActionsDocumentation": "Přehrajte nahranou relaci nebo natrvalo odeberte záznam.", + "ColumnBrowserDocumentation": "Prohlížeč, který návštěvník používal při záznamu této relace.", + "ColumnDeviceDocumentation": "Zařízení, které návštěvník používal při záznamu této relace.", + "ColumnLabelRecordedSessions": "Vstupní URL → Výstupní URL", + "ColumnLocationDocumentation": "Lokalita návštěvníka, kdy byla tato relace zaznamenána.", + "ColumnOperatingSystem": "OS", + "ColumnOperatingSystemDocumentation": "Operační systém, který návštěvník používal při záznamu této relace.", + "ColumnPageviewsDocumentation": "Počet zobrazení stránek zaznamenaných během této relace.", + "ColumnResolutionDocumentation": "Počet pixelů viditelného výřezu v okně prohlížeče, když uživatel otevřel tuto stránku.", + "ColumnTime": "Čas", + "ColumnTimeDocumentation": "Čas zahájení nahrávání v časovém pásmu webové stránky.", + "ColumnTimeOnPageDocumentation": "Kolik času strávil návštěvník na této stránce během záznamu relace", + "ColumnTimeOnSiteDocumentation": "Kolik času strávil návštěvník celkem během záznamu relace", + "ConfigsInternetDisabled": "Nemohli jsme zkontrolovat, zda je '%s' přístupné, protože je zakázáno připojení k internetu.", + "ConfigsPhpErrorResult": "Výsledkem je, že sledování heatmap a záznamů relací nemusí fungovat. Možná budete muset změnit konfiguraci webového serveru, abyste umožnili přístup k tomuto souboru přes internet nebo intranet.", + "ConfigsPhpManualCheck": "Otevřete prosím adresu URL ručně v prohlížeči a zjistěte, zda odpověď obsahuje 'Piwik.HeatmapSessionRecording'. Pokud tomu tak není, možná budete muset upravit konfiguraci serveru, protože tento soubor musí být přístupný prostřednictvím prohlížeče z Internetu nebo intranetu.", + "ConfigsPhpNotAccessible": "Zdá se, že adresa URL '%s' není přístupná z internetu nebo intranetu.", + "ConfigsPhpSelfSignedError": "Vyžádání '%s' způsobilo chybu SSL. Možná používáte certifikát s vlastním podpisem?", + "ConfigsPhpSuccess": "'%s' je přístupný", + "ConfigsPhpUnknown": "Nemohli jsme zkontrolovat, zda je '%s' přístupný přes internet nebo intranet.", + "ConfigsPhpUnknownError": "Vyžádání '%1$s' způsobilo chybu: %2$s.", + "CreateNewHeatmap": "Vytvořte novou heatmapu", + "CreateNewSessionRecording": "Vytvořte nový záznam relací", + "CreationDate": "Datum vytvoření", + "DeleteHeatmapConfirm": "Opravdu chcete tuto heatmapu odstranit? Zprávy dříve generované heatmapy již nebudou k dispozici v uživatelském rozhraní pro vykazování.", + "DeleteHeatmapScreenshotConfirm": "Opravdu chcete odstranit tento snímek obrazovky? Může chvíli trvat, než bude snímek obrazovky znovu pořízen.", + "DeleteRecordedPageview": "Smazat toto zaznamenané zobrazení stránky (není vyžadováno potvrzení)", + "DeleteRecordedSession": "Smazat tuto zaznamenanou relaci (není vyžadováno potvrzení)", + "DeleteScreenshot": "Odstranit snímek obrazovky", + "DeleteSessionRecordingConfirm": "Opravdu chcete smazat tento záznam relace? Záznamy, které byly dříve vytvořeny, již nebudou v uživatelském rozhraní hlášení k dispozici.", + "DeleteX": "Vymazat %s", + "EditHeatmapX": "Upravit heatmapu %s", + "EditSessionRecordingX": "Upravit záznam relací %s", + "EditX": "Upravit %s", + "EndHeatmapConfirm": "Opravdu chcete přestat zaznamenávat aktivity pro tuto heatmapu?", + "EndSessionRecordingConfirm": "Opravdu chcete zastavit nahrávání nových relací?", + "ErrorArrayMissingKey": "Chybí klíč pole \"%1$s\" v \"%2$s\" na pozici \"%3$s\".", + "ErrorArrayMissingValue": "Chybí hodnota klíče pole \"%1$s\" v \"%2$s\" na pozici \"%3$s\".", + "ErrorHeatmapDoesNotExist": "Požadovaná heatmapa neexistuje", + "ErrorHeatmapNameDuplicate": "Název heatmapy je již používán jinou heatmapou.", + "ErrorInnerIsNotAnArray": "Každé \"%1$s\" v \"%2$s\" musí být pole.", + "ErrorInvalidRegExp": "Regulární výraz \"%1$s\" nemá platný formát.", + "ErrorNotAnArray": "\"%1$s\" musí být pole.", + "ErrorPageRuleRequired": "Musí být nastaveno alespoň jedno pravidlo stránky.", + "ErrorSessionRecordingDoesNotExist": "Požadovaný záznam relace neexistuje", + "ErrorXContainsWhitespace": "\"%1$s\" nesmí obsahovat žádné prázdné místo.", + "ErrorXNotANumber": "\"%1$s\" musí být číslo.", + "ErrorXNotProvided": "Zadejte prosím hodnotu pro \"%1$s\".", + "ErrorXNotWhitelisted": "Hodnota pro \"%1$s\" není povolena, použijte jednu z: %2$s.", + "ErrorXTooHigh": "\"%1$s\"je příliš vysoká, maximální povolená hodnota je %2$s.", + "ErrorXTooLong": "\"%1$s\" je příliš dlouhý, povoleno je maximálně %2$s znaků.", + "ErrorXTooLow": "\"%1$s\"je příliš nízká, minimální povolená hodnota je %2$s.", + "ExcludedElements": "Vyloučené prvky", + "ExcludedElementsHelp": "Volitelně můžete definovat selektory CSS, abyste vyloučili určité prvky, které by neměly být zobrazeny v náhledu heatmapy. Například vyskakovací okno, které se zobrazí při otevření cílové stránky. Můžete oddělit více selektorů čárkou.", + "FieldIncludedTargetsHelp": "Cíle vám umožňují definovat, na kterých stránkách by se měly aktivity uživatelů zaznamenávat. Můžete definovat jednu nebo více podmínek. Můžete například definovat zaznamenávání aktivit, kdykoli se adresa URL nebo cesta rovná určité hodnotě a pouze v případě, že je v adrese URL přítomen určitý parametr adresy URL. Činnosti budou zaznamenány, pouze pokud jsou splněny všechny podmínky, nikoli pokud je splněna pouze jedna z nich. Všechny podmínky budou vyhodnoceny bez ohledu na velikost písmen. Pokud vyberete možnost „rovná se jednoduchému“, protokol URL, parametry vyhledávání a koncové lomítko budou ignorovány.", + "FieldIncludedTargetsHelpSessions": "Cíle vám umožňují nakonfigurovat spuštění nahrávání relace, jakmile návštěvník dosáhne určité stránky. To vám umožní například zaznamenávat pouze relace, které procházejí procesem odhlášení. Můžete definovat jednu nebo více podmínek. Můžete například definovat, že chcete relaci zaznamenat, pouze pokud se adresa URL nebo cesta rovná určité hodnotě a pouze v případě, že je v adrese URL přítomen určitý parametr adresy URL. Relace se zaznamenají pouze tehdy, jsou-li při zobrazení stránky splněny všechny podmínky, nikoli pokud je splněna pouze jedna z nich. Všechny podmínky budou vyhodnoceny bez ohledu na velikost písmen. Pokud vyberete možnost „rovná se jednoduchému“, protokol URL, parametry vyhledávání a koncové lomítko budou ignorovány.", + "FieldNamePlaceholder": "např. 'Stránka registrace'", + "FilesystemDirectory": "adresář", + "Filter": "Filtr", + "GettingStarted": "Začínáme", + "Heatmap": "Heatmapa", + "HeatmapCreated": "Heatmapa byla úspěšně vytvořena.", + "HeatmapNameHelp": "Definuje název, pod kterým bude k dispozici zpráva pro tuto heatmapu.", + "HeatmapSampleLimit": "Počet zobrazení", + "HeatmapSampleLimitHelp": "Definuje celkový počet zobrazení stránky, které chcete celkem zaznamenat.", + "HeatmapSampleRateHelp": "Také známý jako 'provoz'. Pokud vyberete 100%%, budou zaznamenáni všichni návštěvníci, kteří navštíví vybranou cílovou stránku. Pokud vyberete například 10%%, bude zaznamenán pouze každý 10. návštěvník. Čím nižší procento vyberete, tím déle bude trvat dosažení vybraného limitu vzorku.", + "HeatmapUpdated": "Heatmapa byla úspěšně aktualizována.", + "HeatmapUsageBenefits": "Heatmapy umožňují zaznamenávat všechna kliknutí, pohyby myší a procházení aktivit vašich návštěvníků na určité stránce. To vám pomůže zjistit, kde si uživatelé myslí, že na něco lze kliknout, ale není to, zda existují části stránky, které jsou jen stěží prohlíženy nebo s nimi interagovány, co návštěvníci hledají, kolik stránky je viditelné, když si uživatelé prohlíží vaši stránku a další.", + "HeatmapWidth": "Šířka heatmapy", + "HeatmapX": "Heatmapa %s", + "HeatmapXRecordedSamplesSince": "%1$s vzorků bylo nahráno od %2$s.", + "Heatmaps": "Heatmapy", + "Manage": "Spravovat", + "ManageHeatmapSubcategoryHelp": "Heatmaps vám umožní zaznamenat všechna kliknutí, pohyby myší a posouvání vašich návštěvníků na konkrétní stránku. Tato část umožňuje vytvářet a spravovat sledování Heatmap pro váš web.", + "ManageHeatmaps": "Spravujte heatmapy", + "ManageSessionRecordingSubcategoryHelp": "Session recordings vám umožňují zaznamenávat všechny aktivity skutečného návštěvníka během jeho relace (návštěvy), jako jsou kliknutí, pohyby myši, posouvání, změny velikosti okna, změny stránky a interakce formulářů. Tato část umožňuje vytvářet a spravovat záznamy relací pro vaše stránky.", + "ManageSessionRecordings": "Spravujte záznamy relací", + "MinSessionTime": "Min. doba relace", + "MinSessionTimeHelp": "Relace bude zaznamenána, pouze pokud návštěvník strávil alespoň určitou dobu na stránce.", + "NHeatmaps": "%s heatmapy", + "NSessionRecordings": "%s záznamy relací", + "NoHeatmapSamplesRecordedYet": "Pro tuto heatmapu nebylo dosud zaznamenáno žádné zobrazení stránky. Pokud se do této chvíle mají nahrávat, je možné, že cíle stránky nakonfigurované pro tuto heatmapu neodpovídají žádné stránce na vašem webu. Doporučuje se také zkontrolovat „Kontrola systému“ v části „Administrace“ (pouze Super Users), abyste zjistili, zda je váš systém nakonfigurován na automatické zaznamenávání relací.", + "NoHeatmapScreenshotRecordedYet": "Dosud byly zaznamenány vzorky %1$s. Zatím však nebyl pořízen žádný screenshot. Pokud pro tuto heatmapu existuje \"%2$s\", může chvíli trvat, než bude snímek obrazovky dostupný, protože uživatel musí nejprve otevřít tuto adresu URL obrazovky. V závislosti na vzorkovací frekvenci to může chvíli trvat.", + "NoHeatmapsConfiguredInfo": "Momentálně neexistují žádné aktivní heatmapy. Chcete-li zobrazit heatmapy, požádejte uživatele s alespoň přístupem správce o vytvoření nové heatmapy.", + "NoHeatmapsFound": "Heatmapy nenalezeny", + "NoSessionRecordedYet": "Zatím nebyla zaznamenána žádná relace. Pokud se nyní mají zaznamenat relace, možná nakonfigurovaná vstupní stránka neodpovídá žádné stránce na vašem webu. Doporučuje se také zkontrolovat „Kontrola systému“ v části „Administrace“ (pouze Super Users), abyste zjistili, zda je váš systém nakonfigurován na automatické zaznamenávání relací.", + "NoSessionRecordingsConfiguredInfo": "Momentálně neexistují žádné aktivní záznamy relací. Chcete-li zaznamenat nové relace, požádejte uživatele s alespoň přístupem správce o vytvoření nové nahrávky.", + "NoSessionRecordingsFound": "Záznamy relací nenalezeny", + "NotSupportedBrowser": "Tento prohlížeč není podporován, použijte novější verzi nebo zkuste jiný prohlížeč.", + "OnePageview": "1 zobrazení stránky", + "PageRule": "Pravidlo stránky", + "PageviewXofY": "Stránka %1$s z %2$s", + "PageviewsInVisit": "Zaznamenané zobrazení stránek v této relaci", + "PersonalInformationNote": "Upozorňujeme, že %1$s může zaznamenávat osobní nebo citlivé informace. Pokud si přejete skrýt určitý obsah na svém webu nebo v aplikaci, můžete tento obsah označit přidáním atributu %2$sdata-matomo-mask%3$s k HTML značce. %4$sDalší informace%5$s", + "PlayRecordedSession": "Přehrajte tuto zaznamenanou relaci", + "PlayerDurationXofY": "%1$s z %2$s", + "PlayerForwardFast": "Přeskočit %1$s sekund (zkratka %2$s)", + "PlayerPageViewNext": "Přehrát další zobrazení tohoto návštěvníka (%1$s) (zkratka %2$s)", + "PlayerPageViewPrevious": "Přehrát předchozí zobrazení tohoto návštěvníka %1$s (zkratka %2$s)", + "PlayerPause": "Pauza (zkratka %s nebo mezera)", + "PlayerPlay": "Přehrát (zkratka %s nebo mezera)", + "PlayerReplay": "Přehrát (zkratka %s nebo mezera)", + "PlayerRewindFast": "Převinout %1$s sekund (zkratka %2$s)", + "RecordedHeatmapDocStatusActive": "Tato heatmapa je aktivní. Až %1$d zobrazení stránek bude zaznamenáno se vzorkovací frekvencí %2$s.", + "RecordedHeatmapDocStatusEnded": "Tato heatmapa skončila. Nebudou zaznamenány žádné nové aktivity.", + "RecordedSessions": "Záznamy relací", + "RecordedSessionsDocStatusActive": "Toto nahrávání relace je aktivní. Bude zaznamenáno až %1$d relací se vzorkovací frekvencí %2$s.", + "RecordedSessionsDocStatusEnded": "Toto nahrávání relací skončilo a nebudou zaznamenány žádné nové relace.", + "Recordings": "Záznamy", + "ReplayRecordedSession": "Přehrát záznam relace", + "ReplayX": "Přehrát %s", + "ReportRecordedSessionsDocumentation": "Seznam všech zaznamenaných relací. Můžete přehrát jakoukoli zaznamenanou relaci.", + "RequiresActivity": "Vyžaduje aktivitu", + "RequiresActivityHelp": "Pokud je povoleno, budou zaznamenány pouze relace, které mají v jednom zobrazení stránky svitek a aktivitu kliknutí. Tím se zabrání záznamu relací s malou aktivitou.", + "Rule": "Pravidlo", + "SampleLimit": "Limit vzorku", + "SampleRate": "Vzorkovací frekvence", + "ScreenshotUrl": "Adresa URL snímku obrazovky", + "ScreenshotUrlHelp": "Ve výchozím nastavení je snímek obrazovky pořízen, když první návštěvník zobrazí cílovou stránku. Pokud se vaší cílové stránce shodují různé adresy URL stránek, můžete určit konkrétní adresu URL, která by se měla použít k zachycení snímku obrazovky. Vizualizace heatmapy bude k dispozici, pouze pokud alespoň jeden návštěvník navštívil tuto adresu URL, jejíž aktivity jsou zaznamenány. Pokud je definována, musí se adresa URL přesně shodovat. Chcete-li protokol ignorovat, spusťte adresu URL dvěma dvojitými lomítky (//example.com). Upozorňujeme, že tuto adresu URL nelze změnit, jakmile bude pořízen snímek obrazovky.", + "SessionNameHelp": "Definuje název, pod kterým budou záznamy relace k dispozici.", + "SessionRecording": "Záznam relace", + "SessionRecordingCreated": "Záznam relace byl úspěšně vytvořen.", + "SessionRecordingUpdated": "Záznam relace byl úspěšně aktualizován.", + "SessionRecordingX": "Záznam relace %s", + "SessionRecordings": "Záznamy relací", + "SessionRecordingsUsageBenefits": "Záznamy relací umožňují zaznamenávat všechny aktivity skutečného návštěvníka během jejich relace (návštěvy), jako jsou kliknutí, pohyby myši, svitky, změny velikosti okna, změny stránky a interakce formulářů. Poté můžete tyto interakce přehrát, abyste přesně viděli, jak návštěvník interagoval s vaším webem. Tímto způsobem pochopíte jejich očekávání, problémy, které mohou mít, způsoby používání a další.", + "SessionSampleLimit": "Počet relací", + "SessionSampleLimitHelp": "Definuje celkový počet relací, které chcete zaznamenat.", + "SessionSampleRateHelp": "Také známý jako 'provoz'. Pokud vyberete 100%%, budou všechny relace zaznamenány, jakmile dosáhnou cílové stránky. Pokud vyberete například 10%%, zaznamená se pouze každá 10. relace. Čím nižší procento vyberete, tím déle bude trvat dosažení vybraného limitu vzorku.", + "StatusActive": "Aktivní", + "StatusEnded": "Ukončeno", + "StopX": "Přestaňte zaznamenávat nové aktivity %s.", + "TargetAttributePath": "Cesta", + "TargetAttributeUrl": "URL", + "TargetAttributeUrlParameter": "Parametr adresy URL", + "TargetAttributeUrlParameterExample": "nazevParametru", + "TargetPage": "Cílová stránka", + "TargetPageTestErrorInvalidUrl": "Zadejte adresu URL včetně protokolu.", + "TargetPageTestLabel": "Zadejte úplnou adresu URL včetně protokolu a zkontrolujte, zda budou na této adrese URL zaznamenány aktivity:", + "TargetPageTestTitle": "URL validátor", + "TargetPageTestUrlMatches": "Na této adrese URL budou zaznamenány aktivity", + "TargetPageTestUrlNotMatches": "Na této adrese URL nebudou aktivity zaznamenány", + "TargetPages": "Vstupní stránka", + "TargetTypeContains": "obsahuje", + "TargetTypeEqualsExactly": "rovná se přesně", + "TargetTypeEqualsExactlyInfo": "Hodnota se musí přesně shodovat, včetně protokolu URL, vyhledávacího dotazu a hashe.", + "TargetTypeEqualsSimple": "rovná se jednoduché", + "TargetTypeEqualsSimpleInfo": "URL bude odpovídat každému protokolu (např. http a https) s nebo bez subdomény \"www.\". Jakákoli koncová lomka v cestě, stejně jako vyhledávací dotaz a hashovaná část adresy URL, budou při přiřazování adresy URL ignorovány.", + "TargetTypeExists": "existuje", + "TargetTypeIsAny": "je jakýkoliv", + "TargetTypeIsNot": "ne %s", + "TargetTypeRegExp": "odpovídá regulárnímu výrazu", + "TargetTypeRegExpInfo": "Jakýkoli regulární výraz, například \"^(.*)test(.*)$\".", + "TargetTypeStartsWith": "začíná s", + "TimeOnSite": "Čas na stránce", + "TrackingDisabledDefaultSettingDescription": "Tato funkce může být užitečná, pokud má vaše Matomo více webů a chcete tuto funkci používat pouze na několika konkrétních webech. Když ve výchozím nastavení deaktivujete sledování, funkce Heatmap a Session Recording se nespustí na žádném webu, pokud ji výslovně nepovolíte jako součást svého měřicího kódu, například '_paq.push ([' HeatmapSessionRecording :: enable ']);' . Tím se vyhnete zbytečným síťovým požadavkům na soubor 'configs.php', abyste zjistili, zda jsou pro aktuální web nakonfigurovány heatmapy nebo session recording. Může to být také užitečné z důvodů ochrany osobních údajů, pokud se chcete ujistit, že tato funkce bude použita pouze na některých webech.", + "TrackingDisabledDefaultSettingTitle": "Deaktivovat sledování ve výchozím nastavení", + "UpdatingData": "Aktualizuji data…", + "UrlParameterValueToMatchPlaceholder": "Hodnota odpovídající názvu parametru URL", + "UrlXDoesNotLookLikeUrl": "%s nevypadá jako URL. Ujistěte se, že obsahuje protokol jako http: // nebo že začíná na //", + "ViewReport": "Zobrazit report", + "ViewportResolution": "Rozlišení viditelného zobrazení (šířka x výška)", + "XSamples": "%s vzorků", + "disable": "zakázat", + "enable": "povolit" + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/da.json b/files/plugin-HeatmapSessionRecording-5.2.4/lang/da.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/lang/da.json @@ -0,0 +1 @@ +{} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/de.json b/files/plugin-HeatmapSessionRecording-5.2.4/lang/de.json new file mode 100644 index 0000000..977699e --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/lang/de.json @@ -0,0 +1,80 @@ +{ + "HeatmapSessionRecording": { + "ActivityClick": "Klick", + "ActivityFormChange": "Form-Änderung", + "ActivityMove": "Bewegung", + "AdvancedOptions": "Erweiterte Optionen", + "CaptureKeystrokes": "Tastenanschläge aufzeichnen", + "ColumnTime": "Zeit", + "CreateNewHeatmap": "Neue Heatmap erstellen", + "CreateNewSessionRecording": "Neues Session Recording erstellen", + "CreationDate": "Erstellungsdatum", + "DeleteX": "Lösche %s", + "EditHeatmapX": "Bearbeite Heatmap %s", + "EditSessionRecordingX": "Bearbeite Session Recording %s", + "EditX": "Bearbeite %s", + "ErrorArrayMissingKey": "Fehlender array Schlüssel \"%1$s\" in \"%2$s\" an der Position \"%3$s\".", + "ErrorArrayMissingValue": "Fehlender Wert für den Array-Schlüssel \"%1$s\" in \"%2$s\" bei Position \"%3$s\".", + "ErrorInnerIsNotAnArray": "Jedes \"%1$s\" innerhalb von \"%2$s\" muss ein array sein.", + "ErrorInvalidRegExp": "Der reguläre Ausdruck \"%1$s\" hat kein gültiges Format.", + "ErrorNotAnArray": "\"%1$s\" muss ein array sein.", + "ErrorXContainsWhitespace": "Der \"%1$s\" darf keine Leerzeichen enthalten.", + "ErrorXNotANumber": "\"%1$s\" muss eine Nummer sein.", + "ErrorXNotProvided": "Bitte geben Sie einen Wert für \"%1$s\" an.", + "ErrorXNotWhitelisted": "Der Wert für \"%1$s\" ist nicht erlaubt, verwenden Sie einen von: %2$s.", + "ErrorXTooHigh": "\"%1$s\" ist zu niedrig, der Wert darf maximal %2$s sein.", + "ErrorXTooLong": "\"%1$s\" ist zu lang, maximal %2$s Zeichen sind erlaubt.", + "ErrorXTooLow": "\"%1$s\" ist zu lang, der Wert muss mindestens %2$s sein.", + "FilesystemDirectory": "Verzeichnis", + "Filter": "Filter", + "GettingStarted": "Erste Schritte", + "Heatmap": "Heatmap", + "HeatmapCreated": "Die Heatmap wurde erfolgreich erstellt.", + "HeatmapSampleLimit": "Anzahl der Seitenansichten", + "HeatmapUpdated": "Die Heatmap wurde erfolgreich aktualisiert.", + "HeatmapWidth": "Heatmap breite", + "Manage": "Verwalten", + "ManageHeatmaps": "Heatmap Verwaltung", + "ManageSessionRecordings": "Session Recording Verwaltung", + "NoHeatmapsFound": "Keine Heatmaps gefunden", + "NoSessionRecordingsFound": "Keine Session Recordings gefunden", + "OnePageview": "1 Seitenansicht", + "PageRule": "Seiten-Regel", + "PlayerDurationXofY": "%1$s von %2$s", + "RequiresActivity": "Aktivität benötigt", + "Rule": "Regel", + "SampleLimit": "Limit", + "SampleRate": "Aufzeichnungsrate", + "SessionRecording": "Sitzungsaufzeichnung", + "SessionRecordingCreated": "Das Session Recording wurde erfolgreich erstellt.", + "SessionRecordingUpdated": "Das Session Recording wurde erfolgreich aktualisiert.", + "SessionSampleLimit": "Anzahl der Sessions", + "StatusActive": "Aktiv", + "StatusEnded": "Beendet", + "TargetAttributePath": "Pfad", + "TargetAttributeUrl": "URL", + "TargetAttributeUrlParameter": "URL Parameter", + "TargetAttributeUrlParameterExample": "nameEinesUrlParameters", + "TargetPage": "Ziel-Seite", + "TargetPageTestErrorInvalidUrl": "Geben Sie eine URL inklusive Schema an.", + "TargetPageTestTitle": "URL Prüfer", + "TargetPages": "Einstiegs-Seite", + "TargetTypeContains": "enthält", + "TargetTypeEqualsExactly": "entspricht genau", + "TargetTypeEqualsExactlyInfo": "Der Wert muss genau übereinstimmen, inklusive des URL-Schemas, der URL-Parameter und des URL-Hashes.", + "TargetTypeEqualsSimple": "entspricht einfach", + "TargetTypeEqualsSimpleInfo": "Das URL-Schema (z.B. http and https) und die Subdomain \"www.\" muss nicht übereinstimmen und wird ignoriert. Ein abschließender Schrägstrich im Pfad wie auch URL-Parameter und URL-Hashes werden ebenso ignoriert, wenn die URL verglichen wird.", + "TargetTypeExists": "existiert", + "TargetTypeIsAny": "ist beliebig", + "TargetTypeIsNot": "nicht %s", + "TargetTypeRegExp": "entspricht dem Regulären Ausdruck", + "TargetTypeRegExpInfo": "Beliebiger Regulärer Ausdruck, zum Beispiel \"^(.*)test(.*)$\".", + "TargetTypeStartsWith": "startet mit", + "TimeOnSite": "Zeit auf der Webseite", + "UpdatingData": "Aktualisiere Daten…", + "UrlParameterValueToMatchPlaceholder": "Wert für den URL-Parameter", + "ViewReport": "Gehe zum Report", + "disable": "deaktivieren", + "enable": "aktivieren" + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/en.json b/files/plugin-HeatmapSessionRecording-5.2.4/lang/en.json new file mode 100644 index 0000000..9f39115 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/lang/en.json @@ -0,0 +1,237 @@ +{ + "HeatmapSessionRecording": { + "Recordings": "Recordings", + "Heatmaps": "Heatmaps", + "Heatmap": "Heatmap", + "HeatmapX": "Heatmap %s", + "NHeatmaps": "%s heatmaps", + "NSessionRecordings": "%s session recordings", + "NoHeatmapsFound": "No heatmaps found", + "NoSessionRecordingsFound": "No session recordings found", + "HeatmapUsageBenefits": "Heatmaps let you record all clicks, mouse moves, and scroll activities of your visitors on a certain page. This helps you to find out where users think something is clickable but is not, whether there are parts of the page that are being barely viewed or interacted with, what your visitors are looking for, how much of the page is visible when users view your page, and more.", + "HeatmapXRecordedSamplesSince": "%1$s samples have been recorded since %2$s.", + "HeatmapTroubleshoot": "Having issues? %1$sLearn more%2$s.", + "SessionRecordingsUsageBenefits": "Session recordings let you record all activities of a real visitor during their session (visit) such as clicks, mouse movements, scrolls, window resizes, page changes, and form interactions. You can then replay these interactions to see exactly how a visitor interacted with your website. This way you understand their expectations, problems they may have, usage patterns, and more.", + "SessionRecordings": "Session Recordings", + "SessionRecording": "Session Recording", + "AutoPlayNextPageview": "Click to %1$s autoplay next page view (shortcut %2$s)", + "ClickToSkipPauses": "Click to %1$s skip pauses (shortcut %2$s)", + "ChangeReplaySpeed": "Change replay speed (shortcut %s)", + "PageviewXofY": "Pageview %1$s of %2$s", + "enable": "enable", + "disable": "disable", + "ReplayX": "Replay %s", + "DeleteScreenshot": "Delete Screenshot", + "CreationDate": "Creation Date", + "SessionRecordingX": "Session Recording %s", + "ReplayRecordedSession": "Replay recorded session", + "RecordedSessions": "Recorded Sessions", + "RecordedSessionsDocStatusActive": "This session recording is active. Up to %1$d sessions will be recorded with a sample rate of %2$s.", + "RecordedSessionsDocStatusEnded": "This session recording has ended and no new sessions will be recorded.", + "RecordedHeatmapDocStatusActive": "This heatmap is active. Up to %1$d page views will be recorded with a sample rate of %2$s.", + "RecordedHeatmapDocStatusEnded": "This heatmap has ended. No new activity will be recorded.", + "TrackingDisabledDefaultSettingTitle": "Disable tracking by default", + "TrackingDisabledDefaultSettingDescription": "This feature can be useful if your Matomo has multiple sites and you want to use the feature only in a few specific sites. When disabling tracking by default, the Heatmap and Session Recording feature won't execute on any site unless you specifically enable it as part of your tracking code for example by calling '_paq.push(['HeatmapSessionRecording::enable']);'. This avoids unnecessary network requests to the 'configs.php' file to determine if a heatmap or session recording is configured for the current site. It can also be useful for privacy reasons if you want to make sure the feature will be only used on some sites.", + "StatusActive": "Active", + "StatusEnded": "Ended", + "StatusPaused": "Paused", + "ExcludedElements": "Excluded Elements", + "ExcludedElementsHelp": "You can optionally define CSS selectors to exclude certain elements that should not be visible in the heatmap preview. For example a popup that is shown when the target page is opened. You can separate multiple selectors with a comma.", + "Rule": "Rule", + "Manage": "Manage", + "ViewReport": "View report", + "ErrorXNotANumber": "\"%1$s\" has to be a number.", + "SampleLimit": "Sample Limit", + "TimeOnSite": "Time on website", + "HeatmapSampleLimit": "Number of page views", + "SessionSampleLimit": "Number of sessions", + "HeatmapSampleLimitHelp": "Defines the amount of page views you want to record in total.", + "SessionSampleLimitHelp": "Defines the amount of sessions you want to record in total.", + "PageviewsInVisit": "Recorded pageviews in this session", + "PlayRecordedSession": "Play this recorded session", + "DeleteRecordedSession": "Delete this recorded session (no confirmation asked)", + "DeleteRecordedPageview": "Delete this recorded page view (no confirmation asked)", + "ViewportResolution": "Viewport Resolution (width x height)", + "OnePageview": "1 pageview", + "TargetPage": "Target Page", + "TargetPages": "Entry Pages", + "ActivityClick": "Click", + "ActivityMove": "Move", + "ActivityScroll": "Scroll", + "ActivityScrollElement": "Scroll Element", + "ActivityResize": "Resize", + "ActivityFormChange": "Form Change", + "ActivityFormText": "Form Text Change", + "ActivityFormValue": "Form Value Change", + "ActivityInitialDom": "Initial Page", + "ActivityPageChange": "Change Within Page", + "PlayerRewindFast": "Rewind %1$s seconds (shortcut %2$s)", + "PlayerForwardFast": "Skip %1$s seconds (shortcut %2$s)", + "PlayerPageViewPrevious": "Play previous pageview of this visitor %1$s (shortcut %2$s)", + "PlayerPageViewNext": "Play next pageview of this visitor (%1$s) (shortcut %2$s)", + "PlayerPlay": "Play (shortcut %s or space)", + "PlayerPause": "Pause (shortcut %s or space)", + "PlayerReplay": "Replay (shortcut %s or space)", + "PlayerDurationXofY": "%1$s of %2$s", + "AdvancedOptions": "Advanced options", + "AvgAboveFoldTitle": "Avg. Above Fold %spx", + "XSamples": "%s samples", + "AvgAboveFoldDescription": "On average visitors see the content above this line without scrolling", + "NoSessionRecordedYet": "No session has been recorded yet. If there are supposed to be recorded sessions by now, maybe the configured entry page does not match any page on your website. It is also recommended to check the \"System Check\" under \"Administration\" (only Super Users) to see if your system is configured to automatically recorded sessions.", + "NoHeatmapSamplesRecordedYet": "No page view has been recorded yet for this heatmap. If there are supposed to be recordings by now, maybe the page targets configured for this heatmap do not match any page on your website. It is also recommended to check the \"System Check\" under \"Administration\" (only Super Users) to see if your system is configured to automatically track Heatmaps.", + "NoSessionRecordedYetWithoutSystemConfiguration": "No session has been recorded yet. If there are supposed to be recorded sessions by now, maybe the configured entry page does not match any page on your website.", + "NoHeatmapSamplesRecordedYetWithoutSystemConfiguration": "No page view has been recorded yet for this heatmap. If there are supposed to be recordings by now, maybe the page targets configured for this heatmap do not match any page on your website.", + "NoHeatmapScreenshotRecordedYet": "There have been %1$s samples recorded so far. However, no screenshot has been taken yet. If there is a \"%2$s\" for this heatmap, it may take a while for the screenshot to become available as a user first needs to open this screenshot URL. Depending on the sample rate this may take a while.", + "ColumnPageviewsDocumentation": "The number of page views that were recorded during this session.", + "ColumnBrowserDocumentation": "The browser the visitor was using when this session was recorded.", + "ColumnDeviceDocumentation": "The device the visitor was using when this session was recorded.", + "ColumnLocationDocumentation": "The location of the visitor when this session was recorded.", + "ColumnOperatingSystem": "OS", + "ColumnTime": "Time", + "ColumnLabelRecordedSessions": "Entry URL → Exit URL", + "ColumnOperatingSystemDocumentation": "The operating system the visitor was using when this session was recorded.", + "ColumnActionsDocumentation": "Play the recorded session or remove the recording permanently.", + "ColumnTimeDocumentation": "The time the recording started in the timezone of the website.", + "ColumnTimeOnSiteDocumentation": "How much time the visitor spent in total while the session was recorded", + "ColumnTimeOnPageDocumentation": "How much time the visitor spent on this page while the session was recorded", + "ColumnResolutionDocumentation": "The number of pixels of the visible viewport in the browser window when the user opened this page.", + "UrlXDoesNotLookLikeUrl": "The %s does not look like a URL. Make sure it contains a protocol like http:\/\/ or that it starts with \/\/", + "ReportRecordedSessionsDocumentation": "A list of all sessions that have been recorded. You can replay any recorded session.", + "NoHeatmapsConfiguredInfo": "There are currently no active heatmaps. To view heatmaps, please ask a user with at least admin access to create a new heatmap.", + "NoSessionRecordingsConfiguredInfo": "There are currently no active session recordings. To record new sessions, please ask a user with at least admin access to create a new recording.", + "NotSupportedBrowser": "This browser is not supported, please use a later version or try a different browser.", + "HeatmapWidth": "Heatmap width", + "Width": "Width", + "Action": "Action", + "DeviceType": "Device Type", + "BreakpointX": "Breakpoint %s", + "BreakpointGeneralHelp": "The device type is automatically detected when a visitor views your website. However, sometimes it might not be possible to detect the device type. If your website is responsive and your layout breaks at a certain point, we will put desktop users with a small resolution into this device type for more accurate heatmaps. Any width lower than this value will be put into this device type. Set it to zero if your website is not responsive.", + "BreakpointGeneralHelpManage": "A user with Super User access can change the default values for these breakpoints in Administration => General Settings.", + "CaptureKeystrokes": "Capture keystrokes", + "CaptureKeystrokesHelp": "If enabled, any text that is entered into text form fields are recorded. While the text is recorded, any character a user enters is replaced with a star ('*'). You may whitelist certain fields to be recorded in plain text by specifying a 'data-matomo-unmask' attribute on a form field. However, password fields and many other fields which could potentially include personal information (address, email, credit card information, username, phone number, …) will be always automatically masked if detected by us as such (%1$slearn more%2$s). Please note that when you enable this feature, you may record personal data which may affect GDPR.", + "PersonalInformationNote": "Please note a %1$s may record personal or sensitive information. If you wish to hide certain content on your website or app, you may tag this content by adding a %2$sdata-matomo-mask%3$s attribute to the HTML markup. %4$sLearn more%5$s", + "RequiresActivity": "Requires activity", + "RequiresActivityHelp": "If enabled, only sessions that have a scroll and a click activity in one page view will be recorded. This prevents the recording of sessions with not much activity.", + "SampleRate": "Sample Rate", + "HeatmapSampleRateHelp": "Also known as 'traffic'. When you select 100%%, all visitors that visit the selected target page will be recorded. When you select for example 10%%, only every 10th visitor will be recorded. The lower the percentage you select, the longer it will take to reach the selected sample limit.", + "SessionSampleRateHelp": "Also known as 'traffic'. When you select 100%%, all sessions will be recorded as soon as they have reached the target page. When you select for example 10%%, only every 10th session will be recorded. The lower the percentage you select, the longer it will take to reach the selected sample limit.", + "MinSessionTime": "Min Session Time", + "MinSessionTimeHelp": "A session will be only recorded when a visitor has spent at least the specified time on a page.", + "ScreenshotUrl": "Screenshot URL", + "ScreenshotUrlHelp": "By default, a screenshot is taken when the first visitor views the target page. If different page URLs match your target page, you can specify a specific URL that should be used to capture the screenshot. The heatmap visualization will be only available once at least one visitor has visited this URL whose activities are recorded. If defined, the URL has to match exactly. To ignore the protocol, start the URL with two double slashes (\/\/example.com). Please note that this URL cannot be changed as soon as a screenshot has been taken.", + "GettingStarted": "Getting Started", + "TargetPageTestTitle": "URL validator", + "TargetPageTestLabel": "Enter a full URL including the protocol to check if activities will be recorded on this URL:", + "TargetPageTestErrorInvalidUrl": "Enter a URL including a protocol.", + "TargetPageTestUrlMatches": "Activities will be recorded on this URL", + "TargetPageTestUrlNotMatches": "Activities will not be recorded on this URL", + "UrlParameterValueToMatchPlaceholder": "Value to match for URL parameter name", + "TargetTypeIsAny": "is any", + "TargetTypeIsNot": "not %s", + "TargetTypeEqualsExactly": "equals exactly", + "TargetTypeEqualsExactlyInfo": "The value has to match exactly, including the URL protocol, search query and hash.", + "TargetTypeEqualsSimple": "equals simple", + "TargetTypeEqualsSimpleInfo": "The URL will match any protocol (eg http and https) with or without \"www.\" subdomain. Any trailing slash in the path as well as the search query and hash part of the URL will be ignored when matching the URL.", + "TargetTypeContains": "contains", + "TargetTypeExists": "exists", + "TargetTypeStartsWith": "starts with", + "TargetTypeRegExp": "matches the regular expression", + "TargetTypeRegExpInfo": "Any regular expression, for example \"^(.*)test(.*)$\".", + "UpdatingData": "Updating data…", + "FieldIncludedTargetsHelp": "Targets allow you to define on which pages the user activities should be recorded. You can define one or more conditions. For example, you can define to record activities whenever the URL or path equals a certain value and only if a certain URL parameter is present in the URL. Activities will be recorded only when all conditions are met, not if only one of them is met. All conditions will be evaluated case-insensitive. When you select 'equals simple', the URL protocol, search parameters and a trailing slash will be ignored.", + "FieldIncludedTargetsHelpSessions": "Targets allow you to configure to only start the recording of a session as soon as a visitor has reached a certain page. This lets you for example only record sessions that go through the check out process. You can define one or more conditions. For example, you can define to record a session only when the URL or path equals a certain value and only if a certain URL parameter is present in the URL. Sessions will be only recorded when all conditions are met on a page view, not if only one of them is met. All conditions will be evaluated case-insensitive. When you select 'equals simple', the URL protocol, search parameters and a trailing slash will be ignored.", + "DeleteHeatmapScreenshotConfirm": "Are you sure you want to delete this screenshot? It may take a while for the screenshot to be re-taken.", + "DeleteHeatmapConfirm": "Are you sure you want to delete this heatmap? Reports previously generated heatmaps will no longer be available in the reporting UI.", + "DeleteSessionRecordingConfirm": "Are you sure you want to delete this session recording? Recordings previously generated will no longer be available in the reporting UI.", + "EndSessionRecordingConfirm": "Are you sure you want to stop recording any new sessions?", + "EndHeatmapConfirm": "Are you sure you want to stop capturing any activities for this heatmap?", + "ErrorInvalidRegExp": "The regular expression \"%1$s\" does not have a valid format.", + "ErrorHeatmapNameDuplicate": "The heatmap name is already in use by another heatmap.", + "ErrorHeatmapDoesNotExist": "The requested heatmap does not exist", + "ErrorSessionRecordingDoesNotExist": "The requested session recording does not exist", + "ErrorSessionRecordingDisabled": "The session recording feature is disabled. To use this feature it needs to be enabled by a Matomo super user in the general settings.", + "ErrorHeatmapRecordingDisabled": "The heatmap recording feature is disabled. To use this feature it needs to be enabled by a Matomo super user in the general settings.", + "ErrorXNotProvided": "Please provide a value for \"%1$s\".", + "ErrorXTooLow": "\"%1$s\" is too low, minimum allowed value is %2$s.", + "ErrorXTooHigh": "\"%1$s\" is too high, maximum allowed value is %2$s.", + "ErrorXTooLong": "\"%1$s\" is too long, max %2$s characters are allowed.", + "ErrorNotAnArray": "\"%1$s\" has to be an array.", + "ErrorInnerIsNotAnArray": "Each \"%1$s\" within \"%2$s\" has to be an array.", + "ErrorArrayMissingKey": "Missing array key \"%1$s\" in \"%2$s\" at position \"%3$s\".", + "ErrorArrayMissingValue": "Missing value for array key \"%1$s\" in \"%2$s\" at position \"%3$s\".", + "ErrorXNotWhitelisted": "The value for \"%1$s\" is not allowed, use one of: %2$s.", + "ErrorXContainsWhitespace": "The \"%1$s\" is not allowed to contain any white space.", + "ErrorPageRuleRequired": "At least one page rule has to be set.", + "HeatmapCreated": "The heatmap has been successfully created.", + "HeatmapUpdated": "The heatmap has been successfully updated.", + "SessionRecordingCreated": "The session recording has been successfully created.", + "SessionRecordingUpdated": "The session recording has been successfully updated.", + "FieldNamePlaceholder": "eg 'Sign Up Page'", + "PageRule": "Page Rule", + "HeatmapNameHelp": "Defines the name under which the report for this heatmap will be available.", + "SessionNameHelp": "Defines the name under which the session recordings will be available.", + "CreateNewHeatmap": "Create new heatmap", + "StopX": "Stop recording any new activities for this %s.", + "EditX": "Edit %s", + "DeleteX": "Delete %s", + "EditHeatmapX": "Edit Heatmap %s", + "Filter": "Filter", + "EditSessionRecordingX": "Edit Session Recording %s", + "CreateNewSessionRecording": "Create new session recording", + "TargetAttributeUrl": "URL", + "TargetAttributePath": "Path", + "FilesystemDirectory": "directory", + "TargetAttributeUrlParameter": "URL Parameter", + "TargetAttributeUrlParameterExample": "nameOfUrlParameter", + "ManageSessionRecordings": "Manage Session Recordings", + "ManageHeatmaps": "Manage Heatmaps", + "ConfigsPhpSuccess": "'%s' is accessible", + "ConfigsPhpNotAccessible": "The URL '%s' seems to be not accessible from the Internet or Intranet.", + "ConfigsPhpErrorResult": "As a result, tracking Heatmaps and Session Recordings may not work. You may need to change your webserver configuration to allow access to this file via the Internet or Intranet.", + "ConfigsPhpSelfSignedError": "Requesting '%s' resulted in an SSL error. Maybe you are using a self signed certificate?", + "ConfigsInternetDisabled": "We couldn't check if '%s' is accessible because the internet connection is disabled on this Matomo.", + "ConfigsPhpUnknown": "We couldn't check if '%s' is accessible over the Internet or Intranet.", + "ConfigsPhpManualCheck": "Please open the URL manually in a browser to see if the response contains 'Piwik.HeatmapSessionRecording'. If not, you might need to modify your server configuration as this file needs to be accessible via a browser from the Internet or Intranet.", + "ConfigsPhpUnknownError": "Requesting '%1$s' resulted in an error: %2$s.", + "ManageHeatmapSubcategoryHelp": "Heatmaps let you record all clicks, mouse moves, and scroll activities of your visitors on a specific page. This section enables you to create and manage Heatmap tracking for your website.", + "ManageSessionRecordingSubcategoryHelp": "Session recordings let you record all activities of a real visitor during their session (visit) such as clicks, mouse movements, scrolls, window resizes, page changes, and form interactions. This section enables you to create and manage Session Recordings for your site.", + "DisableSessionRecordingTitle": "Disable Session Recording", + "DisableSessionRecordingDescription": "By disabling this feature no session recordings can be configured or tracked anymore.", + "DisableSessionRecordingInlineHelp": "%1$sNote: Disabling this feature will delete all session recordings including previously tracked session recordings. There will be no changes for heatmaps if you enable or disable this feature.%2$s", + "PeriodDisabledErrorMessage": "The \"%1$s\" period is disabled but required for this feature to work. Select a different period or ask a system administrator to change Matomo's config file 'config\/config.ini.php' to allow the \"%1$s\" period in the \"enabled_periods_API\" setting.", + "DisableHeatmapRecordingTitle": "Disable Heatmaps", + "DisableHeatmapRecordingDescription": "By disabling this feature no heatmap can be configured or recorded anymore.", + "DisableHeatmapRecordingInlineHelp": "%1$sNote: Disabling this feature will delete all heatmaps including previously tracked heatmaps. There will be no changes for session recording if you enable or disable this feature.%2$s", + "EnableIncludeCountriesTitle": "Only track visitors from specific countries", + "EnableIncludeCountriesDescription": "By default, all visits are recorded. Enable this section when you want to track visits from specific countries.", + "Country": "Country", + "HeatmapInfoTrackVisitsFromCountries": "The heatmap is configured to only track visits from %1$s.", + "SessionRecordingInfoTrackVisitsFromCountries": "The session recording is configured to only track visits from %1$s.", + "AdBlockerDetected": "%1$s might not be visible when using an ad blocker. Please disable your ad blocker on this site to make sure Matomo works without any issues.", + "PauseReason": "The %1$s was paused due to exhaustion of your quota, please contact support for further details.", + "TotalEvents": "Events", + "ColumnTotalEventsDocumentation": "The number of events that were recorded during this session. For example: click, move, scroll, resize, page changes and form interaction events.", + "CaptureDomTitle": "Capture Heatmap Snapshot Manually", + "CaptureDomInlineHelp": "By default, heatmap snapshots are automatically taken on page load. If your page dynamically loads elements (such as with lazy loading) your snapshot could be incomplete. You can manually capture a complete snapshot by enabling this option and executing the following Javascript code in your browser: %1$s %2$sNote:%3$s This option resets automatically after capturing the snapshot.", + "MatomoJSNotWritableErrorMessage": "HeatmapSessionRecording: %1$s are currently unable to track visits, because the matomo.js file is read-only. Please see the %2$sdocumentation%3$s about how to make the file writable.", + "Clicks": "Clicks:", + "ClickRate": "Click rate:", + "Moves": "Moves:", + "MoveRate": "Move rate:", + "HeatmapAddedActivity": "added a heatmap \"%1$s\" for site \"%2$s\"", + "HeatmapDeletedActivity": "deleted the heatmap \"%1$s\" for site \"%2$s\"", + "HeatmapPausedActivity": "paused the heatmap \"%1$s\" for site \"%2$s\"", + "HeatmapResumedActivity": "resumed the heatmap \"%1$s\" for site \"%2$s\"", + "HeatmapEndedActivity": "ended the heatmap \"%1$s\" for site \"%2$s\"", + "HeatmapScreenshotDeletedActivity": "deleted the heatmap screenshot \"%1$s\" for site \"%2$s\"", + "HeatmapUpdatedActivity": "updated the heatmap \"%1$s\" for site \"%2$s\"", + "SessionRecordingAddedActivity": "added a session recording \"%1$s\" for site \"%2$s\"", + "SessionRecordingDeletedActivity": "deleted the session recording \"%1$s\" for site \"%2$s\"", + "SessionRecordingEndedActivity": "ended the session recording \"%1$s\" for site \"%2$s\"", + "SessionRecordingUpdatedActivity": "updated the session recording \"%1$s\" for site \"%2$s\"", + "SessionRecordingPausedActivity": "paused the session recording \"%1$s\" for site \"%2$s\"", + "SessionRecordingResumedActivity": "resumed the session recording \"%1$s\" for site \"%2$s\"", + "RecordedSessionDeletedActivity": "deleted a recorded session for session recording \"%1$s\" for site \"%2$s\"", + "RecordedPageviewDeletedActivity": "deleted a recorded page view for session recording \"%1$s\" for site \"%2$s\"" + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/es.json b/files/plugin-HeatmapSessionRecording-5.2.4/lang/es.json new file mode 100644 index 0000000..f11ba37 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/lang/es.json @@ -0,0 +1,183 @@ +{ + "HeatmapSessionRecording": { + "ActivityClick": "Clic", + "ActivityFormChange": "Cambiar formulario", + "ActivityFormText": "Cambio del texto del formulario", + "ActivityFormValue": "Cambio de valor del formulario", + "ActivityInitialDom": "Página inicial", + "ActivityMove": "Mover", + "ActivityResize": "Redimensionar", + "ActivityScroll": "Desplazar", + "ActivityScrollElement": "Desplazar elemento", + "AdvancedOptions": "Opciones avanzadas", + "AutoPlayNextPageview": "Haga clic para %1$s reproducir automáticamente la siguiente página (atajo %2$s)", + "AvgAboveFoldDescription": "En promedio, los visitantes ven el contenido sobre esta línea sin desplazarse", + "AvgAboveFoldTitle": "Promedio sobre el doblez %spx", + "BreakpointGeneralHelp": "El tipo de dispositivo se detecta automáticamente cuando un visitante visita su sitio web. Sin embargo, a veces puede que no sea posible detectarlo. Si su sitio web es receptivo y su diseño en un cierto punto se fractura, pondremos usuarios de escritorio con una pequeña resolución en este tipo de dispositivo para obtener mapas de calor más precisos. Cualquier ancho inferior a este valor se pondrá en este tipo de dispositivo. Establézcalo en cero si su sitio web no es receptivo.", + "BreakpointGeneralHelpManage": "Un usuario con acceso de Super Usuario puede cambiar los valores predeterminados para estos puntos de interrupción en Administración => Configuración general.", + "BreakpointX": "Punto de interrupción %s", + "CaptureKeystrokes": "Captura de pulsaciones de teclado", + "CaptureKeystrokesHelp": "Si está habilitado, se registrará cualquier texto que se ingrese en los campos de formulario de texto. Mientras se graba el texto, cualquier carácter que ingrese un usuario se reemplaza con una estrella ('*'). Puede incluir en la lista blanca ciertos campos para que se registren en texto sin formato especificando un atributo 'data-matomo-unmask' en un campo de formulario. Sin embargo, los campos de contraseña y muchos otros campos que podrían incluir información personal (dirección, correo electrónico, información de tarjeta de crédito, nombre de usuario, número de teléfono, …) siempre se ocultarán automáticamente si los detectamos como tales (%1$sobtenga más información%2$s). Tenga en cuenta que cuando habilita esta función, puede registrar datos personales que pueden afectar a GDPR.", + "ChangeReplaySpeed": "Cambiar la velocidad de repetición (atajo %s)", + "ClickToSkipPauses": "Haga clic %1$s para saltar pausas (atajo %2$s)", + "ColumnActionsDocumentation": "Reproducir la sesión grabada o eliminarla permanentemente.", + "ColumnBrowserDocumentation": "El navegador que el visitante estaba usando cuando esta sesión fue grabada.", + "ColumnDeviceDocumentation": "El dispositivo que el visitante estaba usando cuando esta sesión fue grabada.", + "ColumnLabelRecordedSessions": "URL de entrada → URL de salida", + "ColumnLocationDocumentation": "La ubicación del visitante cuando esta sesión fue grabada.", + "ColumnOperatingSystem": "OS", + "ColumnOperatingSystemDocumentation": "El sistema operativo que estaba utilizando el visitante cuando se grabó esta sesión.", + "ColumnPageviewsDocumentation": "El número de páginas vistas que se registraron durante esta sesión.", + "ColumnResolutionDocumentation": "El número de píxeles de la ventana visible en la ventana del navegador cuando el usuario abrió esta página.", + "ColumnTime": "Tiempo", + "ColumnTimeDocumentation": "La hora en que comenzó la grabación en la zona horaria del sitio web.", + "ColumnTimeOnPageDocumentation": "Cuánto tiempo pasó el visitante en esta página mientras se grabó la sesión", + "ColumnTimeOnSiteDocumentation": "Cuánto tiempo pasó en total el visitante mientras se grabó la sesión", + "ConfigsInternetDisabled": "No pudimos verificar si '%s' está accesible porque la conexión a internet está deshabilitada en esta instalación Matomo.", + "ConfigsPhpErrorResult": "Como resultado, el seguimiento de los mapas de calor y las grabaciones de sesión puede no funcionar. Es posible que deba cambiar la configuración del servidor web para permitir el acceso a este archivo a través de Internet o Intranet.", + "ConfigsPhpManualCheck": "Abra la dirección URL manualmente en un navegador para ver si la respuesta contiene 'Piwik.HeatmapSessionRecording'. De lo contrario, es posible que deba modificar la configuración de su servidor, ya que este archivo debe ser accesible a través de un navegador desde Internet o Intranet.", + "ConfigsPhpNotAccessible": "La URL '%s' parece no estar accesible desde Internet o Intranet.", + "ConfigsPhpSelfSignedError": "La solicitud '%s' resultó en un error SSL. Tal vez ¿está utilizando un certificado autofirmado?", + "ConfigsPhpSuccess": "'%s' es accessible", + "ConfigsPhpUnknown": "No pudimos comprobar si '%s' está accesible a través de Internet o Intranet.", + "ConfigsPhpUnknownError": "Solicitando '%1$s' resultó en un error: %2$s.", + "CreateNewHeatmap": "Crear nuevo mapa de calor", + "CreateNewSessionRecording": "Crear nueva sesión de grabación", + "CreationDate": "Fecha de creación", + "DeleteHeatmapConfirm": "¿Estás seguro de que desea eliminar este mapa de calor? Los informes de mapas de calor generados anteriormente ya no estarán disponibles en la interfaz de usuario de los informes.", + "DeleteRecordedPageview": "Eliminar esta página vista registrada (sin solicitud de confirmación)", + "DeleteRecordedSession": "Eliminar esta sesión grabada (no se solicita confirmación)", + "DeleteSessionRecordingConfirm": "¿Estás seguro de que desea eliminar esta grabación de sesión? Las grabaciones generadas anteriormente ya no estarán disponibles en la interfaz de usuario de los informes.", + "DeleteX": "Eliminar %s", + "EditHeatmapX": "Modificar mapa de calor %s", + "EditSessionRecordingX": "Editar grabación de sesión %s", + "EditX": "Modificar %s", + "EndHeatmapConfirm": "¿Está seguro de que desea dejar de capturar cualquier actividad para este mapa de calor?", + "EndSessionRecordingConfirm": "¿Está seguro de que desea dejar de grabar nuevas sesiones?", + "ErrorArrayMissingKey": "Falta la clave \"%1$s\" en \"%2$s\" en la posición \"%3$s\".", + "ErrorArrayMissingValue": "Falta el valor para la clave de la matriz \"%1$s\" en \"%2$s\" en la posición \"%3$s\".", + "ErrorHeatmapDoesNotExist": "El mapa de calor solicitado no existe.", + "ErrorHeatmapNameDuplicate": "El nombre de mapa de calor ya está en uso por otro mapa de calor.", + "ErrorInnerIsNotAnArray": "Cada \"%1$s\" dentro de \"%2$s\" tiene que ser una matriz.", + "ErrorInvalidRegExp": "La expresión regular \"%1$s\" no tiene en formato válido.", + "ErrorNotAnArray": "\"%1$s\" tiene que ser una matriz.", + "ErrorPageRuleRequired": "Al menos una regla de página debe ser dispuesta.", + "ErrorSessionRecordingDoesNotExist": "No existe el registro de la sesión solicitada.", + "ErrorXContainsWhitespace": "El \"%1$s\" no puede contener ningún espacio en blanco.", + "ErrorXNotANumber": "\"%1$s\" tiene que ser un número.", + "ErrorXNotProvided": "Por favor suministre un valor para \"%1$s\".", + "ErrorXNotWhitelisted": "El valor para \"%1$s\" no está permitido, use uno de: %2$s.", + "ErrorXTooHigh": "\"%1$s\" es demasiado alto, el valor máximo permitido es %2$s.", + "ErrorXTooLong": "\"%1$s\" es muy largo, un máximo de %2$s caracteres es permitido.", + "ErrorXTooLow": "\"%1$s\" es demasiado bajo, el valor mínimo permitido es %2$s.", + "ExcludedElements": "Elementos excluidos", + "ExcludedElementsHelp": "Opcionalmente, puede definir los selectores CSS para excluir ciertos elementos que no deberían ser visibles en la vista previa del mapa de calor. Por ejemplo, una ventana emergente que se muestra cuando se abre la página de destino. Puede separar múltiples selectores con una coma.", + "FieldIncludedTargetsHelp": "Los objetivos le permiten definir en qué páginas se deben registrar las actividades del usuario. Puede definir una o más condiciones. Por ejemplo, puede definir registrar actividades cuando la dirección URL o la ruta de acceso es igual a un cierto valor y solo si un determinado parámetro de URL está presente en la URL. Las actividades se registrarán solo cuando se cumplan todas las condiciones, no si solo se cumple una de ellas. Todas las condiciones serán evaluadas sin distinción de mayúsculas y minúsculas. Cuando selecciona 'es igual a simple', se ignorarán el protocolo de URL, los parámetros de búsqueda y la barra diagonal.", + "FieldIncludedTargetsHelpSessions": "Los objetivos le permiten configurar para iniciar solo la grabación de una sesión tan pronto como un visitante haya alcanzado una determinada página. Esto le permite, por ejemplo, solo grabar sesiones que pasan por el proceso de salida. Puede definir una o más condiciones. Por ejemplo, puede definir grabar una sesión solo cuando la URL o la ruta de acceso es igual a un cierto valor y solo si un determinado parámetro de URL está presente en la URL. Las sesiones solo se grabarán cuando se cumplan todas las condiciones en una página vista, no si solo se cumple una de ellas. Todas las condiciones serán evaluadas sin distinción de mayúsculas y minúsculas. Cuando selecciona 'es igual a simple', se ignorarán el protocolo de URL, los parámetros de búsqueda y la barra diagonal.", + "FieldNamePlaceholder": "ej 'Registrarse'", + "FilesystemDirectory": "directorio", + "Filter": "Filtro", + "GettingStarted": "Empezando", + "Heatmap": "Mapa de calor", + "HeatmapCreated": "El mapa de calor ha sido creado con éxito.", + "HeatmapNameHelp": "Define el nombre bajo el cual el informe para este mapa de calor estará disponible.", + "HeatmapSampleLimit": "Número de páginas vistas", + "HeatmapSampleLimitHelp": "Define la cantidad de página vistas que desea grabar en total.", + "HeatmapSampleRateHelp": "También conocido como 'tráfico'. Cuando selecciona el 100%%, se registrarán todos los visitantes que visiten la página de destino seleccionada. Cuando selecciona, por ejemplo, el 10%%, solo se registrará cada décimo visitante. Cuanto menor sea el porcentaje que seleccione, más tardará en alcanzar el límite de muestra seleccionado.", + "HeatmapUpdated": "El mapa de calor se ha actualizado con éxito.", + "HeatmapUsageBenefits": "Los mapas de calor le permiten registrar todos los clics, movimientos del mouse y actividades de desplazamiento de sus visitantes en una página determinada. Esto le ayuda a descubrir dónde los usuarios creen que se puede hacer clic en algo pero no si hay partes de la página que apenas se ven o interactúan, qué buscan sus visitantes, qué parte de la página está visible cuando los usuarios ven su página, y más.", + "HeatmapWidth": "Ancho del mapa del calor", + "HeatmapX": "Mapa de calor %s", + "HeatmapXRecordedSamplesSince": "%1$s muestras se han grabado desde %2$s.", + "Heatmaps": "Mapas de color", + "Manage": "Administrar", + "ManageHeatmaps": "Gestionar mapas de calor", + "ManageSessionRecordings": "Administrar grabaciones de sesión", + "MinSessionTime": "Tiempo de sesión min", + "MinSessionTimeHelp": "Una sesión solo se grabará cuando un visitante haya pasado al menos el tiempo especificado en una página.", + "NHeatmaps": "%s mapas de calor", + "NSessionRecordings": "%s grabaciones de sesión", + "NoHeatmapSamplesRecordedYet": "Aún no se ha registrado una vista de página para este mapa de calor. Si se supone que ya debiera haber grabaciones, tal vez los objetivos de página configurados para este mapa de calor no coincidan con ninguna página de su sitio web. También se recomienda verificar la \"Comprobación del sistema\" en \"Administración\" (solo para los Super usuarios) para ver si su sistema está configurado para rastrear automáticamente los mapas de calor.", + "NoHeatmapScreenshotRecordedYet": "Se han registrado %1$s muestras hasta el momento. Sin embargo, aún no se ha tomado ninguna captura de pantalla. Si hay un \"%2$s\" para este mapa de calor, la captura de pantalla puede tardar un tiempo en estar disponible, ya que el usuario primero debe abrir esta dirección URL de captura de pantalla. Dependiendo de la frecuencia de muestreo, esto puede tomar un tiempo.", + "NoHeatmapsConfiguredInfo": "Actualmente no hay mapas de calor activos. Para verlos, solicite a un usuario con al menos acceso de administrador para crear un nuevo mapa de calor.", + "NoHeatmapsFound": "No se encontraron mapas de calor", + "NoSessionRecordedYet": "Aún no se ha registrado ninguna sesión. Si se supone que ya habrá sesiones grabadas, tal vez la página de entrada configurada no coincida con ninguna página de su sitio web. También se recomienda verificar la \"Comprobación del sistema\" en \"Administración\" (solo para los Super Usuarios) para ver si su sistema está configurado para sesiones grabadas automáticamente.", + "NoSessionRecordingsConfiguredInfo": "Actualmente no hay grabaciones de sesión activas. Para grabar nuevas sesiones, solicite a un usuario con al menos acceso de administrador para crear una nueva grabación.", + "NoSessionRecordingsFound": "No se encontraron grabaciones de sesión", + "NotSupportedBrowser": "Este navegador no es compatible, utilice una versión posterior o inténtelo con otro navegador.", + "OnePageview": "1 página vista", + "PageRule": "Norma de la página", + "PageviewXofY": "Vista de página %1$s de %2$s", + "PageviewsInVisit": "Páginas vistas registradas en esta sesión", + "PersonalInformationNote": "Tenga en cuenta que %1$spuede grabar información personal o confidencial. Si desea ocultar cierto contenido en su sitio web o aplicación, puede etiquetar este contenido agregando un atributo %2$sdata-matomo-mask%3$s al código HTML. %4$sAprende más%5$s", + "PlayRecordedSession": "Reproducir esta sesión registrada", + "PlayerDurationXofY": "%1$s de %2$s", + "PlayerForwardFast": "Saltar %1$s segundos (atajo %2$s)", + "PlayerPageViewNext": "Reproducir la siguiente página vista de este visitante (%1$s) (atajo %2$s)", + "PlayerPageViewPrevious": "Reproducir la próxima página vista de este visitante %1$s (atajo %2$s)", + "PlayerPause": "Pausa (atajo %s o barra espaciadora)", + "PlayerPlay": "Reproducir (atajo %s o barra espaciadora)", + "PlayerReplay": "Repetición (atajo %s o barra espaciadora)", + "PlayerRewindFast": "Rebobinar %1$s segundos (atajo %2$s)", + "RecordedHeatmapDocStatusActive": "Este mapa de calor está activo. Se registrarán hasta %1$d vistas de página con una frecuencia de muestreo de %2$s.", + "RecordedHeatmapDocStatusEnded": "Este mapa de calor ha terminado. No se grabará ninguna nueva actividad.", + "RecordedSessions": "Sesiones registradas", + "RecordedSessionsDocStatusActive": "Esta sesión registrada está activa. Hasta %1$d sesiones serán registradas con una frecuencia de muestreo de %2$s.", + "RecordedSessionsDocStatusEnded": "Esta sesión de grabación ha finalizado y no se grabarán nuevas sesiones.", + "Recordings": "Registros", + "ReplayRecordedSession": "Repetir la sesión registrada", + "ReplayX": "Repetir %s", + "ReportRecordedSessionsDocumentation": "Una lista de todas las sesiones que se han grabado. Puede reproducir cualquier sesión grabada.", + "RequiresActivity": "Requiere actividad", + "RequiresActivityHelp": "Si está habilitado, solo se grabarán las sesiones que tengan un desplazamiento y actividades de los clics en una vista de página. Esto evita la grabación de sesiones con poca actividad.", + "Rule": "Regla", + "SampleLimit": "Límite de muestra", + "SampleRate": "Frecuencia de muestreo", + "ScreenshotUrl": "URL de la captura de pantalla", + "ScreenshotUrlHelp": "De forma predeterminada, se toma una captura de pantalla cuando el primer visitante ve la página de destino. Si diferentes URL de página coinciden con su página de destino, puede especificar una URL específica que se debe utilizar para capturar la captura de pantalla. La visualización del mapa de calor solo estará disponible una vez que al menos un visitante haya visitado esta URL cuyas actividades estén registradas. Si se define, la URL debe coincidir exactamente. Para ignorar el protocolo, inicie la URL con dos barras diagonales dobles (//example.com). Tenga en cuenta que esta URL no se puede cambiar tan pronto como se haya tomado una captura de pantalla.", + "SessionNameHelp": "Define el nombre bajo el cual estarán disponibles las grabaciones de la sesión.", + "SessionRecording": "Registro de sesión", + "SessionRecordingCreated": "La grabación de la sesión se ha creado con éxito.", + "SessionRecordingUpdated": "La grabación de la sesión se ha actualizado con éxito.", + "SessionRecordingX": "Registrando sesión %s", + "SessionRecordings": "Registros de sesión", + "SessionRecordingsUsageBenefits": "Las grabaciones de sesión le permiten registrar todas las actividades de un visitante durante su sesión (visita), como clics, movimientos del mouse, desplazamientos, cambio de tamaño de ventana, cambios de página e interacciones de formularios. Luego puede reproducir estas interacciones para ver exactamente cómo interactuó con su sitio web. De esta manera puede comprende las expectativas, los problemas que pueden tener, sus patrones de uso y mucho más.", + "SessionSampleLimit": "Número de sesiones", + "SessionSampleLimitHelp": "Defina la cantidad de sesiones que desea registrar en total.", + "SessionSampleRateHelp": "También conocido como 'tráfico'. Cuando selecciona el 100%%, todas las sesiones se grabarán tan pronto como alcancen la página de destino. Cuando selecciona, por ejemplo, el 10%%, solo se grabará cada décima sesión. Cuanto menor sea el porcentaje que seleccione, más tardará en alcanzar el límite de muestra seleccionado.", + "StatusActive": "Activo", + "StatusEnded": "Finalizó", + "StopX": "Dejar de grabar nuevas actividades para este %s.", + "TargetAttributePath": "Ruta", + "TargetAttributeUrl": "URL", + "TargetAttributeUrlParameter": "Parámetro URL", + "TargetAttributeUrlParameterExample": "nameOfUrlParameter", + "TargetPage": "Página de destino", + "TargetPageTestErrorInvalidUrl": "Ingrese un URL incluyendo su protocolo.", + "TargetPageTestLabel": "Ingrese una URL completa que incluya el protocolo para verificar si las actividades se registrarán en esta URL:", + "TargetPageTestTitle": "Validador URL", + "TargetPageTestUrlMatches": "Las actividades serán registradas en esta URL.", + "TargetPageTestUrlNotMatches": "Las actividades no serán registradas en esta URL.", + "TargetPages": "Páginas de entrada", + "TargetTypeContains": "contiene", + "TargetTypeEqualsExactly": "exactamente igual", + "TargetTypeEqualsExactlyInfo": "El valor debe coincidir exactamente, incluido el protocolo de URL, la consulta de búsqueda y el hash.", + "TargetTypeEqualsSimple": "es igual a simple", + "TargetTypeEqualsSimpleInfo": "La URL coincidirá con cualquier protocolo (por ejemplo, http y https) con o sin subdominio \"www\". Cualquier barra diagonal final en la ruta, así como la consulta de búsqueda y la parte de hash de la URL se ignorarán cuando coincida la URL.", + "TargetTypeExists": "existe", + "TargetTypeIsAny": "es cualquier", + "TargetTypeIsNot": "no %s", + "TargetTypeRegExp": "coincide con la expresión regular", + "TargetTypeRegExpInfo": "Cualquier expresión regular, por ejemplo \"^(.*)test(.*)$\".", + "TargetTypeStartsWith": "empieza con", + "TimeOnSite": "Tiempo en sitio web", + "UpdatingData": "Actualizando datos…", + "UrlParameterValueToMatchPlaceholder": "Valor para coincidir con el nombre del parámetro de la URL", + "UrlXDoesNotLookLikeUrl": "La %s no se parece a una dirección URL. Asegúrese que respeta el protocolo http:// o que comienza con //", + "ViewReport": "Ver informe", + "ViewportResolution": "Resolución de la ventana gráfica (ancho x alto)", + "XSamples": "%s muestras", + "disable": "deshabilitar", + "enable": "habilitar" + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/fi.json b/files/plugin-HeatmapSessionRecording-5.2.4/lang/fi.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/lang/fi.json @@ -0,0 +1 @@ +{} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/fr.json b/files/plugin-HeatmapSessionRecording-5.2.4/lang/fr.json new file mode 100644 index 0000000..2763f48 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/lang/fr.json @@ -0,0 +1,237 @@ +{ + "HeatmapSessionRecording": { + "Action": "Action", + "ActivityClick": "Clic", + "ActivityFormChange": "Changement de formulaire", + "ActivityFormText": "Modification du texte du formulaire", + "ActivityFormValue": "Changement de valeur du formulaire", + "ActivityInitialDom": "Page initiale", + "ActivityMove": "Mouvement", + "ActivityPageChange": "Changement dans la page", + "ActivityResize": "Redimensionner", + "ActivityScroll": "Défilement", + "ActivityScrollElement": "Élément de défilement", + "AdBlockerDetected": "%1$s peut ne pas être visible lorsque vous utilisez un bloqueur de publicités. Veuillez désactiver votre bloqueur de publicités sur ce site pour vous assurer que Matomo fonctionne correctement.", + "AdvancedOptions": "Options avancées", + "AutoPlayNextPageview": "Cliquez sur %1$s pour lire automatiquement la page suivante (raccourci %2$s)", + "AvgAboveFoldDescription": "En moyenne, les visiteurs voient le contenu au-dessus de cette ligne sans défiler", + "AvgAboveFoldTitle": "Moy. au-dessus de la ligne de flottaison %spx", + "BreakpointGeneralHelp": "Le type d'appareil est automatiquement détecté lorsqu'un visiteur consulte votre site web. Cependant, il arrive parfois qu'il ne soit pas possible de détecter le type d'appareil. Si votre site Web est responsive et que votre mise en page s'interrompt à un moment donné, nous classerons les utilisateurs d'ordinateurs de bureau ayant une faible résolution dans ce type d'appareil pour obtenir des cartes de chaleur plus précises. Toute largeur inférieure à cette valeur sera classée dans ce type d'appareil. Mettez la valeur zéro si votre site Web n'est pas responsive.", + "BreakpointGeneralHelpManage": "Un utilisateur disposant d'un accès Super utilisateur peut modifier les valeurs par défaut de ces points d'arrêt dans Administration => Paramètres généraux.", + "BreakpointX": "Point d'arrêt %s", + "CaptureDomInlineHelp": "Par défaut, les instantanés de la carte de chaleur sont automatiquement pris au chargement de la page. Si votre page charge dynamiquement des éléments (comme avec le chargement différé), votre instantané pourrait être incomplet (page blanche). Vous pouvez capturer manuellement un instantané complet en activant cette option et en exécutant le code Javascript suivant dans votre navigateur : %1$s %2$sNote :%3$s Cette option se réinitialise automatiquement après la capture de l'instantané.", + "CaptureDomTitle": "Capturer un instantané de la carte de chaleur manuellement", + "CaptureKeystrokes": "Enregistrement des frappes de clavier", + "CaptureKeystrokesHelp": "Si cette option est activée, tout le texte saisi dans les champs de formulaire texte seront enregistrés. Pendant l'enregistrement du texte, tout caractère saisi par l'utilisateur est remplacé par une étoile ('*'). Vous pouvez établir une liste blanche de certains champs qui seront enregistrés en texte brut en spécifiant un attribut \"data-matomo-unmask\" sur un champ de formulaire. Toutefois, les champs relatifs au mot de passe et de nombreux autres champs susceptibles de contenir des informations personnelles (adresse, courriel, informations relatives à la carte de crédit, nom d'utilisateur, numéro de téléphone, ...) seront toujours automatiquement masqués si nous les détectons comme tels (%1$sen savoir plus%2$s). Veuillez noter que lorsque vous activez cette fonction, vous pouvez enregistrer des données personnelles, ce qui peut entrer en conflit avec le RGPD.", + "ChangeReplaySpeed": "Modifier la vitesse de lecture (raccourci %s)", + "ClickRate": "Taux de clics :", + "ClickToSkipPauses": "Cliquez pour %1$s sauter les pauses (raccourci %2$s)", + "Clicks": "Clics :", + "ColumnActionsDocumentation": "Lire la session enregistrée ou supprimez l'enregistrement de façon permanente.", + "ColumnBrowserDocumentation": "Le navigateur que le visiteur utilisait au moment de l'enregistrement de cette session.", + "ColumnDeviceDocumentation": "L'appareil que le visiteur utilisait lorsque cette session a été enregistrée.", + "ColumnLabelRecordedSessions": "URL d'entrée → URL de sortie", + "ColumnLocationDocumentation": "L'emplacement du visiteur lorsque cette session a été enregistrée.", + "ColumnOperatingSystem": "Système d'exploitation", + "ColumnOperatingSystemDocumentation": "Le système d'exploitation que le visiteur utilisait lorsque cette session a été enregistrée.", + "ColumnPageviewsDocumentation": "Le nombre de pages vues qui ont été enregistrées pendant cette session.", + "ColumnResolutionDocumentation": "Le nombre de pixels visiblede la fenêtre d'affichage dans la fenêtre du navigateur lorsque l'utilisateur a ouvert cette page.", + "ColumnTime": "Heure", + "ColumnTimeDocumentation": "L'heure à laquelle l'enregistrement a commencé dans le fuseau horaire du site web.", + "ColumnTimeOnPageDocumentation": "Le temps que le visiteur a passé sur cette page pendant l'enregistrement de la session", + "ColumnTimeOnSiteDocumentation": "Combien de temps le visiteur a passé au total pendant que la session était enregistrée", + "ColumnTotalEventsDocumentation": "Le nombre d'événements qui ont été enregistrés durant cette session. Par exemple : clic, déplacement, défilement, redimensionnement, changements de page et interactions avec les formulaires.", + "ConfigsInternetDisabled": "Nous n'avons pas pu vérifier si '%s' est accessible car la connexion internet est désactivée sur ce Matomo.", + "ConfigsPhpErrorResult": "Par conséquent, le suivi des cartes de chaleur et des enregistrements de sessions peut ne pas fonctionner. Vous devrez peut-être modifier la configuration de votre serveur Web pour permettre l'accès à ce fichier via Internet ou Intranet.", + "ConfigsPhpManualCheck": "Veuillez ouvrir l'URL manuellement dans un navigateur pour voir si la réponse contient 'Piwik.HeatmapSessionRecording'. Si ce n'est pas le cas, vous devrez peut-être modifier la configuration de votre serveur car ce fichier doit être accessible via un navigateur depuis Internet ou Intranet.", + "ConfigsPhpNotAccessible": "L'URL '%s' semble ne pas être accessible depuis Internet ou Intranet.", + "ConfigsPhpSelfSignedError": "La requête de '%s' a donné lieu à une erreur SSL. Peut-être utilisez-vous un certificat auto-signé ?", + "ConfigsPhpSuccess": "'%s' est accessible", + "ConfigsPhpUnknown": "Nous n'avons pas pu vérifier si '%s' est accessible par Internet ou Intranet.", + "ConfigsPhpUnknownError": "La requête de '%1$s' a donné lieu à une erreur : %2$s.", + "Country": "Pays", + "CreateNewHeatmap": "Créer une nouvelle carte de chaleur", + "CreateNewSessionRecording": "Créer un nouvel enregistrement de session", + "CreationDate": "Date de création", + "DeleteHeatmapConfirm": "Êtes-vous sûr de vouloir supprimer cette carte de chaleur ? Les cartes de chaleur générées précédemment ne seront plus disponibles dans l'interface de création de rapports.", + "DeleteHeatmapScreenshotConfirm": "Êtes-vous sûr de vouloir supprimer cette capture d'écran ? Il peut s'écouler un certain temps avant que la capture d'écran ne soit refaite.", + "DeleteRecordedPageview": "Supprimer cette vue de la page enregistrée (aucune confirmation demandée)", + "DeleteRecordedSession": "Supprimer cette session enregistrée (aucune confirmation n'est demandée)", + "DeleteScreenshot": "Supprimer la capture d'écran", + "DeleteSessionRecordingConfirm": "Êtes-vous sûr de vouloir supprimer cet enregistrement de session ? Les enregistrements générés précédemment ne seront plus disponibles dans l'interface utilisateur des rapports.", + "DeleteX": "Supprimer %s", + "DeviceType": "Type d'appareil", + "DisableHeatmapRecordingDescription": "En désactivant cette fonction, aucune carte de chaleur ne peut plus être configurée ou enregistrée.", + "DisableHeatmapRecordingInlineHelp": "%1$sRemarque : la désactivation de cette fonction entraîne la suppression de toutes les cartes de chaleur, y compris les cartes de chaleur précédemment suivies. Aucun changement ne sera apporté à l'enregistrement de la session si vous activez ou désactivez cette fonctionnalité.%2$s", + "DisableHeatmapRecordingTitle": "Désactiver les cartes de chaleur", + "DisableSessionRecordingDescription": "En désactivant cette fonctionnalité, aucun enregistrement de session ne peut plus être configuré ou suivi.", + "DisableSessionRecordingInlineHelp": "%1$sRemarque : la désactivation de cette fonctionnalité supprimera tous les enregistrements de session, y compris les enregistrements de session précédemment suivis. Il n'y aura aucun changement pour les cartes de chaleur si vous activez ou désactivez cette fonction.%2$s", + "DisableSessionRecordingTitle": "Désactiver l'enregistrement des sessions", + "EditHeatmapX": "Modifier la carte de chaleur %s", + "EditSessionRecordingX": "Modifier l'enregistrement de la session %s", + "EditX": "Modifier %s", + "EnableIncludeCountriesDescription": "Par défaut, toutes les visites sont enregistrées. Activez cette section si vous souhaitez suivre les visites provenant de certains pays.", + "EnableIncludeCountriesTitle": "Ne suivre que les visiteurs provenant de certains pays", + "EndHeatmapConfirm": "Êtes-vous sûr de vouloir arrêter de mesurer des activités pour cette carte de chaleur ?", + "EndSessionRecordingConfirm": "Êtes-vous sûr de vouloir arrêter d'enregistrer de nouvelles sessions ?", + "ErrorArrayMissingKey": "Clé de tableau manquante \"%1$s\" dans \"%2$s\" à la position \"%3$s\".", + "ErrorArrayMissingValue": "Valeur manquante pour la clé du tableau \"%1$s\" dans \"%2$s\" à la position \"%3$s\".", + "ErrorHeatmapDoesNotExist": "La carte de chaleur demandée n'existe pas", + "ErrorHeatmapNameDuplicate": "Le nom de la carte de chaleur est déjà utilisé par une autre carte.", + "ErrorHeatmapRecordingDisabled": "La fonction d'enregistrement des cartes de chaleur est désactivée. Pour utiliser cette fonction, elle doit être activée par un super utilisateur Matomo dans les paramètres généraux.", + "ErrorInnerIsNotAnArray": "Chaque \"%1$s\" dans \"%2$s\" doit être un tableau.", + "ErrorInvalidRegExp": "L'expression régulière \"%1$s\" n'a pas un format valide.", + "ErrorNotAnArray": "\"%1$s\" doit être un tableau.", + "ErrorPageRuleRequired": "Au moins une règle de page doit être définie.", + "ErrorSessionRecordingDisabled": "La fonction d'enregistrement de session est désactivée. Pour utiliser cette fonction, elle doit être activée par un super utilisateur Matomo dans les paramètres généraux.", + "ErrorSessionRecordingDoesNotExist": "L'enregistrement de session demandé n'existe pas", + "ErrorXContainsWhitespace": "Le \"%1$s\" n'est pas autorisé car ne doit pas contenir d'espace blanc.", + "ErrorXNotANumber": "\"%1$s\" doit être un nombre.", + "ErrorXNotProvided": "Veuillez indiquer une valeur pour \"%1$s\".", + "ErrorXNotWhitelisted": "La valeur pour \"%1$s\" n'est pas autorisée, utilisez une de celles-ci : %2$s.", + "ErrorXTooHigh": "\"%1$s\" est trop élevé, la valeur maximale autorisée est %2$s.", + "ErrorXTooLong": "\"%1$s\" est trop long, %2$s caractères maximum sont autorisés.", + "ErrorXTooLow": "\"%1$s\" est trop faible, la valeur minimale autorisée est %2$s.", + "ExcludedElements": "Éléments exclus", + "ExcludedElementsHelp": "Vous pouvez éventuellement définir des sélecteurs CSS pour exclure certains éléments qui ne doivent pas être visibles dans l'aperçu de la carte de chaleur. Par exemple, une fenêtre contextuelle qui s'affiche lorsque la page cible est ouverte. Vous pouvez séparer plusieurs sélecteurs par une virgule.", + "FieldIncludedTargetsHelp": "Les cibles vous permettent de définir sur quelles pages les activités des utilisateurs doivent être enregistrées. Vous pouvez définir une ou plusieurs conditions. Par exemple, vous pouvez définir l'enregistrement des activités lorsque l'URL ou le chemin d'accès est égal à une certaine valeur et seulement si un certain paramètre d'URL est présent dans l'URL. Les activités ne seront enregistrées que si toutes les conditions sont remplies, et non si une seule d'entre elles l'est. Toutes les conditions sont évaluées sans tenir compte de la casse. Lorsque vous sélectionnez \"est égal à\", le protocole de l'URL, les paramètres de recherche et la barre oblique de fin de ligne sont ignorés.", + "FieldIncludedTargetsHelpSessions": "Les cibles vous permettent de configurer le démarrage de l'enregistrement d'une session uniquement lorsqu'un visiteur a atteint une certaine page. Cela vous permet par exemple de n'enregistrer que les sessions qui passent par le processus de paiement. Vous pouvez définir une ou plusieurs conditions. Par exemple, vous pouvez définir l'enregistrement d'une session uniquement lorsque l'URL ou le chemin d'accès est égal à une certaine valeur et uniquement si un certain paramètre d'URL est présent dans l'URL. Les sessions ne seront enregistrées que si toutes les conditions sont remplies lors de l'affichage d'une page, et non si une seule d'entre elles l'est. Toutes les conditions sont évaluées sans tenir compte de la casse. Lorsque vous sélectionnez \"est égal à\", le protocole de l'URL, les paramètres de recherche et la barre oblique de fin de ligne sont ignorés.", + "FieldNamePlaceholder": "ex 'Page d'inscription'", + "FilesystemDirectory": "répertoire", + "Filter": "Filtre", + "GettingStarted": "Démarrer", + "Heatmap": "Carte de chaleur", + "HeatmapAddedActivity": "a ajouté une carte thermique \"%1$s\" pour le site \"%2$s\"", + "HeatmapCreated": "La carte de chaleur a été créée avec succès.", + "HeatmapDeletedActivity": "a supprimé la carte de chaleur \"%1$s\" pour le site \"%2$s\"", + "HeatmapEndedActivity": "a terminé la carte de chaleur \"%1$s\" pour le site \"%2$s\"", + "HeatmapInfoTrackVisitsFromCountries": "La carte de chaleur est configurée pour ne suivre que les visites provenant de %1$s.", + "HeatmapNameHelp": "Définit le nom sous lequel le rapport de cette carte de chaleur sera disponible.", + "HeatmapPausedActivity": "a mis en pause la carte de chaleur \"%1$s\" pour le site \"%2$s\"", + "HeatmapResumedActivity": "a repris la carte de chaleur \"%1$s\" pour le site \"%2$s\"", + "HeatmapSampleLimit": "Nombre de pages vues", + "HeatmapSampleLimitHelp": "Définit le nombre de pages vues que vous souhaitez enregistrer au total.", + "HeatmapSampleRateHelp": "Également connu sous le nom de \"trafic\". Lorsque vous sélectionnez 100%%, tous les visiteurs qui se rendent sur la page cible sélectionnée seront enregistrés. Si vous sélectionnez par exemple 10 %%, seul un visiteur sur dix sera enregistré. Plus le pourcentage sélectionné est faible, plus il faudra de temps pour atteindre la limite d'échantillonnage choisie.", + "HeatmapScreenshotDeletedActivity": "a supprimé la capture d'écran de la carte de chaleur \"%1$s\" pour le site \"%2$s\"", + "HeatmapTroubleshoot": "Vous rencontrez des problèmes ? %1$sEn savoir plus%2$s.", + "HeatmapUpdated": "La carte de chaleur a été mise à jour avec succès.", + "HeatmapUpdatedActivity": "a mis à jour la carte de chaleur \"%1$s\" pour le site \"%2$s\"", + "HeatmapUsageBenefits": "Les cartes de chaleur vous permettent d'enregistrer tous les clics, mouvements de souris et activités de défilement de vos visiteurs sur une page donnée. Cela vous permet de savoir où les utilisateurs pensent que quelque chose est cliquable mais ne l'est pas, s'il y a des parties de la page qui sont à peine consultées ou avec lesquelles il y a peu d'interaction, ce que vos visiteurs recherchent, quelle partie de la page est visible lorsque les utilisateurs consultent votre page, et plus encore.", + "HeatmapWidth": "Largeur de la carte de chaleur", + "HeatmapX": "Carte de chaleur %s", + "HeatmapXRecordedSamplesSince": "%1$s échantillons ont été enregistrés depuis %2$s.", + "Heatmaps": "Cartes de chaleur", + "Manage": "Gérer", + "ManageHeatmapSubcategoryHelp": "Les cartes de chaleur vous permettent d'enregistrer tous les clics, mouvements de souris et activités de défilement de vos visiteurs sur une page spécifique. Cette section vous permet de créer et de gérer le suivi de cartes de chaleur pour votre site Web.", + "ManageHeatmaps": "Gérer les cartes de chaleur", + "ManageSessionRecordingSubcategoryHelp": "Les enregistrements de session vous permettent d'enregistrer toutes les activités d'un visiteur réel au cours de sa session (visite), telles que les clics, les mouvements de souris, les défilements, les redimensionnements de fenêtre, les changements de page et les interactions de formulaire. Cette section vous permet de créer et de gérer les enregistrements de session pour votre site.", + "ManageSessionRecordings": "Gérer les enregistrements des sessions", + "MatomoJSNotWritableErrorMessage": "HeatmapSessionRecording : %1$s ne peut actuellement pas suivre les visites, car le fichier matomo.js est en lecture seule. Veuillez consulter la %2$sdocumentation%3$s pour savoir comment rendre le fichier modifiable.", + "MinSessionTime": "Durée minimale de la visite", + "MinSessionTimeHelp": "Une session ne sera enregistrée que lorsqu'un visiteur aura passé au moins le temps spécifié sur une page.", + "MoveRate": "Taux de mouvements :", + "Moves": "Mouvements :", + "NHeatmaps": "%s cartes de chaleur", + "NSessionRecordings": "%s enregistrements de sessions", + "NoHeatmapSamplesRecordedYet": "Aucune page vue n'a encore été enregistrée pour cette carte de chaleur. Si des enregistrements sont censés avoir lieu à l'heure actuelle, il se peut que les pages cibles configurées pour cette carte de chaleur ne correspondent à aucune page de votre site Web. Il est également recommandé de vérifier la \"Vérification du système\" sous \"Administration\" (uniquement pour les super utilisateurs) pour voir si votre système est configuré pour suivre automatiquement les cartes de chaleur.", + "NoHeatmapSamplesRecordedYetWithoutSystemConfiguration": "Aucune vue de page n'a encore été enregistrée pour cette carte de chaleur. S'il devait y avoir des enregistrements à ce stade, il est possible que les pages configurées ciblées pour cette carte de chaleur ne correspondent à aucune page de votre site web.", + "NoHeatmapScreenshotRecordedYet": "Il y a eu %1$s échantillons enregistrés jusqu'à présent. Cependant, aucune capture d'écran n'a encore été effectuée. S'il y a un \"%2$s\" pour cette carte de chaleur, la capture d'écran peut prendre un certain temps avant d'être disponible car l'utilisateur doit d'abord ouvrir cette URL de capture d'écran. Selon le taux d'échantillonnage, cela peut prendre un certain temps.", + "NoHeatmapsConfiguredInfo": "Il n'y a actuellement aucune carte de chaleur active. Pour afficher les cartes de chaleur, veuillez demander à un utilisateur ayant au moins un accès administrateur de créer une nouvelle carte de chaleur.", + "NoHeatmapsFound": "Pas de carte de chaleur trouvée", + "NoSessionRecordedYet": "Aucune session n'a encore été enregistrée. Si des sessions sont censées être déjà enregistrées, il se peut que la page d'entrée configurée ne corresponde à aucune page de votre site Web. Il est également recommandé de vérifier la \"Vérification du système\" sous \"Administration\" (uniquement pour les super utilisateurs) pour voir si votre système est configuré pour enregistrer automatiquement les sessions.", + "NoSessionRecordedYetWithoutSystemConfiguration": "Aucune session n'a encore été enregistrée. S'il devait y avoir des sessions enregistrées à ce stade, il est possible que la page d'entrée configurée ne corresponde à aucune page de votre site web.", + "NoSessionRecordingsConfiguredInfo": "Il n'y a actuellement aucun enregistrement de session actif. Pour enregistrer de nouvelles sessions, veuillez demander à un utilisateur ayant au moins un accès administrateur de créer un nouvel enregistrement.", + "NoSessionRecordingsFound": "Aucun enregistrement de session trouvé", + "NotSupportedBrowser": "Ce navigateur n'est pas pris en charge. Veuillez utiliser une version plus récente ou essayer un autre navigateur.", + "OnePageview": "1 page vue", + "PageRule": "Règle de la page", + "PageviewXofY": "Page vue %1$s de %2$s", + "PageviewsInVisit": "Pages vues enregistrées au cours de cette session", + "PauseReason": "Le %1$s a été mis en pause en raison de la consommation de votre quota, veuillez contacter le support pour plus de détails.", + "PeriodDisabledErrorMessage": "La période \"%1$s\" est désactivée mais est nécessaire pour que cette fonctionnalité marche. Sélectionnez une autre période ou demandez à un administrateur système de modifier le fichier de configuration 'config/config.ini.php' de Matomo pour autoriser la période \"%1$s\" dans le paramètre \"enabled_periods_API\".", + "PersonalInformationNote": "Veuillez noter qu'un %1$s peut enregistrer des informations personnelles ou sensibles. Si vous souhaitez masquer certains contenus sur votre site Web ou votre application, vous pouvez marquer ces contenus en ajoutant un attribut %2$sdata-matomo-mask%3$s au balisage HTML. %4$sEn savoir plus%5$s", + "PlayRecordedSession": "Lire cette session enregistrée", + "PlayerDurationXofY": "%1$s de %2$s", + "PlayerForwardFast": "Passer %1$s secondes (raccourci %2$s)", + "PlayerPageViewNext": "Lire la page suivante de ce visiteur (%1$s) (raccourci %2$s)", + "PlayerPageViewPrevious": "Lire la page précédente de ce visiteur %1$s (raccourci %2$s)", + "PlayerPause": "Pause (raccourci %s ou espace)", + "PlayerPlay": "Lire (raccourci %s ou espace)", + "PlayerReplay": "Relecture (raccourci %s ou espace)", + "PlayerRewindFast": "Rembobiner %1$s secondes (raccourci %2$s)", + "RecordedHeatmapDocStatusActive": "Cette carte de chaleur est active. Jusqu'à %1$d pages vues seront enregistrées avec un taux d'échantillonnage de %2$s.", + "RecordedHeatmapDocStatusEnded": "Cette carte de chaleur est terminée. Aucune nouvelle activité ne sera enregistrée.", + "RecordedPageviewDeletedActivity": "a supprimé une vue de page enregistrée pour l'enregistrement de session \"%1$s\" pour le site \"%2$s\"", + "RecordedSessionDeletedActivity": "a supprimé un enregistrement de session pour l'enregistrement de session \"%1$s\" pour le site \"%2$s\"", + "RecordedSessions": "Sessions enregistrées", + "RecordedSessionsDocStatusActive": "L'enregistrement de cette session est actif. Jusqu'à %1$d sessions seront enregistrées avec une fréquence d'échantillonnage de %2$s.", + "RecordedSessionsDocStatusEnded": "L'enregistrement de cette session est terminé et aucune nouvelle session ne sera enregistrée.", + "Recordings": "Enregistrements", + "ReplayRecordedSession": "Regarder à nouveau l'enregistrement de session", + "ReplayX": "Regarder à nouveau %s", + "ReportRecordedSessionsDocumentation": "Une liste de toutes les sessions qui ont été enregistrées. Vous pouvez revisionner n'importe quelle session enregistrée.", + "RequiresActivity": "Requiert une activité", + "RequiresActivityHelp": "Si cette option est activée, seules les sessions qui présentent une activité de défilement et de clic dans une vue de page seront enregistrées. Cela permet d'éviter l'enregistrement de sessions avec peu d'activité.", + "Rule": "Règle", + "SampleLimit": "Limite d'échantillonnage", + "SampleRate": "Taux d'échantillonnage", + "ScreenshotUrl": "URL de la capture d'écran", + "ScreenshotUrlHelp": "Par défaut, une capture d'écran est réalisée lorsque le premier visiteur visualise la page cible. Si différentes URLs de pages correspondent à votre page cible, vous pouvez spécifier une URL spécifique qui doit être utilisée pour capturer l'écran. La visualisation de la carte de chaleur ne sera disponible que lorsqu'au moins un visiteur aura visité cette URL dont les activités sont enregistrées. Si elle est définie, l'URL doit correspondre exactement. Pour ignorer le protocole, commencez l'URL par deux doubles barres obliques (//exemple.com). Veuillez noter que cette URL ne peut pas être modifiée dès qu'une capture d'écran a été réalisée.", + "SessionNameHelp": "Définit le nom sous lequel les enregistrements de la session seront disponibles.", + "SessionRecording": "Enregistrement de sessions", + "SessionRecordingAddedActivity": "a ajouté un enregistrement de session \"%1$s\" pour le site \"%2$s\"", + "SessionRecordingCreated": "L'enregistrement de la session a été créé avec succès.", + "SessionRecordingDeletedActivity": "a supprimé l'enregistrement de session \"%1$s\" pour le site %2$s\"", + "SessionRecordingEndedActivity": "a terminé l'enregistrement de session \"%1$s\" pour le site \"%2$s\"", + "SessionRecordingInfoTrackVisitsFromCountries": "L'enregistrement de session est configuré pour ne suivre que les visites provenant de %1$s.", + "SessionRecordingPausedActivity": "a mis en pause l'enregistrement de session \"%1$s\" pour le site \"%2$s\"", + "SessionRecordingResumedActivity": "a repris l'enregistrement de session \"%1$s\" pour le site \"%2$s\"", + "SessionRecordingUpdated": "L'enregistrement de la session a été mis à jour avec succès.", + "SessionRecordingUpdatedActivity": "a mis à jour l'enregistrement de session \"%1$s\" pour le site \"%2$s\"", + "SessionRecordingX": "Enregistrement de session %s", + "SessionRecordings": "Enregistrements des sessions", + "SessionRecordingsUsageBenefits": "Les enregistrements de session vous permettent d'enregistrer toutes les activités d'un visiteur réel au cours de sa session (visite), telles que les clics, les mouvements de souris, les défilements, les redimensionnements de fenêtre, les changements de page et les interactions de formulaire. Vous pouvez ensuite rejouer ces interactions pour voir exactement comment un visiteur a interagi avec votre site Web. De cette façon, vous comprenez leurs attentes, les problèmes qu'ils peuvent rencontrer, leurs habitudes d'utilisation, etc.", + "SessionSampleLimit": "Nombre de sessions", + "SessionSampleLimitHelp": "Définit le nombre de sessions que vous souhaitez enregistrer au total.", + "SessionSampleRateHelp": "Également connu sous le nom de \"trafic\". Si vous sélectionnez 100%%, toutes les sessions seront enregistrées dès qu'elles auront atteint la page cible. Si vous sélectionnez par exemple 10 %%, seule une session sur dix sera enregistrée. Plus le pourcentage sélectionné est faible, plus il faudra de temps pour atteindre la limite d'échantillonnage choisie.", + "StatusActive": "Active", + "StatusEnded": "Terminée", + "StatusPaused": "En pause", + "StopX": "Arrêtez d'enregistrer toute nouvelle activité pour ce %s.", + "TargetAttributePath": "Chemin", + "TargetAttributeUrl": "URL", + "TargetAttributeUrlParameter": "Paramètre d'URL", + "TargetAttributeUrlParameterExample": "nameOfUrlParameter", + "TargetPage": "Page cible", + "TargetPageTestErrorInvalidUrl": "Entrez une URL incluant un protocole.", + "TargetPageTestLabel": "Saisissez une URL complète, y compris le protocole, pour vérifier si les activités seront enregistrées sur cette URL :", + "TargetPageTestTitle": "Validation de l'URL", + "TargetPageTestUrlMatches": "Les activités seront enregistrées pour cette URL", + "TargetPageTestUrlNotMatches": "Les activités ne seront pas enregistrées pour cette URL", + "TargetPages": "Pages d'entrée", + "TargetTypeContains": "contient", + "TargetTypeEqualsExactly": "strictement égal", + "TargetTypeEqualsExactlyInfo": "La valeur doit correspondre exactement, y compris le protocole URL, la requête de recherche et le hachage.", + "TargetTypeEqualsSimple": "est égal à", + "TargetTypeEqualsSimpleInfo": "L'URL correspondra à n'importe quel protocole (par exemple http et https) avec ou sans le sous-domaine \"www.\". Tout slash de fin de chemin ainsi que la requête de recherche et la partie hash de l'URL seront ignorés lors de la recherche de l'URL.", + "TargetTypeExists": "existe", + "TargetTypeIsAny": "n'importe quel", + "TargetTypeIsNot": "n'est pas %s", + "TargetTypeRegExp": "correspond à l'expression régulière", + "TargetTypeRegExpInfo": "Toute expression régulière, par exemple \"^(.*)test(.*)$\".", + "TargetTypeStartsWith": "commence par", + "TimeOnSite": "Temps passé sur le site web", + "TotalEvents": "Événements", + "TrackingDisabledDefaultSettingDescription": "Cette fonctionnalité peut être utile si votre Matomo possède plusieurs sites et que vous souhaitez utiliser la fonction uniquement sur quelques sites spécifiques. Lorsque vous désactivez le suivi par défaut, la fonction d'enregistrement des cartes de chaleur et des sessions ne s'exécutera sur aucun site, sauf si vous l'activez spécifiquement dans le cadre de votre code de suivi, par exemple en appelant '_paq.push(['HeatmapSessionRecording::enable']);'. Cela permet d'éviter des requêtes réseau inutiles vers le fichier 'configs.php' pour déterminer si une carte de chaleur ou un enregistrement de session est configuré pour le site actuel. Cela peut également être utile pour des raisons de confidentialité si vous voulez vous assurer que la fonctionnalité ne sera utilisée que sur certains sites.", + "TrackingDisabledDefaultSettingTitle": "Désactiver le suivi par défaut", + "UpdatingData": "Mise à jour des données…", + "UrlParameterValueToMatchPlaceholder": "Valeur à faire correspondre au nom du paramètre de l'URL", + "UrlXDoesNotLookLikeUrl": "Le %s ne ressemble pas à une URL. Assurez-vous qu'il contient un protocole comme http:// ou qu'il commence par //", + "ViewReport": "Voir le rapport", + "ViewportResolution": "Résolution de la fenêtre de visualisation (largeur x hauteur)", + "Width": "Largeur", + "XSamples": "%s échantillons", + "disable": "désactivé", + "enable": "activé" + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/hi.json b/files/plugin-HeatmapSessionRecording-5.2.4/lang/hi.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/lang/hi.json @@ -0,0 +1 @@ +{} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/it.json b/files/plugin-HeatmapSessionRecording-5.2.4/lang/it.json new file mode 100644 index 0000000..960baeb --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/lang/it.json @@ -0,0 +1,190 @@ +{ + "HeatmapSessionRecording": { + "ActivityClick": "Clicca", + "ActivityFormChange": "Cambio Form", + "ActivityFormText": "Cambio Testo Form", + "ActivityFormValue": "Cambio Valore Form", + "ActivityInitialDom": "Pagina Iniziale", + "ActivityMove": "Sposta", + "ActivityPageChange": "Cambia all'interno della pagina", + "ActivityResize": "Ridimensiona", + "ActivityScroll": "Scorri", + "ActivityScrollElement": "Elemento Scroll", + "AdvancedOptions": "Opzioni avanzate", + "AutoPlayNextPageview": "Clicca per %1$s riprodurre automaticamente la successiva visualizzazione di pagina (scorciatoia %2$s)", + "AvgAboveFoldDescription": "In media i visitatori vedono il contenuto sopra questa riga senza dover scorrere", + "AvgAboveFoldTitle": "Media Parte Alta (Above Fold) %spx", + "BreakpointGeneralHelp": "Il tipo di dispositivo viene rilevato automaticamente quando un visitatore visualizza il tuo sito web. Tuttavia, a volte potrebbe non essere possibile rilevarlo. Se il tuo sito web è responsive e il tuo layout si interrompe a un certo punto, metteremo gli utenti desktop con una piccola risoluzione in questo tipo di dispositivo per heatmap più accurate. Qualsiasi larghezza inferiore a questo valore verrà inserita in questo tipo di dispositivo. Impostalo su zero se il tuo sito web non è responsive.", + "BreakpointGeneralHelpManage": "Un utente con accesso Super User può modificare i valori predefiniti per questi breakpoints in Amministrazione => Impostazioni generali.", + "BreakpointX": "Breakpoint %s", + "CaptureKeystrokes": "Cattura sequenze di tasti", + "CaptureKeystrokesHelp": "Se abilitato, qualsiasi testo inserito nei campi del modulo di testo viene registrato. Mentre il testo viene registrato, ogni carattere inserito da un utente viene sostituito da un asterisco (*). È possibile autorizzare la registrazione di determinati campi di testo semplice mediante la specifica di un attributo \"data-matomo-unmask\" in un campo del form. Tuttavia, i campi password e molti altri campi che potrebbero potenzialmente includere informazioni personali (indirizzo, e-mail, dati della carta di credito, nome utente, numero di telefono, ...) saranno sempre mascherati automaticamente se rilevati come tali (%1$sulteriori informazioni%2$s). Si noti che quando si attiva questa funzione, è possibile registrare i dati personali che possono essere rilevanti per il GDPR.", + "ChangeReplaySpeed": "Cambia velocità di riproduzione (scorciatoia %s)", + "ClickToSkipPauses": "Clicca per %1$s saltare le pause (scorciatoia %2$s)", + "ColumnActionsDocumentation": "Riproduci la sessione registrata o elimina definitivamente la registrazione.", + "ColumnBrowserDocumentation": "Browser che il visitatore stava utilizzando quando è stata registrata questa sessione.", + "ColumnDeviceDocumentation": "Dispositivo che il visitatore stava utilizzando quando è stata registrata questa sessione.", + "ColumnLabelRecordedSessions": "URL Ingresso → URL Uscita", + "ColumnLocationDocumentation": "Posizione del visitatore quando è stata registrata questa sessione.", + "ColumnOperatingSystem": "SO", + "ColumnOperatingSystemDocumentation": "Sistema operativo che il visitatore stava utilizzando quando è stata registrata questa sessione.", + "ColumnPageviewsDocumentation": "Numero di visualizzazioni di pagina registrate durante questa sessione.", + "ColumnResolutionDocumentation": "Numero di pixel della finestra visibile del browser quando l'utente ha aperto questa pagina.", + "ColumnTime": "Ora", + "ColumnTimeDocumentation": "Ora della registrazione nel fuso orario del sito.", + "ColumnTimeOnPageDocumentation": "Quanto tempo ha trascorso il visitatore in questa pagina durante la registrazione della sessione", + "ColumnTimeOnSiteDocumentation": "Quanto tempo ha trascorso il visitatore in totale durante la registrazione della sessione", + "ConfigsInternetDisabled": "Non è stato possibile verificare se \"%s\" è accessibile perché la connessione Internet è disabilitata su questo Matomo.", + "ConfigsPhpErrorResult": "Di conseguenza, il tracciamento delle Heatmaps e delle Registrazioni di Sessione potrebbe non funzionare. Potrebbe essere necessario modificare la configurazione del server per consentire l'accesso a questo file tramite Internet o Intranet.", + "ConfigsPhpManualCheck": "Si prega di aprire l'URL manualmente in un browser per vedere se la risposta contiene \"Piwik.HeatmapSessionRecording\". In caso contrario, potrebbe essere necessario modificare la configurazione del server in quanto questo file deve essere accessibile tramite un browser da Internet o Intranet.", + "ConfigsPhpNotAccessible": "L'URL \"%s\" sembra non essere accessibile da Internet o Intranet.", + "ConfigsPhpSelfSignedError": "La richiesta \"%s\" ha provocato un errore SSL. Forse stai usando un certificato \"self-signed\"?", + "ConfigsPhpSuccess": "\"%s\" è accessibile", + "ConfigsPhpUnknown": "Non è stato possibile verificare se \"%s\" è accessibile su Internet o Intranet..", + "ConfigsPhpUnknownError": "La richiesta \"%1$s\" ha provocato un errore: %2$s.", + "CreateNewHeatmap": "Crea nuova heatmap", + "CreateNewSessionRecording": "Crea nuova registrazione di sessione", + "CreationDate": "Data Creazione", + "DeleteHeatmapConfirm": "Sei sicuro di voler eliminare questa heatmap? I report sulle heatmap generati in precedenza non saranno più disponibili nell'interfaccia utente dei rapporti.", + "DeleteHeatmapScreenshotConfirm": "Sei sicuro di voler eliminare questo screenshot? Potrebbe essere necessario un po 'di tempo prima che lo screenshot venga ripreso.", + "DeleteRecordedPageview": "Elimina questa visualizzazione di pagina registrata (non è richiesta conferma)", + "DeleteRecordedSession": "Elimina questa sessione registrata (non è richiesta conferma)", + "DeleteScreenshot": "Elimina Screenshot", + "DeleteSessionRecordingConfirm": "Sei sicuro di voler eliminare questa registrazione di sessione? Le registrazioni precedentemente generate non saranno più disponibili nell'interfaccia utente dei rapporti.", + "DeleteX": "Cancella %s", + "EditHeatmapX": "Modifica Heatmap %s", + "EditSessionRecordingX": "Modifica Registrazione di Sessione %s", + "EditX": "Modifica %s", + "EndHeatmapConfirm": "Sei sicuro di voler arrestare la cattura di ogni attività per questa heatmap?", + "EndSessionRecordingConfirm": "Sei sicuro di voler arrestate la registrazione delle nuove sessioni?", + "ErrorArrayMissingKey": "Chiave array mancante \"%1$s\" in \"%2$s\" nella posizione \"%3$s\".", + "ErrorArrayMissingValue": "Valore chiave array \"%1$s\" mancante in \"%2$s\" nella posizione \"%3$s\".", + "ErrorHeatmapDoesNotExist": "La heatmap richiesta non esiste", + "ErrorHeatmapNameDuplicate": "Questo nome di heatmap è già utilizzato da un'altra heatmap.", + "ErrorInnerIsNotAnArray": "Ciascun \"%1$s\" in \"%2$s\" deve essere un array.", + "ErrorInvalidRegExp": "L'espressione regolare \"%1$s\" non è in un formato valido.", + "ErrorNotAnArray": "\"%1$s\" deve essere un array.", + "ErrorPageRuleRequired": "Deve essere impostata almeno una regola di pagina", + "ErrorSessionRecordingDoesNotExist": "La registrazione di sessione richiesta non esiste", + "ErrorXContainsWhitespace": "\"%1$s\" non può contenere spazi vuoti.", + "ErrorXNotANumber": "\"%1$s\" deve essere un numero.", + "ErrorXNotProvided": "Si prega di assegnare un valore per \"%1$s\".", + "ErrorXNotWhitelisted": "Il valore per \"%1$s\" non è consentito, usane uno tra: %2$s.", + "ErrorXTooHigh": "\"%1$s\" è troppo alto, il valore massimo consentito è %2$s.", + "ErrorXTooLong": "\"%1$s\" è troppo lungo, è permesso un massimo di %2$s caratteri.", + "ErrorXTooLow": "\"%1$s\" è troppo piccolo, il valore minimo consentito è %2$s.", + "ExcludedElements": "Elementi Esclusi", + "ExcludedElementsHelp": "Puoi opzionalmente definire dei selettori CSS per escludere determinati elementi che non devono essere visibili nell'anteprima della heatmap. Ad esempio, un popup che viene mostrato quando viene aperta la pagina di destinazione. È possibile separare più selettori con una virgola.", + "FieldIncludedTargetsHelp": "I target consentono di definire su quali pagine devono essere registrate le attività dell'utente. È possibile definire una o più condizioni. Ad esempio, è possibile stabilire di registrare le attività ogni volta che l'URL o il percorso è uguale a un determinato valore e solo se è presente. un determinato parametro URL. Le attività verranno registrate solo quando tutte le condizioni sono soddisfatte, non se ne viene soddisfatta solo una. Tutte le condizioni saranno valutate senza distinzione tra maiuscole e minuscole. Quando selezioni \"uguale semplice\", il protocollo URL, i parametri di ricerca e una barra finale verranno ignorati.", + "FieldIncludedTargetsHelpSessions": "I target consentono di configurare solo l'avvio della registrazione di una sessione non appena un visitatore ha raggiunto una determinata pagina. Questo ti consente, ad esempio, di registrare solo le sessioni che arrivano al processo di checkout. È possibile definire una o più condizioni. Ad esempio, è possibile stabilire di registrare una sessione solo quando l'URL o il percorso sono uguali a un determinato valore e solo se è presente un determinato parametro URL. Le sessioni verranno registrate solo quando tutte le condizioni sono soddisfatte in una visualizzazione di pagina, non se viene soddisfatta solo una di esse. Tutte le condizioni saranno valutate senza distinzione tra maiuscole e minuscole. Quando selezioni \"uguale semplice\", il protocollo URL, i parametri di ricerca e una barra finale verranno ignorati.", + "FieldNamePlaceholder": "es. \"Pagina di registrazione\"", + "FilesystemDirectory": "directory", + "Filter": "Filtra", + "GettingStarted": "Cominciamo", + "Heatmap": "Heatmap", + "HeatmapCreated": "La heatmap è stata creata con successo.", + "HeatmapNameHelp": "Definisce il nome con cui sarà disponibile il report per questa heatmap.", + "HeatmapSampleLimit": "Numero di visualizzazioni di pagina", + "HeatmapSampleLimitHelp": "Definisce il numero totale di visualizzazioni di pagina che vuoi registrare.", + "HeatmapSampleRateHelp": "Conosciuto anche come \"traffico\". Quando selezioni 100%%, verranno registrati tutti i visitatori che visitano la pagina di destinazione impostata. Quando selezioni, per esempio, 10%%, verrà registrato solo un visitatore ogni 10. Più bassa è la percentuale selezionata, più tempo ci vorrà per raggiungere il limite di campionamento selezionato.", + "HeatmapUpdated": "La heatmap è stata aggiornata con successo.", + "HeatmapUsageBenefits": "Le heatmaps, o \"mappe di calore\", consentono di registrare tutti i click, i movimenti del mouse e le attività di scroll dei visitatori su una determinata pagina. Questo ti aiuta a scoprire dove gli utenti pensano che qualcosa sia cliccabile ma non lo è, se ci sono parti della pagina che vengono visualizzate o interagiscono a malapena, cosa cercano i tuoi visitatori, quanta parte della pagina è visibile quando gli utenti la visualizzano e altro.", + "HeatmapWidth": "Larghezza heatmap", + "HeatmapX": "Heatmap %s", + "HeatmapXRecordedSamplesSince": "%1$s campioni sono stati registrati dal %2$s.", + "Heatmaps": "Heatmaps", + "Manage": "Gestione", + "ManageHeatmapSubcategoryHelp": "Le heatmaps ti consentono di registrare tutti i click, i movimenti del mouse e le attività di scorrimento dei tuoi visitatori su una pagina specifica. Questa sezione ti consente di creare e di gestire il tracking Heatmap per il tuo sito web.", + "ManageHeatmaps": "Gestisci Heatmaps", + "ManageSessionRecordingSubcategoryHelp": "Le registrazioni delle sessioni ti consentono di registrare tutte le attività di un visitatore reale durante la sua sessione (visita) come click, movimenti del mouse, scroll, ridimensionamenti delle finestre, cambi di pagina e interazioni del modulo. Questa sezione ti consente di creare e gestire le registrazioni delle sessioni per il tuo sito.", + "ManageSessionRecordings": "Gestione Registrazioni di Sessione", + "MinSessionTime": "Tempo Minimo per Sessione", + "MinSessionTimeHelp": "Una sessione verrà registrata solo quando un visitatore ha trascorso almeno il tempo specificato su una pagina.", + "NHeatmaps": "%s heatmaps", + "NSessionRecordings": "%s registrazioni di sessione", + "NoHeatmapSamplesRecordedYet": "Nessuna visualizzazione di pagina è stata ancora registrata per questa heatmap. Se si suppone che ci dovrebbero essere delle registrazioni, forse le pagine obiettivo configurate per questa heatmap non corrispondono a nessuna pagina sul tuo sito web. Si consiglia inoltre di verificare \"Controllo Sistema\" in \"Amministrazione\" (solo Super Users) per vedere se il sistema è configurato per monitorare automaticamente le Heatmap.", + "NoHeatmapScreenshotRecordedYet": "Finora sono stati registrati %1$s campioni. Tuttavia, nessuno screenshot è stato ancora preso. Se c'è un \"%2$s\" per questa heatmap, potrebbe volerci un po' prima che lo screenshot diventi disponibile, poiché un utente deve prima aprire l'URL di questo screenshot. A seconda della frequenza di campionamento, ciò potrebbe richiedere del tempo.", + "NoHeatmapsConfiguredInfo": "Non ci sono al momento heatmap attive. Per visualizzare le heatmap, chiedi a un utente con almeno l'accesso come amministratore di crearne una nuova.", + "NoHeatmapsFound": "Non sono state trovate heatmaps", + "NoSessionRecordedYet": "Nessuna sessione è stata ancora registrata. Se in questo momento dovrebbero essere state registrate delle sessioni, forse la pagina di ingresso configurata non corrisponde a nessuna pagina sul tuo sito web. Si consiglia inoltre di verificare \"Controllo del Sistema\" in \"Amministrazione\" (solo Super Users) per vedere se il sistema è configurato per registrare automaticamente le sessioni.", + "NoSessionRecordingsConfiguredInfo": "Non ci sono attualmente registrazioni di sessioni attive. Per registrare nuove sessioni, chiedi a un utente che abbia almeno l'accesso come amministratore di creare una nuova registrazione.", + "NoSessionRecordingsFound": "Non sono state trovate registrazioni di sessione", + "NotSupportedBrowser": "Questo browser non è supportato, si prega di utilizzare una versione successiva o di provare un browser diverso.", + "OnePageview": "1 visualizzazione di pagina", + "PageRule": "Regola Pagina", + "PageviewXofY": "Vista pagina %1$s di %2$s", + "PageviewsInVisit": "Visualizzazioni di pagina registrate in questa sessione", + "PersonalInformationNote": "Si noti che %1$s può registrare informazioni personali o sensibili. Se desideri nascondere determinati contenuti sul tuo sito web o app, puoi taggare questo contenuto aggiungendo un attributo %2$sdata-matomo-mask%3$s al codice HTML. %4$sPer saperne di più%5$s", + "PlayRecordedSession": "Riproduci questa sessione registrata", + "PlayerDurationXofY": "%1$s di %2$s", + "PlayerForwardFast": "Salta %1$s secondi (scorciatoia %2$s)", + "PlayerPageViewNext": "Riproduci la successiva visualizzazione di pagina (%1$s) (scorciatoia %2$s)", + "PlayerPageViewPrevious": "Riproduci le precedenti visualizzazioni di pagina di questo visitatore %1$s (scorciatoia %2$s)", + "PlayerPause": "Pausa (scorciatoia %s o spazio)", + "PlayerPlay": "Riproduci (scorciatoia %s o spazio)", + "PlayerReplay": "Ripeti (scorciatoia %s o spazio)", + "PlayerRewindFast": "Indietro di %1$s secondi (scorciatoia %2$s)", + "RecordedHeatmapDocStatusActive": "Questa heatmap è attiva. Verranno registrate fino a %1$d visualizzazioni di pagina con una frequenza di campionamento di %2$s.", + "RecordedHeatmapDocStatusEnded": "Questa registrazione di sessione è terminata. Non verranno registrate nuove attività.", + "RecordedSessions": "Sessioni Registrate", + "RecordedSessionsDocStatusActive": "Questa registrazione di sessione è attiva. Verranno registrate fino a %1$d sessioni, con una frequenza di campionamento di %2$s.", + "RecordedSessionsDocStatusEnded": "Questa registrazione di sessione è terminata e non verranno registrate nuove sessioni.", + "Recordings": "Registrazioni", + "ReplayRecordedSession": "Riproduci sessione registrata", + "ReplayX": "Riproduci %s", + "ReportRecordedSessionsDocumentation": "Elenco di tutte le sessioni che sono state registrate. Puoi ripetere qualsiasi sessione registrata.", + "RequiresActivity": "Richiede attività", + "RequiresActivityHelp": "Se abilitato, verranno registrate solo le sessioni che hanno un'attività di scorrimento e di click in una visualizzazione di pagina. Ciò impedisce la registrazione di sessioni con poca attività.", + "Rule": "Regola", + "SampleLimit": "Limite di Campionamnto", + "SampleRate": "Frequenza di Campionamento", + "ScreenshotUrl": "Screenshot URL", + "ScreenshotUrlHelp": "Per impostazione predefinita, viene eseguito uno screenshot quando il primo visitatore visualizza la pagina di destinazione. Se diversi URL di pagina corrispondono alla tua pagina di destinazione, puoi impostare un URL specifico affinché sia usato per catturare lo screenshot. La visualizzazione della heatmap sarà disponibile solo quando almeno un visitatore ha visitato questo URL, le cui attività sono registrate. Se definito, l'URL deve corrispondere in maniera esatta. Per ignorare il protocollo, avvia l'URL con due doppie barre (//example.com). Si prega di notare che questo URL non può essere modificato dal momento in cui viene preso uno screenshot.", + "SessionNameHelp": "Definisce il nome con cui saranno disponibili le registrazioni della sessione.", + "SessionRecording": "Registrazione di Sessione", + "SessionRecordingCreated": "La registrazione di sessione è stata creata con successo.", + "SessionRecordingUpdated": "La registrazione di sessione è stata aggiornata con successo.", + "SessionRecordingX": "Registrazione di Sessione %s", + "SessionRecordings": "Registrazioni di Sessione", + "SessionRecordingsUsageBenefits": "Le registrazioni di sessione consentono di registrare tutte le attività di un visitatore reale durante la propria sessione (visita) quali click, movimenti del mouse, scroll, ridimensionamenti della finestra, modifiche della pagina e interazioni tra moduli. È quindi possibile ripetere queste interazioni per vedere esattamente come un visitatore ha interagito con il tuo sito web. In questo modo puoi capire le aspettative, i problemi, gli schemi di utilizzo e altro ancora.", + "SessionSampleLimit": "Numero di sessioni", + "SessionSampleLimitHelp": "Definisce il numero totale di sessioni che vuoi registrare.", + "SessionSampleRateHelp": "Conosciuto anche come \"traffico\". Quando selezioni 100%%, verranno registrate tutte le sessioni non appena avranno raggiunto la pagina di destinazione. Quando selezioni, per esempio, 10%%, verrà registrata solo una sessione ogni 10. Più bassa è la percentuale selezionata, più tempo ci vorrà per raggiungere il limite di campionamento selezionato.", + "StatusActive": "Attiva", + "StatusEnded": "Terminata", + "StopX": "Ferma la registrazione di ogni nuova attività per questa %s.", + "TargetAttributePath": "Percorso", + "TargetAttributeUrl": "URL", + "TargetAttributeUrlParameter": "Parametro URL", + "TargetAttributeUrlParameterExample": "nameOfUrlParameter", + "TargetPage": "Pagina Obiettivo", + "TargetPageTestErrorInvalidUrl": "Inserisci un URL incluso un protocollo.", + "TargetPageTestLabel": "Inserisci un URL completo, incluso il protocollo, per verificare se le attività su questo URL verranno registrate:", + "TargetPageTestTitle": "Convalida URL", + "TargetPageTestUrlMatches": "Le attività su questo URL verranno registrate", + "TargetPageTestUrlNotMatches": "Le attività su questo URL non verranno registrate", + "TargetPages": "Pagine di Ingresso", + "TargetTypeContains": "Contiene", + "TargetTypeEqualsExactly": "esattamente uguale a", + "TargetTypeEqualsExactlyInfo": "Il valore deve corrispondere esattamente, incluso il protocollo URL, la query di ricerca e l'hash.", + "TargetTypeEqualsSimple": "uguale semplice", + "TargetTypeEqualsSimpleInfo": "L'URL corrisponderà a qualsiasi protocollo (ad esempio http e https) con o senza il sottodominio \"www.\". Qualsiasi barra finale del percorso, nonché la query di ricerca e la parte hash dell'URL verranno ignorate durante la corrispondenza dell'URL.", + "TargetTypeExists": "esiste", + "TargetTypeIsAny": "è qualsiasi", + "TargetTypeIsNot": "non %s", + "TargetTypeRegExp": "corrisponde all'espressione regolare", + "TargetTypeRegExpInfo": "Ogni espressione regolare, per esempio \"^(.*)test(.*)$\".", + "TargetTypeStartsWith": "comincia con", + "TimeOnSite": "Tempo sul sito", + "TrackingDisabledDefaultSettingDescription": "Questa funzionalità può essere utile se il tuo Matomo gestisce più siti e desideri utilizzare la funzione solo in alcuni siti specifici. Quando si disabilita il tracking come impostazione predefinita, le funzionalità Heatmap e Registrazione della sessione non verranno eseguite su alcun sito a meno che non le si abiliti specificamente come parte del codice di tracking, ad esempio chiamando '_paq.push (['HeatmapSessionRecording :: enable']); ' . Ciò evita richieste di rete non necessarie al file 'configs.php' per determinare se una heatmap o una registrazione di sessione è configurata per il sito corrente. Può anche essere utile per motivi di privacy se vuoi assicurarti che la funzione venga utilizzata solo su alcuni siti.", + "TrackingDisabledDefaultSettingTitle": "Disabilita il tracking come impostazione predefinita", + "UpdatingData": "Aggiornamento dati…", + "UrlParameterValueToMatchPlaceholder": "Valore da abbinare per il nome del parametro URL", + "UrlXDoesNotLookLikeUrl": "%snon sembra un URL. Assicurati che contenga un protocollo come http:// oppure che inizi con //", + "ViewReport": "Vedi report", + "ViewportResolution": "Risoluzione della finestra visibile (Viewport: larghezza x altezza)", + "XSamples": "%s campioni", + "disable": "disabilita", + "enable": "abilita" + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/ja.json b/files/plugin-HeatmapSessionRecording-5.2.4/lang/ja.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/lang/ja.json @@ -0,0 +1 @@ +{} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/nb.json b/files/plugin-HeatmapSessionRecording-5.2.4/lang/nb.json new file mode 100644 index 0000000..ef82999 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/lang/nb.json @@ -0,0 +1,35 @@ +{ + "HeatmapSessionRecording": { + "Recordings": "Opptak", + "Heatmaps": "Varmekart", + "Heatmap": "Varmekart", + "HeatmapX": "Varmekart %s", + "NHeatmaps": "%s varmekart", + "NSessionRecordings": "%s opptak av besøk", + "NoHeatmapsFound": "Fant ingen varmekart", + "NoSessionRecordingsFound": "Fant ingen opptak av besøk", + "HeatmapUsageBenefits": "Varmekart lar deg ta opp og visualisere alle klikk, musbevegelser og skrolle-aktiviteter når dine besøkere er på en gitt side. Dette hjelper deg å finne ut hva brukere tror kan klikkes på men ikke er det, om det er deler av siden som ikke brukes, hva besøkerne ser etter, hvor mye av siden som synlig når brukere ser på siden, og mer.", + "HeatmapXRecordedSamplesSince": "%1$s opptak har blitt gjort siden %2$s.", + "SessionRecordingsUsageBenefits": "Opptak av besøk lar deg spille inn alle aktiviteter av reelle brukere i løpet av besøkene, slik som klikk, musebevegelser, skrolling, justering av vindusstørrelse, sideendringer og skjemainteraksjoner. Du kan så spille av disse interaksjonene for å se nøyaktig hvordan en bruker brukte ditt nettsted. På denne måten forstår du bedre deres forventninger, problemer, bruksmønstre, og mer.", + "SessionRecordings": "Opptak av besøk", + "SessionRecording": "Opptak av besøk", + "AutoPlayNextPageview": "Klikk for å %1$s autospilling av neste sidevisning (snarvei %2$s)", + "ClickToSkipPauses": "Klikk for å %1$s hoppe over pauser (snarvei %2$s)", + "ChangeReplaySpeed": "Endre avspillingshastighet (snarvei %s)", + "PageviewXofY": "Sidevisning %1$s av %2$s", + "enable": "aktiver", + "disable": "deaktiver", + "ReplayX": "Spill av %s", + "CreationDate": "Opprettet dato", + "SessionRecordingX": "Opptak %s", + "ReplayRecordedSession": "Spill av innspilt besøk", + "RecordedSessions": "Opptak av besøk", + "RecordedSessionsDocStatusActive": "Dette opptaket er aktivt. %2$s av besøkene vil bli tatt opp, opp til %1$d besøk.", + "RecordedSessionsDocStatusEnded": "Dette opptaket har blitt avsluttet, og ingen nye besøk vil bli tatt opp.", + "RecordedHeatmapDocStatusActive": "Varmekartet er aktivt. %2$s av sidevisningene vil bli tatt opp, opp til %1$d sidevisninger.", + "RecordedHeatmapDocStatusEnded": "Opptaket av varmekartet er avsluttet. Ingen ny aktivitet vil bli tatt opp.", + "StatusActive": "Aktiv", + "StatusEnded": "Avsluttet", + "ExcludedElements": "Ekskluderte elementer" + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/nl.json b/files/plugin-HeatmapSessionRecording-5.2.4/lang/nl.json new file mode 100644 index 0000000..9eb6ade --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/lang/nl.json @@ -0,0 +1,185 @@ +{ + "HeatmapSessionRecording": { + "ActivityClick": "Klik", + "ActivityFormChange": "Formulier Wijziging", + "ActivityFormText": "Formulier Tekst Wijziging", + "ActivityFormValue": "Formulier Waarde Wijziging", + "ActivityInitialDom": "Initiële Pagina", + "ActivityMove": "Verplaats", + "ActivityResize": "Verkleinen", + "ActivityScroll": "Scroll", + "ActivityScrollElement": "Scroll Element", + "AdvancedOptions": "Geavanceerde Instellingen", + "AutoPlayNextPageview": "Klik %1$s om de volgende pagina bezoek automatisch af te spelen (shortcut %2$s)", + "AvgAboveFoldDescription": "Gemiddeld zien bezoekers de inhoud boven deze regel zonder te scrollen", + "AvgAboveFoldTitle": "Gem. Boven Vouw %spx", + "BreakpointGeneralHelp": "Het apparaattype wordt automatisch gedetecteerd wanneer een bezoeker uw website bezoekt. Soms is het echter niet mogelijk om het apparaattype te detecteren. Als uw website reageert en uw lay-out op een bepaald punt breekt, zullen we desktopgebruikers met een kleine resolutie in dit apparaattype plaatsen voor nauwkeurigere heatmaps. Elke breedte lager dan deze waarde wordt in dit apparaattype geplaatst. Zet het op nul als uw website niet reageert.", + "BreakpointGeneralHelpManage": "Een gebruiker met Super User rechten kan de standaard waarden voor deze breakpoints wijzigen in Administratie => Algemene Instellingen.", + "BreakpointX": "Breakpoint %s", + "CaptureKeystrokes": "Leg toetsaanslagen vast", + "CaptureKeystrokesHelp": "Indien ingeschakeld, wordt alle tekst die is ingevoerd in tekstformuliervelden, vastgelegd. Terwijl de tekst wordt opgenomen, wordt elk teken dat een gebruiker invoert vervangen door een ster ('*'). U kunt bepaalde velden die in platte tekst moeten worden opgenomen, op de witte lijst zetten door een 'data-matomo-unmask'-kenmerk op te geven in een formulierveld. Wachtwoordvelden en vele andere velden die mogelijk persoonlijke informatie kunnen bevatten (adres, e-mail, creditcardgegevens, gebruikersnaam, telefoonnummer, ...) worden echter altijd automatisch gemaskeerd als ze door ons als zodanig worden gedetecteerd (%1$slearn more%2$s) . Houd er rekening mee dat wanneer u deze functie inschakelt, u persoonlijke gegevens kunt opnemen die van invloed kunnen zijn op de AVG.", + "ChangeReplaySpeed": "Wijzig afspeel snelheid (shortcut %s)", + "ClickToSkipPauses": "Klik op %1$s om pauzes over te slaan (shortcut %2$s)", + "ColumnActionsDocumentation": "Speel de opgenomen sessie of verwijder de opname definitief.", + "ColumnBrowserDocumentation": "De browser die de bezoeker gebruikte toen deze sessie werd opgenomen.", + "ColumnDeviceDocumentation": "Het apparaat die de bezoeker gebruikte toen deze sessie werd opgenomen.", + "ColumnLabelRecordedSessions": "Binnenkomst URL → Exit URL", + "ColumnLocationDocumentation": "De locatie van de bezoeker toen deze sessie werd opgenomen.", + "ColumnOperatingSystem": "OS", + "ColumnOperatingSystemDocumentation": "Het besturingssysteem dat de bezoeker gebruikte toen deze sessie werd opgenomen.", + "ColumnPageviewsDocumentation": "Het aantal paginaweergaven dat is opgenomen gedurende deze sessie.", + "ColumnResolutionDocumentation": "Het aantal pixels van de zichtbare viewport in het browservenster toen de gebruiker deze pagina opende.", + "ColumnTime": "Tijd", + "ColumnTimeDocumentation": "Het tijdstip de opname startte in de tijdzone van de website.", + "ColumnTimeOnPageDocumentation": "Hoeveel tijd de bezoeker op deze pagina heeft doorgebracht terwijl de sessie werd opgenomen", + "ColumnTimeOnSiteDocumentation": "Hoeveel tijd de bezoeker in totaal doorbracht terwijl de sessie werd opgenomen.", + "ConfigsInternetDisabled": "We konden niet controleren of '%s' toegankelijk is omdat de internetverbinding op deze Matomo is uitgeschakeld.", + "ConfigsPhpErrorResult": "Als gevolg hiervan werkt het bijhouden van Heatmaps en Session Recordings mogelijk niet. Mogelijk moet u uw webserverconfiguratie wijzigen om toegang tot dit bestand via internet of intranet mogelijk te maken.", + "ConfigsPhpManualCheck": "Open de URL handmatig in een browser om te zien of het antwoord 'Piwik.HeatmapSessionRecording' bevat. Als dat niet het geval is, moet u mogelijk uw serverconfiguratie wijzigen omdat dit bestand toegankelijk moet zijn via een browser vanaf internet of intranet.", + "ConfigsPhpNotAccessible": "De URL '%s' lijkt niet toegankelijk vanaf het Internet of Intranet.", + "ConfigsPhpSelfSignedError": "Opvragen van '%s' resulteerde in een SSL error. Misschien gebruik je een zelfondertekend certificaat?", + "ConfigsPhpSuccess": "'%s' is toegankelijk", + "ConfigsPhpUnknown": "We konden niet controleren of '%s' toegankelijk is via internet of intranet.", + "ConfigsPhpUnknownError": "Het opvragen van '%1$s' resulteerde in een error: %2$s.", + "CreateNewHeatmap": "Maak een nieuwe heatmap", + "CreateNewSessionRecording": "Creëer nieuwe sessie opname", + "CreationDate": "Creatie Datum", + "DeleteHeatmapConfirm": "Weet je zeker dat je deze heatmap wilt verwijderen? Rapporten die eerder zijn gegenereerd, zijn niet langer beschikbaar in de rapportage-gebruikersinterface.", + "DeleteHeatmapScreenshotConfirm": "Weet je zeker dat je dit screenshot wilt verwijderen? Het kan een tijd duren voordat het screenshot opnieuw genomen wordt.", + "DeleteRecordedPageview": "Verwijder deze opgenomen paginaweergave (er wordt geen bevestiging gevraagd)", + "DeleteRecordedSession": "Verwijder deze opgenomen sessie (er wordt geen bevestiging gevraagd)", + "DeleteScreenshot": "Verwijder Screenshot", + "DeleteSessionRecordingConfirm": "Weet u zeker dat u deze sessie-opname wilt verwijderen? Eerder gegenereerde opnamen zijn niet langer beschikbaar in de rapportage-UI.", + "DeleteX": "Verwijder %s", + "EditHeatmapX": "Bewerk Heatmap %s", + "EditSessionRecordingX": "Bewerk Sessie Opname %s", + "EditX": "Bewerk %s", + "EndHeatmapConfirm": "Weet je zeker dat je wilt stoppen met het opnemen van activiteiten voor deze heatmap?", + "EndSessionRecordingConfirm": "Weet je zeker dat je wilt stoppen met het opnemen van nieuwe sessies?", + "ErrorArrayMissingKey": "Missende array sleutel \"%1$s\" in \"%2$s\" op positie \"%3$s\".", + "ErrorArrayMissingValue": "Missende waarde voor array sleutel \"%1$s\" in \"%2$s\" op positie \"%3$s\".", + "ErrorHeatmapDoesNotExist": "De opgevraagde heatmap bestaat niet.", + "ErrorHeatmapNameDuplicate": "De heatmap naam is al in gebruik door een andere heatmap.", + "ErrorInnerIsNotAnArray": "Elke \"%1$s\" binnen \"%2$s\" dient een array (reeks) te zijn.", + "ErrorInvalidRegExp": "De reguliere expressie \"%1$s\" heeft geen geldig formaat.", + "ErrorNotAnArray": "\"%1$s\" dient een reeks te zijn.", + "ErrorPageRuleRequired": "Er moet minimaal één paginaregel worden ingesteld.", + "ErrorSessionRecordingDoesNotExist": "De gevraagde sessie-opname bestaat niet", + "ErrorXContainsWhitespace": "De \"%1$s\" mag geen witruimte bevatten.", + "ErrorXNotANumber": "\"%1$s\" moet een getal zijn.", + "ErrorXNotProvided": "Geef een waarde voor \"1%1$s\".", + "ErrorXNotWhitelisted": "De waarde voor \"%1$s\" is niet toegestaan. Gebruik één van de: %2$s", + "ErrorXTooHigh": "\"%1$s\" is te hoog, de maximaal toegestane waarde is %2$s.", + "ErrorXTooLong": "\"%1$s\" is te lang, maximaal %2$skarakters zijn toegestaan.", + "ErrorXTooLow": "\"%1$s\" is te laag, de minimaal toegestane waarde is %2$s.", + "ExcludedElements": "Uitgesloten elementen", + "ExcludedElementsHelp": "Optioneel kunnen CSS-selectors gedefiniërd worden om bepaalde elementen uit te sluiten die niet zichtbaar zouden moeten zijn in het voorbeeld van de heatmap. Bijvoorbeeld een pop-up die wordt getoond wanneer de doelpagina wordt geopend. Meerdere selectors worden gescheiden met een komma.", + "FieldIncludedTargetsHelp": "Met doelen kunt u definiëren op welke pagina's de gebruikersactiviteiten moeten worden geregistreerd. U kunt een of meer voorwaarden definiëren. U kunt bijvoorbeeld definiëren om activiteiten vast te leggen wanneer de URL of het pad gelijk is aan een bepaalde waarde en alleen als een bepaalde URL-parameter aanwezig is in de URL. Activiteiten worden alleen geregistreerd als aan alle voorwaarden is voldaan, niet als slechts aan één ervan is voldaan. Alle voorwaarden worden hoofdletterongevoelig beoordeeld. Als u 'is gelijk aan eenvoudig' selecteert, worden het URL-protocol, zoekparameters en een slash achterwege gelaten.", + "FieldIncludedTargetsHelpSessions": "Targets stellen u in staat om te configureren om de opname van een sessie pas te starten zodra een bezoeker een bepaalde pagina heeft bereikt. Hiermee kunt u bijvoorbeeld alleen sessies opnemen die het afrekenproces doorlopen. U kunt een of meer voorwaarden definiëren. U kunt bijvoorbeeld definiëren om een sessie alleen op te nemen als de URL of het pad gelijk is aan een bepaalde waarde en alleen als een bepaalde URL-parameter aanwezig is in de URL. Sessies worden alleen geregistreerd als aan alle voorwaarden op een paginaweergave is voldaan, niet als slechts aan één ervan is voldaan. Alle voorwaarden worden hoofdletterongevoelig beoordeeld. Als u 'is gelijk aan eenvoudig' selecteert, worden het URL-protocol, zoekparameters en een slash achterwege gelaten.", + "FieldNamePlaceholder": "bijv 'Aanmeld Pagina'", + "FilesystemDirectory": "directory", + "Filter": "Filter", + "GettingStarted": "Getting started", + "Heatmap": "Heatmap", + "HeatmapCreated": "De heatmap is met succes aangemaakt.", + "HeatmapNameHelp": "Bepaalt de naam waaronder het rapport voor deze heatmap beschikbaar zal zijn.", + "HeatmapSampleLimit": "Aantal paginaweergaven", + "HeatmapSampleLimitHelp": "Bepaalt het aantal paginaweergaven dat je in totaal wilt opnemen.", + "HeatmapSampleRateHelp": "Ook wel 'traffic' genoemd. Wanneer u 100%% selecteert, worden alle bezoekers die de geselecteerde doelpagina bezoeken, geregistreerd. Als u bijvoorbeeld 10%% selecteert, wordt alleen elke 10e bezoeker geregistreerd. Hoe lager het percentage dat u selecteert, hoe langer het duurt om de geselecteerde monsterlimiet te bereiken.", + "HeatmapUpdated": "De heatmap is succesvol bijgewerkt.", + "HeatmapUsageBenefits": "Met heatmaps worden alle klikken, muisbewegingen en schuifactiviteiten van bezoekers op een bepaalde pagina opgenomen. Dit helpt om erachter te komen waar gebruikers denken dat iets klikbaar is, maar dat niet is, of dat er delen van de pagina zijn die nauwelijks worden bekeken of interactie mee is, of wat bezoekers zoeken, of hoeveel van de pagina zichtbaar is wanneer gebruikers uw pagina bezoeken en meer.", + "HeatmapWidth": "Heatmap breedte", + "HeatmapX": "Heatmap %s", + "HeatmapXRecordedSamplesSince": "%1$s samples zijn opgenomen sinds %2$s.", + "Heatmaps": "Heatmaps", + "Manage": "Beheer", + "ManageHeatmaps": "Beheer Heatmaps", + "ManageSessionRecordings": "Beheer Sessie Opnames", + "MinSessionTime": "Min Sessie Tijd", + "MinSessionTimeHelp": "Een sessie wordt alleen opgenomen als een bezoeker ten minste de opgegeven tijd op een pagina heeft doorgebracht.", + "NHeatmaps": "%s heatmaps", + "NSessionRecordings": "%s sessie opnames", + "NoHeatmapSamplesRecordedYet": "Er is nog geen paginaweergave geregistreerd voor deze heatmap. Als er nu opnames zouden moeten zijn, komen de paginadoelen die voor deze heatmap zijn geconfigureerd misschien niet overeen met een pagina op uw website. Het wordt ook aanbevolen om de \"Systeemcontrole\" onder \"Beheer\" (alleen Supergebruikers) te controleren om te zien of uw systeem is geconfigureerd om Heatmaps automatisch te volgen.", + "NoHeatmapScreenshotRecordedYet": "Er zijn tot nu toe %1$s samples opgenomen. Er is echter nog geen screenshot gemaakt. Als er een \"%2$s\" is voor deze heatmap, kan het even duren voordat de schermafbeelding beschikbaar komt, aangezien een gebruiker eerst deze schermafbeelding-URL moet openen. Afhankelijk van de samplefrequentie kan dit even duren.", + "NoHeatmapsConfiguredInfo": "Er zijn momenteel geen actieve heatmaps. Om heatmaps te bekijken, vraag een gebruiker met minstens admin-toegang om een nieuwe heatmap te maken.", + "NoHeatmapsFound": "Geen heatmaps gevonden", + "NoSessionRecordedYet": "Er is nog geen sessie opgenomen. Als er inmiddels opnamesessies moeten zijn, komt de geconfigureerde invoerpagina misschien niet overeen met een pagina op uw website. Het wordt ook aanbevolen om de \"Systeemcontrole\" onder \"Beheer\" (alleen Supergebruikers) te controleren om te zien of uw systeem is geconfigureerd voor automatisch opgenomen sessies.", + "NoSessionRecordingsConfiguredInfo": "Er zijn momenteel geen actieve sessie-opnames. Vraag een gebruiker met ten minste admin rechten om een nieuwe opname te maken om nieuwe sessies op te nemen.", + "NoSessionRecordingsFound": "Geen sessie opnames gevonden", + "NotSupportedBrowser": "Deze browser wordt niet ondersteund, gebruik een latere versie of een andere browser.", + "OnePageview": "1 pageweergave", + "PageRule": "Pagina Regel", + "PageviewXofY": "Paginaweergave %1$s van %2$s", + "PageviewsInVisit": "Opgenomen paginaweergaven in deze sessie", + "PersonalInformationNote": "Houd er rekening mee dat %1$s persoonlijke of gevoelige informatie kan vastleggen. Als u bepaalde inhoud op uw website of app wilt verbergen, kunt u deze inhoud taggen door een kenmerk %2$s data-matomo-mask %3$s toe te voegen aan de HTML-opmaak. %4$s Meer informatie %5$s", + "PlayRecordedSession": "Speel deze opgenomen sessie af", + "PlayerDurationXofY": "%1$s of %2$s", + "PlayerForwardFast": "Sla %1$s seconden over (shortcut %2$s)", + "PlayerPageViewNext": "Speel de volgende paginaweergave van deze bezoeker (%1$s) (shortcut %2$s)", + "PlayerPageViewPrevious": "Speel de vorige paginaweergave van deze bezoeker %1$s (shortcut %2$s)", + "PlayerPause": "Pauze (shortcut %s of spatie)", + "PlayerPlay": "Afspelen (shortcut %s of spatie)", + "PlayerReplay": "Herhaal (shortcut %s of spatie)", + "PlayerRewindFast": "Terugspoelen %1$s seconden (shortcut %2$s)", + "RecordedHeatmapDocStatusActive": "Deze heatmap is actief. Tot %1$d paginaweergaves zullen worden met een sample percentage van %2$s.", + "RecordedHeatmapDocStatusEnded": "Deze heatmap is geëindigd. Nieuwe activiteiten worden niet meer opgenomen.", + "RecordedSessions": "Opgenomen sessies", + "RecordedSessionsDocStatusActive": "Deze sessie opname is actief. Tot %1$d sessies zullen worden opgenomen met een sample percentage van %2$s.", + "RecordedSessionsDocStatusEnded": "Deze sessie opname is beëindigd en nieuwe sessies worden niet meer opgenomen.", + "Recordings": "Opnamen", + "ReplayRecordedSession": "Herhaal opgenomen sessie", + "ReplayX": "Herhaal %s", + "ReportRecordedSessionsDocumentation": "Een lijst met all sessie die opgenomen zijn. Je kan alle opgenomen sessie opnieuw afspelen.", + "RequiresActivity": "Vereist activiteit", + "RequiresActivityHelp": "Indien ingeschakeld, worden alleen sessies met een scroll- en klikactiviteit in één paginaweergave opgenomen. Dit voorkomt het opnemen van sessies met weinig activiteit.", + "Rule": "Regel", + "SampleLimit": "Sample Limiet", + "SampleRate": "Sample Percentage", + "ScreenshotUrl": "Screenshot URL", + "ScreenshotUrlHelp": "Standaard wordt er een screenshot gemaakt wanneer de eerste bezoeker de doelpagina bekijkt. Als verschillende pagina-URL's overeenkomen met uw doelpagina, kunt u een specifieke URL specificeren die moet worden gebruikt om de schermafbeelding vast te leggen. De heatmap-visualisatie is pas beschikbaar als ten minste één bezoeker deze URL heeft bezocht waarvan de activiteiten zijn vastgelegd. Indien gedefinieerd, moet de URL exact overeenkomen. Om het protocol te negeren, start u de URL met twee dubbele schuine strepen (//example.com). Houd er rekening mee dat deze URL niet kan worden gewijzigd zodra er een screenshot is gemaakt", + "SessionNameHelp": "Definieert de naam waaronder de sessie-opnames beschikbaar zullen zijn.", + "SessionRecording": "Sessie Opname", + "SessionRecordingCreated": "De sessie-opname is met succes gemaakt.", + "SessionRecordingUpdated": "De sessie-opname is succesvol bijgewerkt.", + "SessionRecordingX": "Sessie Opname %s", + "SessionRecordings": "Sessie Opnames", + "SessionRecordingsUsageBenefits": "Met sessie-opnames worden alle activiteiten van een echte bezoeker tijdens zijn of haar sessie (bezoek) opgenomen, zoals klikken, muisbewegingen, schuiven, schermgrootte, paginawijzigingen en formulier interacties. Deze interacties kunnen vervolgens opnieuw afgespeeld worden om precies te zien hoe een bezoeker door de website ging. Op deze manier begrijpt je hun verwachtingen, problemen die ze kunnen hebben, gebruikers patronen en meer.", + "SessionSampleLimit": "Aantal sessies", + "SessionSampleLimitHelp": "Bepaalt het aantal sessies dat je in totaal wilt opnemen.", + "SessionSampleRateHelp": "Ook wel 'traffic' genoemd. Wanneer u 100%% selecteert, worden alle sessies opgenomen zodra ze de doelpagina hebben bereikt. Als u bijvoorbeeld 10%% selecteert, wordt alleen elke 10e sessie geregistreerd. Hoe lager het percentage dat u selecteert, hoe langer het duurt om de geselecteerde monsterlimiet te bereiken.", + "StatusActive": "Actief", + "StatusEnded": "Gestopt", + "StopX": "Stop met het opnemen van nieuwe activiteiten voor %s.", + "TargetAttributePath": "Pad", + "TargetAttributeUrl": "URL", + "TargetAttributeUrlParameter": "URL Parameter", + "TargetAttributeUrlParameterExample": "naamVanUrlParameter", + "TargetPage": "Landingspagina", + "TargetPageTestErrorInvalidUrl": "Voer een URL in inclusief het protocol.", + "TargetPageTestLabel": "Voer een volledige URL inclusief het protocol in om te controleren of alle activiteiten opgenomen op deze URL:", + "TargetPageTestTitle": "URL validator", + "TargetPageTestUrlMatches": "Activiteiten worden op deze URL vastgelegd", + "TargetPageTestUrlNotMatches": "Activiteiten worden niet vastgelegd op deze URL", + "TargetPages": "Binnenkomst Pagina's", + "TargetTypeContains": "bevat", + "TargetTypeEqualsExactly": "is precies hetzelfde", + "TargetTypeEqualsExactlyInfo": "De waarde dient precies hetzelfde te zijn, inclusief het URL protocol, zoek vraag en hash.", + "TargetTypeEqualsSimple": "is gelijk aan", + "TargetTypeEqualsSimpleInfo": "De URL komt overeen met elk protocol (bijv. Http en https) met of zonder \"www.\" subdomein. Elke volgende slash in het pad, evenals de zoekopdracht en het hash gedeelte van de URL worden genegeerd bij het matchen van de URL.", + "TargetTypeExists": "bestaat", + "TargetTypeIsAny": "is er een", + "TargetTypeIsNot": "geen %s", + "TargetTypeRegExp": "komt overeen met reguliere expressie", + "TargetTypeRegExpInfo": "Elke reguliere expressie, bijvoorbeeld \"^(.*)test(.*)$\".", + "TargetTypeStartsWith": "begint met", + "TimeOnSite": "Tijd op de website", + "UpdatingData": "Bijwerken gegevens…", + "UrlParameterValueToMatchPlaceholder": "Te matchen waarde voor URL-parameternaam", + "UrlXDoesNotLookLikeUrl": "%s lijkt niet op een URL. Zorg ervoor dat deze een protocol zoals http:// bevat of start met //", + "ViewReport": "Bekijk rapport", + "ViewportResolution": "Viewport Resolutie (breedte x hoogte)", + "XSamples": "%s samples", + "disable": "deactiveer", + "enable": "activeer" + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/pl.json b/files/plugin-HeatmapSessionRecording-5.2.4/lang/pl.json new file mode 100644 index 0000000..8b4e9ad --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/lang/pl.json @@ -0,0 +1,185 @@ +{ + "HeatmapSessionRecording": { + "ActivityClick": "Kliknięcie", + "ActivityFormChange": "Zmiana w formularzu", + "ActivityFormText": "Zmiana tekstu w formularzu", + "ActivityFormValue": "Zmiana wartości w formularzu", + "ActivityInitialDom": "Strona początkowa", + "ActivityMove": "Ruch", + "ActivityResize": "Zmiana rozmiaru", + "ActivityScroll": "Przewinięcie", + "ActivityScrollElement": "Przewijany element", + "AdvancedOptions": "Zaawansowane", + "AutoPlayNextPageview": "Kliknij aby %1$s odtworzyć następne nagranie (skrót %2$s)", + "AvgAboveFoldDescription": "Przeciętny odwiedzający widzi zawartość powyżej tej linii bez przewijania", + "AvgAboveFoldTitle": "Śr. powyżej zgięcia %spx", + "BreakpointGeneralHelp": "Typ urządzenia jest automatycznie wykrywany podczas odwiedzin na Twojej stronie. Czasami zdarza się, że poprawne wykrycie urządzenia nie jest możliwe. W przypadku stron responsywnych, gdy projekt strony zmienia się dla pewnych rozdzielczości, możemy umieścić użytkowników komputerów stacjonarnych z ekranami o niskich rozdzielczościach umieścić w tej grupie, aby wygenerować dokładniejsze mapy aktywności. Dowolna szerokość mniejsza niż ta wartość spowoduje umieszczenie odwiedzającego w tej grupie urządzeń. Ustaw zero dla nieresponsywnej strony.", + "BreakpointGeneralHelpManage": "Użytkownik z dostępem Super Użytkownika może zmienić domyślne wartości punktów zatrzymania w Ustawienia => Ustawienia ogólne.", + "BreakpointX": "Punkt podziału %s", + "CaptureKeystrokes": "Przechwytuj naciskane klawisze", + "CaptureKeystrokesHelp": "Włączenie tej opcji spowoduje rejestrację dowolnego tekstu wprowadzanego w polach formularza.dy wprowadzony przez użytkownika znak jest zamieniany na gwiazdkę ('*'). Możesz dodać wybrane pola do białej listy, co spowoduje ich zapisywanie jako zwykłego tekstu poprzez dodanie im atrybutu 'data-matomo-unmask'. Pola haseł i inne potencjalnie przenoszące dane osobowe (adres, email, dane karty kredytowej, nazwa użytkownika, numer telefonu) po wykryciu przez nas zawsze będą maskowane automatycznie (%1$sdowiedz się więcej%2$s). Prosimy pamiętaj, że włączając tą funkcjonalność możesz zapisać dane osobowe, co może naruszać zapisy RODO.", + "ChangeReplaySpeed": "Zmień prędkość odtwarzania (skrót %s)", + "ClickToSkipPauses": "Kliknij aby %1$s pominąć pauzy (skrót %2$s)", + "ColumnActionsDocumentation": "Odtwórz zapisaną sesję lub trwale usuń nagranie.", + "ColumnBrowserDocumentation": "Przeglądarka używana przez odwiedzającego podczas nagrywania tej sesji.", + "ColumnDeviceDocumentation": "Urządzenie użyte podczas nagrywania tej sesji.", + "ColumnLabelRecordedSessions": "Adres wejścia → Adres wyjścia", + "ColumnLocationDocumentation": "Lokalizacja odwiedzającego w czasie nagrywania tej sesji.", + "ColumnOperatingSystem": "System operacyjny", + "ColumnOperatingSystemDocumentation": "System operacyjny zainstalowany na urządzeniu odwiedzającego podczas nagrywania sesji.", + "ColumnPageviewsDocumentation": "Liczba odsłon strony, które zostały zapisane podczas tej sesji.", + "ColumnResolutionDocumentation": "Liczba pikseli widocznych w oknie przeglądarki w momencie otwarcia strony przez odwiedzającego.", + "ColumnTime": "Czas", + "ColumnTimeDocumentation": "Czas rozpoczęcia nagrania w strefie czasowej serwisu.", + "ColumnTimeOnPageDocumentation": "Czas spędzony przez odwiedzającego na stronie podczas nagrywania sesji", + "ColumnTimeOnSiteDocumentation": "Łączny czas spędzony przez odwiedzającego podczas nagrywania sesji", + "ConfigsInternetDisabled": "Nie mogliśmy sprawdzić, czy '%s' jest dostępny z powodu braku połączenia internetowego na tym Matomo.", + "ConfigsPhpErrorResult": "Wynikiem tego może być brak śledzenia Map Aktywności lub Zapisów Sesji. Możliwe, że musisz zmienić konfigurację swojego serwera www, aby udostępnić ten plik w Internecie lub Intranecie.", + "ConfigsPhpManualCheck": "Otwórz, proszę, stronę pod tym adresem w swojej przeglądarce i sprawdź, czy odpowiedź zawiera 'Piwik.HeatmapSessionRecording'. Jeśli nie znajdujesz tej frazy w odpowiedzi, konieczna może być zmiana ustawień serwera, ponieważ plik ten musi być dostępny dla przeglądarki w Internecie lub Intranecie.", + "ConfigsPhpNotAccessible": "Adres '%s' zdaje się być niedostępny z Internetu lub Intranetu.", + "ConfigsPhpSelfSignedError": "Żądanie '%s' zwróciło błąd SSL. Czy korzystasz z samopodpisanego certyfikatu?", + "ConfigsPhpSuccess": "'%s' jest dostępne", + "ConfigsPhpUnknown": "Nie mogliśmy sprawdzić, czy '%s' jest dostępne z Internetu lub Intranetu.", + "ConfigsPhpUnknownError": "Żądanie '%1$s' zwróciło błąd: %2$s.", + "CreateNewHeatmap": "Utwórz nową mapę aktywności", + "CreateNewSessionRecording": "Utwórz nowe nagranie sesji", + "CreationDate": "Data utworzenia", + "DeleteHeatmapConfirm": "Czy na pewno chcesz usunąć tą mapę aktywności? Poprzednio wygenerowane raporty map aktywności przestaną być dostępne w panelu.", + "DeleteHeatmapScreenshotConfirm": "Na pewno chcesz usunąć ten zrzut ekranu? Ponowne wygenerowania zrzutu potrwa dłuższą chwilę.", + "DeleteRecordedPageview": "Usuń to nagranie wyświetlenia strony (bez wyświetlenia potwierdzenia)", + "DeleteRecordedSession": "Usuń tą nagraną sesję (bez wyświetlenia potwierdzenia)", + "DeleteScreenshot": "Usuń zrzut ekranu", + "DeleteSessionRecordingConfirm": "Czy na pewno chcesz usunąć ten zapis sesji? Poprzednio wygenerowane zapisy sesji przestaną być dostępne w panelu.", + "DeleteX": "Usuń %s", + "EditHeatmapX": "Edytuj Mapę Aktywności %s", + "EditSessionRecordingX": "Edytuj Zapis Sesji %s", + "EditX": "Edytuj %s", + "EndHeatmapConfirm": "Czy na pewno chcesz wstrzymać przechwytywanie aktywności dla wybranej mapy?", + "EndSessionRecordingConfirm": "Czy na pewno chcesz wstrzymać zapisywanie nowych sesji?", + "ErrorArrayMissingKey": "W tablicy \"%2$s\" brak klucza \"%1$s\" na pozycji \"%3$s\".", + "ErrorArrayMissingValue": "W tablicy \"%2$s\" brakuje wartości \"%1$s\" na pozycji \"%3$s\".", + "ErrorHeatmapDoesNotExist": "Poszukiwana mapa aktywności nie istnieje", + "ErrorHeatmapNameDuplicate": "Nazwa mapy aktywności jest już wykorzystywana przez inną mapę.", + "ErrorInnerIsNotAnArray": "Każdy \"%1$s\" w \"%2$s\" musi być tablicą.", + "ErrorInvalidRegExp": "Wyrażenie regularne \"%1$s\" nie ma właściwego formatu.", + "ErrorNotAnArray": "\"%1$s\" musi być tablicą.", + "ErrorPageRuleRequired": "Przynajmniej 1 reguła strony musi być ustawiona", + "ErrorSessionRecordingDoesNotExist": "Poszukiwany zapis sesji nie istnieje", + "ErrorXContainsWhitespace": "Pole \"%1$s\" nie może zawierać spacji.", + "ErrorXNotANumber": "\"%1$s\" musi być liczbą.", + "ErrorXNotProvided": "Proszę podać wartość dla \"%1$s\".", + "ErrorXNotWhitelisted": "Niedozwolona wartość pola \"%1$s\", spróbuj jednej z: %2$s.", + "ErrorXTooHigh": "Wartość \"%1$s\" przekracza maksimum, które wynosi %2$s.", + "ErrorXTooLong": "Ciąg \"%1$s\" jest za długi. Dopuszczone są ciągi o długości do %2$s znaków.", + "ErrorXTooLow": "Wartość \"%1$s\" jest poniżej minimum wynoszącego %2$s.", + "ExcludedElements": "Wykluczone elementy", + "ExcludedElementsHelp": "Tu możesz zdefiniować selektory CSS, które ukryją wybrane elementy, których nie chcesz wyświetlać w podglądzie aktywności. Przykładem może być popup otwierany po dotarciu do strony docelowej. Do rozdzielenia kilku selektorów użyj przecinka.", + "FieldIncludedTargetsHelp": "Cele pozwalają Ci zdefiniować, na których stronach aktywności użytkownika powinny być rejestrowane. Możesz zdefiniować jeden lub więcej warunków. Możesz, na przykład, zdefiniować rejestrowanie aktywności zawsze, gdy adres lub ścieżka równa jest pewnej wartości i tylko jeśli wybrany parametr adresu jest w nim obecny. Aktywności będą zapisywane, tylko gdy wszystkie warunki zostaną spełnione. Wszystkie warunki weryfikowane są z rozróżnieniem wielkości liter. Wybierając 'proste porównanie' spowodujesz ignorowanie protokołu, parametrów wyszukiwania i ostatniego ukośnika adresu.", + "FieldIncludedTargetsHelpSessions": "Cele pozwalają Ci zdefiniować strony, na które musi dotrzeć odwiedzający, aby uruchomić zapis sesji. W ten sposób możesz na przykład rejestrować sesje, podczas których użytkownik przechodzi przez koszyk. Możesz zdefiniować jeden lub więcej warunków. Możesz, na przykład, skonfigurować zapis sesji tylko, gdy adres lub ścieżka zawiera określoną wartość i tylko gdy wybrany parametr adresu jest w nim obecny. Sesje będą rejestrowane tylko, gdy wszystkie warunki zostaną spełnione podczas odsłony strony. Wszystkie warunki są sprawdzane z rozróżnieniem wielkości liter. Wybierając 'proste porównanie' spowodujesz ignorowanie protokołu, parametrów wyszukiwania i ostatniego ukośnika adresu.", + "FieldNamePlaceholder": "n.p. 'Strona rejestracji'", + "FilesystemDirectory": "katalog", + "Filter": "Filtruj", + "GettingStarted": "Jak rozpocząć", + "Heatmap": "Mapa aktywności", + "HeatmapCreated": "Mapa aktywności została pomyślnie utworzona.", + "HeatmapNameHelp": "Definiuje nazwę reprezentującą raport tej mapy aktywności.", + "HeatmapSampleLimit": "Liczba wyświetleń stron", + "HeatmapSampleLimitHelp": "Określ liczbę wszystkich wyświetleń strony, które chcesz nagrać.", + "HeatmapSampleRateHelp": "Znany jako 'transfer'. Po wybraniu wartości 100%%, funkcjonalność zarejestruje każdego odwiedzającego skonfigurowaną stronę docelową. Zmiana tego ustawienia na przykład na 10%%, spowoduje rejestrowanie co 10 odwiedzającego. Niskie wartości tego parametru spowodują wydłużenie czasu, w którym aplikacja zbierze wymaganą liczbę próbek.", + "HeatmapUpdated": "Mapa aktywności została pomyślnie zaktualizowana.", + "HeatmapUsageBenefits": "Mapy aktywności pozwalają Ci zapisywać kliknięcia, ruchy wskaźnika i przewijania, wykonywane przez Twoich odwiedzających wybraną stronę. To pozwoli Ci zrozumieć, gdzie użytkownikom wydaje się, że jest klikalny element, czy na stronie znajdują się rzadko oglądane obszary, lub z którymi użytkownicy rzadko wchodzą w interakcję. Dzięki nim zrozumiesz, jakich informacji poszukują odwiedzający, jaką część strony widzą podczas odwiedzin i wiele więcej.", + "HeatmapWidth": "Szerokość mapy aktywności", + "HeatmapX": "Mapa aktywności %s", + "HeatmapXRecordedSamplesSince": "%1$s próbek zostało zarejestrowane od %2$s.", + "Heatmaps": "Mapy aktywności", + "Manage": "Zarządzaj", + "ManageHeatmaps": "Zarządzaj Mapami Aktywności", + "ManageSessionRecordings": "Zarządzań Nagraniami Sesji", + "MinSessionTime": "Min. czas sesji", + "MinSessionTimeHelp": "Sesja zostanie zapisana tylko wtedy, gdy użytkownik spędzi na stronie przynajmniej określoną tu ilość czasu.", + "NHeatmaps": "%s map aktywności", + "NSessionRecordings": "%s nagranych sesji", + "NoHeatmapSamplesRecordedYet": "Nie nagrano jeszcze żadnego wyświetlenia strony dla tej mapy aktywności. Jeśli powinny tu już widnieć nagrania, możliwe jest, że skonfigurowane dla tej mapy aktywności cele nie pasują do stron Twojego serwisu. Zalecane jest również odwiedzenie zakładki \"Sprawdzenie systemu\" w \"Ustawieniach\" (tylko Super Użytkownicy), w celu sprawdzenia, czy w systemie zostało skonfigurowane automatyczne tworzenie map aktywności.", + "NoHeatmapScreenshotRecordedYet": "Do tej pory zarejestrowano %1$s próbek, lecz nie wykonano zrzutu ekranu. Jeśli dla tej mapy aktywności istnieje \"%2$s\", to udostępnienie zrzutu ekranu może zająć chwilę, gdyż użytkownik musi wpierw odwiedzić jego adres. Czas jego dostępności zależy od częstotliwości próbkowania.", + "NoHeatmapsConfiguredInfo": "Brakuje obecnie aktywnych map aktywności. W celu ich przeglądania, poproś użytkownika, który ma przynajmniej dostęp administratora, aby utworzył nową mapę aktywności.", + "NoHeatmapsFound": "Brak map aktywności", + "NoSessionRecordedYet": "Nie nagrano jeszcze żadnej sesji. Jeśli powinny tu już widnieć nagrane sesje, możliwe jest, że skonfigurowana strona wejścia nie pasuje do stron Twojego serwisu. Zalecane jest również odwiedzenie zakładki \"Sprawdzenie systemu\" w \"Ustawieniach\" (tylko Super Użytkownicy), w celu sprawdzenia, czy w systemie zostało skonfigurowane automatyczne nagrywanie sesji.", + "NoSessionRecordingsConfiguredInfo": "W tej chwili nie nagrywasz żadnej sesji. Aby nagrać nowe sesje, skontaktuj się z użytkownikiem o dostępie co najmniej administratora, aby utworzył nowe nagrania.", + "NoSessionRecordingsFound": "Brak nagranych sesji", + "NotSupportedBrowser": "Twoja przeglądarka nie jest wspierana, proszę użyj aktualniejszej wersji lub użyj innej przeglądarki.", + "OnePageview": "1 wyświetlenie strony", + "PageRule": "Reguły strony", + "PageviewXofY": "Odwiedziny %1$s z %2$s", + "PageviewsInVisit": "Wyświetlenia strony nagrane w tej sesji", + "PersonalInformationNote": "Pamiętaj, że %1$s może zapisywać dane osobiste i wrażliwe. Jeśli życzysz sobie ukryć wybrane treści na swojej stronie lub aplikacji, możesz nadać im atrybut %2$sdata-matomo-mask%3$s w źródle HTML. Kliknij, aby %4$sdowiedzieć się więcej%5$s", + "PlayRecordedSession": "Odtwórz tą nagraną sesję", + "PlayerDurationXofY": "%1$s z %2$s", + "PlayerForwardFast": "W przód o %1$s sekund (skrót %2$s)", + "PlayerPageViewNext": "Odtwórz następne wyświetlenie strony użytkownika %1$s (skrót %2$s)", + "PlayerPageViewPrevious": "Odtwórz poprzednie wyświetlenie strony użytkownika %1$s (skrót %2$s)", + "PlayerPause": "Pauza (skrót %s lub spacja)", + "PlayerPlay": "Odtwórz (skrót %s lub spacja)", + "PlayerReplay": "Ponów (skrót %s lub spacja)", + "PlayerRewindFast": "Cofnij o %1$s sekund (skrót %2$s)", + "RecordedHeatmapDocStatusActive": "Ta mapa aktywności jest aktywna. Do %1$d wyświetleń strony zostanie zapisane z częstotliwością próbkowania %2$s.", + "RecordedHeatmapDocStatusEnded": "Ta mapa aktywności została zakończona. Żadne nowe interakcje nie będą zapisywane.", + "RecordedSessions": "Nagrane sesje", + "RecordedSessionsDocStatusActive": "Ta sesja jest w trakcie nagrywania. Do %1$d sesji zostanie nagrane z częstotliwością próbkowania %2$s.", + "RecordedSessionsDocStatusEnded": "Nagranie tej sesji zakończyło się. Żadne nowe sesje nie będą zapisywane.", + "Recordings": "Nagrania", + "ReplayRecordedSession": "Odtwórz nagraną sesję", + "ReplayX": "Odtwórz %s", + "ReportRecordedSessionsDocumentation": "Lista wszystkich zarejestrowanych sesji. Możesz odtworzyć dowolną zapisaną sesję.", + "RequiresActivity": "Wymagana aktywność", + "RequiresActivityHelp": "Włączenie spowoduje zapis tylko tych sesji, w czasie których zostaną zarejestrowane wydarzenia przewinięcia i kliknięcia na stronie. To zapobiega rejestracji sesji z niską aktywnością.", + "Rule": "Reguła", + "SampleLimit": "Limit próbkowania", + "SampleRate": "Częstotliwość próbkowania", + "ScreenshotUrl": "Adres zrzutu ekranu", + "ScreenshotUrlHelp": "Domyślnie, zrzut ekranu wykonywany jest podczas odwiedzin pierwszego użytkownika na stronie docelowej. W sytuacji, gdy możliwe będzie dopasowanie adresu innej strony, możesz podać adres wybranej strony, na której ma zostać wykonany zrzut ekranu. Wizualizacja Mapy Aktywności stanie się dostępna po wizycie pierwszego odwiedzającego, którego aktywność zostanie zarejestrowana. Po zdefiniowaniu, adres zostanie dokładnie dopasowany. W celu pominięcia protokołu, rozpocznij wpis adresu od podwójnego ukośnika (//example.com). Pamiętaj, że adresu tego nie można zmienić po wykonaniu zrzutu ekranu.", + "SessionNameHelp": "Definiuje nazwę reprezentującą to nagranie sesji.", + "SessionRecording": "Nagrana sesja", + "SessionRecordingCreated": "Konfiguracja nagrania sesji została pomyślnie zapisana.", + "SessionRecordingUpdated": "Konfiguracja nagrania sesji została pomyślnie zaktualizowana.", + "SessionRecordingX": "Nagranie sesji %s", + "SessionRecordings": "Nagrania sesji", + "SessionRecordingsUsageBenefits": "Nagrania sesji pozwalają Ci zapisać wszystkie aktywności użytkowników w czasie trwania ich sesji (wizyty). Do aktywności tych należy zaliczyć kliknięcia, ruchy wskaźnika, przewijanie, zmiany wielkości okna, zmiany strony czy korzystanie z formularzy. Po zapisaniu możesz odtworzyć te interakcje, dzięki czemu uzyskasz dokładny obraz działań użytkowników w Twoim serwisie. W ten sposób zrozumiesz ich oczekiwania, możliwe problemy, wzorce użytkowania i wiele więcej.", + "SessionSampleLimit": "Liczba sesji", + "SessionSampleLimitHelp": "Określ liczbę wszystkich sesji, które chcesz nagrać.", + "SessionSampleRateHelp": "Znany jako 'transfer'. Po wybraniu wartości 100%%, funkcjonalność zarejestruje każdego odwiedzającego skonfigurowaną stronę docelową już w momencie jej wyświetlenia. Zmiana tego ustawienia na przykład na 10%%, spowoduje rejestrowanie co 10 odwiedzającego. Niskie wartości tego parametru spowodują wydłużenie czasu, w którym aplikacja zbierze wymaganą liczbę próbek.", + "StatusActive": "Aktywna", + "StatusEnded": "Zakończona", + "StopX": "Przerwij zapis nowych wydarzeń dla sesji %s.", + "TargetAttributePath": "Ścieżka", + "TargetAttributeUrl": "Adres", + "TargetAttributeUrlParameter": "Parametr adresu", + "TargetAttributeUrlParameterExample": "nazwaParamertuAdresu", + "TargetPage": "Strona docelowa", + "TargetPageTestErrorInvalidUrl": "Wprowadź adres z protokołem.", + "TargetPageTestLabel": "Wprowadź pełny adres URL włącznie z protokołem, aby sprawdzić, czy aktywności będą zapisywane dla tego adresu:", + "TargetPageTestTitle": "Walidator adresu", + "TargetPageTestUrlMatches": "Aktywność będzie rejestrowana dla tego adresu", + "TargetPageTestUrlNotMatches": "Aktywność nie będzie rejestrowana dla tego adresu", + "TargetPages": "Strony wejścia", + "TargetTypeContains": "zawiera", + "TargetTypeEqualsExactly": "jest dokładnie", + "TargetTypeEqualsExactlyInfo": "Wartość musi być dokładnie dopasowana i zawierać adres z protokołem, ciąg zapytania i fragment.", + "TargetTypeEqualsSimple": "porównanie proste", + "TargetTypeEqualsSimpleInfo": "Adres zostanie dopasowany do dowolnego protokołu (n.p. HTTP i HTTPS) z lub bez subdomeny \"www.\". Ukośnik, ciąg zapytania jak i hash zostaną pominięte podczas dopasowywania adresu do wzorca.", + "TargetTypeExists": "występuje", + "TargetTypeIsAny": "dowolny", + "TargetTypeIsNot": "nie%s", + "TargetTypeRegExp": "pasuje do wyrażenia regularnego", + "TargetTypeRegExpInfo": "Dowolne wyrażenie regularne, na przykład \"^(.*)test(.*)$\".", + "TargetTypeStartsWith": "zaczyna się od", + "TimeOnSite": "Czas w serwisie", + "UpdatingData": "Aktualizacja danych…", + "UrlParameterValueToMatchPlaceholder": "Wartość dopasowywana dla parametru URL", + "UrlXDoesNotLookLikeUrl": "%s nie wygląda jak adres URL. Upewnij się, że adres zawiera protokół np. http:// lub zaczyna się od //", + "ViewReport": "Raport wyświetleń", + "ViewportResolution": "Rozdzielczość przeglądarki (szerokość x wysokość)", + "XSamples": "%s próbek", + "disable": "wyłącz", + "enable": "włącz" + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/pt.json b/files/plugin-HeatmapSessionRecording-5.2.4/lang/pt.json new file mode 100644 index 0000000..0bd96f3 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/lang/pt.json @@ -0,0 +1,185 @@ +{ + "HeatmapSessionRecording": { + "ActivityClick": "Clique", + "ActivityFormChange": "Alteração no formulário", + "ActivityFormText": "Alteração de texto no formulário", + "ActivityFormValue": "Alteração de valor no formulário", + "ActivityInitialDom": "Página inicial", + "ActivityMove": "Movimento", + "ActivityResize": "Redimensionamento", + "ActivityScroll": "Deslocamento", + "ActivityScrollElement": "Deslocamento de elemento", + "AdvancedOptions": "Opções avançadas", + "AutoPlayNextPageview": "Clique para %1$s reproduzir de forma automática a próxima visualização da página (atalho %2$s)", + "AvgAboveFoldDescription": "Em média os visitantes vêm o conteúdo acima desta linha sem deslocamento", + "AvgAboveFoldTitle": "%spx de média sob o limite", + "BreakpointGeneralHelp": "O tipo de dispositivo é detetado automaticamente quando um visitante visita o seu site. No entanto, por vezes, pode não ser possível detetar o tipo de dispositivo. Se o seu site é responsivo e o seu interface se modifica em determinado ponto, nós iremos colocar utilizadores de computadores com uma resolução pequena neste tipo de dispositivos para mapas térmicos mais fiáveis. Qualquer largura inferior a este valor será colocada neste tipo de dispositivo. Defina zero se o seu site não é responsivo.", + "BreakpointGeneralHelpManage": "Um utilizador com acesso de super-utilizador pode alterar os valores predefinidos para estes pontos de quebra em Administração => Definições gerais.", + "BreakpointX": "Ponto de quebra %s", + "CaptureKeystrokes": "Capturar teclas pressionadas", + "CaptureKeystrokesHelp": "Se ativo, qualquer texto que seja introduzido em campos de texto de formulários será gravado. Enquanto o texto é gravado, qualquer carater que um utilizador introduza é substituído por um asterisco ('*'). Pode permitir que determinados campos sejam gravados em texto-simples, especificando um atributo 'data-matomo-unmask' num campo do formulário. No entanto, campos de palavras-passe e muitos outros campos que podem potencialmente conter informações pessoais (endereços, e-mail, informações do cartão de crédito, nome de utilizador, número de telefone, ...) serão sempre mascarados automaticamente se for detetado por nós como tal (%1$ssaber mais%2$s). Por favor, note que quando ativa esta funcionalidade, pode guardar informações pessoais que podem afetar o RGPD.", + "ChangeReplaySpeed": "Alterar velocidade de reprodução (atalho %s)", + "ClickToSkipPauses": "Clique para %1$s saltar pausas (atalho %2$s)", + "ColumnActionsDocumentation": "Reproduzir a sessão gravada ou eliminar a gravação de forma permanente.", + "ColumnBrowserDocumentation": "O navegador que o visitante estava a utilizar quando esta sessão foi gravada.", + "ColumnDeviceDocumentation": "O dispositivo que o visitante estava a utilizar quando esta sessão foi gravada.", + "ColumnLabelRecordedSessions": "Endereço de entrada → Endereço de saída", + "ColumnLocationDocumentation": "A localização do visitante quando esta sessão foi gravada.", + "ColumnOperatingSystem": "Sistema operativo", + "ColumnOperatingSystemDocumentation": "O sistema operativo que o visitante estava a utilizar quando esta sessão foi guardada.", + "ColumnPageviewsDocumentation": "O número de visualizações de páginas que foram guardadas durante esta sessão.", + "ColumnResolutionDocumentation": "O número de pixeis da área de visualização vísivel na janela do navegador quando o utilizador abriu esta página.", + "ColumnTime": "Hora", + "ColumnTimeDocumentation": "A hora que a gravação começou no fuso horário do site.", + "ColumnTimeOnPageDocumentation": "Quanto tempo o visitante despendeu nesta página enquanto a sessão foi gravada", + "ColumnTimeOnSiteDocumentation": "Quanto tempo o visitante despendeu no total enquanto a sessão foi gravada", + "ConfigsInternetDisabled": "Não conseguimos confirmar se '%s' é acessível porque a ligação à Internet está desativada neste Matomo.", + "ConfigsPhpErrorResult": "Como resultado, o acompanhamento de mapas térmicos e gravações de sessões pode não funcionar. Pode necessitar de alterar a configuração do seu servidor web para permitir o acesso a este ficheiro via Internet ou Intranet.", + "ConfigsPhpManualCheck": "Por favor, abra o endereço manualmente num navegador para verificar se a resposta contem 'Piwik.HeatmapSessionRecording'. Caso contrário, pode precisar de modificar a configuração do seu servidor dado que este ficheiro precisa de ser acessível através de um navegador a partir da Internet ou Intranet.", + "ConfigsPhpNotAccessible": "O endereço '%s' aparenta não ser acessível a partir da Internet ou Intranet.", + "ConfigsPhpSelfSignedError": "A solicitação de '%s' resultou num erro SSL. Talvez esteja a utilizar um certificado auto-assinado?", + "ConfigsPhpSuccess": "'%s' está acessível", + "ConfigsPhpUnknown": "Não conseguimos confirmar se '%s' é acessível pela Internet ou Intranet.", + "ConfigsPhpUnknownError": "A solicitação de '%1$s' resultou num erro: %2$s.", + "CreateNewHeatmap": "Criar novo mapa térmico", + "CreateNewSessionRecording": "Criar nova gravação de sessão", + "CreationDate": "Data de criação", + "DeleteHeatmapConfirm": "Tem a certeza que pretende eliminar este mapa térmico? Os relatórios dos mapas térmicos gerados anteriormente deixarão de estar disponíveis a partir do interface dos relatórios.", + "DeleteHeatmapScreenshotConfirm": "Tem a certeza que pretende eliminar esta captura de ecrã? Pode demorar algum tempo até a captura de ecrã ser novamente feita.", + "DeleteRecordedPageview": "Eliminar esta visualização de página (não é solicitada confirmação)", + "DeleteRecordedSession": "Eliminar esta sessão gravada (não é solicitada confirmação)", + "DeleteScreenshot": "Eliminar captura de ecrã", + "DeleteSessionRecordingConfirm": "Tem a certeza que pretende eliminar esta gravação da sessão? As gravações geradas anteriormente deixarão de estar disponíveis a partir do interface dos relatórios.", + "DeleteX": "Eliminar %s", + "EditHeatmapX": "Editar mapa térmico %s", + "EditSessionRecordingX": "Editar gravação de sessão %s", + "EditX": "Editar %s", + "EndHeatmapConfirm": "Tem a certeza que pretende parar a captura de quaisquer atividades para este mapa térmico?", + "EndSessionRecordingConfirm": "Tem a certeza que pretende parar a gravação de quaisquer novas sessões?", + "ErrorArrayMissingKey": "Falta a chave \"%1$s\" do vetor \"%2$s\" na posição \"%3$s\".", + "ErrorArrayMissingValue": "Falta o valor para a chave \"%1$s\" do vetor \"%2$s\" na posição \"%3$s\".", + "ErrorHeatmapDoesNotExist": "O mapa térmico solicitado não existe", + "ErrorHeatmapNameDuplicate": "O nome do mapa térmico já está a ser utilizado por outro mapa térmico.", + "ErrorInnerIsNotAnArray": "Cada \"%1$s\" dentro de \"%2$s\" tem de ser um vetor.", + "ErrorInvalidRegExp": "A expressão regular \"%1$s\" não tem um formato válido.", + "ErrorNotAnArray": "\"%1$s\" tem de ser um vetor.", + "ErrorPageRuleRequired": "Tem de ser definido pelo menos uma regra de página.", + "ErrorSessionRecordingDoesNotExist": "A gravação da sessão solicitada não existe", + "ErrorXContainsWhitespace": "Não é permitido que \"%1$s\" contenha qualquer espaço em branco.", + "ErrorXNotANumber": "\"%1$s\" tem de ser um número.", + "ErrorXNotProvided": "Por favor, forneça um valor para \"%1$s\".", + "ErrorXNotWhitelisted": "O valor para \"%1$s\" não é permitido; utilize um de: %2$s.", + "ErrorXTooHigh": "\"%1$s\" é demasiado alto, o valor máximo permitido é %2$s.", + "ErrorXTooLong": "\"%1$s\" é demasiado longo; pode utilizar um máximo de %2$s carateres.", + "ErrorXTooLow": "\"%1$s\" é demasiado baixo, o valor mínimo permitido é %2$s.", + "ExcludedElements": "Elementos excluídos", + "ExcludedElementsHelp": "Opcionalmente pode definir seletores CSS para excluir determinados elementos que não devem ser visíveis na pré-visualização do mapa térmico. Por exemplo, um popup que seja apresentado quando a página-alvo seja aberta. Pode separar múltiplos seletores com uma vírgula.", + "FieldIncludedTargetsHelp": "Os alvos permitem-lhe definir em que páginas as atividades de um visitante devem ser gravadas. Pode definir uma ou mais condições. Por exemplo, pode definir que a gravação de atividades ocorre sempre que o endereço ou caminho é igual a um determinado valor ou apenas se um determinado parâmetro do endereço estiver presente no mesmo. As atividades serão gravadas apenas quando todas as condições se verificarem, e não quando apenas uma se confirmar. Todas as condições serão avaliadas sem diferenciação entre maiúsculas e minúsculas. Quando seleciona 'igualdade simples', o protocolo, parâmetros da consulta e barra invertida do endereço serão ignorados.", + "FieldIncludedTargetsHelpSessions": "Os alvos permitem-lhe definir para apenas se iniciar a gravação de uma sessão assim que um visitante atingir uma determinada página. Isto permite, por exemplo, que apenas grave sessões que passem por um processo específico. Pode definir uma ou mais condições. Por exemplo, pode definir que a gravação da sessão ocorre sempre que o endereço ou caminho é igual a um determinado valor ou apenas se um determinado parâmetro do endereço estiver presente no mesmo. As sessões serão gravadas apenas quando todas as condições se verificarem na visualização de uma página, e não quando apenas uma se confirmar. Todas as condições serão avaliadas sem diferenciação entre maiúsculas e minúsculas. Quando seleciona 'igualdade simples', o protocolo, parâmetros da consulta e barra invertida do endereço serão ignorados.", + "FieldNamePlaceholder": "por exemplo, 'Página de registo'", + "FilesystemDirectory": "diretório", + "Filter": "Filtro", + "GettingStarted": "Começar", + "Heatmap": "Mapa térmico", + "HeatmapCreated": "O mapa térmico foi criado com sucesso.", + "HeatmapNameHelp": "Define o nome sob o qual o relatório para este mapa térmico ficará disponível.", + "HeatmapSampleLimit": "Número de visualizações de páginas", + "HeatmapSampleLimitHelp": "Define a quantidade de visualizações de páginas que pretende guardar no total.", + "HeatmapSampleRateHelp": "Também conhecido como 'tráfego'. Quando selecionar 100%%, todos os visitantes que visitem a página-alvo selecionada serão gravados. Quando selecionar, por exemplo 10%%, apenas 10 em cada 100 visitantes serão gravados. Quanto menor a percentagem que selecionar, mais tempo irá demorar a atingir o limite de amostras selecionado.", + "HeatmapUpdated": "O mapa térmico foi atualizado com sucesso.", + "HeatmapUsageBenefits": "Os mapas térmicos permitem-lhe gravar todos os cliques, movimentos do rato e atividades de deslocamento dos seus visitantes numa determinada página. Isto ajuda-o a perceber onde os utilizadores pensam que existe algo clicável mas que não é, se existem partes da página que raramente são vistas ou manipuladas, o que os utilizadores estão à procura, que parte da página é visível quando os utilizadores vêm a sua página, entre outras informações.", + "HeatmapWidth": "Largura do mapa térmico", + "HeatmapX": "Mapa térmico %s", + "HeatmapXRecordedSamplesSince": "Foram guardadas %1$s amostras desde %2$s.", + "Heatmaps": "Mapas térmicos", + "Manage": "Gerir", + "ManageHeatmaps": "Gerir mapas térmicos", + "ManageSessionRecordings": "Gerir gravações de sessões", + "MinSessionTime": "Tempo mínimo de sessão", + "MinSessionTimeHelp": "Uma sessão só será guardada quando o visitante despender numa página, pelo menos, o tempo especificado.", + "NHeatmaps": "%s mapas térmicos", + "NSessionRecordings": "%s gravações de sessões", + "NoHeatmapSamplesRecordedYet": "Ainda não foram guardadas visualizações de páginas para este mapa térmico. Se já era suposto ter gravações, talvez as páginas-alvo configuradas para este mapa térmico não coincidam com qualquer página no seu site. Também é recomendável fazer a \"Verificação de sistema\" sob \"Administração\" (apenas para super-utilizadores) para ver se o seu sistema está configurado para acompanhar automaticamente mapas térmicos.", + "NoHeatmapScreenshotRecordedYet": "Já foram gravadas %1$s amostras até ao momento. No entanto, ainda não foi feita qualquer captura de ecrã até ao momento. Se existe um \"%2$s\" para este mapa térmico, pode demorar um pouco para a captura de ecrã ficar disponível dado que um utilizador tem de primeiro abrir este endereço da captura de ecrã. Dependendo da taxa de amostragem, pode demorar um pouco.", + "NoHeatmapsConfiguredInfo": "Não existem mapas térmicos ativos. Para ver mapas térmicos, por favor, solicite a um utilizador com um acesso de administrador para criar um novo mapa térmico.", + "NoHeatmapsFound": "Não foram encontrados mapas térmicos", + "NoSessionRecordedYet": "Ainda não foram guardadas sessões. Se já era suposto ter sessões guardadas, talvez a página de entrada configurada não coincida com qualquer página no seu site. Também é recomendável fazer a \"Verificação de sistema\" sob \"Administração\" (apenas para super-utilizadores) para ver se o seu sistema está configurado para gravações automáticas de sessões.", + "NoSessionRecordingsConfiguredInfo": "Não existem gravações de sessões ativas. Para gravar novas sessões, por favor, solicite a um utilizador com um acesso de administrador para criar uma nova gravação.", + "NoSessionRecordingsFound": "Não foram encontradas gravações de sessão", + "NotSupportedBrowser": "Este navegador não é suportado. Por favor, utilize uma versão mais recente ou experimente um navegador diferente.", + "OnePageview": "1 visualização de página", + "PageRule": "Regra de página", + "PageviewXofY": "Visualização de página %1$s de %2$s", + "PageviewsInVisit": "Visualizações de páginas guardadas para esta sessão", + "PersonalInformationNote": "Por favor, note que um %1$s pode gravar informações pessoais ou sensíveis. Se quiser ocultar determinado conteúdo no seu site ou aplicação, pode etiquetar este conteúdo adicionando um atributo %2$sdata-matomo-mask%3$s na marcação HTML. %4$sSaber mais%5$s", + "PlayRecordedSession": "Reproduzir esta sessão gravada", + "PlayerDurationXofY": "%1$s de %2$s", + "PlayerForwardFast": "Saltar %1$s segundos (atalho %2$s)", + "PlayerPageViewNext": "Reproduzir a visualização de página seguinte para este visitante (%1$s) (atalho %2$s)", + "PlayerPageViewPrevious": "Reproduzir a visualização de página anterior para este visitante %1$s (atalho %2$s)", + "PlayerPause": "Pausar (atalho %s ou espaço)", + "PlayerPlay": "Reproduzir (atalho %s ou espaço)", + "PlayerReplay": "Reproduzir (atalho %s ou espaço)", + "PlayerRewindFast": "Retroceder %1$s segundos (atalho %2$s)", + "RecordedHeatmapDocStatusActive": "Este mapa térmico está ativo. Serão gravadas até %1$d visualizações de páginas com uma taxa de amostragem de %2$s.", + "RecordedHeatmapDocStatusEnded": "Este mapa térmico terminou. Não serão gravadas mais atividades.", + "RecordedSessions": "Sessões gravadas", + "RecordedSessionsDocStatusActive": "Esta gravação da sessão está ativa. Serão gravadas até %1$d sessões com uma taxa de amostragem de %2$s.", + "RecordedSessionsDocStatusEnded": "Esta gravação da sessão terminou e não serão gravadas novas sessões.", + "Recordings": "Gravações", + "ReplayRecordedSession": "Reproduzir sessão gravada", + "ReplayX": "Reproduzir %s", + "ReportRecordedSessionsDocumentation": "Uma lista de todas as sessões que foram guardadas. Pode reproduzir qualquer sessão guardada.", + "RequiresActivity": "Requer atividade", + "RequiresActivityHelp": "Se ativo, apenas sessões que tenham atividades de clique ou deslocamento numa visualização de página serão guardadas. Isto impede a gravação de sessões sem muita atividade.", + "Rule": "Regra", + "SampleLimit": "Limite de amostras", + "SampleRate": "Taxa de amostragem", + "ScreenshotUrl": "Endereço da captura de ecrã", + "ScreenshotUrlHelp": "Por predefinição, é feita uma captura de ecrã quando o primeiro visitante visualiza a página-alvo. Se vários endereços de páginas diferentes coincidem com a página-alvo, pode especificar o endereço que deve ser utilizado para capturar o ecrã. A visualização do mapa térmico só estará disponível quando pelo menos um utilizador visitar este endereço cujas atividades são gravadas. Se definido, o endereço tem de coincidir em absoluto. Para ignorar o protocolo, comece o endereço com duas barras invertidas (//example.com). Por favor, note que este endereço não poderá ser alterado a partir do momento que uma captura de ecrã tenha sido feita.", + "SessionNameHelp": "Define o nome sob o qual as gravações de sessões ficarão disponíveis.", + "SessionRecording": "Gravação de sessão", + "SessionRecordingCreated": "A gravação de sessão foi criada com sucesso.", + "SessionRecordingUpdated": "A gravação de sessão foi atualizada com sucesso.", + "SessionRecordingX": "Gravação da sessão %s", + "SessionRecordings": "Gravações de sessões", + "SessionRecordingsUsageBenefits": "As gravações de sessões permitem-lhe guardar todas as atividades de um utilizador em tempo-real durante a sua sessão (visita), tais como cliques, movimentos do rato, deslocamentos, dimensionamento de janelas, alterações na página e interações em formulários. Pode depois reproduzir estas interações para ver, exatamente, como um visitante interagiu com o seu site. Desta forma pode perceber as expetativas, problemas, padrões de utilização, etc., que estes têm.", + "SessionSampleLimit": "Número de sessões", + "SessionSampleLimitHelp": "Define a quantidade de sessões que pretende guardar no total.", + "SessionSampleRateHelp": "Também conhecido como 'tráfego'. Quando selecionar 100%%, todas as sessões serão gravadas assim que atinjam a página-alvo. Quando selecionar, por exemplo 10%%, apenas 10 em cada 100 sessões serão gravadas. Quanto menor a percentagem que selecionar, mais tempo irá demorar a atingir o limite de amostras selecionado.", + "StatusActive": "Ativo", + "StatusEnded": "Terminou", + "StopX": "Parar a gravação de quaisquer novas atividades para este %s.", + "TargetAttributePath": "Caminho", + "TargetAttributeUrl": "Endereço", + "TargetAttributeUrlParameter": "Parâmetro do endereço", + "TargetAttributeUrlParameterExample": "nomeDoParametroDoEndereco", + "TargetPage": "Página-alvo", + "TargetPageTestErrorInvalidUrl": "Introduza um endereço, incluindo o protocolo.", + "TargetPageTestLabel": "Introduza um endereço completo, incluindo o protocolo, para confirmar se as atividades serão gravadas neste endereço:", + "TargetPageTestTitle": "Validador do endereço", + "TargetPageTestUrlMatches": "As atividades serão gravadas para este endereço", + "TargetPageTestUrlNotMatches": "As atividades não serão gravadas para este endereço", + "TargetPages": "Páginas de entrada", + "TargetTypeContains": "contém", + "TargetTypeEqualsExactly": "exatamente igual", + "TargetTypeEqualsExactlyInfo": "O valor deve coincidir em absoluto, incluindo o protocolo do endereço, termos de pesquisa e ancora.", + "TargetTypeEqualsSimple": "igualdade simples", + "TargetTypeEqualsSimpleInfo": "O endereço irá coincidir qualquer protocolo (por exemplo, http e https) com e sem o subdomínio \"www.\". Qualquer barra transversal no caminho, bem como os parâmetros de consulta e âncora que sejam parte do endereço serão ignorados na comparação do endereço.", + "TargetTypeExists": "existe", + "TargetTypeIsAny": "é qualquer", + "TargetTypeIsNot": "não %s", + "TargetTypeRegExp": "coincide com a expressão regular", + "TargetTypeRegExpInfo": "Qualquer expressão regular, por exemplo, \"^(.*)test(.*)$\".", + "TargetTypeStartsWith": "começa com", + "TimeOnSite": "Tempo no site", + "UpdatingData": "A atualizar dados…", + "UrlParameterValueToMatchPlaceholder": "Valor a coincidir com o nome do parâmetro do endereço", + "UrlXDoesNotLookLikeUrl": "%s não parece ser um endereço. Confirme que contém um protocolo como http:// ou que começa com //", + "ViewReport": "Ver relatório", + "ViewportResolution": "Resolução da área de visualização (largura x altura)", + "XSamples": "%s amostras", + "disable": "desativar", + "enable": "ativar" + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/ro.json b/files/plugin-HeatmapSessionRecording-5.2.4/lang/ro.json new file mode 100644 index 0000000..c6ed30e --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/lang/ro.json @@ -0,0 +1,6 @@ +{ + "HeatmapSessionRecording": { + "Heatmap": "Hartă termică", + "SessionRecording": "Înregistrare sesiune" + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/ru.json b/files/plugin-HeatmapSessionRecording-5.2.4/lang/ru.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/lang/ru.json @@ -0,0 +1 @@ +{} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/sq.json b/files/plugin-HeatmapSessionRecording-5.2.4/lang/sq.json new file mode 100644 index 0000000..96cc617 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/lang/sq.json @@ -0,0 +1,237 @@ +{ + "HeatmapSessionRecording": { + "Action": "Veprim", + "ActivityClick": "Klikim", + "ActivityFormChange": "Ndryshim Forme", + "ActivityFormText": "Ndryshim Teksti Formulari", + "ActivityFormValue": "Ndryshim Vlere Formulari", + "ActivityInitialDom": "Faqe Fillestare", + "ActivityMove": "Lëvizje", + "ActivityPageChange": "Ndryshoje Brenda Faqes", + "ActivityResize": "Ripërmasim", + "ActivityScroll": "Rrëshqitje", + "ActivityScrollElement": "Element Rrëshqitjeje", + "AdBlockerDetected": "%1$s mund të mos jetë e dukshme, kur përdoret një bllokues reklamash. Ju lutemi, çaktivizoni bllokuesin e reklamave në këtë sajt, për të garantuar se Matomo funksionon pa ndonjë problem.", + "AdvancedOptions": "Mundësi të mëtejshme", + "AutoPlayNextPageview": "Klikoni që të %1$s vetëluhet pamja e faqes pasuese (shkurtore %2$s)", + "AvgAboveFoldDescription": "Mesatarisht, përdoruesit shohin lëndën mbi këtë vijë pa rrëshqitje", + "AvgAboveFoldTitle": "Mes. Mbi Dosje %spx", + "BreakpointGeneralHelp": "Kur një vizitor sheh sajtin tuaj, lloji i pajisjes zbulohet vetvetiu. Por ndonjëherë mund të mos jetë e mundur të zbulohet lloji i pajisjes. Nëse sajti juaj është reagues dhe skema e tij shformohet në një pikë të caktuar, për harta të përpikta veprimesh ne do ta kalojmë përdoruesin nën një qartësi më të vogël për këtë lloj pajisjeje. Çfarëdo gjerësie më e vogël se kjo vlerë, do të kalohet në këtë lloj pajisjeje. Nëse sajti juaj s’është reagues, caktojeni zero.", + "BreakpointGeneralHelpManage": "Një përdorues me të drejta Superpërdoruesi mund të ndryshojë vlerat parazgjedhje për këto pika shformimi, që nga Administrim => Rregullime të Përgjithshme.", + "BreakpointX": "Pikë shformimi %s", + "CaptureDomInlineHelp": "Si parazgjedhje, fotografimet e hartës së veprimeve bëhen automatikisht gjatë ngarkimit të faqes. Nëse faqja juaj ngarkon elementë në mënyrë dinamike (b.f., “lazy loading”) fotografimi juaj mund të ishte i paplotë. Mundeni të fotografoni dorazi një pamje të plotë, duke aktivizuar këtë mundësi dhe duke ekzekutuar në shfletuesin tuaj, kodin Javascript: %1$s %2$sShënim:%3$s Kjo mundësi rikthehet automatikisht te parazgjedhja, pas bërjes së fotografimit.", + "CaptureDomTitle": "Fotografoni Dorazi Pamje Harte Veprimesh", + "CaptureKeystrokes": "Regjistro shtypje tastesh", + "CaptureKeystrokesHelp": "Në u aktivizoftë, çfarëdo teksti që jepet në fusha formularësh, regjistrohet. Teksa teksti regjistrohet, çfarëdo shenje që përdoruesi përdor zëvendësohet me një yll ('*'). Duke u caktuar atributin “data-matomo-unmask”, mund të lejoni që fusha formulari të regjistrohen si tekst i thjeshtë. Sido qoftë, fusha për fjalëkalime dhe mjaft fusha të tjera, të cilat mundet potencialisht të përfshijnë të dhëna personale (adresë, email, të dhëna karte krediti, emër përdoruesi, numër telefoni, …) do të maskohen automatikisht, në u zbulofshin prej nesh si të tilla (%1$smësoni më tepër%2$s). Ju lutemi, kini parasysh se kur aktivizoni këtë veçori, mund të regjistroni të dhëna personale, gjë që mund të prekë GDPR-në.", + "ChangeReplaySpeed": "Ndryshoni shpejtësi luajtjeje (shkurtore %s)", + "ClickRate": "Nivel klikimi:", + "ClickToSkipPauses": "Klikoni që të %1$s anashkalohen ndalesat (shkurtore %2$s)", + "Clicks": "Klikime:", + "ColumnActionsDocumentation": "Luani sesionin e regjistruar, ose hiqeni regjistrimin përgjithmonë.", + "ColumnBrowserDocumentation": "Shfletuesi që po përdorte vizitori, kur u regjistrua ky sesion.", + "ColumnDeviceDocumentation": "Pajisja që po përdorte vizitori, kur u regjistrua ky sesion.", + "ColumnLabelRecordedSessions": "URL Hyrjeje → URL Daljeje", + "ColumnLocationDocumentation": "Vendndodhja e vizitorit, kur u regjistrua ky sesion.", + "ColumnOperatingSystem": "OS", + "ColumnOperatingSystemDocumentation": "Sistemi operativ që po përdorte vizitori, kur u regjistrua ky sesion.", + "ColumnPageviewsDocumentation": "Numri i parjeve të faqeve që janë regjistruar gjatë këtij sesioni.", + "ColumnResolutionDocumentation": "Numri i pikselave të pjesës së dukshme në dritaren e shfletuesit, kur përdoruesi hapi këtë faqe.", + "ColumnTime": "Kohë", + "ColumnTimeDocumentation": "Koha kur nisi regjistrimi, sipas zonës kohore të sajtit.", + "ColumnTimeOnPageDocumentation": "Sa kohë shpenzoi vizitori në këtë faqe, teksa regjistrohej sesioni", + "ColumnTimeOnSiteDocumentation": "Sa kohë shpenzoi vizitori gjithsej, teksa regjistrohej sesioni", + "ColumnTotalEventsDocumentation": "Numri i akteve që qenë regjistruar gjatë këtij sesioni. Për shembull: akte klikimi, lëvizjeje, rrëshqitjeje, ripërmasimi, ndryshimi faqesh dhe ndërveprimi me formular.", + "ConfigsInternetDisabled": "S’kontrolluam dot nëse “%s” është i përdorshëm, ngaqë në këtë Matomo është e çaktivizuar lidhja në Internet.", + "ConfigsPhpErrorResult": "Si rrjedhojë, ndjekja e Hartave të Veprimeve dhe Regjistrimet e Sesionit mund të mos funksionojnë. Mund t’ju duhet të ndryshoni formësimet e shërbyesit tuaj, në mënyrë që të lejoni përdorimin e kësaj kartele që nga Interneti apo Intraneti.", + "ConfigsPhpManualCheck": "Ju lutemi, hapeni URL-në dorazi në një shfletues për të parë nëse përgjigja përmban “Piwik.HeatmapSessionRecording”. Nëse jo, mund t’ju duhet të ndryshoni formësimin e shërbyesit tuaj, ngaqë kjo kartelë lyp të jetë e përdorshme përmes një shfletuesi që nga Interneti ose Intraneti.", + "ConfigsPhpNotAccessible": "URL-ja “%s” duket se nuk është e kapshme prej Interneti apo Intraneti.", + "ConfigsPhpSelfSignedError": "Kërkesa për “%s” solli një gabim SSL. Mos vallë përdorni një dëshmi të vetënënshkruar?", + "ConfigsPhpSuccess": "“%s” është i kapshëm", + "ConfigsPhpUnknown": "S’kontrolluam dot nëse “%s” është i përdorshëm në Internet apo Intranet.", + "ConfigsPhpUnknownError": "Kërkimi i “%1$s” solli një gabim: %2$s.", + "Country": "Vend", + "CreateNewHeatmap": "Krijoni hartë të re veprimesh", + "CreateNewSessionRecording": "Krijoni regjistrim të ri sesioni", + "CreationDate": "Datë Krijimi", + "DeleteHeatmapConfirm": "Jeni i sigurt se doni të fshihet kjo hartë veprimesh? Raporte për harta veprimesh të prodhuara më herët s’do të jenë më të passhëm që nga UI e raporteve.", + "DeleteHeatmapScreenshotConfirm": "Jeni i sigurt se doni të fshihet kjo foto ekrani? Ribërja e fotos së ekranit mund të zgjasë ca.", + "DeleteRecordedPageview": "Fshije këtë parje të regjistruar faqeje (pa kërkuar ripohim)", + "DeleteRecordedSession": "Fshije këtë sesion të regjistruar (pa kërkuar ripohim)", + "DeleteScreenshot": "Fshije Foton e Ekranit", + "DeleteSessionRecordingConfirm": "Jeni i sigurt se doni të fshihet ky regjistrim sesioni? S’do të jenë më të passhëm që nga UI e raporteve.", + "DeleteX": "Fshije %s", + "DeviceType": "Lloj Pajisjeje", + "DisableHeatmapRecordingDescription": "Duke çaktivizuar këtë veçori, s’mund të formësohet më, apo regjistrohet ndonjë hartë veprimesh.", + "DisableHeatmapRecordingInlineHelp": "%1$sShënim: Çaktivizimi i kësaj veçorie do të fshijë krejt hartat e veprimeve, përfshi ato të ndjekura më parë. S’do të ketë ndryshime për regjistrim sesionesh, nëse aktivizoni, ose çaktivizoni këtë veçori.%2$s", + "DisableHeatmapRecordingTitle": "Çaktivizo Harta Veprimesh", + "DisableSessionRecordingDescription": "Duke çaktivizuar këtë veçori, s’mund të formësohen, apo ndiqen më regjistrime sesionesh.", + "DisableSessionRecordingInlineHelp": "%1$sShënim: Çaktivizimi i kësaj veçorie do të fshijë krejt regjistrimet e sesioneve, përfshi regjistrime sesionesh ndjekur më parë. S’do të ketë ndryshime për harta veprimesh, nëse aktivizoni, ose çaktivizoni këtë veçori.%2$s", + "DisableSessionRecordingTitle": "Çaktivizo Regjistrim Sesionesh", + "EditHeatmapX": "Përpunoni Hartë Veprimesh %s", + "EditSessionRecordingX": "Përpunoni Regjistrim Sesioni %s", + "EditX": "Përpunoni %s", + "EnableIncludeCountriesDescription": "Si parazgjedhje, regjistrohen krejt vizitat. Aktivizojeni këtë pjesë, kur doni të ndiqni vizita nga vende të caktuara.", + "EnableIncludeCountriesTitle": "Ndiq vetëm vizitorë prej vendesh të caktuara", + "EndHeatmapConfirm": "Jeni i sigurt se doni të ndalet regjistrimi i çfarëdo veprimtarish për këtë hartë veprimesh?", + "EndSessionRecordingConfirm": "Jeni i sigurt se doni të ndalet regjistrimi i çfarëdo sesionesh të reja?", + "ErrorArrayMissingKey": "Mungon kyç matrice “%1$s” te “%2$s” në pozicionin “%3$s”.", + "ErrorArrayMissingValue": "Mungon vlerë për kyç matrice “%1$s” te “%2$s” në pozicionin “%3$s”.", + "ErrorHeatmapDoesNotExist": "Harta e kërkuar e veprimeve s’ekziston", + "ErrorHeatmapNameDuplicate": "Emri i hartës së veprimeve është tashmë në përdorim nga një tjetër hartë veprimesh.", + "ErrorHeatmapRecordingDisabled": "Veçoria e regjistrimit për harta veprimesh është e çaktivizuar. Për të përdorur këtë veçori, lypset të aktivizohet nga një superpërdorues i Matomo-s, te rregullimet e përgjithshme.", + "ErrorInnerIsNotAnArray": "Çdo “%1$s” brenda “%2$s” duhet të jetë një matricë.", + "ErrorInvalidRegExp": "Shprehja e rregullt “%1$s” s’ka format të vlefshëm.", + "ErrorNotAnArray": "“%1$s” duhet të jetë një matricë.", + "ErrorPageRuleRequired": "Duhet caktuar të paktën një rregull faqeje.", + "ErrorSessionRecordingDisabled": "Veçoria e regjistrimit të sesionit është e çaktivizuar. Për të përdorur këtë veçori, lypset të aktivizohet nga një superpërdorues i Matomo-s, te rregullimet e përgjithshme.", + "ErrorSessionRecordingDoesNotExist": "Regjistrimi i sesioneve i kërkuar s’ekziston", + "ErrorXContainsWhitespace": "“%1$s” s’lejohet të përmbajë hapësirë të zbrazët.", + "ErrorXNotANumber": "“%1$s” duhet të jetë një numër.", + "ErrorXNotProvided": "Ju lutemi, jepni një vlerë për “%1$s”.", + "ErrorXNotWhitelisted": "Vlera për “%1$s” s’lejohet, përdorni një nga: %2$s.", + "ErrorXTooHigh": "“%1$s” është shumë i lartë, vlera maksimum e lejuar është %2$s.", + "ErrorXTooLong": "“%1$s” është shumë i gjatë, lejohen maksimumi %2$s shenja.", + "ErrorXTooLow": "“%1$s” është shumë i ulët, vlera minimum e lejuar është %2$s.", + "ExcludedElements": "Elementë të Përjashtuar", + "ExcludedElementsHelp": "Nëse doni, mund të përkufizoni përzgjedhës CSS për të përjashtuar disa elementë të cilët s’duhet të jenë të dukshëm gjatë paraparjes së hartës së veprimeve. Për shembull, një flluskë që shfaqet kur hapet faqja e synuar. Përzgjedhës të shumtë mund t’i ndani me presje.", + "FieldIncludedTargetsHelp": "Objektivat ju lejojnë të përkufizoni se në cilat faqe duhet regjistruar veprimtaria e përdoruesit. Mund të përkufizoni një ose më shumë kushte. Për shembull, mund të përcaktoni të regjistrohet veprimtari kurdo që URL-ja ose shtegu është baras me një vlerë të dhënë dhe vetëm nëse në të është i pranishëm një parametër URL i dhënë. Veprimtaritë do të regjistrohen vetëm kur plotësohen të tëra kushtet, jo kur plotësohet vetëm një prej tyre. Krejt kushtet do peshohen pa marrë parasysh shkrimin me të mëdha apo me të vogla. Kur përzgjidhni “është e barabartë thjesht me”, protokolli URL, parametrat e kërkimit dhe një pjerrake paraprijëse do të shpërfillen.", + "FieldIncludedTargetsHelpSessions": "Objektivat ju lejojnë të formësoni nisjen e regjistrimit të një sesioni vetëm sapo një vizitor të ketë mbërritur në një faqe të caktuar. Kjo ju lejon, për shembull, të regjistroni vetëm sesione që kalojnë nëpër procesin e përfundimit të blerjes. Mund të përcaktoni një ose më shumë kushte. Për shembull, mund të përcaktoni të regjistroni një sesion, vetëm kur URL-ja ose shtegu është baras me një vlerë të dhënë dhe vetëm nëse te URL-ja është i pranishëm një parametër URL i dhënë. Sesionet do të regjistrohen vetëm kur gjatë parjes së një faqeje plotësohen të tëra kushtet, jo kur plotësohet vetëm një prej tyre. Krejt kushtet do peshohen pa marrë parasysh shkrimin me të mëdha apo me të vogla. Kur përzgjidhni “është e barabartë thjesht me”, protokolli URL, parametrat e kërkimit dhe një pjerrake paraprijëse do të shpërfillen.", + "FieldNamePlaceholder": "p.sh., “Faqe Regjistrimi”", + "FilesystemDirectory": "drejtori", + "Filter": "Filtroni", + "GettingStarted": "Si T’ia Fillohet", + "Heatmap": "Hartë veprimesh", + "HeatmapAddedActivity": "u shtua hartë veprimesh “%1$s” për sajtin “%2$s”", + "HeatmapCreated": "Harta e veprimeve u krijuar me sukses.", + "HeatmapDeletedActivity": "u fshi hartë veprimesh “%1$s” për sajtin “%2$s”", + "HeatmapEndedActivity": "u përfundua harta e veprimeve “%1$s” për sajtin “%2$s”", + "HeatmapInfoTrackVisitsFromCountries": "Harta e veprimeve është formësuar të ndjekë vetëm vizita nga %1$s.", + "HeatmapNameHelp": "Përcakton emrin nën të cilin do të jetë i gatshëm raporti për këtë hartë veprimesh.", + "HeatmapPausedActivity": "u ndal harta e veprimeve “%1$s” për sajtin “%2$s”", + "HeatmapResumedActivity": "u kthye në punë harta e veprimeve “%1$s” për sajtin “%2$s”", + "HeatmapSampleLimit": "Numër parjesh faqesh", + "HeatmapSampleLimitHelp": "Përcakton sasinë e parjeve të faqes që doni të regjistrohen gjithsej.", + "HeatmapSampleRateHelp": "E njohur edhe si “trafik”. Kur përzgjidhni 100%%, do të regjistrohen krejt vizitorët që vizitojnë faqet e synuar të përzgjedhur. Kur përzgjidhni, për shembull 10%%, do të regjistrohet vetëm çdo vizitor i dhjetë. Sa më e vogël përqindja që përzgjidhni, aq më shumë kohë do të duhet për të mbërritur në shpejtësinë e përzgjedhur të kampionizimit.", + "HeatmapScreenshotDeletedActivity": "u fshi foto ekrani harte veprimesh “%1$s” për sajtin “%2$s”", + "HeatmapTroubleshoot": "Keni probleme? %1$sMësoni më tepër%2$s.", + "HeatmapUpdated": "Harta e veprimeve u përditësua me sukses.", + "HeatmapUpdatedActivity": "u përditësua harta e veprimeve “%1$s” për sajtin “%2$s”", + "HeatmapUsageBenefits": "Hartat e veprimeve ju lejojnë të regjistroni krejt klikimet, lëvizjet e kursorit dhe akte rrëshqitjesh në faqe të vizitorëve tuaj në një faqe të dhënë. Kjo ju ndihmon të gjeni se ku mendojnë përdoruesit se diçka është e klikueshme, por në fakt s’është, nëse pjesë të faqes thjesht po shihen apo ka ndërveprim me to, për çfarë po shohin vizitorët tuaj, ç’përqindje e faqes është e dukshme kur vizitorët shohin një faqe tuajën, etj.", + "HeatmapWidth": "Gjerësi harte veprimesh", + "HeatmapX": "Hartë veprimesh %s", + "HeatmapXRecordedSamplesSince": "Janë regjistruar %1$s kampione që prej %2$s.", + "Heatmaps": "Harta veprimesh", + "Manage": "Administroni", + "ManageHeatmapSubcategoryHelp": "Hartat e veprimeve ju lejojnë të regjistroni krejt klikimet, lëvizjet e kursorit dhe akte rrëshqitjesh në faqe të vizitorëve tuaj për një faqe specifike. Kjo ndarje ju jep mundësinë të krijoni dhe administroni ndjekje Hartash Veprimesh për sajtin tuaj.", + "ManageHeatmaps": "Administroni Harta Veprimesh", + "ManageSessionRecordingSubcategoryHelp": "Regjistrimet e sesioneve ju lejojnë të regjistroni krejt aktet e një vizitori të njëmendtë gjatë sesionit (vizitës) së tij, fjala vjen, klikime, lëvizje të kursorit, rrëshqitje në faqe, ripërmasime dritareje, ndryshime faqeje dhe ndërveprime me formularë. Kjo ndarje ju jep mundësinë të krijoni dhe administroni Regjistrime Sesionesh për sajtin tuaj.", + "ManageSessionRecordings": "Administroni Regjistrime Sesionesh", + "MatomoJSNotWritableErrorMessage": "HeatmapSessionRecording: %1$s s’janë në gjendje të ndjekin vizita, ngaqë kartela matomo.js është vetëm-për-lexim. Se si ta bëni kartelën të shkrueshme, ju lutemi, shihni %2$sdokumentimin%3$s.", + "MinSessionTime": "Kohë Minimum Sesioni", + "MinSessionTimeHelp": "Një sesion do të regjistrohet vetëm kur një përdorues ka shpenzuar në faqe të paktën kohën e përcaktuar.", + "MoveRate": "Nivel lëvizjeje:", + "Moves": "Lëvizje:", + "NHeatmaps": "%s harta veprimesh", + "NSessionRecordings": "Regjistrime sesioni %s", + "NoHeatmapSamplesRecordedYet": "S’është regjistruar ende ndonjë parje faqeje për këtë hartë veprimesh. Nëse duhej të kishte sesione të regjistruar tashmë deri këtu, ndoshta faqet e synuara për këtë hartë veprimesh nuk kanë përputhje me ndonjë faqe në sajtin tuaj. Këshillohet gjithashtu të kontrolloni “Kontroll Sistemi” nën “Administrim” (vetëm Superpërdoruesit), për të parë nëse sistemi juaj është formësuar të ndjekë vetvetiu Harta veprimesh.", + "NoHeatmapSamplesRecordedYetWithoutSystemConfiguration": "Për këtë hartë veprimesh, s’është regjistruar parje faqesh. Nëse pritej që të kishte të regjistruara deri tani, ndoshta objektivat faqe të formësuar për këtë hartë veprimesh nuk kanë përputhje me ndonjë faqe në sajtin tuaj.", + "NoHeatmapScreenshotRecordedYet": "Deri këtu janë regjistruar %1$s kampione. Por ende nuk është bërë ndonjë foto ekrani. Nëse ka një “%2$s” për këtë hartë veprimesh, mund të duhet ca kohë që të ketë një foto ekrani, ngaqë përdoruesi së pari duhet ta hapë URL-në e kësaj fotoje ekrani. Në varësi të shkallës së kampionizimit, kjo mund të dojë ca kohë.", + "NoHeatmapsConfiguredInfo": "S’ka harta aktive veprimesh. Që të shihni harta veprimesh, ju lutemi, kërkojini një përdoruesi me të paktën leje përgjegjësi të krijojë një hartë të re veprimesh.", + "NoHeatmapsFound": "S’u gjetën harta veprimesh", + "NoSessionRecordedYet": "S’është regjistruar ende ndonjë sesion. Nëse duhej të kishte sesione të regjistruar tashmë deri këtu, ndoshta faqja e zërit të formësuar nuk ka përputhje me ndonjë faqe në sajtin tuaj. Këshillohet gjithashtu të kontrolloni “Kontroll Sistemi” nën “Administrim” (vetëm Superpërdoruesit), për të parë nëse sistemi juaj është formësuar të regjistrojë vetvetiu sesione.", + "NoSessionRecordedYetWithoutSystemConfiguration": "S’është regjistruar ende ndonjë sesion. Nëse pritej që të kishte sesione të regjistruar deri tani, ndoshta faqja e formësuar si hyrje s’përputhet me ndonjë faqe në sajtin tuaj.", + "NoSessionRecordingsConfiguredInfo": "S’ka regjistrime aktive sesioni. Që të regjistroni sesione të reja, ju lutemi, kërkojini një përdoruesi me, të paktën, leje përgjegjësi të krijojë një regjistrim të ri.", + "NoSessionRecordingsFound": "S’u gjetën regjistrime sesioni", + "NotSupportedBrowser": "Ky shfletues nuk mbulohet, ju lutemi përdorni një version të mëvonshëm, ose provoni me një tjetër shfletues.", + "OnePageview": "1 parje faqeje", + "PageRule": "Rregull Faqeje", + "PageviewXofY": "Parje faqesh %1$s nga %2$s", + "PageviewsInVisit": "Parje të regjistruara faqesh në këtë sesion", + "PauseReason": "%1$s u ndal, për shkak të plotësimit të kuotave tuaja, për hollësi të mëtejshme, ju lutemi, lidhuni me asistencën.", + "PeriodDisabledErrorMessage": "Periudha “%1$s” është e çaktivizuar, por e domosdoshme që kjo veçori të funksionojë. Përzgjidhni një periudhë tjetër, ose kërkojini një përgjegjësi të sistemit të ndryshojë kartelën “config/config.ini.php” të formësimit të Matomo-s, për të lejuar periudhën “%1$s” te rregullimi “enabled_periods_API”.", + "PersonalInformationNote": "Ju lutemi, kini parasysh se një %1$s mund të regjistrojë të dhëna personale ose të rezervuara. Nëse dëshironi të fshini një lëndë të caktuar në sajtin apo aplikacionin tuaj, mund ta etiketoni këtë lëndë duke shtuar një atribut %2$sdata-matomo-mask%3$s te pjesa HTML. %4$sMësoni më tepër%5$s", + "PlayRecordedSession": "Luaj këtë sesion të regjistruar", + "PlayerDurationXofY": "%1$s nga %2$s", + "PlayerForwardFast": "Kapërce %1$s sekonda (shkurtore %2$s)", + "PlayerPageViewNext": "Luaj parjen pasuese të faqes nga ky vizitor (%1$s) (shkurtore %2$s)", + "PlayerPageViewPrevious": "Luaj parjen e mëparshme të faqes nga ky vizitor %1$s (shkurtore %2$s)", + "PlayerPause": "Ndalesë (shkurtore %s ose hapësirë)", + "PlayerPlay": "Luaje (shkurtore %s ose hapësirë)", + "PlayerReplay": "Riluaje (shkurtore %s ose hapësirë)", + "PlayerRewindFast": "Prapaktheje %1$s sekonda (shkurtore %2$s)", + "RecordedHeatmapDocStatusActive": "Kjo hartë veprimesh është aktive. Do të regjistrohen deri në %1$d parje faqesh me një shpejtësi kampionizimi prej %2$s.", + "RecordedHeatmapDocStatusEnded": "Kjo hartë veprimesh ka përfunduar. S’do të regjistrohet veprimtari e re.", + "RecordedPageviewDeletedActivity": "u fshi një parje e regjistruar faqeje për regjistrimin e sesionit “%1$s” për sajtin “%2$s”", + "RecordedSessionDeletedActivity": "u fshi një sesion i regjistruar për regjistrimin e sesionit “%1$s” për sajtin “%2$s”", + "RecordedSessions": "Sesione të Regjistruar", + "RecordedSessionsDocStatusActive": "Ky regjistrim sesion është aktiv. Do të regjistrohen deri në %1$d sesione me një shpejtësi kampionizimi prej %2$s.", + "RecordedSessionsDocStatusEnded": "Ky regjistrim sesioni ka përfunduar dhe s’do të regjistrohen sesione të reja.", + "Recordings": "Regjistrime", + "ReplayRecordedSession": "Riluaj sesion të regjistruar", + "ReplayX": "Riluaje %s", + "ReportRecordedSessionsDocumentation": "Listë e krejt sesioneve që janë regjistruar. Mund të riluani cilindo sesion të regjistruar.", + "RequiresActivity": "Lyp veprimtari", + "RequiresActivityHelp": "Në u aktivizoftë, do të regjistrohen vetëm sesione që përmbajnë rrëshqitje dhe akte klikimi në një parje faqeje. Kjo pengon regjistrim sesionesh me jo shumë veprimtari.", + "Rule": "Rregull", + "SampleLimit": "Kufi Kampionizimi", + "SampleRate": "Shpejtësi Kampionizimi", + "ScreenshotUrl": "URL Fotoje Ekrani", + "ScreenshotUrlHelp": "Si parazgjedhje, një foto ekrani bëhet kur vizitori i parë sheh faqen e synuar. Nëse me faqen tuaj të synuar kanë përputhje URL të ndryshme faqesh, mund të përcaktoni një URL të dhënë që do të duhej përdorur për të bërë foton e ekrani. Vizualizimi i hartës së veprimeve do të jetë i mundshëm sapo të paktën një vizitor të ketë vizituar këtë URL, veprimtaritë e të cilit regjistrohen. Në u përcaktoftë, përputhja me URL-në duhet të jetë e saktë. Për të shpërfillur protokollin, fillojeni URL-në me dy pjerrake (//example.com). Ju lutemi, kini parasysh se, pas bërjes së fotos së ekranit, kjo URL s’mund të ndryshohet.", + "SessionNameHelp": "Përcakton emrin nën të cilin do të jetë i gatshëm regjistrimi i sesionit.", + "SessionRecording": "Regjistrim Sesioni", + "SessionRecordingAddedActivity": "u shtua një regjistrim sesioni “%1$s” për sajtin “%2$s”", + "SessionRecordingCreated": "Regjistrimi i sesionit u krijua me sukses.", + "SessionRecordingDeletedActivity": "u fshi regjistrimi i sesionit “%1$s” për sajtin “%2$s”", + "SessionRecordingEndedActivity": "mbaroi regjistrimi i sesionit “%1$s” për sajtin “%2$s”", + "SessionRecordingInfoTrackVisitsFromCountries": "Regjistrimi i sesioneve është formësuar të ndjekë vetëm vizita nga %1$s.", + "SessionRecordingPausedActivity": "u ndal regjistrim sesioni “%1$s” për sajtin “%2$s”", + "SessionRecordingResumedActivity": "u kthye në punë regjistrimi i sesionit “%1$s” për sajtin “%2$s”", + "SessionRecordingUpdated": "Regjistrimi i sesionit u përditësua me sukses.", + "SessionRecordingUpdatedActivity": "u përditësua regjistrimi i sesionit “%1$s” për sajtin “%2$s”", + "SessionRecordingX": "Regjistrim Sesioni %s", + "SessionRecordings": "Regjistrime Sesionesh", + "SessionRecordingsUsageBenefits": "Regjistrimet e sesioneve ju lejojnë të regjistroni krejt aktet e një vizitori të njëmendtë gjatë sesionit (vizitës) së tij, të tilla si klikime, lëvizje të kursorit, rrëshqitje në faqe, ripërmasime dritareje, ndryshime faqeje dhe ndërveprime me formularë. Mandej mundeni t’i rikryeni këto ndërveprime për të parë saktësisht se si ndërvepron me sajtin tuaj një vizitor. Në këtë mënyrë kuptoni se ç’prisnin, probleme që mund të kenë, rregullsi përdorimi, etj.", + "SessionSampleLimit": "Numër sesionesh", + "SessionSampleLimitHelp": "Përcakton sasinë e sesioneve që doni të regjistrohen gjithsej.", + "SessionSampleRateHelp": "E njohur edhe si “trafik”. Kur përzgjidhni 100%%, do të regjistrohen krejt sesionet, sapo të jetë mbërritur te faqja e synuar. Kur përzgjidhni, për shembull 10%%, do të regjistrohet vetëm çdo sesion i dhjetë. Sa më e vogël përqindja që përzgjidhni, aq më shumë kohë do të duhet për të mbërritur në shpejtësinë e përzgjedhur të kampionizimit.", + "StatusActive": "Aktiv", + "StatusEnded": "I përfunduar", + "StatusPaused": "Ndalur", + "StopX": "Resht së regjistruari çfarëdo veprimtarish të reja për këtë %s.", + "TargetAttributePath": "Shteg", + "TargetAttributeUrl": "URL", + "TargetAttributeUrlParameter": "Parametër URL-je", + "TargetAttributeUrlParameterExample": "emërParametriURL-je", + "TargetPage": "Faqe e Synuar", + "TargetPageTestErrorInvalidUrl": "Jepni një URL, përfshi protokollin.", + "TargetPageTestLabel": "Për të kontrolluar nëse veprimtaritë do të regjistrohen në këtë URL, jepni një URL të plotë, përfshi protokollin:", + "TargetPageTestTitle": "Vleftësues URL-sh", + "TargetPageTestUrlMatches": "Veprimtaritë do të regjistrohen në këtë URL", + "TargetPageTestUrlNotMatches": "Veprimtaritë s’do të regjistrohen në këtë URL", + "TargetPages": "Faqe Hyrjesh", + "TargetTypeContains": "përmban", + "TargetTypeEqualsExactly": "është saktësisht i barabartë me", + "TargetTypeEqualsExactlyInfo": "Vlera s’ka përputhje saktësisht, përfshi protokollin URL, kërkesë dhe hash kërkimi.", + "TargetTypeEqualsSimple": "është e barabartë thjesht me", + "TargetTypeEqualsSimpleInfo": "URL-ja do të krahasohet për përputhje me çfarëdo protokolli (p.sh., http dhe https) me ose pa nënpërkatësi “www.”. Çfarëdo shenje / në shteg, si dhe termi i kërkimit dhe pjesa hash e URL-së do të shpërfillen gjatë kërkimi përputhjesh të URL-së.", + "TargetTypeExists": "ekziston", + "TargetTypeIsAny": "është çfarëdo", + "TargetTypeIsNot": "jo %s", + "TargetTypeRegExp": "ka përputhje me shprehjen e rregullt", + "TargetTypeRegExpInfo": "Çfarëdo shprehje të rregullt, për shembull, “^(.*)test(.*)$”.", + "TargetTypeStartsWith": "fillon me", + "TimeOnSite": "Koha në sajt", + "TotalEvents": "Akte", + "TrackingDisabledDefaultSettingDescription": "Kjo veçori mund të jetë e dobishme nëse instanca juaj Matomo ka shumë sajte dhe doni të përdorni veçorinë vetëm me pak sajte specifikë. Kur ndjekja çaktivizohet si parazgjedhje, veçoria Hartë Veprimesh dhe Regjistrim Sesioni s’do të përdoret në ndonjë sajt, veç në e aktivizofshi ju posaçërisht, si pjesë e kodit tuaj të ndjekjes, për shembull duke thirrur '_paq.push(['HeatmapSessionRecording::enable']);'. Kjo shmang kërkesa të panevojshme ndaj rrjetit për kartelën 'configs.php', për të përcaktuar nëse është formësuar për sajtin në fjalë një hartë veprimesh se regjistrim sesioni. Mundet gjithashtu të jetë e dobishme për arsye privatësie, nëse doni të garantoni që veçoria do të përdoret vetëm në disa sajte.", + "TrackingDisabledDefaultSettingTitle": "Çaktivizoje ndjekjen, si parazgjedhje", + "UpdatingData": "Po përditësohen të dhëna…", + "UrlParameterValueToMatchPlaceholder": "Vlerë për përputhje për emër parametri URL-je", + "UrlXDoesNotLookLikeUrl": "%s s’duket si URL. Sigurohuni që përmban një protokoll të tillë si http:// ose se fillon me //", + "ViewReport": "Shiheni raportin", + "ViewportResolution": "Qartësi Zone Parjeje (gjerësi x lartësi)", + "Width": "Gjerësi", + "XSamples": "%s kampione", + "disable": "çaktivizoje", + "enable": "aktivizoje" + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/sv.json b/files/plugin-HeatmapSessionRecording-5.2.4/lang/sv.json new file mode 100644 index 0000000..f3b7739 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/lang/sv.json @@ -0,0 +1,80 @@ +{ + "HeatmapSessionRecording": { + "ActivityClick": "Klick", + "ActivityFormChange": "Formulärändring", + "ActivityFormText": "Formulärtext ändrad", + "ActivityFormValue": "Formulärvärde ändrat", + "ActivityInitialDom": "Initial sida", + "ActivityMove": "Flytta", + "ActivityPageChange": "Ändring inom sida", + "ActivityResize": "Ändra storlek", + "ActivityScroll": "Skroll", + "ActivityScrollElement": "Skrollelement", + "AutoPlayNextPageview": "Klicka för %1$s autospela nästa sidvisning (genväg %2$s)", + "BreakpointGeneralHelp": "Enhetstypen upptäcks automatiskt när en besökare besöker din webbplats. Men ibland kan det vara omöjligt att upptäcka enhetstypen. Om din webbplats är responsiv och layouten bryts vid en viss punkt, kommer vi att placera desktop-användare med en liten upplösning i denna enhetstyp för mer precisa värmekartor. Alla breddvärden lägre än detta värde kommer att placeras i denna enhetstyp. Sätt det till noll om din webbplats inte är responsiv.", + "CaptureDomInlineHelp": "Som standard tas värmekartors skärmdumpar automatiskt vid sidladdning. Om din sida laddar element dynamiskt (t.ex. med lazy loading) kan din snapshot vara ofullständig. Du kan manuellt fånga en komplett skärmdump genom att aktivera det här alternativet och köra följande Javascript-kod i din webbläsare: %1$s %2$sNotera:%3$s Det här alternativet återställs automatiskt efter att skärmdumpen har fångats.", + "CaptureKeystrokesHelp": "Vid aktivering kommer all text som matas in i textformulärfält att spelas in. Medan texten spelas in, ersätts varje tecken som en användare matar in med en stjärna ('*'). Du kan lägga till vissa fält på en lista för att spelas in i klartext genom att specificera ett 'data-matomo-unmask'-attribut på ett formulärfält. Däremot kommer lösenordsfält och många andra fält som potentiellt kan innehålla personlig information (adress, e-post, kreditkortsinformation, användarnamn, telefonnummer, ...) alltid att maskeras automatiskt om de upptäcks av oss som sådana (%1$släs mer%2$s). Vänligen notera att när du aktiverar denna funktion kan du spela in personlig data, vilket kan påverka GDPR.", + "ChangeReplaySpeed": "Ändra uppspelningshastighet (genväg %s)", + "ClickToSkipPauses": "Klicka för %1$s skippa pauser (genväg %2$s)", + "CreationDate": "Skapandedatum", + "DeleteRecordedPageview": "Radera denna inspelade sidvisning (ingen bekräftelse begärs)", + "DeleteRecordedSession": "Radera denna inspelade session (ingen bekräftelsefråga ställs)", + "DeleteScreenshot": "Radera skärmklipp", + "ErrorXNotANumber": "\"%1$s\" behöver vara ett nummer.", + "ExcludedElements": "Exkluderade element", + "ExcludedElementsHelp": "Du kan valfritt definiera CSS-selektorer för att utesluta vissa element som inte ska vara synliga i förhandsgranskningen av värmekartan. Till exempel ett popup-fönster som visas när målsidan öppnas. Du kan separera flera selektorer med ett komma.", + "FieldIncludedTargetsHelp": "Mål låter dig definiera på vilka sidor användaraktiviteter ska spelas in. Du kan definiera ett eller flera villkor. Till exempel kan du definiera att aktiviteter ska spelas in när URL:en eller sökvägen är lika med ett visst värde och endast om en viss URL-parameter finns i URL:en. Aktiviteter kommer endast att spelas in när alla villkor är uppfyllda, inte om endast ett av dem är uppfyllt. Alla villkor utvärderas oberoende av versaler och gemener. När du väljer 'equals simple' kommer URL-protokollet, sökparametrar och en avslutande snedstreck att ignoreras.", + "FieldIncludedTargetsHelpSessions": "Mål låter dig konfigurera så att inspelningen av en session endast startar när en besökare har nått en viss sida. Detta gör att du till exempel endast spelar in sessioner som går igenom kassa-processen. Du kan definiera ett eller flera villkor. Till exempel kan du definiera att en session endast ska spelas in när URL:en eller sökvägen är lika med ett visst värde och endast om en viss URL-parameter är närvarande i URL:en. Sessioner kommer endast att spelas in när alla villkor är uppfyllda vid en sidvisning, inte om bara ett av dem är uppfyllt. Alla villkor utvärderas oberoende av versaler och gemener. När du väljer 'equals simple' kommer URL-protokollet, sökparametrar och en avslutande snedstreck att ignoreras.", + "Heatmap": "Värmekarta", + "HeatmapSampleLimit": "Antal sidvisningar", + "HeatmapSampleLimitHelp": "Definierar antalet sidvisningar du totalt vill spela in.", + "HeatmapUsageBenefits": "Värmekartor låter dig registrera alla klick, musrörelser och skrollaktiviteter från dina besökare på en viss sida. Detta hjälper dig att ta reda på var användare tror att något är klickbart men inte är det, om det finns delar av sidan som knappt ses eller interageras med, vad dina besökare letar efter, hur mycket av sidan som är synligt när användare ser din sida, och mer.", + "HeatmapX": "Värmekarta %s", + "HeatmapXRecordedSamplesSince": "%1$s prover har spelats in sedan %2$s.", + "Heatmaps": "Värmekartor", + "Manage": "Hantera", + "NHeatmaps": "%s värmekartor", + "NSessionRecordings": "%s sessioninspelningar", + "NoHeatmapSamplesRecordedYet": "Ingen sidvisning har spelats in ännu för denna värmekarta. Om det borde finnas inspelningar vid det här laget, kanske de målsidor som konfigurerats för denna värmekarta inte matchar någon sida på din webbplats. Det rekommenderas också att kontrollera \"Systemkontroll\" under \"Administration\" (endast superanvändare) för att se om ditt system är konfigurerat att automatiskt spåra värmekartor.", + "NoHeatmapScreenshotRecordedYet": "Det har registrerats %1$s sidvisningar hittills. Dock har ingen skärmdump tagits än. Om det finns en \"%2$s\" för denna värmekarta, kan det dröja ett tag innan skärmdumpen blir tillgänglig, eftersom en användare först behöver öppna denna skärmdumps-URL. Beroende på urvalsfrekvensen kan detta ta ett tag.", + "NoHeatmapsFound": "Inga värmekartor hittade", + "NoSessionRecordedYet": "Ingen session har spelats in ännu. Om det borde finnas inspelade sessioner vid det här laget, kanske den konfigurerade ingångssidan inte matchar någon sida på din webbplats. Det rekommenderas också att kontrollera \"Systemkontroll\" under \"Administration\" (endast superanvändare) för att se om ditt system är konfigurerat att automatiskt spela in sessioner.", + "NoSessionRecordingsFound": "Inga sessionsinspelningar hittade", + "OnePageview": "1 sidvisning", + "PageviewXofY": "Sidvisning %1$s av %2$s", + "PageviewsInVisit": "Inspelade sidvisningar under denna session", + "PlayRecordedSession": "Spela upp inspelad session", + "PlayerForwardFast": "Hoppa över %1$s sekunder (genväg %2$s)", + "PlayerRewindFast": "Spola tillbaka %1$s sekunder (genväg %2$s)", + "RecordedHeatmapDocStatusActive": "Denna värmekarta är aktiv. Upp till %1$d sidvisningar kommer att spelas in med en urvalsfrekvens på %2$s.", + "RecordedHeatmapDocStatusEnded": "Värmekarta avslutad. Ingen ny aktivitet kommer spelas in.", + "RecordedSessions": "Inspelade sessioner", + "RecordedSessionsDocStatusActive": "Denna sessioninspelning är aktiv. Upp till %1$d sessioner kommer att spelas in med en urvalsfrekvens på %2$s.", + "RecordedSessionsDocStatusEnded": "Denna sessioninspelning har avslutats och inga nya sessioner kommer att spelas in.", + "Recordings": "Inspelningar", + "ReplayRecordedSession": "Spela upp inspelad session", + "ReplayX": "Spela upp %s", + "Rule": "Regel", + "SampleLimit": "Urvalsgräns", + "ScreenshotUrlHelp": "Som standard tas en skärmdump när den första besökaren visar målsidan. Om flera sid-URL:er matchar din målsida kan du specificera en specifik URL som ska användas för att fånga skärmdumpen. Värmekartans visualisering kommer endast att vara tillgänglig när minst en besökare har besökt denna URL vars aktiviteter är inspelade. Vid definiering måste URL:en matcha exakt. För att ignorera protokollet, börja URL:en med två snedstreck (//example.com). Vänligen notera att denna URL inte kan ändras när en skärmdump har tagits.", + "SessionRecording": "Sessionsinspelning", + "SessionRecordingX": "Sessioninspelning %s", + "SessionRecordings": "Sessionsinspelningar", + "SessionRecordingsUsageBenefits": "Sessioninspelningar låter dig spela in alla aktiviteter från en besökare under deras session (besök), såsom klick, musrörelser, skrollningar, fönsterändringar, sidförändringar och formulärinteraktioner. Du kan sedan spela upp dessa interaktioner för att se exakt hur en besökare interagerade med din webbplats. På så sätt förstår du deras förväntningar, eventuella problem de kan ha, användningsmönster och mer.", + "SessionSampleLimit": "Antal sessioner", + "SessionSampleLimitHelp": "Definierar antalet sessioner du totalt vill spela in.", + "SessionSampleRateHelp": "Även känd som 'trafik'. När du väljer 100%% kommer alla sessioner att spelas in så snart de har nått målsidan. När du exempelvis väljer 10%%, kommer endast var tionde session att spelas in. Ju lägre procentandel du väljer, desto längre tid kommer det att ta att nå den valda urvalsgränsen", + "StatusActive": "Aktiv", + "StatusEnded": "Avslutad", + "StatusPaused": "Pausad", + "TargetPage": "Målsida", + "TargetPages": "Ingångssidor", + "TimeOnSite": "Tid på webbplats", + "TrackingDisabledDefaultSettingDescription": "Denna funktion kan vara användbar om din Matomo har flera webbplatser och du vill använda funktionen endast på några specifika webbplatser. När spårning är inaktiverad som standard, kommer funktionerna för värmekarta och sessioninspelning inte att köras på någon webbplats om du inte specifikt aktiverar dem som en del av din spårningskod, till exempel genom att anropa '_paq.push(['HeatmapSessionRecording::enable']);'. Detta undviker onödiga nätverksförfrågningar till 'configs.php'-filen för att avgöra om en värmekarta eller sessioninspelning är konfigurerad för den aktuella webbplatsen. Det kan också vara användbart av integritetsskäl om du vill säkerställa att funktionen endast används på vissa webbplatser.", + "TrackingDisabledDefaultSettingTitle": "Inaktivera spårning per som standardinställning", + "ViewReport": "Visa rapport", + "ViewportResolution": "Viewports upplösning (bredd x höjd)", + "disable": "inaktivera", + "enable": "aktivera" + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/tr.json b/files/plugin-HeatmapSessionRecording-5.2.4/lang/tr.json new file mode 100644 index 0000000..1def63f --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/lang/tr.json @@ -0,0 +1,237 @@ +{ + "HeatmapSessionRecording": { + "Action": "İşlem", + "ActivityClick": "Tıklama", + "ActivityFormChange": "Form değiştirme", + "ActivityFormText": "Form metni değiştirme", + "ActivityFormValue": "Form değeri değiştirme", + "ActivityInitialDom": "Giriş sayfası", + "ActivityMove": "Hareket etme", + "ActivityPageChange": "Sayfa içindeki değişiklik", + "ActivityResize": "Yeniden boyutlandırma", + "ActivityScroll": "Kaydırma", + "ActivityScrollElement": "Kaydırma bileşeni", + "AdBlockerDetected": "Bir reklam engelleyici kullanıldığında %1$s görüntülenemeyebilir. Matomo'nun sorunsuz çalışacağından emin olmak için lütfen reklam engelleyicinizi bu site için kapatın.", + "AdvancedOptions": "Gelişmiş ayarlar", + "AutoPlayNextPageview": "Sonraki sayfa gösteriminin otomatik olarak oynatılması için %1$s üzerine tıklayın (%2$s kısayolu)", + "AvgAboveFoldDescription": "Ortalama olarak ziyaretçiler kaydırmadan bu çizginin üzerindeki içeriği görür", + "AvgAboveFoldTitle": "Ortalama yukarı katlanma %s piksel", + "BreakpointGeneralHelp": "Bir ziyaretçi siteyi ziyaret ettiğinde aygıt türü otomatik olarak algılanır. Bununla birlikte bazen aygıt türünün belirlenemediği olur. Site uyumlu yapıda ise ve görünümü belirli boyut değişikliği noktalarında değişiyorsa, daha ayrıntılı ısı haritaları oluşturmak için küçük çözünürlük kullanan masaüstü ziyaretçileri bu aygıt türüne konulur. Bu değerden daha küçük genişlik değerleri bu aygıt türüne konulur. Site uyumlu yapıda değilse sıfır olarak ayarlayın.", + "BreakpointGeneralHelpManage": "Süper kullanıcı izinlerine sahip bir kullanıcı Yönetim => Genel ayarlar bölümünden bu boyut değişikliği noktalarının varsayılan değerlerini değiştirebilir.", + "BreakpointX": "%s boyut değişikliği noktası", + "CaptureDomInlineHelp": "Varsayılan olarak, anlık sıcaklık haritası görüntüleri sayfa yüklemesinde otomatik olarak kaydedilir. Sayfanız dinamik olarak bileşenler yüklüyorsa (örneğin yavaş yükleme ile) anlık görüntünüz eksik olabilir. Bu seçeneği açtığınızda ve tarayıcınızda aşağıdaki JavaScript kodunu çalıştırarak tam bir anlık görüntü elde edebilirsiniz: %1$s %2$sNot:%3$s Bu seçenek, anlık görüntüyü kaydettikten sonra otomatik olarak sıfırlanır.", + "CaptureDomTitle": "Anlık sıcaklık haritası görüntüsünü el ile kaydet", + "CaptureKeystrokes": "Tuş basmaları kaydedilsin", + "CaptureKeystrokesHelp": "Açıldığında, form metin alanlarına yazılan metinler kaydedilir. Metin kaydedilirken ziyaretçinin yazdığı karakterler yıldız ('*') ile değiştirilir. Form alanında bir 'data-matomo-unmask' özniteliği belirterek istediğiniz alanları beyaz listeye alabilir ve içeriklerinin düz metin biçiminde kaydedilmesini sağlayabilirsiniz. Bununla birlikte parola ve kişisel verilerin (adres, e-posta, kredi kartı bilgileri, kullanıcı adı, telefon numarası gibi) bulunabileceği pek çok diğer alan algılandığında her zaman otomatik olarak gizlenir (%1$sayrıntılı bilgi alın%2$s). Açıldığında, KVKK ile ilgili kişisel verilerin kaydedebileceğini unutmayın.", + "ChangeReplaySpeed": "Yeniden oynatma hızını değiştir (%s kısayolu)", + "ClickRate": "Tıklama oranı:", + "ClickToSkipPauses": "Duraklamaları atlamak için %1$s üzerine tıklayın (%2$s kısayolu)", + "Clicks": "Tıklamalar:", + "ColumnActionsDocumentation": "Kaydedilmiş oturumu oynatın ya da kayıtları kalıcı olarak silin.", + "ColumnBrowserDocumentation": "Bu oturum kaydedilirken ziyaretçinin kullandığı tarayıcı.", + "ColumnDeviceDocumentation": "Bu oturum kaydedilirken ziyaretçinin kullandığı aygıt.", + "ColumnLabelRecordedSessions": "Giriş adresi → Çıkış adresi", + "ColumnLocationDocumentation": "Bu oturum kaydedilirken ziyaretçinin bulunduğu konum.", + "ColumnOperatingSystem": "İşletim sistemi", + "ColumnOperatingSystemDocumentation": "Bu oturum kaydedilirken ziyaretçinin kullandığı işletim sistemi.", + "ColumnPageviewsDocumentation": "Bu oturum boyunca kaydedilen sayfa gösterimlerinin sayısı.", + "ColumnResolutionDocumentation": "Ziyaretçi bu sayfayı açtığında, tarayıcı penceresinde görüntülenen bakış açısının piksel sayısı.", + "ColumnTime": "Zaman", + "ColumnTimeDocumentation": "Sitenin saat dilimine göre kayıdın başladığı zaman.", + "ColumnTimeOnPageDocumentation": "Oturum kaydedilirken ziyaretçinin bu sayfada geçirdiği süre", + "ColumnTimeOnSiteDocumentation": "Oturum kaydedilirken ziyaretçinin toplam geçirdiği süre", + "ColumnTotalEventsDocumentation": "Bu oturum sırasında kaydedilen olayların sayısı. Örneğin: tıklama, hareket etme, kaydırma, yeniden boyutlandırma, sayfa değişiklikleri ve form etkileşimi olayları.", + "ConfigsInternetDisabled": "Bu Matomo kopyası için İnternet bağlantısı kapatılmış olduğundan '%s' erişimi denetlenemedi.", + "ConfigsPhpErrorResult": "Sonuç olarak ısı haritaları ve oturum kayıtları çalışmayabilir. Bu dosyaya Internet ya da Intranet üzerinden erişilmesine izin vermek için site sunucunuzun yapılandırmasını değiştirmeniz gerekebilir.", + "ConfigsPhpManualCheck": "Lütfen adresi el ile açarak yanıtta 'Piwik.HeatmapSessionRecording' olup olmadığına bakın. Göremiyorsanız, bu dosyaya İnternet ya da İntranet üzerinden erişilebilmesi için sunucu yapılandırmanızı değiştirmeniz gerekebilir.", + "ConfigsPhpNotAccessible": "'%s' adresine İnternet ya da Intranet üzerinden erişilemiyor gibi görünüyor.", + "ConfigsPhpSelfSignedError": "'%s' isteği yapıldığında bir SSL sorunu çıktı. Kendinden imzalı bir sertifika kullanıyor olabilir misiniz?", + "ConfigsPhpSuccess": "'%s' erişilebilir", + "ConfigsPhpUnknown": "'%s' için İnternet ya da İntranet erişimi denetlenemedi.", + "ConfigsPhpUnknownError": "'%1$s' isteğinde sorun çıktı: %2$s.", + "Country": "Ülke", + "CreateNewHeatmap": "Yeni ısı haritası ekle", + "CreateNewSessionRecording": "Yeni oturum kaydı ekle", + "CreationDate": "Eklenme tarihi", + "DeleteHeatmapConfirm": "Bu ısı haritasını silmek istediğinize emin misiniz? Daha önce üretilmiş raporlar kullanıcı arayüzündeki raporlar bölümünde görüntülenmeyecek.", + "DeleteHeatmapScreenshotConfirm": "Bu ekran görüntüsünü silmek istediğinize emin misiniz? Ekran görüntüsünün yeniden alınması biraz zaman alabilir.", + "DeleteRecordedPageview": "Bu sayfa gösterimi kaydını sil (onay istenmez)", + "DeleteRecordedSession": "Bu oturum kaydını sil (onay istenmez)", + "DeleteScreenshot": "Ekran görüntüsünü sil", + "DeleteSessionRecordingConfirm": "Bu oturum kaydını silmek istediğinize emin misiniz? Daha önce üretilmiş raporlar kullanıcı arayüzündeki raporlar bölümünde görüntülenmeyecek.", + "DeleteX": "%s sil", + "DeviceType": "Aygıt türü", + "DisableHeatmapRecordingDescription": "Açıldığında, herhangi bir sıcaklık haritası yapılandırılamaz ya da kaydedilemez.", + "DisableHeatmapRecordingInlineHelp": "%1$sNot: Bu özellik kapatıldığında, önceden izlenen sıcaklık haritaları ile birlikte tüm sıcaklık haritaları silinir. Bu özelliği açtığınızda ya da kapattığınızda, oturum kaydında herhangi bir değişiklik olmaz.%2$s", + "DisableHeatmapRecordingTitle": "Sıcaklık haritaları kapatılsın", + "DisableSessionRecordingDescription": "Bu özellik kapatıldığında hiçbir oturum kaydı yapılandırılamaz ya da izlenemez.", + "DisableSessionRecordingInlineHelp": "%1$s Not: Bu özellik kapatıldığında, daha önce izlenen oturum kayıtları ile birlikte tüm oturum kayıtları silinir. Bu özelliği açar ya da kapatırsanız sıcaklık haritalarında herhangi bir değişiklik olmaz. %2$s", + "DisableSessionRecordingTitle": "Oturum kaydını kapat", + "EditHeatmapX": "%s ısı haritasını düzenle", + "EditSessionRecordingX": "%s oturum kaydını düzenle", + "EditX": "%s düzenle", + "EnableIncludeCountriesDescription": "Varsayılan olarak, tüm ziyaretler kaydedilir. Belirli ülkelerden ziyaretleri izlemek istediğinizde bu bölümü kullanıma alın.", + "EnableIncludeCountriesTitle": "Yalnızca belirli ülkelerden gelen ziyaretçiler izlensin", + "EndHeatmapConfirm": "Bu ısı haritası için yeni işlemlerin kaydedilmesini durdurmak istediğinize emin misiniz?", + "EndSessionRecordingConfirm": "Yeni oturumların kaydedilmesini durdurmak istediğinize emin misiniz?", + "ErrorArrayMissingKey": "\"%2$s\" içinde \"%3$s\" konumundaki \"%1$s\" dizi anahtarı eksik.", + "ErrorArrayMissingValue": "\"%2$s\" içinde \"%3$s\" konumundaki \"%1$s\" dizi anahtarının değeri eksik.", + "ErrorHeatmapDoesNotExist": "İstenilen ısı haritası bulunamadı", + "ErrorHeatmapNameDuplicate": "Aynı adı kullanan başka bir ısı haritası zaten var.", + "ErrorHeatmapRecordingDisabled": "Sıcaklık haritası kaydetme özelliği kapatılmış. Bu özelliği kullanmak istiyorsanız, bir Matomo süper kullanıcısının genel ayarlar bölümünden açılması gerekir.", + "ErrorInnerIsNotAnArray": "\"%2$s\" içindeki her \"%1$s\" bir dizi olmalıdır.", + "ErrorInvalidRegExp": "\"%1$s\" kurallı ifadesinin biçimi geçersiz.", + "ErrorNotAnArray": "\"%1$s\" bir dizi olmalıdır.", + "ErrorPageRuleRequired": "En az bir sayfa kuralı ayarlanmış olmalıdır.", + "ErrorSessionRecordingDisabled": "Oturum kaydetme özelliği kapatıldı. Kullanılabilmesi için bu özelliğin genel ayarlar bölümünden süper kullanıcı düzeyinde bir Matomo kullanıcısı tarafından açılması gerekir.", + "ErrorSessionRecordingDoesNotExist": "İstenilen oturum kaydı bulunamadı", + "ErrorXContainsWhitespace": "\"%1$s\" içinde boşluk bulunamaz.", + "ErrorXNotANumber": "\"%1$s\" bir sayı olmalıdır.", + "ErrorXNotProvided": "Lütfen \"%1$s\" için bir değer yazın.", + "ErrorXNotWhitelisted": "\"%1$s\" değeri kullanılamaz. Şunlardan birini kullanın: %2$s.", + "ErrorXTooHigh": "\"%1$s\" çok büyük. İzin verilen en büyük değer %2$s.", + "ErrorXTooLong": "\"%1$s\" çok uzun. İzin verilen en fazla karakter sayısı %2$s.", + "ErrorXTooLow": "\"%1$s\" çok küçük. İzin verilen en küçük değer %2$s.", + "ExcludedElements": "Katılmayan bileşenler", + "ExcludedElementsHelp": "İsteğe bağlı olarak, görünmesini istemediğiniz bileşenlerin ısı haritası ön izlemesine katılmaması için CSS seçicileri tanımlayabilirsiniz. Örnek: Hedef sayfa açıldığında görüntülenen bir açılan pencere. Virgül ile ayırarak birden fazla seçici yazabilirsiniz.", + "FieldIncludedTargetsHelp": "Hedefler hangi sayfalardaki kullanıcı işlemlerinin kaydedileceğini belirler. Bir ya da bir kaç koşul tanımlayabilirsiniz. Örneğin, adres ya da yol belirli bir değer olduğunda ya da adreste yalnızca belirli adres parametreleri bulunduğunda işlemlerin kaydedilmesini sağlayabilirsiniz. İşlemler bazı koşullar değil yalnızca tüm koşullar karşılandığında kaydedilir. Tüm koşullar büyük-küçük harfe duyarlı olarak değerlendirilir. 'basit olarak eşleşen' koşulu seçildiğinde adres iletişim kuralı, arama parametreleri ve sondaki bölü karakteri yok sayılır.", + "FieldIncludedTargetsHelpSessions": "Hedefler hangi sayfalara kullanıcılar eriştiğinde oturumların kaydedileceğini belirler. Böylece örneğin yalnızca ödeme işlemlerinin yapıldığı oturumları kaydedebilirsiniz. Bir ya da bir kaç koşul tanımlayabilirsiniz. Örneğin, adres ya da yol belirli bir değer olduğunda ya da adreste yalnızca belirli adres parametreleri bulunduğunda oturumların kaydedilmesini sağlayabilirsiniz. Oturumlar bazı koşullar değil yalnızca tüm koşullar karşılandığında kaydedilir. Tüm koşullar büyük-küçük harfe duyarlı olarak değerlendirilir. 'basit olarak eşleşen' koşulu seçildiğinde adres iletişim kuralı, arama parametreleri ve sondaki bölü karakteri yok sayılır.", + "FieldNamePlaceholder": "Örnek: 'Kayıt sayfası'", + "FilesystemDirectory": "klasör", + "Filter": "Süzgeç", + "GettingStarted": "Başlarken", + "Heatmap": "Isı haritası", + "HeatmapAddedActivity": "\"%2$s\" sitesi için \"%1$s\" sıcaklık haritasını ekledi", + "HeatmapCreated": "Isı haritası eklendi.", + "HeatmapDeletedActivity": "\"%2$s\" sitesi için \"%1$s\" sıcaklık haritasını sildi", + "HeatmapEndedActivity": "\"%2$s\" sitesi için \"%1$s\" sıcaklık haritasını sonlandırdı", + "HeatmapInfoTrackVisitsFromCountries": "Sıcaklık haritası yalnızca %1$s ziyaretlerini izlemek için yapılandırılmış.", + "HeatmapNameHelp": "Bu ısı haritası raporu için kullanılacak adı belirler.", + "HeatmapPausedActivity": "\"%2$s\" sitesi için \"%1$s\" sıcaklık haritasını duraklattı", + "HeatmapResumedActivity": "\"%2$s\" sitesi için \"%1$s\" sıcaklık haritasını sürdürdü", + "HeatmapSampleLimit": "Sayfa gösterimi sayısı", + "HeatmapSampleLimitHelp": "Kaydedilmesini istediğiniz toplam sayfa gösterimi sayısı.", + "HeatmapSampleRateHelp": "'Trafik' olarak da bilinir. %%100 seçildiğinde, seçilmiş hedef sayfayı ziyaret eden tüm ziyaretçiler kaydedilir. Örneğin %%10 seçildiğinde, her 10 ziyaretçiden biri kaydedilir. Yüzde değerini azalttığınızda seçilmiş örnek sınırına erişilmesi daha uzun zaman alır.", + "HeatmapScreenshotDeletedActivity": "\"%2$s\" sitesi için \"%1$s\" sıcaklık haritası ekran görüntüsünü sildi", + "HeatmapTroubleshoot": "Sorun mu yaşıyorsunuz? %1$sAyrıntılı bilgi alın%2$s.", + "HeatmapUpdated": "Isı haritası güncellendi.", + "HeatmapUpdatedActivity": "\"%2$s\" sitesi için \"%1$s\" sıcaklık haritasını güncelledi", + "HeatmapUsageBenefits": "Isı haritaları, belirli bir sayfa için ziyaretçilerin tüm tıklama, fare hareketi ve kaydırma işlemlerinin kaydedilmesini sağlar. Böylece ziyaretçilerin, tıklanabilir olduğunu düşündükleri halde tıklanamayan bileşenleri, sayfada görmedikleri ya da etkileşim içinde oldukları bölümleri, neye baktıklarını, sayfanın ne kadarını gördükleri gibi bilgileri öğrenebilirsiniz.", + "HeatmapWidth": "Isı haritası genişliği", + "HeatmapX": "%sısı haritası", + "HeatmapXRecordedSamplesSince": "%2$s zamanından beri %1$s örnek kaydedilmiş.", + "Heatmaps": "Isı haritaları", + "Manage": "Yönetim", + "ManageHeatmapSubcategoryHelp": "Isı haritaları, ziyaretçilerin belirli bir sayfadaki tüm tıklamalarını, fare hareketlerini ve kaydırma işlemlerinin kaydedilmesini sağlar. Site için ısı haritası izlemeleri bu bölümden eklenip yönetilebilir.", + "ManageHeatmaps": "Isı haritaları yönetimi", + "ManageSessionRecordingSubcategoryHelp": "Oturum kayıtları, gerçek bir ziyaretçinin oturumu sırasında yaptığı tıklama, fare hareketi, kaydırma, pencereyi yeniden boyutlandırma, sayfa değiştirme ve form etkileşimi gibi işlemlerin kaydedilmesini sağlar. Site için oturum kayıtları bu bölümden eklenip yönetilebilir.", + "ManageSessionRecordings": "Oturum kayıtları yönetimi", + "MatomoJSNotWritableErrorMessage": "HeatmapSessionRecording: %1$s, matomo.js dosyası salt okunur olduğu için şu anda ziyaretleri izleyemiyor. Dosyayı yazılabilir yapmak için lütfen %2$sbelgelere%3$s bakın.", + "MinSessionTime": "En kısa oturum süresi", + "MinSessionTimeHelp": "Bir sayfada yalnızca belirtilen değerden daha uzun süre geçirilen oturumlar kaydedilir.", + "MoveRate": "Hareket oranı:", + "Moves": "Hareketler:", + "NHeatmaps": "%s ısı haritası", + "NSessionRecordings": "%s oturum kayıtları", + "NoHeatmapSamplesRecordedYet": "Bu ısı haritası için henüz bir sayfa gösterimi kaydedilmemiş. Burada kaydedilmiş sayfa gösterimlerinin olması gerekiyorsa, bu ısı haritası için yapılandırılmış sayfa hedefleri sitenizdeki hiç bir sayfa ile eşleşmiyor olabilir. Ayrıca \"Yönetim\" başlığı altından \"Sistem denetimi\" bölümüne (yalnızca süper kullanıcılar görebilir) bakarak sisteminizin ısı haritalarını otomatik olarak izleyecek şekilde yapılandırıldığından emin olmanız önerilir.", + "NoHeatmapSamplesRecordedYetWithoutSystemConfiguration": "Bu sıcaklık haritası için henüz bir sayfa görünümü kaydedilmemiş. Şimdiye kadar bazı kayıtlar olması gerektiğini düşünüyorsanız, bu sıcaklık haritası için yapılandırılmış sayfa hedefleri sitenizdeki hiçbir sayfayla eşleşmiyor olabilir.", + "NoHeatmapScreenshotRecordedYet": "Şu ana kadar %1$s örnek kaydedilmiş. Bununla birlikte henüz bir ekran görüntüsü kaydedilmemiş. Bu ısı haritası için bir \"%2$s\" varsa, bir ziyaretçinin önce bu ekran görüntüsü adresini açması gerektiğinden ekran görüntüsünün kullanılabilmesi zaman alabilir. Örnekleme hızına göre bu işlem biraz zaman alabilir.", + "NoHeatmapsConfiguredInfo": "Şu anda etkin bir ısı haritası yok. Isı haritalarını görüntüleyebilmek için, en az yönetici izinlerine sahip bir kullanıcıdan yeni bir ısı haritası eklemesini isteyin.", + "NoHeatmapsFound": "Herhangi bir ısı haritası bulunamadı", + "NoSessionRecordedYet": "Henüz bir oturum kaydedilmemiş. Burada kaydedilmiş bir oturum olması gerekiyorsa, yapılandırılmış giriş sayfası sitenizdeki hiç bir sayfa ile eşleşmiyor olabilir. Ayrıca \"Yönetim\" başlığı altından \"Sistem denetimi\" bölümüne (yalnızca süper kullanıcılar görebilir) bakarak sisteminizin oturumları otomatik olarak kaydedecek şekilde yapılandırıldığından emin olmanız önerilir.", + "NoSessionRecordedYetWithoutSystemConfiguration": "Henüz herhangi bir oturum kaydedilmemiş. Şimdiye kadar bazı kaydedilmiş oturumlar olması gerektiğini düşünüyorsanız, ayarlanmış kayıt sayfası sitenizdeki hiçbir sayfayla eşleşmiyor olabilir.", + "NoSessionRecordingsConfiguredInfo": "Şu anda etkin bir oturum kaydı yok. Yeni oturumları kaydedebilmek için, en az yönetici izinlerine sahip bir kullanıcıdan yeni bir kayıt işlemi eklemesini isteyin.", + "NoSessionRecordingsFound": "Herhangi bir oturum kaydı bulunamadı", + "NotSupportedBrowser": "Bu tarayıcı desteklenmiyor. Lütfen daha yeni bir sürümü kullanın ya da farklı bir tarayıcı deneyin.", + "OnePageview": "1 sayfa gösterimi", + "PageRule": "Sayfa kuralı", + "PageviewXofY": "%1$s / %2$s sayfa gösterimi", + "PageviewsInVisit": "Bu oturumda kaydedilmiş sayfa gösterimleri", + "PauseReason": "Kotanızın dolması nedeniyle %1$s duraklatıldı. Lütfen ayrıntılı bilgi almak için destek ekibi ile görüşün.", + "PeriodDisabledErrorMessage": "\"%1$s\" dönemi kullanımdan kaldırılmış. Ancak bu özelliğin çalışması için gerekli. Başka bir dönem seçin ya da sistem yöneticisinden 'config/config.ini.php' Matomo yapılandırma dosyasındaki \"enabled_periods_API\" seçeneğini \"%1$s\" dönemine izin verecek şekilde değiştirmesini isteyin.", + "PersonalInformationNote": "Lütfen bir %1$s tarafından kişisel verilerin kaydedilebileceğini unutmayın. Sitenizdeki ya da uygulamanızdaki belirli içeriklerin gizlenmesini istiyorsanız, HTML kodunda bu içeriklere %2$sdata-matomo-mask%3$s özniteliğini ekleyebilirsiniz. %4$sAyrıntılı bilgi alın%5$s", + "PlayRecordedSession": "Bu oturum kaydını oynat", + "PlayerDurationXofY": "%1$s / %2$s", + "PlayerForwardFast": "%1$s saniye ileri git (%2$s kısayolu)", + "PlayerPageViewNext": "Bu ziyaretçinin sonraki sayfa gösterimini oynat %1$s (%2$s kısayolu)", + "PlayerPageViewPrevious": "Bu ziyaretçinin önceki sayfa gösterimini oynat %1$s (%2$s kısayolu)", + "PlayerPause": "Duraklat (%s kısayolu ya da boşluk)", + "PlayerPlay": "Oynat (%s kısayolu ya da boşluk)", + "PlayerReplay": "Yeniden oynat (%s kısayolu ya da boşluk)", + "PlayerRewindFast": "%1$s saniye geri git (%2$s kısayolu)", + "RecordedHeatmapDocStatusActive": "Bu ısı haritası etkin. %2$s örnekleme hızı ile en çok %1$d sayfa gösterimi kaydedilebilir.", + "RecordedHeatmapDocStatusEnded": "Bu ısı haritası sona ermiş. Yeni işlemler kaydedilmeyecek.", + "RecordedPageviewDeletedActivity": "\"%2$s\" sitesi için \"%1$s\" oturum kaydının kaydedilmiş bir sayfa görünümünü sildi", + "RecordedSessionDeletedActivity": "\"%2$s\" sitesi için \"%1$s\" oturum kaydının bir oturum kaydını sildi", + "RecordedSessions": "Kaydedilmiş oturumlar", + "RecordedSessionsDocStatusActive": "Bu oturum kaydı etkin. %2$s örnekleme hızı ile en çok %1$d oturum kaydedilebilir.", + "RecordedSessionsDocStatusEnded": "Bu oturum kaydı sona erdi ve yeni bir oturum kaydedilmeyecek.", + "Recordings": "Kayıtlar", + "ReplayRecordedSession": "Kaydedilmiş oturumu yeniden oynat", + "ReplayX": "%s yeniden oynatılsın", + "ReportRecordedSessionsDocumentation": "Kaydedilen tüm oturumların listesi. Kaydedilen herhangi bir oturumu yeniden oynatabilirsiniz.", + "RequiresActivity": "İşlem yapılması gereksin", + "RequiresActivityHelp": "Açıldığında, bir sayfa gösteriminde yalnızca bir kaydırma ve tıklama işlemi yapılan oturumlar kaydedilir. Böylece çok fazla işlem yapılmayan oturumların kaydedilmesi engellenir.", + "Rule": "Kural", + "SampleLimit": "Örnek sınırı", + "SampleRate": "Örnekleme hızı", + "ScreenshotUrl": "Ekran görüntüsü adresi", + "ScreenshotUrlHelp": "Varsayılan olarak, bir ziyaretçi bir hedef sayfasını ilk kez görüntülediğinde bir ekran görüntüsü kaydedilir. Hedef sayfanız ile birden çok sayfa adresi eşleşiyorsa ekran görüntüsünün kaydedileceği belirli bir adres yazabilirsiniz. Isı haritası görselleştirmesi yalnızca işlemleri kaydedilen bir ziyaretçinin bu adrese erişmesinden sonra oluşturulur. Belirtildiğinde adres tam olarak eşleşmelidir. İletişim kuralının yok sayılması için adresin başına iki tane bölü karakteri ekleyin (//ornek.com gibi). Bir ekran görüntüsü kaydedildikten sonra bu adresin değiştirilemeyeceğini unutmayın.", + "SessionNameHelp": "Oturum kayıtları için kullanılacak adı belirler.", + "SessionRecording": "Oturum kaydı", + "SessionRecordingAddedActivity": "\"%2$s\" sitesi için \"%1$s\" oturum kaydını ekledi", + "SessionRecordingCreated": "Oturum kaydı eklendi.", + "SessionRecordingDeletedActivity": "\"%2$s\" sitesi için \"%1$s\" oturum kaydını sildi", + "SessionRecordingEndedActivity": "\"%2$s\" sitesi için \"%1$s\" oturum kaydını sonlandırdı", + "SessionRecordingInfoTrackVisitsFromCountries": "Oturum kaydı yalnızca %1$s ziyaretlerini izlemek için yapılandırılmış.", + "SessionRecordingPausedActivity": "\"%2$s\" sitesi için \"%1$s\" oturum kaydını duraklattı", + "SessionRecordingResumedActivity": "\"%2$s\" sitesi için \"%1$s\" oturum kaydını sürdürdü", + "SessionRecordingUpdated": "Oturum kaydı güncellendi.", + "SessionRecordingUpdatedActivity": "\"%2$s\" sitesi için \"%1$s\" oturum kaydını güncelledi", + "SessionRecordingX": "%s oturum kaydı", + "SessionRecordings": "Oturum kayıtları", + "SessionRecordingsUsageBenefits": "Oturum kayıtları, gerçek bir ziyaretçinin oturumu sırasında yaptığı tıklama, fare hareketi, kaydırma, pencereyi yeniden boyutlandırma, sayfa değiştirme ve form etkileşimi gibi işlemlerin kaydedilmesini sağlar. Bir ziyaretçinin sitenizle nasıl etkileşime girdiğini tam olarak görmek için bu etkileşimleri daha sonra yeniden oynatabilirsiniz. Böylece ziyaretçilerin beklentileri, yaşamış olabilecekleri sorunlar, kullanım alışkanlıkları gibi pek çok şeyi öğrenebilirsiniz.", + "SessionSampleLimit": "Oturum sayısı", + "SessionSampleLimitHelp": "Kaydedilmesini istediğiniz toplam oturum sayısı.", + "SessionSampleRateHelp": "'Trafik' olarak da bilinir. %%100 seçildiğinde, seçilmiş hedef sayfaya erişen tüm oturumlar kaydedilir. Örneğin %%10 seçildiğinde, her 10 oturumdan biri kaydedilir. Yüzde değerini azalttığınızda seçilmiş örnek sınırına erişilmesi daha uzun zaman alır.", + "StatusActive": "Etkin", + "StatusEnded": "Sona ermiş", + "StatusPaused": "Duraklatıldı", + "StopX": "Bu %s için yeni işlemleri kaydetmeyi durdurur.", + "TargetAttributePath": "Yol", + "TargetAttributeUrl": "Adres", + "TargetAttributeUrlParameter": "Adres parametresi", + "TargetAttributeUrlParameterExample": "AdresParametresininAdı", + "TargetPage": "Hedef sayfa", + "TargetPageTestErrorInvalidUrl": "Başında iletişim kuralı olan bir adres yazın.", + "TargetPageTestLabel": "İşlem kaydını denetlemek için başında iletişim kuralı olan tam adresi yazın:", + "TargetPageTestTitle": "Adres doğrulayıcı", + "TargetPageTestUrlMatches": "Bu adresteki işlemler kaydediliyor", + "TargetPageTestUrlNotMatches": "Bu adresteki işlemler kaydedilmiyor", + "TargetPages": "Kayıt sayfaları", + "TargetTypeContains": "şunu içeren", + "TargetTypeEqualsExactly": "tam olarak eşleşen", + "TargetTypeEqualsExactlyInfo": "Değer, adres iletişim kuralı, arama sorgusu ve karma ile tam olarak eşleşmelidir.", + "TargetTypeEqualsSimple": "basit olarak eşleşen", + "TargetTypeEqualsSimpleInfo": "Adres başında \"www.\" alt etki alanı olarak ya da olmayarak tüm iletişim kuralları (http ve https gibi) ile eşleşir. Eşleştirilirken adresin sonundaki bölü karakteri, arama sorgusu ve karma bölümü yok sayılır.", + "TargetTypeExists": "var olan", + "TargetTypeIsAny": "şunların tümü", + "TargetTypeIsNot": "%s olmayan", + "TargetTypeRegExp": "şu kurallı ifadeye uyan", + "TargetTypeRegExpInfo": "Herhangi bir kurallı ifade. Örnek: \"^(.*)test(.*)$\".", + "TargetTypeStartsWith": "şununla başlayan", + "TimeOnSite": "Sitedeki zaman", + "TotalEvents": "Olaylar", + "TrackingDisabledDefaultSettingDescription": "Bu özellik, Matomo üzerinde birden fazla site varsa ve yalnızca belirli birkaç sitenin izlenmesi isteniyorsa yararlı olabilir. İzleme özelliği varsayılan olarak kapatıldığında, izleme kodu içine özellikle '_paq.push(['HeatmapSessionRecording::enable']);' gibi bir satır eklenerek açılmadıkça, hiç bir site izlenmez. Böylece geçerli site için bir ısı haritası veya oturum kaydının yapılıp yapılmayacağını belirlemek amacıyla 'configs.php' dosyasına yapılacak gereksiz ağ istekleri önlenebilir. Gizlilik nedeniyle izleme özelliğinin yalnızca bazı sitelerde kullanılacağından emin olmak istiyorsanız da yararlı olabilir.", + "TrackingDisabledDefaultSettingTitle": "Varsayılan olarak izleme kullanılmasın", + "UpdatingData": "Veriler güncelleniyor…", + "UrlParameterValueToMatchPlaceholder": "Adres parametresi adı için eşleştirilecek değer", + "UrlXDoesNotLookLikeUrl": "%s bir adres gibi görünmüyor. Adresin başında http:// gibi bir iletişim kuralı ya da // bulunduğundan emin olun", + "ViewReport": "Raporu görüntüle", + "ViewportResolution": "Bakış açısı çözünürlüğü (genişlik x yükseklik)", + "Width": "Genişlik", + "XSamples": "%s örnek", + "disable": "kullanımdan kaldır", + "enable": "aç" + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/uk.json b/files/plugin-HeatmapSessionRecording-5.2.4/lang/uk.json new file mode 100644 index 0000000..a71dbe5 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/lang/uk.json @@ -0,0 +1,6 @@ +{ + "HeatmapSessionRecording": { + "Heatmap": "Теплова карта", + "SessionRecording": "Запис сеансу" + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/zh-cn.json b/files/plugin-HeatmapSessionRecording-5.2.4/lang/zh-cn.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/lang/zh-cn.json @@ -0,0 +1 @@ +{} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/lang/zh-tw.json b/files/plugin-HeatmapSessionRecording-5.2.4/lang/zh-tw.json new file mode 100644 index 0000000..2085aab --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/lang/zh-tw.json @@ -0,0 +1,185 @@ +{ + "HeatmapSessionRecording": { + "ActivityClick": "點擊", + "ActivityFormChange": "表單變更", + "ActivityFormText": "表單文字變更", + "ActivityFormValue": "表單值變更", + "ActivityInitialDom": "初始網頁", + "ActivityMove": "移動", + "ActivityResize": "縮放", + "ActivityScroll": "捲動", + "ActivityScrollElement": "捲動元素", + "AdvancedOptions": "進階選項", + "AutoPlayNextPageview": "點擊以%1$s自動播放下個瀏覽頁面(快捷鍵 %2$s)", + "AvgAboveFoldDescription": "訪客不捲動時平均能看到這條線以上的內容", + "AvgAboveFoldTitle": "平均涵蓋 %s 像素之上", + "BreakpointGeneralHelp": "裝置類型在訪客訪問你的網站時就會自動偵測。然而,裝置類型有時候無法偵測。如果你的網站是響應式設計並且佈局在某個寬度下會變更,我們會將螢幕解析度較低的電腦版使用者納入這個裝置類型以增加更準確的熱度圖。任何比這個值還低的寬度將會被納入這個裝置類型。如果你的網站不是響應式設計,就設定為 0。", + "BreakpointGeneralHelpManage": "擁有超級使用者權限的使用者可以在管理中心 => 一般設定中變更這些斷點的預設值", + "BreakpointX": "%s斷點", + "CaptureKeystrokes": "捕捉按鍵", + "CaptureKeystrokesHelp": "若啟用,任何輸入於文字表單的文字將被紀錄。當文字被紀錄時,任何使用者輸入的文字將被取代為星號(*)。你可以將特定表單欄位加入屬性「data-matomo-unmask」來添加到白名單以錄製純文字。然而,密碼欄位和其他可能包含個人資訊(地址、Email、信用卡資訊、使用者名稱、電話號碼等等)欄位若被我們偵測到時,將會被自動隱藏(%1$s了解更多%2$s)。請注意如果你啟用了這項功能,你可能錄製到個人資料並影響 GDPR。", + "ChangeReplaySpeed": "變更重播速度(快捷鍵 %s)", + "ClickToSkipPauses": "點擊以%1$s略過暫停(快捷鍵 %2$s)", + "ColumnActionsDocumentation": "播放錄製的行為或永久刪除紀錄。", + "ColumnBrowserDocumentation": "錄製行為時,訪客使用的瀏覽器。", + "ColumnDeviceDocumentation": "錄製行為時,訪客使用的裝置。", + "ColumnLabelRecordedSessions": "到達網頁 → 離開網頁", + "ColumnLocationDocumentation": "錄製行為時,訪客所在的地點。", + "ColumnOperatingSystem": "作業系統", + "ColumnOperatingSystemDocumentation": "錄製行為時,訪客使用的作業系統。", + "ColumnPageviewsDocumentation": "這段行為錄製的過程中訪問的頁面數量。", + "ColumnResolutionDocumentation": "使用者開啟這個網頁時瀏覽器視窗內可見區域的像素大小。", + "ColumnTime": "時間", + "ColumnTimeDocumentation": "開始錄製的時間點,以網站的時區顯示。", + "ColumnTimeOnPageDocumentation": "錄製行為時,訪客在此網頁中花了多少時間。", + "ColumnTimeOnSiteDocumentation": "錄製行為時,訪客一共花了多少時間。", + "ConfigsInternetDisabled": "由於這個 Matomo 被禁止了網路連線,因此我們無法檢查「%s」是否能訪問。", + "ConfigsPhpErrorResult": "結果顯示,追蹤熱度圖和行為錄製可能無法作用。你可能需要變更你的伺服器設定來允許網際網路或內部網路訪問這個文件。", + "ConfigsPhpManualCheck": "請手動在瀏覽器中開啟網址來查看回應內容是否包含「Piwik.HeatmapSessionRecording」。如果沒有,你可能需要變更伺服器設定,因為這個檔案必須要能經由網際網路或內部網路在瀏覽器中訪問。", + "ConfigsPhpNotAccessible": "網址「%s」似乎無法經由網際網路或內部網路訪問。", + "ConfigsPhpSelfSignedError": "請求的「%s」出現 SSL 錯誤。或許你使用的自行簽署的憑證?", + "ConfigsPhpSuccess": "「%s」可以訪問", + "ConfigsPhpUnknown": "我們無法檢查「%s」是否能在網際網路或內部網路中訪問。", + "ConfigsPhpUnknownError": "請求的「%1$s」出現錯誤:%2$s。", + "CreateNewHeatmap": "建立新的熱度圖", + "CreateNewSessionRecording": "建立新的行為錄製", + "CreationDate": "建立日期", + "DeleteHeatmapConfirm": "你確定要刪除這個熱度圖?之前產生的熱度圖報表將不再顯示於報表 UI 中。", + "DeleteHeatmapScreenshotConfirm": "你確定要刪除這個截圖嗎?重新產生截圖可能需要花上一些時間。", + "DeleteRecordedPageview": "刪除這個網頁瀏覽紀錄(不會詢問確認)", + "DeleteRecordedSession": "刪除這個行為錄製(不會詢問確認)", + "DeleteScreenshot": "刪除截圖", + "DeleteSessionRecordingConfirm": "你確定要刪除這個行為錄製?之前產生的錄製將不再顯示於報表 UI 中。", + "DeleteX": "刪除%s", + "EditHeatmapX": "編輯熱度圖 %s", + "EditSessionRecordingX": "編輯行為錄製 %s", + "EditX": "編輯%s", + "EndHeatmapConfirm": "你確定要停止這個熱度圖捕捉新的活動?", + "EndSessionRecordingConfirm": "你確定要停止錄製新的行為?", + "ErrorArrayMissingKey": "陣列 %2$s 中位置 %3$s 的鍵「%1$s」遺失。", + "ErrorArrayMissingValue": "陣列 %2$s 中位置 %3$s 的鍵「%1$s」遺失值。", + "ErrorHeatmapDoesNotExist": "請求的熱度圖不存在", + "ErrorHeatmapNameDuplicate": "熱度圖的名稱已經被使用。", + "ErrorInnerIsNotAnArray": "「%2$s」中的每個「%1$s」必須是陣列。", + "ErrorInvalidRegExp": "正規表示式「%1$s」不含有效格式。", + "ErrorNotAnArray": "「%1$s」必須是陣列。", + "ErrorPageRuleRequired": "至少需要設定一條網頁規則。", + "ErrorSessionRecordingDoesNotExist": "請求的行為錄製不存在", + "ErrorXContainsWhitespace": "「%1$s」不允許包含任何空格。", + "ErrorXNotANumber": "「%1$s」必須是數字。", + "ErrorXNotProvided": "請輸入「%1$s」的值。", + "ErrorXNotWhitelisted": "「%1$s」的值不被允許,使用以下其一:%2$s。", + "ErrorXTooHigh": "「%1$s」過高,最大允許值為 %2$s。", + "ErrorXTooLong": "「%1$s」過長,最多允許 %2$s 個字元。", + "ErrorXTooLow": "「%1$s」過低,最小允許值為 %2$s。", + "ExcludedElements": "排除元素", + "ExcludedElementsHelp": "你可以選填定義 CSS 選擇器來排除不該在熱度圖預覽出現的特定元素。例如一個在網頁載入完成會出現的彈跳視窗。可以使用半形逗號來分隔多個選擇器。", + "FieldIncludedTargetsHelp": "目標讓你定義要在哪些網頁中紀錄使用者活動。你可以定義一個或多個條件。例如,你可以定義當網址或路徑等於某個特定值,並且只有當包含網址某個參數時才紀錄活動。活動只會在符合全部條件,而非符合某一個條件時才開始紀錄。所有的條件都不區分大小寫。當你選擇「簡易符合」時,網址的協議、搜尋參數和結尾斜線會被忽略。", + "FieldIncludedTargetsHelpSessions": "目標讓你設定只有在訪客到達指定網頁時才開始錄製行為。這可以讓你紀錄例如前往結帳前的過程。你可以定義一個或多個條件。例如,你可以定義當網址或路徑等於某個特定值,並且只有當包含網址某個參數時才錄製行為。行為只會在符合全部條件,而非符合某一個條件時才開始錄製。所有的條件都不區分大小寫。當你選擇「簡易符合」時,網址的協議、搜尋參數和結尾斜線會被忽略。", + "FieldNamePlaceholder": "例如「註冊網頁」", + "FilesystemDirectory": "資料夾", + "Filter": "過濾", + "GettingStarted": "開始使用", + "Heatmap": "熱度圖", + "HeatmapCreated": "已成功建立新的熱度圖。", + "HeatmapNameHelp": "定義這個熱度圖的報表要顯示的名稱。", + "HeatmapSampleLimit": "網頁瀏覽數", + "HeatmapSampleLimitHelp": "定義你想要錄製的網頁瀏覽總量。", + "HeatmapSampleRateHelp": "也被稱作「流量」。當你選擇 100%%,所有訪問了指定頁面的訪客都會被紀錄。當你選擇 10%%,只有每第 10 個訪客才會被紀錄。選擇的比率越低,達到選擇的採樣率時間會越長。", + "HeatmapUpdated": "熱度圖已成功更新。", + "HeatmapUsageBenefits": "熱度圖讓你紀錄你的使用者在特定網頁中的所有點擊、滑鼠移動和捲動紀錄。這將能幫助你知道使用者點擊了哪些不能點擊的東西、網頁中是否有幾乎不會被查看或互動的部分、你的訪客想尋找的東西和當使用者瀏覽你的網頁時能見度是多少等等。", + "HeatmapWidth": "熱度圖寬度", + "HeatmapX": "熱度圖 %s", + "HeatmapXRecordedSamplesSince": "自 %2$s 起已紀錄了 %1$s 個樣本。", + "Heatmaps": "熱度圖", + "Manage": "管理", + "ManageHeatmaps": "管理熱度圖", + "ManageSessionRecordings": "管理行為錄製", + "MinSessionTime": "最低行為時間", + "MinSessionTimeHelp": "只有在訪客至少在網頁中停留了指定時間才會開始紀錄行為。", + "NHeatmaps": "%s 個熱度圖", + "NSessionRecordings": "%s 個行為錄製", + "NoHeatmapSamplesRecordedYet": "這個熱度圖還沒有紀錄到任何網頁瀏覽。如果現在應該要有紀錄到東西,可能是這個熱度圖的網頁目標設定不符合你網站中的任何頁面。建議確認「管理中心」(僅限超級使用者)內的「系統檢查」來查看你的系統是否已設定自動追蹤熱度圖。", + "NoHeatmapScreenshotRecordedYet": "至目前為止已經錄製了 %1$s 個樣本。然而,還沒有擷取任何截圖。如果這個熱度圖有「%2$s」,可能會在第一個使用者首次開啟後一段時間才能顯示。根據採樣率可能會花上一些時間。", + "NoHeatmapsConfiguredInfo": "目前沒有啟用中的熱度圖。要查看熱度圖,請向權限管理員以上的使用者要求建立一個新的熱度圖。", + "NoHeatmapsFound": "找不到熱度圖", + "NoSessionRecordedYet": "還沒有錄製到任何行為。如果現在應該要有錄製到東西,可能是到達網頁的設定不符合你網站中的任何頁面。建議確認「管理中心」(僅限超級使用者)內的「系統檢查」來查看你的系統是否已設定自動錄製行為。", + "NoSessionRecordingsConfiguredInfo": "目前沒有啟用中的行為錄製。要錄製新的行為,請向權限管理員以上的使用者要求建立一個新的行為錄製。", + "NoSessionRecordingsFound": "找不到行為錄製", + "NotSupportedBrowser": "不支援這個瀏覽器,請使用較新的版本或常是其他瀏覽器。", + "OnePageview": "1 個瀏覽數", + "PageRule": "網頁規則", + "PageviewXofY": "瀏覽頁面 %1$s / %2$s", + "PageviewsInVisit": "這段錄製行為內紀錄到的網頁", + "PersonalInformationNote": "請注意%1$s可能紀錄到個人或敏感資訊。如果你想要隱藏網站或應用程式中特定的內容,你可以在這個內容的 HTML 標籤上加入 %2$sdata-matomo-mask%3$s 屬性。%4$s了解更多%5$s", + "PlayRecordedSession": "播放此錄製的行為", + "PlayerDurationXofY": "%1$s / %2$s", + "PlayerForwardFast": "快轉 %1$s 秒(快捷鍵 %2$s)", + "PlayerPageViewNext": "播放此訪客下一個瀏覽的頁面(%1$s)(快捷鍵 %2$s)", + "PlayerPageViewPrevious": "播放此訪客上一個瀏覽的頁面(%1$s)(快捷鍵 %2$s)", + "PlayerPause": "暫停(快捷鍵 %s 或空白鍵)", + "PlayerPlay": "播放(快捷鍵 %s 或空白鍵)", + "PlayerReplay": "重播(快捷鍵 %s 或空白鍵)", + "PlayerRewindFast": "倒帶 %1$s 秒(快捷鍵 %2$s)", + "RecordedHeatmapDocStatusActive": "這個熱度圖已啟用。最多會錄製 %1$d 個瀏覽數中的 %2$s。", + "RecordedHeatmapDocStatusEnded": "這個熱度圖已結束,因此不會再紀錄新的活動。", + "RecordedSessions": "已錄製的行為", + "RecordedSessionsDocStatusActive": "這個行為錄製已啟用。最多會錄製 %1$d 個行為中的 %2$s。", + "RecordedSessionsDocStatusEnded": "這個行為錄製已結束,因此不會再紀錄新的行為。", + "Recordings": "行為錄製", + "ReplayRecordedSession": "重播錄製的行為", + "ReplayX": "重播 %s", + "ReportRecordedSessionsDocumentation": "已錄製行為的列表。你可以重播任何錄製的行為。", + "RequiresActivity": "需要活動", + "RequiresActivityHelp": "若啟用,只有在包含捲動或點擊活動的瀏覽會被錄製行為。這將預防錄製到沒有太多活動的行為。", + "Rule": "規則", + "SampleLimit": "採樣限制", + "SampleRate": "取樣率", + "ScreenshotUrl": "截圖網址", + "ScreenshotUrlHelp": "預設狀態下,當訪客首次訪問目標網頁時會拍攝網頁截圖。如果有符合你的目標網頁的不同網址,你可以指定要用來擷取截圖的網址。可視化熱度圖只有在當至少有一個訪客訪問了這個網址並且紀錄了活動才可用。如果輸入了,網址必須完全相符。若要忽略協議,將網址以雙斜線開頭(//example.com)。請注意當截圖已擷取後,這個網址就無法變更。", + "SessionNameHelp": "定義這個行為錄製要顯示的名稱。", + "SessionRecording": "行為錄製", + "SessionRecordingCreated": "已成功建立新的行為錄製。", + "SessionRecordingUpdated": "行為錄製已成功更新。", + "SessionRecordingX": "行為錄製 %s", + "SessionRecordings": "行為錄製", + "SessionRecordingsUsageBenefits": "行為錄製讓你能紀錄一個真實的訪客在訪問過程中的所有活動,如點擊、滑鼠移動、捲動、視窗縮放、網頁變更和表單互動。接著你可以重播這些互動內容來觀察訪客是怎麼和你的網站互動的。這將讓你了解他們的期望、可能遇到的問題和使用模式等等。", + "SessionSampleLimit": "行為數量", + "SessionSampleLimitHelp": "定義你想要錄製的行為總量。", + "SessionSampleRateHelp": "也被稱作「流量」。當你選擇 100%%,所有訪問了目標網頁的行為都會被錄製。當你選擇 10%%,只有每第 10 個訪客才會被紀錄。選擇的比率越低,達到選擇的採樣率時間會越長。", + "StatusActive": "啟用中", + "StatusEnded": "已結束", + "StopX": "停止這個%s繼續紀錄新的活動。", + "TargetAttributePath": "路徑", + "TargetAttributeUrl": "網址", + "TargetAttributeUrlParameter": "網址參數", + "TargetAttributeUrlParameterExample": "nameOfUrlParameter", + "TargetPage": "目標網頁", + "TargetPageTestErrorInvalidUrl": "輸入包含協議的網址。", + "TargetPageTestLabel": "輸入包含協議的完整網址來檢查活動是否會在這個網址上進行紀錄:", + "TargetPageTestTitle": "網址驗證器", + "TargetPageTestUrlMatches": "這個網址上的活動會被紀錄", + "TargetPageTestUrlNotMatches": "這個網址上的活動不會被紀錄", + "TargetPages": "到達網頁", + "TargetTypeContains": "包含", + "TargetTypeEqualsExactly": "完全相符", + "TargetTypeEqualsExactlyInfo": "值必須完全相符,包含網址協議、搜尋參數和 hash。", + "TargetTypeEqualsSimple": "簡易相符", + "TargetTypeEqualsSimpleInfo": "這個網址將符合任何協議(例如 http 和 https)並忽略是否包含「www.」子域名。網址路徑內任何結尾斜線和搜尋參數以及 hash 部分都會被忽略。", + "TargetTypeExists": "存在", + "TargetTypeIsAny": "任何", + "TargetTypeIsNot": "不%s", + "TargetTypeRegExp": "符合正規表示式", + "TargetTypeRegExpInfo": "任何正規表示式,例如「^(.*)test(.*)$」。", + "TargetTypeStartsWith": "開始於", + "TimeOnSite": "停留時間", + "UpdatingData": "數據更新中…", + "UrlParameterValueToMatchPlaceholder": "符合網址參數名稱的值", + "UrlXDoesNotLookLikeUrl": "%s 看起來不像網址。請確定它包含了協議如 http:// 或起始於 //", + "ViewReport": "查看報表", + "ViewportResolution": "可見區域解析度(寬 x 高)", + "XSamples": "%s 個樣本", + "disable": "停用", + "enable": "啟用" + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/libs/MutationObserver.js/MutationObserver.js b/files/plugin-HeatmapSessionRecording-5.2.4/libs/MutationObserver.js/MutationObserver.js new file mode 100644 index 0000000..2393f42 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/libs/MutationObserver.js/MutationObserver.js @@ -0,0 +1,624 @@ +/*! + * Shim for MutationObserver interface + * Author: Graeme Yeates (github.com/megawac) + * Repository: https://github.com/megawac/MutationObserver.js + * License: WTFPL V2, 2004 (wtfpl.net). + * Though credit and staring the repo will make me feel pretty, you can modify and redistribute as you please. + * Attempts to follow spec (https://www.w3.org/TR/dom/#mutation-observers) as closely as possible for native javascript + * See https://github.com/WebKit/webkit/blob/master/Source/WebCore/dom/MutationObserver.cpp for current webkit source c++ implementation + */ + +/** + * prefix bugs: + - https://bugs.webkit.org/show_bug.cgi?id=85161 + - https://bugzilla.mozilla.org/show_bug.cgi?id=749920 + * Don't use WebKitMutationObserver as Safari (6.0.5-6.1) use a buggy implementation +*/ + +/* + * Modified by InnoCraft to also fallback to WebKitMutationObserver and MozMutationObserver + */ +window.MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver || (function(undefined) { + "use strict"; + /** + * @param {function(Array., MutationObserver)} listener + * @constructor + */ + function MutationObserver(listener) { + /** + * @type {Array.} + * @private + */ + this._watched = []; + /** @private */ + this._listener = listener; + } + + /** + * Start a recursive timeout function to check all items being observed for mutations + * @type {MutationObserver} observer + * @private + */ + function startMutationChecker(observer) { + (function check() { + var mutations = observer.takeRecords(); + + if (mutations.length) { // fire away + // calling the listener with context is not spec but currently consistent with FF and WebKit + observer._listener(mutations, observer); + } + /** @private */ + observer._timeout = setTimeout(check, MutationObserver._period); + })(); + } + + /** + * Period to check for mutations (~32 times/sec) + * @type {number} + * @expose + */ + MutationObserver._period = 30 /*ms+runtime*/ ; + + /** + * Exposed API + * @expose + * @final + */ + MutationObserver.prototype = { + /** + * see https://dom.spec.whatwg.org/#dom-mutationobserver-observe + * not going to throw here but going to follow the current spec config sets + * @param {Node|null} $target + * @param {Object|null} config : MutationObserverInit configuration dictionary + * @expose + * @return undefined + */ + observe: function($target, config) { + /** + * Using slightly different names so closure can go ham + * @type {!Object} : A custom mutation config + */ + var settings = { + attr: !! (config.attributes || config.attributeFilter || config.attributeOldValue), + + // some browsers enforce that subtree must be set with childList, attributes or characterData. + // We don't care as spec doesn't specify this rule. + kids: !! config.childList, + descendents: !! config.subtree, + charData: !! (config.characterData || config.characterDataOldValue) + }; + + var watched = this._watched; + + // remove already observed target element from pool + for (var i = 0; i < watched.length; i++) { + if (watched[i].tar === $target) watched.splice(i, 1); + } + + if (config.attributeFilter) { + /** + * converts to a {key: true} dict for faster lookup + * @type {Object.} + */ + settings.afilter = reduce(config.attributeFilter, function(a, b) { + a[b] = true; + return a; + }, {}); + } + + watched.push({ + tar: $target, + fn: createMutationSearcher($target, settings) + }); + + // reconnect if not connected + if (!this._timeout) { + startMutationChecker(this); + } + }, + + /** + * Finds mutations since last check and empties the "record queue" i.e. mutations will only be found once + * @expose + * @return {Array.} + */ + takeRecords: function() { + var mutations = []; + var watched = this._watched; + + for (var i = 0; i < watched.length; i++) { + watched[i].fn(mutations); + } + + return mutations; + }, + + /** + * @expose + * @return undefined + */ + disconnect: function() { + this._watched = []; // clear the stuff being observed + clearTimeout(this._timeout); // ready for garbage collection + /** @private */ + this._timeout = null; + } + }; + + /** + * Simple MutationRecord pseudoclass. No longer exposing as its not fully compliant + * @param {Object} data + * @return {Object} a MutationRecord + */ + function MutationRecord(data) { + var settings = { // technically these should be on proto so hasOwnProperty will return false for non explicitly props + type: null, + target: null, + addedNodes: [], + removedNodes: [], + previousSibling: null, + nextSibling: null, + attributeName: null, + attributeNamespace: null, + oldValue: null + }; + for (var prop in data) { + if (has(settings, prop) && data[prop] !== undefined) settings[prop] = data[prop]; + } + return settings; + } + + /** + * Creates a func to find all the mutations + * + * @param {Node} $target + * @param {!Object} config : A custom mutation config + */ + function createMutationSearcher($target, config) { + /** type {Elestuct} */ + var $oldstate = clone($target, config); // create the cloned datastructure + + /** + * consumes array of mutations we can push to + * + * @param {Array.} mutations + */ + return function(mutations) { + var olen = mutations.length, dirty; + + if (config.charData && $target.nodeType === 3 && $target.nodeValue !== $oldstate.charData) { + mutations.push(new MutationRecord({ + type: "characterData", + target: $target, + oldValue: $oldstate.charData + })); + } + + // Alright we check base level changes in attributes... easy + if (config.attr && $oldstate.attr) { + findAttributeMutations(mutations, $target, $oldstate.attr, config.afilter); + } + + // check childlist or subtree for mutations + if (config.kids || config.descendents) { + dirty = searchSubtree(mutations, $target, $oldstate, config); + } + + // reclone data structure if theres changes + if (dirty || mutations.length !== olen) { + /** type {Elestuct} */ + $oldstate = clone($target, config); + } + }; + } + + /* attributes + attributeFilter helpers */ + + // Check if the environment has the attribute bug (#4) which cause + // element.attributes.style to always be null. + var hasAttributeBug = document.createElement("i"); + hasAttributeBug.style.top = 0; + hasAttributeBug = hasAttributeBug.attributes.style.value != "null"; + + /** + * Gets an attribute value in an environment without attribute bug + * + * @param {Node} el + * @param {Attr} attr + * @return {String} an attribute value + */ + function getAttributeSimple(el, attr) { + // There is a potential for a warning to occur here if the attribute is a + // custom attribute in IE<9 with a custom .toString() method. This is + // just a warning and doesn't affect execution (see #21) + return attr.value; + } + + /** + * Gets an attribute value with special hack for style attribute (see #4) + * + * @param {Node} el + * @param {Attr} attr + * @return {String} an attribute value + */ + function getAttributeWithStyleHack(el, attr) { + // As with getAttributeSimple there is a potential warning for custom attribtues in IE7. + return attr.name !== "style" ? attr.value : el.style.cssText; + } + + var getAttributeValue = hasAttributeBug ? getAttributeSimple : getAttributeWithStyleHack; + + /** + * fast helper to check to see if attributes object of an element has changed + * doesnt handle the textnode case + * + * @param {Array.} mutations + * @param {Node} $target + * @param {Object.} $oldstate : Custom attribute clone data structure from clone + * @param {Object} filter + */ + function findAttributeMutations(mutations, $target, $oldstate, filter) { + var checked = {}; + var attributes = $target.attributes; + var attr; + var name; + var i = attributes.length; + while (i--) { + attr = attributes[i]; + name = attr.name; + if (!filter || has(filter, name)) { + if (getAttributeValue($target, attr) !== $oldstate[name]) { + // The pushing is redundant but gzips very nicely + mutations.push(MutationRecord({ + type: "attributes", + target: $target, + attributeName: name, + oldValue: $oldstate[name], + attributeNamespace: attr.namespaceURI // in ie<8 it incorrectly will return undefined + })); + } + checked[name] = true; + } + } + for (name in $oldstate) { + if (!(checked[name])) { + mutations.push(MutationRecord({ + target: $target, + type: "attributes", + attributeName: name, + oldValue: $oldstate[name] + })); + } + } + } + + /** + * searchSubtree: array of mutations so far, element, element clone, bool + * synchronous dfs comparision of two nodes + * This function is applied to any observed element with childList or subtree specified + * Sorry this is kind of confusing as shit, tried to comment it a bit... + * codereview.stackexchange.com/questions/38351 discussion of an earlier version of this func + * + * @param {Array} mutations + * @param {Node} $target + * @param {!Object} $oldstate : A custom cloned node from clone() + * @param {!Object} config : A custom mutation config + */ + function searchSubtree(mutations, $target, $oldstate, config) { + // Track if the tree is dirty and has to be recomputed (#14). + var dirty; + /* + * Helper to identify node rearrangment and stuff... + * There is no gaurentee that the same node will be identified for both added and removed nodes + * if the positions have been shuffled. + * conflicts array will be emptied by end of operation + */ + function resolveConflicts(conflicts, node, $kids, $oldkids, numAddedNodes) { + // the distance between the first conflicting node and the last + var distance = conflicts.length - 1; + // prevents same conflict being resolved twice consider when two nodes switch places. + // only one should be given a mutation event (note -~ is used as a math.ceil shorthand) + var counter = -~((distance - numAddedNodes) / 2); + var $cur; + var oldstruct; + var conflict; + while ((conflict = conflicts.pop())) { + $cur = $kids[conflict.i]; + oldstruct = $oldkids[conflict.j]; + + // attempt to determine if there was node rearrangement... won't gaurentee all matches + // also handles case where added/removed nodes cause nodes to be identified as conflicts + if (config.kids && counter && Math.abs(conflict.i - conflict.j) >= distance) { + mutations.push(MutationRecord({ + type: "childList", + target: node, + addedNodes: [$cur], + removedNodes: [$cur], + // haha don't rely on this please + nextSibling: $cur.nextSibling, + previousSibling: $cur.previousSibling + })); + counter--; // found conflict + } + + // Alright we found the resorted nodes now check for other types of mutations + if (config.attr && oldstruct.attr) findAttributeMutations(mutations, $cur, oldstruct.attr, config.afilter); + if (config.charData && $cur.nodeType === 3 && $cur.nodeValue !== oldstruct.charData) { + mutations.push(MutationRecord({ + type: "characterData", + target: $cur, + oldValue: oldstruct.charData + })); + } + // now look @ subtree + if (config.descendents) findMutations($cur, oldstruct); + } + } + + /** + * Main worker. Finds and adds mutations if there are any + * @param {Node} node + * @param {!Object} old : A cloned data structure using internal clone + */ + function findMutations(node, old) { + var $kids = node.childNodes; + var $oldkids = old.kids; + var klen = $kids.length; + // $oldkids will be undefined for text and comment nodes + var olen = $oldkids ? $oldkids.length : 0; + // if (!olen && !klen) return; // both empty; clearly no changes + + // we delay the intialization of these for marginal performance in the expected case (actually quite signficant on large subtrees when these would be otherwise unused) + // map of checked element of ids to prevent registering the same conflict twice + var map; + // array of potential conflicts (ie nodes that may have been re arranged) + var conflicts; + var id; // element id from getElementId helper + var idx; // index of a moved or inserted element + + var oldstruct; + // current and old nodes + var $cur; + var $old; + // track the number of added nodes so we can resolve conflicts more accurately + var numAddedNodes = 0; + + // iterate over both old and current child nodes at the same time + var i = 0, j = 0; + // while there is still anything left in $kids or $oldkids (same as i < $kids.length || j < $oldkids.length;) + while( i < klen || j < olen ) { + // current and old nodes at the indexs + $cur = $kids[i]; + oldstruct = $oldkids[j]; + $old = oldstruct && oldstruct.node; + + if ($cur === $old) { // expected case - optimized for this case + // check attributes as specified by config + if (config.attr && oldstruct.attr) /* oldstruct.attr instead of textnode check */findAttributeMutations(mutations, $cur, oldstruct.attr, config.afilter); + // check character data if node is a comment or textNode and it's being observed + if (config.charData && oldstruct.charData !== undefined && $cur.nodeValue !== oldstruct.charData) { + mutations.push(MutationRecord({ + type: "characterData", + target: $cur, + oldValue: oldstruct.charData + })); + } + + // resolve conflicts; it will be undefined if there are no conflicts - otherwise an array + if (conflicts) resolveConflicts(conflicts, node, $kids, $oldkids, numAddedNodes); + + // recurse on next level of children. Avoids the recursive call when there are no children left to iterate + if (config.descendents && ($cur.childNodes.length || oldstruct.kids && oldstruct.kids.length)) findMutations($cur, oldstruct); + + i++; + j++; + } else { // (uncommon case) lookahead until they are the same again or the end of children + dirty = true; + if (!map) { // delayed initalization (big perf benefit) + map = {}; + conflicts = []; + } + if ($cur) { + // check id is in the location map otherwise do a indexOf search + if (!(map[id = getElementId($cur)])) { // to prevent double checking + // mark id as found + map[id] = true; + // custom indexOf using comparitor checking oldkids[i].node === $cur + if ((idx = indexOfCustomNode($oldkids, $cur, j)) === -1) { + if (config.kids) { + mutations.push(MutationRecord({ + type: "childList", + target: node, + addedNodes: [$cur], // $cur is a new node + nextSibling: $cur.nextSibling, + previousSibling: $cur.previousSibling + })); + numAddedNodes++; + } + } else { + conflicts.push({ // add conflict + i: i, + j: idx + }); + } + } + i++; + } + + if ($old && + // special case: the changes may have been resolved: i and j appear congurent so we can continue using the expected case + $old !== $kids[i] + ) { + if (!(map[id = getElementId($old)])) { + map[id] = true; + if ((idx = indexOf($kids, $old, i)) === -1) { + if (config.kids) { + mutations.push(MutationRecord({ + type: "childList", + target: old.node, + removedNodes: [$old], + nextSibling: $oldkids[j + 1], // praise no indexoutofbounds exception + previousSibling: $oldkids[j - 1] + })); + numAddedNodes--; + } + } else { + conflicts.push({ + i: idx, + j: j + }); + } + } + j++; + } + }// end uncommon case + }// end loop + + // resolve any remaining conflicts + if (conflicts) resolveConflicts(conflicts, node, $kids, $oldkids, numAddedNodes); + } + findMutations($target, $oldstate); + return dirty; + } + + /** + * Utility + * Cones a element into a custom data structure designed for comparision. https://gist.github.com/megawac/8201012 + * + * @param {Node} $target + * @param {!Object} config : A custom mutation config + * @return {!Object} : Cloned data structure + */ + function clone($target, config) { + var recurse = true; // set true so childList we'll always check the first level + return (function copy($target) { + var elestruct = { + /** @type {Node} */ + node: $target + }; + + // Store current character data of target text or comment node if the config requests + // those properties to be observed. + if (config.charData && ($target.nodeType === 3 || $target.nodeType === 8)) { + elestruct.charData = $target.nodeValue; + } + // its either a element, comment, doc frag or document node + else { + // Add attr only if subtree is specified or top level and avoid if + // attributes is a document object (#13). + if (config.attr && recurse && $target.nodeType === 1) { + /** + * clone live attribute list to an object structure {name: val} + * @type {Object.} + */ + elestruct.attr = reduce($target.attributes, function(memo, attr) { + if (!config.afilter || config.afilter[attr.name]) { + memo[attr.name] = getAttributeValue($target, attr); + } + return memo; + }, {}); + } + + // whether we should iterate the children of $target node + if (recurse && ((config.kids || config.charData) || (config.attr && config.descendents)) ) { + /** @type {Array.} : Array of custom clone */ + elestruct.kids = map($target.childNodes, copy); + } + + recurse = config.descendents; + } + return elestruct; + })($target); + } + + /** + * indexOf an element in a collection of custom nodes + * + * @param {NodeList} set + * @param {!Object} $node : A custom cloned node + * @param {number} idx : index to start the loop + * @return {number} + */ + function indexOfCustomNode(set, $node, idx) { + return indexOf(set, $node, idx, JSCompiler_renameProperty("node")); + } + + // using a non id (eg outerHTML or nodeValue) is extremely naive and will run into issues with nodes that may appear the same like
  • + var counter = 1; // don't use 0 as id (falsy) + /** @const */ + var expando = "mo_id"; + + /** + * Attempt to uniquely id an element for hashing. We could optimize this for legacy browsers but it hopefully wont be called enough to be a concern + * + * @param {Node} $ele + * @return {(string|number)} + */ + function getElementId($ele) { + try { + return $ele.id || ($ele[expando] = $ele[expando] || counter++); + } catch (o_O) { // ie <8 will throw if you set an unknown property on a text node + try { + return $ele.nodeValue; // naive + } catch (shitie) { // when text node is removed: https://gist.github.com/megawac/8355978 :( + return counter++; + } + } + } + + /** + * **map** Apply a mapping function to each item of a set + * @param {Array|NodeList} set + * @param {Function} iterator + */ + function map(set, iterator) { + var results = []; + for (var index = 0; index < set.length; index++) { + results[index] = iterator(set[index], index, set); + } + return results; + } + + /** + * **Reduce** builds up a single result from a list of values + * @param {Array|NodeList|NamedNodeMap} set + * @param {Function} iterator + * @param {*} [memo] Initial value of the memo. + */ + function reduce(set, iterator, memo) { + for (var index = 0; index < set.length; index++) { + memo = iterator(memo, set[index], index, set); + } + return memo; + } + + /** + * **indexOf** find index of item in collection. + * @param {Array|NodeList} set + * @param {Object} item + * @param {number} idx + * @param {string} [prop] Property on set item to compare to item + */ + function indexOf(set, item, idx, prop) { + for (/*idx = ~~idx*/; idx < set.length; idx++) {// start idx is always given as this is internal + if ((prop ? set[idx][prop] : set[idx]) === item) return idx; + } + return -1; + } + + /** + * @param {Object} obj + * @param {(string|number)} prop + * @return {boolean} + */ + function has(obj, prop) { + return obj[prop] !== undefined; // will be nicely inlined by gcc + } + + // GCC hack see https://stackoverflow.com/a/23202438/1517919 + function JSCompiler_renameProperty(a) { + return a; + } + + return MutationObserver; +})(void 0); diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/libs/MutationObserver.js/README.md b/files/plugin-HeatmapSessionRecording-5.2.4/libs/MutationObserver.js/README.md new file mode 100644 index 0000000..a2112ad --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/libs/MutationObserver.js/README.md @@ -0,0 +1,68 @@ +MutationObserver +======================== + +[![Browser Test Status](https://saucelabs.com/browser-matrix/mutationobserver.svg)](https://saucelabs.com/u/mutationobserver) +Note: the svg swapped the working browsers; IE8 works while IE7 fails 1 test + +A polyfill for the [MutationObserver API](http://www.w3.org/TR/2013/WD-dom-20131107/#mutation-observers) ([can I use?](http://caniuse.com/mutationobserver)). The polyfill is more cause we can than should (with subtree at any rate)... It's async and uses a recursive timeout fallback (default checks changes every 30ms + runtime) instead of using the deprecated [DOM3 MutationEvents](http://www.w3.org/TR/DOM-Level-3-Events/#events-mutationevents) so theoretically can support virtually any environment. + +```sh +$ npm install mutationobserver-shim +$ bower install MutationObserver-shim +``` + +```html + +``` + +### Polyfill differences from standard interface + +#### MutationObserver + +* Implemented using a recursive `setTimeout` (every ~30 ms) rather than using a `setImmediate` polyfill; so calls will be made less frequently and likely with more data than the standard MutationObserver. In addition, it can miss changes that occur and then are lost in the interval window. +* Setting an observed elements html using `innerHTML` will call `childList` observer listeners with several mutations with only 1 addedNode or removed node per mutation. With the standard you would have 1 call with multiple nodes in addedNodes and removedNodes node lists. +* With `childList` and `subtree` changes in node order (eg first element gets swapped with last) should fire a `addedNode` and `removedNode` mutation but the correct node may not always be identified. + +#### MutationRecord + +* `addedNodes` and `removedNodes` are arrays instead of `NodeList`s +* `oldValue` is always called with attribute changes +* `nextSibling` and `previousSibling` correctfullness is questionable (hard to know if the order of appended items). I'd suggest not relying on them anyway (my tests are extremely permissive with these attributes) + +### Supported MutationObserverInit properties + +Currently supports the following [MutationObserverInit properties](https://developer.mozilla.org/en/docs/Web/API/MutationObserver#MutationObserverInit): + +* **childList**: Set to truthy if mutations to target's immediate children are to be observed. +* **subtree**: Set to truthy to do deep scans on a target's children. +* **attributes**: Set to truthy if mutations to target's children are to be observed. As explained in #4, the `style` attribute may not be matched in ie<8. +* **attributeFilter**: Set to an array of attribute local names (without namespace) if not all attribute mutations need to be observed. +* **attributeOldValue**: doesn't do anything attributes are always called with old value +* **characterData**: currently follows Mozilla's implementation in that it will only watch `textNodes` values and not, like in webkit, where setting .innerHTML will add a characterData mutation. + +### Performance + +By default, the polyfill will check observed nodes about 25 times per second (30 ms interval) for mutations. Try running [these jsperf.com tests](http://jsperf.com/mutationobserver-shim) and the JSLitmus tests in the test suite for usage performance tests. It may be worthwile to adapt `MutationObserver._period` based on UA or heuristics (todo). + +From my tests observing any size element without `subtree` enabled is relatively cheap. Although I've optimized the subtree check to the best of my abilities it can be costly on large trees. You can draw your own conclusions based on the JSLitmus and jsperf tests noting that you can expect the `mo` to do its check 28+ times a second (by default). + +Although supported, I'd recommend against watching `attributes` on the `subtree` on large structures, as the check is complex and expensive on terrible hardware like my phone :( + +The included minified file has been tuned for performance. + +### Compatibility + +I've tested and verified compatibility in the following browsers + [these Sauce browsers](https://saucelabs.com/u/mutationobserver) + +* Internet Explorer 8 (emulated), 9, 10 in win7 and win8 +* Firefox 4, 21, 24, 26 in OSX, win7 and win8 +* Opera 11.8, 12.16 in win7 +* "Internet" on Android HTC One V +* Blackberry 6.0.16 + +Try [running the test suite](https://rawgithub.com/megawac/MutationObserver.js/master/test/index.html) and see some simple example usage: + +* http://jsbin.com/suqewogone listen to images being appended dynamically +* http://jsbin.com/bapohopuwi autoscroll an element as new content is added + +See http://dev.opera.com/articles/view/mutation-observers-tutorial/ for some sample usage. diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/libs/MutationObserver.js/dist/README.md b/files/plugin-HeatmapSessionRecording-5.2.4/libs/MutationObserver.js/dist/README.md new file mode 100644 index 0000000..a3e83b2 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/libs/MutationObserver.js/dist/README.md @@ -0,0 +1,7 @@ +###Compiled files + +*Compiled by Google closure compiler in `ADVANCED_OPTIMIZATIONS`* + +- Original: 25 kB +- Minified: 3.7 kB +- Gzipped: 1.6 kB diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/libs/MutationObserver.js/dist/mutationobserver.min.js b/files/plugin-HeatmapSessionRecording-5.2.4/libs/MutationObserver.js/dist/mutationobserver.min.js new file mode 100644 index 0000000..94e8949 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/libs/MutationObserver.js/dist/mutationobserver.min.js @@ -0,0 +1,10 @@ +// mutationobserver-shim v0.3.2 (github.com/megawac/MutationObserver.js) +// Authors: Graeme Yeates (github.com/megawac) +window.MutationObserver=window.MutationObserver||function(w){function v(a){this.i=[];this.m=a}function I(a){(function c(){var d=a.takeRecords();d.length&&a.m(d,a);a.h=setTimeout(c,v._period)})()}function p(a){var b={type:null,target:null,addedNodes:[],removedNodes:[],previousSibling:null,nextSibling:null,attributeName:null,attributeNamespace:null,oldValue:null},c;for(c in a)b[c]!==w&&a[c]!==w&&(b[c]=a[c]);return b}function J(a,b){var c=C(a,b);return function(d){var f=d.length,n;b.a&&3===a.nodeType&& +a.nodeValue!==c.a&&d.push(new p({type:"characterData",target:a,oldValue:c.a}));b.b&&c.b&&A(d,a,c.b,b.f);if(b.c||b.g)n=K(d,a,c,b);if(n||d.length!==f)c=C(a,b)}}function L(a,b){return b.value}function M(a,b){return"style"!==b.name?b.value:a.style.cssText}function A(a,b,c,d){for(var f={},n=b.attributes,k,g,x=n.length;x--;)k=n[x],g=k.name,d&&d[g]===w||(D(b,k)!==c[g]&&a.push(p({type:"attributes",target:b,attributeName:g,oldValue:c[g],attributeNamespace:k.namespaceURI})),f[g]=!0);for(g in c)f[g]||a.push(p({target:b, +type:"attributes",attributeName:g,oldValue:c[g]}))}function K(a,b,c,d){function f(b,c,f,k,y){var g=b.length-1;y=-~((g-y)/2);for(var h,l,e;e=b.pop();)h=f[e.j],l=k[e.l],d.c&&y&&Math.abs(e.j-e.l)>=g&&(a.push(p({type:"childList",target:c,addedNodes:[h],removedNodes:[h],nextSibling:h.nextSibling,previousSibling:h.previousSibling})),y--),d.b&&l.b&&A(a,h,l.b,d.f),d.a&&3===h.nodeType&&h.nodeValue!==l.a&&a.push(p({type:"characterData",target:h,oldValue:l.a})),d.g&&n(h,l)}function n(b,c){for(var g=b.childNodes, +q=c.c,x=g.length,v=q?q.length:0,h,l,e,m,t,z=0,u=0,r=0;u + +This work is free. You can redistribute it and/or modify it under the +terms of the Do What The Fuck You Want To Public License, Version 2, +as published by Sam Hocevar. See http://www.wtfpl.net/ for more details. + +This program is free software. It comes without any warranty, to +the extent permitted by applicable law. You can redistribute it +and/or modify it under the terms of the Do What The Fuck You Want +To Public License, Version 2, as published by Sam Hocevar. See +http://www.wtfpl.net/ for more details. \ No newline at end of file diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/libs/MutationObserver.js/package.json b/files/plugin-HeatmapSessionRecording-5.2.4/libs/MutationObserver.js/package.json new file mode 100644 index 0000000..f99c9bc --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/libs/MutationObserver.js/package.json @@ -0,0 +1,49 @@ +{ + "name": "mutationobserver-shim", + "short name": "mutationobserver", + "description": "MutationObserver shim for ES3 environments", + "version": "0.3.2", + "keywords": [ + "DOM", + "observer", + "mutation observer", + "MutationObserver" + ], + "authors": [ + { + "name": "Graeme Yeates", + "email": "github.com/megawac" + } + ], + "repository": { + "type": "git", + "git": "git@github.com:megawac/MutationObserver.js.git", + "url": "github.com/megawac/MutationObserver.js" + }, + "main": "dist/mutationobserver.min.js", + "scripts": { + "test": "grunt test --verbose" + }, + "files": [ + "MutationObserver.js", + "dist/mutationobserver.min.js" + ], + "license": { + "type": "WTFPL", + "version": "v2 2004", + "url": "http://www.wtfpl.net/" + }, + "devDependencies": { + "grunt": "~0.4.2", + "grunt-bumpup": "~0.5.0", + "grunt-closurecompiler": "0.9", + "grunt-contrib-connect": "0.7", + "grunt-contrib-jshint": ">= 0.8", + "grunt-contrib-qunit": "~0.5.0", + "grunt-file-info": "~1.0.14", + "grunt-saucelabs": "~4.1.2", + "grunt-tagrelease": "~0.3.1", + "matchdep": "~0.3.0", + "phantomjs": "1.9.x" + } +} \ No newline at end of file diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/COPYING b/files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/COPYING new file mode 100644 index 0000000..65ee1c1 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/COPYING @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/README.md b/files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/README.md new file mode 100644 index 0000000..5eac04f --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/README.md @@ -0,0 +1,59 @@ +# What is this? # + +Mutation Summary is a JavaScript library that makes observing changes to the DOM fast, easy and safe. + +It's built on top of (and requires) a new browser API called [DOM Mutation Observers](http://dom.spec.whatwg.org/#mutation-observers). + + * [Browsers which currently implement DOM Mutation Observers](DOMMutationObservers.md#browser-availability). + * [DOM Mutation Observers API and its relationship to this library and (the deprecated) DOM Mutation Events](DOMMutationObservers.md). + + + +# Why do I need it? # + +Mutation Summary does five main things for you: + + * **It tells you how the document is different now from how it was.** As its name suggests, it summarizes what’s happened. It’s as if it takes a picture of the document when you first create it, and then again after each time it calls you back. When things have changed, it calls you with a concise description of exactly what’s different now from the last picture it took for you. + * **It handles any and all changes, no matter how complex.** All kinds of things can happen to the DOM: values can change and but put back to what they were, large parts can be pulled out, changed, rearranged, put back. Mutation Summary can take any crazy thing you throw at it. Go ahead, tear the document to shreds, Mutation Summary won’t even blink. + * **It lets you express what kinds of things you’re interested in.** It presents a query API that lets you tell it exactly what kinds of changes you’re interested in. This includes support for simple CSS-like selector descriptions of elements you care about. + * **It’s fast.** The time and memory it takes is dependant on number of changes that occurred (which typically involves only a few nodes) -- not the size of your document (which is commonly thousands of nodes). + * **It can automatically ignore changes you make during your callback.** Mutation Summary is going to call you back when changes have occurred. If you need to react to those changes by making more changes -- won’t you hear about those changes the next time it calls you back? Not unless you [ask for that](APIReference.md#configuration-options). By default, it stops watching the document immediately before it calls you back and resumes watching as soon as your callback finishes. + +# What is it useful for? # + +Lots of things, here are some examples: + + * **Browser extensions.** Want to make a browser extension that creates a link to your mapping application whenever an address appears in a page? You’ll need to know when those addresses appear (and disappear). + * **Implement missing HTML capabilities.** Think building web apps is too darn hard and you know what’s missing from HTML that would make it a snap? Writing the code for the desired behavior is only half the battle--you’ll also need to know when those elements and attributes show up and what happens to them. In fact, there’s already two widely used classes of libraries which do exactly this, but don’t currently have a good way to observe changes to the DOM. + * **UI Widget** libraries, e.g. Dojo Widgets + * **Templating** and/or **Databinding** libraries, e.g. Angular or KnockoutJS + * **Text Editors.** HTML Text editors often want to observe what’s being input and “fix it up” so that they can maintain a consistent WYSWIG UI. + +# What is this _not_ useful for? # + +The intent here isn't to be all things to all use-cases. Mutation Summary is not meant to: + + * **Use the DOM as some sort of state-transition machine.** It won't report transient states that the DOM moved through. It will only tell you what the difference is between the previous state and the present one. + * **Observing complex selectors.** It offers support for a simple [subset of CSS selectors](APIReference.md#supported-selector-syntax). Want to observe all elements that match `“div[foo] span.bar > p:first-child”`? Unfortunately, efficiently computing that is much harder and currently outside the scope of this library. + +Note that both of the above use cases are possible given the data that the underlying Mutation Observers API provides -- we simply judged them to be outside the "80% use case" that we targeted with this particular library. + +# Where can Mutation Summary be used? # + +The Mutation Summary library depends on the presence of the Mutation Observer DOM API. Mutation Observers are available in + + * [Google Chrome](https://www.google.com/chrome) + * [Firefox](http://www.mozilla.org/en-US/firefox/new/) + * [Safari](http://www.apple.com/safari/) + * [Opera](http://www.opera.com/) + * [IE11](http://www.microsoft.com/ie) + +Mutation Observers is the work of the [W3C WebApps working group](http://www.w3.org/2008/webapps/). In the future it will be implemented in other browsers (we’ll keep the above list of supporting browsers as up-to-date as possible). + +# Great. I want to get started. What’s next? # + + * Check out the [tutorial](Tutorial.md) and the [API reference](APIReference.md). + +# Google groups discussion list # + + * [mutation-summary-discuss@googlegroups.com](https://groups.google.com/group/mutation-summary-discuss) diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/package.json b/files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/package.json new file mode 100644 index 0000000..3029ee6 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/package.json @@ -0,0 +1,30 @@ +{ + "name": "mutation-summary", + "version": "0.0.0", + "description": "Makes observing the DOM fast and easy", + "main": "src/mutation-summary.js", + "directories": { + "example": "examples", + "test": "tests" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://code.google.com/p/mutation-summary/" + }, + "author": "", + "license": "Apache 2.0", + "devDependencies": { + "chai": "*", + "mocha": "*" + } +} + + + + + + + diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/src/mutation-summary.js b/files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/src/mutation-summary.js new file mode 100644 index 0000000..feef885 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/src/mutation-summary.js @@ -0,0 +1,1406 @@ +// Copyright 2011 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var MutationObserverCtor; +if (typeof WebKitMutationObserver !== 'undefined') + MutationObserverCtor = WebKitMutationObserver; +else + MutationObserverCtor = MutationObserver; +if (MutationObserverCtor === undefined) { + console.error('DOM Mutation Observers are required.'); + console.error('https://developer.mozilla.org/en-US/docs/DOM/MutationObserver'); + throw Error('DOM Mutation Observers are required'); +} +var NodeMap = /** @class */ (function () { + function NodeMap() { + this.nodes = []; + this.values = []; + } + NodeMap.prototype.isIndex = function (s) { + return +s === s >>> 0; + }; + NodeMap.prototype.nodeId = function (node) { + var id = node[NodeMap.ID_PROP]; + if (!id) + id = node[NodeMap.ID_PROP] = NodeMap.nextId_++; + return id; + }; + NodeMap.prototype.set = function (node, value) { + var id = this.nodeId(node); + this.nodes[id] = node; + this.values[id] = value; + }; + NodeMap.prototype.get = function (node) { + var id = this.nodeId(node); + return this.values[id]; + }; + NodeMap.prototype.has = function (node) { + return this.nodeId(node) in this.nodes; + }; + NodeMap.prototype["delete"] = function (node) { + var id = this.nodeId(node); + delete this.nodes[id]; + this.values[id] = undefined; + }; + NodeMap.prototype.keys = function () { + var nodes = []; + for (var id in this.nodes) { + if (!this.isIndex(id)) + continue; + nodes.push(this.nodes[id]); + } + return nodes; + }; + NodeMap.ID_PROP = '__mutation_summary_node_map_id__'; + NodeMap.nextId_ = 1; + return NodeMap; +}()); +/** + * var reachableMatchableProduct = [ + * // STAYED_OUT, ENTERED, STAYED_IN, EXITED + * [ STAYED_OUT, STAYED_OUT, STAYED_OUT, STAYED_OUT ], // STAYED_OUT + * [ STAYED_OUT, ENTERED, ENTERED, STAYED_OUT ], // ENTERED + * [ STAYED_OUT, ENTERED, STAYED_IN, EXITED ], // STAYED_IN + * [ STAYED_OUT, STAYED_OUT, EXITED, EXITED ] // EXITED + * ]; + */ +var Movement; +(function (Movement) { + Movement[Movement["STAYED_OUT"] = 0] = "STAYED_OUT"; + Movement[Movement["ENTERED"] = 1] = "ENTERED"; + Movement[Movement["STAYED_IN"] = 2] = "STAYED_IN"; + Movement[Movement["REPARENTED"] = 3] = "REPARENTED"; + Movement[Movement["REORDERED"] = 4] = "REORDERED"; + Movement[Movement["EXITED"] = 5] = "EXITED"; +})(Movement || (Movement = {})); +function enteredOrExited(changeType) { + return changeType === Movement.ENTERED || changeType === Movement.EXITED; +} +var NodeChange = /** @class */ (function () { + function NodeChange(node, childList, attributes, characterData, oldParentNode, added, attributeOldValues, characterDataOldValue) { + if (childList === void 0) { childList = false; } + if (attributes === void 0) { attributes = false; } + if (characterData === void 0) { characterData = false; } + if (oldParentNode === void 0) { oldParentNode = null; } + if (added === void 0) { added = false; } + if (attributeOldValues === void 0) { attributeOldValues = null; } + if (characterDataOldValue === void 0) { characterDataOldValue = null; } + this.node = node; + this.childList = childList; + this.attributes = attributes; + this.characterData = characterData; + this.oldParentNode = oldParentNode; + this.added = added; + this.attributeOldValues = attributeOldValues; + this.characterDataOldValue = characterDataOldValue; + this.isCaseInsensitive = + this.node.nodeType === Node.ELEMENT_NODE && + this.node instanceof HTMLElement && + this.node.ownerDocument instanceof HTMLDocument; + } + NodeChange.prototype.getAttributeOldValue = function (name) { + if (!this.attributeOldValues) + return undefined; + if (this.isCaseInsensitive) + name = name.toLowerCase(); + return this.attributeOldValues[name]; + }; + NodeChange.prototype.getAttributeNamesMutated = function () { + var names = []; + if (!this.attributeOldValues) + return names; + for (var name in this.attributeOldValues) { + names.push(name); + } + return names; + }; + NodeChange.prototype.attributeMutated = function (name, oldValue) { + this.attributes = true; + this.attributeOldValues = this.attributeOldValues || {}; + if (name in this.attributeOldValues) + return; + this.attributeOldValues[name] = oldValue; + }; + NodeChange.prototype.characterDataMutated = function (oldValue) { + if (this.characterData) + return; + this.characterData = true; + this.characterDataOldValue = oldValue; + }; + // Note: is it possible to receive a removal followed by a removal. This + // can occur if the removed node is added to an non-observed node, that + // node is added to the observed area, and then the node removed from + // it. + NodeChange.prototype.removedFromParent = function (parent) { + this.childList = true; + if (this.added || this.oldParentNode) + this.added = false; + else + this.oldParentNode = parent; + }; + NodeChange.prototype.insertedIntoParent = function () { + this.childList = true; + this.added = true; + }; + // An node's oldParent is + // -its present parent, if its parentNode was not changed. + // -null if the first thing that happened to it was an add. + // -the node it was removed from if the first thing that happened to it + // was a remove. + NodeChange.prototype.getOldParent = function () { + if (this.childList) { + if (this.oldParentNode) + return this.oldParentNode; + if (this.added) + return null; + } + return this.node.parentNode; + }; + return NodeChange; +}()); +var ChildListChange = /** @class */ (function () { + function ChildListChange() { + this.added = new NodeMap(); + this.removed = new NodeMap(); + this.maybeMoved = new NodeMap(); + this.oldPrevious = new NodeMap(); + this.moved = undefined; + } + return ChildListChange; +}()); +var TreeChanges = /** @class */ (function (_super) { + __extends(TreeChanges, _super); + function TreeChanges(rootNode, mutations) { + var _this = _super.call(this) || this; + _this.rootNode = rootNode; + _this.reachableCache = undefined; + _this.wasReachableCache = undefined; + _this.anyParentsChanged = false; + _this.anyAttributesChanged = false; + _this.anyCharacterDataChanged = false; + for (var m = 0; m < mutations.length; m++) { + var mutation = mutations[m]; + switch (mutation.type) { + case 'childList': + _this.anyParentsChanged = true; + for (var i = 0; i < mutation.removedNodes.length; i++) { + var node = mutation.removedNodes[i]; + _this.getChange(node).removedFromParent(mutation.target); + } + for (var i = 0; i < mutation.addedNodes.length; i++) { + var node = mutation.addedNodes[i]; + _this.getChange(node).insertedIntoParent(); + } + break; + case 'attributes': + _this.anyAttributesChanged = true; + var change = _this.getChange(mutation.target); + change.attributeMutated(mutation.attributeName, mutation.oldValue); + break; + case 'characterData': + _this.anyCharacterDataChanged = true; + var change = _this.getChange(mutation.target); + change.characterDataMutated(mutation.oldValue); + break; + } + } + return _this; + } + TreeChanges.prototype.getChange = function (node) { + var change = this.get(node); + if (!change) { + change = new NodeChange(node); + this.set(node, change); + } + return change; + }; + TreeChanges.prototype.getOldParent = function (node) { + var change = this.get(node); + return change ? change.getOldParent() : node.parentNode; + }; + TreeChanges.prototype.getIsReachable = function (node) { + if (node === this.rootNode) + return true; + if (!node) + return false; + this.reachableCache = this.reachableCache || new NodeMap(); + var isReachable = this.reachableCache.get(node); + if (isReachable === undefined) { + isReachable = this.getIsReachable(node.parentNode); + this.reachableCache.set(node, isReachable); + } + return isReachable; + }; + // A node wasReachable if its oldParent wasReachable. + TreeChanges.prototype.getWasReachable = function (node) { + if (node === this.rootNode) + return true; + if (!node) + return false; + this.wasReachableCache = this.wasReachableCache || new NodeMap(); + var wasReachable = this.wasReachableCache.get(node); + if (wasReachable === undefined) { + wasReachable = this.getWasReachable(this.getOldParent(node)); + this.wasReachableCache.set(node, wasReachable); + } + return wasReachable; + }; + TreeChanges.prototype.reachabilityChange = function (node) { + if (this.getIsReachable(node)) { + return this.getWasReachable(node) ? + Movement.STAYED_IN : Movement.ENTERED; + } + return this.getWasReachable(node) ? + Movement.EXITED : Movement.STAYED_OUT; + }; + return TreeChanges; +}(NodeMap)); +var MutationProjection = /** @class */ (function () { + // TOOD(any) + function MutationProjection(rootNode, mutations, selectors, calcReordered, calcOldPreviousSibling) { + this.rootNode = rootNode; + this.mutations = mutations; + this.selectors = selectors; + this.calcReordered = calcReordered; + this.calcOldPreviousSibling = calcOldPreviousSibling; + this.treeChanges = new TreeChanges(rootNode, mutations); + this.entered = []; + this.exited = []; + this.stayedIn = new NodeMap(); + this.visited = new NodeMap(); + this.childListChangeMap = undefined; + this.characterDataOnly = undefined; + this.matchCache = undefined; + this.processMutations(); + } + MutationProjection.prototype.processMutations = function () { + if (!this.treeChanges.anyParentsChanged && + !this.treeChanges.anyAttributesChanged) + return; + var changedNodes = this.treeChanges.keys(); + for (var i = 0; i < changedNodes.length; i++) { + this.visitNode(changedNodes[i], undefined); + } + }; + MutationProjection.prototype.visitNode = function (node, parentReachable) { + if (this.visited.has(node)) + return; + this.visited.set(node, true); + var change = this.treeChanges.get(node); + var reachable = parentReachable; + // node inherits its parent's reachability change unless + // its parentNode was mutated. + if ((change && change.childList) || reachable == undefined) + reachable = this.treeChanges.reachabilityChange(node); + if (reachable === Movement.STAYED_OUT) + return; + // Cache match results for sub-patterns. + this.matchabilityChange(node); + if (reachable === Movement.ENTERED) { + this.entered.push(node); + } + else if (reachable === Movement.EXITED) { + this.exited.push(node); + this.ensureHasOldPreviousSiblingIfNeeded(node); + } + else if (reachable === Movement.STAYED_IN) { + var movement = Movement.STAYED_IN; + if (change && change.childList) { + if (change.oldParentNode !== node.parentNode) { + movement = Movement.REPARENTED; + this.ensureHasOldPreviousSiblingIfNeeded(node); + } + else if (this.calcReordered && this.wasReordered(node)) { + movement = Movement.REORDERED; + } + } + this.stayedIn.set(node, movement); + } + if (reachable === Movement.STAYED_IN) + return; + // reachable === ENTERED || reachable === EXITED. + for (var child = node.firstChild; child; child = child.nextSibling) { + this.visitNode(child, reachable); + } + }; + MutationProjection.prototype.ensureHasOldPreviousSiblingIfNeeded = function (node) { + if (!this.calcOldPreviousSibling) + return; + this.processChildlistChanges(); + var parentNode = node.parentNode; + var nodeChange = this.treeChanges.get(node); + if (nodeChange && nodeChange.oldParentNode) + parentNode = nodeChange.oldParentNode; + var change = this.childListChangeMap.get(parentNode); + if (!change) { + change = new ChildListChange(); + this.childListChangeMap.set(parentNode, change); + } + if (!change.oldPrevious.has(node)) { + change.oldPrevious.set(node, node.previousSibling); + } + }; + MutationProjection.prototype.getChanged = function (summary, selectors, characterDataOnly) { + this.selectors = selectors; + this.characterDataOnly = characterDataOnly; + for (var i = 0; i < this.entered.length; i++) { + var node = this.entered[i]; + var matchable = this.matchabilityChange(node); + if (matchable === Movement.ENTERED || matchable === Movement.STAYED_IN) + summary.added.push(node); + } + var stayedInNodes = this.stayedIn.keys(); + for (var i = 0; i < stayedInNodes.length; i++) { + var node = stayedInNodes[i]; + var matchable = this.matchabilityChange(node); + if (matchable === Movement.ENTERED) { + summary.added.push(node); + } + else if (matchable === Movement.EXITED) { + summary.removed.push(node); + } + else if (matchable === Movement.STAYED_IN && (summary.reparented || summary.reordered)) { + var movement = this.stayedIn.get(node); + if (summary.reparented && movement === Movement.REPARENTED) + summary.reparented.push(node); + else if (summary.reordered && movement === Movement.REORDERED) + summary.reordered.push(node); + } + } + for (var i = 0; i < this.exited.length; i++) { + var node = this.exited[i]; + var matchable = this.matchabilityChange(node); + if (matchable === Movement.EXITED || matchable === Movement.STAYED_IN) + summary.removed.push(node); + } + }; + MutationProjection.prototype.getOldParentNode = function (node) { + var change = this.treeChanges.get(node); + if (change && change.childList) + return change.oldParentNode ? change.oldParentNode : null; + var reachabilityChange = this.treeChanges.reachabilityChange(node); + if (reachabilityChange === Movement.STAYED_OUT || reachabilityChange === Movement.ENTERED) + throw Error('getOldParentNode requested on invalid node.'); + return node.parentNode; + }; + MutationProjection.prototype.getOldPreviousSibling = function (node) { + var parentNode = node.parentNode; + var nodeChange = this.treeChanges.get(node); + if (nodeChange && nodeChange.oldParentNode) + parentNode = nodeChange.oldParentNode; + var change = this.childListChangeMap.get(parentNode); + if (!change) + throw Error('getOldPreviousSibling requested on invalid node.'); + return change.oldPrevious.get(node); + }; + MutationProjection.prototype.getOldAttribute = function (element, attrName) { + var change = this.treeChanges.get(element); + if (!change || !change.attributes) + throw Error('getOldAttribute requested on invalid node.'); + var value = change.getAttributeOldValue(attrName); + if (value === undefined) + throw Error('getOldAttribute requested for unchanged attribute name.'); + return value; + }; + MutationProjection.prototype.attributeChangedNodes = function (includeAttributes) { + if (!this.treeChanges.anyAttributesChanged) + return {}; // No attributes mutations occurred. + var attributeFilter; + var caseInsensitiveFilter; + if (includeAttributes) { + attributeFilter = {}; + caseInsensitiveFilter = {}; + for (var i = 0; i < includeAttributes.length; i++) { + var attrName = includeAttributes[i]; + attributeFilter[attrName] = true; + caseInsensitiveFilter[attrName.toLowerCase()] = attrName; + } + } + var result = {}; + var nodes = this.treeChanges.keys(); + for (var i = 0; i < nodes.length; i++) { + var node = nodes[i]; + var change = this.treeChanges.get(node); + if (!change.attributes) + continue; + if (Movement.STAYED_IN !== this.treeChanges.reachabilityChange(node) || + Movement.STAYED_IN !== this.matchabilityChange(node)) { + continue; + } + var element = node; + var changedAttrNames = change.getAttributeNamesMutated(); + for (var j = 0; j < changedAttrNames.length; j++) { + var attrName = changedAttrNames[j]; + if (attributeFilter && + !attributeFilter[attrName] && + !(change.isCaseInsensitive && caseInsensitiveFilter[attrName])) { + continue; + } + var oldValue = change.getAttributeOldValue(attrName); + if (oldValue === element.getAttribute(attrName)) + continue; + if (caseInsensitiveFilter && change.isCaseInsensitive) + attrName = caseInsensitiveFilter[attrName]; + result[attrName] = result[attrName] || []; + result[attrName].push(element); + } + } + return result; + }; + MutationProjection.prototype.getOldCharacterData = function (node) { + var change = this.treeChanges.get(node); + if (!change || !change.characterData) + throw Error('getOldCharacterData requested on invalid node.'); + return change.characterDataOldValue; + }; + MutationProjection.prototype.getCharacterDataChanged = function () { + if (!this.treeChanges.anyCharacterDataChanged) + return []; // No characterData mutations occurred. + var nodes = this.treeChanges.keys(); + var result = []; + for (var i = 0; i < nodes.length; i++) { + var target = nodes[i]; + if (Movement.STAYED_IN !== this.treeChanges.reachabilityChange(target)) + continue; + var change = this.treeChanges.get(target); + if (!change.characterData || + target.textContent == change.characterDataOldValue) + continue; + result.push(target); + } + return result; + }; + MutationProjection.prototype.computeMatchabilityChange = function (selector, el) { + if (!this.matchCache) + this.matchCache = []; + if (!this.matchCache[selector.uid]) + this.matchCache[selector.uid] = new NodeMap(); + var cache = this.matchCache[selector.uid]; + var result = cache.get(el); + if (result === undefined) { + result = selector.matchabilityChange(el, this.treeChanges.get(el)); + cache.set(el, result); + } + return result; + }; + MutationProjection.prototype.matchabilityChange = function (node) { + var _this = this; + // TODO(rafaelw): Include PI, CDATA? + // Only include text nodes. + if (this.characterDataOnly) { + switch (node.nodeType) { + case Node.COMMENT_NODE: + case Node.TEXT_NODE: + return Movement.STAYED_IN; + default: + return Movement.STAYED_OUT; + } + } + // No element filter. Include all nodes. + if (!this.selectors) + return Movement.STAYED_IN; + // Element filter. Exclude non-elements. + if (node.nodeType !== Node.ELEMENT_NODE) + return Movement.STAYED_OUT; + var el = node; + var matchChanges = this.selectors.map(function (selector) { + return _this.computeMatchabilityChange(selector, el); + }); + var accum = Movement.STAYED_OUT; + var i = 0; + while (accum !== Movement.STAYED_IN && i < matchChanges.length) { + switch (matchChanges[i]) { + case Movement.STAYED_IN: + accum = Movement.STAYED_IN; + break; + case Movement.ENTERED: + if (accum === Movement.EXITED) + accum = Movement.STAYED_IN; + else + accum = Movement.ENTERED; + break; + case Movement.EXITED: + if (accum === Movement.ENTERED) + accum = Movement.STAYED_IN; + else + accum = Movement.EXITED; + break; + } + i++; + } + return accum; + }; + MutationProjection.prototype.getChildlistChange = function (el) { + var change = this.childListChangeMap.get(el); + if (!change) { + change = new ChildListChange(); + this.childListChangeMap.set(el, change); + } + return change; + }; + MutationProjection.prototype.processChildlistChanges = function () { + if (this.childListChangeMap) + return; + this.childListChangeMap = new NodeMap(); + for (var i = 0; i < this.mutations.length; i++) { + var mutation = this.mutations[i]; + if (mutation.type != 'childList') + continue; + if (this.treeChanges.reachabilityChange(mutation.target) !== Movement.STAYED_IN && + !this.calcOldPreviousSibling) + continue; + var change = this.getChildlistChange(mutation.target); + var oldPrevious = mutation.previousSibling; + function recordOldPrevious(node, previous) { + if (!node || + change.oldPrevious.has(node) || + change.added.has(node) || + change.maybeMoved.has(node)) + return; + if (previous && + (change.added.has(previous) || + change.maybeMoved.has(previous))) + return; + change.oldPrevious.set(node, previous); + } + for (var j = 0; j < mutation.removedNodes.length; j++) { + var node = mutation.removedNodes[j]; + recordOldPrevious(node, oldPrevious); + if (change.added.has(node)) { + change.added["delete"](node); + } + else { + change.removed.set(node, true); + change.maybeMoved["delete"](node); + } + oldPrevious = node; + } + recordOldPrevious(mutation.nextSibling, oldPrevious); + for (var j = 0; j < mutation.addedNodes.length; j++) { + var node = mutation.addedNodes[j]; + if (change.removed.has(node)) { + change.removed["delete"](node); + change.maybeMoved.set(node, true); + } + else { + change.added.set(node, true); + } + } + } + }; + MutationProjection.prototype.wasReordered = function (node) { + if (!this.treeChanges.anyParentsChanged) + return false; + this.processChildlistChanges(); + var parentNode = node.parentNode; + var nodeChange = this.treeChanges.get(node); + if (nodeChange && nodeChange.oldParentNode) + parentNode = nodeChange.oldParentNode; + var change = this.childListChangeMap.get(parentNode); + if (!change) + return false; + if (change.moved) + return change.moved.get(node); + change.moved = new NodeMap(); + var pendingMoveDecision = new NodeMap(); + function isMoved(node) { + if (!node) + return false; + if (!change.maybeMoved.has(node)) + return false; + var didMove = change.moved.get(node); + if (didMove !== undefined) + return didMove; + if (pendingMoveDecision.has(node)) { + didMove = true; + } + else { + pendingMoveDecision.set(node, true); + didMove = getPrevious(node) !== getOldPrevious(node); + } + if (pendingMoveDecision.has(node)) { + pendingMoveDecision["delete"](node); + change.moved.set(node, didMove); + } + else { + didMove = change.moved.get(node); + } + return didMove; + } + var oldPreviousCache = new NodeMap(); + function getOldPrevious(node) { + var oldPrevious = oldPreviousCache.get(node); + if (oldPrevious !== undefined) + return oldPrevious; + oldPrevious = change.oldPrevious.get(node); + while (oldPrevious && + (change.removed.has(oldPrevious) || isMoved(oldPrevious))) { + oldPrevious = getOldPrevious(oldPrevious); + } + if (oldPrevious === undefined) + oldPrevious = node.previousSibling; + oldPreviousCache.set(node, oldPrevious); + return oldPrevious; + } + var previousCache = new NodeMap(); + function getPrevious(node) { + if (previousCache.has(node)) + return previousCache.get(node); + var previous = node.previousSibling; + while (previous && (change.added.has(previous) || isMoved(previous))) + previous = previous.previousSibling; + previousCache.set(node, previous); + return previous; + } + change.maybeMoved.keys().forEach(isMoved); + return change.moved.get(node); + }; + return MutationProjection; +}()); +var Summary = /** @class */ (function () { + function Summary(projection, query) { + var _this = this; + this.projection = projection; + this.added = []; + this.removed = []; + this.reparented = query.all || query.element || query.characterData ? [] : undefined; + this.reordered = query.all ? [] : undefined; + projection.getChanged(this, query.elementFilter, query.characterData); + if (query.all || query.attribute || query.attributeList) { + var filter = query.attribute ? [query.attribute] : query.attributeList; + var attributeChanged = projection.attributeChangedNodes(filter); + if (query.attribute) { + this.valueChanged = attributeChanged[query.attribute] || []; + } + else { + this.attributeChanged = attributeChanged; + if (query.attributeList) { + query.attributeList.forEach(function (attrName) { + if (!_this.attributeChanged.hasOwnProperty(attrName)) + _this.attributeChanged[attrName] = []; + }); + } + } + } + if (query.all || query.characterData) { + var characterDataChanged = projection.getCharacterDataChanged(); + if (query.characterData) + this.valueChanged = characterDataChanged; + else + this.characterDataChanged = characterDataChanged; + } + if (this.reordered) + this.getOldPreviousSibling = projection.getOldPreviousSibling.bind(projection); + } + Summary.prototype.getOldParentNode = function (node) { + return this.projection.getOldParentNode(node); + }; + Summary.prototype.getOldAttribute = function (node, name) { + return this.projection.getOldAttribute(node, name); + }; + Summary.prototype.getOldCharacterData = function (node) { + return this.projection.getOldCharacterData(node); + }; + Summary.prototype.getOldPreviousSibling = function (node) { + return this.projection.getOldPreviousSibling(node); + }; + return Summary; +}()); +// TODO(rafaelw): Allow ':' and '.' as valid name characters. +var validNameInitialChar = /[a-zA-Z_]+/; +var validNameNonInitialChar = /[a-zA-Z0-9_\-]+/; +// TODO(rafaelw): Consider allowing backslash in the attrValue. +// TODO(rafaelw): There's got a to be way to represent this state machine +// more compactly??? +function escapeQuotes(value) { + return '"' + value.replace(/"/, '\\\"') + '"'; +} +var Qualifier = /** @class */ (function () { + function Qualifier() { + } + Qualifier.prototype.matches = function (oldValue) { + if (oldValue === null) + return false; + if (this.attrValue === undefined) + return true; + if (!this.contains) + return this.attrValue == oldValue; + var tokens = oldValue.split(' '); + for (var i = 0; i < tokens.length; i++) { + if (this.attrValue === tokens[i]) + return true; + } + return false; + }; + Qualifier.prototype.toString = function () { + if (this.attrName === 'class' && this.contains) + return '.' + this.attrValue; + if (this.attrName === 'id' && !this.contains) + return '#' + this.attrValue; + if (this.contains) + return '[' + this.attrName + '~=' + escapeQuotes(this.attrValue) + ']'; + if ('attrValue' in this) + return '[' + this.attrName + '=' + escapeQuotes(this.attrValue) + ']'; + return '[' + this.attrName + ']'; + }; + return Qualifier; +}()); +var Selector = /** @class */ (function () { + function Selector() { + this.uid = Selector.nextUid++; + this.qualifiers = []; + } + Object.defineProperty(Selector.prototype, "caseInsensitiveTagName", { + get: function () { + return this.tagName.toUpperCase(); + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Selector.prototype, "selectorString", { + get: function () { + return this.tagName + this.qualifiers.join(''); + }, + enumerable: false, + configurable: true + }); + Selector.prototype.isMatching = function (el) { + return el[Selector.matchesSelector](this.selectorString); + }; + Selector.prototype.wasMatching = function (el, change, isMatching) { + if (!change || !change.attributes) + return isMatching; + var tagName = change.isCaseInsensitive ? this.caseInsensitiveTagName : this.tagName; + if (tagName !== '*' && tagName !== el.tagName) + return false; + var attributeOldValues = []; + var anyChanged = false; + for (var i = 0; i < this.qualifiers.length; i++) { + var qualifier = this.qualifiers[i]; + var oldValue = change.getAttributeOldValue(qualifier.attrName); + attributeOldValues.push(oldValue); + anyChanged = anyChanged || (oldValue !== undefined); + } + if (!anyChanged) + return isMatching; + for (var i = 0; i < this.qualifiers.length; i++) { + var qualifier = this.qualifiers[i]; + var oldValue = attributeOldValues[i]; + if (oldValue === undefined) + oldValue = el.getAttribute(qualifier.attrName); + if (!qualifier.matches(oldValue)) + return false; + } + return true; + }; + Selector.prototype.matchabilityChange = function (el, change) { + var isMatching = this.isMatching(el); + if (isMatching) + return this.wasMatching(el, change, isMatching) ? Movement.STAYED_IN : Movement.ENTERED; + else + return this.wasMatching(el, change, isMatching) ? Movement.EXITED : Movement.STAYED_OUT; + }; + Selector.parseSelectors = function (input) { + var selectors = []; + var currentSelector; + var currentQualifier; + function newSelector() { + if (currentSelector) { + if (currentQualifier) { + currentSelector.qualifiers.push(currentQualifier); + currentQualifier = undefined; + } + selectors.push(currentSelector); + } + currentSelector = new Selector(); + } + function newQualifier() { + if (currentQualifier) + currentSelector.qualifiers.push(currentQualifier); + currentQualifier = new Qualifier(); + } + var WHITESPACE = /\s/; + var valueQuoteChar; + var SYNTAX_ERROR = 'Invalid or unsupported selector syntax.'; + var SELECTOR = 1; + var TAG_NAME = 2; + var QUALIFIER = 3; + var QUALIFIER_NAME_FIRST_CHAR = 4; + var QUALIFIER_NAME = 5; + var ATTR_NAME_FIRST_CHAR = 6; + var ATTR_NAME = 7; + var EQUIV_OR_ATTR_QUAL_END = 8; + var EQUAL = 9; + var ATTR_QUAL_END = 10; + var VALUE_FIRST_CHAR = 11; + var VALUE = 12; + var QUOTED_VALUE = 13; + var SELECTOR_SEPARATOR = 14; + var state = SELECTOR; + var i = 0; + while (i < input.length) { + var c = input[i++]; + switch (state) { + case SELECTOR: + if (c.match(validNameInitialChar)) { + newSelector(); + currentSelector.tagName = c; + state = TAG_NAME; + break; + } + if (c == '*') { + newSelector(); + currentSelector.tagName = '*'; + state = QUALIFIER; + break; + } + if (c == '.') { + newSelector(); + newQualifier(); + currentSelector.tagName = '*'; + currentQualifier.attrName = 'class'; + currentQualifier.contains = true; + state = QUALIFIER_NAME_FIRST_CHAR; + break; + } + if (c == '#') { + newSelector(); + newQualifier(); + currentSelector.tagName = '*'; + currentQualifier.attrName = 'id'; + state = QUALIFIER_NAME_FIRST_CHAR; + break; + } + if (c == '[') { + newSelector(); + newQualifier(); + currentSelector.tagName = '*'; + currentQualifier.attrName = ''; + state = ATTR_NAME_FIRST_CHAR; + break; + } + if (c.match(WHITESPACE)) + break; + throw Error(SYNTAX_ERROR); + case TAG_NAME: + if (c.match(validNameNonInitialChar)) { + currentSelector.tagName += c; + break; + } + if (c == '.') { + newQualifier(); + currentQualifier.attrName = 'class'; + currentQualifier.contains = true; + state = QUALIFIER_NAME_FIRST_CHAR; + break; + } + if (c == '#') { + newQualifier(); + currentQualifier.attrName = 'id'; + state = QUALIFIER_NAME_FIRST_CHAR; + break; + } + if (c == '[') { + newQualifier(); + currentQualifier.attrName = ''; + state = ATTR_NAME_FIRST_CHAR; + break; + } + if (c.match(WHITESPACE)) { + state = SELECTOR_SEPARATOR; + break; + } + if (c == ',') { + state = SELECTOR; + break; + } + throw Error(SYNTAX_ERROR); + case QUALIFIER: + if (c == '.') { + newQualifier(); + currentQualifier.attrName = 'class'; + currentQualifier.contains = true; + state = QUALIFIER_NAME_FIRST_CHAR; + break; + } + if (c == '#') { + newQualifier(); + currentQualifier.attrName = 'id'; + state = QUALIFIER_NAME_FIRST_CHAR; + break; + } + if (c == '[') { + newQualifier(); + currentQualifier.attrName = ''; + state = ATTR_NAME_FIRST_CHAR; + break; + } + if (c.match(WHITESPACE)) { + state = SELECTOR_SEPARATOR; + break; + } + if (c == ',') { + state = SELECTOR; + break; + } + throw Error(SYNTAX_ERROR); + case QUALIFIER_NAME_FIRST_CHAR: + if (c.match(validNameInitialChar)) { + currentQualifier.attrValue = c; + state = QUALIFIER_NAME; + break; + } + throw Error(SYNTAX_ERROR); + case QUALIFIER_NAME: + if (c.match(validNameNonInitialChar)) { + currentQualifier.attrValue += c; + break; + } + if (c == '.') { + newQualifier(); + currentQualifier.attrName = 'class'; + currentQualifier.contains = true; + state = QUALIFIER_NAME_FIRST_CHAR; + break; + } + if (c == '#') { + newQualifier(); + currentQualifier.attrName = 'id'; + state = QUALIFIER_NAME_FIRST_CHAR; + break; + } + if (c == '[') { + newQualifier(); + state = ATTR_NAME_FIRST_CHAR; + break; + } + if (c.match(WHITESPACE)) { + state = SELECTOR_SEPARATOR; + break; + } + if (c == ',') { + state = SELECTOR; + break; + } + throw Error(SYNTAX_ERROR); + case ATTR_NAME_FIRST_CHAR: + if (c.match(validNameInitialChar)) { + currentQualifier.attrName = c; + state = ATTR_NAME; + break; + } + if (c.match(WHITESPACE)) + break; + throw Error(SYNTAX_ERROR); + case ATTR_NAME: + if (c.match(validNameNonInitialChar)) { + currentQualifier.attrName += c; + break; + } + if (c.match(WHITESPACE)) { + state = EQUIV_OR_ATTR_QUAL_END; + break; + } + if (c == '~') { + currentQualifier.contains = true; + state = EQUAL; + break; + } + if (c == '=') { + currentQualifier.attrValue = ''; + state = VALUE_FIRST_CHAR; + break; + } + if (c == ']') { + state = QUALIFIER; + break; + } + throw Error(SYNTAX_ERROR); + case EQUIV_OR_ATTR_QUAL_END: + if (c == '~') { + currentQualifier.contains = true; + state = EQUAL; + break; + } + if (c == '=') { + currentQualifier.attrValue = ''; + state = VALUE_FIRST_CHAR; + break; + } + if (c == ']') { + state = QUALIFIER; + break; + } + if (c.match(WHITESPACE)) + break; + throw Error(SYNTAX_ERROR); + case EQUAL: + if (c == '=') { + currentQualifier.attrValue = ''; + state = VALUE_FIRST_CHAR; + break; + } + throw Error(SYNTAX_ERROR); + case ATTR_QUAL_END: + if (c == ']') { + state = QUALIFIER; + break; + } + if (c.match(WHITESPACE)) + break; + throw Error(SYNTAX_ERROR); + case VALUE_FIRST_CHAR: + if (c.match(WHITESPACE)) + break; + if (c == '"' || c == "'") { + valueQuoteChar = c; + state = QUOTED_VALUE; + break; + } + currentQualifier.attrValue += c; + state = VALUE; + break; + case VALUE: + if (c.match(WHITESPACE)) { + state = ATTR_QUAL_END; + break; + } + if (c == ']') { + state = QUALIFIER; + break; + } + if (c == "'" || c == '"') + throw Error(SYNTAX_ERROR); + currentQualifier.attrValue += c; + break; + case QUOTED_VALUE: + if (c == valueQuoteChar) { + state = ATTR_QUAL_END; + break; + } + currentQualifier.attrValue += c; + break; + case SELECTOR_SEPARATOR: + if (c.match(WHITESPACE)) + break; + if (c == ',') { + state = SELECTOR; + break; + } + throw Error(SYNTAX_ERROR); + } + } + switch (state) { + case SELECTOR: + case TAG_NAME: + case QUALIFIER: + case QUALIFIER_NAME: + case SELECTOR_SEPARATOR: + // Valid end states. + newSelector(); + break; + default: + throw Error(SYNTAX_ERROR); + } + if (!selectors.length) + throw Error(SYNTAX_ERROR); + return selectors; + }; + Selector.nextUid = 1; + Selector.matchesSelector = (function () { + var element = document.createElement('div'); + if (typeof element['webkitMatchesSelector'] === 'function') + return 'webkitMatchesSelector'; + if (typeof element['mozMatchesSelector'] === 'function') + return 'mozMatchesSelector'; + if (typeof element['msMatchesSelector'] === 'function') + return 'msMatchesSelector'; + return 'matchesSelector'; + })(); + return Selector; +}()); +var attributeFilterPattern = /^([a-zA-Z:_]+[a-zA-Z0-9_\-:\.]*)$/; +function validateAttribute(attribute) { + if (typeof attribute != 'string') + throw Error('Invalid request opion. attribute must be a non-zero length string.'); + attribute = attribute.trim(); + if (!attribute) + throw Error('Invalid request opion. attribute must be a non-zero length string.'); + if (!attribute.match(attributeFilterPattern)) + throw Error('Invalid request option. invalid attribute name: ' + attribute); + return attribute; +} +function validateElementAttributes(attribs) { + if (!attribs.trim().length) + throw Error('Invalid request option: elementAttributes must contain at least one attribute.'); + var lowerAttributes = {}; + var attributes = {}; + var tokens = attribs.split(/\s+/); + for (var i = 0; i < tokens.length; i++) { + var name = tokens[i]; + if (!name) + continue; + var name = validateAttribute(name); + var nameLower = name.toLowerCase(); + if (lowerAttributes[nameLower]) + throw Error('Invalid request option: observing multiple case variations of the same attribute is not supported.'); + attributes[name] = true; + lowerAttributes[nameLower] = true; + } + return Object.keys(attributes); +} +function elementFilterAttributes(selectors) { + var attributes = {}; + selectors.forEach(function (selector) { + selector.qualifiers.forEach(function (qualifier) { + attributes[qualifier.attrName] = true; + }); + }); + return Object.keys(attributes); +} +var MutationSummary = /** @class */ (function () { + function MutationSummary(opts) { + var _this = this; + this.connected = false; + this.options = MutationSummary.validateOptions(opts); + this.observerOptions = MutationSummary.createObserverOptions(this.options.queries); + this.root = this.options.rootNode; + this.callback = this.options.callback; + this.elementFilter = Array.prototype.concat.apply([], this.options.queries.map(function (query) { + return query.elementFilter ? query.elementFilter : []; + })); + if (!this.elementFilter.length) + this.elementFilter = undefined; + this.calcReordered = this.options.queries.some(function (query) { + return query.all; + }); + this.queryValidators = []; // TODO(rafaelw): Shouldn't always define this. + if (MutationSummary.createQueryValidator) { + this.queryValidators = this.options.queries.map(function (query) { + return MutationSummary.createQueryValidator(_this.root, query); + }); + } + this.observer = new MutationObserverCtor(function (mutations) { + _this.observerCallback(mutations); + }); + this.reconnect(); + } + MutationSummary.createObserverOptions = function (queries) { + var observerOptions = { + childList: true, + subtree: true + }; + var attributeFilter; + function observeAttributes(attributes) { + if (observerOptions.attributes && !attributeFilter) + return; // already observing all. + observerOptions.attributes = true; + observerOptions.attributeOldValue = true; + if (!attributes) { + // observe all. + attributeFilter = undefined; + return; + } + // add to observed. + attributeFilter = attributeFilter || {}; + attributes.forEach(function (attribute) { + attributeFilter[attribute] = true; + attributeFilter[attribute.toLowerCase()] = true; + }); + } + queries.forEach(function (query) { + if (query.characterData) { + observerOptions.characterData = true; + observerOptions.characterDataOldValue = true; + return; + } + if (query.all) { + observeAttributes(); + observerOptions.characterData = true; + observerOptions.characterDataOldValue = true; + return; + } + if (query.attribute) { + observeAttributes([query.attribute.trim()]); + return; + } + var attributes = elementFilterAttributes(query.elementFilter).concat(query.attributeList || []); + if (attributes.length) + observeAttributes(attributes); + }); + if (attributeFilter) + observerOptions.attributeFilter = Object.keys(attributeFilter); + return observerOptions; + }; + MutationSummary.validateOptions = function (options) { + for (var prop in options) { + if (!(prop in MutationSummary.optionKeys)) + throw Error('Invalid option: ' + prop); + } + if (typeof options.callback !== 'function') + throw Error('Invalid options: callback is required and must be a function'); + if (!options.queries || !options.queries.length) + throw Error('Invalid options: queries must contain at least one query request object.'); + var opts = { + callback: options.callback, + rootNode: options.rootNode || document, + observeOwnChanges: !!options.observeOwnChanges, + oldPreviousSibling: !!options.oldPreviousSibling, + queries: [] + }; + for (var i = 0; i < options.queries.length; i++) { + var request = options.queries[i]; + // all + if (request.all) { + if (Object.keys(request).length > 1) + throw Error('Invalid request option. all has no options.'); + opts.queries.push({ all: true }); + continue; + } + // attribute + if ('attribute' in request) { + var query = { + attribute: validateAttribute(request.attribute) + }; + query.elementFilter = Selector.parseSelectors('*[' + query.attribute + ']'); + if (Object.keys(request).length > 1) + throw Error('Invalid request option. attribute has no options.'); + opts.queries.push(query); + continue; + } + // element + if ('element' in request) { + var requestOptionCount = Object.keys(request).length; + var query = { + element: request.element, + elementFilter: Selector.parseSelectors(request.element) + }; + if (request.hasOwnProperty('elementAttributes')) { + query.attributeList = validateElementAttributes(request.elementAttributes); + requestOptionCount--; + } + if (requestOptionCount > 1) + throw Error('Invalid request option. element only allows elementAttributes option.'); + opts.queries.push(query); + continue; + } + // characterData + if (request.characterData) { + if (Object.keys(request).length > 1) + throw Error('Invalid request option. characterData has no options.'); + opts.queries.push({ characterData: true }); + continue; + } + throw Error('Invalid request option. Unknown query request.'); + } + return opts; + }; + MutationSummary.prototype.createSummaries = function (mutations) { + if (!mutations || !mutations.length) + return []; + var projection = new MutationProjection(this.root, mutations, this.elementFilter, this.calcReordered, this.options.oldPreviousSibling); + var summaries = []; + for (var i = 0; i < this.options.queries.length; i++) { + summaries.push(new Summary(projection, this.options.queries[i])); + } + return summaries; + }; + MutationSummary.prototype.checkpointQueryValidators = function () { + this.queryValidators.forEach(function (validator) { + if (validator) + validator.recordPreviousState(); + }); + }; + MutationSummary.prototype.runQueryValidators = function (summaries) { + this.queryValidators.forEach(function (validator, index) { + if (validator) + validator.validate(summaries[index]); + }); + }; + MutationSummary.prototype.changesToReport = function (summaries) { + return summaries.some(function (summary) { + var summaryProps = ['added', 'removed', 'reordered', 'reparented', + 'valueChanged', 'characterDataChanged']; + if (summaryProps.some(function (prop) { return summary[prop] && summary[prop].length; })) + return true; + if (summary.attributeChanged) { + var attrNames = Object.keys(summary.attributeChanged); + var attrsChanged = attrNames.some(function (attrName) { + return !!summary.attributeChanged[attrName].length; + }); + if (attrsChanged) + return true; + } + return false; + }); + }; + MutationSummary.prototype.observerCallback = function (mutations) { + if (!this.options.observeOwnChanges) + this.observer.disconnect(); + var summaries = this.createSummaries(mutations); + this.runQueryValidators(summaries); + if (this.options.observeOwnChanges) + this.checkpointQueryValidators(); + if (this.changesToReport(summaries)) + this.callback(summaries); + // disconnect() may have been called during the callback. + if (!this.options.observeOwnChanges && this.connected) { + this.checkpointQueryValidators(); + this.observer.observe(this.root, this.observerOptions); + } + }; + MutationSummary.prototype.reconnect = function () { + if (this.connected) + throw Error('Already connected'); + this.observer.observe(this.root, this.observerOptions); + this.connected = true; + this.checkpointQueryValidators(); + }; + MutationSummary.prototype.takeSummaries = function () { + if (!this.connected) + throw Error('Not connected'); + var summaries = this.createSummaries(this.observer.takeRecords()); + return this.changesToReport(summaries) ? summaries : undefined; + }; + MutationSummary.prototype.disconnect = function () { + var summaries = this.takeSummaries(); + this.observer.disconnect(); + this.connected = false; + return summaries; + }; + MutationSummary.NodeMap = NodeMap; // exposed for use in TreeMirror. + MutationSummary.parseElementFilter = Selector.parseSelectors; // exposed for testing. + MutationSummary.optionKeys = { + 'callback': true, + 'queries': true, + 'rootNode': true, + 'oldPreviousSibling': true, + 'observeOwnChanges': true + }; + return MutationSummary; +}()); diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/src/mutation-summary.ts b/files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/src/mutation-summary.ts new file mode 100644 index 0000000..ee3a268 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/src/mutation-summary.ts @@ -0,0 +1,1750 @@ +// Copyright 2011 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +var MutationObserverCtor; +if (typeof WebKitMutationObserver !== 'undefined') + MutationObserverCtor = WebKitMutationObserver; +else + MutationObserverCtor = MutationObserver; + +if (MutationObserverCtor === undefined) { + console.error('DOM Mutation Observers are required.'); + console.error('https://developer.mozilla.org/en-US/docs/DOM/MutationObserver'); + throw Error('DOM Mutation Observers are required'); +} + +interface StringMap { + [key: string]: T; +} + +interface NumberMap { + [key: number]: T; +} + +class NodeMap { + + private static ID_PROP = '__mutation_summary_node_map_id__'; + private static nextId_:number = 1; + + private nodes:Node[]; + private values:T[]; + + constructor() { + this.nodes = []; + this.values = []; + } + + private isIndex(s:string):boolean { + return +s === s >>> 0; + } + + private nodeId(node:Node) { + var id = node[NodeMap.ID_PROP]; + if (!id) + id = node[NodeMap.ID_PROP] = NodeMap.nextId_++; + return id; + } + + set(node:Node, value:T) { + var id = this.nodeId(node); + this.nodes[id] = node; + this.values[id] = value; + } + + get(node:Node):T { + var id = this.nodeId(node); + return this.values[id]; + } + + has(node:Node):boolean { + return this.nodeId(node) in this.nodes; + } + + delete(node:Node) { + var id = this.nodeId(node); + delete this.nodes[id]; + this.values[id] = undefined; + } + + keys():Node[] { + var nodes:Node[] = []; + for (var id in this.nodes) { + if (!this.isIndex(id)) + continue; + nodes.push(this.nodes[id]); + } + + return nodes; + } +} + +/** + * var reachableMatchableProduct = [ + * // STAYED_OUT, ENTERED, STAYED_IN, EXITED + * [ STAYED_OUT, STAYED_OUT, STAYED_OUT, STAYED_OUT ], // STAYED_OUT + * [ STAYED_OUT, ENTERED, ENTERED, STAYED_OUT ], // ENTERED + * [ STAYED_OUT, ENTERED, STAYED_IN, EXITED ], // STAYED_IN + * [ STAYED_OUT, STAYED_OUT, EXITED, EXITED ] // EXITED + * ]; + */ + +enum Movement { + STAYED_OUT, + ENTERED, + STAYED_IN, + REPARENTED, + REORDERED, + EXITED +} + +function enteredOrExited(changeType:Movement):boolean { + return changeType === Movement.ENTERED || changeType === Movement.EXITED; +} + +class NodeChange { + + public isCaseInsensitive:boolean; + + constructor(public node:Node, + public childList:boolean = false, + public attributes:boolean = false, + public characterData:boolean = false, + public oldParentNode:Node = null, + public added:boolean = false, + private attributeOldValues:StringMap = null, + public characterDataOldValue:string = null) { + this.isCaseInsensitive = + this.node.nodeType === Node.ELEMENT_NODE && + this.node instanceof HTMLElement && + this.node.ownerDocument instanceof HTMLDocument; + } + + getAttributeOldValue(name:string):string { + if (!this.attributeOldValues) + return undefined; + if (this.isCaseInsensitive) + name = name.toLowerCase(); + return this.attributeOldValues[name]; + } + + getAttributeNamesMutated():string[] { + var names:string[] = []; + if (!this.attributeOldValues) + return names; + for (var name in this.attributeOldValues) { + names.push(name); + } + return names; + } + + attributeMutated(name:string, oldValue:string) { + this.attributes = true; + this.attributeOldValues = this.attributeOldValues || {}; + + if (name in this.attributeOldValues) + return; + + this.attributeOldValues[name] = oldValue; + } + + characterDataMutated(oldValue:string) { + if (this.characterData) + return; + this.characterData = true; + this.characterDataOldValue = oldValue; + } + + // Note: is it possible to receive a removal followed by a removal. This + // can occur if the removed node is added to an non-observed node, that + // node is added to the observed area, and then the node removed from + // it. + removedFromParent(parent:Node) { + this.childList = true; + if (this.added || this.oldParentNode) + this.added = false; + else + this.oldParentNode = parent; + } + + insertedIntoParent() { + this.childList = true; + this.added = true; + } + + // An node's oldParent is + // -its present parent, if its parentNode was not changed. + // -null if the first thing that happened to it was an add. + // -the node it was removed from if the first thing that happened to it + // was a remove. + getOldParent() { + if (this.childList) { + if (this.oldParentNode) + return this.oldParentNode; + if (this.added) + return null; + } + + return this.node.parentNode; + } +} + +class ChildListChange { + + public added:NodeMap; + public removed:NodeMap; + public maybeMoved:NodeMap; + public oldPrevious:NodeMap; + public moved:NodeMap; + + constructor() { + this.added = new NodeMap(); + this.removed = new NodeMap(); + this.maybeMoved = new NodeMap(); + this.oldPrevious = new NodeMap(); + this.moved = undefined; + } +} + +class TreeChanges extends NodeMap { + + public anyParentsChanged:boolean; + public anyAttributesChanged:boolean; + public anyCharacterDataChanged:boolean; + + private reachableCache:NodeMap; + private wasReachableCache:NodeMap; + + private rootNode:Node; + + constructor(rootNode:Node, mutations:MutationRecord[]) { + super(); + + this.rootNode = rootNode; + this.reachableCache = undefined; + this.wasReachableCache = undefined; + this.anyParentsChanged = false; + this.anyAttributesChanged = false; + this.anyCharacterDataChanged = false; + + for (var m = 0; m < mutations.length; m++) { + var mutation = mutations[m]; + switch (mutation.type) { + + case 'childList': + this.anyParentsChanged = true; + for (var i = 0; i < mutation.removedNodes.length; i++) { + var node = mutation.removedNodes[i]; + this.getChange(node).removedFromParent(mutation.target); + } + for (var i = 0; i < mutation.addedNodes.length; i++) { + var node = mutation.addedNodes[i]; + this.getChange(node).insertedIntoParent(); + } + break; + + case 'attributes': + this.anyAttributesChanged = true; + var change = this.getChange(mutation.target); + change.attributeMutated(mutation.attributeName, mutation.oldValue); + break; + + case 'characterData': + this.anyCharacterDataChanged = true; + var change = this.getChange(mutation.target); + change.characterDataMutated(mutation.oldValue); + break; + } + } + } + + getChange(node:Node):NodeChange { + var change = this.get(node); + if (!change) { + change = new NodeChange(node); + this.set(node, change); + } + return change; + } + + getOldParent(node:Node):Node { + var change = this.get(node); + return change ? change.getOldParent() : node.parentNode; + } + + getIsReachable(node:Node):boolean { + if (node === this.rootNode) + return true; + if (!node) + return false; + + this.reachableCache = this.reachableCache || new NodeMap(); + var isReachable = this.reachableCache.get(node); + if (isReachable === undefined) { + isReachable = this.getIsReachable(node.parentNode); + this.reachableCache.set(node, isReachable); + } + return isReachable; + } + + // A node wasReachable if its oldParent wasReachable. + getWasReachable(node:Node):boolean { + if (node === this.rootNode) + return true; + if (!node) + return false; + + this.wasReachableCache = this.wasReachableCache || new NodeMap(); + var wasReachable:boolean = this.wasReachableCache.get(node); + if (wasReachable === undefined) { + wasReachable = this.getWasReachable(this.getOldParent(node)); + this.wasReachableCache.set(node, wasReachable); + } + return wasReachable; + } + + reachabilityChange(node:Node):Movement { + if (this.getIsReachable(node)) { + return this.getWasReachable(node) ? + Movement.STAYED_IN : Movement.ENTERED; + } + + return this.getWasReachable(node) ? + Movement.EXITED : Movement.STAYED_OUT; + } +} + +class MutationProjection { + + private treeChanges:TreeChanges; + private entered:Node[]; + private exited:Node[]; + private stayedIn:NodeMap; + private visited:NodeMap; + private childListChangeMap:NodeMap; + private characterDataOnly:boolean; + private matchCache:NumberMap>; + + // TOOD(any) + constructor(public rootNode:Node, + public mutations:MutationRecord[], + public selectors:Selector[], + public calcReordered:boolean, + public calcOldPreviousSibling:boolean) { + + this.treeChanges = new TreeChanges(rootNode, mutations); + this.entered = []; + this.exited = []; + this.stayedIn = new NodeMap(); + this.visited = new NodeMap(); + this.childListChangeMap = undefined; + this.characterDataOnly = undefined; + this.matchCache = undefined; + + this.processMutations(); + } + + processMutations() { + if (!this.treeChanges.anyParentsChanged && + !this.treeChanges.anyAttributesChanged) + return; + + var changedNodes:Node[] = this.treeChanges.keys(); + for (var i = 0; i < changedNodes.length; i++) { + this.visitNode(changedNodes[i], undefined); + } + } + + visitNode(node:Node, parentReachable:Movement) { + if (this.visited.has(node)) + return; + + this.visited.set(node, true); + + var change = this.treeChanges.get(node); + var reachable = parentReachable; + + // node inherits its parent's reachability change unless + // its parentNode was mutated. + if ((change && change.childList) || reachable == undefined) + reachable = this.treeChanges.reachabilityChange(node); + + if (reachable === Movement.STAYED_OUT) + return; + + // Cache match results for sub-patterns. + this.matchabilityChange(node); + + if (reachable === Movement.ENTERED) { + this.entered.push(node); + } else if (reachable === Movement.EXITED) { + this.exited.push(node); + this.ensureHasOldPreviousSiblingIfNeeded(node); + + } else if (reachable === Movement.STAYED_IN) { + var movement = Movement.STAYED_IN; + + if (change && change.childList) { + if (change.oldParentNode !== node.parentNode) { + movement = Movement.REPARENTED; + this.ensureHasOldPreviousSiblingIfNeeded(node); + } else if (this.calcReordered && this.wasReordered(node)) { + movement = Movement.REORDERED; + } + } + + this.stayedIn.set(node, movement); + } + + if (reachable === Movement.STAYED_IN) + return; + + // reachable === ENTERED || reachable === EXITED. + for (var child = node.firstChild; child; child = child.nextSibling) { + this.visitNode(child, reachable); + } + } + + ensureHasOldPreviousSiblingIfNeeded(node:Node) { + if (!this.calcOldPreviousSibling) + return; + + this.processChildlistChanges(); + + var parentNode = node.parentNode; + var nodeChange = this.treeChanges.get(node); + if (nodeChange && nodeChange.oldParentNode) + parentNode = nodeChange.oldParentNode; + + var change = this.childListChangeMap.get(parentNode); + if (!change) { + change = new ChildListChange(); + this.childListChangeMap.set(parentNode, change); + } + + if (!change.oldPrevious.has(node)) { + change.oldPrevious.set(node, node.previousSibling); + } + } + + getChanged(summary:Summary, selectors:Selector[], characterDataOnly:boolean) { + this.selectors = selectors; + this.characterDataOnly = characterDataOnly; + + for (var i = 0; i < this.entered.length; i++) { + var node = this.entered[i]; + var matchable = this.matchabilityChange(node); + if (matchable === Movement.ENTERED || matchable === Movement.STAYED_IN) + summary.added.push(node); + } + + var stayedInNodes = this.stayedIn.keys(); + for (var i = 0; i < stayedInNodes.length; i++) { + var node = stayedInNodes[i]; + var matchable = this.matchabilityChange(node); + + if (matchable === Movement.ENTERED) { + summary.added.push(node); + } else if (matchable === Movement.EXITED) { + summary.removed.push(node); + } else if (matchable === Movement.STAYED_IN && (summary.reparented || summary.reordered)) { + var movement:Movement = this.stayedIn.get(node); + if (summary.reparented && movement === Movement.REPARENTED) + summary.reparented.push(node); + else if (summary.reordered && movement === Movement.REORDERED) + summary.reordered.push(node); + } + } + + for (var i = 0; i < this.exited.length; i++) { + var node = this.exited[i]; + var matchable = this.matchabilityChange(node); + if (matchable === Movement.EXITED || matchable === Movement.STAYED_IN) + summary.removed.push(node); + } + } + + getOldParentNode(node:Node):Node { + var change = this.treeChanges.get(node); + if (change && change.childList) + return change.oldParentNode ? change.oldParentNode : null; + + var reachabilityChange = this.treeChanges.reachabilityChange(node); + if (reachabilityChange === Movement.STAYED_OUT || reachabilityChange === Movement.ENTERED) + throw Error('getOldParentNode requested on invalid node.'); + + return node.parentNode; + } + + getOldPreviousSibling(node:Node):Node { + var parentNode = node.parentNode; + var nodeChange = this.treeChanges.get(node); + if (nodeChange && nodeChange.oldParentNode) + parentNode = nodeChange.oldParentNode; + + var change = this.childListChangeMap.get(parentNode); + if (!change) + throw Error('getOldPreviousSibling requested on invalid node.'); + + return change.oldPrevious.get(node); + } + + getOldAttribute(element:Node, attrName:string):string { + var change = this.treeChanges.get(element); + if (!change || !change.attributes) + throw Error('getOldAttribute requested on invalid node.'); + + var value = change.getAttributeOldValue(attrName); + if (value === undefined) + throw Error('getOldAttribute requested for unchanged attribute name.'); + + return value; + } + + attributeChangedNodes(includeAttributes:string[]):StringMap { + if (!this.treeChanges.anyAttributesChanged) + return {}; // No attributes mutations occurred. + + var attributeFilter:StringMap; + var caseInsensitiveFilter:StringMap; + if (includeAttributes) { + attributeFilter = {}; + caseInsensitiveFilter = {}; + for (var i = 0; i < includeAttributes.length; i++) { + var attrName:string = includeAttributes[i]; + attributeFilter[attrName] = true; + caseInsensitiveFilter[attrName.toLowerCase()] = attrName; + } + } + + var result:StringMap = {}; + var nodes = this.treeChanges.keys(); + + for (var i = 0; i < nodes.length; i++) { + var node = nodes[i]; + + var change = this.treeChanges.get(node); + if (!change.attributes) + continue; + + if (Movement.STAYED_IN !== this.treeChanges.reachabilityChange(node) || + Movement.STAYED_IN !== this.matchabilityChange(node)) { + continue; + } + + var element = node; + var changedAttrNames = change.getAttributeNamesMutated(); + for (var j = 0; j < changedAttrNames.length; j++) { + var attrName = changedAttrNames[j]; + + if (attributeFilter && + !attributeFilter[attrName] && + !(change.isCaseInsensitive && caseInsensitiveFilter[attrName])) { + continue; + } + + var oldValue = change.getAttributeOldValue(attrName); + if (oldValue === element.getAttribute(attrName)) + continue; + + if (caseInsensitiveFilter && change.isCaseInsensitive) + attrName = caseInsensitiveFilter[attrName]; + + result[attrName] = result[attrName] || []; + result[attrName].push(element); + } + } + + return result; + } + + getOldCharacterData(node:Node):string { + var change = this.treeChanges.get(node); + if (!change || !change.characterData) + throw Error('getOldCharacterData requested on invalid node.'); + + return change.characterDataOldValue; + } + + getCharacterDataChanged():Node[] { + if (!this.treeChanges.anyCharacterDataChanged) + return []; // No characterData mutations occurred. + + var nodes = this.treeChanges.keys(); + var result:Node[] = []; + for (var i = 0; i < nodes.length; i++) { + var target = nodes[i]; + if (Movement.STAYED_IN !== this.treeChanges.reachabilityChange(target)) + continue; + + var change = this.treeChanges.get(target); + if (!change.characterData || + target.textContent == change.characterDataOldValue) + continue + + result.push(target); + } + + return result; + } + + computeMatchabilityChange(selector:Selector, el:Element):Movement { + if (!this.matchCache) + this.matchCache = []; + if (!this.matchCache[selector.uid]) + this.matchCache[selector.uid] = new NodeMap(); + + var cache = this.matchCache[selector.uid]; + var result = cache.get(el); + if (result === undefined) { + result = selector.matchabilityChange(el, this.treeChanges.get(el)); + cache.set(el, result); + } + return result; + } + + matchabilityChange(node:Node) { + // TODO(rafaelw): Include PI, CDATA? + // Only include text nodes. + if (this.characterDataOnly) { + switch (node.nodeType) { + case Node.COMMENT_NODE: + case Node.TEXT_NODE: + return Movement.STAYED_IN; + default: + return Movement.STAYED_OUT; + } + } + + // No element filter. Include all nodes. + if (!this.selectors) + return Movement.STAYED_IN; + + // Element filter. Exclude non-elements. + if (node.nodeType !== Node.ELEMENT_NODE) + return Movement.STAYED_OUT; + + var el = node; + + var matchChanges = this.selectors.map((selector:Selector) => { + return this.computeMatchabilityChange(selector, el); + }); + + var accum:Movement = Movement.STAYED_OUT; + var i = 0; + + while (accum !== Movement.STAYED_IN && i < matchChanges.length) { + switch(matchChanges[i]) { + case Movement.STAYED_IN: + accum = Movement.STAYED_IN; + break; + case Movement.ENTERED: + if (accum === Movement.EXITED) + accum = Movement.STAYED_IN; + else + accum = Movement.ENTERED; + break; + case Movement.EXITED: + if (accum === Movement.ENTERED) + accum = Movement.STAYED_IN; + else + accum = Movement.EXITED; + break; + } + + i++; + } + + return accum; + } + + getChildlistChange(el:Element):ChildListChange { + var change = this.childListChangeMap.get(el); + if (!change) { + change = new ChildListChange(); + this.childListChangeMap.set(el, change); + } + + return change; + } + + processChildlistChanges() { + if (this.childListChangeMap) + return; + + this.childListChangeMap = new NodeMap(); + + for (var i = 0; i < this.mutations.length; i++) { + var mutation = this.mutations[i]; + if (mutation.type != 'childList') + continue; + + if (this.treeChanges.reachabilityChange(mutation.target) !== Movement.STAYED_IN && + !this.calcOldPreviousSibling) + continue; + + var change = this.getChildlistChange(mutation.target); + + var oldPrevious = mutation.previousSibling; + + function recordOldPrevious(node:Node, previous:Node) { + if (!node || + change.oldPrevious.has(node) || + change.added.has(node) || + change.maybeMoved.has(node)) + return; + + if (previous && + (change.added.has(previous) || + change.maybeMoved.has(previous))) + return; + + change.oldPrevious.set(node, previous); + } + + for (var j = 0; j < mutation.removedNodes.length; j++) { + var node = mutation.removedNodes[j]; + recordOldPrevious(node, oldPrevious); + + if (change.added.has(node)) { + change.added.delete(node); + } else { + change.removed.set(node, true); + change.maybeMoved.delete(node); + } + + oldPrevious = node; + } + + recordOldPrevious(mutation.nextSibling, oldPrevious); + + for (var j = 0; j < mutation.addedNodes.length; j++) { + var node = mutation.addedNodes[j]; + if (change.removed.has(node)) { + change.removed.delete(node); + change.maybeMoved.set(node, true); + } else { + change.added.set(node, true); + } + } + } + } + + wasReordered(node:Node) { + if (!this.treeChanges.anyParentsChanged) + return false; + + this.processChildlistChanges(); + + var parentNode = node.parentNode; + var nodeChange = this.treeChanges.get(node); + if (nodeChange && nodeChange.oldParentNode) + parentNode = nodeChange.oldParentNode; + + var change = this.childListChangeMap.get(parentNode); + if (!change) + return false; + + if (change.moved) + return change.moved.get(node); + + change.moved = new NodeMap(); + var pendingMoveDecision = new NodeMap(); + + function isMoved(node:Node) { + if (!node) + return false; + if (!change.maybeMoved.has(node)) + return false; + + var didMove = change.moved.get(node); + if (didMove !== undefined) + return didMove; + + if (pendingMoveDecision.has(node)) { + didMove = true; + } else { + pendingMoveDecision.set(node, true); + didMove = getPrevious(node) !== getOldPrevious(node); + } + + if (pendingMoveDecision.has(node)) { + pendingMoveDecision.delete(node); + change.moved.set(node, didMove); + } else { + didMove = change.moved.get(node); + } + + return didMove; + } + + var oldPreviousCache = new NodeMap(); + function getOldPrevious(node:Node):Node { + var oldPrevious = oldPreviousCache.get(node); + if (oldPrevious !== undefined) + return oldPrevious; + + oldPrevious = change.oldPrevious.get(node); + while (oldPrevious && + (change.removed.has(oldPrevious) || isMoved(oldPrevious))) { + oldPrevious = getOldPrevious(oldPrevious); + } + + if (oldPrevious === undefined) + oldPrevious = node.previousSibling; + oldPreviousCache.set(node, oldPrevious); + + return oldPrevious; + } + + var previousCache = new NodeMap(); + function getPrevious(node:Node):Node { + if (previousCache.has(node)) + return previousCache.get(node); + + var previous = node.previousSibling; + while (previous && (change.added.has(previous) || isMoved(previous))) + previous = previous.previousSibling; + + previousCache.set(node, previous); + return previous; + } + + change.maybeMoved.keys().forEach(isMoved); + return change.moved.get(node); + } +} + +class Summary { + public added:Node[]; + public removed:Node[]; + public reparented:Node[]; + public reordered:Node[]; + public valueChanged:Node[]; + public attributeChanged:StringMap; + public characterDataChanged:Node[]; + + constructor(private projection:MutationProjection, query:Query) { + this.added = []; + this.removed = []; + this.reparented = query.all || query.element || query.characterData ? [] : undefined; + this.reordered = query.all ? [] : undefined; + + projection.getChanged(this, query.elementFilter, query.characterData); + + if (query.all || query.attribute || query.attributeList) { + var filter = query.attribute ? [ query.attribute ] : query.attributeList; + var attributeChanged = projection.attributeChangedNodes(filter); + + if (query.attribute) { + this.valueChanged = attributeChanged[query.attribute] || []; + } else { + this.attributeChanged = attributeChanged; + if (query.attributeList) { + query.attributeList.forEach((attrName) => { + if (!this.attributeChanged.hasOwnProperty(attrName)) + this.attributeChanged[attrName] = []; + }); + } + } + } + + if (query.all || query.characterData) { + var characterDataChanged = projection.getCharacterDataChanged() + + if (query.characterData) + this.valueChanged = characterDataChanged; + else + this.characterDataChanged = characterDataChanged; + } + + if (this.reordered) + this.getOldPreviousSibling = projection.getOldPreviousSibling.bind(projection); + } + + getOldParentNode(node:Node):Node { + return this.projection.getOldParentNode(node); + } + + getOldAttribute(node:Node, name: string):string { + return this.projection.getOldAttribute(node, name); + } + + getOldCharacterData(node:Node):string { + return this.projection.getOldCharacterData(node); + } + + getOldPreviousSibling(node:Node):Node { + return this.projection.getOldPreviousSibling(node); + } +} + +// TODO(rafaelw): Allow ':' and '.' as valid name characters. +var validNameInitialChar = /[a-zA-Z_]+/; +var validNameNonInitialChar = /[a-zA-Z0-9_\-]+/; + +// TODO(rafaelw): Consider allowing backslash in the attrValue. +// TODO(rafaelw): There's got a to be way to represent this state machine +// more compactly??? + +function escapeQuotes(value:string):string { + return '"' + value.replace(/"/, '\\\"') + '"'; +} + +class Qualifier { + public attrName:string; + public attrValue:string; + public contains:boolean; + + constructor() {} + + public matches(oldValue:string):boolean { + if (oldValue === null) + return false; + + if (this.attrValue === undefined) + return true; + + if (!this.contains) + return this.attrValue == oldValue; + + var tokens = oldValue.split(' '); + for (var i = 0; i < tokens.length; i++) { + if (this.attrValue === tokens[i]) + return true; + } + + return false; + } + + public toString():string { + if (this.attrName === 'class' && this.contains) + return '.' + this.attrValue; + + if (this.attrName === 'id' && !this.contains) + return '#' + this.attrValue; + + if (this.contains) + return '[' + this.attrName + '~=' + escapeQuotes(this.attrValue) + ']'; + + if ('attrValue' in this) + return '[' + this.attrName + '=' + escapeQuotes(this.attrValue) + ']'; + + return '[' + this.attrName + ']'; + } +} + +class Selector { + private static nextUid:number = 1; + private static matchesSelector:string = (function(){ + var element = document.createElement('div'); + if (typeof element['webkitMatchesSelector'] === 'function') + return 'webkitMatchesSelector'; + if (typeof element['mozMatchesSelector'] === 'function') + return 'mozMatchesSelector'; + if (typeof element['msMatchesSelector'] === 'function') + return 'msMatchesSelector'; + + return 'matchesSelector'; + })(); + + public tagName:string; + public qualifiers:Qualifier[]; + public uid:number; + + private get caseInsensitiveTagName():string { + return this.tagName.toUpperCase(); + } + + get selectorString() { + return this.tagName + this.qualifiers.join(''); + } + + constructor() { + this.uid = Selector.nextUid++; + this.qualifiers = []; + } + + private isMatching(el:Element):boolean { + return el[Selector.matchesSelector](this.selectorString); + } + + private wasMatching(el:Element, change:NodeChange, isMatching:boolean):boolean { + if (!change || !change.attributes) + return isMatching; + + var tagName = change.isCaseInsensitive ? this.caseInsensitiveTagName : this.tagName; + if (tagName !== '*' && tagName !== el.tagName) + return false; + + var attributeOldValues:string[] = []; + var anyChanged = false; + for (var i = 0; i < this.qualifiers.length; i++) { + var qualifier = this.qualifiers[i]; + var oldValue = change.getAttributeOldValue(qualifier.attrName); + attributeOldValues.push(oldValue); + anyChanged = anyChanged || (oldValue !== undefined); + } + + if (!anyChanged) + return isMatching; + + for (var i = 0; i < this.qualifiers.length; i++) { + var qualifier = this.qualifiers[i]; + var oldValue = attributeOldValues[i]; + if (oldValue === undefined) + oldValue = el.getAttribute(qualifier.attrName); + if (!qualifier.matches(oldValue)) + return false; + } + + return true; + } + + public matchabilityChange(el:Element, change:NodeChange):Movement { + var isMatching = this.isMatching(el); + if (isMatching) + return this.wasMatching(el, change, isMatching) ? Movement.STAYED_IN : Movement.ENTERED; + else + return this.wasMatching(el, change, isMatching) ? Movement.EXITED : Movement.STAYED_OUT; + } + + public static parseSelectors(input:string):Selector[] { + var selectors:Selector[] = []; + var currentSelector:Selector; + var currentQualifier:Qualifier; + + function newSelector() { + if (currentSelector) { + if (currentQualifier) { + currentSelector.qualifiers.push(currentQualifier); + currentQualifier = undefined; + } + + selectors.push(currentSelector); + } + currentSelector = new Selector(); + } + + function newQualifier() { + if (currentQualifier) + currentSelector.qualifiers.push(currentQualifier); + + currentQualifier = new Qualifier(); + } + + var WHITESPACE = /\s/; + var valueQuoteChar:string; + var SYNTAX_ERROR = 'Invalid or unsupported selector syntax.'; + + var SELECTOR = 1; + var TAG_NAME = 2; + var QUALIFIER = 3; + var QUALIFIER_NAME_FIRST_CHAR = 4; + var QUALIFIER_NAME = 5; + var ATTR_NAME_FIRST_CHAR = 6; + var ATTR_NAME = 7; + var EQUIV_OR_ATTR_QUAL_END = 8; + var EQUAL = 9; + var ATTR_QUAL_END = 10; + var VALUE_FIRST_CHAR = 11; + var VALUE = 12; + var QUOTED_VALUE = 13; + var SELECTOR_SEPARATOR = 14; + + var state = SELECTOR; + var i = 0; + while (i < input.length) { + var c = input[i++]; + + switch (state) { + case SELECTOR: + if (c.match(validNameInitialChar)) { + newSelector(); + currentSelector.tagName = c; + state = TAG_NAME; + break; + } + + if (c == '*') { + newSelector(); + currentSelector.tagName = '*'; + state = QUALIFIER; + break; + } + + if (c == '.') { + newSelector(); + newQualifier(); + currentSelector.tagName = '*'; + currentQualifier.attrName = 'class'; + currentQualifier.contains = true; + state = QUALIFIER_NAME_FIRST_CHAR; + break; + } + if (c == '#') { + newSelector(); + newQualifier(); + currentSelector.tagName = '*'; + currentQualifier.attrName = 'id'; + state = QUALIFIER_NAME_FIRST_CHAR; + break; + } + if (c == '[') { + newSelector(); + newQualifier(); + currentSelector.tagName = '*'; + currentQualifier.attrName = ''; + state = ATTR_NAME_FIRST_CHAR; + break; + } + + if (c.match(WHITESPACE)) + break; + + throw Error(SYNTAX_ERROR); + + case TAG_NAME: + if (c.match(validNameNonInitialChar)) { + currentSelector.tagName += c; + break; + } + + if (c == '.') { + newQualifier(); + currentQualifier.attrName = 'class'; + currentQualifier.contains = true; + state = QUALIFIER_NAME_FIRST_CHAR; + break; + } + if (c == '#') { + newQualifier(); + currentQualifier.attrName = 'id'; + state = QUALIFIER_NAME_FIRST_CHAR; + break; + } + if (c == '[') { + newQualifier(); + currentQualifier.attrName = ''; + state = ATTR_NAME_FIRST_CHAR; + break; + } + + if (c.match(WHITESPACE)) { + state = SELECTOR_SEPARATOR; + break; + } + + if (c == ',') { + state = SELECTOR; + break; + } + + throw Error(SYNTAX_ERROR); + + case QUALIFIER: + if (c == '.') { + newQualifier(); + currentQualifier.attrName = 'class'; + currentQualifier.contains = true; + state = QUALIFIER_NAME_FIRST_CHAR; + break; + } + if (c == '#') { + newQualifier(); + currentQualifier.attrName = 'id'; + state = QUALIFIER_NAME_FIRST_CHAR; + break; + } + if (c == '[') { + newQualifier(); + currentQualifier.attrName = ''; + state = ATTR_NAME_FIRST_CHAR; + break; + } + + if (c.match(WHITESPACE)) { + state = SELECTOR_SEPARATOR; + break; + } + + if (c == ',') { + state = SELECTOR; + break; + } + + throw Error(SYNTAX_ERROR); + + case QUALIFIER_NAME_FIRST_CHAR: + if (c.match(validNameInitialChar)) { + currentQualifier.attrValue = c; + state = QUALIFIER_NAME; + break; + } + + throw Error(SYNTAX_ERROR); + + case QUALIFIER_NAME: + if (c.match(validNameNonInitialChar)) { + currentQualifier.attrValue += c; + break; + } + + if (c == '.') { + newQualifier(); + currentQualifier.attrName = 'class'; + currentQualifier.contains = true; + state = QUALIFIER_NAME_FIRST_CHAR; + break; + } + if (c == '#') { + newQualifier(); + currentQualifier.attrName = 'id'; + state = QUALIFIER_NAME_FIRST_CHAR; + break; + } + if (c == '[') { + newQualifier(); + state = ATTR_NAME_FIRST_CHAR; + break; + } + + if (c.match(WHITESPACE)) { + state = SELECTOR_SEPARATOR; + break; + } + if (c == ',') { + state = SELECTOR; + break + } + + throw Error(SYNTAX_ERROR); + + case ATTR_NAME_FIRST_CHAR: + if (c.match(validNameInitialChar)) { + currentQualifier.attrName = c; + state = ATTR_NAME; + break; + } + + if (c.match(WHITESPACE)) + break; + + throw Error(SYNTAX_ERROR); + + case ATTR_NAME: + if (c.match(validNameNonInitialChar)) { + currentQualifier.attrName += c; + break; + } + + if (c.match(WHITESPACE)) { + state = EQUIV_OR_ATTR_QUAL_END; + break; + } + + if (c == '~') { + currentQualifier.contains = true; + state = EQUAL; + break; + } + + if (c == '=') { + currentQualifier.attrValue = ''; + state = VALUE_FIRST_CHAR; + break; + } + + if (c == ']') { + state = QUALIFIER; + break; + } + + throw Error(SYNTAX_ERROR); + + case EQUIV_OR_ATTR_QUAL_END: + if (c == '~') { + currentQualifier.contains = true; + state = EQUAL; + break; + } + + if (c == '=') { + currentQualifier.attrValue = ''; + state = VALUE_FIRST_CHAR; + break; + } + + if (c == ']') { + state = QUALIFIER; + break; + } + + if (c.match(WHITESPACE)) + break; + + throw Error(SYNTAX_ERROR); + + case EQUAL: + if (c == '=') { + currentQualifier.attrValue = ''; + state = VALUE_FIRST_CHAR + break; + } + + throw Error(SYNTAX_ERROR); + + case ATTR_QUAL_END: + if (c == ']') { + state = QUALIFIER; + break; + } + + if (c.match(WHITESPACE)) + break; + + throw Error(SYNTAX_ERROR); + + case VALUE_FIRST_CHAR: + if (c.match(WHITESPACE)) + break; + + if (c == '"' || c == "'") { + valueQuoteChar = c; + state = QUOTED_VALUE; + break; + } + + currentQualifier.attrValue += c; + state = VALUE; + break; + + case VALUE: + if (c.match(WHITESPACE)) { + state = ATTR_QUAL_END; + break; + } + if (c == ']') { + state = QUALIFIER; + break; + } + if (c == "'" || c == '"') + throw Error(SYNTAX_ERROR); + + currentQualifier.attrValue += c; + break; + + case QUOTED_VALUE: + if (c == valueQuoteChar) { + state = ATTR_QUAL_END; + break; + } + + currentQualifier.attrValue += c; + break; + + case SELECTOR_SEPARATOR: + if (c.match(WHITESPACE)) + break; + + if (c == ',') { + state = SELECTOR; + break + } + + throw Error(SYNTAX_ERROR); + } + } + + switch (state) { + case SELECTOR: + case TAG_NAME: + case QUALIFIER: + case QUALIFIER_NAME: + case SELECTOR_SEPARATOR: + // Valid end states. + newSelector(); + break; + default: + throw Error(SYNTAX_ERROR); + } + + if (!selectors.length) + throw Error(SYNTAX_ERROR); + + return selectors; + } +} + +var attributeFilterPattern = /^([a-zA-Z:_]+[a-zA-Z0-9_\-:\.]*)$/; + +function validateAttribute(attribute:string) { + if (typeof attribute != 'string') + throw Error('Invalid request opion. attribute must be a non-zero length string.'); + + attribute = attribute.trim(); + + if (!attribute) + throw Error('Invalid request opion. attribute must be a non-zero length string.'); + + + if (!attribute.match(attributeFilterPattern)) + throw Error('Invalid request option. invalid attribute name: ' + attribute); + + return attribute; +} + +function validateElementAttributes(attribs:string):string[] { + if (!attribs.trim().length) + throw Error('Invalid request option: elementAttributes must contain at least one attribute.'); + + var lowerAttributes = {}; + var attributes = {}; + + var tokens = attribs.split(/\s+/); + for (var i = 0; i < tokens.length; i++) { + var name = tokens[i]; + if (!name) + continue; + + var name = validateAttribute(name); + var nameLower = name.toLowerCase(); + if (lowerAttributes[nameLower]) + throw Error('Invalid request option: observing multiple case variations of the same attribute is not supported.'); + + attributes[name] = true; + lowerAttributes[nameLower] = true; + } + + return Object.keys(attributes); +} + + + +function elementFilterAttributes(selectors:Selector[]):string[] { + var attributes:StringMap = {}; + + selectors.forEach((selector) => { + selector.qualifiers.forEach((qualifier) => { + attributes[qualifier.attrName] = true; + }); + }); + + return Object.keys(attributes); +} + +interface Query { + element?:string; + attribute?:string; + all?:boolean; + characterData?:boolean; + elementAttributes?:string; + attributeList?:string[]; + elementFilter?:Selector[]; +} + +interface Options { + callback:(summaries:Summary[]) => any; + queries: Query[]; + rootNode?:Node; + oldPreviousSibling?:boolean; + observeOwnChanges?:boolean; +} + +class MutationSummary { + + public static NodeMap = NodeMap; // exposed for use in TreeMirror. + public static parseElementFilter = Selector.parseSelectors; // exposed for testing. + + public static createQueryValidator:(root:Node, query:Query)=>any; + private connected:boolean; + private options:Options; + private observer:MutationObserver; + private observerOptions:MutationObserverInit; + private root:Node; + private callback:(summaries:Summary[])=>any; + private elementFilter:Selector[]; + private calcReordered:boolean; + private queryValidators:any[]; + + private static optionKeys:StringMap = { + 'callback': true, // required + 'queries': true, // required + 'rootNode': true, + 'oldPreviousSibling': true, + 'observeOwnChanges': true + }; + + private static createObserverOptions(queries:Query[]):MutationObserverInit { + var observerOptions:MutationObserverInit = { + childList: true, + subtree: true + }; + + var attributeFilter:StringMap; + function observeAttributes(attributes?:string[]) { + if (observerOptions.attributes && !attributeFilter) + return; // already observing all. + + observerOptions.attributes = true; + observerOptions.attributeOldValue = true; + + if (!attributes) { + // observe all. + attributeFilter = undefined; + return; + } + + // add to observed. + attributeFilter = attributeFilter || {}; + attributes.forEach((attribute) => { + attributeFilter[attribute] = true; + attributeFilter[attribute.toLowerCase()] = true; + }); + } + + queries.forEach((query) => { + if (query.characterData) { + observerOptions.characterData = true; + observerOptions.characterDataOldValue = true; + return; + } + + if (query.all) { + observeAttributes(); + observerOptions.characterData = true; + observerOptions.characterDataOldValue = true; + return; + } + + if (query.attribute) { + observeAttributes([query.attribute.trim()]); + return; + } + + var attributes = elementFilterAttributes(query.elementFilter).concat(query.attributeList || []); + if (attributes.length) + observeAttributes(attributes); + }); + + if (attributeFilter) + observerOptions.attributeFilter = Object.keys(attributeFilter); + + return observerOptions; + } + + private static validateOptions(options:Options):Options { + for (var prop in options) { + if (!(prop in MutationSummary.optionKeys)) + throw Error('Invalid option: ' + prop); + } + + if (typeof options.callback !== 'function') + throw Error('Invalid options: callback is required and must be a function'); + + if (!options.queries || !options.queries.length) + throw Error('Invalid options: queries must contain at least one query request object.'); + + var opts:Options = { + callback: options.callback, + rootNode: options.rootNode || document, + observeOwnChanges: !!options.observeOwnChanges, + oldPreviousSibling: !!options.oldPreviousSibling, + queries: [] + }; + + for (var i = 0; i < options.queries.length; i++) { + var request = options.queries[i]; + + // all + if (request.all) { + if (Object.keys(request).length > 1) + throw Error('Invalid request option. all has no options.'); + + opts.queries.push({all: true}); + continue; + } + + // attribute + if ('attribute' in request) { + var query:Query = { + attribute: validateAttribute(request.attribute) + }; + + query.elementFilter = Selector.parseSelectors('*[' + query.attribute + ']'); + + if (Object.keys(request).length > 1) + throw Error('Invalid request option. attribute has no options.'); + + opts.queries.push(query); + continue; + } + + // element + if ('element' in request) { + var requestOptionCount = Object.keys(request).length; + var query:Query = { + element: request.element, + elementFilter: Selector.parseSelectors(request.element) + }; + + if (request.hasOwnProperty('elementAttributes')) { + query.attributeList = validateElementAttributes(request.elementAttributes); + requestOptionCount--; + } + + if (requestOptionCount > 1) + throw Error('Invalid request option. element only allows elementAttributes option.'); + + opts.queries.push(query); + continue; + } + + // characterData + if (request.characterData) { + if (Object.keys(request).length > 1) + throw Error('Invalid request option. characterData has no options.'); + + opts.queries.push({ characterData: true }); + continue; + } + + throw Error('Invalid request option. Unknown query request.'); + } + + return opts; + } + + private createSummaries(mutations:MutationRecord[]):Summary[] { + if (!mutations || !mutations.length) + return []; + + var projection = new MutationProjection(this.root, mutations, this.elementFilter, this.calcReordered, this.options.oldPreviousSibling); + + var summaries:Summary[] = []; + for (var i = 0; i < this.options.queries.length; i++) { + summaries.push(new Summary(projection, this.options.queries[i])); + } + + return summaries; + } + + private checkpointQueryValidators() { + this.queryValidators.forEach((validator) => { + if (validator) + validator.recordPreviousState(); + }); + } + + private runQueryValidators(summaries:Summary[]) { + this.queryValidators.forEach((validator, index) => { + if (validator) + validator.validate(summaries[index]); + }); + } + + private changesToReport(summaries:Summary[]):boolean { + return summaries.some((summary) => { + var summaryProps = ['added', 'removed', 'reordered', 'reparented', + 'valueChanged', 'characterDataChanged']; + if (summaryProps.some(function(prop) { return summary[prop] && summary[prop].length; })) + return true; + + if (summary.attributeChanged) { + var attrNames = Object.keys(summary.attributeChanged); + var attrsChanged = attrNames.some((attrName) => { + return !!summary.attributeChanged[attrName].length + }); + if (attrsChanged) + return true; + } + return false; + }); + } + + constructor(opts:Options) { + this.connected = false; + this.options = MutationSummary.validateOptions(opts); + this.observerOptions = MutationSummary.createObserverOptions(this.options.queries); + this.root = this.options.rootNode; + this.callback = this.options.callback; + + this.elementFilter = Array.prototype.concat.apply([], this.options.queries.map((query) => { + return query.elementFilter ? query.elementFilter : []; + })); + if (!this.elementFilter.length) + this.elementFilter = undefined; + + this.calcReordered = this.options.queries.some((query) => { + return query.all; + }); + + this.queryValidators = []; // TODO(rafaelw): Shouldn't always define this. + if (MutationSummary.createQueryValidator) { + this.queryValidators = this.options.queries.map((query) => { + return MutationSummary.createQueryValidator(this.root, query); + }); + } + + this.observer = new MutationObserverCtor((mutations:MutationRecord[]) => { + this.observerCallback(mutations); + }); + + this.reconnect(); + } + + private observerCallback(mutations:MutationRecord[]) { + if (!this.options.observeOwnChanges) + this.observer.disconnect(); + + var summaries = this.createSummaries(mutations); + this.runQueryValidators(summaries); + + if (this.options.observeOwnChanges) + this.checkpointQueryValidators(); + + if (this.changesToReport(summaries)) + this.callback(summaries); + + // disconnect() may have been called during the callback. + if (!this.options.observeOwnChanges && this.connected) { + this.checkpointQueryValidators(); + this.observer.observe(this.root, this.observerOptions); + } + } + + reconnect() { + if (this.connected) + throw Error('Already connected'); + + this.observer.observe(this.root, this.observerOptions); + this.connected = true; + this.checkpointQueryValidators(); + } + + takeSummaries():Summary[] { + if (!this.connected) + throw Error('Not connected'); + + var summaries = this.createSummaries(this.observer.takeRecords()); + return this.changesToReport(summaries) ? summaries : undefined; + } + + disconnect():Summary[] { + var summaries = this.takeSummaries(); + this.observer.disconnect(); + this.connected = false; + return summaries; + } +} + diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/util/tree-mirror.js b/files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/util/tree-mirror.js new file mode 100644 index 0000000..fb63e09 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/util/tree-mirror.js @@ -0,0 +1,268 @@ +/// +var TreeMirror = /** @class */ (function () { + function TreeMirror(root, delegate) { + this.root = root; + this.delegate = delegate; + this.idMap = {}; + } + TreeMirror.prototype.initialize = function (rootId, children) { + this.idMap[rootId] = this.root; + for (var i = 0; i < children.length; i++) + this.deserializeNode(children[i], this.root); + }; + TreeMirror.prototype.applyChanged = function (removed, addedOrMoved, attributes, text) { + var _this = this; + // NOTE: Applying the changes can result in an attempting to add a child + // to a parent which is presently an ancestor of the parent. This can occur + // based on random ordering of moves. The way we handle this is to first + // remove all changed nodes from their parents, then apply. + addedOrMoved.forEach(function (data) { + var node = _this.deserializeNode(data); + var parent = _this.deserializeNode(data.parentNode); + var previous = _this.deserializeNode(data.previousSibling); + if (node.parentNode) + node.parentNode.removeChild(node); + }); + removed.forEach(function (data) { + var node = _this.deserializeNode(data); + if (node.parentNode) + node.parentNode.removeChild(node); + }); + addedOrMoved.forEach(function (data) { + var node = _this.deserializeNode(data); + var parent = _this.deserializeNode(data.parentNode); + var previous = _this.deserializeNode(data.previousSibling); + parent.insertBefore(node, previous ? previous.nextSibling : parent.firstChild); + }); + attributes.forEach(function (data) { + var node = _this.deserializeNode(data); + Object.keys(data.attributes).forEach(function (attrName) { + var newVal = data.attributes[attrName]; + if (newVal === null) { + node.removeAttribute(attrName); + } + else { + if (!_this.delegate || + !_this.delegate.setAttribute || + !_this.delegate.setAttribute(node, attrName, newVal)) { + node.setAttribute(attrName, newVal); + } + } + }); + }); + text.forEach(function (data) { + var node = _this.deserializeNode(data); + node.textContent = data.textContent; + }); + removed.forEach(function (node) { + delete _this.idMap[node.id]; + }); + }; + TreeMirror.prototype.deserializeNode = function (nodeData, parent) { + var _this = this; + if (nodeData === null) + return null; + var node = this.idMap[nodeData.id]; + if (node) + return node; + var doc = this.root.ownerDocument; + if (doc === null) + doc = this.root; + switch (nodeData.nodeType) { + case Node.COMMENT_NODE: + node = doc.createComment(nodeData.textContent); + break; + case Node.TEXT_NODE: + node = doc.createTextNode(nodeData.textContent); + break; + case Node.DOCUMENT_TYPE_NODE: + node = doc.implementation.createDocumentType(nodeData.name, nodeData.publicId, nodeData.systemId); + break; + case Node.ELEMENT_NODE: + if (this.delegate && this.delegate.createElement) + node = this.delegate.createElement(nodeData.tagName); + if (!node) + try { + node = doc.createElement(nodeData.tagName); + } + catch (e) { + console.log("Removing invalid node", nodeData); + return null; + } + Object.keys(nodeData.attributes).forEach(function (name) { + if (!_this.delegate || + !_this.delegate.setAttribute || + !_this.delegate.setAttribute(node, name, nodeData.attributes[name])) { + try { + node.setAttribute(name, nodeData.attributes[name]); + } + catch (e) { + console.log("Removing node due to invalid attribute", nodeData); + return null; + } + } + }); + break; + } + if (!node) + throw "ouch"; + this.idMap[nodeData.id] = node; + if (parent) + parent.appendChild(node); + if (nodeData.childNodes) { + for (var i = 0; i < nodeData.childNodes.length; i++) + this.deserializeNode(nodeData.childNodes[i], node); + } + return node; + }; + return TreeMirror; +}()); +var TreeMirrorClient = /** @class */ (function () { + function TreeMirrorClient(target, mirror, testingQueries) { + var _this = this; + this.target = target; + this.mirror = mirror; + this.nextId = 1; + this.knownNodes = new MutationSummary.NodeMap(); + var rootId = this.serializeNode(target).id; + var children = []; + for (var child = target.firstChild; child; child = child.nextSibling) + children.push(this.serializeNode(child, true)); + this.mirror.initialize(rootId, children); + var self = this; + var queries = [{ all: true }]; + if (testingQueries) + queries = queries.concat(testingQueries); + this.mutationSummary = new MutationSummary({ + rootNode: target, + callback: function (summaries) { + _this.applyChanged(summaries); + }, + queries: queries + }); + } + TreeMirrorClient.prototype.disconnect = function () { + if (this.mutationSummary) { + this.mutationSummary.disconnect(); + this.mutationSummary = undefined; + } + }; + TreeMirrorClient.prototype.rememberNode = function (node) { + var id = this.nextId++; + this.knownNodes.set(node, id); + return id; + }; + TreeMirrorClient.prototype.forgetNode = function (node) { + this.knownNodes["delete"](node); + }; + TreeMirrorClient.prototype.serializeNode = function (node, recursive) { + if (node === null) + return null; + var id = this.knownNodes.get(node); + if (id !== undefined) { + return { id: id }; + } + var data = { + nodeType: node.nodeType, + id: this.rememberNode(node) + }; + switch (data.nodeType) { + case Node.DOCUMENT_TYPE_NODE: + var docType = node; + data.name = docType.name; + data.publicId = docType.publicId; + data.systemId = docType.systemId; + break; + case Node.COMMENT_NODE: + case Node.TEXT_NODE: + data.textContent = node.textContent; + break; + case Node.ELEMENT_NODE: + var elm = node; + data.tagName = elm.tagName; + data.attributes = {}; + for (var i = 0; i < elm.attributes.length; i++) { + var attr = elm.attributes[i]; + data.attributes[attr.name] = attr.value; + } + if (recursive && elm.childNodes.length) { + data.childNodes = []; + for (var child = elm.firstChild; child; child = child.nextSibling) + data.childNodes.push(this.serializeNode(child, true)); + } + break; + } + return data; + }; + TreeMirrorClient.prototype.serializeAddedAndMoved = function (added, reparented, reordered) { + var _this = this; + var all = added.concat(reparented).concat(reordered); + var parentMap = new MutationSummary.NodeMap(); + all.forEach(function (node) { + var parent = node.parentNode; + var children = parentMap.get(parent); + if (!children) { + children = new MutationSummary.NodeMap(); + parentMap.set(parent, children); + } + children.set(node, true); + }); + var moved = []; + parentMap.keys().forEach(function (parent) { + var children = parentMap.get(parent); + var keys = children.keys(); + while (keys.length) { + var node = keys[0]; + while (node.previousSibling && children.has(node.previousSibling)) + node = node.previousSibling; + while (node && children.has(node)) { + var data = _this.serializeNode(node); + data.previousSibling = _this.serializeNode(node.previousSibling); + data.parentNode = _this.serializeNode(node.parentNode); + moved.push(data); + children["delete"](node); + node = node.nextSibling; + } + var keys = children.keys(); + } + }); + return moved; + }; + TreeMirrorClient.prototype.serializeAttributeChanges = function (attributeChanged) { + var _this = this; + var map = new MutationSummary.NodeMap(); + Object.keys(attributeChanged).forEach(function (attrName) { + attributeChanged[attrName].forEach(function (element) { + var record = map.get(element); + if (!record) { + record = _this.serializeNode(element); + record.attributes = {}; + map.set(element, record); + } + record.attributes[attrName] = element.getAttribute(attrName); + }); + }); + return map.keys().map(function (node) { + return map.get(node); + }); + }; + TreeMirrorClient.prototype.applyChanged = function (summaries) { + var _this = this; + var summary = summaries[0]; + var removed = summary.removed.map(function (node) { + return _this.serializeNode(node); + }); + var moved = this.serializeAddedAndMoved(summary.added, summary.reparented, summary.reordered); + var attributes = this.serializeAttributeChanges(summary.attributeChanged); + var text = summary.characterDataChanged.map(function (node) { + var data = _this.serializeNode(node); + data.textContent = node.textContent; + return data; + }); + this.mirror.applyChanged(removed, moved, attributes, text); + summary.removed.forEach(function (node) { + _this.forgetNode(node); + }); + }; + return TreeMirrorClient; +}()); diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/util/tree-mirror.ts b/files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/util/tree-mirror.ts new file mode 100644 index 0000000..fd5f0d1 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/libs/mutation-summary/util/tree-mirror.ts @@ -0,0 +1,375 @@ +/// + +// Copyright 2013 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +interface NodeData { + id:number; + nodeType?:number; + name?:string; + publicId?:string; + systemId?:string; + textContent?:string; + tagName?:string; + attributes?:StringMap; + childNodes?:NodeData[]; +} + +interface PositionData extends NodeData { + previousSibling:NodeData; + parentNode:NodeData; +} + +interface AttributeData extends NodeData { + attributes:StringMap; +} + +interface TextData extends NodeData{ + textContent:string; +} + +class TreeMirror { + + private idMap:NumberMap; + + constructor(public root:Node, public delegate?:any) { + this.idMap = {}; + } + + initialize(rootId:number, children:NodeData[]) { + this.idMap[rootId] = this.root; + + for (var i = 0; i < children.length; i++) + this.deserializeNode(children[i], this.root); + } + + applyChanged(removed:NodeData[], + addedOrMoved:PositionData[], + attributes:AttributeData[], + text:TextData[]) { + + // NOTE: Applying the changes can result in an attempting to add a child + // to a parent which is presently an ancestor of the parent. This can occur + // based on random ordering of moves. The way we handle this is to first + // remove all changed nodes from their parents, then apply. + addedOrMoved.forEach((data:PositionData) => { + var node = this.deserializeNode(data); + var parent = this.deserializeNode(data.parentNode); + var previous = this.deserializeNode(data.previousSibling); + if (node.parentNode) + node.parentNode.removeChild(node); + }); + + removed.forEach((data:NodeData) => { + var node = this.deserializeNode(data); + if (node.parentNode) + node.parentNode.removeChild(node); + }); + + addedOrMoved.forEach((data:PositionData) => { + var node = this.deserializeNode(data); + var parent = this.deserializeNode(data.parentNode); + var previous = this.deserializeNode(data.previousSibling); + parent.insertBefore(node, + previous ? previous.nextSibling : parent.firstChild); + }); + + attributes.forEach((data:AttributeData) => { + var node = this.deserializeNode(data); + Object.keys(data.attributes).forEach((attrName) => { + var newVal = data.attributes[attrName]; + if (newVal === null) { + node.removeAttribute(attrName); + } else { + if (!this.delegate || + !this.delegate.setAttribute || + !this.delegate.setAttribute(node, attrName, newVal)) { + node.setAttribute(attrName, newVal); + } + } + }); + }); + + text.forEach((data:TextData) => { + var node = this.deserializeNode(data); + node.textContent = data.textContent; + }); + + removed.forEach((node:NodeData) => { + delete this.idMap[node.id]; + }); + } + + private deserializeNode(nodeData:NodeData, parent?:Element):Node { + if (nodeData === null) + return null; + + var node:Node = this.idMap[nodeData.id]; + if (node) + return node; + + var doc = this.root.ownerDocument; + if (doc === null) + doc = this.root; + + switch(nodeData.nodeType) { + case Node.COMMENT_NODE: + node = doc.createComment(nodeData.textContent); + break; + + case Node.TEXT_NODE: + node = doc.createTextNode(nodeData.textContent); + break; + + case Node.DOCUMENT_TYPE_NODE: + node = doc.implementation.createDocumentType(nodeData.name, nodeData.publicId, nodeData.systemId); + break; + + case Node.ELEMENT_NODE: + if (this.delegate && this.delegate.createElement) + node = this.delegate.createElement(nodeData.tagName); + if (!node) + try { + node = doc.createElement(nodeData.tagName); + } catch (e) { + console.log("Removing invalid node", nodeData); + return null; + } + + Object.keys(nodeData.attributes).forEach((name) => { + if (!this.delegate || + !this.delegate.setAttribute || + !this.delegate.setAttribute(node, name, nodeData.attributes[name])) { + try { + (node).setAttribute(name, nodeData.attributes[name]); + } catch (e) { + console.log("Removing node due to invalid attribute", nodeData); + return null; + } + } + }); + + break; + } + + if (!node) + throw "ouch"; + + this.idMap[nodeData.id] = node; + + if (parent) + parent.appendChild(node); + + if (nodeData.childNodes) { + for (var i = 0; i < nodeData.childNodes.length; i++) + this.deserializeNode(nodeData.childNodes[i], node); + } + + return node; + } +} + +class TreeMirrorClient { + private nextId:number; + + private mutationSummary:MutationSummary; + private knownNodes:NodeMap; + + constructor(public target:Node, public mirror:any, testingQueries:Query[]) { + this.nextId = 1; + this.knownNodes = new MutationSummary.NodeMap(); + + var rootId = this.serializeNode(target).id; + var children:NodeData[] = []; + for (var child = target.firstChild; child; child = child.nextSibling) + children.push(this.serializeNode(child, true)); + + this.mirror.initialize(rootId, children); + + var self = this; + + var queries = [{ all: true }]; + + if (testingQueries) + queries = queries.concat(testingQueries); + + this.mutationSummary = new MutationSummary({ + rootNode: target, + callback: (summaries:Summary[]) => { + this.applyChanged(summaries); + }, + queries: queries + }); + } + + + disconnect() { + if (this.mutationSummary) { + this.mutationSummary.disconnect(); + this.mutationSummary = undefined; + } + } + + private rememberNode(node:Node):number { + var id = this.nextId++; + this.knownNodes.set(node, id); + return id; + } + + private forgetNode(node:Node) { + this.knownNodes.delete(node); + } + + private serializeNode(node:Node, recursive?:boolean):NodeData { + if (node === null) + return null; + + var id = this.knownNodes.get(node); + if (id !== undefined) { + return { id: id }; + } + + var data:NodeData = { + nodeType: node.nodeType, + id: this.rememberNode(node) + }; + + switch(data.nodeType) { + case Node.DOCUMENT_TYPE_NODE: + var docType = node; + data.name = docType.name; + data.publicId = docType.publicId; + data.systemId = docType.systemId; + break; + + case Node.COMMENT_NODE: + case Node.TEXT_NODE: + data.textContent = node.textContent; + break; + + case Node.ELEMENT_NODE: + var elm = node; + data.tagName = elm.tagName; + data.attributes = {}; + for (var i = 0; i < elm.attributes.length; i++) { + var attr = elm.attributes[i]; + data.attributes[attr.name] = attr.value; + } + + if (recursive && elm.childNodes.length) { + data.childNodes = []; + + for (var child = elm.firstChild; child; child = child.nextSibling) + data.childNodes.push(this.serializeNode(child, true)); + } + break; + } + + return data; + } + + private serializeAddedAndMoved(added:Node[], + reparented:Node[], + reordered:Node[]):PositionData[] { + var all = added.concat(reparented).concat(reordered); + + var parentMap = new MutationSummary.NodeMap>(); + + all.forEach((node) => { + var parent = node.parentNode; + var children = parentMap.get(parent) + if (!children) { + children = new MutationSummary.NodeMap(); + parentMap.set(parent, children); + } + + children.set(node, true); + }); + + var moved:PositionData[] = []; + + parentMap.keys().forEach((parent) => { + var children = parentMap.get(parent); + + var keys = children.keys(); + while (keys.length) { + var node = keys[0]; + while (node.previousSibling && children.has(node.previousSibling)) + node = node.previousSibling; + + while (node && children.has(node)) { + var data = this.serializeNode(node); + data.previousSibling = this.serializeNode(node.previousSibling); + data.parentNode = this.serializeNode(node.parentNode); + moved.push(data); + children.delete(node); + node = node.nextSibling; + } + + var keys = children.keys(); + } + }); + + return moved; + } + + private serializeAttributeChanges(attributeChanged:StringMap):AttributeData[] { + var map = new MutationSummary.NodeMap(); + + Object.keys(attributeChanged).forEach((attrName) => { + attributeChanged[attrName].forEach((element) => { + var record = map.get(element); + if (!record) { + record = this.serializeNode(element); + record.attributes = {}; + map.set(element, record); + } + + record.attributes[attrName] = element.getAttribute(attrName); + }); + }); + + return map.keys().map((node:Node) => { + return map.get(node); + }); + } + + applyChanged(summaries:Summary[]) { + var summary:Summary = summaries[0] + + var removed:NodeData[] = summary.removed.map((node:Node) => { + return this.serializeNode(node); + }); + + var moved:PositionData[] = + this.serializeAddedAndMoved(summary.added, + summary.reparented, + summary.reordered); + + var attributes:AttributeData[] = + this.serializeAttributeChanges(summary.attributeChanged); + + var text:TextData[] = summary.characterDataChanged.map((node:Node) => { + var data = this.serializeNode(node); + data.textContent = node.textContent; + return data; + }); + + this.mirror.applyChanged(removed, moved, attributes, text); + + summary.removed.forEach((node:Node) => { + this.forgetNode(node); + }); + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/libs/svg.js/CHANGELOG.md b/files/plugin-HeatmapSessionRecording-5.2.4/libs/svg.js/CHANGELOG.md new file mode 100644 index 0000000..6d9eb1e --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/libs/svg.js/CHANGELOG.md @@ -0,0 +1,642 @@ +# Change Log + +All notable changes to this project will be documented in this file. + +The document follows the conventions described in [“Keep a CHANGELOG”](http://keepachangelog.com). + + +==== + + +## UNRELEASED 3.0.0 + +### Added +- added `'random'` option and `randomize()` method to `SVG.Color` -> __TODO!__ +- added `precision()` method to round numeric element attributes -> __TODO!__ +- added specs for `SVG.FX` -> __TODO!__ + +### Changed +- made transform-methods relative as default (breaking change) +- changed SVG() to use querySelector instead of getElementById (breaking change) -> __TODO!__ +- made `parents()` method on `SVG.Element` return an instance of SVG.Set (breaking change) -> __TODO!__ +- replaced static reference to `masker` in `SVG.Mask` with the `masker()` method (breaking change) -> __TODO!__ +- replaced static reference to `clipper` in `SVG.ClipPath` with the `clipper()` method (breaking change) -> __TODO!__ +- replaced static reference to `targets` in `SVG.Mask` and `SVG.ClipPath` with the `targets()` method (breaking change) -> __TODO!__ +- moved all regexes to `SVG.regex` (in color, element, pointarray, style, transform and viewbox) -> __TODO!__ + +### Fixed +- fixed a bug in clipping and masking where empty nodes persists after removal -> __TODO!__ +- fixed a bug in IE11 with `mouseenter` and `mouseleave` -> __TODO!__ + + +## [2.6.1] - 2017-04-25 + +### Fixed +- fixed a bug in path parser which made it stop parsing when hitting z command (#665) + +## [2.6.0] - 2017-04-21 + +### Added +- added `options` object to `SVG.on()` and `el.on()` (#661) + +### Changed +- back to sloppy mode because of problems with plugins (#660) + + +## [2.5.3] - 2017-04-15 + +### Added +- added gitter badge in readme + + +### Fixed +- fixed svg.js.d.ts (#644 #648) +- fixed bug in `el.flip()` which causes an error when calling flip without any argument + +### Removed +- component.json (#652) + + +## [2.5.2] - 2017-04-11 + +### Changed +- SVG.js is now running in strict mode + +### Fixed +- `clear()` does not remove the parser in svg documents anymore +- `len` not declared in FX module, making it a global variable (9737e8a) +- `bbox` not declared in SVG.Box.transform in the Box module (131df0f) +- `namespace` not declared in the Event module (e89c97e) + + +## [2.5.1] - 2017-03-27 + +### Changed +- make svgjs ready to be used on the server + +### Fixed +- fixed `SVG.PathArray.parse` that did not correctly parsed flat arrays +- prevented unnecessary parsing of point or path strings + + +## [2.5.0] - 2017-03-10 + +### Added +- added a plot and array method to `SVG.TextPath` (#582) +- added `clone()` method to `SVG.Array/PointArray/PathArray` (#590) +- added `font()` method to `SVG.Tspan` +- added `SVG.Box()` +- added `transform()` method to boxes +- added `event()` to `SVG.Element` to retrieve the event that was fired last on the element (#550) + +### Changed +- changed CHANGELOG to follow the conventions described in [“Keep a CHANGELOG”](http://keepachangelog.com) (#578) +- make the method plot a getter when no parameter is passed for `SVG.Polyline`,`SVG.Polygon`, `SVG.Line`, `SVG.Path` (related #547) +- allow `SVG.PointArray` to be passed flat array +- change the regexp `SVG.PointArray` use to parse string to allow more flexibility in the way spaces and commas can be used +- allow `plot` to be called with 4 parameters when animating an `SVG.Line` +- relative value for `SVG.Number` are now calculated in its `morph` method (related #547) +- clean up the implementation of the `initAnimation` method of the FX module (#547, #552, #584) +- deprecated `.tbox()`. `.tbox()` now map to `.rbox()`. If you are using `.tbox()`, you can substitute it with `.rbox()` (#594, #602) +- all boxes now accept 4 values or an object on creation +- `el.rbox()` now always returns the right boxes in screen coordinates and has an additional paramater to transform the box into other coordinate systems +- `font()` method can now be used like `attr()` method (#620) +- events are now cancelable by default (#550) + +### Fixed +- fixed a bug in the plain morphing part of `SVG.MorphObj` that is in the FX module +- fixed bug which produces an error when removing an event from a node which was formerly removed with a global `off()` (#518) +- fixed a bug in `size()` for poly elements when their height/width is zero (#505) +- viewbox now also accepts strings and arrays as constructor arguments +- `SVG.Array` now accepts a comma seperated string and returns array of numbers instead of strings +- `SVG.Matrix` now accepts an array as input +- `SVG.Element.matrix()` now accepts also 6 values +- `dx()/dy()` now accepts percentage values, too but only if the value on the element is already percentage +- `flip()` now flips on both axis when no parameter is passed +- fixed bug with `documentElement.contains()` in IE +- fixed offset produced by svg parser (#553) +- fixed a bug with clone which didnt copy over dom data (#621) + + +## [2.4.0] - 2017-01-14 + +### Added +- added support for basic path animations (#561) + + +## [2.3.7] - 2017-01-14 + +### Added +- added code coverage https://coveralls.io/github/svgdotjs/svg.js (3e614d4) +- added `npm run test:quick` which aim at being fast rather than correct - great for git hooks (981ce24) + +### Changed +- moved project to [svgdotjs](https://github.com/svgdotjs) +- made matrixify work with transformation chain separated by commas (#543) +- updated dev dependencies; request and gulp-chmod - `npm run build` now requires nodejs 4.x+ + +### Fixed +- fixed `SVG.Matrix.skew()` (#545) +- fixed broken animations, if using polyfills for es6/7 proposals (#504) +- fixed and improved `SVG.FX.dequeue()` (#546) +- fixed an error in `SVG.FX.step`, if custom properties is added to `Array.prototype` (#549) + + +## [2.3.6] - 2016-10-21 + +### Changed +- make SVG.FX.loop modify the last situation instead of the current one (#532) + +### Fixed +- fixed leading and trailing space in SVG.PointArray would return NaN for some points (695f26a) (#529) +- fixed test of `SVG.FX.afterAll` (#534) +- fixed `SVG.FX.speed()` (#536) + + +## [2.3.5] - 2016-10-13 + +### Added +- added automated unit tests via [Travis](https://travis-ci.org/svgdotjs/svg.js) (#527) +- added `npm run build` to build a new version of SVG.js without requiring gulp to be globally installed + +### Changed +- calling `fill()`, `stroke()` without an argument is now a nop +- Polygon now accepts comma less points to achieve parity with Adobe Illustrator (#529) +- updated dependencies + + +## [2.3.4] - 2016-08-04 + +### Changed +- reworked parent module for speed improvemenents +- reworked `filterSVGElements` utility to use a for loop instead of the native filter function + + +## [2.3.3] - 2016-08-02 + +### Added +- add error callback on image loading (#508) + +### Fixed +- fixed bug when getting bbox of text elements which are not in the dom (#514) +- fixed bug when getting bbox of element which is hidden with css (#516) + + +## [2.3.2] - 2016-06-21 + +### Added +- added specs for `SVG.ViewBox` +- added `parent` parameter for `clone()` +- added spec for mentioned issue + +### Fixed +- fixed string parsing in viewbox (#483) +- fixed bbox when element is not in the dom (#480) +- fixed line constructor which doesn't work with Array as input (#487) +- fixed problem in IE with `document.contains` (#490) related to (#480) +- fixed `undo` when undoing transformations (#494) + + +## [2.3.1] - 2016-05-05 + +### Added +- added typings for svg.js (#470) + +### Fixed +- fixed `SVG.morph()` (#473) +- fixed parser error (#471) +- fixed bug in `SVG.Color` with new fx +- fixed `radius()` for circles when animating and other related code (#477) +- fixed bug where `stop(true)` throws an error when element is not animated (#475) +- fixed bug in `add()` when altering svgs with whitespaces +- fixed bug in `SVG.Doc().create` where size was set to 100% even if size was already specified +- fixed bug in `parse()` from `SVG.PathArray` which does not correctly handled `S` and `T` (#485) + + +## [2.3.0] - 2016-03-30 + +### Added +- added `SVG.Point` which serves as Wrapper to the native `SVGPoint` (#437) +- added `element.point(x,y)` which transforms a point from screen coordinates to the elements space (#403) +- added `element.is()` which helps to check for the object instance faster (instanceof check) +- added more fx specs + +### Changed +- textpath now is a parent element, the lines method of text will return the tspans inside the textpath (#450) +- fx module rewritten to support animation chaining and several other stuff (see docs) + +### Fixed +- fixed `svgjs:data` attribute which was not set properly in all browsers (#428) +- fixed `isNumber` and `numberAndUnit` regex (#405) +- fixed error where a parent node is not found when loading an image but the canvas was cleared (#447) +- fixed absolute transformation animations (not perfect but better) +- fixed event listeners which didnt work correctly when identic funtions used + + +## [2.2.5] - 2015-12-29 + +### Added +- added check for existence of node (#431) + +### Changed +- `group.move()` now allows string numbers as input (#433) +- `matrixify()` will not apply the calculated matrix to the node anymore + + +## [2.2.4] - 2015-12-12 + +### Fixed +- fixed `transform()` which returns the matrix values (a-f) now, too (#423) +- double newlines (\n\n) are correctly handled as blank line from `text()` +- fixed use of scrollX vs pageXOffset in `rbox()` (#425) +- fixed target array in mask and clip which was removed instead of reinitialized (#429) + + +## [2.2.3] - 2015-11-30 + +### Fixed +- fixed null check in image (see 2.2.2) +- fixed bug related to the new path parser (see 2.2.2) +- fixed amd loader (#412) + + +## [2.2.2] - 2015-11-28 + +### Added +- added null check in image onload callback (#415) + +### Changed +- documentation rework (#407) [thanks @snowyplover] + +### Fixed +- fixed leading point bug in path parsing (#416) + + +## [2.2.1] - 2015-11-18 + +### Added +- added workaround for `SvgPathSeg` which is removed in Chrome 48 (#409) +- added `gbox()` to group to get bbox with translation included (#405) + +### Fixed +- fixed dom data which was not cleaned up properly (#398) + + +## [2.2.0] - 2015-11-06 + +### Added +- added `ungroup()/flatten()` (#238), `toParent()` and `toDoc()` +- added UMD-Wrapper with possibility to pass custom window object (#352) +- added `morph()` method for paths via plugin [svg.pathmorphing.js](https://github.com/Fuzzyma/svg.pathmorphing.js) +- added support for css selectors within the `parent()` method +- added `parents()` method to get an array of all parenting elements + +### Changed +- svgjs now saves crucial data in the dom before export and restores them when element is adopted + +### Fixed +- fixed pattern and gradient animation (#385) +- fixed mask animation in Firefox (#287) +- fixed return value of `text()` after import/clone (#393) + + +## [2.1.1] - 2015-10-03 + +### Added +- added custom context binding to event callback (default is the element the event is bound to) + + +## [2.1.0] - 2015-09-20 + +### Added +- added transform to pattern and gradients (#383) + +### Fixed +- fixed clone of textnodes (#369) +- fixed transformlists in IE (#372) +- fixed typo that leads to broken gradients (#370) +- fixed animate radius for circles (#367) + + +## [2.0.2] - 2015-06-22 + +### Fixed +- Fixed zoom consideration in circle and ellipse + + +## [2.0.1] - 2015-06-21 + +### Added +- added possibility to remove all events from a certain namespace + +### Fixed +- fixed bug with `doc()` which always should return root svg +- fixed bug in `SVG.FX` when animating with `plot()` + +### Removed +- removed target reference from use which caused bugs in `dmove()` and `use()` with external file +- removed scale consideration in `move()` duo to incompatibilities with other move-functions e.g. in `SVG.PointArray` + + +## [2.0.0] - 2015-06-11 + +### Added +- implemented an SVG adoption system to be able to manipulate existing SVG's not created with svg.js +- added polyfill for IE9 and IE10 custom events [thanks @Fuzzyma] +- added DOM query selector with the `select()` method globally or on parent elements +- added the intentionally neglected `SVG.Circle` element +- added `rx()` and `ry()` to `SVG.Rect`, `SVG.Circle`, `SVG.Ellispe` and `SVG.FX` +- added support to clone manually built text elements +- added `svg.wiml.js` plugin to plugins list +- added `ctm()` method to for matrix-centric transformations +- added `morph()` method to `SVG.Matrix` +- added support for new matrix system to `SVG.FX` +- added `native()` method to elements and matrix to get to the native api +- added `untransform()` method to remove all transformations +- added raw svg import functionality with the `svg()` method +- added coding style description to README +- added reverse functionality for animations +- documented the `situation` object in `SVG.FX` +- added distinction between relative and absolute matrix transformations +- implemented the `element()` method using the `SVG.Bare` class to create elements that are not described by SVG.js +- added `w` and `h` properties as shorthand for `width` and `height` to `SVG.BBox` +- added `SVG.TBox` to get a bounding box that is affected by transformation values +- added event-based or complete detaching of event listeners in `off()` method + +### Changed +- changed `parent` reference on elements to `parent()` method +- using `CustomEvent` instead of `Event` to be able to fire events with a `detail` object [thanks @Fuzzyma] +- renamed `SVG.TSpan` class to `SVG.Tspan` to play nice with the adoption system +- completely reworked `clone()` method to use the adoption system +- completely reworked transformations to be chainable and more true to their nature +- changed `lines` reference to `lines()` on `SVG.Text` +- changed `track` reference to `track()` on `SVG.Text` +- changed `textPath` reference to `textPath()` on `SVG.Text` +- changed `array` reference to `array()` method on `SVG.Polyline`, `SVG.Polygon` and `SVG.Path` +- reworked sup-pixel offset implementation to be more compact +- switched from Ruby's `rake` to Node's `gulp` for building [thanks to Alex Ewerlöf] +- changed `to()` method to `at()` method in `SVG.FX` +- renamed `SVG.SetFX` to `SVG.FX.Set` +- reworked `SVG.Number` to return new instances with calculations rather than itself +- reworked animatable matrix rotations +- removed `SVG.Symbol` but kept the `symbol()` method using the new `element()` method + +### Fixed +- fixed bug in `radius()` method when `y` value equals `0` +- fixed a bug where events are not detached properly + + +## [1.0.0-rc.9] - 2014-06-17 + +### Added +- added `SVG.Marker` +- added `SVG.Symbol` +- added `first()` and `last()` methods to `SVG.Set` +- added `length()` method to `SVG.Text` and `SVG.TSpan` to calculate total text length +- added `reference()` method to get referenced elements from a given attribute value + +### Changed +- `SVG.get()` will now also fetch elements with a `xlink:href="#elementId"` or `url(#elementId)` value given + +### Fixed +- fixed infinite loop in viewbox when element has a percentage width / height [thanks @shabegger] + + +## [1.0.0-rc.8] - 2014-06-12 + +### Fixed +- fixed bug in `SVG.off` +- fixed offset by window scroll position in `rbox()` [thanks @bryhoyt] + + +## [1.0.0-rc.7] - 2014-06-11 + +### Added +- added `classes()`, `hasClass()`, `addClass()`, `removeClass()` and `toggleClass()` [thanks @pklingem] + +### Changed +- binding events listeners to svg.js instance +- calling `after()` when calling `stop(true)` (fulfill flag) [thanks @vird] +- text element fires `rebuild` event whenever the `rebuild()` method is called + +### Fixed +- fixed a bug where `Element#style()` would not save empty values in IE11 [thanks @Shtong] +- fixed `SVG is not defined error` [thanks @anvaka] +- fixed a bug in `move()`on text elements with a string based value +- fix for `text()` method on text element when acting as getter [thanks @Lochemage] +- fix in `style()` method with a css string [thanks @TobiasHeckel] + + +## [1.0.0-rc.6] - 2014-03-03 + +### Added +- added `leading()` method to `SVG.FX` +- added `reverse()` method to `SVG.Array` (and thereby also to `SVG.PointArray` and `SVG.PathArray`) +- added `fulfill` option to `stop()` method in `SVG.FX` to finalise animations +- added more output values to `bbox()` and `rbox()` methods + +### Changed +- fine-tuned text element positioning +- calling `at()` method directly on morphable svg.js instances in `SVG.FX` module +- moved most `_private` methods to local named functions +- moved helpers to a separate file + +### Fixed +- fixed a bug in text `dy()` method + +### Removed +- removed internal representation for `style` + + +## [1.0.0-rc.5] - 2014-02-14 + +### Added +- added `plain()` method to `SVG.Text` element to add plain text content, without tspans +- added `plain()` method to parent elements to create a text element without tspans +- added `build()` to enable/disable build mode + +### Changed +- updated `SVG.TSpan` to accept nested tspan elements, not unlike the `text()` method in `SVG.Text` +- removed the `relative()` method in favour of `dx()`, `dy()` and `dmove()` +- switched form objects to arrays in `SVG.PathArray` for compatibility with other libraries and better performance on parsing and rendering (up-to 48% faster than 1.0.0-rc.4) +- refined docs on element-specific methods and `SVG.PathArray` structure +- reworked `leading()` implementation to be more font-size "aware" +- refactored the `attr` method on `SVG.Element` +- applied Helvetica as default font +- building `SVG.FX` class with `SVG.invent()` function + +### Removed +- removed verbose style application to tspans + + +## [1.0.0-rc.4] - 2014-02-04 + +### Added +- automatic pattern creation by passing an image url or instance as `fill` attribute on elements +- added `loaded()` method to image tag +- added `pointAt()` method to `SVG.Path`, wrapping the native `getPointAtLength()` + +### Changed +- switched to `MAJOR`.`MINOR`.`PATCH` versioning format to play nice with package managers +- made svg.pattern.js part of the core library +- moved `length()` method to sugar module + +### Fixed +- fix in `animate('=').to()` +- fix for arcs in patharray `toString()` method [thanks @dotnetCarpenter] + + +## [v1.0rc3] - 2014-02-03 + +### Added +- added the `SVG.invent` function to ease invention of new elements +- added second values for `animate('2s')` +- added `length()` mehtod to path, wrapping the native `getTotalLength()` + +### Changed +- using `SVG.invent` to generate core shapes as well for leaner code + +### Fixed +- fix for html-less documents +- fix for arcs in patharray `toString()` method + + +## [v1.0rc2] - 2014-02-01 + +### Added +- added `index()` method to `SVG.Parent` and `SVG.Set` +- added `morph()` and `at()` methods to `SVG.Number` for unit morphing + +### Changed +- modified `cx()` and `cy()` methods on elements with native `x`, `y`, `width` and `height` attributes for better performance + + +## [v1.0rc1] - 2014-01-31 + +### Added +- added `SVG.PathArray` for real path transformations +- added `bbox()` method to `SVG.Set` +- added `relative()` method for moves relative to the current position +- added `morph()` and `at()` methods to `SVG.Color` for color morphing + +### Changed +- enabled proportional resizing on `size()` method with `null` for either `width` or `height` values +- moved data module to separate file +- `data()` method now accepts object for for multiple key / value assignments + +### Removed +- removed `unbiased` system for paths + + +## [v0.38] - 2014-01-28 + +### Added +- added `loop()` method to `SVG.FX` + +### Changed +- switched from `setInterval` to `requestAnimFrame` for animations + + +## [v0.37] - 2014-01-26 + +### Added +- added `get()` to `SVG.Set` + +### Changed +- moved `SVG.PointArray` to a separate file + + +## [v0.36] - 2014-01-25 + +### Added +- added `linkTo()`, `addTo()` and `putIn()` methods on `SVG.Element` + +### Changed +- provided more detailed documentation on parent elements + +### Fixed + + +## [v0.35] - 2014-01-23 + +### Added +- added `SVG.A` element with the `link()` + + +## [v0.34] - 2014-01-23 + +### Added +- added `pause()` and `play()` to `SVG.FX` + +### Changed +- storing animation values in `situation` object + + +## [v0.33] - 2014-01-22 + +### Added +- added `has()` method to `SVG.Set` +- added `width()` and `height()` as setter and getter methods on all shapes +- added `replace()` method to elements +- added `radius()` method to `SVG.Rect` and `SVG.Ellipse` +- added reference to parent node in defs + +### Changed +- moved sub-pixel offset fix to be an optional method (e.g. `SVG('drawing').fixSubPixelOffset()`) +- merged plotable.js and path.js + + +## [v0.32] + +### Added +- added library to [cdnjs](http://cdnjs.com) + + + +[2.6.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.6.0 +[2.5.3]: https://github.com/svgdotjs/svg.js/releases/tag/2.5.3 +[2.5.2]: https://github.com/svgdotjs/svg.js/releases/tag/2.5.2 +[2.5.1]: https://github.com/svgdotjs/svg.js/releases/tag/2.5.1 +[2.5.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.5.0 +[2.4.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.4.0 + +[2.3.7]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.7 +[2.3.6]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.6 +[2.3.5]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.5 +[2.3.4]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.4 +[2.3.3]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.3 +[2.3.2]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.2 +[2.3.1]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.1 +[2.3.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.0 + +[2.2.5]: https://github.com/svgdotjs/svg.js/releases/tag/2.2.5 +[2.2.4]: https://github.com/svgdotjs/svg.js/releases/tag/2.2.4 +[2.2.3]: https://github.com/svgdotjs/svg.js/releases/tag/2.2.3 +[2.2.2]: https://github.com/svgdotjs/svg.js/releases/tag/2.2.2 +[2.2.1]: https://github.com/svgdotjs/svg.js/releases/tag/2.2.1 +[2.2.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.2.0 + +[2.1.1]: https://github.com/svgdotjs/svg.js/releases/tag/2.1.1 +[2.1.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.1.0 + +[2.0.2]: https://github.com/svgdotjs/svg.js/releases/tag/2.0.2 +[2.0.1]: https://github.com/svgdotjs/svg.js/releases/tag/2.0.1 +[2.0.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.0.0 + +[1.0.0-rc.9]: https://github.com/svgdotjs/svg.js/releases/tag/1.0.0-rc.9 +[1.0.0-rc.8]: https://github.com/svgdotjs/svg.js/releases/tag/1.0.0-rc.8 +[1.0.0-rc.7]: https://github.com/svgdotjs/svg.js/releases/tag/1.0.0-rc.7 +[1.0.0-rc.6]: https://github.com/svgdotjs/svg.js/releases/tag/1.0.0-rc.6 +[1.0.0-rc.5]: https://github.com/svgdotjs/svg.js/releases/tag/1.0.0-rc.5 +[1.0.0-rc.4]: https://github.com/svgdotjs/svg.js/releases/tag/1.0.0-rc.4 +[v1.0rc3]: https://github.com/svgdotjs/svg.js/releases/tag/v1.0rc3 +[v1.0rc2]: https://github.com/svgdotjs/svg.js/releases/tag/v1.0rc2 +[v1.0rc1]: https://github.com/svgdotjs/svg.js/releases/tag/v1.0rc1 + +[v0.38]: https://github.com/svgdotjs/svg.js/releases/tag/v0.38 +[v0.37]: https://github.com/svgdotjs/svg.js/releases/tag/v0.37 +[v0.36]: https://github.com/svgdotjs/svg.js/releases/tag/v0.36 +[v0.35]: https://github.com/svgdotjs/svg.js/releases/tag/v0.35 +[v0.34]: https://github.com/svgdotjs/svg.js/releases/tag/v0.34 +[v0.33]: https://github.com/svgdotjs/svg.js/releases/tag/v0.33 +[v0.32]: https://github.com/svgdotjs/svg.js/releases/tag/v0.32 diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/libs/svg.js/LICENSE.txt b/files/plugin-HeatmapSessionRecording-5.2.4/libs/svg.js/LICENSE.txt new file mode 100644 index 0000000..148b70a --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/libs/svg.js/LICENSE.txt @@ -0,0 +1,21 @@ +Copyright (c) 2012-2017 Wout Fierens +https://svgdotjs.github.io/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/libs/svg.js/README.md b/files/plugin-HeatmapSessionRecording-5.2.4/libs/svg.js/README.md new file mode 100644 index 0000000..b88c5a5 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/libs/svg.js/README.md @@ -0,0 +1,29 @@ +# SVG.js + +[![Build Status](https://travis-ci.org/svgdotjs/svg.js.svg?branch=master)](https://travis-ci.org/svgdotjs/svg.js) +[![Coverage Status](https://coveralls.io/repos/github/svgdotjs/svg.js/badge.svg?branch=master)](https://coveralls.io/github/svgdotjs/svg.js?branch=master) +[![CDNJS](https://img.shields.io/cdnjs/v/svg.js.svg)](https://cdnjs.com/libraries/svg.js) +[![Join the chat at https://gitter.im/svgdotjs/svg.js](https://badges.gitter.im/svgdotjs/svg.js.svg)](https://gitter.im/svgdotjs/svg.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +__A lightweight library for manipulating and animating SVG, without any dependencies.__ + +SVG.js is licensed under the terms of the MIT License. + +## Installation + +#### Bower: + +`bower install svg.js` + +#### Node: + +`npm install svg.js` + +#### Cdnjs: + +[https://cdnjs.com/libraries/svg.js](https://cdnjs.com/libraries/svg.js) + +## Documentation +Check [https://svgdotjs.github.io](https://svgdotjs.github.io/) to learn more. + +[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=pay%40woutfierens.com&lc=US&item_name=SVG.JS¤cy_code=EUR&bn=PP-DonationsBF%3Abtn_donate_74x21.png%3ANonHostedGuest) diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/libs/svg.js/dist/svg.js b/files/plugin-HeatmapSessionRecording-5.2.4/libs/svg.js/dist/svg.js new file mode 100644 index 0000000..d2fd5d3 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/libs/svg.js/dist/svg.js @@ -0,0 +1,5518 @@ +/*! +* svg.js - A lightweight library for manipulating and animating SVG. +* @version 2.6.1 +* https://svgdotjs.github.io/ +* +* @copyright Wout Fierens +* @license MIT +* +* BUILT: Tue Apr 25 2017 11:58:09 GMT+0200 (Mitteleuropäische Sommerzeit) +*/; +(function(root, factory) { + /* istanbul ignore next */ + if (typeof define === 'function' && define.amd) { + define(function(){ + return factory(root, root.document) + }) + } else if (typeof exports === 'object') { + module.exports = root.document ? factory(root, root.document) : function(w){ return factory(w, w.document) } + } else { + root.SVG = factory(root, root.document) + } +}(typeof window !== "undefined" ? window : this, function(window, document) { + +// The main wrapping element +var SVG = this.SVG = function(element) { + if (SVG.supported) { + element = new SVG.Doc(element) + + if(!SVG.parser.draw) + SVG.prepare() + + return element + } +} + +// Default namespaces +SVG.ns = 'http://www.w3.org/2000/svg' +SVG.xmlns = 'http://www.w3.org/2000/xmlns/' +SVG.xlink = 'http://www.w3.org/1999/xlink' +SVG.svgjs = 'http://svgjs.com/svgjs' + +// Svg support test +SVG.supported = (function() { + return !! document.createElementNS && + !! document.createElementNS(SVG.ns,'svg').createSVGRect +})() + +// Don't bother to continue if SVG is not supported +if (!SVG.supported) return false + +// Element id sequence +SVG.did = 1000 + +// Get next named element id +SVG.eid = function(name) { + return 'Svgjs' + capitalize(name) + (SVG.did++) +} + +// Method for element creation +SVG.create = function(name) { + // create element + var element = document.createElementNS(this.ns, name) + + // apply unique id + element.setAttribute('id', this.eid(name)) + + return element +} + +// Method for extending objects +SVG.extend = function() { + var modules, methods, key, i + + // Get list of modules + modules = [].slice.call(arguments) + + // Get object with extensions + methods = modules.pop() + + for (i = modules.length - 1; i >= 0; i--) + if (modules[i]) + for (key in methods) + modules[i].prototype[key] = methods[key] + + // Make sure SVG.Set inherits any newly added methods + if (SVG.Set && SVG.Set.inherit) + SVG.Set.inherit() +} + +// Invent new element +SVG.invent = function(config) { + // Create element initializer + var initializer = typeof config.create == 'function' ? + config.create : + function() { + this.constructor.call(this, SVG.create(config.create)) + } + + // Inherit prototype + if (config.inherit) + initializer.prototype = new config.inherit + + // Extend with methods + if (config.extend) + SVG.extend(initializer, config.extend) + + // Attach construct method to parent + if (config.construct) + SVG.extend(config.parent || SVG.Container, config.construct) + + return initializer +} + +// Adopt existing svg elements +SVG.adopt = function(node) { + // check for presence of node + if (!node) return null + + // make sure a node isn't already adopted + if (node.instance) return node.instance + + // initialize variables + var element + + // adopt with element-specific settings + if (node.nodeName == 'svg') + element = node.parentNode instanceof window.SVGElement ? new SVG.Nested : new SVG.Doc + else if (node.nodeName == 'linearGradient') + element = new SVG.Gradient('linear') + else if (node.nodeName == 'radialGradient') + element = new SVG.Gradient('radial') + else if (SVG[capitalize(node.nodeName)]) + element = new SVG[capitalize(node.nodeName)] + else + element = new SVG.Element(node) + + // ensure references + element.type = node.nodeName + element.node = node + node.instance = element + + // SVG.Class specific preparations + if (element instanceof SVG.Doc) + element.namespace().defs() + + // pull svgjs data from the dom (getAttributeNS doesn't work in html5) + element.setData(JSON.parse(node.getAttribute('svgjs:data')) || {}) + + return element +} + +// Initialize parsing element +SVG.prepare = function() { + // Select document body and create invisible svg element + var body = document.getElementsByTagName('body')[0] + , draw = (body ? new SVG.Doc(body) : SVG.adopt(document.documentElement).nested()).size(2, 0) + + // Create parser object + SVG.parser = { + body: body || document.documentElement + , draw: draw.style('opacity:0;position:absolute;left:-100%;top:-100%;overflow:hidden').node + , poly: draw.polyline().node + , path: draw.path().node + , native: SVG.create('svg') + } +} + +SVG.parser = { + native: SVG.create('svg') +} + +document.addEventListener('DOMContentLoaded', function() { + if(!SVG.parser.draw) + SVG.prepare() +}, false) + +// Storage for regular expressions +SVG.regex = { + // Parse unit value + numberAndUnit: /^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i + + // Parse hex value +, hex: /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i + + // Parse rgb value +, rgb: /rgb\((\d+),(\d+),(\d+)\)/ + + // Parse reference id +, reference: /#([a-z0-9\-_]+)/i + + // splits a transformation chain +, transforms: /\)\s*,?\s*/ + + // Whitespace +, whitespace: /\s/g + + // Test hex value +, isHex: /^#[a-f0-9]{3,6}$/i + + // Test rgb value +, isRgb: /^rgb\(/ + + // Test css declaration +, isCss: /[^:]+:[^;]+;?/ + + // Test for blank string +, isBlank: /^(\s+)?$/ + + // Test for numeric string +, isNumber: /^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i + + // Test for percent value +, isPercent: /^-?[\d\.]+%$/ + + // Test for image url +, isImage: /\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i + + // split at whitespace and comma +, delimiter: /[\s,]+/ + + // The following regex are used to parse the d attribute of a path + + // Matches all hyphens which are not after an exponent +, hyphen: /([^e])\-/gi + + // Replaces and tests for all path letters +, pathLetters: /[MLHVCSQTAZ]/gi + + // yes we need this one, too +, isPathLetter: /[MLHVCSQTAZ]/i + + // matches 0.154.23.45 +, numbersWithDots: /((\d?\.\d+(?:e[+-]?\d+)?)((?:\.\d+(?:e[+-]?\d+)?)+))+/gi + + // matches . +, dots: /\./g +} + +SVG.utils = { + // Map function + map: function(array, block) { + var i + , il = array.length + , result = [] + + for (i = 0; i < il; i++) + result.push(block(array[i])) + + return result + } + + // Filter function +, filter: function(array, block) { + var i + , il = array.length + , result = [] + + for (i = 0; i < il; i++) + if (block(array[i])) + result.push(array[i]) + + return result + } + + // Degrees to radians +, radians: function(d) { + return d % 360 * Math.PI / 180 + } + + // Radians to degrees +, degrees: function(r) { + return r * 180 / Math.PI % 360 + } + +, filterSVGElements: function(nodes) { + return this.filter( nodes, function(el) { return el instanceof window.SVGElement }) + } + +} + +SVG.defaults = { + // Default attribute values + attrs: { + // fill and stroke + 'fill-opacity': 1 + , 'stroke-opacity': 1 + , 'stroke-width': 0 + , 'stroke-linejoin': 'miter' + , 'stroke-linecap': 'butt' + , fill: '#000000' + , stroke: '#000000' + , opacity: 1 + // position + , x: 0 + , y: 0 + , cx: 0 + , cy: 0 + // size + , width: 0 + , height: 0 + // radius + , r: 0 + , rx: 0 + , ry: 0 + // gradient + , offset: 0 + , 'stop-opacity': 1 + , 'stop-color': '#000000' + // text + , 'font-size': 16 + , 'font-family': 'Helvetica, Arial, sans-serif' + , 'text-anchor': 'start' + } + +} +// Module for color convertions +SVG.Color = function(color) { + var match + + // initialize defaults + this.r = 0 + this.g = 0 + this.b = 0 + + if(!color) return + + // parse color + if (typeof color === 'string') { + if (SVG.regex.isRgb.test(color)) { + // get rgb values + match = SVG.regex.rgb.exec(color.replace(SVG.regex.whitespace,'')) + + // parse numeric values + this.r = parseInt(match[1]) + this.g = parseInt(match[2]) + this.b = parseInt(match[3]) + + } else if (SVG.regex.isHex.test(color)) { + // get hex values + match = SVG.regex.hex.exec(fullHex(color)) + + // parse numeric values + this.r = parseInt(match[1], 16) + this.g = parseInt(match[2], 16) + this.b = parseInt(match[3], 16) + + } + + } else if (typeof color === 'object') { + this.r = color.r + this.g = color.g + this.b = color.b + + } + +} + +SVG.extend(SVG.Color, { + // Default to hex conversion + toString: function() { + return this.toHex() + } + // Build hex value +, toHex: function() { + return '#' + + compToHex(this.r) + + compToHex(this.g) + + compToHex(this.b) + } + // Build rgb value +, toRgb: function() { + return 'rgb(' + [this.r, this.g, this.b].join() + ')' + } + // Calculate true brightness +, brightness: function() { + return (this.r / 255 * 0.30) + + (this.g / 255 * 0.59) + + (this.b / 255 * 0.11) + } + // Make color morphable +, morph: function(color) { + this.destination = new SVG.Color(color) + + return this + } + // Get morphed color at given position +, at: function(pos) { + // make sure a destination is defined + if (!this.destination) return this + + // normalise pos + pos = pos < 0 ? 0 : pos > 1 ? 1 : pos + + // generate morphed color + return new SVG.Color({ + r: ~~(this.r + (this.destination.r - this.r) * pos) + , g: ~~(this.g + (this.destination.g - this.g) * pos) + , b: ~~(this.b + (this.destination.b - this.b) * pos) + }) + } + +}) + +// Testers + +// Test if given value is a color string +SVG.Color.test = function(color) { + color += '' + return SVG.regex.isHex.test(color) + || SVG.regex.isRgb.test(color) +} + +// Test if given value is a rgb object +SVG.Color.isRgb = function(color) { + return color && typeof color.r == 'number' + && typeof color.g == 'number' + && typeof color.b == 'number' +} + +// Test if given value is a color +SVG.Color.isColor = function(color) { + return SVG.Color.isRgb(color) || SVG.Color.test(color) +} +// Module for array conversion +SVG.Array = function(array, fallback) { + array = (array || []).valueOf() + + // if array is empty and fallback is provided, use fallback + if (array.length == 0 && fallback) + array = fallback.valueOf() + + // parse array + this.value = this.parse(array) +} + +SVG.extend(SVG.Array, { + // Make array morphable + morph: function(array) { + this.destination = this.parse(array) + + // normalize length of arrays + if (this.value.length != this.destination.length) { + var lastValue = this.value[this.value.length - 1] + , lastDestination = this.destination[this.destination.length - 1] + + while(this.value.length > this.destination.length) + this.destination.push(lastDestination) + while(this.value.length < this.destination.length) + this.value.push(lastValue) + } + + return this + } + // Clean up any duplicate points +, settle: function() { + // find all unique values + for (var i = 0, il = this.value.length, seen = []; i < il; i++) + if (seen.indexOf(this.value[i]) == -1) + seen.push(this.value[i]) + + // set new value + return this.value = seen + } + // Get morphed array at given position +, at: function(pos) { + // make sure a destination is defined + if (!this.destination) return this + + // generate morphed array + for (var i = 0, il = this.value.length, array = []; i < il; i++) + array.push(this.value[i] + (this.destination[i] - this.value[i]) * pos) + + return new SVG.Array(array) + } + // Convert array to string +, toString: function() { + return this.value.join(' ') + } + // Real value +, valueOf: function() { + return this.value + } + // Parse whitespace separated string +, parse: function(array) { + array = array.valueOf() + + // if already is an array, no need to parse it + if (Array.isArray(array)) return array + + return this.split(array) + } + // Strip unnecessary whitespace +, split: function(string) { + return string.trim().split(SVG.regex.delimiter).map(parseFloat) + } + // Reverse array +, reverse: function() { + this.value.reverse() + + return this + } +, clone: function() { + var clone = new this.constructor() + clone.value = array_clone(this.value) + return clone + } +}) +// Poly points array +SVG.PointArray = function(array, fallback) { + SVG.Array.call(this, array, fallback || [[0,0]]) +} + +// Inherit from SVG.Array +SVG.PointArray.prototype = new SVG.Array +SVG.PointArray.prototype.constructor = SVG.PointArray + +SVG.extend(SVG.PointArray, { + // Convert array to string + toString: function() { + // convert to a poly point string + for (var i = 0, il = this.value.length, array = []; i < il; i++) + array.push(this.value[i].join(',')) + + return array.join(' ') + } + // Convert array to line object +, toLine: function() { + return { + x1: this.value[0][0] + , y1: this.value[0][1] + , x2: this.value[1][0] + , y2: this.value[1][1] + } + } + // Get morphed array at given position +, at: function(pos) { + // make sure a destination is defined + if (!this.destination) return this + + // generate morphed point string + for (var i = 0, il = this.value.length, array = []; i < il; i++) + array.push([ + this.value[i][0] + (this.destination[i][0] - this.value[i][0]) * pos + , this.value[i][1] + (this.destination[i][1] - this.value[i][1]) * pos + ]) + + return new SVG.PointArray(array) + } + // Parse point string and flat array +, parse: function(array) { + var points = [] + + array = array.valueOf() + + // if it is an array + if (Array.isArray(array)) { + // and it is not flat, there is no need to parse it + if(Array.isArray(array[0])) { + return array + } + } else { // Else, it is considered as a string + // parse points + array = array.trim().split(SVG.regex.delimiter).map(parseFloat) + } + + // validate points - https://svgwg.org/svg2-draft/shapes.html#DataTypePoints + // Odd number of coordinates is an error. In such cases, drop the last odd coordinate. + if (array.length % 2 !== 0) array.pop() + + // wrap points in two-tuples and parse points as floats + for(var i = 0, len = array.length; i < len; i = i + 2) + points.push([ array[i], array[i+1] ]) + + return points + } + // Move point string +, move: function(x, y) { + var box = this.bbox() + + // get relative offset + x -= box.x + y -= box.y + + // move every point + if (!isNaN(x) && !isNaN(y)) + for (var i = this.value.length - 1; i >= 0; i--) + this.value[i] = [this.value[i][0] + x, this.value[i][1] + y] + + return this + } + // Resize poly string +, size: function(width, height) { + var i, box = this.bbox() + + // recalculate position of all points according to new size + for (i = this.value.length - 1; i >= 0; i--) { + if(box.width) this.value[i][0] = ((this.value[i][0] - box.x) * width) / box.width + box.x + if(box.height) this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y + } + + return this + } + // Get bounding box of points +, bbox: function() { + SVG.parser.poly.setAttribute('points', this.toString()) + + return SVG.parser.poly.getBBox() + } +}) + +var pathHandlers = { + M: function(c, p, p0) { + p.x = p0.x = c[0] + p.y = p0.y = c[1] + + return ['M', p.x, p.y] + }, + L: function(c, p) { + p.x = c[0] + p.y = c[1] + return ['L', c[0], c[1]] + }, + H: function(c, p) { + p.x = c[0] + return ['H', c[0]] + }, + V: function(c, p) { + p.y = c[0] + return ['V', c[0]] + }, + C: function(c, p) { + p.x = c[4] + p.y = c[5] + return ['C', c[0], c[1], c[2], c[3], c[4], c[5]] + }, + S: function(c, p) { + p.x = c[2] + p.y = c[3] + return ['S', c[0], c[1], c[2], c[3]] + }, + Q: function(c, p) { + p.x = c[2] + p.y = c[3] + return ['Q', c[0], c[1], c[2], c[3]] + }, + T: function(c, p) { + p.x = c[0] + p.y = c[1] + return ['T', c[0], c[1]] + }, + Z: function(c, p, p0) { + p.x = p0.x + p.y = p0.y + return ['Z'] + }, + A: function(c, p) { + p.x = c[5] + p.y = c[6] + return ['A', c[0], c[1], c[2], c[3], c[4], c[5], c[6]] + } +} + +var mlhvqtcsa = 'mlhvqtcsaz'.split('') + +for(var i = 0, il = mlhvqtcsa.length; i < il; ++i){ + pathHandlers[mlhvqtcsa[i]] = (function(i){ + return function(c, p, p0) { + if(i == 'H') c[0] = c[0] + p.x + else if(i == 'V') c[0] = c[0] + p.y + else if(i == 'A'){ + c[5] = c[5] + p.x, + c[6] = c[6] + p.y + } + else + for(var j = 0, jl = c.length; j < jl; ++j) { + c[j] = c[j] + (j%2 ? p.y : p.x) + } + + return pathHandlers[i](c, p, p0) + } + })(mlhvqtcsa[i].toUpperCase()) +} + +// Path points array +SVG.PathArray = function(array, fallback) { + SVG.Array.call(this, array, fallback || [['M', 0, 0]]) +} + +// Inherit from SVG.Array +SVG.PathArray.prototype = new SVG.Array +SVG.PathArray.prototype.constructor = SVG.PathArray + +SVG.extend(SVG.PathArray, { + // Convert array to string + toString: function() { + return arrayToString(this.value) + } + // Move path string +, move: function(x, y) { + // get bounding box of current situation + var box = this.bbox() + + // get relative offset + x -= box.x + y -= box.y + + if (!isNaN(x) && !isNaN(y)) { + // move every point + for (var l, i = this.value.length - 1; i >= 0; i--) { + l = this.value[i][0] + + if (l == 'M' || l == 'L' || l == 'T') { + this.value[i][1] += x + this.value[i][2] += y + + } else if (l == 'H') { + this.value[i][1] += x + + } else if (l == 'V') { + this.value[i][1] += y + + } else if (l == 'C' || l == 'S' || l == 'Q') { + this.value[i][1] += x + this.value[i][2] += y + this.value[i][3] += x + this.value[i][4] += y + + if (l == 'C') { + this.value[i][5] += x + this.value[i][6] += y + } + + } else if (l == 'A') { + this.value[i][6] += x + this.value[i][7] += y + } + + } + } + + return this + } + // Resize path string +, size: function(width, height) { + // get bounding box of current situation + var i, l, box = this.bbox() + + // recalculate position of all points according to new size + for (i = this.value.length - 1; i >= 0; i--) { + l = this.value[i][0] + + if (l == 'M' || l == 'L' || l == 'T') { + this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x + this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y + + } else if (l == 'H') { + this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x + + } else if (l == 'V') { + this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y + + } else if (l == 'C' || l == 'S' || l == 'Q') { + this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x + this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y + this.value[i][3] = ((this.value[i][3] - box.x) * width) / box.width + box.x + this.value[i][4] = ((this.value[i][4] - box.y) * height) / box.height + box.y + + if (l == 'C') { + this.value[i][5] = ((this.value[i][5] - box.x) * width) / box.width + box.x + this.value[i][6] = ((this.value[i][6] - box.y) * height) / box.height + box.y + } + + } else if (l == 'A') { + // resize radii + this.value[i][1] = (this.value[i][1] * width) / box.width + this.value[i][2] = (this.value[i][2] * height) / box.height + + // move position values + this.value[i][6] = ((this.value[i][6] - box.x) * width) / box.width + box.x + this.value[i][7] = ((this.value[i][7] - box.y) * height) / box.height + box.y + } + + } + + return this + } + // Test if the passed path array use the same path data commands as this path array +, equalCommands: function(pathArray) { + var i, il, equalCommands + + pathArray = new SVG.PathArray(pathArray) + + equalCommands = this.value.length === pathArray.value.length + for(i = 0, il = this.value.length; equalCommands && i < il; i++) { + equalCommands = this.value[i][0] === pathArray.value[i][0] + } + + return equalCommands + } + // Make path array morphable +, morph: function(pathArray) { + pathArray = new SVG.PathArray(pathArray) + + if(this.equalCommands(pathArray)) { + this.destination = pathArray + } else { + this.destination = null + } + + return this + } + // Get morphed path array at given position +, at: function(pos) { + // make sure a destination is defined + if (!this.destination) return this + + var sourceArray = this.value + , destinationArray = this.destination.value + , array = [], pathArray = new SVG.PathArray() + , i, il, j, jl + + // Animate has specified in the SVG spec + // See: https://www.w3.org/TR/SVG11/paths.html#PathElement + for (i = 0, il = sourceArray.length; i < il; i++) { + array[i] = [sourceArray[i][0]] + for(j = 1, jl = sourceArray[i].length; j < jl; j++) { + array[i][j] = sourceArray[i][j] + (destinationArray[i][j] - sourceArray[i][j]) * pos + } + // For the two flags of the elliptical arc command, the SVG spec say: + // Flags and booleans are interpolated as fractions between zero and one, with any non-zero value considered to be a value of one/true + // Elliptical arc command as an array followed by corresponding indexes: + // ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y] + // 0 1 2 3 4 5 6 7 + if(array[i][0] === 'A') { + array[i][4] = +(array[i][4] != 0) + array[i][5] = +(array[i][5] != 0) + } + } + + // Directly modify the value of a path array, this is done this way for performance + pathArray.value = array + return pathArray + } + // Absolutize and parse path to array +, parse: function(array) { + // if it's already a patharray, no need to parse it + if (array instanceof SVG.PathArray) return array.valueOf() + + // prepare for parsing + var i, x0, y0, s, seg, arr + , x = 0 + , y = 0 + , paramCnt = { 'M':2, 'L':2, 'H':1, 'V':1, 'C':6, 'S':4, 'Q':4, 'T':2, 'A':7, 'Z':0 } + + if(typeof array == 'string'){ + + array = array + .replace(SVG.regex.numbersWithDots, pathRegReplace) // convert 45.123.123 to 45.123 .123 + .replace(SVG.regex.pathLetters, ' $& ') // put some room between letters and numbers + .replace(SVG.regex.hyphen, '$1 -') // add space before hyphen + .trim() // trim + .split(SVG.regex.delimiter) // split into array + + }else{ + array = array.reduce(function(prev, curr){ + return [].concat.call(prev, curr) + }, []) + } + + // array now is an array containing all parts of a path e.g. ['M', '0', '0', 'L', '30', '30' ...] + var arr = [] + , p = new SVG.Point() + , p0 = new SVG.Point() + , index = 0 + , len = array.length + + do{ + // Test if we have a path letter + if(SVG.regex.isPathLetter.test(array[index])){ + s = array[index] + ++index + // If last letter was a move command and we got no new, it defaults to [L]ine + }else if(s == 'M'){ + s = 'L' + }else if(s == 'm'){ + s = 'l' + } + + arr.push(pathHandlers[s].call(null, + array.slice(index, (index = index + paramCnt[s.toUpperCase()])).map(parseFloat), + p, p0 + ) + ) + + }while(len > index) + + return arr + + } + // Get bounding box of path +, bbox: function() { + SVG.parser.path.setAttribute('d', this.toString()) + + return SVG.parser.path.getBBox() + } + +}) + +// Module for unit convertions +SVG.Number = SVG.invent({ + // Initialize + create: function(value, unit) { + // initialize defaults + this.value = 0 + this.unit = unit || '' + + // parse value + if (typeof value === 'number') { + // ensure a valid numeric value + this.value = isNaN(value) ? 0 : !isFinite(value) ? (value < 0 ? -3.4e+38 : +3.4e+38) : value + + } else if (typeof value === 'string') { + unit = value.match(SVG.regex.numberAndUnit) + + if (unit) { + // make value numeric + this.value = parseFloat(unit[1]) + + // normalize + if (unit[5] == '%') + this.value /= 100 + else if (unit[5] == 's') + this.value *= 1000 + + // store unit + this.unit = unit[5] + } + + } else { + if (value instanceof SVG.Number) { + this.value = value.valueOf() + this.unit = value.unit + } + } + + } + // Add methods +, extend: { + // Stringalize + toString: function() { + return ( + this.unit == '%' ? + ~~(this.value * 1e8) / 1e6: + this.unit == 's' ? + this.value / 1e3 : + this.value + ) + this.unit + } + , toJSON: function() { + return this.toString() + } + , // Convert to primitive + valueOf: function() { + return this.value + } + // Add number + , plus: function(number) { + number = new SVG.Number(number) + return new SVG.Number(this + number, this.unit || number.unit) + } + // Subtract number + , minus: function(number) { + number = new SVG.Number(number) + return new SVG.Number(this - number, this.unit || number.unit) + } + // Multiply number + , times: function(number) { + number = new SVG.Number(number) + return new SVG.Number(this * number, this.unit || number.unit) + } + // Divide number + , divide: function(number) { + number = new SVG.Number(number) + return new SVG.Number(this / number, this.unit || number.unit) + } + // Convert to different unit + , to: function(unit) { + var number = new SVG.Number(this) + + if (typeof unit === 'string') + number.unit = unit + + return number + } + // Make number morphable + , morph: function(number) { + this.destination = new SVG.Number(number) + + if(number.relative) { + this.destination.value += this.value + } + + return this + } + // Get morphed number at given position + , at: function(pos) { + // Make sure a destination is defined + if (!this.destination) return this + + // Generate new morphed number + return new SVG.Number(this.destination) + .minus(this) + .times(pos) + .plus(this) + } + + } +}) + + +SVG.Element = SVG.invent({ + // Initialize node + create: function(node) { + // make stroke value accessible dynamically + this._stroke = SVG.defaults.attrs.stroke + this._event = null + + // initialize data object + this.dom = {} + + // create circular reference + if (this.node = node) { + this.type = node.nodeName + this.node.instance = this + + // store current attribute value + this._stroke = node.getAttribute('stroke') || this._stroke + } + } + + // Add class methods +, extend: { + // Move over x-axis + x: function(x) { + return this.attr('x', x) + } + // Move over y-axis + , y: function(y) { + return this.attr('y', y) + } + // Move by center over x-axis + , cx: function(x) { + return x == null ? this.x() + this.width() / 2 : this.x(x - this.width() / 2) + } + // Move by center over y-axis + , cy: function(y) { + return y == null ? this.y() + this.height() / 2 : this.y(y - this.height() / 2) + } + // Move element to given x and y values + , move: function(x, y) { + return this.x(x).y(y) + } + // Move element by its center + , center: function(x, y) { + return this.cx(x).cy(y) + } + // Set width of element + , width: function(width) { + return this.attr('width', width) + } + // Set height of element + , height: function(height) { + return this.attr('height', height) + } + // Set element size to given width and height + , size: function(width, height) { + var p = proportionalSize(this, width, height) + + return this + .width(new SVG.Number(p.width)) + .height(new SVG.Number(p.height)) + } + // Clone element + , clone: function(parent, withData) { + // write dom data to the dom so the clone can pickup the data + this.writeDataToDom() + + // clone element and assign new id + var clone = assignNewId(this.node.cloneNode(true)) + + // insert the clone in the given parent or after myself + if(parent) parent.add(clone) + else this.after(clone) + + return clone + } + // Remove element + , remove: function() { + if (this.parent()) + this.parent().removeElement(this) + + return this + } + // Replace element + , replace: function(element) { + this.after(element).remove() + + return element + } + // Add element to given container and return self + , addTo: function(parent) { + return parent.put(this) + } + // Add element to given container and return container + , putIn: function(parent) { + return parent.add(this) + } + // Get / set id + , id: function(id) { + return this.attr('id', id) + } + // Checks whether the given point inside the bounding box of the element + , inside: function(x, y) { + var box = this.bbox() + + return x > box.x + && y > box.y + && x < box.x + box.width + && y < box.y + box.height + } + // Show element + , show: function() { + return this.style('display', '') + } + // Hide element + , hide: function() { + return this.style('display', 'none') + } + // Is element visible? + , visible: function() { + return this.style('display') != 'none' + } + // Return id on string conversion + , toString: function() { + return this.attr('id') + } + // Return array of classes on the node + , classes: function() { + var attr = this.attr('class') + + return attr == null ? [] : attr.trim().split(SVG.regex.delimiter) + } + // Return true if class exists on the node, false otherwise + , hasClass: function(name) { + return this.classes().indexOf(name) != -1 + } + // Add class to the node + , addClass: function(name) { + if (!this.hasClass(name)) { + var array = this.classes() + array.push(name) + this.attr('class', array.join(' ')) + } + + return this + } + // Remove class from the node + , removeClass: function(name) { + if (this.hasClass(name)) { + this.attr('class', this.classes().filter(function(c) { + return c != name + }).join(' ')) + } + + return this + } + // Toggle the presence of a class on the node + , toggleClass: function(name) { + return this.hasClass(name) ? this.removeClass(name) : this.addClass(name) + } + // Get referenced element form attribute value + , reference: function(attr) { + return SVG.get(this.attr(attr)) + } + // Returns the parent element instance + , parent: function(type) { + var parent = this + + // check for parent + if(!parent.node.parentNode) return null + + // get parent element + parent = SVG.adopt(parent.node.parentNode) + + if(!type) return parent + + // loop trough ancestors if type is given + while(parent && parent.node instanceof window.SVGElement){ + if(typeof type === 'string' ? parent.matches(type) : parent instanceof type) return parent + parent = SVG.adopt(parent.node.parentNode) + } + } + // Get parent document + , doc: function() { + return this instanceof SVG.Doc ? this : this.parent(SVG.Doc) + } + // return array of all ancestors of given type up to the root svg + , parents: function(type) { + var parents = [], parent = this + + do{ + parent = parent.parent(type) + if(!parent || !parent.node) break + + parents.push(parent) + } while(parent.parent) + + return parents + } + // matches the element vs a css selector + , matches: function(selector){ + return matches(this.node, selector) + } + // Returns the svg node to call native svg methods on it + , native: function() { + return this.node + } + // Import raw svg + , svg: function(svg) { + // create temporary holder + var well = document.createElement('svg') + + // act as a setter if svg is given + if (svg && this instanceof SVG.Parent) { + // dump raw svg + well.innerHTML = '' + svg.replace(/\n/, '').replace(/<(\w+)([^<]+?)\/>/g, '<$1$2>') + '' + + // transplant nodes + for (var i = 0, il = well.firstChild.childNodes.length; i < il; i++) + this.node.appendChild(well.firstChild.firstChild) + + // otherwise act as a getter + } else { + // create a wrapping svg element in case of partial content + well.appendChild(svg = document.createElement('svg')) + + // write svgjs data to the dom + this.writeDataToDom() + + // insert a copy of this node + svg.appendChild(this.node.cloneNode(true)) + + // return target element + return well.innerHTML.replace(/^/, '').replace(/<\/svg>$/, '') + } + + return this + } + // write svgjs data to the dom + , writeDataToDom: function() { + + // dump variables recursively + if(this.each || this.lines){ + var fn = this.each ? this : this.lines(); + fn.each(function(){ + this.writeDataToDom() + }) + } + + // remove previously set data + this.node.removeAttribute('svgjs:data') + + if(Object.keys(this.dom).length) + this.node.setAttribute('svgjs:data', JSON.stringify(this.dom)) // see #428 + + return this + } + // set given data to the elements data property + , setData: function(o){ + this.dom = o + return this + } + , is: function(obj){ + return is(this, obj) + } + } +}) + +SVG.easing = { + '-': function(pos){return pos} +, '<>':function(pos){return -Math.cos(pos * Math.PI) / 2 + 0.5} +, '>': function(pos){return Math.sin(pos * Math.PI / 2)} +, '<': function(pos){return -Math.cos(pos * Math.PI / 2) + 1} +} + +SVG.morph = function(pos){ + return function(from, to) { + return new SVG.MorphObj(from, to).at(pos) + } +} + +SVG.Situation = SVG.invent({ + + create: function(o){ + this.init = false + this.reversed = false + this.reversing = false + + this.duration = new SVG.Number(o.duration).valueOf() + this.delay = new SVG.Number(o.delay).valueOf() + + this.start = +new Date() + this.delay + this.finish = this.start + this.duration + this.ease = o.ease + + // this.loop is incremented from 0 to this.loops + // it is also incremented when in an infinite loop (when this.loops is true) + this.loop = 0 + this.loops = false + + this.animations = { + // functionToCall: [list of morphable objects] + // e.g. move: [SVG.Number, SVG.Number] + } + + this.attrs = { + // holds all attributes which are not represented from a function svg.js provides + // e.g. someAttr: SVG.Number + } + + this.styles = { + // holds all styles which should be animated + // e.g. fill-color: SVG.Color + } + + this.transforms = [ + // holds all transformations as transformation objects + // e.g. [SVG.Rotate, SVG.Translate, SVG.Matrix] + ] + + this.once = { + // functions to fire at a specific position + // e.g. "0.5": function foo(){} + } + + } + +}) + + +SVG.FX = SVG.invent({ + + create: function(element) { + this._target = element + this.situations = [] + this.active = false + this.situation = null + this.paused = false + this.lastPos = 0 + this.pos = 0 + // The absolute position of an animation is its position in the context of its complete duration (including delay and loops) + // When performing a delay, absPos is below 0 and when performing a loop, its value is above 1 + this.absPos = 0 + this._speed = 1 + } + +, extend: { + + /** + * sets or returns the target of this animation + * @param o object || number In case of Object it holds all parameters. In case of number its the duration of the animation + * @param ease function || string Function which should be used for easing or easing keyword + * @param delay Number indicating the delay before the animation starts + * @return target || this + */ + animate: function(o, ease, delay){ + + if(typeof o == 'object'){ + ease = o.ease + delay = o.delay + o = o.duration + } + + var situation = new SVG.Situation({ + duration: o || 1000, + delay: delay || 0, + ease: SVG.easing[ease || '-'] || ease + }) + + this.queue(situation) + + return this + } + + /** + * sets a delay before the next element of the queue is called + * @param delay Duration of delay in milliseconds + * @return this.target() + */ + , delay: function(delay){ + // The delay is performed by an empty situation with its duration + // attribute set to the duration of the delay + var situation = new SVG.Situation({ + duration: delay, + delay: 0, + ease: SVG.easing['-'] + }) + + return this.queue(situation) + } + + /** + * sets or returns the target of this animation + * @param null || target SVG.Element which should be set as new target + * @return target || this + */ + , target: function(target){ + if(target && target instanceof SVG.Element){ + this._target = target + return this + } + + return this._target + } + + // returns the absolute position at a given time + , timeToAbsPos: function(timestamp){ + return (timestamp - this.situation.start) / (this.situation.duration/this._speed) + } + + // returns the timestamp from a given absolute positon + , absPosToTime: function(absPos){ + return this.situation.duration/this._speed * absPos + this.situation.start + } + + // starts the animationloop + , startAnimFrame: function(){ + this.stopAnimFrame() + this.animationFrame = window.requestAnimationFrame(function(){ this.step() }.bind(this)) + } + + // cancels the animationframe + , stopAnimFrame: function(){ + window.cancelAnimationFrame(this.animationFrame) + } + + // kicks off the animation - only does something when the queue is currently not active and at least one situation is set + , start: function(){ + // dont start if already started + if(!this.active && this.situation){ + this.active = true + this.startCurrent() + } + + return this + } + + // start the current situation + , startCurrent: function(){ + this.situation.start = +new Date + this.situation.delay/this._speed + this.situation.finish = this.situation.start + this.situation.duration/this._speed + return this.initAnimations().step() + } + + /** + * adds a function / Situation to the animation queue + * @param fn function / situation to add + * @return this + */ + , queue: function(fn){ + if(typeof fn == 'function' || fn instanceof SVG.Situation) + this.situations.push(fn) + + if(!this.situation) this.situation = this.situations.shift() + + return this + } + + /** + * pulls next element from the queue and execute it + * @return this + */ + , dequeue: function(){ + // stop current animation + this.stop() + + // get next animation from queue + this.situation = this.situations.shift() + + if(this.situation){ + if(this.situation instanceof SVG.Situation) { + this.start() + } else { + // If it is not a SVG.Situation, then it is a function, we execute it + this.situation.call(this) + } + } + + return this + } + + // updates all animations to the current state of the element + // this is important when one property could be changed from another property + , initAnimations: function() { + var i, source + var s = this.situation + + if(s.init) return this + + for(i in s.animations){ + source = this.target()[i]() + + // The condition is because some methods return a normal number instead + // of a SVG.Number + if(s.animations[i] instanceof SVG.Number) + source = new SVG.Number(source) + + s.animations[i] = source.morph(s.animations[i]) + } + + for(i in s.attrs){ + s.attrs[i] = new SVG.MorphObj(this.target().attr(i), s.attrs[i]) + } + + for(i in s.styles){ + s.styles[i] = new SVG.MorphObj(this.target().style(i), s.styles[i]) + } + + s.initialTransformation = this.target().matrixify() + + s.init = true + return this + } + , clearQueue: function(){ + this.situations = [] + return this + } + , clearCurrent: function(){ + this.situation = null + return this + } + /** stops the animation immediately + * @param jumpToEnd A Boolean indicating whether to complete the current animation immediately. + * @param clearQueue A Boolean indicating whether to remove queued animation as well. + * @return this + */ + , stop: function(jumpToEnd, clearQueue){ + var active = this.active + this.active = false + + if(clearQueue){ + this.clearQueue() + } + + if(jumpToEnd && this.situation){ + // initialize the situation if it was not + !active && this.startCurrent() + this.atEnd() + } + + this.stopAnimFrame() + + return this.clearCurrent() + } + + /** resets the element to the state where the current element has started + * @return this + */ + , reset: function(){ + if(this.situation){ + var temp = this.situation + this.stop() + this.situation = temp + this.atStart() + } + return this + } + + // Stop the currently-running animation, remove all queued animations, and complete all animations for the element. + , finish: function(){ + + this.stop(true, false) + + while(this.dequeue().situation && this.stop(true, false)); + + this.clearQueue().clearCurrent() + + return this + } + + // set the internal animation pointer at the start position, before any loops, and updates the visualisation + , atStart: function() { + return this.at(0, true) + } + + // set the internal animation pointer at the end position, after all the loops, and updates the visualisation + , atEnd: function() { + if (this.situation.loops === true) { + // If in a infinite loop, we end the current iteration + this.situation.loops = this.situation.loop + 1 + } + + if(typeof this.situation.loops == 'number') { + // If performing a finite number of loops, we go after all the loops + return this.at(this.situation.loops, true) + } else { + // If no loops, we just go at the end + return this.at(1, true) + } + } + + // set the internal animation pointer to the specified position and updates the visualisation + // if isAbsPos is true, pos is treated as an absolute position + , at: function(pos, isAbsPos){ + var durDivSpd = this.situation.duration/this._speed + + this.absPos = pos + // If pos is not an absolute position, we convert it into one + if (!isAbsPos) { + if (this.situation.reversed) this.absPos = 1 - this.absPos + this.absPos += this.situation.loop + } + + this.situation.start = +new Date - this.absPos * durDivSpd + this.situation.finish = this.situation.start + durDivSpd + + return this.step(true) + } + + /** + * sets or returns the speed of the animations + * @param speed null || Number The new speed of the animations + * @return Number || this + */ + , speed: function(speed){ + if (speed === 0) return this.pause() + + if (speed) { + this._speed = speed + // We use an absolute position here so that speed can affect the delay before the animation + return this.at(this.absPos, true) + } else return this._speed + } + + // Make loopable + , loop: function(times, reverse) { + var c = this.last() + + // store total loops + c.loops = (times != null) ? times : true + c.loop = 0 + + if(reverse) c.reversing = true + return this + } + + // pauses the animation + , pause: function(){ + this.paused = true + this.stopAnimFrame() + + return this + } + + // unpause the animation + , play: function(){ + if(!this.paused) return this + this.paused = false + // We use an absolute position here so that the delay before the animation can be paused + return this.at(this.absPos, true) + } + + /** + * toggle or set the direction of the animation + * true sets direction to backwards while false sets it to forwards + * @param reversed Boolean indicating whether to reverse the animation or not (default: toggle the reverse status) + * @return this + */ + , reverse: function(reversed){ + var c = this.last() + + if(typeof reversed == 'undefined') c.reversed = !c.reversed + else c.reversed = reversed + + return this + } + + + /** + * returns a float from 0-1 indicating the progress of the current animation + * @param eased Boolean indicating whether the returned position should be eased or not + * @return number + */ + , progress: function(easeIt){ + return easeIt ? this.situation.ease(this.pos) : this.pos + } + + /** + * adds a callback function which is called when the current animation is finished + * @param fn Function which should be executed as callback + * @return number + */ + , after: function(fn){ + var c = this.last() + , wrapper = function wrapper(e){ + if(e.detail.situation == c){ + fn.call(this, c) + this.off('finished.fx', wrapper) // prevent memory leak + } + } + + this.target().on('finished.fx', wrapper) + + return this._callStart() + } + + // adds a callback which is called whenever one animation step is performed + , during: function(fn){ + var c = this.last() + , wrapper = function(e){ + if(e.detail.situation == c){ + fn.call(this, e.detail.pos, SVG.morph(e.detail.pos), e.detail.eased, c) + } + } + + // see above + this.target().off('during.fx', wrapper).on('during.fx', wrapper) + + this.after(function(){ + this.off('during.fx', wrapper) + }) + + return this._callStart() + } + + // calls after ALL animations in the queue are finished + , afterAll: function(fn){ + var wrapper = function wrapper(e){ + fn.call(this) + this.off('allfinished.fx', wrapper) + } + + // see above + this.target().off('allfinished.fx', wrapper).on('allfinished.fx', wrapper) + + return this._callStart() + } + + // calls on every animation step for all animations + , duringAll: function(fn){ + var wrapper = function(e){ + fn.call(this, e.detail.pos, SVG.morph(e.detail.pos), e.detail.eased, e.detail.situation) + } + + this.target().off('during.fx', wrapper).on('during.fx', wrapper) + + this.afterAll(function(){ + this.off('during.fx', wrapper) + }) + + return this._callStart() + } + + , last: function(){ + return this.situations.length ? this.situations[this.situations.length-1] : this.situation + } + + // adds one property to the animations + , add: function(method, args, type){ + this.last()[type || 'animations'][method] = args + return this._callStart() + } + + /** perform one step of the animation + * @param ignoreTime Boolean indicating whether to ignore time and use position directly or recalculate position based on time + * @return this + */ + , step: function(ignoreTime){ + + // convert current time to an absolute position + if(!ignoreTime) this.absPos = this.timeToAbsPos(+new Date) + + // This part convert an absolute position to a position + if(this.situation.loops !== false) { + var absPos, absPosInt, lastLoop + + // If the absolute position is below 0, we just treat it as if it was 0 + absPos = Math.max(this.absPos, 0) + absPosInt = Math.floor(absPos) + + if(this.situation.loops === true || absPosInt < this.situation.loops) { + this.pos = absPos - absPosInt + lastLoop = this.situation.loop + this.situation.loop = absPosInt + } else { + this.absPos = this.situation.loops + this.pos = 1 + // The -1 here is because we don't want to toggle reversed when all the loops have been completed + lastLoop = this.situation.loop - 1 + this.situation.loop = this.situation.loops + } + + if(this.situation.reversing) { + // Toggle reversed if an odd number of loops as occured since the last call of step + this.situation.reversed = this.situation.reversed != Boolean((this.situation.loop - lastLoop) % 2) + } + + } else { + // If there are no loop, the absolute position must not be above 1 + this.absPos = Math.min(this.absPos, 1) + this.pos = this.absPos + } + + // while the absolute position can be below 0, the position must not be below 0 + if(this.pos < 0) this.pos = 0 + + if(this.situation.reversed) this.pos = 1 - this.pos + + + // apply easing + var eased = this.situation.ease(this.pos) + + // call once-callbacks + for(var i in this.situation.once){ + if(i > this.lastPos && i <= eased){ + this.situation.once[i].call(this.target(), this.pos, eased) + delete this.situation.once[i] + } + } + + // fire during callback with position, eased position and current situation as parameter + if(this.active) this.target().fire('during', {pos: this.pos, eased: eased, fx: this, situation: this.situation}) + + // the user may call stop or finish in the during callback + // so make sure that we still have a valid situation + if(!this.situation){ + return this + } + + // apply the actual animation to every property + this.eachAt() + + // do final code when situation is finished + if((this.pos == 1 && !this.situation.reversed) || (this.situation.reversed && this.pos == 0)){ + + // stop animation callback + this.stopAnimFrame() + + // fire finished callback with current situation as parameter + this.target().fire('finished', {fx:this, situation: this.situation}) + + if(!this.situations.length){ + this.target().fire('allfinished') + this.target().off('.fx') // there shouldnt be any binding left, but to make sure... + this.active = false + } + + // start next animation + if(this.active) this.dequeue() + else this.clearCurrent() + + }else if(!this.paused && this.active){ + // we continue animating when we are not at the end + this.startAnimFrame() + } + + // save last eased position for once callback triggering + this.lastPos = eased + return this + + } + + // calculates the step for every property and calls block with it + , eachAt: function(){ + var i, len, at, self = this, target = this.target(), s = this.situation + + // apply animations which can be called trough a method + for(i in s.animations){ + + at = [].concat(s.animations[i]).map(function(el){ + return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el + }) + + target[i].apply(target, at) + + } + + // apply animation which has to be applied with attr() + for(i in s.attrs){ + + at = [i].concat(s.attrs[i]).map(function(el){ + return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el + }) + + target.attr.apply(target, at) + + } + + // apply animation which has to be applied with style() + for(i in s.styles){ + + at = [i].concat(s.styles[i]).map(function(el){ + return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el + }) + + target.style.apply(target, at) + + } + + // animate initialTransformation which has to be chained + if(s.transforms.length){ + + // get initial initialTransformation + at = s.initialTransformation + for(i = 0, len = s.transforms.length; i < len; i++){ + + // get next transformation in chain + var a = s.transforms[i] + + // multiply matrix directly + if(a instanceof SVG.Matrix){ + + if(a.relative){ + at = at.multiply(new SVG.Matrix().morph(a).at(s.ease(this.pos))) + }else{ + at = at.morph(a).at(s.ease(this.pos)) + } + continue + } + + // when transformation is absolute we have to reset the needed transformation first + if(!a.relative) + a.undo(at.extract()) + + // and reapply it after + at = at.multiply(a.at(s.ease(this.pos))) + + } + + // set new matrix on element + target.matrix(at) + } + + return this + + } + + + // adds an once-callback which is called at a specific position and never again + , once: function(pos, fn, isEased){ + + if(!isEased)pos = this.situation.ease(pos) + + this.situation.once[pos] = fn + + return this + } + + , _callStart: function() { + setTimeout(function(){this.start()}.bind(this), 0) + return this + } + + } + +, parent: SVG.Element + + // Add method to parent elements +, construct: { + // Get fx module or create a new one, then animate with given duration and ease + animate: function(o, ease, delay) { + return (this.fx || (this.fx = new SVG.FX(this))).animate(o, ease, delay) + } + , delay: function(delay){ + return (this.fx || (this.fx = new SVG.FX(this))).delay(delay) + } + , stop: function(jumpToEnd, clearQueue) { + if (this.fx) + this.fx.stop(jumpToEnd, clearQueue) + + return this + } + , finish: function() { + if (this.fx) + this.fx.finish() + + return this + } + // Pause current animation + , pause: function() { + if (this.fx) + this.fx.pause() + + return this + } + // Play paused current animation + , play: function() { + if (this.fx) + this.fx.play() + + return this + } + // Set/Get the speed of the animations + , speed: function(speed) { + if (this.fx) + if (speed == null) + return this.fx.speed() + else + this.fx.speed(speed) + + return this + } + } + +}) + +// MorphObj is used whenever no morphable object is given +SVG.MorphObj = SVG.invent({ + + create: function(from, to){ + // prepare color for morphing + if(SVG.Color.isColor(to)) return new SVG.Color(from).morph(to) + // prepare number for morphing + if(SVG.regex.numberAndUnit.test(to)) return new SVG.Number(from).morph(to) + + // prepare for plain morphing + this.value = from + this.destination = to + } + +, extend: { + at: function(pos, real){ + return real < 1 ? this.value : this.destination + }, + + valueOf: function(){ + return this.value + } + } + +}) + +SVG.extend(SVG.FX, { + // Add animatable attributes + attr: function(a, v, relative) { + // apply attributes individually + if (typeof a == 'object') { + for (var key in a) + this.attr(key, a[key]) + + } else { + this.add(a, v, 'attrs') + } + + return this + } + // Add animatable styles +, style: function(s, v) { + if (typeof s == 'object') + for (var key in s) + this.style(key, s[key]) + + else + this.add(s, v, 'styles') + + return this + } + // Animatable x-axis +, x: function(x, relative) { + if(this.target() instanceof SVG.G){ + this.transform({x:x}, relative) + return this + } + + var num = new SVG.Number(x) + num.relative = relative + return this.add('x', num) + } + // Animatable y-axis +, y: function(y, relative) { + if(this.target() instanceof SVG.G){ + this.transform({y:y}, relative) + return this + } + + var num = new SVG.Number(y) + num.relative = relative + return this.add('y', num) + } + // Animatable center x-axis +, cx: function(x) { + return this.add('cx', new SVG.Number(x)) + } + // Animatable center y-axis +, cy: function(y) { + return this.add('cy', new SVG.Number(y)) + } + // Add animatable move +, move: function(x, y) { + return this.x(x).y(y) + } + // Add animatable center +, center: function(x, y) { + return this.cx(x).cy(y) + } + // Add animatable size +, size: function(width, height) { + if (this.target() instanceof SVG.Text) { + // animate font size for Text elements + this.attr('font-size', width) + + } else { + // animate bbox based size for all other elements + var box + + if(!width || !height){ + box = this.target().bbox() + } + + if(!width){ + width = box.width / box.height * height + } + + if(!height){ + height = box.height / box.width * width + } + + this.add('width' , new SVG.Number(width)) + .add('height', new SVG.Number(height)) + + } + + return this + } + // Add animatable plot +, plot: function() { + // We use arguments here since SVG.Line's plot method can be passed 4 parameters + return this.add('plot', arguments.length > 1 ? [].slice.call(arguments) : arguments[0]) + } + // Add leading method +, leading: function(value) { + return this.target().leading ? + this.add('leading', new SVG.Number(value)) : + this + } + // Add animatable viewbox +, viewbox: function(x, y, width, height) { + if (this.target() instanceof SVG.Container) { + this.add('viewbox', new SVG.ViewBox(x, y, width, height)) + } + + return this + } +, update: function(o) { + if (this.target() instanceof SVG.Stop) { + if (typeof o == 'number' || o instanceof SVG.Number) { + return this.update({ + offset: arguments[0] + , color: arguments[1] + , opacity: arguments[2] + }) + } + + if (o.opacity != null) this.attr('stop-opacity', o.opacity) + if (o.color != null) this.attr('stop-color', o.color) + if (o.offset != null) this.attr('offset', o.offset) + } + + return this + } +}) + +SVG.Box = SVG.invent({ + create: function(x, y, width, height) { + if (typeof x == 'object' && !(x instanceof SVG.Element)) { + // chromes getBoundingClientRect has no x and y property + return SVG.Box.call(this, x.left != null ? x.left : x.x , x.top != null ? x.top : x.y, x.width, x.height) + } else if (arguments.length == 4) { + this.x = x + this.y = y + this.width = width + this.height = height + } + + // add center, right, bottom... + fullBox(this) + } +, extend: { + // Merge rect box with another, return a new instance + merge: function(box) { + var b = new this.constructor() + + // merge boxes + b.x = Math.min(this.x, box.x) + b.y = Math.min(this.y, box.y) + b.width = Math.max(this.x + this.width, box.x + box.width) - b.x + b.height = Math.max(this.y + this.height, box.y + box.height) - b.y + + return fullBox(b) + } + + , transform: function(m) { + var xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, p, bbox + + var pts = [ + new SVG.Point(this.x, this.y), + new SVG.Point(this.x2, this.y), + new SVG.Point(this.x, this.y2), + new SVG.Point(this.x2, this.y2) + ] + + pts.forEach(function(p) { + p = p.transform(m) + xMin = Math.min(xMin,p.x) + xMax = Math.max(xMax,p.x) + yMin = Math.min(yMin,p.y) + yMax = Math.max(yMax,p.y) + }) + + bbox = new this.constructor() + bbox.x = xMin + bbox.width = xMax-xMin + bbox.y = yMin + bbox.height = yMax-yMin + + fullBox(bbox) + + return bbox + } + } +}) + +SVG.BBox = SVG.invent({ + // Initialize + create: function(element) { + SVG.Box.apply(this, [].slice.call(arguments)) + + // get values if element is given + if (element instanceof SVG.Element) { + var box + + // yes this is ugly, but Firefox can be a bitch when it comes to elements that are not yet rendered + try { + + if (!document.documentElement.contains){ + // This is IE - it does not support contains() for top-level SVGs + var topParent = element.node + while (topParent.parentNode){ + topParent = topParent.parentNode + } + if (topParent != document) throw new Exception('Element not in the dom') + } else { + // the element is NOT in the dom, throw error + if(!document.documentElement.contains(element.node)) throw new Exception('Element not in the dom') + } + + // find native bbox + box = element.node.getBBox() + } catch(e) { + if(element instanceof SVG.Shape){ + var clone = element.clone(SVG.parser.draw.instance).show() + box = clone.node.getBBox() + clone.remove() + }else{ + box = { + x: element.node.clientLeft + , y: element.node.clientTop + , width: element.node.clientWidth + , height: element.node.clientHeight + } + } + } + + SVG.Box.call(this, box) + } + + } + + // Define ancestor +, inherit: SVG.Box + + // Define Parent +, parent: SVG.Element + + // Constructor +, construct: { + // Get bounding box + bbox: function() { + return new SVG.BBox(this) + } + } + +}) + +SVG.BBox.prototype.constructor = SVG.BBox + + +SVG.extend(SVG.Element, { + tbox: function(){ + console.warn('Use of TBox is deprecated and mapped to RBox. Use .rbox() instead.') + return this.rbox(this.doc()) + } +}) + +SVG.RBox = SVG.invent({ + // Initialize + create: function(element) { + SVG.Box.apply(this, [].slice.call(arguments)) + + if (element instanceof SVG.Element) { + SVG.Box.call(this, element.node.getBoundingClientRect()) + } + } + +, inherit: SVG.Box + + // define Parent +, parent: SVG.Element + +, extend: { + addOffset: function() { + // offset by window scroll position, because getBoundingClientRect changes when window is scrolled + this.x += window.pageXOffset + this.y += window.pageYOffset + return this + } + } + + // Constructor +, construct: { + // Get rect box + rbox: function(el) { + if (el) return new SVG.RBox(this).transform(el.screenCTM().inverse()) + return new SVG.RBox(this).addOffset() + } + } + +}) + +SVG.RBox.prototype.constructor = SVG.RBox + +SVG.Matrix = SVG.invent({ + // Initialize + create: function(source) { + var i, base = arrayToMatrix([1, 0, 0, 1, 0, 0]) + + // ensure source as object + source = source instanceof SVG.Element ? + source.matrixify() : + typeof source === 'string' ? + arrayToMatrix(source.split(SVG.regex.delimiter).map(parseFloat)) : + arguments.length == 6 ? + arrayToMatrix([].slice.call(arguments)) : + Array.isArray(source) ? + arrayToMatrix(source) : + typeof source === 'object' ? + source : base + + // merge source + for (i = abcdef.length - 1; i >= 0; --i) + this[abcdef[i]] = source && typeof source[abcdef[i]] === 'number' ? + source[abcdef[i]] : base[abcdef[i]] + } + + // Add methods +, extend: { + // Extract individual transformations + extract: function() { + // find delta transform points + var px = deltaTransformPoint(this, 0, 1) + , py = deltaTransformPoint(this, 1, 0) + , skewX = 180 / Math.PI * Math.atan2(px.y, px.x) - 90 + + return { + // translation + x: this.e + , y: this.f + , transformedX:(this.e * Math.cos(skewX * Math.PI / 180) + this.f * Math.sin(skewX * Math.PI / 180)) / Math.sqrt(this.a * this.a + this.b * this.b) + , transformedY:(this.f * Math.cos(skewX * Math.PI / 180) + this.e * Math.sin(-skewX * Math.PI / 180)) / Math.sqrt(this.c * this.c + this.d * this.d) + // skew + , skewX: -skewX + , skewY: 180 / Math.PI * Math.atan2(py.y, py.x) + // scale + , scaleX: Math.sqrt(this.a * this.a + this.b * this.b) + , scaleY: Math.sqrt(this.c * this.c + this.d * this.d) + // rotation + , rotation: skewX + , a: this.a + , b: this.b + , c: this.c + , d: this.d + , e: this.e + , f: this.f + , matrix: new SVG.Matrix(this) + } + } + // Clone matrix + , clone: function() { + return new SVG.Matrix(this) + } + // Morph one matrix into another + , morph: function(matrix) { + // store new destination + this.destination = new SVG.Matrix(matrix) + + return this + } + // Get morphed matrix at a given position + , at: function(pos) { + // make sure a destination is defined + if (!this.destination) return this + + // calculate morphed matrix at a given position + var matrix = new SVG.Matrix({ + a: this.a + (this.destination.a - this.a) * pos + , b: this.b + (this.destination.b - this.b) * pos + , c: this.c + (this.destination.c - this.c) * pos + , d: this.d + (this.destination.d - this.d) * pos + , e: this.e + (this.destination.e - this.e) * pos + , f: this.f + (this.destination.f - this.f) * pos + }) + + return matrix + } + // Multiplies by given matrix + , multiply: function(matrix) { + return new SVG.Matrix(this.native().multiply(parseMatrix(matrix).native())) + } + // Inverses matrix + , inverse: function() { + return new SVG.Matrix(this.native().inverse()) + } + // Translate matrix + , translate: function(x, y) { + return new SVG.Matrix(this.native().translate(x || 0, y || 0)) + } + // Scale matrix + , scale: function(x, y, cx, cy) { + // support uniformal scale + if (arguments.length == 1) { + y = x + } else if (arguments.length == 3) { + cy = cx + cx = y + y = x + } + + return this.around(cx, cy, new SVG.Matrix(x, 0, 0, y, 0, 0)) + } + // Rotate matrix + , rotate: function(r, cx, cy) { + // convert degrees to radians + r = SVG.utils.radians(r) + + return this.around(cx, cy, new SVG.Matrix(Math.cos(r), Math.sin(r), -Math.sin(r), Math.cos(r), 0, 0)) + } + // Flip matrix on x or y, at a given offset + , flip: function(a, o) { + return a == 'x' ? + this.scale(-1, 1, o, 0) : + a == 'y' ? + this.scale(1, -1, 0, o) : + this.scale(-1, -1, a, o != null ? o : a) + } + // Skew + , skew: function(x, y, cx, cy) { + // support uniformal skew + if (arguments.length == 1) { + y = x + } else if (arguments.length == 3) { + cy = cx + cx = y + y = x + } + + // convert degrees to radians + x = SVG.utils.radians(x) + y = SVG.utils.radians(y) + + return this.around(cx, cy, new SVG.Matrix(1, Math.tan(y), Math.tan(x), 1, 0, 0)) + } + // SkewX + , skewX: function(x, cx, cy) { + return this.skew(x, 0, cx, cy) + } + // SkewY + , skewY: function(y, cx, cy) { + return this.skew(0, y, cx, cy) + } + // Transform around a center point + , around: function(cx, cy, matrix) { + return this + .multiply(new SVG.Matrix(1, 0, 0, 1, cx || 0, cy || 0)) + .multiply(matrix) + .multiply(new SVG.Matrix(1, 0, 0, 1, -cx || 0, -cy || 0)) + } + // Convert to native SVGMatrix + , native: function() { + // create new matrix + var matrix = SVG.parser.native.createSVGMatrix() + + // update with current values + for (var i = abcdef.length - 1; i >= 0; i--) + matrix[abcdef[i]] = this[abcdef[i]] + + return matrix + } + // Convert matrix to string + , toString: function() { + return 'matrix(' + this.a + ',' + this.b + ',' + this.c + ',' + this.d + ',' + this.e + ',' + this.f + ')' + } + } + + // Define parent +, parent: SVG.Element + + // Add parent method +, construct: { + // Get current matrix + ctm: function() { + return new SVG.Matrix(this.node.getCTM()) + }, + // Get current screen matrix + screenCTM: function() { + /* https://bugzilla.mozilla.org/show_bug.cgi?id=1344537 + This is needed because FF does not return the transformation matrix + for the inner coordinate system when getScreenCTM() is called on nested svgs. + However all other Browsers do that */ + if(this instanceof SVG.Nested) { + var rect = this.rect(1,1) + var m = rect.node.getScreenCTM() + rect.remove() + return new SVG.Matrix(m) + } + return new SVG.Matrix(this.node.getScreenCTM()) + } + + } + +}) + +SVG.Point = SVG.invent({ + // Initialize + create: function(x,y) { + var i, source, base = {x:0, y:0} + + // ensure source as object + source = Array.isArray(x) ? + {x:x[0], y:x[1]} : + typeof x === 'object' ? + {x:x.x, y:x.y} : + x != null ? + {x:x, y:(y != null ? y : x)} : base // If y has no value, then x is used has its value + + // merge source + this.x = source.x + this.y = source.y + } + + // Add methods +, extend: { + // Clone point + clone: function() { + return new SVG.Point(this) + } + // Morph one point into another + , morph: function(x, y) { + // store new destination + this.destination = new SVG.Point(x, y) + + return this + } + // Get morphed point at a given position + , at: function(pos) { + // make sure a destination is defined + if (!this.destination) return this + + // calculate morphed matrix at a given position + var point = new SVG.Point({ + x: this.x + (this.destination.x - this.x) * pos + , y: this.y + (this.destination.y - this.y) * pos + }) + + return point + } + // Convert to native SVGPoint + , native: function() { + // create new point + var point = SVG.parser.native.createSVGPoint() + + // update with current values + point.x = this.x + point.y = this.y + + return point + } + // transform point with matrix + , transform: function(matrix) { + return new SVG.Point(this.native().matrixTransform(matrix.native())) + } + + } + +}) + +SVG.extend(SVG.Element, { + + // Get point + point: function(x, y) { + return new SVG.Point(x,y).transform(this.screenCTM().inverse()); + } + +}) + +SVG.extend(SVG.Element, { + // Set svg element attribute + attr: function(a, v, n) { + // act as full getter + if (a == null) { + // get an object of attributes + a = {} + v = this.node.attributes + for (n = v.length - 1; n >= 0; n--) + a[v[n].nodeName] = SVG.regex.isNumber.test(v[n].nodeValue) ? parseFloat(v[n].nodeValue) : v[n].nodeValue + + return a + + } else if (typeof a == 'object') { + // apply every attribute individually if an object is passed + for (v in a) this.attr(v, a[v]) + + } else if (v === null) { + // remove value + this.node.removeAttribute(a) + + } else if (v == null) { + // act as a getter if the first and only argument is not an object + v = this.node.getAttribute(a) + return v == null ? + SVG.defaults.attrs[a] : + SVG.regex.isNumber.test(v) ? + parseFloat(v) : v + + } else { + // BUG FIX: some browsers will render a stroke if a color is given even though stroke width is 0 + if (a == 'stroke-width') + this.attr('stroke', parseFloat(v) > 0 ? this._stroke : null) + else if (a == 'stroke') + this._stroke = v + + // convert image fill and stroke to patterns + if (a == 'fill' || a == 'stroke') { + if (SVG.regex.isImage.test(v)) + v = this.doc().defs().image(v, 0, 0) + + if (v instanceof SVG.Image) + v = this.doc().defs().pattern(0, 0, function() { + this.add(v) + }) + } + + // ensure correct numeric values (also accepts NaN and Infinity) + if (typeof v === 'number') + v = new SVG.Number(v) + + // ensure full hex color + else if (SVG.Color.isColor(v)) + v = new SVG.Color(v) + + // parse array values + else if (Array.isArray(v)) + v = new SVG.Array(v) + + // if the passed attribute is leading... + if (a == 'leading') { + // ... call the leading method instead + if (this.leading) + this.leading(v) + } else { + // set given attribute on node + typeof n === 'string' ? + this.node.setAttributeNS(n, a, v.toString()) : + this.node.setAttribute(a, v.toString()) + } + + // rebuild if required + if (this.rebuild && (a == 'font-size' || a == 'x')) + this.rebuild(a, v) + } + + return this + } +}) +SVG.extend(SVG.Element, { + // Add transformations + transform: function(o, relative) { + // get target in case of the fx module, otherwise reference this + var target = this + , matrix, bbox + + // act as a getter + if (typeof o !== 'object') { + // get current matrix + matrix = new SVG.Matrix(target).extract() + + return typeof o === 'string' ? matrix[o] : matrix + } + + // get current matrix + matrix = new SVG.Matrix(target) + + // ensure relative flag + relative = !!relative || !!o.relative + + // act on matrix + if (o.a != null) { + matrix = relative ? + // relative + matrix.multiply(new SVG.Matrix(o)) : + // absolute + new SVG.Matrix(o) + + // act on rotation + } else if (o.rotation != null) { + // ensure centre point + ensureCentre(o, target) + + // apply transformation + matrix = relative ? + // relative + matrix.rotate(o.rotation, o.cx, o.cy) : + // absolute + matrix.rotate(o.rotation - matrix.extract().rotation, o.cx, o.cy) + + // act on scale + } else if (o.scale != null || o.scaleX != null || o.scaleY != null) { + // ensure centre point + ensureCentre(o, target) + + // ensure scale values on both axes + o.scaleX = o.scale != null ? o.scale : o.scaleX != null ? o.scaleX : 1 + o.scaleY = o.scale != null ? o.scale : o.scaleY != null ? o.scaleY : 1 + + if (!relative) { + // absolute; multiply inversed values + var e = matrix.extract() + o.scaleX = o.scaleX * 1 / e.scaleX + o.scaleY = o.scaleY * 1 / e.scaleY + } + + matrix = matrix.scale(o.scaleX, o.scaleY, o.cx, o.cy) + + // act on skew + } else if (o.skew != null || o.skewX != null || o.skewY != null) { + // ensure centre point + ensureCentre(o, target) + + // ensure skew values on both axes + o.skewX = o.skew != null ? o.skew : o.skewX != null ? o.skewX : 0 + o.skewY = o.skew != null ? o.skew : o.skewY != null ? o.skewY : 0 + + if (!relative) { + // absolute; reset skew values + var e = matrix.extract() + matrix = matrix.multiply(new SVG.Matrix().skew(e.skewX, e.skewY, o.cx, o.cy).inverse()) + } + + matrix = matrix.skew(o.skewX, o.skewY, o.cx, o.cy) + + // act on flip + } else if (o.flip) { + if(o.flip == 'x' || o.flip == 'y') { + o.offset = o.offset == null ? target.bbox()['c' + o.flip] : o.offset + } else { + if(o.offset == null) { + bbox = target.bbox() + o.flip = bbox.cx + o.offset = bbox.cy + } else { + o.flip = o.offset + } + } + + matrix = new SVG.Matrix().flip(o.flip, o.offset) + + // act on translate + } else if (o.x != null || o.y != null) { + if (relative) { + // relative + matrix = matrix.translate(o.x, o.y) + } else { + // absolute + if (o.x != null) matrix.e = o.x + if (o.y != null) matrix.f = o.y + } + } + + return this.attr('transform', matrix) + } +}) + +SVG.extend(SVG.FX, { + transform: function(o, relative) { + // get target in case of the fx module, otherwise reference this + var target = this.target() + , matrix, bbox + + // act as a getter + if (typeof o !== 'object') { + // get current matrix + matrix = new SVG.Matrix(target).extract() + + return typeof o === 'string' ? matrix[o] : matrix + } + + // ensure relative flag + relative = !!relative || !!o.relative + + // act on matrix + if (o.a != null) { + matrix = new SVG.Matrix(o) + + // act on rotation + } else if (o.rotation != null) { + // ensure centre point + ensureCentre(o, target) + + // apply transformation + matrix = new SVG.Rotate(o.rotation, o.cx, o.cy) + + // act on scale + } else if (o.scale != null || o.scaleX != null || o.scaleY != null) { + // ensure centre point + ensureCentre(o, target) + + // ensure scale values on both axes + o.scaleX = o.scale != null ? o.scale : o.scaleX != null ? o.scaleX : 1 + o.scaleY = o.scale != null ? o.scale : o.scaleY != null ? o.scaleY : 1 + + matrix = new SVG.Scale(o.scaleX, o.scaleY, o.cx, o.cy) + + // act on skew + } else if (o.skewX != null || o.skewY != null) { + // ensure centre point + ensureCentre(o, target) + + // ensure skew values on both axes + o.skewX = o.skewX != null ? o.skewX : 0 + o.skewY = o.skewY != null ? o.skewY : 0 + + matrix = new SVG.Skew(o.skewX, o.skewY, o.cx, o.cy) + + // act on flip + } else if (o.flip) { + if(o.flip == 'x' || o.flip == 'y') { + o.offset = o.offset == null ? target.bbox()['c' + o.flip] : o.offset + } else { + if(o.offset == null) { + bbox = target.bbox() + o.flip = bbox.cx + o.offset = bbox.cy + } else { + o.flip = o.offset + } + } + + matrix = new SVG.Matrix().flip(o.flip, o.offset) + + // act on translate + } else if (o.x != null || o.y != null) { + matrix = new SVG.Translate(o.x, o.y) + } + + if(!matrix) return this + + matrix.relative = relative + + this.last().transforms.push(matrix) + + return this._callStart() + } +}) + +SVG.extend(SVG.Element, { + // Reset all transformations + untransform: function() { + return this.attr('transform', null) + }, + // merge the whole transformation chain into one matrix and returns it + matrixify: function() { + + var matrix = (this.attr('transform') || '') + // split transformations + .split(SVG.regex.transforms).slice(0,-1).map(function(str){ + // generate key => value pairs + var kv = str.trim().split('(') + return [kv[0], kv[1].split(SVG.regex.delimiter).map(function(str){ return parseFloat(str) })] + }) + // merge every transformation into one matrix + .reduce(function(matrix, transform){ + + if(transform[0] == 'matrix') return matrix.multiply(arrayToMatrix(transform[1])) + return matrix[transform[0]].apply(matrix, transform[1]) + + }, new SVG.Matrix()) + + return matrix + }, + // add an element to another parent without changing the visual representation on the screen + toParent: function(parent) { + if(this == parent) return this + var ctm = this.screenCTM() + var pCtm = parent.screenCTM().inverse() + + this.addTo(parent).untransform().transform(pCtm.multiply(ctm)) + + return this + }, + // same as above with parent equals root-svg + toDoc: function() { + return this.toParent(this.doc()) + } + +}) + +SVG.Transformation = SVG.invent({ + + create: function(source, inversed){ + + if(arguments.length > 1 && typeof inversed != 'boolean'){ + return this.constructor.call(this, [].slice.call(arguments)) + } + + if(Array.isArray(source)){ + for(var i = 0, len = this.arguments.length; i < len; ++i){ + this[this.arguments[i]] = source[i] + } + } else if(typeof source == 'object'){ + for(var i = 0, len = this.arguments.length; i < len; ++i){ + this[this.arguments[i]] = source[this.arguments[i]] + } + } + + this.inversed = false + + if(inversed === true){ + this.inversed = true + } + + } + +, extend: { + + arguments: [] + , method: '' + + , at: function(pos){ + + var params = [] + + for(var i = 0, len = this.arguments.length; i < len; ++i){ + params.push(this[this.arguments[i]]) + } + + var m = this._undo || new SVG.Matrix() + + m = new SVG.Matrix().morph(SVG.Matrix.prototype[this.method].apply(m, params)).at(pos) + + return this.inversed ? m.inverse() : m + + } + + , undo: function(o){ + for(var i = 0, len = this.arguments.length; i < len; ++i){ + o[this.arguments[i]] = typeof this[this.arguments[i]] == 'undefined' ? 0 : o[this.arguments[i]] + } + + // The method SVG.Matrix.extract which was used before calling this + // method to obtain a value for the parameter o doesn't return a cx and + // a cy so we use the ones that were provided to this object at its creation + o.cx = this.cx + o.cy = this.cy + + this._undo = new SVG[capitalize(this.method)](o, true).at(1) + + return this + } + + } + +}) + +SVG.Translate = SVG.invent({ + + parent: SVG.Matrix +, inherit: SVG.Transformation + +, create: function(source, inversed){ + this.constructor.apply(this, [].slice.call(arguments)) + } + +, extend: { + arguments: ['transformedX', 'transformedY'] + , method: 'translate' + } + +}) + +SVG.Rotate = SVG.invent({ + + parent: SVG.Matrix +, inherit: SVG.Transformation + +, create: function(source, inversed){ + this.constructor.apply(this, [].slice.call(arguments)) + } + +, extend: { + arguments: ['rotation', 'cx', 'cy'] + , method: 'rotate' + , at: function(pos){ + var m = new SVG.Matrix().rotate(new SVG.Number().morph(this.rotation - (this._undo ? this._undo.rotation : 0)).at(pos), this.cx, this.cy) + return this.inversed ? m.inverse() : m + } + , undo: function(o){ + this._undo = o + return this + } + } + +}) + +SVG.Scale = SVG.invent({ + + parent: SVG.Matrix +, inherit: SVG.Transformation + +, create: function(source, inversed){ + this.constructor.apply(this, [].slice.call(arguments)) + } + +, extend: { + arguments: ['scaleX', 'scaleY', 'cx', 'cy'] + , method: 'scale' + } + +}) + +SVG.Skew = SVG.invent({ + + parent: SVG.Matrix +, inherit: SVG.Transformation + +, create: function(source, inversed){ + this.constructor.apply(this, [].slice.call(arguments)) + } + +, extend: { + arguments: ['skewX', 'skewY', 'cx', 'cy'] + , method: 'skew' + } + +}) + +SVG.extend(SVG.Element, { + // Dynamic style generator + style: function(s, v) { + if (arguments.length == 0) { + // get full style + return this.node.style.cssText || '' + + } else if (arguments.length < 2) { + // apply every style individually if an object is passed + if (typeof s == 'object') { + for (v in s) this.style(v, s[v]) + + } else if (SVG.regex.isCss.test(s)) { + // parse css string + s = s.split(/\s*;\s*/) + // filter out suffix ; and stuff like ;; + .filter(function(e) { return !!e }) + .map(function(e){ return e.split(/\s*:\s*/) }) + + // apply every definition individually + while (v = s.pop()) { + this.style(v[0], v[1]) + } + } else { + // act as a getter if the first and only argument is not an object + return this.node.style[camelCase(s)] + } + + } else { + this.node.style[camelCase(s)] = v === null || SVG.regex.isBlank.test(v) ? '' : v + } + + return this + } +}) +SVG.Parent = SVG.invent({ + // Initialize node + create: function(element) { + this.constructor.call(this, element) + } + + // Inherit from +, inherit: SVG.Element + + // Add class methods +, extend: { + // Returns all child elements + children: function() { + return SVG.utils.map(SVG.utils.filterSVGElements(this.node.childNodes), function(node) { + return SVG.adopt(node) + }) + } + // Add given element at a position + , add: function(element, i) { + if (i == null) + this.node.appendChild(element.node) + else if (element.node != this.node.childNodes[i]) + this.node.insertBefore(element.node, this.node.childNodes[i]) + + return this + } + // Basically does the same as `add()` but returns the added element instead + , put: function(element, i) { + this.add(element, i) + return element + } + // Checks if the given element is a child + , has: function(element) { + return this.index(element) >= 0 + } + // Gets index of given element + , index: function(element) { + return [].slice.call(this.node.childNodes).indexOf(element.node) + } + // Get a element at the given index + , get: function(i) { + return SVG.adopt(this.node.childNodes[i]) + } + // Get first child + , first: function() { + return this.get(0) + } + // Get the last child + , last: function() { + return this.get(this.node.childNodes.length - 1) + } + // Iterates over all children and invokes a given block + , each: function(block, deep) { + var i, il + , children = this.children() + + for (i = 0, il = children.length; i < il; i++) { + if (children[i] instanceof SVG.Element) + block.apply(children[i], [i, children]) + + if (deep && (children[i] instanceof SVG.Container)) + children[i].each(block, deep) + } + + return this + } + // Remove a given child + , removeElement: function(element) { + this.node.removeChild(element.node) + + return this + } + // Remove all elements in this container + , clear: function() { + // remove children + while(this.node.hasChildNodes()) + this.node.removeChild(this.node.lastChild) + + // remove defs reference + delete this._defs + + return this + } + , // Get defs + defs: function() { + return this.doc().defs() + } + } + +}) + +SVG.extend(SVG.Parent, { + + ungroup: function(parent, depth) { + if(depth === 0 || this instanceof SVG.Defs || this.node == SVG.parser.draw) return this + + parent = parent || (this instanceof SVG.Doc ? this : this.parent(SVG.Parent)) + depth = depth || Infinity + + this.each(function(){ + if(this instanceof SVG.Defs) return this + if(this instanceof SVG.Parent) return this.ungroup(parent, depth-1) + return this.toParent(parent) + }) + + this.node.firstChild || this.remove() + + return this + }, + + flatten: function(parent, depth) { + return this.ungroup(parent, depth) + } + +}) +SVG.Container = SVG.invent({ + // Initialize node + create: function(element) { + this.constructor.call(this, element) + } + + // Inherit from +, inherit: SVG.Parent + +}) + +SVG.ViewBox = SVG.invent({ + + create: function(source) { + var i, base = [0, 0, 0, 0] + + var x, y, width, height, box, view, we, he + , wm = 1 // width multiplier + , hm = 1 // height multiplier + , reg = /[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?/gi + + if(source instanceof SVG.Element){ + + we = source + he = source + view = (source.attr('viewBox') || '').match(reg) + box = source.bbox + + // get dimensions of current node + width = new SVG.Number(source.width()) + height = new SVG.Number(source.height()) + + // find nearest non-percentual dimensions + while (width.unit == '%') { + wm *= width.value + width = new SVG.Number(we instanceof SVG.Doc ? we.parent().offsetWidth : we.parent().width()) + we = we.parent() + } + while (height.unit == '%') { + hm *= height.value + height = new SVG.Number(he instanceof SVG.Doc ? he.parent().offsetHeight : he.parent().height()) + he = he.parent() + } + + // ensure defaults + this.x = 0 + this.y = 0 + this.width = width * wm + this.height = height * hm + this.zoom = 1 + + if (view) { + // get width and height from viewbox + x = parseFloat(view[0]) + y = parseFloat(view[1]) + width = parseFloat(view[2]) + height = parseFloat(view[3]) + + // calculate zoom accoring to viewbox + this.zoom = ((this.width / this.height) > (width / height)) ? + this.height / height : + this.width / width + + // calculate real pixel dimensions on parent SVG.Doc element + this.x = x + this.y = y + this.width = width + this.height = height + + } + + }else{ + + // ensure source as object + source = typeof source === 'string' ? + source.match(reg).map(function(el){ return parseFloat(el) }) : + Array.isArray(source) ? + source : + typeof source == 'object' ? + [source.x, source.y, source.width, source.height] : + arguments.length == 4 ? + [].slice.call(arguments) : + base + + this.x = source[0] + this.y = source[1] + this.width = source[2] + this.height = source[3] + } + + + } + +, extend: { + + toString: function() { + return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height + } + , morph: function(x, y, width, height){ + this.destination = new SVG.ViewBox(x, y, width, height) + return this + } + + , at: function(pos) { + + if(!this.destination) return this + + return new SVG.ViewBox([ + this.x + (this.destination.x - this.x) * pos + , this.y + (this.destination.y - this.y) * pos + , this.width + (this.destination.width - this.width) * pos + , this.height + (this.destination.height - this.height) * pos + ]) + + } + + } + + // Define parent +, parent: SVG.Container + + // Add parent method +, construct: { + + // get/set viewbox + viewbox: function(x, y, width, height) { + if (arguments.length == 0) + // act as a getter if there are no arguments + return new SVG.ViewBox(this) + + // otherwise act as a setter + return this.attr('viewBox', new SVG.ViewBox(x, y, width, height)) + } + + } + +}) +// Add events to elements +;[ 'click' + , 'dblclick' + , 'mousedown' + , 'mouseup' + , 'mouseover' + , 'mouseout' + , 'mousemove' + // , 'mouseenter' -> not supported by IE + // , 'mouseleave' -> not supported by IE + , 'touchstart' + , 'touchmove' + , 'touchleave' + , 'touchend' + , 'touchcancel' ].forEach(function(event) { + + // add event to SVG.Element + SVG.Element.prototype[event] = function(f) { + // bind event to element rather than element node + SVG.on(this.node, event, f) + return this + } +}) + +// Initialize listeners stack +SVG.listeners = [] +SVG.handlerMap = [] +SVG.listenerId = 0 + +// Add event binder in the SVG namespace +SVG.on = function(node, event, listener, binding, options) { + // create listener, get object-index + var l = listener.bind(binding || node.instance || node) + , index = (SVG.handlerMap.indexOf(node) + 1 || SVG.handlerMap.push(node)) - 1 + , ev = event.split('.')[0] + , ns = event.split('.')[1] || '*' + + + // ensure valid object + SVG.listeners[index] = SVG.listeners[index] || {} + SVG.listeners[index][ev] = SVG.listeners[index][ev] || {} + SVG.listeners[index][ev][ns] = SVG.listeners[index][ev][ns] || {} + + if(!listener._svgjsListenerId) + listener._svgjsListenerId = ++SVG.listenerId + + // reference listener + SVG.listeners[index][ev][ns][listener._svgjsListenerId] = l + + // add listener + node.addEventListener(ev, l, options || false) +} + +// Add event unbinder in the SVG namespace +SVG.off = function(node, event, listener) { + var index = SVG.handlerMap.indexOf(node) + , ev = event && event.split('.')[0] + , ns = event && event.split('.')[1] + , namespace = '' + + if(index == -1) return + + if (listener) { + if(typeof listener == 'function') listener = listener._svgjsListenerId + if(!listener) return + + // remove listener reference + if (SVG.listeners[index][ev] && SVG.listeners[index][ev][ns || '*']) { + // remove listener + node.removeEventListener(ev, SVG.listeners[index][ev][ns || '*'][listener], false) + + delete SVG.listeners[index][ev][ns || '*'][listener] + } + + } else if (ns && ev) { + // remove all listeners for a namespaced event + if (SVG.listeners[index][ev] && SVG.listeners[index][ev][ns]) { + for (listener in SVG.listeners[index][ev][ns]) + SVG.off(node, [ev, ns].join('.'), listener) + + delete SVG.listeners[index][ev][ns] + } + + } else if (ns){ + // remove all listeners for a specific namespace + for(event in SVG.listeners[index]){ + for(namespace in SVG.listeners[index][event]){ + if(ns === namespace){ + SVG.off(node, [event, ns].join('.')) + } + } + } + + } else if (ev) { + // remove all listeners for the event + if (SVG.listeners[index][ev]) { + for (namespace in SVG.listeners[index][ev]) + SVG.off(node, [ev, namespace].join('.')) + + delete SVG.listeners[index][ev] + } + + } else { + // remove all listeners on a given node + for (event in SVG.listeners[index]) + SVG.off(node, event) + + delete SVG.listeners[index] + delete SVG.handlerMap[index] + + } +} + +// +SVG.extend(SVG.Element, { + // Bind given event to listener + on: function(event, listener, binding, options) { + SVG.on(this.node, event, listener, binding, options) + + return this + } + // Unbind event from listener +, off: function(event, listener) { + SVG.off(this.node, event, listener) + + return this + } + // Fire given event +, fire: function(event, data) { + + // Dispatch event + if(event instanceof window.Event){ + this.node.dispatchEvent(event) + }else{ + this.node.dispatchEvent(event = new window.CustomEvent(event, {detail:data, cancelable: true})) + } + + this._event = event + return this + } +, event: function() { + return this._event + } +}) + + +SVG.Defs = SVG.invent({ + // Initialize node + create: 'defs' + + // Inherit from +, inherit: SVG.Container + +}) +SVG.G = SVG.invent({ + // Initialize node + create: 'g' + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Move over x-axis + x: function(x) { + return x == null ? this.transform('x') : this.transform({ x: x - this.x() }, true) + } + // Move over y-axis + , y: function(y) { + return y == null ? this.transform('y') : this.transform({ y: y - this.y() }, true) + } + // Move by center over x-axis + , cx: function(x) { + return x == null ? this.gbox().cx : this.x(x - this.gbox().width / 2) + } + // Move by center over y-axis + , cy: function(y) { + return y == null ? this.gbox().cy : this.y(y - this.gbox().height / 2) + } + , gbox: function() { + + var bbox = this.bbox() + , trans = this.transform() + + bbox.x += trans.x + bbox.x2 += trans.x + bbox.cx += trans.x + + bbox.y += trans.y + bbox.y2 += trans.y + bbox.cy += trans.y + + return bbox + } + } + + // Add parent method +, construct: { + // Create a group element + group: function() { + return this.put(new SVG.G) + } + } +}) + +// ### This module adds backward / forward functionality to elements. + +// +SVG.extend(SVG.Element, { + // Get all siblings, including myself + siblings: function() { + return this.parent().children() + } + // Get the curent position siblings +, position: function() { + return this.parent().index(this) + } + // Get the next element (will return null if there is none) +, next: function() { + return this.siblings()[this.position() + 1] + } + // Get the next element (will return null if there is none) +, previous: function() { + return this.siblings()[this.position() - 1] + } + // Send given element one step forward +, forward: function() { + var i = this.position() + 1 + , p = this.parent() + + // move node one step forward + p.removeElement(this).add(this, i) + + // make sure defs node is always at the top + if (p instanceof SVG.Doc) + p.node.appendChild(p.defs().node) + + return this + } + // Send given element one step backward +, backward: function() { + var i = this.position() + + if (i > 0) + this.parent().removeElement(this).add(this, i - 1) + + return this + } + // Send given element all the way to the front +, front: function() { + var p = this.parent() + + // Move node forward + p.node.appendChild(this.node) + + // Make sure defs node is always at the top + if (p instanceof SVG.Doc) + p.node.appendChild(p.defs().node) + + return this + } + // Send given element all the way to the back +, back: function() { + if (this.position() > 0) + this.parent().removeElement(this).add(this, 0) + + return this + } + // Inserts a given element before the targeted element +, before: function(element) { + element.remove() + + var i = this.position() + + this.parent().add(element, i) + + return this + } + // Insters a given element after the targeted element +, after: function(element) { + element.remove() + + var i = this.position() + + this.parent().add(element, i + 1) + + return this + } + +}) +SVG.Mask = SVG.invent({ + // Initialize node + create: function() { + this.constructor.call(this, SVG.create('mask')) + + // keep references to masked elements + this.targets = [] + } + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Unmask all masked elements and remove itself + remove: function() { + // unmask all targets + for (var i = this.targets.length - 1; i >= 0; i--) + if (this.targets[i]) + this.targets[i].unmask() + this.targets = [] + + // remove mask from parent + this.parent().removeElement(this) + + return this + } + } + + // Add parent method +, construct: { + // Create masking element + mask: function() { + return this.defs().put(new SVG.Mask) + } + } +}) + + +SVG.extend(SVG.Element, { + // Distribute mask to svg element + maskWith: function(element) { + // use given mask or create a new one + this.masker = element instanceof SVG.Mask ? element : this.parent().mask().add(element) + + // store reverence on self in mask + this.masker.targets.push(this) + + // apply mask + return this.attr('mask', 'url("#' + this.masker.attr('id') + '")') + } + // Unmask element +, unmask: function() { + delete this.masker + return this.attr('mask', null) + } + +}) + +SVG.ClipPath = SVG.invent({ + // Initialize node + create: function() { + this.constructor.call(this, SVG.create('clipPath')) + + // keep references to clipped elements + this.targets = [] + } + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Unclip all clipped elements and remove itself + remove: function() { + // unclip all targets + for (var i = this.targets.length - 1; i >= 0; i--) + if (this.targets[i]) + this.targets[i].unclip() + this.targets = [] + + // remove clipPath from parent + this.parent().removeElement(this) + + return this + } + } + + // Add parent method +, construct: { + // Create clipping element + clip: function() { + return this.defs().put(new SVG.ClipPath) + } + } +}) + +// +SVG.extend(SVG.Element, { + // Distribute clipPath to svg element + clipWith: function(element) { + // use given clip or create a new one + this.clipper = element instanceof SVG.ClipPath ? element : this.parent().clip().add(element) + + // store reverence on self in mask + this.clipper.targets.push(this) + + // apply mask + return this.attr('clip-path', 'url("#' + this.clipper.attr('id') + '")') + } + // Unclip element +, unclip: function() { + delete this.clipper + return this.attr('clip-path', null) + } + +}) +SVG.Gradient = SVG.invent({ + // Initialize node + create: function(type) { + this.constructor.call(this, SVG.create(type + 'Gradient')) + + // store type + this.type = type + } + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Add a color stop + at: function(offset, color, opacity) { + return this.put(new SVG.Stop).update(offset, color, opacity) + } + // Update gradient + , update: function(block) { + // remove all stops + this.clear() + + // invoke passed block + if (typeof block == 'function') + block.call(this, this) + + return this + } + // Return the fill id + , fill: function() { + return 'url(#' + this.id() + ')' + } + // Alias string convertion to fill + , toString: function() { + return this.fill() + } + // custom attr to handle transform + , attr: function(a, b, c) { + if(a == 'transform') a = 'gradientTransform' + return SVG.Container.prototype.attr.call(this, a, b, c) + } + } + + // Add parent method +, construct: { + // Create gradient element in defs + gradient: function(type, block) { + return this.defs().gradient(type, block) + } + } +}) + +// Add animatable methods to both gradient and fx module +SVG.extend(SVG.Gradient, SVG.FX, { + // From position + from: function(x, y) { + return (this._target || this).type == 'radial' ? + this.attr({ fx: new SVG.Number(x), fy: new SVG.Number(y) }) : + this.attr({ x1: new SVG.Number(x), y1: new SVG.Number(y) }) + } + // To position +, to: function(x, y) { + return (this._target || this).type == 'radial' ? + this.attr({ cx: new SVG.Number(x), cy: new SVG.Number(y) }) : + this.attr({ x2: new SVG.Number(x), y2: new SVG.Number(y) }) + } +}) + +// Base gradient generation +SVG.extend(SVG.Defs, { + // define gradient + gradient: function(type, block) { + return this.put(new SVG.Gradient(type)).update(block) + } + +}) + +SVG.Stop = SVG.invent({ + // Initialize node + create: 'stop' + + // Inherit from +, inherit: SVG.Element + + // Add class methods +, extend: { + // add color stops + update: function(o) { + if (typeof o == 'number' || o instanceof SVG.Number) { + o = { + offset: arguments[0] + , color: arguments[1] + , opacity: arguments[2] + } + } + + // set attributes + if (o.opacity != null) this.attr('stop-opacity', o.opacity) + if (o.color != null) this.attr('stop-color', o.color) + if (o.offset != null) this.attr('offset', new SVG.Number(o.offset)) + + return this + } + } + +}) + +SVG.Pattern = SVG.invent({ + // Initialize node + create: 'pattern' + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Return the fill id + fill: function() { + return 'url(#' + this.id() + ')' + } + // Update pattern by rebuilding + , update: function(block) { + // remove content + this.clear() + + // invoke passed block + if (typeof block == 'function') + block.call(this, this) + + return this + } + // Alias string convertion to fill + , toString: function() { + return this.fill() + } + // custom attr to handle transform + , attr: function(a, b, c) { + if(a == 'transform') a = 'patternTransform' + return SVG.Container.prototype.attr.call(this, a, b, c) + } + + } + + // Add parent method +, construct: { + // Create pattern element in defs + pattern: function(width, height, block) { + return this.defs().pattern(width, height, block) + } + } +}) + +SVG.extend(SVG.Defs, { + // Define gradient + pattern: function(width, height, block) { + return this.put(new SVG.Pattern).update(block).attr({ + x: 0 + , y: 0 + , width: width + , height: height + , patternUnits: 'userSpaceOnUse' + }) + } + +}) +SVG.Doc = SVG.invent({ + // Initialize node + create: function(element) { + if (element) { + // ensure the presence of a dom element + element = typeof element == 'string' ? + document.getElementById(element) : + element + + // If the target is an svg element, use that element as the main wrapper. + // This allows svg.js to work with svg documents as well. + if (element.nodeName == 'svg') { + this.constructor.call(this, element) + } else { + this.constructor.call(this, SVG.create('svg')) + element.appendChild(this.node) + this.size('100%', '100%') + } + + // set svg element attributes and ensure defs node + this.namespace().defs() + } + } + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Add namespaces + namespace: function() { + return this + .attr({ xmlns: SVG.ns, version: '1.1' }) + .attr('xmlns:xlink', SVG.xlink, SVG.xmlns) + .attr('xmlns:svgjs', SVG.svgjs, SVG.xmlns) + } + // Creates and returns defs element + , defs: function() { + if (!this._defs) { + var defs + + // Find or create a defs element in this instance + if (defs = this.node.getElementsByTagName('defs')[0]) + this._defs = SVG.adopt(defs) + else + this._defs = new SVG.Defs + + // Make sure the defs node is at the end of the stack + this.node.appendChild(this._defs.node) + } + + return this._defs + } + // custom parent method + , parent: function() { + return this.node.parentNode.nodeName == '#document' ? null : this.node.parentNode + } + // Fix for possible sub-pixel offset. See: + // https://bugzilla.mozilla.org/show_bug.cgi?id=608812 + , spof: function(spof) { + var pos = this.node.getScreenCTM() + + if (pos) + this + .style('left', (-pos.e % 1) + 'px') + .style('top', (-pos.f % 1) + 'px') + + return this + } + + // Removes the doc from the DOM + , remove: function() { + if(this.parent()) { + this.parent().removeChild(this.node) + } + + return this + } + , clear: function() { + // remove children + while(this.node.hasChildNodes()) + this.node.removeChild(this.node.lastChild) + + // remove defs reference + delete this._defs + + // add back parser + if(!SVG.parser.draw.parentNode) + this.node.appendChild(SVG.parser.draw) + + return this + } + } + +}) + +SVG.Shape = SVG.invent({ + // Initialize node + create: function(element) { + this.constructor.call(this, element) + } + + // Inherit from +, inherit: SVG.Element + +}) + +SVG.Bare = SVG.invent({ + // Initialize + create: function(element, inherit) { + // construct element + this.constructor.call(this, SVG.create(element)) + + // inherit custom methods + if (inherit) + for (var method in inherit.prototype) + if (typeof inherit.prototype[method] === 'function') + this[method] = inherit.prototype[method] + } + + // Inherit from +, inherit: SVG.Element + + // Add methods +, extend: { + // Insert some plain text + words: function(text) { + // remove contents + while (this.node.hasChildNodes()) + this.node.removeChild(this.node.lastChild) + + // create text node + this.node.appendChild(document.createTextNode(text)) + + return this + } + } +}) + + +SVG.extend(SVG.Parent, { + // Create an element that is not described by SVG.js + element: function(element, inherit) { + return this.put(new SVG.Bare(element, inherit)) + } +}) + +SVG.Symbol = SVG.invent({ + // Initialize node + create: 'symbol' + + // Inherit from +, inherit: SVG.Container + +, construct: { + // create symbol + symbol: function() { + return this.put(new SVG.Symbol) + } + } +}) + +SVG.Use = SVG.invent({ + // Initialize node + create: 'use' + + // Inherit from +, inherit: SVG.Shape + + // Add class methods +, extend: { + // Use element as a reference + element: function(element, file) { + // Set lined element + return this.attr('href', (file || '') + '#' + element, SVG.xlink) + } + } + + // Add parent method +, construct: { + // Create a use element + use: function(element, file) { + return this.put(new SVG.Use).element(element, file) + } + } +}) +SVG.Rect = SVG.invent({ + // Initialize node + create: 'rect' + + // Inherit from +, inherit: SVG.Shape + + // Add parent method +, construct: { + // Create a rect element + rect: function(width, height) { + return this.put(new SVG.Rect()).size(width, height) + } + } +}) +SVG.Circle = SVG.invent({ + // Initialize node + create: 'circle' + + // Inherit from +, inherit: SVG.Shape + + // Add parent method +, construct: { + // Create circle element, based on ellipse + circle: function(size) { + return this.put(new SVG.Circle).rx(new SVG.Number(size).divide(2)).move(0, 0) + } + } +}) + +SVG.extend(SVG.Circle, SVG.FX, { + // Radius x value + rx: function(rx) { + return this.attr('r', rx) + } + // Alias radius x value +, ry: function(ry) { + return this.rx(ry) + } +}) + +SVG.Ellipse = SVG.invent({ + // Initialize node + create: 'ellipse' + + // Inherit from +, inherit: SVG.Shape + + // Add parent method +, construct: { + // Create an ellipse + ellipse: function(width, height) { + return this.put(new SVG.Ellipse).size(width, height).move(0, 0) + } + } +}) + +SVG.extend(SVG.Ellipse, SVG.Rect, SVG.FX, { + // Radius x value + rx: function(rx) { + return this.attr('rx', rx) + } + // Radius y value +, ry: function(ry) { + return this.attr('ry', ry) + } +}) + +// Add common method +SVG.extend(SVG.Circle, SVG.Ellipse, { + // Move over x-axis + x: function(x) { + return x == null ? this.cx() - this.rx() : this.cx(x + this.rx()) + } + // Move over y-axis + , y: function(y) { + return y == null ? this.cy() - this.ry() : this.cy(y + this.ry()) + } + // Move by center over x-axis + , cx: function(x) { + return x == null ? this.attr('cx') : this.attr('cx', x) + } + // Move by center over y-axis + , cy: function(y) { + return y == null ? this.attr('cy') : this.attr('cy', y) + } + // Set width of element + , width: function(width) { + return width == null ? this.rx() * 2 : this.rx(new SVG.Number(width).divide(2)) + } + // Set height of element + , height: function(height) { + return height == null ? this.ry() * 2 : this.ry(new SVG.Number(height).divide(2)) + } + // Custom size function + , size: function(width, height) { + var p = proportionalSize(this, width, height) + + return this + .rx(new SVG.Number(p.width).divide(2)) + .ry(new SVG.Number(p.height).divide(2)) + } +}) +SVG.Line = SVG.invent({ + // Initialize node + create: 'line' + + // Inherit from +, inherit: SVG.Shape + + // Add class methods +, extend: { + // Get array + array: function() { + return new SVG.PointArray([ + [ this.attr('x1'), this.attr('y1') ] + , [ this.attr('x2'), this.attr('y2') ] + ]) + } + // Overwrite native plot() method + , plot: function(x1, y1, x2, y2) { + if (x1 == null) + return this.array() + else if (typeof y1 !== 'undefined') + x1 = { x1: x1, y1: y1, x2: x2, y2: y2 } + else + x1 = new SVG.PointArray(x1).toLine() + + return this.attr(x1) + } + // Move by left top corner + , move: function(x, y) { + return this.attr(this.array().move(x, y).toLine()) + } + // Set element size to given width and height + , size: function(width, height) { + var p = proportionalSize(this, width, height) + + return this.attr(this.array().size(p.width, p.height).toLine()) + } + } + + // Add parent method +, construct: { + // Create a line element + line: function(x1, y1, x2, y2) { + // make sure plot is called as a setter + // x1 is not necessarily a number, it can also be an array, a string and a SVG.PointArray + return SVG.Line.prototype.plot.apply( + this.put(new SVG.Line) + , x1 != null ? [x1, y1, x2, y2] : [0, 0, 0, 0] + ) + } + } +}) + +SVG.Polyline = SVG.invent({ + // Initialize node + create: 'polyline' + + // Inherit from +, inherit: SVG.Shape + + // Add parent method +, construct: { + // Create a wrapped polyline element + polyline: function(p) { + // make sure plot is called as a setter + return this.put(new SVG.Polyline).plot(p || new SVG.PointArray) + } + } +}) + +SVG.Polygon = SVG.invent({ + // Initialize node + create: 'polygon' + + // Inherit from +, inherit: SVG.Shape + + // Add parent method +, construct: { + // Create a wrapped polygon element + polygon: function(p) { + // make sure plot is called as a setter + return this.put(new SVG.Polygon).plot(p || new SVG.PointArray) + } + } +}) + +// Add polygon-specific functions +SVG.extend(SVG.Polyline, SVG.Polygon, { + // Get array + array: function() { + return this._array || (this._array = new SVG.PointArray(this.attr('points'))) + } + // Plot new path +, plot: function(p) { + return (p == null) ? + this.array() : + this.clear().attr('points', typeof p == 'string' ? p : (this._array = new SVG.PointArray(p))) + } + // Clear array cache +, clear: function() { + delete this._array + return this + } + // Move by left top corner +, move: function(x, y) { + return this.attr('points', this.array().move(x, y)) + } + // Set element size to given width and height +, size: function(width, height) { + var p = proportionalSize(this, width, height) + + return this.attr('points', this.array().size(p.width, p.height)) + } + +}) + +// unify all point to point elements +SVG.extend(SVG.Line, SVG.Polyline, SVG.Polygon, { + // Define morphable array + morphArray: SVG.PointArray + // Move by left top corner over x-axis +, x: function(x) { + return x == null ? this.bbox().x : this.move(x, this.bbox().y) + } + // Move by left top corner over y-axis +, y: function(y) { + return y == null ? this.bbox().y : this.move(this.bbox().x, y) + } + // Set width of element +, width: function(width) { + var b = this.bbox() + + return width == null ? b.width : this.size(width, b.height) + } + // Set height of element +, height: function(height) { + var b = this.bbox() + + return height == null ? b.height : this.size(b.width, height) + } +}) +SVG.Path = SVG.invent({ + // Initialize node + create: 'path' + + // Inherit from +, inherit: SVG.Shape + + // Add class methods +, extend: { + // Define morphable array + morphArray: SVG.PathArray + // Get array + , array: function() { + return this._array || (this._array = new SVG.PathArray(this.attr('d'))) + } + // Plot new path + , plot: function(d) { + return (d == null) ? + this.array() : + this.clear().attr('d', typeof d == 'string' ? d : (this._array = new SVG.PathArray(d))) + } + // Clear array cache + , clear: function() { + delete this._array + return this + } + // Move by left top corner + , move: function(x, y) { + return this.attr('d', this.array().move(x, y)) + } + // Move by left top corner over x-axis + , x: function(x) { + return x == null ? this.bbox().x : this.move(x, this.bbox().y) + } + // Move by left top corner over y-axis + , y: function(y) { + return y == null ? this.bbox().y : this.move(this.bbox().x, y) + } + // Set element size to given width and height + , size: function(width, height) { + var p = proportionalSize(this, width, height) + + return this.attr('d', this.array().size(p.width, p.height)) + } + // Set width of element + , width: function(width) { + return width == null ? this.bbox().width : this.size(width, this.bbox().height) + } + // Set height of element + , height: function(height) { + return height == null ? this.bbox().height : this.size(this.bbox().width, height) + } + + } + + // Add parent method +, construct: { + // Create a wrapped path element + path: function(d) { + // make sure plot is called as a setter + return this.put(new SVG.Path).plot(d || new SVG.PathArray) + } + } +}) + +SVG.Image = SVG.invent({ + // Initialize node + create: 'image' + + // Inherit from +, inherit: SVG.Shape + + // Add class methods +, extend: { + // (re)load image + load: function(url) { + if (!url) return this + + var self = this + , img = new window.Image() + + // preload image + SVG.on(img, 'load', function() { + var p = self.parent(SVG.Pattern) + + if(p === null) return + + // ensure image size + if (self.width() == 0 && self.height() == 0) + self.size(img.width, img.height) + + // ensure pattern size if not set + if (p && p.width() == 0 && p.height() == 0) + p.size(self.width(), self.height()) + + // callback + if (typeof self._loaded === 'function') + self._loaded.call(self, { + width: img.width + , height: img.height + , ratio: img.width / img.height + , url: url + }) + }) + + SVG.on(img, 'error', function(e){ + if (typeof self._error === 'function'){ + self._error.call(self, e) + } + }) + + return this.attr('href', (img.src = this.src = url), SVG.xlink) + } + // Add loaded callback + , loaded: function(loaded) { + this._loaded = loaded + return this + } + + , error: function(error) { + this._error = error + return this + } + } + + // Add parent method +, construct: { + // create image element, load image and set its size + image: function(source, width, height) { + return this.put(new SVG.Image).load(source).size(width || 0, height || width || 0) + } + } + +}) +SVG.Text = SVG.invent({ + // Initialize node + create: function() { + this.constructor.call(this, SVG.create('text')) + + this.dom.leading = new SVG.Number(1.3) // store leading value for rebuilding + this._rebuild = true // enable automatic updating of dy values + this._build = false // disable build mode for adding multiple lines + + // set default font + this.attr('font-family', SVG.defaults.attrs['font-family']) + } + + // Inherit from +, inherit: SVG.Shape + + // Add class methods +, extend: { + // Move over x-axis + x: function(x) { + // act as getter + if (x == null) + return this.attr('x') + + return this.attr('x', x) + } + // Move over y-axis + , y: function(y) { + var oy = this.attr('y') + , o = typeof oy === 'number' ? oy - this.bbox().y : 0 + + // act as getter + if (y == null) + return typeof oy === 'number' ? oy - o : oy + + return this.attr('y', typeof y === 'number' ? y + o : y) + } + // Move center over x-axis + , cx: function(x) { + return x == null ? this.bbox().cx : this.x(x - this.bbox().width / 2) + } + // Move center over y-axis + , cy: function(y) { + return y == null ? this.bbox().cy : this.y(y - this.bbox().height / 2) + } + // Set the text content + , text: function(text) { + // act as getter + if (typeof text === 'undefined'){ + var text = '' + var children = this.node.childNodes + for(var i = 0, len = children.length; i < len; ++i){ + + // add newline if its not the first child and newLined is set to true + if(i != 0 && children[i].nodeType != 3 && SVG.adopt(children[i]).dom.newLined == true){ + text += '\n' + } + + // add content of this node + text += children[i].textContent + } + + return text + } + + // remove existing content + this.clear().build(true) + + if (typeof text === 'function') { + // call block + text.call(this, this) + + } else { + // store text and make sure text is not blank + text = text.split('\n') + + // build new lines + for (var i = 0, il = text.length; i < il; i++) + this.tspan(text[i]).newLine() + } + + // disable build mode and rebuild lines + return this.build(false).rebuild() + } + // Set font size + , size: function(size) { + return this.attr('font-size', size).rebuild() + } + // Set / get leading + , leading: function(value) { + // act as getter + if (value == null) + return this.dom.leading + + // act as setter + this.dom.leading = new SVG.Number(value) + + return this.rebuild() + } + // Get all the first level lines + , lines: function() { + var node = (this.textPath && this.textPath() || this).node + + // filter tspans and map them to SVG.js instances + var lines = SVG.utils.map(SVG.utils.filterSVGElements(node.childNodes), function(el){ + return SVG.adopt(el) + }) + + // return an instance of SVG.set + return new SVG.Set(lines) + } + // Rebuild appearance type + , rebuild: function(rebuild) { + // store new rebuild flag if given + if (typeof rebuild == 'boolean') + this._rebuild = rebuild + + // define position of all lines + if (this._rebuild) { + var self = this + , blankLineOffset = 0 + , dy = this.dom.leading * new SVG.Number(this.attr('font-size')) + + this.lines().each(function() { + if (this.dom.newLined) { + if (!self.textPath()) + this.attr('x', self.attr('x')) + if(this.text() == '\n') { + blankLineOffset += dy + }else{ + this.attr('dy', dy + blankLineOffset) + blankLineOffset = 0 + } + } + }) + + this.fire('rebuild') + } + + return this + } + // Enable / disable build mode + , build: function(build) { + this._build = !!build + return this + } + // overwrite method from parent to set data properly + , setData: function(o){ + this.dom = o + this.dom.leading = new SVG.Number(o.leading || 1.3) + return this + } + } + + // Add parent method +, construct: { + // Create text element + text: function(text) { + return this.put(new SVG.Text).text(text) + } + // Create plain text element + , plain: function(text) { + return this.put(new SVG.Text).plain(text) + } + } + +}) + +SVG.Tspan = SVG.invent({ + // Initialize node + create: 'tspan' + + // Inherit from +, inherit: SVG.Shape + + // Add class methods +, extend: { + // Set text content + text: function(text) { + if(text == null) return this.node.textContent + (this.dom.newLined ? '\n' : '') + + typeof text === 'function' ? text.call(this, this) : this.plain(text) + + return this + } + // Shortcut dx + , dx: function(dx) { + return this.attr('dx', dx) + } + // Shortcut dy + , dy: function(dy) { + return this.attr('dy', dy) + } + // Create new line + , newLine: function() { + // fetch text parent + var t = this.parent(SVG.Text) + + // mark new line + this.dom.newLined = true + + // apply new hy¡n + return this.dy(t.dom.leading * t.attr('font-size')).attr('x', t.x()) + } + } + +}) + +SVG.extend(SVG.Text, SVG.Tspan, { + // Create plain text node + plain: function(text) { + // clear if build mode is disabled + if (this._build === false) + this.clear() + + // create text node + this.node.appendChild(document.createTextNode(text)) + + return this + } + // Create a tspan +, tspan: function(text) { + var node = (this.textPath && this.textPath() || this).node + , tspan = new SVG.Tspan + + // clear if build mode is disabled + if (this._build === false) + this.clear() + + // add new tspan + node.appendChild(tspan.node) + + return tspan.text(text) + } + // Clear all lines +, clear: function() { + var node = (this.textPath && this.textPath() || this).node + + // remove existing child nodes + while (node.hasChildNodes()) + node.removeChild(node.lastChild) + + return this + } + // Get length of text element +, length: function() { + return this.node.getComputedTextLength() + } +}) + +SVG.TextPath = SVG.invent({ + // Initialize node + create: 'textPath' + + // Inherit from +, inherit: SVG.Parent + + // Define parent class +, parent: SVG.Text + + // Add parent method +, construct: { + // Create path for text to run on + path: function(d) { + // create textPath element + var path = new SVG.TextPath + , track = this.doc().defs().path(d) + + // move lines to textpath + while (this.node.hasChildNodes()) + path.node.appendChild(this.node.firstChild) + + // add textPath element as child node + this.node.appendChild(path.node) + + // link textPath to path and add content + path.attr('href', '#' + track, SVG.xlink) + + return this + } + // return the array of the path track element + , array: function() { + var track = this.track() + + return track ? track.array() : null + } + // Plot path if any + , plot: function(d) { + var track = this.track() + , pathArray = null + + if (track) { + pathArray = track.plot(d) + } + + return (d == null) ? pathArray : this + } + // Get the path track element + , track: function() { + var path = this.textPath() + + if (path) + return path.reference('href') + } + // Get the textPath child + , textPath: function() { + if (this.node.firstChild && this.node.firstChild.nodeName == 'textPath') + return SVG.adopt(this.node.firstChild) + } + } +}) + +SVG.Nested = SVG.invent({ + // Initialize node + create: function() { + this.constructor.call(this, SVG.create('svg')) + + this.style('overflow', 'visible') + } + + // Inherit from +, inherit: SVG.Container + + // Add parent method +, construct: { + // Create nested svg document + nested: function() { + return this.put(new SVG.Nested) + } + } +}) +SVG.A = SVG.invent({ + // Initialize node + create: 'a' + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Link url + to: function(url) { + return this.attr('href', url, SVG.xlink) + } + // Link show attribute + , show: function(target) { + return this.attr('show', target, SVG.xlink) + } + // Link target attribute + , target: function(target) { + return this.attr('target', target) + } + } + + // Add parent method +, construct: { + // Create a hyperlink element + link: function(url) { + return this.put(new SVG.A).to(url) + } + } +}) + +SVG.extend(SVG.Element, { + // Create a hyperlink element + linkTo: function(url) { + var link = new SVG.A + + if (typeof url == 'function') + url.call(link, link) + else + link.to(url) + + return this.parent().put(link).put(this) + } + +}) +SVG.Marker = SVG.invent({ + // Initialize node + create: 'marker' + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Set width of element + width: function(width) { + return this.attr('markerWidth', width) + } + // Set height of element + , height: function(height) { + return this.attr('markerHeight', height) + } + // Set marker refX and refY + , ref: function(x, y) { + return this.attr('refX', x).attr('refY', y) + } + // Update marker + , update: function(block) { + // remove all content + this.clear() + + // invoke passed block + if (typeof block == 'function') + block.call(this, this) + + return this + } + // Return the fill id + , toString: function() { + return 'url(#' + this.id() + ')' + } + } + + // Add parent method +, construct: { + marker: function(width, height, block) { + // Create marker element in defs + return this.defs().marker(width, height, block) + } + } + +}) + +SVG.extend(SVG.Defs, { + // Create marker + marker: function(width, height, block) { + // Set default viewbox to match the width and height, set ref to cx and cy and set orient to auto + return this.put(new SVG.Marker) + .size(width, height) + .ref(width / 2, height / 2) + .viewbox(0, 0, width, height) + .attr('orient', 'auto') + .update(block) + } + +}) + +SVG.extend(SVG.Line, SVG.Polyline, SVG.Polygon, SVG.Path, { + // Create and attach markers + marker: function(marker, width, height, block) { + var attr = ['marker'] + + // Build attribute name + if (marker != 'all') attr.push(marker) + attr = attr.join('-') + + // Set marker attribute + marker = arguments[1] instanceof SVG.Marker ? + arguments[1] : + this.doc().marker(width, height, block) + + return this.attr(attr, marker) + } + +}) +// Define list of available attributes for stroke and fill +var sugar = { + stroke: ['color', 'width', 'opacity', 'linecap', 'linejoin', 'miterlimit', 'dasharray', 'dashoffset'] +, fill: ['color', 'opacity', 'rule'] +, prefix: function(t, a) { + return a == 'color' ? t : t + '-' + a + } +} + +// Add sugar for fill and stroke +;['fill', 'stroke'].forEach(function(m) { + var i, extension = {} + + extension[m] = function(o) { + if (typeof o == 'undefined') + return this + if (typeof o == 'string' || SVG.Color.isRgb(o) || (o && typeof o.fill === 'function')) + this.attr(m, o) + + else + // set all attributes from sugar.fill and sugar.stroke list + for (i = sugar[m].length - 1; i >= 0; i--) + if (o[sugar[m][i]] != null) + this.attr(sugar.prefix(m, sugar[m][i]), o[sugar[m][i]]) + + return this + } + + SVG.extend(SVG.Element, SVG.FX, extension) + +}) + +SVG.extend(SVG.Element, SVG.FX, { + // Map rotation to transform + rotate: function(d, cx, cy) { + return this.transform({ rotation: d, cx: cx, cy: cy }) + } + // Map skew to transform +, skew: function(x, y, cx, cy) { + return arguments.length == 1 || arguments.length == 3 ? + this.transform({ skew: x, cx: y, cy: cx }) : + this.transform({ skewX: x, skewY: y, cx: cx, cy: cy }) + } + // Map scale to transform +, scale: function(x, y, cx, cy) { + return arguments.length == 1 || arguments.length == 3 ? + this.transform({ scale: x, cx: y, cy: cx }) : + this.transform({ scaleX: x, scaleY: y, cx: cx, cy: cy }) + } + // Map translate to transform +, translate: function(x, y) { + return this.transform({ x: x, y: y }) + } + // Map flip to transform +, flip: function(a, o) { + o = typeof a == 'number' ? a : o + return this.transform({ flip: a || 'both', offset: o }) + } + // Map matrix to transform +, matrix: function(m) { + return this.attr('transform', new SVG.Matrix(arguments.length == 6 ? [].slice.call(arguments) : m)) + } + // Opacity +, opacity: function(value) { + return this.attr('opacity', value) + } + // Relative move over x axis +, dx: function(x) { + return this.x(new SVG.Number(x).plus(this instanceof SVG.FX ? 0 : this.x()), true) + } + // Relative move over y axis +, dy: function(y) { + return this.y(new SVG.Number(y).plus(this instanceof SVG.FX ? 0 : this.y()), true) + } + // Relative move over x and y axes +, dmove: function(x, y) { + return this.dx(x).dy(y) + } +}) + +SVG.extend(SVG.Rect, SVG.Ellipse, SVG.Circle, SVG.Gradient, SVG.FX, { + // Add x and y radius + radius: function(x, y) { + var type = (this._target || this).type; + return type == 'radial' || type == 'circle' ? + this.attr('r', new SVG.Number(x)) : + this.rx(x).ry(y == null ? x : y) + } +}) + +SVG.extend(SVG.Path, { + // Get path length + length: function() { + return this.node.getTotalLength() + } + // Get point at length +, pointAt: function(length) { + return this.node.getPointAtLength(length) + } +}) + +SVG.extend(SVG.Parent, SVG.Text, SVG.Tspan, SVG.FX, { + // Set font + font: function(a, v) { + if (typeof a == 'object') { + for (v in a) this.font(v, a[v]) + } + + return a == 'leading' ? + this.leading(v) : + a == 'anchor' ? + this.attr('text-anchor', v) : + a == 'size' || a == 'family' || a == 'weight' || a == 'stretch' || a == 'variant' || a == 'style' ? + this.attr('font-'+ a, v) : + this.attr(a, v) + } +}) + +SVG.Set = SVG.invent({ + // Initialize + create: function(members) { + // Set initial state + Array.isArray(members) ? this.members = members : this.clear() + } + + // Add class methods +, extend: { + // Add element to set + add: function() { + var i, il, elements = [].slice.call(arguments) + + for (i = 0, il = elements.length; i < il; i++) + this.members.push(elements[i]) + + return this + } + // Remove element from set + , remove: function(element) { + var i = this.index(element) + + // remove given child + if (i > -1) + this.members.splice(i, 1) + + return this + } + // Iterate over all members + , each: function(block) { + for (var i = 0, il = this.members.length; i < il; i++) + block.apply(this.members[i], [i, this.members]) + + return this + } + // Restore to defaults + , clear: function() { + // initialize store + this.members = [] + + return this + } + // Get the length of a set + , length: function() { + return this.members.length + } + // Checks if a given element is present in set + , has: function(element) { + return this.index(element) >= 0 + } + // retuns index of given element in set + , index: function(element) { + return this.members.indexOf(element) + } + // Get member at given index + , get: function(i) { + return this.members[i] + } + // Get first member + , first: function() { + return this.get(0) + } + // Get last member + , last: function() { + return this.get(this.members.length - 1) + } + // Default value + , valueOf: function() { + return this.members + } + // Get the bounding box of all members included or empty box if set has no items + , bbox: function(){ + // return an empty box of there are no members + if (this.members.length == 0) + return new SVG.RBox() + + // get the first rbox and update the target bbox + var rbox = this.members[0].rbox(this.members[0].doc()) + + this.each(function() { + // user rbox for correct position and visual representation + rbox = rbox.merge(this.rbox(this.doc())) + }) + + return rbox + } + } + + // Add parent method +, construct: { + // Create a new set + set: function(members) { + return new SVG.Set(members) + } + } +}) + +SVG.FX.Set = SVG.invent({ + // Initialize node + create: function(set) { + // store reference to set + this.set = set + } + +}) + +// Alias methods +SVG.Set.inherit = function() { + var m + , methods = [] + + // gather shape methods + for(var m in SVG.Shape.prototype) + if (typeof SVG.Shape.prototype[m] == 'function' && typeof SVG.Set.prototype[m] != 'function') + methods.push(m) + + // apply shape aliasses + methods.forEach(function(method) { + SVG.Set.prototype[method] = function() { + for (var i = 0, il = this.members.length; i < il; i++) + if (this.members[i] && typeof this.members[i][method] == 'function') + this.members[i][method].apply(this.members[i], arguments) + + return method == 'animate' ? (this.fx || (this.fx = new SVG.FX.Set(this))) : this + } + }) + + // clear methods for the next round + methods = [] + + // gather fx methods + for(var m in SVG.FX.prototype) + if (typeof SVG.FX.prototype[m] == 'function' && typeof SVG.FX.Set.prototype[m] != 'function') + methods.push(m) + + // apply fx aliasses + methods.forEach(function(method) { + SVG.FX.Set.prototype[method] = function() { + for (var i = 0, il = this.set.members.length; i < il; i++) + this.set.members[i].fx[method].apply(this.set.members[i].fx, arguments) + + return this + } + }) +} + + + + +SVG.extend(SVG.Element, { + // Store data values on svg nodes + data: function(a, v, r) { + if (typeof a == 'object') { + for (v in a) + this.data(v, a[v]) + + } else if (arguments.length < 2) { + try { + return JSON.parse(this.attr('data-' + a)) + } catch(e) { + return this.attr('data-' + a) + } + + } else { + this.attr( + 'data-' + a + , v === null ? + null : + r === true || typeof v === 'string' || typeof v === 'number' ? + v : + JSON.stringify(v) + ) + } + + return this + } +}) +SVG.extend(SVG.Element, { + // Remember arbitrary data + remember: function(k, v) { + // remember every item in an object individually + if (typeof arguments[0] == 'object') + for (var v in k) + this.remember(v, k[v]) + + // retrieve memory + else if (arguments.length == 1) + return this.memory()[k] + + // store memory + else + this.memory()[k] = v + + return this + } + + // Erase a given memory +, forget: function() { + if (arguments.length == 0) + this._memory = {} + else + for (var i = arguments.length - 1; i >= 0; i--) + delete this.memory()[arguments[i]] + + return this + } + + // Initialize or return local memory object +, memory: function() { + return this._memory || (this._memory = {}) + } + +}) +// Method for getting an element by id +SVG.get = function(id) { + var node = document.getElementById(idFromReference(id) || id) + return SVG.adopt(node) +} + +// Select elements by query string +SVG.select = function(query, parent) { + return new SVG.Set( + SVG.utils.map((parent || document).querySelectorAll(query), function(node) { + return SVG.adopt(node) + }) + ) +} + +SVG.extend(SVG.Parent, { + // Scoped select method + select: function(query) { + return SVG.select(query, this.node) + } + +}) +function pathRegReplace(a, b, c, d) { + return c + d.replace(SVG.regex.dots, ' .') +} + +// creates deep clone of array +function array_clone(arr){ + var clone = arr.slice(0) + for(var i = clone.length; i--;){ + if(Array.isArray(clone[i])){ + clone[i] = array_clone(clone[i]) + } + } + return clone +} + +// tests if a given element is instance of an object +function is(el, obj){ + return el instanceof obj +} + +// tests if a given selector matches an element +function matches(el, selector) { + return (el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector).call(el, selector); +} + +// Convert dash-separated-string to camelCase +function camelCase(s) { + return s.toLowerCase().replace(/-(.)/g, function(m, g) { + return g.toUpperCase() + }) +} + +// Capitalize first letter of a string +function capitalize(s) { + return s.charAt(0).toUpperCase() + s.slice(1) +} + +// Ensure to six-based hex +function fullHex(hex) { + return hex.length == 4 ? + [ '#', + hex.substring(1, 2), hex.substring(1, 2) + , hex.substring(2, 3), hex.substring(2, 3) + , hex.substring(3, 4), hex.substring(3, 4) + ].join('') : hex +} + +// Component to hex value +function compToHex(comp) { + var hex = comp.toString(16) + return hex.length == 1 ? '0' + hex : hex +} + +// Calculate proportional width and height values when necessary +function proportionalSize(element, width, height) { + if (width == null || height == null) { + var box = element.bbox() + + if (width == null) + width = box.width / box.height * height + else if (height == null) + height = box.height / box.width * width + } + + return { + width: width + , height: height + } +} + +// Delta transform point +function deltaTransformPoint(matrix, x, y) { + return { + x: x * matrix.a + y * matrix.c + 0 + , y: x * matrix.b + y * matrix.d + 0 + } +} + +// Map matrix array to object +function arrayToMatrix(a) { + return { a: a[0], b: a[1], c: a[2], d: a[3], e: a[4], f: a[5] } +} + +// Parse matrix if required +function parseMatrix(matrix) { + if (!(matrix instanceof SVG.Matrix)) + matrix = new SVG.Matrix(matrix) + + return matrix +} + +// Add centre point to transform object +function ensureCentre(o, target) { + o.cx = o.cx == null ? target.bbox().cx : o.cx + o.cy = o.cy == null ? target.bbox().cy : o.cy +} + +// PathArray Helpers +function arrayToString(a) { + for (var i = 0, il = a.length, s = ''; i < il; i++) { + s += a[i][0] + + if (a[i][1] != null) { + s += a[i][1] + + if (a[i][2] != null) { + s += ' ' + s += a[i][2] + + if (a[i][3] != null) { + s += ' ' + s += a[i][3] + s += ' ' + s += a[i][4] + + if (a[i][5] != null) { + s += ' ' + s += a[i][5] + s += ' ' + s += a[i][6] + + if (a[i][7] != null) { + s += ' ' + s += a[i][7] + } + } + } + } + } + } + + return s + ' ' +} + +// Deep new id assignment +function assignNewId(node) { + // do the same for SVG child nodes as well + for (var i = node.childNodes.length - 1; i >= 0; i--) + if (node.childNodes[i] instanceof window.SVGElement) + assignNewId(node.childNodes[i]) + + return SVG.adopt(node).id(SVG.eid(node.nodeName)) +} + +// Add more bounding box properties +function fullBox(b) { + if (b.x == null) { + b.x = 0 + b.y = 0 + b.width = 0 + b.height = 0 + } + + b.w = b.width + b.h = b.height + b.x2 = b.x + b.width + b.y2 = b.y + b.height + b.cx = b.x + b.width / 2 + b.cy = b.y + b.height / 2 + + return b +} + +// Get id from reference string +function idFromReference(url) { + var m = url.toString().match(SVG.regex.reference) + + if (m) return m[1] +} + +// Create matrix array for looping +var abcdef = 'abcdef'.split('') +// Add CustomEvent to IE9 and IE10 +if (typeof window.CustomEvent !== 'function') { + // Code from: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent + var CustomEvent = function(event, options) { + options = options || { bubbles: false, cancelable: false, detail: undefined } + var e = document.createEvent('CustomEvent') + e.initCustomEvent(event, options.bubbles, options.cancelable, options.detail) + return e + } + + CustomEvent.prototype = window.Event.prototype + + window.CustomEvent = CustomEvent +} + +// requestAnimationFrame / cancelAnimationFrame Polyfill with fallback based on Paul Irish +(function(w) { + var lastTime = 0 + var vendors = ['moz', 'webkit'] + + for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + w.requestAnimationFrame = w[vendors[x] + 'RequestAnimationFrame'] + w.cancelAnimationFrame = w[vendors[x] + 'CancelAnimationFrame'] || + w[vendors[x] + 'CancelRequestAnimationFrame'] + } + + w.requestAnimationFrame = w.requestAnimationFrame || + function(callback) { + var currTime = new Date().getTime() + var timeToCall = Math.max(0, 16 - (currTime - lastTime)) + + var id = w.setTimeout(function() { + callback(currTime + timeToCall) + }, timeToCall) + + lastTime = currTime + timeToCall + return id + } + + w.cancelAnimationFrame = w.cancelAnimationFrame || w.clearTimeout; + +}(window)) + +return SVG + +})); \ No newline at end of file diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/libs/svg.js/dist/svg.min.js b/files/plugin-HeatmapSessionRecording-5.2.4/libs/svg.js/dist/svg.min.js new file mode 100644 index 0000000..29ce812 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/libs/svg.js/dist/svg.min.js @@ -0,0 +1,3 @@ +/*! svg.js v2.6.1 MIT*/;!function(t,e){"function"==typeof define&&define.amd?define(function(){return e(t,t.document)}):"object"==typeof exports?module.exports=t.document?e(t,t.document):function(t){return e(t,t.document)}:t.SVG=e(t,t.document)}("undefined"!=typeof window?window:this,function(t,e){function i(t,e,i,n){return i+n.replace(g.regex.dots," .")}function n(t){for(var e=t.slice(0),i=e.length;i--;)Array.isArray(e[i])&&(e[i]=n(e[i]));return e}function r(t,e){return t instanceof e}function s(t,e){return(t.matches||t.matchesSelector||t.msMatchesSelector||t.mozMatchesSelector||t.webkitMatchesSelector||t.oMatchesSelector).call(t,e)}function o(t){return t.toLowerCase().replace(/-(.)/g,function(t,e){return e.toUpperCase()})}function a(t){return t.charAt(0).toUpperCase()+t.slice(1)}function h(t){return 4==t.length?["#",t.substring(1,2),t.substring(1,2),t.substring(2,3),t.substring(2,3),t.substring(3,4),t.substring(3,4)].join(""):t}function u(t){var e=t.toString(16);return 1==e.length?"0"+e:e}function l(t,e,i){if(null==e||null==i){var n=t.bbox();null==e?e=n.width/n.height*i:null==i&&(i=n.height/n.width*e)}return{width:e,height:i}}function c(t,e,i){return{x:e*t.a+i*t.c+0,y:e*t.b+i*t.d+0}}function f(t){return{a:t[0],b:t[1],c:t[2],d:t[3],e:t[4],f:t[5]}}function d(t){return t instanceof g.Matrix||(t=new g.Matrix(t)),t}function p(t,e){t.cx=null==t.cx?e.bbox().cx:t.cx,t.cy=null==t.cy?e.bbox().cy:t.cy}function m(t){for(var e=0,i=t.length,n="";e=0;i--)e.childNodes[i]instanceof t.SVGElement&&x(e.childNodes[i]);return g.adopt(e).id(g.eid(e.nodeName))}function y(t){return null==t.x&&(t.x=0,t.y=0,t.width=0,t.height=0),t.w=t.width,t.h=t.height,t.x2=t.x+t.width,t.y2=t.y+t.height,t.cx=t.x+t.width/2,t.cy=t.y+t.height/2,t}function v(t){var e=t.toString().match(g.regex.reference);if(e)return e[1]}var g=this.SVG=function(t){if(g.supported)return t=new g.Doc(t),g.parser.draw||g.prepare(),t};if(g.ns="http://www.w3.org/2000/svg",g.xmlns="http://www.w3.org/2000/xmlns/",g.xlink="http://www.w3.org/1999/xlink",g.svgjs="http://svgjs.com/svgjs",g.supported=function(){return!!e.createElementNS&&!!e.createElementNS(g.ns,"svg").createSVGRect}(),!g.supported)return!1;g.did=1e3,g.eid=function(t){return"Svgjs"+a(t)+g.did++},g.create=function(t){var i=e.createElementNS(this.ns,t);return i.setAttribute("id",this.eid(t)),i},g.extend=function(){var t,e,i,n;for(t=[].slice.call(arguments),e=t.pop(),n=t.length-1;n>=0;n--)if(t[n])for(i in e)t[n].prototype[i]=e[i];g.Set&&g.Set.inherit&&g.Set.inherit()},g.invent=function(t){var e="function"==typeof t.create?t.create:function(){this.constructor.call(this,g.create(t.create))};return t.inherit&&(e.prototype=new t.inherit),t.extend&&g.extend(e,t.extend),t.construct&&g.extend(t.parent||g.Container,t.construct),e},g.adopt=function(e){if(!e)return null;if(e.instance)return e.instance;var i;return i="svg"==e.nodeName?e.parentNode instanceof t.SVGElement?new g.Nested:new g.Doc:"linearGradient"==e.nodeName?new g.Gradient("linear"):"radialGradient"==e.nodeName?new g.Gradient("radial"):g[a(e.nodeName)]?new(g[a(e.nodeName)]):new g.Element(e),i.type=e.nodeName,i.node=e,e.instance=i,i instanceof g.Doc&&i.namespace().defs(),i.setData(JSON.parse(e.getAttribute("svgjs:data"))||{}),i},g.prepare=function(){var t=e.getElementsByTagName("body")[0],i=(t?new g.Doc(t):g.adopt(e.documentElement).nested()).size(2,0);g.parser={body:t||e.documentElement,draw:i.style("opacity:0;position:absolute;left:-100%;top:-100%;overflow:hidden").node,poly:i.polyline().node,path:i.path().node,native:g.create("svg")}},g.parser={native:g.create("svg")},e.addEventListener("DOMContentLoaded",function(){g.parser.draw||g.prepare()},!1),g.regex={numberAndUnit:/^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i,hex:/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,rgb:/rgb\((\d+),(\d+),(\d+)\)/,reference:/#([a-z0-9\-_]+)/i,transforms:/\)\s*,?\s*/,whitespace:/\s/g,isHex:/^#[a-f0-9]{3,6}$/i,isRgb:/^rgb\(/,isCss:/[^:]+:[^;]+;?/,isBlank:/^(\s+)?$/,isNumber:/^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,isPercent:/^-?[\d\.]+%$/,isImage:/\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i,delimiter:/[\s,]+/,hyphen:/([^e])\-/gi,pathLetters:/[MLHVCSQTAZ]/gi,isPathLetter:/[MLHVCSQTAZ]/i,numbersWithDots:/((\d?\.\d+(?:e[+-]?\d+)?)((?:\.\d+(?:e[+-]?\d+)?)+))+/gi,dots:/\./g},g.utils={map:function(t,e){var i,n=t.length,r=[];for(i=0;i1?1:t,new g.Color({r:~~(this.r+(this.destination.r-this.r)*t),g:~~(this.g+(this.destination.g-this.g)*t),b:~~(this.b+(this.destination.b-this.b)*t)})):this}}),g.Color.test=function(t){return t+="",g.regex.isHex.test(t)||g.regex.isRgb.test(t)},g.Color.isRgb=function(t){return t&&"number"==typeof t.r&&"number"==typeof t.g&&"number"==typeof t.b},g.Color.isColor=function(t){return g.Color.isRgb(t)||g.Color.test(t)},g.Array=function(t,e){t=(t||[]).valueOf(),0==t.length&&e&&(t=e.valueOf()),this.value=this.parse(t)},g.extend(g.Array,{morph:function(t){if(this.destination=this.parse(t),this.value.length!=this.destination.length){for(var e=this.value[this.value.length-1],i=this.destination[this.destination.length-1];this.value.length>this.destination.length;)this.destination.push(i);for(;this.value.length=0;n--)this.value[n]=[this.value[n][0]+t,this.value[n][1]+e];return this},size:function(t,e){var i,n=this.bbox();for(i=this.value.length-1;i>=0;i--)n.width&&(this.value[i][0]=(this.value[i][0]-n.x)*t/n.width+n.x),n.height&&(this.value[i][1]=(this.value[i][1]-n.y)*e/n.height+n.y);return this},bbox:function(){return g.parser.poly.setAttribute("points",this.toString()),g.parser.poly.getBBox()}});for(var w={M:function(t,e,i){return e.x=i.x=t[0],e.y=i.y=t[1],["M",e.x,e.y]},L:function(t,e){return e.x=t[0],e.y=t[1],["L",t[0],t[1]]},H:function(t,e){return e.x=t[0],["H",t[0]]},V:function(t,e){return e.y=t[0],["V",t[0]]},C:function(t,e){return e.x=t[4],e.y=t[5],["C",t[0],t[1],t[2],t[3],t[4],t[5]]},S:function(t,e){return e.x=t[2],e.y=t[3],["S",t[0],t[1],t[2],t[3]]},Q:function(t,e){return e.x=t[2],e.y=t[3],["Q",t[0],t[1],t[2],t[3]]},T:function(t,e){return e.x=t[0],e.y=t[1],["T",t[0],t[1]]},Z:function(t,e,i){return e.x=i.x,e.y=i.y,["Z"]},A:function(t,e){return e.x=t[5],e.y=t[6],["A",t[0],t[1],t[2],t[3],t[4],t[5],t[6]]}},b="mlhvqtcsaz".split(""),C=0,N=b.length;C=0;r--)n=this.value[r][0],"M"==n||"L"==n||"T"==n?(this.value[r][1]+=t,this.value[r][2]+=e):"H"==n?this.value[r][1]+=t:"V"==n?this.value[r][1]+=e:"C"==n||"S"==n||"Q"==n?(this.value[r][1]+=t,this.value[r][2]+=e,this.value[r][3]+=t,this.value[r][4]+=e,"C"==n&&(this.value[r][5]+=t,this.value[r][6]+=e)):"A"==n&&(this.value[r][6]+=t,this.value[r][7]+=e);return this},size:function(t,e){var i,n,r=this.bbox();for(i=this.value.length-1;i>=0;i--)n=this.value[i][0],"M"==n||"L"==n||"T"==n?(this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x,this.value[i][2]=(this.value[i][2]-r.y)*e/r.height+r.y):"H"==n?this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x:"V"==n?this.value[i][1]=(this.value[i][1]-r.y)*e/r.height+r.y:"C"==n||"S"==n||"Q"==n?(this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x,this.value[i][2]=(this.value[i][2]-r.y)*e/r.height+r.y,this.value[i][3]=(this.value[i][3]-r.x)*t/r.width+r.x,this.value[i][4]=(this.value[i][4]-r.y)*e/r.height+r.y,"C"==n&&(this.value[i][5]=(this.value[i][5]-r.x)*t/r.width+r.x,this.value[i][6]=(this.value[i][6]-r.y)*e/r.height+r.y)):"A"==n&&(this.value[i][1]=this.value[i][1]*t/r.width,this.value[i][2]=this.value[i][2]*e/r.height,this.value[i][6]=(this.value[i][6]-r.x)*t/r.width+r.x,this.value[i][7]=(this.value[i][7]-r.y)*e/r.height+r.y);return this},equalCommands:function(t){var e,i,n;for(t=new g.PathArray(t),n=this.value.length===t.value.length,e=0,i=this.value.length;n&&ea);return n},bbox:function(){return g.parser.path.setAttribute("d",this.toString()),g.parser.path.getBBox()}}),g.Number=g.invent({create:function(t,e){this.value=0,this.unit=e||"","number"==typeof t?this.value=isNaN(t)?0:isFinite(t)?t:t<0?-3.4e38:3.4e38:"string"==typeof t?(e=t.match(g.regex.numberAndUnit),e&&(this.value=parseFloat(e[1]),"%"==e[5]?this.value/=100:"s"==e[5]&&(this.value*=1e3),this.unit=e[5])):t instanceof g.Number&&(this.value=t.valueOf(),this.unit=t.unit)},extend:{toString:function(){return("%"==this.unit?~~(1e8*this.value)/1e6:"s"==this.unit?this.value/1e3:this.value)+this.unit},toJSON:function(){return this.toString()},valueOf:function(){return this.value},plus:function(t){return t=new g.Number(t),new g.Number(this+t,this.unit||t.unit)},minus:function(t){return t=new g.Number(t),new g.Number(this-t,this.unit||t.unit)},times:function(t){return t=new g.Number(t),new g.Number(this*t,this.unit||t.unit)},divide:function(t){return t=new g.Number(t),new g.Number(this/t,this.unit||t.unit)},to:function(t){var e=new g.Number(this);return"string"==typeof t&&(e.unit=t),e},morph:function(t){return this.destination=new g.Number(t),t.relative&&(this.destination.value+=this.value),this},at:function(t){return this.destination?new g.Number(this.destination).minus(this).times(t).plus(this):this}}}),g.Element=g.invent({create:function(t){this._stroke=g.defaults.attrs.stroke,this._event=null,this.dom={},(this.node=t)&&(this.type=t.nodeName,this.node.instance=this,this._stroke=t.getAttribute("stroke")||this._stroke)},extend:{x:function(t){return this.attr("x",t)},y:function(t){return this.attr("y",t)},cx:function(t){return null==t?this.x()+this.width()/2:this.x(t-this.width()/2)},cy:function(t){return null==t?this.y()+this.height()/2:this.y(t-this.height()/2)},move:function(t,e){return this.x(t).y(e)},center:function(t,e){return this.cx(t).cy(e)},width:function(t){return this.attr("width",t)},height:function(t){return this.attr("height",t)},size:function(t,e){var i=l(this,t,e);return this.width(new g.Number(i.width)).height(new g.Number(i.height))},clone:function(t,e){this.writeDataToDom();var i=x(this.node.cloneNode(!0));return t?t.add(i):this.after(i),i},remove:function(){return this.parent()&&this.parent().removeElement(this),this},replace:function(t){return this.after(t).remove(),t},addTo:function(t){return t.put(this)},putIn:function(t){return t.add(this)},id:function(t){return this.attr("id",t)},inside:function(t,e){var i=this.bbox();return t>i.x&&e>i.y&&t/,"").replace(/<\/svg>$/,"");i.innerHTML=""+t.replace(/\n/,"").replace(/<(\w+)([^<]+?)\/>/g,"<$1$2>")+"";for(var n=0,r=i.firstChild.childNodes.length;n":function(t){return-Math.cos(t*Math.PI)/2+.5},">":function(t){return Math.sin(t*Math.PI/2)},"<":function(t){return-Math.cos(t*Math.PI/2)+1}},g.morph=function(t){return function(e,i){return new g.MorphObj(e,i).at(t)}},g.Situation=g.invent({create:function(t){this.init=!1,this.reversed=!1,this.reversing=!1,this.duration=new g.Number(t.duration).valueOf(),this.delay=new g.Number(t.delay).valueOf(),this.start=+new Date+this.delay,this.finish=this.start+this.duration,this.ease=t.ease,this.loop=0,this.loops=!1,this.animations={},this.attrs={},this.styles={},this.transforms=[],this.once={}}}),g.FX=g.invent({create:function(t){this._target=t,this.situations=[],this.active=!1,this.situation=null,this.paused=!1,this.lastPos=0,this.pos=0,this.absPos=0,this._speed=1},extend:{animate:function(t,e,i){"object"==typeof t&&(e=t.ease,i=t.delay,t=t.duration);var n=new g.Situation({duration:t||1e3,delay:i||0,ease:g.easing[e||"-"]||e});return this.queue(n),this},delay:function(t){var e=new g.Situation({duration:t,delay:0,ease:g.easing["-"]});return this.queue(e)},target:function(t){return t&&t instanceof g.Element?(this._target=t,this):this._target},timeToAbsPos:function(t){return(t-this.situation.start)/(this.situation.duration/this._speed)},absPosToTime:function(t){return this.situation.duration/this._speed*t+this.situation.start},startAnimFrame:function(){this.stopAnimFrame(),this.animationFrame=t.requestAnimationFrame(function(){this.step()}.bind(this))},stopAnimFrame:function(){t.cancelAnimationFrame(this.animationFrame)},start:function(){return!this.active&&this.situation&&(this.active=!0,this.startCurrent()),this},startCurrent:function(){return this.situation.start=+new Date+this.situation.delay/this._speed,this.situation.finish=this.situation.start+this.situation.duration/this._speed,this.initAnimations().step()},queue:function(t){return("function"==typeof t||t instanceof g.Situation)&&this.situations.push(t),this.situation||(this.situation=this.situations.shift()),this},dequeue:function(){return this.stop(),this.situation=this.situations.shift(),this.situation&&(this.situation instanceof g.Situation?this.start():this.situation.call(this)),this},initAnimations:function(){var t,e,i=this.situation;if(i.init)return this;for(t in i.animations)e=this.target()[t](),i.animations[t]instanceof g.Number&&(e=new g.Number(e)),i.animations[t]=e.morph(i.animations[t]);for(t in i.attrs)i.attrs[t]=new g.MorphObj(this.target().attr(t),i.attrs[t]);for(t in i.styles)i.styles[t]=new g.MorphObj(this.target().style(t),i.styles[t]);return i.initialTransformation=this.target().matrixify(),i.init=!0,this},clearQueue:function(){return this.situations=[],this},clearCurrent:function(){return this.situation=null,this},stop:function(t,e){var i=this.active;return this.active=!1,e&&this.clearQueue(),t&&this.situation&&(!i&&this.startCurrent(),this.atEnd()),this.stopAnimFrame(),this.clearCurrent()},reset:function(){if(this.situation){var t=this.situation;this.stop(),this.situation=t,this.atStart()}return this},finish:function(){for(this.stop(!0,!1);this.dequeue().situation&&this.stop(!0,!1););return this.clearQueue().clearCurrent(),this},atStart:function(){return this.at(0,!0)},atEnd:function(){return this.situation.loops===!0&&(this.situation.loops=this.situation.loop+1),"number"==typeof this.situation.loops?this.at(this.situation.loops,!0):this.at(1,!0)},at:function(t,e){var i=this.situation.duration/this._speed;return this.absPos=t,e||(this.situation.reversed&&(this.absPos=1-this.absPos),this.absPos+=this.situation.loop),this.situation.start=+new Date-this.absPos*i,this.situation.finish=this.situation.start+i,this.step(!0)},speed:function(t){return 0===t?this.pause():t?(this._speed=t,this.at(this.absPos,!0)):this._speed},loop:function(t,e){var i=this.last();return i.loops=null==t||t,i.loop=0,e&&(i.reversing=!0),this},pause:function(){return this.paused=!0,this.stopAnimFrame(),this},play:function(){return this.paused?(this.paused=!1,this.at(this.absPos,!0)):this},reverse:function(t){var e=this.last();return"undefined"==typeof t?e.reversed=!e.reversed:e.reversed=t,this},progress:function(t){return t?this.situation.ease(this.pos):this.pos},after:function(t){var e=this.last(),i=function i(n){n.detail.situation==e&&(t.call(this,e),this.off("finished.fx",i))};return this.target().on("finished.fx",i),this._callStart()},during:function(t){var e=this.last(),i=function(i){i.detail.situation==e&&t.call(this,i.detail.pos,g.morph(i.detail.pos),i.detail.eased,e)};return this.target().off("during.fx",i).on("during.fx",i),this.after(function(){this.off("during.fx",i)}),this._callStart()},afterAll:function(t){var e=function e(i){t.call(this),this.off("allfinished.fx",e)};return this.target().off("allfinished.fx",e).on("allfinished.fx",e),this._callStart()},duringAll:function(t){var e=function(e){t.call(this,e.detail.pos,g.morph(e.detail.pos),e.detail.eased,e.detail.situation)};return this.target().off("during.fx",e).on("during.fx",e),this.afterAll(function(){this.off("during.fx",e)}),this._callStart()},last:function(){return this.situations.length?this.situations[this.situations.length-1]:this.situation},add:function(t,e,i){return this.last()[i||"animations"][t]=e,this._callStart()},step:function(t){if(t||(this.absPos=this.timeToAbsPos(+new Date)),this.situation.loops!==!1){var e,i,n;e=Math.max(this.absPos,0),i=Math.floor(e),this.situation.loops===!0||ithis.lastPos&&s<=r&&(this.situation.once[s].call(this.target(),this.pos,r),delete this.situation.once[s]);return this.active&&this.target().fire("during",{pos:this.pos,eased:r,fx:this,situation:this.situation}),this.situation?(this.eachAt(),1==this.pos&&!this.situation.reversed||this.situation.reversed&&0==this.pos?(this.stopAnimFrame(),this.target().fire("finished",{fx:this,situation:this.situation}),this.situations.length||(this.target().fire("allfinished"),this.target().off(".fx"),this.active=!1),this.active?this.dequeue():this.clearCurrent()):!this.paused&&this.active&&this.startAnimFrame(),this.lastPos=r,this):this},eachAt:function(){var t,e,i,n=this,r=this.target(),s=this.situation;for(t in s.animations)i=[].concat(s.animations[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(n.pos),n.pos):t}),r[t].apply(r,i);for(t in s.attrs)i=[t].concat(s.attrs[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(n.pos),n.pos):t}),r.attr.apply(r,i);for(t in s.styles)i=[t].concat(s.styles[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(n.pos),n.pos):t}),r.style.apply(r,i);if(s.transforms.length){for(i=s.initialTransformation,t=0,e=s.transforms.length;t1?[].slice.call(arguments):arguments[0])},leading:function(t){return this.target().leading?this.add("leading",new g.Number(t)):this},viewbox:function(t,e,i,n){return this.target()instanceof g.Container&&this.add("viewbox",new g.ViewBox(t,e,i,n)),this},update:function(t){if(this.target()instanceof g.Stop){if("number"==typeof t||t instanceof g.Number)return this.update({offset:arguments[0],color:arguments[1],opacity:arguments[2]});null!=t.opacity&&this.attr("stop-opacity",t.opacity),null!=t.color&&this.attr("stop-color",t.color),null!=t.offset&&this.attr("offset",t.offset)}return this}}),g.Box=g.invent({create:function(t,e,i,n){return"object"!=typeof t||t instanceof g.Element?(4==arguments.length&&(this.x=t,this.y=e,this.width=i,this.height=n),void y(this)):g.Box.call(this,null!=t.left?t.left:t.x,null!=t.top?t.top:t.y,t.width,t.height)},extend:{merge:function(t){var e=new this.constructor;return e.x=Math.min(this.x,t.x),e.y=Math.min(this.y,t.y),e.width=Math.max(this.x+this.width,t.x+t.width)-e.x,e.height=Math.max(this.y+this.height,t.y+t.height)-e.y,y(e)},transform:function(t){var e,i=1/0,n=-(1/0),r=1/0,s=-(1/0),o=[new g.Point(this.x,this.y),new g.Point(this.x2,this.y),new g.Point(this.x,this.y2),new g.Point(this.x2,this.y2)];return o.forEach(function(e){e=e.transform(t),i=Math.min(i,e.x),n=Math.max(n,e.x),r=Math.min(r,e.y),s=Math.max(s,e.y)}),e=new this.constructor,e.x=i,e.width=n-i,e.y=r,e.height=s-r,y(e),e}}}),g.BBox=g.invent({create:function(t){if(g.Box.apply(this,[].slice.call(arguments)),t instanceof g.Element){var i;try{if(e.documentElement.contains){if(!e.documentElement.contains(t.node))throw new Exception("Element not in the dom")}else{for(var n=t.node;n.parentNode;)n=n.parentNode;if(n!=e)throw new Exception("Element not in the dom")}i=t.node.getBBox()}catch(e){if(t instanceof g.Shape){var r=t.clone(g.parser.draw.instance).show();i=r.node.getBBox(),r.remove()}else i={x:t.node.clientLeft,y:t.node.clientTop,width:t.node.clientWidth,height:t.node.clientHeight}}g.Box.call(this,i)}},inherit:g.Box,parent:g.Element,construct:{bbox:function(){return new g.BBox(this)}}}),g.BBox.prototype.constructor=g.BBox,g.extend(g.Element,{tbox:function(){return console.warn("Use of TBox is deprecated and mapped to RBox. Use .rbox() instead."),this.rbox(this.doc())}}),g.RBox=g.invent({create:function(t){g.Box.apply(this,[].slice.call(arguments)),t instanceof g.Element&&g.Box.call(this,t.node.getBoundingClientRect())},inherit:g.Box,parent:g.Element,extend:{addOffset:function(){return this.x+=t.pageXOffset,this.y+=t.pageYOffset,this}},construct:{rbox:function(t){return t?new g.RBox(this).transform(t.screenCTM().inverse()):new g.RBox(this).addOffset()}}}),g.RBox.prototype.constructor=g.RBox,g.Matrix=g.invent({create:function(t){var e,i=f([1,0,0,1,0,0]);for(t=t instanceof g.Element?t.matrixify():"string"==typeof t?f(t.split(g.regex.delimiter).map(parseFloat)):6==arguments.length?f([].slice.call(arguments)):Array.isArray(t)?f(t):"object"==typeof t?t:i,e=P.length-1;e>=0;--e)this[P[e]]=t&&"number"==typeof t[P[e]]?t[P[e]]:i[P[e]]},extend:{extract:function(){var t=c(this,0,1),e=c(this,1,0),i=180/Math.PI*Math.atan2(t.y,t.x)-90;return{x:this.e,y:this.f,transformedX:(this.e*Math.cos(i*Math.PI/180)+this.f*Math.sin(i*Math.PI/180))/Math.sqrt(this.a*this.a+this.b*this.b),transformedY:(this.f*Math.cos(i*Math.PI/180)+this.e*Math.sin(-i*Math.PI/180))/Math.sqrt(this.c*this.c+this.d*this.d),skewX:-i,skewY:180/Math.PI*Math.atan2(e.y,e.x),scaleX:Math.sqrt(this.a*this.a+this.b*this.b),scaleY:Math.sqrt(this.c*this.c+this.d*this.d),rotation:i,a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f,matrix:new g.Matrix(this)}},clone:function(){return new g.Matrix(this)},morph:function(t){return this.destination=new g.Matrix(t),this},at:function(t){if(!this.destination)return this;var e=new g.Matrix({a:this.a+(this.destination.a-this.a)*t,b:this.b+(this.destination.b-this.b)*t,c:this.c+(this.destination.c-this.c)*t,d:this.d+(this.destination.d-this.d)*t,e:this.e+(this.destination.e-this.e)*t,f:this.f+(this.destination.f-this.f)*t});return e},multiply:function(t){return new g.Matrix(this.native().multiply(d(t).native()))},inverse:function(){return new g.Matrix(this.native().inverse())},translate:function(t,e){return new g.Matrix(this.native().translate(t||0,e||0))},scale:function(t,e,i,n){return 1==arguments.length?e=t:3==arguments.length&&(n=i,i=e,e=t),this.around(i,n,new g.Matrix(t,0,0,e,0,0))},rotate:function(t,e,i){return t=g.utils.radians(t),this.around(e,i,new g.Matrix(Math.cos(t),Math.sin(t),-Math.sin(t),Math.cos(t),0,0))},flip:function(t,e){return"x"==t?this.scale(-1,1,e,0):"y"==t?this.scale(1,-1,0,e):this.scale(-1,-1,t,null!=e?e:t)},skew:function(t,e,i,n){return 1==arguments.length?e=t:3==arguments.length&&(n=i,i=e,e=t),t=g.utils.radians(t),e=g.utils.radians(e),this.around(i,n,new g.Matrix(1,Math.tan(e),Math.tan(t),1,0,0))},skewX:function(t,e,i){return this.skew(t,0,e,i)},skewY:function(t,e,i){return this.skew(0,t,e,i)},around:function(t,e,i){return this.multiply(new g.Matrix(1,0,0,1,t||0,e||0)).multiply(i).multiply(new g.Matrix(1,0,0,1,-t||0,-e||0))},native:function(){for(var t=g.parser.native.createSVGMatrix(),e=P.length-1;e>=0;e--)t[P[e]]=this[P[e]];return t},toString:function(){return"matrix("+this.a+","+this.b+","+this.c+","+this.d+","+this.e+","+this.f+")"}},parent:g.Element,construct:{ctm:function(){return new g.Matrix(this.node.getCTM()); +},screenCTM:function(){if(this instanceof g.Nested){var t=this.rect(1,1),e=t.node.getScreenCTM();return t.remove(),new g.Matrix(e)}return new g.Matrix(this.node.getScreenCTM())}}}),g.Point=g.invent({create:function(t,e){var i,n={x:0,y:0};i=Array.isArray(t)?{x:t[0],y:t[1]}:"object"==typeof t?{x:t.x,y:t.y}:null!=t?{x:t,y:null!=e?e:t}:n,this.x=i.x,this.y=i.y},extend:{clone:function(){return new g.Point(this)},morph:function(t,e){return this.destination=new g.Point(t,e),this},at:function(t){if(!this.destination)return this;var e=new g.Point({x:this.x+(this.destination.x-this.x)*t,y:this.y+(this.destination.y-this.y)*t});return e},native:function(){var t=g.parser.native.createSVGPoint();return t.x=this.x,t.y=this.y,t},transform:function(t){return new g.Point(this.native().matrixTransform(t.native()))}}}),g.extend(g.Element,{point:function(t,e){return new g.Point(t,e).transform(this.screenCTM().inverse())}}),g.extend(g.Element,{attr:function(t,e,i){if(null==t){for(t={},e=this.node.attributes,i=e.length-1;i>=0;i--)t[e[i].nodeName]=g.regex.isNumber.test(e[i].nodeValue)?parseFloat(e[i].nodeValue):e[i].nodeValue;return t}if("object"==typeof t)for(e in t)this.attr(e,t[e]);else if(null===e)this.node.removeAttribute(t);else{if(null==e)return e=this.node.getAttribute(t),null==e?g.defaults.attrs[t]:g.regex.isNumber.test(e)?parseFloat(e):e;"stroke-width"==t?this.attr("stroke",parseFloat(e)>0?this._stroke:null):"stroke"==t&&(this._stroke=e),"fill"!=t&&"stroke"!=t||(g.regex.isImage.test(e)&&(e=this.doc().defs().image(e,0,0)),e instanceof g.Image&&(e=this.doc().defs().pattern(0,0,function(){this.add(e)}))),"number"==typeof e?e=new g.Number(e):g.Color.isColor(e)?e=new g.Color(e):Array.isArray(e)&&(e=new g.Array(e)),"leading"==t?this.leading&&this.leading(e):"string"==typeof i?this.node.setAttributeNS(i,t,e.toString()):this.node.setAttribute(t,e.toString()),!this.rebuild||"font-size"!=t&&"x"!=t||this.rebuild(t,e)}return this}}),g.extend(g.Element,{transform:function(t,e){var i,n,r=this;if("object"!=typeof t)return i=new g.Matrix(r).extract(),"string"==typeof t?i[t]:i;if(i=new g.Matrix(r),e=!!e||!!t.relative,null!=t.a)i=e?i.multiply(new g.Matrix(t)):new g.Matrix(t);else if(null!=t.rotation)p(t,r),i=e?i.rotate(t.rotation,t.cx,t.cy):i.rotate(t.rotation-i.extract().rotation,t.cx,t.cy);else if(null!=t.scale||null!=t.scaleX||null!=t.scaleY){if(p(t,r),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,!e){var s=i.extract();t.scaleX=1*t.scaleX/s.scaleX,t.scaleY=1*t.scaleY/s.scaleY}i=i.scale(t.scaleX,t.scaleY,t.cx,t.cy)}else if(null!=t.skew||null!=t.skewX||null!=t.skewY){if(p(t,r),t.skewX=null!=t.skew?t.skew:null!=t.skewX?t.skewX:0,t.skewY=null!=t.skew?t.skew:null!=t.skewY?t.skewY:0,!e){var s=i.extract();i=i.multiply((new g.Matrix).skew(s.skewX,s.skewY,t.cx,t.cy).inverse())}i=i.skew(t.skewX,t.skewY,t.cx,t.cy)}else t.flip?("x"==t.flip||"y"==t.flip?t.offset=null==t.offset?r.bbox()["c"+t.flip]:t.offset:null==t.offset?(n=r.bbox(),t.flip=n.cx,t.offset=n.cy):t.flip=t.offset,i=(new g.Matrix).flip(t.flip,t.offset)):null==t.x&&null==t.y||(e?i=i.translate(t.x,t.y):(null!=t.x&&(i.e=t.x),null!=t.y&&(i.f=t.y)));return this.attr("transform",i)}}),g.extend(g.FX,{transform:function(t,e){var i,n,r=this.target();return"object"!=typeof t?(i=new g.Matrix(r).extract(),"string"==typeof t?i[t]:i):(e=!!e||!!t.relative,null!=t.a?i=new g.Matrix(t):null!=t.rotation?(p(t,r),i=new g.Rotate(t.rotation,t.cx,t.cy)):null!=t.scale||null!=t.scaleX||null!=t.scaleY?(p(t,r),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,i=new g.Scale(t.scaleX,t.scaleY,t.cx,t.cy)):null!=t.skewX||null!=t.skewY?(p(t,r),t.skewX=null!=t.skewX?t.skewX:0,t.skewY=null!=t.skewY?t.skewY:0,i=new g.Skew(t.skewX,t.skewY,t.cx,t.cy)):t.flip?("x"==t.flip||"y"==t.flip?t.offset=null==t.offset?r.bbox()["c"+t.flip]:t.offset:null==t.offset?(n=r.bbox(),t.flip=n.cx,t.offset=n.cy):t.flip=t.offset,i=(new g.Matrix).flip(t.flip,t.offset)):null==t.x&&null==t.y||(i=new g.Translate(t.x,t.y)),i?(i.relative=e,this.last().transforms.push(i),this._callStart()):this)}}),g.extend(g.Element,{untransform:function(){return this.attr("transform",null)},matrixify:function(){var t=(this.attr("transform")||"").split(g.regex.transforms).slice(0,-1).map(function(t){var e=t.trim().split("(");return[e[0],e[1].split(g.regex.delimiter).map(function(t){return parseFloat(t)})]}).reduce(function(t,e){return"matrix"==e[0]?t.multiply(f(e[1])):t[e[0]].apply(t,e[1])},new g.Matrix);return t},toParent:function(t){if(this==t)return this;var e=this.screenCTM(),i=t.screenCTM().inverse();return this.addTo(t).untransform().transform(i.multiply(e)),this},toDoc:function(){return this.toParent(this.doc())}}),g.Transformation=g.invent({create:function(t,e){if(arguments.length>1&&"boolean"!=typeof e)return this.constructor.call(this,[].slice.call(arguments));if(Array.isArray(t))for(var i=0,n=this.arguments.length;i=0},index:function(t){return[].slice.call(this.node.childNodes).indexOf(t.node)},get:function(t){return g.adopt(this.node.childNodes[t])},first:function(){return this.get(0)},last:function(){return this.get(this.node.childNodes.length-1)},each:function(t,e){var i,n,r=this.children();for(i=0,n=r.length;in/r?this.height/r:this.width/n,this.x=e,this.y=i,this.width=n,this.height=r)}else t="string"==typeof t?t.match(f).map(function(t){return parseFloat(t)}):Array.isArray(t)?t:"object"==typeof t?[t.x,t.y,t.width,t.height]:4==arguments.length?[].slice.call(arguments):u,this.x=t[0],this.y=t[1],this.width=t[2],this.height=t[3]},extend:{toString:function(){return this.x+" "+this.y+" "+this.width+" "+this.height},morph:function(t,e,i,n){return this.destination=new g.ViewBox(t,e,i,n),this},at:function(t){return this.destination?new g.ViewBox([this.x+(this.destination.x-this.x)*t,this.y+(this.destination.y-this.y)*t,this.width+(this.destination.width-this.width)*t,this.height+(this.destination.height-this.height)*t]):this}},parent:g.Container,construct:{viewbox:function(t,e,i,n){return 0==arguments.length?new g.ViewBox(this):this.attr("viewBox",new g.ViewBox(t,e,i,n))}}}),["click","dblclick","mousedown","mouseup","mouseover","mouseout","mousemove","touchstart","touchmove","touchleave","touchend","touchcancel"].forEach(function(t){g.Element.prototype[t]=function(e){return g.on(this.node,t,e),this}}),g.listeners=[],g.handlerMap=[],g.listenerId=0,g.on=function(t,e,i,n,r){var s=i.bind(n||t.instance||t),o=(g.handlerMap.indexOf(t)+1||g.handlerMap.push(t))-1,a=e.split(".")[0],h=e.split(".")[1]||"*";g.listeners[o]=g.listeners[o]||{},g.listeners[o][a]=g.listeners[o][a]||{},g.listeners[o][a][h]=g.listeners[o][a][h]||{},i._svgjsListenerId||(i._svgjsListenerId=++g.listenerId),g.listeners[o][a][h][i._svgjsListenerId]=s,t.addEventListener(a,s,r||!1)},g.off=function(t,e,i){var n=g.handlerMap.indexOf(t),r=e&&e.split(".")[0],s=e&&e.split(".")[1],o="";if(n!=-1)if(i){if("function"==typeof i&&(i=i._svgjsListenerId),!i)return;g.listeners[n][r]&&g.listeners[n][r][s||"*"]&&(t.removeEventListener(r,g.listeners[n][r][s||"*"][i],!1),delete g.listeners[n][r][s||"*"][i])}else if(s&&r){if(g.listeners[n][r]&&g.listeners[n][r][s]){for(i in g.listeners[n][r][s])g.off(t,[r,s].join("."),i);delete g.listeners[n][r][s]}}else if(s)for(e in g.listeners[n])for(o in g.listeners[n][e])s===o&&g.off(t,[e,s].join("."));else if(r){if(g.listeners[n][r]){for(o in g.listeners[n][r])g.off(t,[r,o].join("."));delete g.listeners[n][r]}}else{for(e in g.listeners[n])g.off(t,e);delete g.listeners[n],delete g.handlerMap[n]}},g.extend(g.Element,{on:function(t,e,i,n){return g.on(this.node,t,e,i,n),this},off:function(t,e){return g.off(this.node,t,e),this},fire:function(e,i){return e instanceof t.Event?this.node.dispatchEvent(e):this.node.dispatchEvent(e=new t.CustomEvent(e,{detail:i,cancelable:!0})),this._event=e,this},event:function(){return this._event}}),g.Defs=g.invent({create:"defs",inherit:g.Container}),g.G=g.invent({create:"g",inherit:g.Container,extend:{x:function(t){return null==t?this.transform("x"):this.transform({x:t-this.x()},!0)},y:function(t){return null==t?this.transform("y"):this.transform({y:t-this.y()},!0)},cx:function(t){return null==t?this.gbox().cx:this.x(t-this.gbox().width/2)},cy:function(t){return null==t?this.gbox().cy:this.y(t-this.gbox().height/2)},gbox:function(){var t=this.bbox(),e=this.transform();return t.x+=e.x,t.x2+=e.x,t.cx+=e.x,t.y+=e.y,t.y2+=e.y,t.cy+=e.y,t}},construct:{group:function(){return this.put(new g.G)}}}),g.extend(g.Element,{siblings:function(){return this.parent().children()},position:function(){return this.parent().index(this)},next:function(){return this.siblings()[this.position()+1]},previous:function(){return this.siblings()[this.position()-1]},forward:function(){var t=this.position()+1,e=this.parent();return e.removeElement(this).add(this,t),e instanceof g.Doc&&e.node.appendChild(e.defs().node),this},backward:function(){var t=this.position();return t>0&&this.parent().removeElement(this).add(this,t-1),this},front:function(){var t=this.parent();return t.node.appendChild(this.node),t instanceof g.Doc&&t.node.appendChild(t.defs().node),this},back:function(){return this.position()>0&&this.parent().removeElement(this).add(this,0),this},before:function(t){t.remove();var e=this.position();return this.parent().add(t,e),this},after:function(t){t.remove();var e=this.position();return this.parent().add(t,e+1),this}}),g.Mask=g.invent({create:function(){this.constructor.call(this,g.create("mask")),this.targets=[]},inherit:g.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unmask();return this.targets=[],this.parent().removeElement(this),this}},construct:{mask:function(){return this.defs().put(new g.Mask)}}}),g.extend(g.Element,{maskWith:function(t){return this.masker=t instanceof g.Mask?t:this.parent().mask().add(t),this.masker.targets.push(this),this.attr("mask",'url("#'+this.masker.attr("id")+'")')},unmask:function(){return delete this.masker,this.attr("mask",null)}}),g.ClipPath=g.invent({create:function(){this.constructor.call(this,g.create("clipPath")),this.targets=[]},inherit:g.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unclip();return this.targets=[],this.parent().removeElement(this),this}},construct:{clip:function(){return this.defs().put(new g.ClipPath)}}}),g.extend(g.Element,{clipWith:function(t){return this.clipper=t instanceof g.ClipPath?t:this.parent().clip().add(t),this.clipper.targets.push(this),this.attr("clip-path",'url("#'+this.clipper.attr("id")+'")')},unclip:function(){return delete this.clipper,this.attr("clip-path",null)}}),g.Gradient=g.invent({create:function(t){this.constructor.call(this,g.create(t+"Gradient")),this.type=t},inherit:g.Container,extend:{at:function(t,e,i){return this.put(new g.Stop).update(t,e,i)},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},fill:function(){return"url(#"+this.id()+")"},toString:function(){return this.fill()},attr:function(t,e,i){return"transform"==t&&(t="gradientTransform"),g.Container.prototype.attr.call(this,t,e,i)}},construct:{gradient:function(t,e){return this.defs().gradient(t,e)}}}),g.extend(g.Gradient,g.FX,{from:function(t,e){return"radial"==(this._target||this).type?this.attr({fx:new g.Number(t),fy:new g.Number(e)}):this.attr({x1:new g.Number(t),y1:new g.Number(e)})},to:function(t,e){return"radial"==(this._target||this).type?this.attr({cx:new g.Number(t),cy:new g.Number(e)}):this.attr({x2:new g.Number(t),y2:new g.Number(e)})}}),g.extend(g.Defs,{gradient:function(t,e){return this.put(new g.Gradient(t)).update(e)}}),g.Stop=g.invent({create:"stop",inherit:g.Element,extend:{update:function(t){return("number"==typeof t||t instanceof g.Number)&&(t={offset:arguments[0],color:arguments[1],opacity:arguments[2]}),null!=t.opacity&&this.attr("stop-opacity",t.opacity),null!=t.color&&this.attr("stop-color",t.color),null!=t.offset&&this.attr("offset",new g.Number(t.offset)),this}}}),g.Pattern=g.invent({create:"pattern",inherit:g.Container,extend:{fill:function(){return"url(#"+this.id()+")"},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},toString:function(){return this.fill()},attr:function(t,e,i){return"transform"==t&&(t="patternTransform"),g.Container.prototype.attr.call(this,t,e,i)}},construct:{pattern:function(t,e,i){return this.defs().pattern(t,e,i)}}}),g.extend(g.Defs,{pattern:function(t,e,i){return this.put(new g.Pattern).update(i).attr({x:0,y:0,width:t,height:e,patternUnits:"userSpaceOnUse"})}}),g.Doc=g.invent({create:function(t){t&&(t="string"==typeof t?e.getElementById(t):t,"svg"==t.nodeName?this.constructor.call(this,t):(this.constructor.call(this,g.create("svg")),t.appendChild(this.node),this.size("100%","100%")),this.namespace().defs())},inherit:g.Container,extend:{namespace:function(){return this.attr({xmlns:g.ns,version:"1.1"}).attr("xmlns:xlink",g.xlink,g.xmlns).attr("xmlns:svgjs",g.svgjs,g.xmlns)},defs:function(){if(!this._defs){var t;(t=this.node.getElementsByTagName("defs")[0])?this._defs=g.adopt(t):this._defs=new g.Defs,this.node.appendChild(this._defs.node)}return this._defs},parent:function(){return"#document"==this.node.parentNode.nodeName?null:this.node.parentNode},spof:function(t){var e=this.node.getScreenCTM();return e&&this.style("left",-e.e%1+"px").style("top",-e.f%1+"px"),this},remove:function(){return this.parent()&&this.parent().removeChild(this.node),this},clear:function(){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return delete this._defs,g.parser.draw.parentNode||this.node.appendChild(g.parser.draw),this}}}),g.Shape=g.invent({create:function(t){this.constructor.call(this,t)},inherit:g.Element}),g.Bare=g.invent({create:function(t,e){if(this.constructor.call(this,g.create(t)),e)for(var i in e.prototype)"function"==typeof e.prototype[i]&&(this[i]=e.prototype[i])},inherit:g.Element,extend:{words:function(t){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return this.node.appendChild(e.createTextNode(t)),this}}}),g.extend(g.Parent,{element:function(t,e){return this.put(new g.Bare(t,e))}}),g.Symbol=g.invent({create:"symbol",inherit:g.Container,construct:{symbol:function(){return this.put(new g.Symbol)}}}),g.Use=g.invent({create:"use",inherit:g.Shape,extend:{element:function(t,e){return this.attr("href",(e||"")+"#"+t,g.xlink)}},construct:{use:function(t,e){return this.put(new g.Use).element(t,e)}}}),g.Rect=g.invent({create:"rect",inherit:g.Shape,construct:{rect:function(t,e){return this.put(new g.Rect).size(t,e)}}}),g.Circle=g.invent({create:"circle",inherit:g.Shape,construct:{circle:function(t){return this.put(new g.Circle).rx(new g.Number(t).divide(2)).move(0,0)}}}),g.extend(g.Circle,g.FX,{rx:function(t){return this.attr("r",t)},ry:function(t){return this.rx(t)}}),g.Ellipse=g.invent({create:"ellipse",inherit:g.Shape,construct:{ellipse:function(t,e){return this.put(new g.Ellipse).size(t,e).move(0,0)}}}),g.extend(g.Ellipse,g.Rect,g.FX,{rx:function(t){return this.attr("rx",t)},ry:function(t){return this.attr("ry",t)}}),g.extend(g.Circle,g.Ellipse,{x:function(t){return null==t?this.cx()-this.rx():this.cx(t+this.rx())},y:function(t){return null==t?this.cy()-this.ry():this.cy(t+this.ry())},cx:function(t){return null==t?this.attr("cx"):this.attr("cx",t)},cy:function(t){return null==t?this.attr("cy"):this.attr("cy",t)},width:function(t){return null==t?2*this.rx():this.rx(new g.Number(t).divide(2))},height:function(t){return null==t?2*this.ry():this.ry(new g.Number(t).divide(2))},size:function(t,e){var i=l(this,t,e);return this.rx(new g.Number(i.width).divide(2)).ry(new g.Number(i.height).divide(2))}}),g.Line=g.invent({create:"line",inherit:g.Shape,extend:{array:function(){return new g.PointArray([[this.attr("x1"),this.attr("y1")],[this.attr("x2"),this.attr("y2")]])},plot:function(t,e,i,n){return null==t?this.array():(t="undefined"!=typeof e?{x1:t,y1:e,x2:i,y2:n}:new g.PointArray(t).toLine(),this.attr(t))},move:function(t,e){return this.attr(this.array().move(t,e).toLine())},size:function(t,e){var i=l(this,t,e);return this.attr(this.array().size(i.width,i.height).toLine())}},construct:{line:function(t,e,i,n){return g.Line.prototype.plot.apply(this.put(new g.Line),null!=t?[t,e,i,n]:[0,0,0,0])}}}),g.Polyline=g.invent({create:"polyline",inherit:g.Shape,construct:{polyline:function(t){return this.put(new g.Polyline).plot(t||new g.PointArray)}}}),g.Polygon=g.invent({create:"polygon",inherit:g.Shape,construct:{polygon:function(t){return this.put(new g.Polygon).plot(t||new g.PointArray)}}}),g.extend(g.Polyline,g.Polygon,{array:function(){return this._array||(this._array=new g.PointArray(this.attr("points")))},plot:function(t){return null==t?this.array():this.clear().attr("points","string"==typeof t?t:this._array=new g.PointArray(t))},clear:function(){return delete this._array,this},move:function(t,e){return this.attr("points",this.array().move(t,e))},size:function(t,e){var i=l(this,t,e);return this.attr("points",this.array().size(i.width,i.height))}}),g.extend(g.Line,g.Polyline,g.Polygon,{morphArray:g.PointArray,x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},width:function(t){var e=this.bbox();return null==t?e.width:this.size(t,e.height)},height:function(t){var e=this.bbox();return null==t?e.height:this.size(e.width,t)}}),g.Path=g.invent({create:"path",inherit:g.Shape,extend:{morphArray:g.PathArray,array:function(){return this._array||(this._array=new g.PathArray(this.attr("d")))},plot:function(t){return null==t?this.array():this.clear().attr("d","string"==typeof t?t:this._array=new g.PathArray(t))},clear:function(){return delete this._array,this},move:function(t,e){return this.attr("d",this.array().move(t,e))},x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},size:function(t,e){var i=l(this,t,e);return this.attr("d",this.array().size(i.width,i.height))},width:function(t){return null==t?this.bbox().width:this.size(t,this.bbox().height)},height:function(t){return null==t?this.bbox().height:this.size(this.bbox().width,t)}},construct:{path:function(t){return this.put(new g.Path).plot(t||new g.PathArray)}}}),g.Image=g.invent({create:"image",inherit:g.Shape,extend:{load:function(e){if(!e)return this;var i=this,n=new t.Image;return g.on(n,"load",function(){var t=i.parent(g.Pattern);null!==t&&(0==i.width()&&0==i.height()&&i.size(n.width,n.height),t&&0==t.width()&&0==t.height()&&t.size(i.width(),i.height()),"function"==typeof i._loaded&&i._loaded.call(i,{width:n.width,height:n.height,ratio:n.width/n.height,url:e}))}),g.on(n,"error",function(t){"function"==typeof i._error&&i._error.call(i,t)}),this.attr("href",n.src=this.src=e,g.xlink)},loaded:function(t){return this._loaded=t,this},error:function(t){return this._error=t,this}},construct:{image:function(t,e,i){return this.put(new g.Image).load(t).size(e||0,i||e||0)}}}),g.Text=g.invent({create:function(){this.constructor.call(this,g.create("text")),this.dom.leading=new g.Number(1.3),this._rebuild=!0,this._build=!1,this.attr("font-family",g.defaults.attrs["font-family"])},inherit:g.Shape,extend:{x:function(t){return null==t?this.attr("x"):this.attr("x",t)},y:function(t){var e=this.attr("y"),i="number"==typeof e?e-this.bbox().y:0;return null==t?"number"==typeof e?e-i:e:this.attr("y","number"==typeof t?t+i:t)},cx:function(t){return null==t?this.bbox().cx:this.x(t-this.bbox().width/2)},cy:function(t){return null==t?this.bbox().cy:this.y(t-this.bbox().height/2)},text:function(t){if("undefined"==typeof t){for(var t="",e=this.node.childNodes,i=0,n=e.length;i=0;e--)null!=i[M[t][e]]&&this.attr(M.prefix(t,M[t][e]),i[M[t][e]]);return this},g.extend(g.Element,g.FX,i)}),g.extend(g.Element,g.FX,{rotate:function(t,e,i){return this.transform({rotation:t,cx:e,cy:i})},skew:function(t,e,i,n){return 1==arguments.length||3==arguments.length?this.transform({skew:t,cx:e,cy:i}):this.transform({skewX:t,skewY:e,cx:i,cy:n})},scale:function(t,e,i,n){return 1==arguments.length||3==arguments.length?this.transform({scale:t,cx:e,cy:i}):this.transform({scaleX:t,scaleY:e,cx:i,cy:n})},translate:function(t,e){return this.transform({x:t,y:e})},flip:function(t,e){return e="number"==typeof t?t:e,this.transform({flip:t||"both",offset:e})},matrix:function(t){return this.attr("transform",new g.Matrix(6==arguments.length?[].slice.call(arguments):t))},opacity:function(t){return this.attr("opacity",t)},dx:function(t){return this.x(new g.Number(t).plus(this instanceof g.FX?0:this.x()),!0)},dy:function(t){return this.y(new g.Number(t).plus(this instanceof g.FX?0:this.y()),!0)},dmove:function(t,e){return this.dx(t).dy(e)}}),g.extend(g.Rect,g.Ellipse,g.Circle,g.Gradient,g.FX,{radius:function(t,e){var i=(this._target||this).type;return"radial"==i||"circle"==i?this.attr("r",new g.Number(t)):this.rx(t).ry(null==e?t:e)}}),g.extend(g.Path,{length:function(){return this.node.getTotalLength()},pointAt:function(t){return this.node.getPointAtLength(t)}}),g.extend(g.Parent,g.Text,g.Tspan,g.FX,{font:function(t,e){if("object"==typeof t)for(e in t)this.font(e,t[e]);return"leading"==t?this.leading(e):"anchor"==t?this.attr("text-anchor",e):"size"==t||"family"==t||"weight"==t||"stretch"==t||"variant"==t||"style"==t?this.attr("font-"+t,e):this.attr(t,e)}}),g.Set=g.invent({create:function(t){Array.isArray(t)?this.members=t:this.clear()},extend:{add:function(){var t,e,i=[].slice.call(arguments);for(t=0,e=i.length;t-1&&this.members.splice(e,1),this},each:function(t){for(var e=0,i=this.members.length;e=0},index:function(t){return this.members.indexOf(t)},get:function(t){return this.members[t]},first:function(){return this.get(0)},last:function(){return this.get(this.members.length-1)},valueOf:function(){return this.members},bbox:function(){if(0==this.members.length)return new g.RBox;var t=this.members[0].rbox(this.members[0].doc());return this.each(function(){t=t.merge(this.rbox(this.doc()))}),t}},construct:{set:function(t){return new g.Set(t)}}}),g.FX.Set=g.invent({create:function(t){this.set=t}}),g.Set.inherit=function(){var t,e=[];for(var t in g.Shape.prototype)"function"==typeof g.Shape.prototype[t]&&"function"!=typeof g.Set.prototype[t]&&e.push(t);e.forEach(function(t){g.Set.prototype[t]=function(){for(var e=0,i=this.members.length;e=0;t--)delete this.memory()[arguments[t]];return this},memory:function(){return this._memory||(this._memory={})}}),g.get=function(t){var i=e.getElementById(v(t)||t);return g.adopt(i)},g.select=function(t,i){return new g.Set(g.utils.map((i||e).querySelectorAll(t),function(t){return g.adopt(t)}))},g.extend(g.Parent,{select:function(t){return g.select(t,this.node)}});var P="abcdef".split("");if("function"!=typeof t.CustomEvent){var A=function(t,i){i=i||{bubbles:!1,cancelable:!1,detail:void 0};var n=e.createEvent("CustomEvent");return n.initCustomEvent(t,i.bubbles,i.cancelable,i.detail),n};A.prototype=t.Event.prototype,t.CustomEvent=A}return function(e){for(var i=0,n=["moz","webkit"],r=0;r", + "main": "dist/svg.js", + "jam": { + "include": [ + "dist/svg.js", + "README.md", + "LICENSE.txt" + ] + }, + "maintainers": [ + { + "name": "Wout Fierens", + "email": "wout@mick-wout.com", + "web": "https://svgdotjs.github.io/" + }, + { + "name": "Alex Ewerlöf", + "email": "alex@userpixel.com", + "web": "http://www.ewerlof.name" + }, + { + "name": "Ulrich-Matthias Schäfer", + "email": "ulima.ums@googlemail.com" + }, + { + "name": "Jon Ege Ronnenberg", + "email": "jon@svgjs.com", + "url": "https://keybase.io/dotnetcarpenter" + } + ], + "licenses": [ + { + "type": "MIT", + "url": "http://www.opensource.org/licenses/mit-license.php" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/svgdotjs/svg.js.git" + }, + "github": "https://github.com/svgdotjs/svg.js", + "license": "MIT", + "typings": "./svg.js.d.ts", + "scripts": { + "build": "gulp", + "build:test": "gulp unify", + "test": "karma start .config/karma.conf.js --single-run", + "test:quick": "karma start .config/karma.quick.js" + }, + "devDependencies": { + "coveralls": "^2.11.15", + "del": "^2.2.0", + "gulp": "^3.8.6", + "gulp-chmod": "^2.0.0", + "gulp-cli": "^1.2.2", + "gulp-concat": "^2.3.3", + "gulp-header": "^1.0.5", + "gulp-rename": "^1.2.0", + "gulp-size": "^2.1.0", + "gulp-trimlines": "^1.0.0", + "gulp-uglify": "^2.0.0", + "gulp-wrap": "^0.13.0", + "jasmine-core": "^2.5.2", + "karma": "^1.3.0", + "karma-coverage": "^1.1.1", + "karma-firefox-launcher": "^1.0.0", + "karma-jasmine": "^1.0.2", + "karma-phantomjs-launcher": "^1.0.2", + "request": "^2.78.0", + "svgdom": "latest" + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/package-lock.json b/files/plugin-HeatmapSessionRecording-5.2.4/package-lock.json new file mode 100644 index 0000000..fd1c5a8 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/package-lock.json @@ -0,0 +1,71 @@ +{ + "name": "heatmapsessionrecording", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "heatmapsessionrecording", + "version": "1.0.0", + "license": "GPL-3.0+", + "dependencies": { + "@types/heatmap.js": "^2.0.37", + "heatmap.js": "^2.0.5" + } + }, + "node_modules/@types/geojson": { + "version": "7946.0.8", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz", + "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==" + }, + "node_modules/@types/heatmap.js": { + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/@types/heatmap.js/-/heatmap.js-2.0.37.tgz", + "integrity": "sha512-Zd1m6WaRSPnXcR1fETGnIvyRSE2rcQK21S0zIU/LWjwsrNyKBA3xdckrQhQpIdG+UTeu7WODv237s30Ky7IVXg==", + "dependencies": { + "@types/leaflet": "^0" + } + }, + "node_modules/@types/leaflet": { + "version": "0.7.35", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-0.7.35.tgz", + "integrity": "sha512-BK+pa9a9dYC1qJyYQulqkRI9N+ZnV4ycAmNSOUmom7C6xaAdmrhOoiCiDMhSQklyjPpasy3KWRTkTRTJuDbBSw==", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/heatmap.js": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/heatmap.js/-/heatmap.js-2.0.5.tgz", + "integrity": "sha1-Rm07hlE/XUkRKknSVwCrJzAUkVM=" + } + }, + "dependencies": { + "@types/geojson": { + "version": "7946.0.8", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz", + "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==" + }, + "@types/heatmap.js": { + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/@types/heatmap.js/-/heatmap.js-2.0.37.tgz", + "integrity": "sha512-Zd1m6WaRSPnXcR1fETGnIvyRSE2rcQK21S0zIU/LWjwsrNyKBA3xdckrQhQpIdG+UTeu7WODv237s30Ky7IVXg==", + "requires": { + "@types/leaflet": "^0" + } + }, + "@types/leaflet": { + "version": "0.7.35", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-0.7.35.tgz", + "integrity": "sha512-BK+pa9a9dYC1qJyYQulqkRI9N+ZnV4ycAmNSOUmom7C6xaAdmrhOoiCiDMhSQklyjPpasy3KWRTkTRTJuDbBSw==", + "requires": { + "@types/geojson": "*" + } + }, + "heatmap.js": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/heatmap.js/-/heatmap.js-2.0.5.tgz", + "integrity": "sha1-Rm07hlE/XUkRKknSVwCrJzAUkVM=" + } + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/package.json b/files/plugin-HeatmapSessionRecording-5.2.4/package.json new file mode 100644 index 0000000..d7cea0a --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/package.json @@ -0,0 +1,27 @@ +{ + "name": "heatmapsessionrecording", + "version": "1.0.0", + "description": "## Description", + "main": "tracker.js", + "directories": { + "doc": "docs", + "test": "tests" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/innocraft/plugin-HeatmapSessionRecording.git" + }, + "author": "", + "license": "InnoCraft EULA", + "bugs": { + "url": "https://github.com/innocraft/plugin-HeatmapSessionRecording/issues" + }, + "homepage": "https://github.com/innocraft/plugin-HeatmapSessionRecording#readme", + "dependencies": { + "@types/heatmap.js": "^2.0.37", + "heatmap.js": "^2.0.5" + } +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/phpcs.xml b/files/plugin-HeatmapSessionRecording-5.2.4/phpcs.xml new file mode 100644 index 0000000..269d510 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/phpcs.xml @@ -0,0 +1,37 @@ + + + + Matomo Coding Standard for HeatmapSessionRecording plugin + + + + . + + tests/javascript/* + */vendor/* + + + + + + + + tests/* + + + + + Updates/* + + + + + tests/* + + + + + tests/* + Tracker/Configs.php + + \ No newline at end of file diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/plugin.json b/files/plugin-HeatmapSessionRecording-5.2.4/plugin.json new file mode 100644 index 0000000..9eab077 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/plugin.json @@ -0,0 +1,41 @@ +{ + "name": "HeatmapSessionRecording", + "description": "Truly understand your visitors by seeing where they click, hover, type and scroll. Replay their actions in a video and ultimately increase conversions.", + "version": "5.2.4", + "theme": false, + "require": { + "matomo": ">=5.0.0-rc1,<6.0.0-b1" + }, + "authors": [ + { + "name": "InnoCraft", + "email": "contact@innocraft.com", + "homepage": "https:\/\/www.innocraft.com" + } + ], + "price": { + "base": 220 + }, + "archive": { + "exclude": ["/tracker.js"] + }, + "preview": { + "video_url": "https://www.youtube-nocookie.com/embed/AUSXjH8U9fk" + }, + "homepage": "https:\/\/www.heatmap-analytics.com", + "license": "InnoCraft EULA", + "keywords": [ + "heatmap", + "session recording", + "session", + "recording", + "move", + "scroll", + "hover", + "click", + "user", + "visitor", + "video", + "visit" + ] +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/pull_request_template.md b/files/plugin-HeatmapSessionRecording-5.2.4/pull_request_template.md new file mode 100644 index 0000000..e7d9cf5 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/pull_request_template.md @@ -0,0 +1,26 @@ +## Description + + +## Issue No + + +## Steps to Replicate the Issue +1. +2. +3. + + + +## Checklist +- [✔/✖] Tested locally or on demo2/demo3? +- [✔/✖/NA] New test case added/updated? +- [✔/✖/NA] Are all newly added texts included via translation? +- [✔/✖/NA] Are text sanitized properly? (Eg use of v-text v/s v-html for vue) +- [✔/✖/NA] Version bumped? \ No newline at end of file diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/stylesheets/edit-entities.less b/files/plugin-HeatmapSessionRecording-5.2.4/stylesheets/edit-entities.less new file mode 100644 index 0000000..9d2fc7b --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/stylesheets/edit-entities.less @@ -0,0 +1,55 @@ + +.editHsr { + .icon-help { + color: #888; + cursor: help; + &:hover { + color: @theme-color-text; + } + } + .icon-minus, + .icon-plus { + cursor: pointer; + } + .icon-minus { + margin-left: 8px; + } + + .matchPageRules > .row { + width: ~"calc(100% - 60px)"; + margin: 0 !important; + + > .col { + padding-left: 0; + } + } + + .form-group { + margin-left: -.75rem; + margin-right: -.75rem; + + &.hsrTargetTest { + margin-left: 0; + margin-right: 0; + } + } + + .matchPageRules { + margin-top: 1em; + + hr { + margin-bottom: 3em; + } + + .form-group { + margin: 0; + } + .input-field { + margin-top: 0.2em; + + input, select{ + width: 100% !important; + } + } + } +} \ No newline at end of file diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/stylesheets/list-entities.less b/files/plugin-HeatmapSessionRecording-5.2.4/stylesheets/list-entities.less new file mode 100644 index 0000000..c7c1189 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/stylesheets/list-entities.less @@ -0,0 +1,35 @@ +.manageHsr { + .filterStatus, .hsrSearchFilter { + display: inline-block; + width:200px; + } + + div.filterStatus { + margin: 0 -0.75em; + display: inline-block; + + .theWidgetContent & { + margin: 0; + } + } + + th.action, td.action { + width: 250px; + + a { + color: black; + } + } + + .index { + width: 60px; + } + + a.table-action { + margin-right: 3.5px; + } + + a.table-action:last-child { + margin-right: 0; + } +} \ No newline at end of file diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/stylesheets/recordings.less b/files/plugin-HeatmapSessionRecording-5.2.4/stylesheets/recordings.less new file mode 100644 index 0000000..a16bbc4 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/stylesheets/recordings.less @@ -0,0 +1,154 @@ +#recordingPlayer { + display: block; + border: 1px solid #ccc; + pointer-events: none !important; + background: white; +} + +.scrollHeatmapLeaf { + position:absolute; + z-index: 10; + opacity: 0.4; +} +#listOfPageviews { + table .inactive { + cursor: pointer; + } +} +.sessionRecording { + padding: 16px; + font-size: 14px; + + .recordingPageviews { + cursor: pointer; + color: @theme-color-link; + } + + .recordingPageviews, + .recordingResolution, + .recordingLogos, + .recordingUrl { + margin-left: 16px; + } + + .recordingLogos { + img { + margin-right: 6px; + height: 14px; + } + .countryFlag { + border: 1px solid #d3d3d3; + height: 15px; + } + } + + .openVisitorProfile { + cursor:pointer; + height: 15px !important; + } +} + +.visitorLogReplaySession { + margin-top: 10px; + padding-bottom: 5px; + display: block; + width: auto!important; + &:hover { + color: @theme-color-brand !important; + } + + .visitor-profile & { + display: none; + } +} + +.visitorLogIconReplaySession { + display: block; + float: left; + font-size: 18px; + margin: 4px 10px 0 0; + + .dataTableVizVisitorLog &, .visitor-profile-header & { + display: none; + } + + &:hover { + text-decoration: none; + color: @theme-color-brand !important; + } +} + +[data-report="HeatmapSessionRecording.getRecordedSessions"] { + .dataTableRowActions { + + .actionHsrPlayRecording, .actionHsrVisitorProfile { + padding-right: 1rem; + } + + .icon-play { + color: @theme-color-brand !important; + display: inline-block; + margin-top: 1px; + } + + .icon-visitor-profile { + font-size: 21px !important; + } + } + + table.subDataTable tr .label.column { + width: 400px; + } + + .countryFlag { + border: 1px solid #d3d3d3; + } +} + +.hsrLoadingOuter { + position: absolute; + z-index: 2; + + .loadingUnderlay { + background: #000; + width:100%; + height:100%; + position: relative; + opacity: 0.6; + } + + .loadingInner { + margin: 0 auto; + font-size: 28px; + color: white; + text-align:center; + top: 50px; + position: absolute; + width: 100%; + } + + .loadingContent { + margin: 0 auto; + } +} + +.heatmapVis .btn-flat { + background-color: @theme-color-brand; + opacity: 0.6; + color: #fff; + border: 0; + box-shadow: 0 2px 3px 0 rgba(0,0,0,0.16), 0 0 3px 0 rgba(0,0,0,0.12); + + &:hover { + opacity: 1 !important; + } + + img { + filter: invert(1); + } +} + +.heatmapVis .visActive { + background-color: @theme-color-brand !important; + opacity: 1 !important; +} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/templates/_detectAdBlocker.twig b/files/plugin-HeatmapSessionRecording-5.2.4/templates/_detectAdBlocker.twig new file mode 100644 index 0000000..dfb74c4 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/templates/_detectAdBlocker.twig @@ -0,0 +1,26 @@ +{% block content %} +
    ') + } + var UI = require('piwik/UI'); + var notification = new UI.Notification(); + notification.show(_pk_translate('HeatmapSessionRecording_AdBlockerDetected',["{{ type }}"]),{context: 'warning',id:'HeatmapAdBlocker'}); + } + document.getElementById("ad-box").remove(); + }, 2000) + } + +{% endblock %} \ No newline at end of file diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/templates/embedPage.twig b/files/plugin-HeatmapSessionRecording-5.2.4/templates/embedPage.twig new file mode 100644 index 0000000..49c91d3 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/templates/embedPage.twig @@ -0,0 +1,43 @@ +{% extends 'empty.twig' %} + +{% set title=('HeatmapSessionRecording_ReplayX'|translate('HeatmapSessionRecording_SessionRecording'|translate)) %} + +{% block content %} + + + + + + + + + + + + + + + +{% endblock %} \ No newline at end of file diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/templates/gettingStartedHeatmaps.twig b/files/plugin-HeatmapSessionRecording-5.2.4/templates/gettingStartedHeatmaps.twig new file mode 100644 index 0000000..1ec5079 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/templates/gettingStartedHeatmaps.twig @@ -0,0 +1,2 @@ +

    {{ 'HeatmapSessionRecording_NoHeatmapsConfiguredInfo'|translate }}

    +

    {{ 'HeatmapSessionRecording_HeatmapUsageBenefits'|translate }}

    \ No newline at end of file diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/templates/gettingStartedSessions.twig b/files/plugin-HeatmapSessionRecording-5.2.4/templates/gettingStartedSessions.twig new file mode 100644 index 0000000..234c4ee --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/templates/gettingStartedSessions.twig @@ -0,0 +1,2 @@ +

    {{ 'HeatmapSessionRecording_NoSessionRecordingsConfiguredInfo'|translate }}

    +

    {{ 'HeatmapSessionRecording_SessionRecordingsUsageBenefits'|translate }}

    \ No newline at end of file diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/templates/manageHeatmap.twig b/files/plugin-HeatmapSessionRecording-5.2.4/templates/manageHeatmap.twig new file mode 100644 index 0000000..ee18072 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/templates/manageHeatmap.twig @@ -0,0 +1,22 @@ +{% extends 'admin.twig' %} + +{% block topcontrols %} +
    +
    +
    +{% endblock %} + +{% block content %} +
    +{% endblock %} \ No newline at end of file diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/templates/manageSessions.twig b/files/plugin-HeatmapSessionRecording-5.2.4/templates/manageSessions.twig new file mode 100644 index 0000000..4fe301c --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/templates/manageSessions.twig @@ -0,0 +1,20 @@ +{% extends 'admin.twig' %} + +{% block topcontrols %} +
    +
    +
    +{% endblock %} + +{% block content %} +
    +{% endblock %} \ No newline at end of file diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/templates/replayRecording.twig b/files/plugin-HeatmapSessionRecording-5.2.4/templates/replayRecording.twig new file mode 100644 index 0000000..c382d09 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/templates/replayRecording.twig @@ -0,0 +1,60 @@ +{% extends 'layout.twig' %} + +{% set title=('HeatmapSessionRecording_ReplayX'|translate('HeatmapSessionRecording_SessionRecording'|translate)) %} + +{% block root %} + {% include '@HeatmapSessionRecording/_detectAdBlocker.twig' with {type: 'Session recordings'} %} + +
    +
    + {{ recording.server_time_pretty }} + + {% if recording.url %}{{ recording.url|truncate(50) }}{% endif %} + + {{ recording.viewport_w_px }}x{{ recording.viewport_h_px }} + {% if recording.numPageviews == 1 %}{{ 'HeatmapSessionRecording_OnePageview'|translate }}{% else %}{{ 'HeatmapSessionRecording_PageviewXofY'|translate(currentPage, recording.numPageviews) }} {% endif %} + + + {% if recording.location_logo %}{% endif %} + {% if recording.device_logo %}{% endif %} + {% if recording.os_logo %}{% endif %} + {% if recording.browser_logo %}{% endif %} + {% if recording.idvisitor and visitorProfileEnabled %}{% endif %} + +
    + + + +
    +
    + +
    +
    +
    + +{% endblock %} diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/templates/showHeatmap.twig b/files/plugin-HeatmapSessionRecording-5.2.4/templates/showHeatmap.twig new file mode 100644 index 0000000..b4c65a5 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/templates/showHeatmap.twig @@ -0,0 +1,21 @@ +{% include '@HeatmapSessionRecording/_detectAdBlocker.twig' with {type: 'Heatmaps'} %} +
    +
    diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/tracker.min.js b/files/plugin-HeatmapSessionRecording-5.2.4/tracker.min.js new file mode 100644 index 0000000..91383a4 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/tracker.min.js @@ -0,0 +1,125 @@ +(function(){var N=1;var aH=9;var o=10;var P=8;var w=3;var ax=["button","submit","reset"]; +/*!! + * Copyright (C) 2015 Pavel Savshenko + * Copyright (C) 2011 Google Inc. All rights reserved. + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2008 Matt Lilek + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +; +var i={};i.cssPath=function(aT,aR){if(aT.nodeType!==N){return""}var aQ=[];var aP=aT;while(aP){var aS=i._cssPathStep(aP,!!aR,aP===aT);if(!aS){break}aQ.push(aS);if(aS.optimized){break}aP=aP.parentNode}aQ.reverse();return aQ.join(" > ")};i._cssPathStep=function(a4,aW,a3){if(a4.nodeType!==N){return null}var a2=a4.getAttribute("id");if(aW){if(a2){return new i.DOMNodePathStep(ba(a2),true)}var aQ=a4.nodeName.toLowerCase();if(aQ==="body"||aQ==="head"||aQ==="html"){return new i.DOMNodePathStep(a4.nodeName.toLowerCase(),true)}}var aP=a4.nodeName.toLowerCase();if(a2){return new i.DOMNodePathStep(aP.toLowerCase()+ba(a2),true)}var aX=a4.parentNode;if(!aX||aX.nodeType===aH){return new i.DOMNodePathStep(aP.toLowerCase(),true)}function bg(bi){var bj=bi.getAttribute("class");if(!bj){return[]}return bj.split(/\s+/g).filter(Boolean).map(function(bk){return"$"+bk})}function ba(bi){return"#"+bf(bi)}function bf(bj){if(bb(bj)){return bj}var bi=/^(?:[0-9]|-[0-9-]?)/.test(bj);var bk=bj.length-1;return bj.replace(/./g,function(bm,bl){return((bi&&bl===0)||!aR(bm))?aT(bm,bl===bk):bm +})}function aT(bj,bi){return"\\"+a1(bj)+(bi?"":" ")}function a1(bj){var bi=bj.charCodeAt(0).toString(16);if(bi.length===1){bi="0"+bi}return bi}function aR(bi){if(/[a-zA-Z0-9_\-]/.test(bi)){return true}return bi.charCodeAt(0)>=160}function bb(bi){return/^-?[a-zA-Z_][a-zA-Z0-9_\-]*$/.test(bi)}function a6(bi){var bk={},bj;for(bj=0;bj>>0};aP.prototype.nodeId=function(aQ){var aR=aQ[aP.ID_PROP];if(!aR){aR=aQ[aP.ID_PROP]=aP.nextId_++}return aR};aP.prototype.set=function(aQ,aR){var aS=this.nodeId(aQ);this.nodes[aS]=aQ;this.values[aS]=aR};aP.prototype.get=function(aQ){var aR=this.nodeId(aQ);return this.values[aR]};aP.prototype.has=function(aQ){return this.nodeId(aQ) in this.nodes};aP.prototype["delete"]=function(aQ){var aR=this.nodeId(aQ);delete this.nodes[aR];this.values[aR]=undefined};aP.prototype.keys=function(){var aQ=[];for(var aR in this.nodes){if(!this.isIndex(aR)){continue}aQ.push(this.nodes[aR])}return aQ};aP.ID_PROP="__mutation_summary_node_map_id__";aP.nextId_=1;return aP})();var aC; +(function(aP){aP[aP.STAYED_OUT=0]="STAYED_OUT";aP[aP.ENTERED=1]="ENTERED";aP[aP.STAYED_IN=2]="STAYED_IN";aP[aP.REPARENTED=3]="REPARENTED";aP[aP.REORDERED=4]="REORDERED";aP[aP.EXITED=5]="EXITED"})(aC||(aC={}));function B(aP){return aP===aC.ENTERED||aP===aC.EXITED}var ab=(function(){function aP(aW,aV,aR,aT,aS,aU,aQ,aX){if(aV===void 0){aV=false}if(aR===void 0){aR=false}if(aT===void 0){aT=false}if(aS===void 0){aS=null}if(aU===void 0){aU=false}if(aQ===void 0){aQ=null}if(aX===void 0){aX=null}this.node=aW;this.childList=aV;this.attributes=aR;this.characterData=aT;this.oldParentNode=aS;this.added=aU;this.attributeOldValues=aQ;this.characterDataOldValue=aX;this.isCaseInsensitive=this.node.nodeType===N&&this.node instanceof HTMLElement&&this.node.ownerDocument instanceof HTMLDocument}aP.prototype.getAttributeOldValue=function(aQ){if(!this.attributeOldValues){return undefined}if(this.isCaseInsensitive){aQ=aQ.toLowerCase()}return this.attributeOldValues[aQ]};aP.prototype.getAttributeNamesMutated=function(){var aR=[]; +if(!this.attributeOldValues){return aR}for(var aQ in this.attributeOldValues){aR.push(aQ)}return aR};aP.prototype.attributeMutated=function(aR,aQ){this.attributes=true;this.attributeOldValues=this.attributeOldValues||{};if(aR in this.attributeOldValues){return}this.attributeOldValues[aR]=aQ};aP.prototype.characterDataMutated=function(aQ){if(this.characterData){return}this.characterData=true;this.characterDataOldValue=aQ};aP.prototype.removedFromParent=function(aQ){this.childList=true;if(this.added||this.oldParentNode){this.added=false}else{this.oldParentNode=aQ}};aP.prototype.insertedIntoParent=function(){this.childList=true;this.added=true};aP.prototype.getOldParent=function(){if(this.childList){if(this.oldParentNode){return this.oldParentNode}if(this.added){return null}}return this.node.parentNode};return aP})();var ao=(function(){function aP(){this.added=new J();this.removed=new J();this.maybeMoved=new J();this.oldPrevious=new J();this.moved=undefined}return aP})();var W=(function(aQ){b(aP,aQ); +function aP(aU,aS){aQ.call(this);this.rootNode=aU;this.reachableCache=undefined;this.wasReachableCache=undefined;this.anyParentsChanged=false;this.anyAttributesChanged=false;this.anyCharacterDataChanged=false;for(var aR=0;aR1){throw Error("Invalid request option. all has no options.")}aU.queries.push({all:true});continue}if("attribute" in aT){var aV={attribute:j(aT.attribute)};aV.elementFilter=aG.parseSelectors("*["+aV.attribute+"]");if(Object.keys(aT).length>1){throw Error("Invalid request option. attribute has no options.")}aU.queries.push(aV);continue}if("element" in aT){var aS=Object.keys(aT).length;var aV={element:aT.element,elementFilter:aG.parseSelectors(aT.element)};if(aT.hasOwnProperty("elementAttributes")){aV.attributeList=az(aT.elementAttributes);aS--}if(aS>1){throw Error("Invalid request option. element only allows elementAttributes option.")}aU.queries.push(aV);continue}if(aT.characterData){if(Object.keys(aT).length>1){throw Error("Invalid request option. characterData has no options.") +}aU.queries.push({characterData:true});continue}throw Error("Invalid request option. Unknown query request.")}return aU};aP.prototype.createSummaries=function(aR){if(!aR||!aR.length){return[]}var aQ=new K(this.root,aR,this.elementFilter,this.calcReordered,this.options.oldPreviousSibling);var aT=[];for(var aS=0;aS=0){aV={}}else{if(aU.attributes.name){var a3=String(aU.attributes.name.value).toLowerCase();if(a3.indexOf("twitter:")>=0||a3.indexOf("description")>=0||a3.indexOf("keywords")>=0){aV={}}}}}else{if("LINK"===aU.tagName){if(aU.attributes.rel){var a2=String(aU.attributes.rel.value).toLowerCase(); +var a1=["icon","preload","preconnect","dns-prefetch","next","prev","alternate","search"];if(a1.indexOf(a2)>=0){aV={}}}if(aU.attributes.href){var aT=String(aU.attributes.href.value).toLowerCase().indexOf(".scr.kaspersky-labs.com");if(aT>5&&aT<=20){aV={}}}if(aU.href){if(typeof I.URL==="function"){var aS=ak.onCssLoaded(aU.href);var aQ=3;if(!aS){aR(aU.href);function aR(a5){if(aQ>0){setTimeout(function(){aQ--;aS=ak.onCssLoaded(aU.href);if(!aS){aR(aU.href)}},300)}}}}aV.url=aU.href}}}}return aV};aP.prototype.serializeAddedAndMoved=function(aT,aQ,aU){var aW=this;var aS=aT.concat(aQ).concat(aU);var aV=new d.NodeMap();aS.forEach(function(aZ){var aY=aZ.parentNode;var aX=aV.get(aY);if(!aX){aX=new d.NodeMap();aV.set(aY,aX)}aX.set(aZ,true)});var aR=[];aV.keys().forEach(function(aY){var aX=aV.get(aY);var a0=aX.keys();while(a0.length){var aZ=a0[0];while(aZ.previousSibling&&aX.has(aZ.previousSibling)){aZ=aZ.previousSibling}while(aZ&&aX.has(aZ)){var a1=aW.serializeNode(aZ);a1.previousSibling=aW.serializeNode(aZ.previousSibling); +a1.parentNode=aW.serializeNode(aZ.parentNode);aR.push(a1);aX["delete"](aZ);aZ=aZ.nextSibling}var a0=aX.keys()}});return aR};aP.prototype.serializeAttributeChanges=function(aQ){var aS=this;var aR=new d.NodeMap();Object.keys(aQ).forEach(function(aT){aQ[aT].forEach(function(aW){var aU=aR.get(aW);if(!aU){aU=aS.serializeNode(aW);aU.attributes={};aR.set(aW,aU)}var aV=aN.shouldMaskElementRecursive(aW);var aX=aS.getAttributesFromNode(aW,aV.isIgnoredField,aV.isIgnoredContent);aU.attributes[aT]=aT in aX?aX[aT]:null})});return aR.keys().map(function(aT){return aR.get(aT)})};aP.prototype.applyChanged=function(aT){var aW=this;var aR=aT[0];var aU=aR.removed.map(function(aX){return aW.serializeNode(aX)});var aS=this.serializeAddedAndMoved(aR.added,aR.reparented,aR.reordered);var aQ=this.serializeAttributeChanges(aR.attributeChanged);var aV=aR.characterDataChanged.map(function(aY){var aZ=aW.serializeNode(aY);if(aY.nodeType===w&&aY.parentNode){aY=aY.parentNode}var aX=aN.shouldMaskElementRecursive(aY,false,false); +aZ.textContent=aN.getMaskedTextContent(aY,aX.isIgnoredField,aX.isIgnoredContent);return aZ});this.mirror.applyChanged(aU,aS,aQ,aV);aR.removed.forEach(function(aX){aW.forgetNode(aX)})};return aP})()} +/*!! + * Copyright (C) InnoCraft Ltd - All rights reserved. + * + * All information contained herein is, and remains the property of InnoCraft Ltd. + * + * @link https://www.innocraft.com/ + * @license For license details see https://www.innocraft.com/license + */ +;var O=document;var I=window;var F=0;var l=false;var L=!u();var am=true;var n=null;var at=false;var ad="";var T=false;var g=15*60*1000;var aa=30*60*1000;var S=10;var av=(5*60*1000);var aA=2000;var C=1000;var H=100;var Y=500;var G=false;function u(){if("object"!==typeof JSON){return true}if("function"!==typeof Array.prototype.map||"function"!==typeof Array.prototype.filter||"function"!==typeof Array.prototype.indexOf){return true}if("function"!==typeof Element.prototype.getBoundingClientRect){return true}var aP=["cc.bingj.com"]; +if(aP.indexOf(O.domain)!==-1||String(O.domain).indexOf(".googleusercontent.com")!==-1){return true}var aR=/alexa|baidu|bing|bot|crawler|curl|crawling|duckduckgo|facebookexternalhit|feedburner|googlebot|google web preview|linkdex|nagios|postrank|pingdom|robot|slurp|spider|yahoo!|yandex|wget/i.test(navigator.userAgent);if(aR){return true}var aQ=String(O.referrer);if(aQ&&aQ.indexOf("module=Overlay&action=startOverlaySession")>=0){return true}return false}function U(){if(l&&"object"===typeof console){if(typeof console.debug==="function"){console.debug.apply(console,arguments)}else{if(typeof console.log==="function"){console.log.apply(console,arguments)}}}}var D=function(){return true};var s=1;var aJ=2;var h=3;var V=4;var aK=5;var ag=6;var a=7;var k=8;var e=9;var ar=10;var p=11;var ay=12;var aF=13;var aD=0;var af=1;var c=2;var aI=true;var Q=false;var an=false;var aO=true;var M=null;var A=false;var ae={};if("object"===typeof JSON){ae=JSON}var aj=false;var al=[];var aL={hasObserver:function(){if(typeof WebKitMutationObserver!=="undefined"){return true +}else{if(typeof MutationObserver!=="undefined"){return true}}return false}};var ai=aL.hasObserver();var r={getScrollLeft:function(){return I.document.body.scrollLeft||I.document.documentElement.scrollLeft},getScrollTop:function(){return I.document.body.scrollTop||I.document.documentElement.scrollTop},getDocumentHeight:function(){return au.safeMathMax([O.body.offsetHeight,O.body.scrollHeight,O.documentElement.offsetHeight,O.documentElement.clientHeight,O.documentElement.scrollHeight,1])},getDocumentWidth:function(){return au.safeMathMax([O.body.offsetWidth,O.body.scrollWidth,O.documentElement.offsetWidth,O.documentElement.clientWidth,O.documentElement.scrollWidth,1])},getWindowSize:function(){var aP=I.innerHeight||O.documentElement.clientHeight||O.body.clientHeight;var aQ=I.innerWidth||O.documentElement.clientWidth||O.body.clientWidth;return{width:aQ,height:aP}}};var t={namespace:"hsr",set:function(aR,aV,aT){aV=parseInt(aV,10);aT=parseInt(aT,10);var aU="";var aQ=t.getHsrConfigs(aR);var aS=false; +for(var aP=0;aP2){return true}}}if(aY){var aX=aP.parentNode?aP.parentNode:null;var aQ=false;while(aX){if(aN.hasAttribute(aX,"data-piwik-mask")||aN.hasAttribute(aX,"data-matomo-mask")){return true}else{if(!aQ&&aX&&aN.hasAttribute(aX,"data-matomo-unmask")){aQ=true}aX=aX.parentNode?aX.parentNode:null}}if(aQ){return false}}if(aN.hasAttribute(aP,"data-matomo-unmask")){return false}if(aT){return false}return true},shouldMaskContent:function(aR,aQ){if(!aR){return false}if(aR.tagName&&aR.tagName!=="FORM"&&aN.hasAttribute(aR,"data-matomo-mask")){return true}if(aR.tagName&&aR.tagName!=="FORM"&&aN.hasAttribute(aR,"data-matomo-unmask")){return false +}if(aQ){var aP=aR.parentNode?aR.parentNode:null;while(aP){if(aR.nodeName==="#text"&&aN.hasAttribute(aP,"data-matomo-unmask")){return false}else{if(aP.tagName!=="FORM"&&aN.hasAttribute(aP,"data-matomo-mask")){return true}else{aP=aP.parentNode?aP.parentNode:null}}}}return false},isAllowedInputType:function(aP){return(aP.type&&ax.indexOf(aP.type)!==-1&&!aN.hasAttribute(aP,"data-piwik-mask")&&!aN.hasAttribute(aP,"data-matomo-mask"))}};var au={safeMathMax:function(aP){var aQ=[];var aR;for(aR=0;aR0}function x(){return f("pk_hsr_forcesample=1")||f("pk_hsr_capturescreen=1")}function v(){return f("pk_hsr_forcesample=0")}function Z(aP){if(x()){return true}if(v()){return false}if(aP>=100){return true}if(aP<=0){return false}if(aP>=1){return aP>=au.getRandomInt(1,H)}return(aP*10)>=au.getRandomInt(1,H*10)}function q(aP){if("undefined"!==typeof aP.HeatmapSessionRecording){return}aP.HeatmapSessionRecording={myId:au.generateUniqueId(),hasReceivedConfig:false,hasRequestedConfig:false,hasTrackedData:false,hasSentStopTrackingEvent:false,enabled:true,hsrIdsToGetDOM:[],disable:function(){this.enabled=false},enable:function(){this.enabled=true +},isEnabled:function(){return L&&this.enabled},numSentTrackingRequests:0,Heatmap:{data:[],hsrids:[],configs:[],addConfig:function(aQ){if("object"!==typeof aQ||!aQ.id){return}aQ.id=parseInt(aQ.id,10);this.configs.push(aQ);if("undefined"===typeof aQ.sample_rate){aQ.sample_rate=H}else{aQ.sample_rate=Math.min(parseFloat(aQ.sample_rate),H)}if(aQ.id&&Z(aQ.sample_rate)&&D(aQ)){this.addHsrId(aQ.id);if((aQ.getdom&&!aQ.capture_manually)||f("pk_hsr_capturescreen=1")){aP.HeatmapSessionRecording.hsrIdsToGetDOM.push(aQ.id)}}},addHsrId:function(aQ){this.hsrids.push(aQ);if(aP.HeatmapSessionRecording.hasTrackedData){z.recordData(af,{ty:a,id:aQ})}}},Both:{data:[]},Session:{data:[],hsrids:[],configs:[],addConfig:function(aS){if("object"!==typeof aS||!aS.id){return}aS.id=parseInt(aS.id,10);if("undefined"===typeof aS.sample_rate){aS.sample_rate=H}else{aS.sample_rate=Math.min(parseFloat(aS.sample_rate),H)}aS.conditionsMet=false;this.configs.push(aS);var aR=parseInt(aP.getSiteId(),10);var aT=t.get(aP,aS.id);if(1===aT&&!v()){aS.sample_rate=H; +aS.activity=false;aS.min_time=0}else{if(x()){}else{if(0===aT||!Z(aS.sample_rate)){t.set(aP,aS.id,0);return}}}this.checkConditionsMet();if(aS.min_time){var aQ=this;Piwik.DOM.onReady(function(){var aU=(aS.min_time*1000)-au.getTimeSincePageReady()+120;if(aU>=0){setTimeout(function(){aQ.checkConditionsMet()},aU)}else{aQ.checkConditionsMet()}})}},checkConditionsMet:function(){var aR;for(var aS=0;aS=au.roundTimeToSeconds(au.getTimeSincePageReady())){aQ=false}if(aR.activity&&!an){an=r.getDocumentHeight()<=r.getWindowSize().height}if(aR.activity&&(!Q||!an)){aQ=false}if(aQ){aR.conditionsMet=true;if(D(aR)){if("undefined"===typeof aR.keystrokes||!aR.keystrokes||aR.keystrokes==="0"){aI=false}this.addHsrId(aR.id)}}}}},addHsrId:function(aQ){this.hsrids.push(aQ);if(aP.HeatmapSessionRecording.hasTrackedData){z.recordData(c,{ty:a,id:aQ})}var aR=parseInt(aP.getSiteId(),10);t.set(aP,aQ,1)}},addConfig:function(aQ){this.hasRequestedConfig=true; +this.hasReceivedConfig=true;if("undefined"===typeof aQ||!aQ){aM.checkAllConfigsReceived();return}if("object"===typeof aQ.heatmap){this.Heatmap.addConfig(aQ.heatmap)}var aR;if(aQ.heatmaps&&au.isArray(aQ.heatmaps)&&aQ.heatmaps.length){for(aR=0;aR=0;aR--){if(aQ[aR]&&aQ[aR].ty&&aQ[aR].ty===e){aQ.splice(aR,1)}}}}}if(aP.length&&aT.Both.data.length){aQ=aQ.concat(aT.Both.data);aT.Both.data=[]}if("undefined"===typeof aX){aX=this.shouldEndRecording(aW)}if(aX&&aT.hasTrackedData&&!aT.hasSentStopTrackingEvent&&aV){aQ.push({ty:p});aT.hasSentStopTrackingEvent=true}if(!aP||!aP.length||!aQ||!aQ.length){return}if(aW.HeatmapSessionRecording.hsrIdsToGetDOM&&aW.HeatmapSessionRecording.hsrIdsToGetDOM.length){if(!ak.initialDOM&&ai){var aU=new y(O,{initialize:function(aY,aZ){ak.initialDOM=ae.stringify({rootId:aY,children:aZ})}});aU.disconnect()}if(ak.initialDOM&&ai){for(var aS=0;aSM)&&aQ.ty&&aQ.ty!==ag){M=aQ.ti}if(aD===aP){aS.HeatmapSessionRecording.Both.data.push(aQ)}else{if(af===aP){aS.HeatmapSessionRecording.Heatmap.data.push(aQ)}else{if(c===aP){aS.HeatmapSessionRecording.Session.data.push(aQ)}}}}});if(l){U("recorddata",ae.stringify(aQ))}},stopSendingData:function(){var aP=z.getPiwikTrackers();aP.forEach(function(aQ){if(aQ.HeatmapSessionRecording){var aR=aQ.HeatmapSessionRecording; +if("undefined"!==typeof aR.trackingInterval){clearInterval(aR.trackingInterval);delete aR.trackingInterval}}})},startSendingData:function(){var aP=z.getPiwikTrackers();aP.forEach(function(aQ){if(aQ.HeatmapSessionRecording&&"undefined"===typeof aQ.HeatmapSessionRecording.trackingInterval){var aR=au.getRandomInt(10250,11250);aQ.HeatmapSessionRecording.trackingInterval=setInterval(function(){z.sendQueuedData(aQ)},aR);z.sendQueuedData(aQ)}})}};function aE(){ +/*!!! hsrTrackerReadyHook */ +;if(typeof window==="object"&&"function"===typeof I.piwikHeatmapSessionRecordingAsyncInit){I.piwikHeatmapSessionRecordingAsyncInit()}if(typeof window==="object"&&"function"===typeof I.matomoHeatmapSessionRecordingAsyncInit){I.matomoHeatmapSessionRecordingAsyncInit()}var aQ=al;al=[];aj=true;for(var aP=0;aPY){aY=aY.substr(0,Y)}if(aN.shouldMaskField(aS,!aN.hasAttribute(aS,"data-matomo-unmask"))){aY=aN.maskFormField(aY,aN.getAttribute(aS,"type")==="password")}}else{if(aP===ar&&"undefined"!==typeof aS.value){aY=String(aS.value)}}}var aV={ti:aR,ty:aP,s:aU,te:aY};if(aU){z.recordData(c,aV)}else{U("No selector found for text input ",aX) +}},onScroll:function(aP){if(!an){an=true;ak.checkTrackersIfConditionsMet()}var aT=au.getTimeSincePageReady();if(aP&&aP.type&&aP.type==="scroll"&&aP.target&&aP.target!==O){var aZ=aP.target;if("undefined"===typeof aZ.scrollTop){return}var aR=aZ.scrollTop;var aU=aZ.scrollLeft;var aS=aN.getWidth(aZ);var aQ=aN.getHeight(aZ);if(aS<=0||aQ<=0||!aS||!aQ){return}var aV=aN.getSelector(aZ);ak.lastElementScroll={time:aT,selector:aV,scrollY:parseInt((C*aR)/aQ,10),scrollX:parseInt((C*aU)/aS,10)};return}var aX=parseInt(r.getScrollTop(),10);var aW=parseInt(r.getScrollLeft(),10);var a1=r.getDocumentHeight();var aY=r.getDocumentWidth();ak.lastScroll={time:aT,scrollY:parseInt((C*aX)/a1,10),scrollX:parseInt((C*aW)/aY,10)};var a0=parseInt((C*(aX+r.getWindowSize().height))/a1,10);if(a0>ak.scrollMaxPercentage){ak.scrollMaxPercentage=a0}},checkTrackersIfConditionsMet:function(){var aQ=z.getPiwikTrackers();for(var aP=0;aPaa){g=aa}},setMaxTextInputLength:function(aQ){Y=aQ},disableCaptureKeystrokes:function(){aI=false},enableCaptureKeystrokes:function(){aI=true},setMatomoTrackers:function(aQ){this.setPiwikTrackers(aQ)},setPiwikTrackers:function(aQ){if(aQ===null){n=null;return}if(!au.isArray(aQ)){aQ=[aQ] +}n=aQ;n.forEach(q);if(aj){if(A){this.enable()}else{if(L){aM.fetch()}}}},enableDebugMode:function(){l=true}};Piwik.DOM.onReady(function(){F=new Date().getTime()});Piwik.addPlugin("HeatmapSessionRecording",{log:function(aQ){if(aO){if(aQ.tracker&&aQ.tracker.getNumTrackedPageViews&&aQ.tracker.getNumTrackedPageViews()>1){setTimeout(function(){Piwik.HeatmapSessionRecording.setNewPageView(true)},10)}}return""},unload:function(){if(!u()){var aQ=z.getPiwikTrackers();z.stopSendingData();aQ.forEach(function(aS){var aR=false;z.sendQueuedData(aS,aR)})}}});if(I.Piwik.initialized){var aP=Piwik.getAsyncTrackers();aP.forEach(q);Piwik.on("TrackerSetup",q);Piwik.retryMissedPluginCalls();aE();aM.fetch();Piwik.on("TrackerAdded",function(){if(A){Piwik.HeatmapSessionRecording.enable()}else{aM.fetch()}})}else{Piwik.on("TrackerSetup",q);Piwik.on("MatomoInitialized",function(){aE();if(L||A){aM.fetch()}Piwik.on("TrackerAdded",function(){if(L){aM.fetch()}else{if(A){Piwik.HeatmapSessionRecording.enable()}}})})}}ad=au.generateUniqueId(); +if("object"===typeof I.Piwik){ah()}else{if("object"!==typeof I.matomoPluginAsyncInit){I.matomoPluginAsyncInit=[]}I.matomoPluginAsyncInit.push(ah)}})(); \ No newline at end of file diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/tsconfig.json b/files/plugin-HeatmapSessionRecording-5.2.4/tsconfig.json new file mode 100644 index 0000000..a541624 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "typeRoots": [ + "../../node_modules/@types", + "../../plugins/CoreVue/types/index.d.ts", + "./node_modules/@types" + ], + "types": [ + "jquery", + "heatmap.js" + ] + } +} \ No newline at end of file diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/vue/dist/HeatmapSessionRecording.umd.js b/files/plugin-HeatmapSessionRecording-5.2.4/vue/dist/HeatmapSessionRecording.umd.js new file mode 100644 index 0000000..5468f48 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/vue/dist/HeatmapSessionRecording.umd.js @@ -0,0 +1,5480 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(require("CoreHome"), require("vue"), require("CorePluginsAdmin")); + else if(typeof define === 'function' && define.amd) + define(["CoreHome", , "CorePluginsAdmin"], factory); + else if(typeof exports === 'object') + exports["HeatmapSessionRecording"] = factory(require("CoreHome"), require("vue"), require("CorePluginsAdmin")); + else + root["HeatmapSessionRecording"] = factory(root["CoreHome"], root["Vue"], root["CorePluginsAdmin"]); +})((typeof self !== 'undefined' ? self : this), function(__WEBPACK_EXTERNAL_MODULE__19dc__, __WEBPACK_EXTERNAL_MODULE__8bbf__, __WEBPACK_EXTERNAL_MODULE_a5a2__) { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = "plugins/HeatmapSessionRecording/vue/dist/"; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = "fae3"); +/******/ }) +/************************************************************************/ +/******/ ({ + +/***/ "19dc": +/***/ (function(module, exports) { + +module.exports = __WEBPACK_EXTERNAL_MODULE__19dc__; + +/***/ }), + +/***/ "246e": +/***/ (function(module, exports, __webpack_require__) { + +var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;/* + * heatmap.js v2.0.5 | JavaScript Heatmap Library + * + * Copyright 2008-2016 Patrick Wied - All rights reserved. + * Dual licensed under MIT and Beerware license + * + * :: 2016-09-05 01:16 + */ +;(function (name, context, factory) { + + // Supports UMD. AMD, CommonJS/Node.js and browser context + if ( true && module.exports) { + module.exports = factory(); + } else if (true) { + !(__WEBPACK_AMD_DEFINE_FACTORY__ = (factory), + __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? + (__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, __webpack_require__, exports, module)) : + __WEBPACK_AMD_DEFINE_FACTORY__), + __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + } else {} + +})("h337", this, function () { + +// Heatmap Config stores default values and will be merged with instance config +var HeatmapConfig = { + defaultRadius: 40, + defaultRenderer: 'canvas2d', + defaultGradient: { 0.25: "rgb(0,0,255)", 0.55: "rgb(0,255,0)", 0.85: "yellow", 1.0: "rgb(255,0,0)"}, + defaultMaxOpacity: 1, + defaultMinOpacity: 0, + defaultBlur: .85, + defaultXField: 'x', + defaultYField: 'y', + defaultValueField: 'value', + plugins: {} +}; +var Store = (function StoreClosure() { + + var Store = function Store(config) { + this._coordinator = {}; + this._data = []; + this._radi = []; + this._min = 10; + this._max = 1; + this._xField = config['xField'] || config.defaultXField; + this._yField = config['yField'] || config.defaultYField; + this._valueField = config['valueField'] || config.defaultValueField; + + if (config["radius"]) { + this._cfgRadius = config["radius"]; + } + }; + + var defaultRadius = HeatmapConfig.defaultRadius; + + Store.prototype = { + // when forceRender = false -> called from setData, omits renderall event + _organiseData: function(dataPoint, forceRender) { + var x = dataPoint[this._xField]; + var y = dataPoint[this._yField]; + var radi = this._radi; + var store = this._data; + var max = this._max; + var min = this._min; + var value = dataPoint[this._valueField] || 1; + var radius = dataPoint.radius || this._cfgRadius || defaultRadius; + + if (!store[x]) { + store[x] = []; + radi[x] = []; + } + + if (!store[x][y]) { + store[x][y] = value; + radi[x][y] = radius; + } else { + store[x][y] += value; + } + var storedVal = store[x][y]; + + if (storedVal > max) { + if (!forceRender) { + this._max = storedVal; + } else { + this.setDataMax(storedVal); + } + return false; + } else if (storedVal < min) { + if (!forceRender) { + this._min = storedVal; + } else { + this.setDataMin(storedVal); + } + return false; + } else { + return { + x: x, + y: y, + value: value, + radius: radius, + min: min, + max: max + }; + } + }, + _unOrganizeData: function() { + var unorganizedData = []; + var data = this._data; + var radi = this._radi; + + for (var x in data) { + for (var y in data[x]) { + + unorganizedData.push({ + x: x, + y: y, + radius: radi[x][y], + value: data[x][y] + }); + + } + } + return { + min: this._min, + max: this._max, + data: unorganizedData + }; + }, + _onExtremaChange: function() { + this._coordinator.emit('extremachange', { + min: this._min, + max: this._max + }); + }, + addData: function() { + if (arguments[0].length > 0) { + var dataArr = arguments[0]; + var dataLen = dataArr.length; + while (dataLen--) { + this.addData.call(this, dataArr[dataLen]); + } + } else { + // add to store + var organisedEntry = this._organiseData(arguments[0], true); + if (organisedEntry) { + // if it's the first datapoint initialize the extremas with it + if (this._data.length === 0) { + this._min = this._max = organisedEntry.value; + } + this._coordinator.emit('renderpartial', { + min: this._min, + max: this._max, + data: [organisedEntry] + }); + } + } + return this; + }, + setData: function(data) { + var dataPoints = data.data; + var pointsLen = dataPoints.length; + + + // reset data arrays + this._data = []; + this._radi = []; + + for(var i = 0; i < pointsLen; i++) { + this._organiseData(dataPoints[i], false); + } + this._max = data.max; + this._min = data.min || 0; + + this._onExtremaChange(); + this._coordinator.emit('renderall', this._getInternalData()); + return this; + }, + removeData: function() { + // TODO: implement + }, + setDataMax: function(max) { + this._max = max; + this._onExtremaChange(); + this._coordinator.emit('renderall', this._getInternalData()); + return this; + }, + setDataMin: function(min) { + this._min = min; + this._onExtremaChange(); + this._coordinator.emit('renderall', this._getInternalData()); + return this; + }, + setCoordinator: function(coordinator) { + this._coordinator = coordinator; + }, + _getInternalData: function() { + return { + max: this._max, + min: this._min, + data: this._data, + radi: this._radi + }; + }, + getData: function() { + return this._unOrganizeData(); + }/*, + + TODO: rethink. + + getValueAt: function(point) { + var value; + var radius = 100; + var x = point.x; + var y = point.y; + var data = this._data; + + if (data[x] && data[x][y]) { + return data[x][y]; + } else { + var values = []; + // radial search for datapoints based on default radius + for(var distance = 1; distance < radius; distance++) { + var neighbors = distance * 2 +1; + var startX = x - distance; + var startY = y - distance; + + for(var i = 0; i < neighbors; i++) { + for (var o = 0; o < neighbors; o++) { + if ((i == 0 || i == neighbors-1) || (o == 0 || o == neighbors-1)) { + if (data[startY+i] && data[startY+i][startX+o]) { + values.push(data[startY+i][startX+o]); + } + } else { + continue; + } + } + } + } + if (values.length > 0) { + return Math.max.apply(Math, values); + } + } + return false; + }*/ + }; + + + return Store; +})(); + +var Canvas2dRenderer = (function Canvas2dRendererClosure() { + + var _getColorPalette = function(config) { + var gradientConfig = config.gradient || config.defaultGradient; + var paletteCanvas = document.createElement('canvas'); + var paletteCtx = paletteCanvas.getContext('2d'); + + paletteCanvas.width = 256; + paletteCanvas.height = 1; + + var gradient = paletteCtx.createLinearGradient(0, 0, 256, 1); + for (var key in gradientConfig) { + gradient.addColorStop(key, gradientConfig[key]); + } + + paletteCtx.fillStyle = gradient; + paletteCtx.fillRect(0, 0, 256, 1); + + return paletteCtx.getImageData(0, 0, 256, 1).data; + }; + + var _getPointTemplate = function(radius, blurFactor) { + var tplCanvas = document.createElement('canvas'); + var tplCtx = tplCanvas.getContext('2d'); + var x = radius; + var y = radius; + tplCanvas.width = tplCanvas.height = radius*2; + + if (blurFactor == 1) { + tplCtx.beginPath(); + tplCtx.arc(x, y, radius, 0, 2 * Math.PI, false); + tplCtx.fillStyle = 'rgba(0,0,0,1)'; + tplCtx.fill(); + } else { + var gradient = tplCtx.createRadialGradient(x, y, radius*blurFactor, x, y, radius); + gradient.addColorStop(0, 'rgba(0,0,0,1)'); + gradient.addColorStop(1, 'rgba(0,0,0,0)'); + tplCtx.fillStyle = gradient; + tplCtx.fillRect(0, 0, 2*radius, 2*radius); + } + + + + return tplCanvas; + }; + + var _prepareData = function(data) { + var renderData = []; + var min = data.min; + var max = data.max; + var radi = data.radi; + var data = data.data; + + var xValues = Object.keys(data); + var xValuesLen = xValues.length; + + while(xValuesLen--) { + var xValue = xValues[xValuesLen]; + var yValues = Object.keys(data[xValue]); + var yValuesLen = yValues.length; + while(yValuesLen--) { + var yValue = yValues[yValuesLen]; + var value = data[xValue][yValue]; + var radius = radi[xValue][yValue]; + renderData.push({ + x: xValue, + y: yValue, + value: value, + radius: radius + }); + } + } + + return { + min: min, + max: max, + data: renderData + }; + }; + + + function Canvas2dRenderer(config) { + var container = config.container; + var shadowCanvas = this.shadowCanvas = document.createElement('canvas'); + var canvas = this.canvas = config.canvas || document.createElement('canvas'); + var renderBoundaries = this._renderBoundaries = [10000, 10000, 0, 0]; + + var computed = getComputedStyle(config.container) || {}; + + canvas.className = 'heatmap-canvas'; + + this._width = canvas.width = shadowCanvas.width = config.width || +(computed.width.replace(/px/,'')); + this._height = canvas.height = shadowCanvas.height = config.height || +(computed.height.replace(/px/,'')); + + this.shadowCtx = shadowCanvas.getContext('2d'); + this.ctx = canvas.getContext('2d'); + + // @TODO: + // conditional wrapper + + canvas.style.cssText = shadowCanvas.style.cssText = 'position:absolute;left:0;top:0;'; + + container.style.position = 'relative'; + container.appendChild(canvas); + + this._palette = _getColorPalette(config); + this._templates = {}; + + this._setStyles(config); + }; + + Canvas2dRenderer.prototype = { + renderPartial: function(data) { + if (data.data.length > 0) { + this._drawAlpha(data); + this._colorize(); + } + }, + renderAll: function(data) { + // reset render boundaries + this._clear(); + if (data.data.length > 0) { + this._drawAlpha(_prepareData(data)); + this._colorize(); + } + }, + _updateGradient: function(config) { + this._palette = _getColorPalette(config); + }, + updateConfig: function(config) { + if (config['gradient']) { + this._updateGradient(config); + } + this._setStyles(config); + }, + setDimensions: function(width, height) { + this._width = width; + this._height = height; + this.canvas.width = this.shadowCanvas.width = width; + this.canvas.height = this.shadowCanvas.height = height; + }, + _clear: function() { + this.shadowCtx.clearRect(0, 0, this._width, this._height); + this.ctx.clearRect(0, 0, this._width, this._height); + }, + _setStyles: function(config) { + this._blur = (config.blur == 0)?0:(config.blur || config.defaultBlur); + + if (config.backgroundColor) { + this.canvas.style.backgroundColor = config.backgroundColor; + } + + this._width = this.canvas.width = this.shadowCanvas.width = config.width || this._width; + this._height = this.canvas.height = this.shadowCanvas.height = config.height || this._height; + + + this._opacity = (config.opacity || 0) * 255; + this._maxOpacity = (config.maxOpacity || config.defaultMaxOpacity) * 255; + this._minOpacity = (config.minOpacity || config.defaultMinOpacity) * 255; + this._useGradientOpacity = !!config.useGradientOpacity; + }, + _drawAlpha: function(data) { + var min = this._min = data.min; + var max = this._max = data.max; + var data = data.data || []; + var dataLen = data.length; + // on a point basis? + var blur = 1 - this._blur; + + while(dataLen--) { + + var point = data[dataLen]; + + var x = point.x; + var y = point.y; + var radius = point.radius; + // if value is bigger than max + // use max as value + var value = Math.min(point.value, max); + var rectX = x - radius; + var rectY = y - radius; + var shadowCtx = this.shadowCtx; + + + + + var tpl; + if (!this._templates[radius]) { + this._templates[radius] = tpl = _getPointTemplate(radius, blur); + } else { + tpl = this._templates[radius]; + } + // value from minimum / value range + // => [0, 1] + var templateAlpha = (value-min)/(max-min); + // this fixes #176: small values are not visible because globalAlpha < .01 cannot be read from imageData + shadowCtx.globalAlpha = templateAlpha < .01 ? .01 : templateAlpha; + + shadowCtx.drawImage(tpl, rectX, rectY); + + // update renderBoundaries + if (rectX < this._renderBoundaries[0]) { + this._renderBoundaries[0] = rectX; + } + if (rectY < this._renderBoundaries[1]) { + this._renderBoundaries[1] = rectY; + } + if (rectX + 2*radius > this._renderBoundaries[2]) { + this._renderBoundaries[2] = rectX + 2*radius; + } + if (rectY + 2*radius > this._renderBoundaries[3]) { + this._renderBoundaries[3] = rectY + 2*radius; + } + + } + }, + _colorize: function() { + var x = this._renderBoundaries[0]; + var y = this._renderBoundaries[1]; + var width = this._renderBoundaries[2] - x; + var height = this._renderBoundaries[3] - y; + var maxWidth = this._width; + var maxHeight = this._height; + var opacity = this._opacity; + var maxOpacity = this._maxOpacity; + var minOpacity = this._minOpacity; + var useGradientOpacity = this._useGradientOpacity; + + if (x < 0) { + x = 0; + } + if (y < 0) { + y = 0; + } + if (x + width > maxWidth) { + width = maxWidth - x; + } + if (y + height > maxHeight) { + height = maxHeight - y; + } + + var img = this.shadowCtx.getImageData(x, y, width, height); + var imgData = img.data; + var len = imgData.length; + var palette = this._palette; + + + for (var i = 3; i < len; i+= 4) { + var alpha = imgData[i]; + var offset = alpha * 4; + + + if (!offset) { + continue; + } + + var finalAlpha; + if (opacity > 0) { + finalAlpha = opacity; + } else { + if (alpha < maxOpacity) { + if (alpha < minOpacity) { + finalAlpha = minOpacity; + } else { + finalAlpha = alpha; + } + } else { + finalAlpha = maxOpacity; + } + } + + imgData[i-3] = palette[offset]; + imgData[i-2] = palette[offset + 1]; + imgData[i-1] = palette[offset + 2]; + imgData[i] = useGradientOpacity ? palette[offset + 3] : finalAlpha; + + } + + img.data = imgData; + this.ctx.putImageData(img, x, y); + + this._renderBoundaries = [1000, 1000, 0, 0]; + + }, + getValueAt: function(point) { + var value; + var shadowCtx = this.shadowCtx; + var img = shadowCtx.getImageData(point.x, point.y, 1, 1); + var data = img.data[3]; + var max = this._max; + var min = this._min; + + value = (Math.abs(max-min) * (data/255)) >> 0; + + return value; + }, + getDataURL: function() { + return this.canvas.toDataURL(); + } + }; + + + return Canvas2dRenderer; +})(); + + +var Renderer = (function RendererClosure() { + + var rendererFn = false; + + if (HeatmapConfig['defaultRenderer'] === 'canvas2d') { + rendererFn = Canvas2dRenderer; + } + + return rendererFn; +})(); + + +var Util = { + merge: function() { + var merged = {}; + var argsLen = arguments.length; + for (var i = 0; i < argsLen; i++) { + var obj = arguments[i] + for (var key in obj) { + merged[key] = obj[key]; + } + } + return merged; + } +}; +// Heatmap Constructor +var Heatmap = (function HeatmapClosure() { + + var Coordinator = (function CoordinatorClosure() { + + function Coordinator() { + this.cStore = {}; + }; + + Coordinator.prototype = { + on: function(evtName, callback, scope) { + var cStore = this.cStore; + + if (!cStore[evtName]) { + cStore[evtName] = []; + } + cStore[evtName].push((function(data) { + return callback.call(scope, data); + })); + }, + emit: function(evtName, data) { + var cStore = this.cStore; + if (cStore[evtName]) { + var len = cStore[evtName].length; + for (var i=0; i { + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("span", { + class: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeClass"])(["btn-flat", { + 'visActive': theHeatmapType.key === _ctx.heatmapType, + [`heatmapType${theHeatmapType.key}`]: true + }]), + onClick: $event => _ctx.changeHeatmapType(theHeatmapType.key), + key: theHeatmapType.key + }, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(theHeatmapType.name), 11, _hoisted_5); + }), 128)), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("h4", _hoisted_6, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_DeviceType')), 1), (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])(external_commonjs_vue_commonjs2_vue_root_Vue_["Fragment"], null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderList"])(_ctx.deviceTypesWithSamples, theDeviceType => { + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("span", { + class: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeClass"])(["btn-flat", { + 'visActive': theDeviceType.key === _ctx.deviceType, + [`deviceType${theDeviceType.key}`]: true + }]), + title: theDeviceType.tooltip, + onClick: $event => _ctx.changeDeviceType(theDeviceType.key), + key: theDeviceType.key + }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("img", { + height: "15", + src: theDeviceType.logo, + alt: `${_ctx.translate('DevicesDetection_Device')} ${theDeviceType.name}` + }, null, 8, _hoisted_8), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", _hoisted_9, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(theDeviceType.numSamples), 1)], 10, _hoisted_7); + }), 128)), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_10, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("h4", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('Installation_Legend')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_11, [_hoisted_12, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("img", { + class: "gradient", + alt: "gradient", + src: _ctx.gradientImgData + }, null, 8, _hoisted_13), _hoisted_14])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_15, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + style: { + "margin-left": "2.5rem", + "margin-right": "13.5px" + }, + textContent: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_Width')) + }, null, 8, _hoisted_16), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, { + uicontrol: "select", + name: "iframewidth", + "model-value": _ctx.customIframeWidth, + "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => { + _ctx.customIframeWidth = $event; + _ctx.changeIframeWidth(_ctx.customIframeWidth, true); + }), + options: _ctx.iframeWidthOptions + }, null, 8, ["model-value", "options"])])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_17, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_18, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_19, null, 512), _hoisted_20]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", { + class: "hsrLoadingOuter", + style: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeStyle"])([{ + "height": "400px" + }, { + width: _ctx.iframeWidth + 'px' + }]) + }, [_hoisted_21, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_22, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_23, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Loading')), 1)])], 4), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.isLoading]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", { + class: "aboveFoldLine", + title: _ctx.translate('HeatmapSessionRecording_AvgAboveFoldDescription'), + style: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeStyle"])({ + width: _ctx.iframeWidth + 'px', + top: _ctx.avgFold + 'px' + }) + }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_AvgAboveFoldTitle', _ctx.avgFold)), 1)], 12, _hoisted_24), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.avgFold]]), _ctx.embedUrl ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("iframe", { + key: 0, + id: "recordingPlayer", + ref: "recordingPlayer", + sandbox: "allow-scripts allow-same-origin", + referrerpolicy: "no-referrer", + onLoad: _cache[1] || (_cache[1] = $event => _ctx.onLoaded()), + height: "400", + src: _ctx.embedUrl, + width: _ctx.iframeWidth + }, null, 40, _hoisted_25)) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true)], 512), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_26, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_SaveButton, { + style: { + "display": "block !important" + }, + loading: _ctx.isLoading, + onClick: _cache[2] || (_cache[2] = $event => _ctx.deleteScreenshot()), + value: _ctx.translate('HeatmapSessionRecording_DeleteScreenshot') + }, null, 8, ["loading", "value"])], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.showDeleteScreenshot]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_27, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("h2", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_DeleteHeatmapScreenshotConfirm')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("input", { + role: "yes", + type: "button", + value: _ctx.translate('General_Yes') + }, null, 8, _hoisted_28), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("input", { + role: "no", + type: "button", + value: _ctx.translate('General_No') + }, null, 8, _hoisted_29)], 512), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Tooltip, { + ref: "tooltip", + "click-count": _ctx.clickCount, + "click-rate": _ctx.clickRate, + "is-moves": _ctx.heatmapType === 1 + }, null, 8, ["click-count", "click-rate", "is-moves"])]); +} +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/HeatmapVis/HeatmapVis.vue?vue&type=template&id=d1a5d966 + +// EXTERNAL MODULE: ./plugins/HeatmapSessionRecording/node_modules/heatmap.js/build/heatmap.js +var heatmap = __webpack_require__("246e"); +var heatmap_default = /*#__PURE__*/__webpack_require__.n(heatmap); + +// EXTERNAL MODULE: external "CoreHome" +var external_CoreHome_ = __webpack_require__("19dc"); + +// EXTERNAL MODULE: external "CorePluginsAdmin" +var external_CorePluginsAdmin_ = __webpack_require__("a5a2"); + +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/getIframeWindow.ts +/** + * Copyright (C) InnoCraft Ltd - All rights reserved. + * + * NOTICE: All information contained herein is, and remains the property of InnoCraft Ltd. + * The intellectual and technical concepts contained herein are protected by trade secret + * or copyright law. Redistribution of this information or reproduction of this material is + * strictly forbidden unless prior written permission is obtained from InnoCraft Ltd. + * + * You shall use this code only in accordance with the license agreement obtained from + * InnoCraft Ltd. + * + * @link https://www.innocraft.com/ + * @license For license details see https://www.innocraft.com/license + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function getIframeWindow(iframeElement) { + if (iframeElement && iframeElement.contentWindow) { + return iframeElement.contentWindow; + } + if (iframeElement && iframeElement.contentDocument && iframeElement.contentDocument.defaultView) { + return iframeElement.contentDocument.defaultView; + } + return undefined; +} +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/oneAtATime.ts +/** + * Copyright (C) InnoCraft Ltd - All rights reserved. + * + * NOTICE: All information contained herein is, and remains the property of InnoCraft Ltd. + * The intellectual and technical concepts contained herein are protected by trade secret + * or copyright law. Redistribution of this information or reproduction of this material is + * strictly forbidden unless prior written permission is obtained from InnoCraft Ltd. + * + * You shall use this code only in accordance with the license agreement obtained from + * InnoCraft Ltd. + * + * @link https://www.innocraft.com/ + * @license For license details see https://www.innocraft.com/license + */ + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function oneAtATime(method, options) { + let abortController = null; + return (params, postParams) => { + if (abortController) { + abortController.abort(); + abortController = null; + } + abortController = new AbortController(); + return external_CoreHome_["AjaxHelper"].post(Object.assign(Object.assign({}, params), {}, { + method + }), postParams, Object.assign(Object.assign({}, options), {}, { + abortController + })).finally(() => { + abortController = null; + }); + }; +} +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--13-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/Tooltip/Tooltip.vue?vue&type=template&id=6a6ace20 + +const Tooltipvue_type_template_id_6a6ace20_hoisted_1 = { + class: "tooltip-item" +}; +const Tooltipvue_type_template_id_6a6ace20_hoisted_2 = { + class: "tooltip-label" +}; +const Tooltipvue_type_template_id_6a6ace20_hoisted_3 = { + class: "tooltip-value" +}; +const Tooltipvue_type_template_id_6a6ace20_hoisted_4 = { + class: "tooltip-item" +}; +const Tooltipvue_type_template_id_6a6ace20_hoisted_5 = { + class: "tooltip-label" +}; +const Tooltipvue_type_template_id_6a6ace20_hoisted_6 = { + class: "tooltip-value" +}; +function Tooltipvue_type_template_id_6a6ace20_render(_ctx, _cache, $props, $setup, $data, $options) { + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])((Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", { + ref: "tooltipRef", + class: "tooltip", + style: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeStyle"])(_ctx.tooltipStyle) + }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Tooltipvue_type_template_id_6a6ace20_hoisted_1, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", Tooltipvue_type_template_id_6a6ace20_hoisted_2, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.getClickCountTranslation), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", Tooltipvue_type_template_id_6a6ace20_hoisted_3, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.getClickCount), 1)]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Tooltipvue_type_template_id_6a6ace20_hoisted_4, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", Tooltipvue_type_template_id_6a6ace20_hoisted_5, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.getClickRateTranslation), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", Tooltipvue_type_template_id_6a6ace20_hoisted_6, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.getClickRate), 1)])], 4)), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.visible]]); +} +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/Tooltip/Tooltip.vue?vue&type=template&id=6a6ace20 + +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--15-0!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--15-2!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/Tooltip/Tooltip.vue?vue&type=script&lang=ts + + +/* harmony default export */ var Tooltipvue_type_script_lang_ts = (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["defineComponent"])({ + props: { + clickCount: { + type: Number, + required: true + }, + clickRate: { + type: Number, + required: true + }, + isMoves: { + type: Boolean, + required: false, + default: false + } + }, + setup() { + const state = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["reactive"])({ + visible: false, + position: { + top: 0, + left: 0 + } + }); + const tooltipRef = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["ref"])(null); + const tooltipStyle = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["computed"])(() => ({ + top: `${state.position.top}px`, + left: `${state.position.left}px`, + position: 'absolute', + zIndex: 1000 + })); + function show(event) { + const scrollTop = window.scrollY || document.documentElement.scrollTop; + const scrollLeft = window.scrollX || document.documentElement.scrollLeft; + state.position.top = event.clientY + scrollTop + 10; + state.position.left = event.clientX + scrollLeft + 10; + state.visible = true; + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["nextTick"])(() => { + const tooltipElement = tooltipRef.value; + if (tooltipElement) { + const { + innerWidth, + innerHeight + } = window; + const tooltipRect = tooltipElement.getBoundingClientRect(); + if (tooltipRect.right > innerWidth) { + state.position.left = event.clientX + scrollLeft - tooltipRect.width - 10; + } + if (tooltipRect.bottom > innerHeight) { + state.position.top = event.clientY + scrollTop - tooltipRect.height - 10; + } + const adjustedTooltipRect = tooltipElement.getBoundingClientRect(); + if (adjustedTooltipRect.left < 0) { + state.position.left = scrollLeft + 10; + } + if (adjustedTooltipRect.top < 0) { + state.position.top = scrollTop + 10; + } + } + }); + } + function hide() { + state.visible = false; + } + return Object.assign(Object.assign({}, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toRefs"])(state)), {}, { + tooltipRef, + show, + hide, + tooltipStyle, + translate: external_CoreHome_["translate"] + }); + }, + computed: { + getClickCount() { + return external_CoreHome_["NumberFormatter"].formatNumber(this.clickCount); + }, + getClickRate() { + return external_CoreHome_["NumberFormatter"].formatPercent(this.clickRate); + }, + getClickCountTranslation() { + const translation = this.isMoves ? 'HeatmapSessionRecording_Moves' : 'HeatmapSessionRecording_Clicks'; + return Object(external_CoreHome_["translate"])(translation); + }, + getClickRateTranslation() { + const translation = this.isMoves ? 'HeatmapSessionRecording_MoveRate' : 'HeatmapSessionRecording_ClickRate'; + return Object(external_CoreHome_["translate"])(translation); + } + } +})); +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/Tooltip/Tooltip.vue?vue&type=script&lang=ts + +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/Tooltip/Tooltip.vue + + + +Tooltipvue_type_script_lang_ts.render = Tooltipvue_type_template_id_6a6ace20_render + +/* harmony default export */ var Tooltip = (Tooltipvue_type_script_lang_ts); +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--15-0!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--15-2!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/HeatmapVis/HeatmapVis.vue?vue&type=script&lang=ts + + + + + + + +const { + $ +} = window; +const deviceDesktop = 1; +const deviceTablet = 2; +const deviceMobile = 3; +let heightPerHeatmap = 32000; +const userAgent = String(window.navigator.userAgent).toLowerCase(); +if (userAgent.match(/(iPod|iPhone|iPad|Android|IEMobile|Windows Phone)/i)) { + heightPerHeatmap = 2000; +} else if (userAgent.indexOf('msie ') > 0 || userAgent.indexOf('trident/') > 0 || userAgent.indexOf('edge') > 0) { + heightPerHeatmap = 8000; +} +function initHeatmap(recordingPlayer, heatmapContainer, +// eslint-disable-next-line @typescript-eslint/no-explicit-any +recordingIframe) { + const $iframe = $(recordingPlayer); + // we first set the iframe to the initial 400px again so we can for sure detect the current + // height of the inner iframe body correctly + $iframe.css('height', '400px'); + const documentHeight = recordingIframe.getIframeHeight(); + $iframe.css('height', `${documentHeight}px`); + $(heatmapContainer).css('height', `${documentHeight}px`).css('width', `${$iframe.width()}px`).empty(); + const numHeatmaps = Math.ceil(documentHeight / heightPerHeatmap); + for (let i = 1; i <= numHeatmaps; i += 1) { + let height = heightPerHeatmap; + if (i === numHeatmaps) { + height = documentHeight % heightPerHeatmap; + } + $(heatmapContainer).append(`
    `); + $(heatmapContainer).find(`#heatmap${i}`).css({ + height: `${height}px` + }); + } + return numHeatmaps; +} +function scrollHeatmap(iframeRecordingContainer, recordingPlayer, +// eslint-disable-next-line @typescript-eslint/no-explicit-any +recordingIframe, scrollReaches) { + const $iframe = $(recordingPlayer); + // we first set the iframe to the initial 400px again so we can for sure detect the current + // height of the inner iframe body correctly + $iframe.css('height', '400px'); + const documentHeight = recordingIframe.getIframeHeight(); + $iframe.css('height', `${documentHeight}px`); + const numIntervals = 1000; + const heightToIntervalRatio = documentHeight / numIntervals; + const numViewersTotal = scrollReaches.reduce((pv, cv) => pv + parseInt(cv.value, 10), 0); + const buckets = []; + let num_viewers = 0; + let lastBucket = null; + let percentage = 100; + let reachScrolledFromPosition = 0; + // reachScrolledFromPosition we start from 0, and then always paint to the next bucket. eg when + // scrollReach is 27 and scrollDepth is 35, then we know that 27 people have scrolled down to + // 3.5% of the page. + scrollReaches.forEach(scrollReachObj => { + // the number of people that reached this point + const scrollReach = parseInt(scrollReachObj.value, 10); + // how far down they scrolled + const scrollDepth = parseInt(scrollReachObj.label, 10); + const reachScrolledToPosition = Math.round(scrollDepth * heightToIntervalRatio); + if (lastBucket && lastBucket.position === reachScrolledToPosition) { + // when page is < 1000 we need to aggregate buckets + num_viewers += scrollReach; + } else { + if (numViewersTotal !== 0) { + percentage = (numViewersTotal - num_viewers) / numViewersTotal * 100; + } + num_viewers += scrollReach; + // percentage.toFixed(1) * 10 => convert eg 99.8 => 998 + lastBucket = { + percentageValue: parseFloat(percentage.toFixed(1)) * 10, + position: reachScrolledFromPosition, + percent: percentage.toFixed(1) + }; + buckets.push(lastBucket); + } + reachScrolledFromPosition = reachScrolledToPosition; + }); + function map(value, istart, istop, ostart, ostop) { + return ostart + (ostop - ostart) * ((value - istart) / (istop - istart)); + } + function mapColorIntensity(intensity, min, max) { + if (min === max || !min && !max) { + return [255, 255, 0]; + } + const cint = map(intensity, min, max, 0, 255); + const step = (max - min) / 5; + if (cint > 204) { + return [255, map(intensity, max - step, max, 255, 0), 0]; + } + if (cint > 153) { + return [map(intensity, max - 2 * step, max - step, 0, 255), 255, 0]; + } + if (cint > 102) { + return [0, 255, map(intensity, max - 3 * step, max - 2 * step, 255, 0)]; + } + if (cint > 51) { + return [0, map(intensity, max - 4 * step, max - 3 * step, 0, 255), 255]; + } + return [map(intensity, min, max - 4 * step, 255, 0), 0, 255]; + } + if (buckets.length) { + // we need to make sure to draw scroll heatmap over full page + const found = buckets.some(b => b.position === 0); + if (!found) { + buckets.unshift({ + percent: '100.0', + percentageValue: 1000, + position: 0 + }); + } + } else { + // we'll show full page as not scrolled + buckets.push({ + percent: '0', + percentageValue: 0, + position: 0 + }); + } + let minValue = 0; + const maxValue = 1000; // max value is always 1000 (=100%) + if (buckets && buckets.length && buckets[0]) { + minValue = buckets[buckets.length - 1].percentageValue; + } + const iframeWidth = $iframe.width(); + let nextBucket = null; + for (let index = 0; index < buckets.length; index += 1) { + const bucket = buckets[index]; + if (buckets[index + 1]) { + nextBucket = buckets[index + 1]; + } else { + nextBucket = { + position: documentHeight + }; + } + const top = bucket.position; + let height = nextBucket.position - bucket.position; + if (height === 0) { + height = 1; // make sure to draw at least one px + } + const percent = `${bucket.percent} percent reached this point`; + const colorValues = mapColorIntensity(bucket.percentageValue, minValue, maxValue); + const color = `rgb(${colorValues.join(',')})`; + $(iframeRecordingContainer).append(`
    `); + } + $('.scrollHeatmapLeaf', iframeRecordingContainer).tooltip({ + track: true, + items: '*', + tooltipClass: 'heatmapTooltip', + show: false, + hide: false + }); + $('.legend-area .min').text(`${(minValue / 10).toFixed(1)}%`); + $('.legend-area .max').text(`${(maxValue / 10).toFixed(1)}%`); +} +function actualRenderHeatmap(recordingPlayer, heatmapContainer, +// eslint-disable-next-line @typescript-eslint/no-explicit-any +recordingIframe, dataPoints) { + const numHeatmaps = initHeatmap(recordingPlayer, heatmapContainer, recordingIframe); + const legendCanvas = document.createElement('canvas'); + legendCanvas.width = 100; + legendCanvas.height = 10; + const min = document.querySelector('.legend-area .min'); + const max = document.querySelector('.legend-area .max'); + const gradientImg = document.querySelector('.legend-area .gradient'); + const legendCtx = legendCanvas.getContext('2d'); + let gradientCfg = {}; + function updateLegend(data) { + // the onExtremaChange callback gives us min, max, and the gradientConfig + // so we can update the legend + min.innerHTML = `${data.min}`; + max.innerHTML = `${data.max}`; + // regenerate gradient image + if (data.gradient && data.gradient !== gradientCfg) { + gradientCfg = data.gradient; + const gradient = legendCtx.createLinearGradient(0, 0, 100, 1); + Object.keys(gradientCfg).forEach(key => { + gradient.addColorStop(parseFloat(key), gradientCfg[key]); + }); + legendCtx.fillStyle = gradient; + legendCtx.fillRect(0, 0, 100, 10); + gradientImg.src = legendCanvas.toDataURL(); + } + } + const heatmapInstances = []; + for (let i = 1; i <= numHeatmaps; i += 1) { + const dpoints = { + min: dataPoints.min, + max: dataPoints.max, + data: [] + }; + const config = { + container: document.getElementById(`heatmap${i}`), + radius: 10, + maxOpacity: 0.5, + minOpacity: 0, + blur: 0.75 + }; + if (i === 1) { + config.onExtremaChange = updateLegend; // typing is wrong here + } + if (dataPoints && dataPoints.data && dataPoints.data.length >= 20000) { + config.radius = 8; + } else if (dataPoints && dataPoints.data && dataPoints.data.length >= 2000) { + config.radius = 9; + } + if (numHeatmaps === 1) { + dpoints.data = dataPoints.data; + } else { + const lowerLimit = (i - 1) * heightPerHeatmap; + const upperLimit = lowerLimit + heightPerHeatmap - 1; + dataPoints.data.forEach(dp => { + if (dp.y >= lowerLimit && dp.y <= upperLimit) { + const thePoint = Object.assign(Object.assign({}, dp), {}, { + y: dp.y - lowerLimit + }); + dpoints.data.push(thePoint); + } + }); + } + const heatmapInstance = heatmap_default.a.create(config); + // heatmap type requires value to be number, but matomo sets it as string + heatmapInstance.setData(dpoints); + heatmapInstances.push(heatmapInstance); + } + return heatmapInstances; +} +/* harmony default export */ var HeatmapVisvue_type_script_lang_ts = (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["defineComponent"])({ + props: { + idSiteHsr: { + type: Number, + required: true + }, + deviceTypes: { + type: Array, + required: true + }, + heatmapTypes: { + type: Array, + required: true + }, + breakpointMobile: { + type: Number, + required: true + }, + breakpointTablet: { + type: Number, + required: true + }, + offsetAccuracy: { + type: Number, + required: true + }, + heatmapPeriod: { + type: String, + required: true + }, + heatmapDate: { + type: String, + required: true + }, + url: { + type: String, + required: true + }, + isActive: Boolean, + numSamples: { + type: Object, + required: true + }, + excludedElements: { + type: String, + required: true + }, + createdDate: { + type: String, + required: true + }, + desktopPreviewSize: { + type: Number, + required: true + }, + iframeResolutionsValues: { + type: Object, + required: true + } + }, + components: { + Field: external_CorePluginsAdmin_["Field"], + SaveButton: external_CorePluginsAdmin_["SaveButton"], + Tooltip: Tooltip + }, + data() { + return { + isLoading: false, + iframeWidth: this.desktopPreviewSize, + customIframeWidth: this.desktopPreviewSize, + avgFold: 0, + heatmapType: this.heatmapTypes[0].key, + deviceType: this.deviceTypes[0].key, + iframeResolutions: this.iframeResolutionsValues, + actualNumSamples: this.numSamples, + dataCoordinates: [], + currentElement: null, + totalClicks: 0, + tooltipShowTimeoutId: null, + clickCount: 0, + clickRate: 0 + }; + }, + setup(props) { + const tooltip = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["ref"])(null); + let iframeLoadedResolve = null; + const iframeLoadedPromise = new Promise(resolve => { + iframeLoadedResolve = resolve; + }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let recordingIframe = null; + const getRecordingIframe = recordingPlayer => { + if (!recordingIframe) { + recordingIframe = getIframeWindow(recordingPlayer).recordingFrame; + recordingIframe.excludeElements(props.excludedElements); + recordingIframe.addClass('html', 'piwikHeatmap'); + recordingIframe.addClass('html', 'matomoHeatmap'); + recordingIframe.addWorkaroundForSharepointHeatmaps(); + } + return recordingIframe; + }; + const heatmapInstances = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["ref"])(null); + const renderHeatmap = (recordingPlayer, heatmapContainer, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + theRecordingIframe, dataPoints) => { + heatmapInstances.value = actualRenderHeatmap(recordingPlayer, heatmapContainer, theRecordingIframe, dataPoints); + }; + return { + iframeLoadedPromise, + onLoaded: iframeLoadedResolve, + getRecordedHeatmap: oneAtATime('HeatmapSessionRecording.getRecordedHeatmap'), + getRecordedHeatmapMetadata: oneAtATime('HeatmapSessionRecording.getRecordedHeatmapMetadata'), + getRecordingIframe, + heatmapInstances, + renderHeatmap, + tooltip + }; + }, + created() { + if (this.iframeResolutions.indexOf(this.breakpointMobile) === -1) { + this.iframeResolutions.push(this.breakpointMobile); + } + if (this.iframeResolutions.indexOf(this.breakpointTablet) === -1) { + this.iframeResolutions.push(this.breakpointTablet); + } + this.iframeResolutions = this.iframeResolutions.sort((a, b) => a - b); + this.fetchHeatmap(); + // Hide the period selector since we don't filter the heatmap by period + external_CoreHome_["Matomo"].postEvent('hidePeriodSelector'); + }, + watch: { + isLoading() { + if (this.isLoading === true) { + return; + } + const heatmapContainer = window.document.getElementById('heatmapContainer'); + if (!heatmapContainer) { + return; + } + heatmapContainer.addEventListener('mouseleave', event => { + // Stop processing tooltip when moving mouse out of parent element + if (this.tooltipShowTimeoutId) { + clearTimeout(this.tooltipShowTimeoutId); + this.tooltipShowTimeoutId = null; + } + // Reset the highlight and tooltip when leaving the container + this.currentElement = null; + this.handleTooltip(event, 0, 0, 'hide'); + const highlightDiv = window.document.getElementById('highlightDiv'); + if (!highlightDiv) { + return; + } + highlightDiv.hidden = true; + }); + heatmapContainer.addEventListener('mousemove', e => { + this.handleMouseMove(e); + }); + } + }, + beforeUnmount() { + this.removeScrollHeatmap(); + }, + methods: { + removeScrollHeatmap() { + const element = this.$refs.iframeRecordingContainer; + $(element).find('.scrollHeatmapLeaf').remove(); + }, + deleteScreenshot() { + external_CoreHome_["Matomo"].helper.modalConfirm(this.$refs.confirmDeleteHeatmapScreenshot, { + yes: () => { + this.isLoading = true; + external_CoreHome_["AjaxHelper"].fetch({ + method: 'HeatmapSessionRecording.deleteHeatmapScreenshot', + idSiteHsr: this.idSiteHsr + }).then(() => { + this.isLoading = false; + window.location.reload(); + }); + } + }); + }, + fetchHeatmap() { + this.removeScrollHeatmap(); + if (this.heatmapInstances) { + const instances = this.heatmapInstances; + instances.forEach(heatmapInstance => { + heatmapInstance.setData({ + max: 1, + min: 0, + data: [] + }); + }); + } + this.isLoading = true; + this.avgFold = 0; + const segment = external_CoreHome_["MatomoUrl"].parsed.value.segment ? decodeURIComponent(external_CoreHome_["MatomoUrl"].parsed.value.segment) : undefined; + const requestParams = { + idSiteHsr: this.idSiteHsr, + heatmapType: this.heatmapType, + deviceType: this.deviceType, + period: this.heatmapPeriod, + date: this.heatmapDate, + filter_limit: -1, + segment + }; + const heatmapDataPromise = this.getRecordedHeatmap(requestParams); + const heatmapMetaDataPromise = this.getRecordedHeatmapMetadata(requestParams); + Promise.all([heatmapDataPromise, heatmapMetaDataPromise, this.iframeLoadedPromise]).then(response => { + const iframeElement = this.$refs.recordingPlayer; + const recordingIframe = this.getRecordingIframe(iframeElement); + initHeatmap(this.$refs.recordingPlayer, this.$refs.heatmapContainer, recordingIframe); + this.removeScrollHeatmap(); + const rows = response[0]; + const numSamples = response[1]; + if (Array.isArray(numSamples) && numSamples[0]) { + [this.actualNumSamples] = numSamples; + } else { + this.actualNumSamples = numSamples; + } + this.isLoading = false; + if (this.isScrollHeatmapType) { + scrollHeatmap(this.$refs.iframeRecordingContainer, iframeElement, recordingIframe, rows); + } else { + var _this$actualNumSample; + const dataPoints = { + min: 0, + max: 0, + data: [] + }; + for (let i = 0; i < rows.length; i += 1) { + const row = rows[i]; + if (row.selector) { + const dataPoint = recordingIframe.getCoordinatesInFrame(row.selector, row.offset_x, row.offset_y, this.offsetAccuracy, true); + if (dataPoint) { + dataPoint.value = row.value; + dataPoints.data.push(dataPoint); + this.dataCoordinates.push(dataPoint); + this.totalClicks += parseInt(row.value, 10); + } + } + } + if (this.heatmapType === 2) { + // click + let numEntriesHigherThan1 = 0; + dataPoints.data.forEach(dp => { + if (dp !== null && dp !== void 0 && dp.value && parseInt(dp.value, 10) > 1) { + numEntriesHigherThan1 += 1; + } + }); + if (numEntriesHigherThan1 / dataPoints.data.length >= 0.10 && dataPoints.data.length > 120) { + // if at least 10% have .value >= 2, then we set max to 2 to differntiate better + // between 1 and 2 clicks but only if we also have more than 80 data points + // ("randomly" chosen that threshold) + dataPoints.max = 2; + } else { + dataPoints.max = 1; + } + } else { + const LIMIT_MAX_DATA_POINT = 10; + const values = {}; + dataPoints.data.forEach(dp => { + if (!dp || !dp.value) { + return; + } + let value = parseInt(dp.value, 10); + if (value > dataPoints.max) { + dataPoints.max = value; + } + if (value > LIMIT_MAX_DATA_POINT) { + value = LIMIT_MAX_DATA_POINT; + } + const valueStr = `${value}`; + if (valueStr in values) { + values[valueStr] += 1; + } else { + values[valueStr] = 0; + } + }); + if (dataPoints.max > LIMIT_MAX_DATA_POINT) { + // we limit it to 10 otherwise many single points are not visible etc + // if there is no single entry having value 10, we set it to 9, 8 or 7 + // to make sure there is actually a dataPoint for this max value. + let sumValuesAboveThreshold = 0; + for (let k = LIMIT_MAX_DATA_POINT; k > 1; k -= 1) { + const kStr = `${k}`; + if (kStr in values) { + // we need to aggregate the value + sumValuesAboveThreshold += values[kStr]; + } + if (sumValuesAboveThreshold / dataPoints.data.length >= 0.2) { + // we make sure to have at least 20% of entries in that max value + dataPoints.max = k; + break; + } + // todo ideally in this case also require that akk 2 - (k-1) have a distribution + // of 0.2 to make sure we have enough values in between, and if not select k-1 or + // so. Otherwise we have maybe 75% with value 1, 20% with value 10, and only 5% in + // between... which would be barely visible those 75% maybe + } + if (dataPoints.max > LIMIT_MAX_DATA_POINT) { + // when no entry has more than 15% distribution, we set a default of 5 + dataPoints.max = 5; + for (let k = 5; k > 0; k -= 1) { + const kStr = `${k}`; + if (kStr in values) { + // we limit it to 10 otherwise many single points are not visible etc + // also if there is no single entry having value 10, we set it to 9, 8 or 7 + // to make sure there is actually a dataPoint for this max value. + dataPoints.max = k; + break; + } + } + } + } + } + this.renderHeatmap(this.$refs.recordingPlayer, this.$refs.heatmapContainer, recordingIframe, dataPoints); + if ((_this$actualNumSample = this.actualNumSamples) !== null && _this$actualNumSample !== void 0 && _this$actualNumSample[`avg_fold_device_${this.deviceType}`]) { + const avgFoldPercent = this.actualNumSamples[`avg_fold_device_${this.deviceType}`]; + const height = recordingIframe.getIframeHeight(); + if (height) { + this.avgFold = parseInt(`${avgFoldPercent / 100 * height}`, 10); + } + } + } + }).finally(() => { + this.isLoading = false; + }); + }, + changeDeviceType(deviceType) { + this.deviceType = deviceType; + if (this.deviceType === deviceDesktop) { + this.changeIframeWidth(this.desktopPreviewSize, false); + } else if (this.deviceType === deviceTablet) { + this.changeIframeWidth(this.breakpointTablet || 960, false); + } else if (this.deviceType === deviceMobile) { + this.changeIframeWidth(this.breakpointMobile || 600, false); + } + }, + changeIframeWidth(iframeWidth, scrollToTop) { + this.iframeWidth = iframeWidth; + this.customIframeWidth = this.iframeWidth; + this.totalClicks = 0; + this.dataCoordinates = []; + this.fetchHeatmap(); + if (scrollToTop) { + external_CoreHome_["Matomo"].helper.lazyScrollToContent(); + } + }, + changeHeatmapType(heatmapType) { + this.heatmapType = heatmapType; + this.totalClicks = 0; + this.clickCount = 0; + this.clickRate = 0; + this.dataCoordinates = []; + this.fetchHeatmap(); + }, + handleMouseMove(event) { + const highlightDiv = window.document.getElementById('highlightDiv'); + if (!highlightDiv) { + return; + } + // Keep the tooltip from showing until the cursor has stopped moving + if (this.tooltipShowTimeoutId) { + clearTimeout(this.tooltipShowTimeoutId); + this.tooltipShowTimeoutId = null; + this.currentElement = null; + } + // If the highlight is visible, move the tooltip around with the cursor + if (!highlightDiv.hidden) { + this.handleTooltip(event, 0, 0, 'move'); + } + const element = this.lookUpRecordedElementAtEventLocation(event); + // If there's no element, don't do anything else + // If the element hasn't changed, there's no need to do anything else + if (!element || element === this.currentElement) { + return; + } + this.handleTooltip(event, 0, 0, 'hide'); + highlightDiv.hidden = true; + const elementRect = element.getBoundingClientRect(); + let elementClicks = 0; + this.dataCoordinates.forEach(dataPoint => { + // Return if the dataPoint isn't within the element + if (dataPoint.y < elementRect.top || dataPoint.y > elementRect.bottom || dataPoint.x < elementRect.left || dataPoint.x > elementRect.right) { + return; + } + elementClicks += parseInt(dataPoint.value, 10); + }); + // Have a slight delay so that it's not jarring when it displays + this.tooltipShowTimeoutId = setTimeout(() => { + this.currentElement = element; + highlightDiv.hidden = false; + // Multiplying by 10000 and then dividing by 100 to get 2 decimal points of precision + const clickRate = this.totalClicks ? Math.round(elementClicks / this.totalClicks * 10000) / 100 : 0; + const rect = element.getBoundingClientRect(); + highlightDiv.style.top = `${rect.top}px`; + highlightDiv.style.left = `${rect.left}px`; + highlightDiv.style.width = `${rect.width}px`; + highlightDiv.style.height = `${rect.height}px`; + this.handleTooltip(event, elementClicks, clickRate, 'show'); + this.tooltipShowTimeoutId = null; + }, 100); + }, + lookUpRecordedElementAtEventLocation(event) { + const targetElement = event.target; + if (!targetElement) { + return null; + } + const frameElement = window.document.getElementById('recordingPlayer'); + if (!frameElement) { + return null; + } + const frameRef = frameElement.contentWindow ? frameElement.contentWindow.document : frameElement.contentDocument; + if (!frameRef) { + return null; + } + const rect = targetElement.getBoundingClientRect(); + return frameRef.elementFromPoint(event.clientX - rect.left, event.clientY - rect.top); + }, + handleTooltip(event, clickCount, clickRate, action) { + if (this.tooltip) { + if (action === 'show') { + this.clickCount = clickCount; + this.clickRate = clickRate; + this.tooltip.show(event); + } else if (action === 'move') { + this.tooltip.show(event); + } else { + this.tooltip.hide(); + } + } + } + }, + computed: { + isScrollHeatmapType() { + return this.heatmapType === 3; + }, + tokenAuth() { + return external_CoreHome_["MatomoUrl"].parsed.value.token_auth; + }, + embedUrl() { + return `?${external_CoreHome_["MatomoUrl"].stringify({ + module: 'HeatmapSessionRecording', + action: 'embedPage', + idSite: external_CoreHome_["Matomo"].idSite, + idSiteHsr: this.idSiteHsr, + token_auth: this.tokenAuth || undefined + })}`; + }, + iframeWidthOptions() { + return this.iframeResolutions.map(width => ({ + key: width, + value: `${width}px` + })); + }, + recordedSamplesSince() { + const string1 = Object(external_CoreHome_["translate"])('HeatmapSessionRecording_HeatmapXRecordedSamplesSince', `${this.actualNumSamples.nb_samples_device_all}`, this.createdDate); + const linkString = Object(external_CoreHome_["externalLink"])('https://matomo.org/faq/heatmap-session-recording/troubleshooting-heatmaps/'); + const string2 = Object(external_CoreHome_["translate"])('HeatmapSessionRecording_HeatmapTroubleshoot', linkString, ''); + return `${string1} ${string2}`; + }, + deviceTypesWithSamples() { + return this.deviceTypes.map(deviceType => { + let numSamples; + if (this.actualNumSamples[`nb_samples_device_${deviceType.key}`]) { + numSamples = this.actualNumSamples[`nb_samples_device_${deviceType.key}`]; + } else { + numSamples = 0; + } + const tooltip = Object(external_CoreHome_["translate"])('HeatmapSessionRecording_XSamples', `${deviceType.name} - ${numSamples}`); + return Object.assign(Object.assign({}, deviceType), {}, { + numSamples, + tooltip + }); + }); + }, + hasWriteAccess() { + return !!(external_CoreHome_["Matomo"] !== null && external_CoreHome_["Matomo"] !== void 0 && external_CoreHome_["Matomo"].heatmapWriteAccess); + }, + showDeleteScreenshot() { + return this.isActive && this.hasWriteAccess; + }, + gradientImgData() { + return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAKCAYAAABCHPt+AAAAnklEQVRYR+2WQQq' + 'DQBAES5wB/f8/Y05RcMWwSu6JIT0Dm4WlH1DUdHew7/z6WYFhhnGRpnlhAEaQpi/ADbh/np0MiBhGhW+2ymFU+DZ' + 'fg1EhaoB4jCFuMYYcQKZrXwPEVvm5Og0pcYakBvI35G1jNIZ4jCHexxjSpz9ZFUjAynLbpOvqteaODkm9sloz5JF' + '+ZTVmSAWSu9Qb65AvgDwBQoLgVDlWfAQAAAAASUVORK5CYII='; + } + } +})); +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/HeatmapVis/HeatmapVis.vue?vue&type=script&lang=ts + +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/HeatmapVis/HeatmapVis.vue + + + +HeatmapVisvue_type_script_lang_ts.render = render + +/* harmony default export */ var HeatmapVis = (HeatmapVisvue_type_script_lang_ts); +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--13-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/SessionRecordingVis/SessionRecordingVis.vue?vue&type=template&id=6f77b61e + +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_1 = { + class: "sessionRecordingPlayer" +}; +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_2 = { + class: "controls" +}; +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_3 = { + class: "playerActions" +}; +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_4 = ["title"]; +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_5 = ["title"]; +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_6 = ["title"]; +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_7 = ["title"]; +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_8 = ["title"]; +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_9 = ["title"]; +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_10 = ["title"]; +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_11 = ["title"]; +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_12 = { + version: "1.1", + xmlns: "http://www.w3.org/2000/svg", + width: "20", + height: "20", + viewBox: "0 0 768 768" +}; +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_13 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("path", { + d: "M480 576.5v-321h-64.5v129h-63v-129h-64.5v192h127.5v129h64.5zM607.5 127.999c34.5 0\n 64.5 30 64.5 64.5v447c0 34.5-30 64.5-64.5 64.5h-447c-34.5\n 0-64.5-30-64.5-64.5v-447c0-34.5 30-64.5 64.5-64.5h447z" +}, null, -1); +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_14 = [SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_13]; +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_15 = { + version: "1.1", + xmlns: "http://www.w3.org/2000/svg", + width: "20", + height: "20", + viewBox: "0 0 768 768" +}; +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_16 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("path", { + d: "M448.5 576.5v-321h-129v64.5h64.5v256.5h64.5zM607.5 127.999c34.5 0 64.5 30 64.5\n 64.5v447c0 34.5-30 64.5-64.5 64.5h-447c-34.5 0-64.5-30-64.5-64.5v-447c0-34.5\n 30-64.5 64.5-64.5h447z" +}, null, -1); +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_17 = [SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_16]; +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_18 = { + version: "1.1", + xmlns: "http://www.w3.org/2000/svg", + width: "20", + height: "20", + viewBox: "0 0 768 768" +}; +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_19 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("path", { + d: "M480 384.5v-64.5c0-36-30-64.5-64.5-64.5h-127.5v64.5h127.5v64.5h-63c-34.5 0-64.5\n 27-64.5 63v129h192v-64.5h-127.5v-64.5h63c34.5 0 64.5-27 64.5-63zM607.5 127.999c34.5\n 0 64.5 30 64.5 64.5v447c0 34.5-30 64.5-64.5 64.5h-447c-34.5\n 0-64.5-30-64.5-64.5v-447c0-34.5 30-64.5 64.5-64.5h447z" +}, null, -1); +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_20 = [SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_19]; +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_21 = { + version: "1.1", + xmlns: "http://www.w3.org/2000/svg", + width: "20", + height: "20", + viewBox: "0 0 768 768" +}; +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_22 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("path", { + d: "M480 320v-64.5h-127.5c-34.5 0-64.5 28.5-64.5 64.5v192c0 36 30 64.5 64.5\n 64.5h63c34.5 0 64.5-28.5 64.5-64.5v-64.5c0-36-30-63-64.5-63h-63v-64.5h127.5zM607.5\n 127.999c34.5 0 64.5 30 64.5 64.5v447c0 34.5-30 64.5-64.5 64.5h-447c-34.5\n 0-64.5-30-64.5-64.5v-447c0-34.5 30-64.5 64.5-64.5h447zM352.5 512v-64.5h63v64.5h-63z" +}, null, -1); +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_23 = [SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_22]; +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_24 = ["title"]; +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_25 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("svg", { + version: "1.1", + xmlns: "http://www.w3.org/2000/svg", + width: "20", + height: "20", + viewBox: "0 0 768 768" +}, [/*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("path", { + d: "M223.5 415.5h111l-64.5-63h-46.5v63zM72 72l624 624-42 40.5-88.5-90c-51 36-114\n 57-181.5 57-177 0-319.5-142.5-319.5-319.5 0-67.5 21-130.5 57-181.5l-90-88.5zM544.5\n 352.5h-111l-231-231c51-36 114-57 181.5-57 177 0 319.5 142.5 319.5 319.5 0 67.5-21\n 130.5-57 181.5l-148.5-150h46.5v-63z" +})], -1); +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_26 = [SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_25]; +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_27 = ["title"]; +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_28 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("svg", { + version: "1.1", + xmlns: "http://www.w3.org/2000/svg", + width: "22", + height: "22", + viewBox: "0 0 768 768" +}, [/*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("path", { + d: "M544.5 609v-129h63v192h-384v96l-127.5-127.5 127.5-127.5v96h321zM223.5\n 288v129h-63v-192h384v-96l127.5 127.5-127.5 127.5v-96h-321z" +})], -1); +const SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_29 = [SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_28]; +const _hoisted_30 = { + class: "duration" +}; +const _hoisted_31 = { + class: "playerHelp" +}; +const _hoisted_32 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "clickEvent" +}, null, -1); +const _hoisted_33 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "moveEvent" +}, null, -1); +const _hoisted_34 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "scrollEvent" +}, null, -1); +const _hoisted_35 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "resizeEvent" +}, null, -1); +const _hoisted_36 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "formChange" +}, null, -1); +const _hoisted_37 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "mutationEvent" +}, null, -1); +const _hoisted_38 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("br", { + style: { + "clear": "right" + } +}, null, -1); +const _hoisted_39 = ["title"]; +const _hoisted_40 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("br", null, null, -1); +const _hoisted_41 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", { + class: "loadingUnderlay" +}, null, -1); +const _hoisted_42 = { + class: "valign-wrapper loadingInner" +}; +const _hoisted_43 = { + class: "loadingContent" +}; +const _hoisted_44 = ["src", "width", "height"]; +function SessionRecordingVisvue_type_template_id_6f77b61e_render(_ctx, _cache, $props, $setup, $data, $options) { + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_1, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_2, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_3, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "playerAction icon-skip-previous", + title: _ctx.skipPreviousButtonTitle, + onClick: _cache[0] || (_cache[0] = $event => _ctx.loadNewRecording(_ctx.previousRecordingId)) + }, null, 8, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_4), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.previousRecordingId]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "playerAction icon-fast-rewind", + title: _ctx.translate('HeatmapSessionRecording_PlayerRewindFast', 10, 'J'), + onClick: _cache[1] || (_cache[1] = $event => _ctx.jumpRelative(10, false)) + }, null, 8, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_5), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "playerAction icon-play", + title: _ctx.translate('HeatmapSessionRecording_PlayerPlay', 'K'), + onClick: _cache[2] || (_cache[2] = $event => _ctx.play()) + }, null, 8, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_6), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], !_ctx.isPlaying && !_ctx.isFinished]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "playerAction icon-replay", + title: _ctx.translate('HeatmapSessionRecording_PlayerReplay', 'K'), + onClick: _cache[3] || (_cache[3] = $event => _ctx.replay()) + }, null, 8, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_7), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], !_ctx.isPlaying && _ctx.isFinished]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "playerAction icon-pause", + title: _ctx.translate('HeatmapSessionRecording_PlayerPause', 'K'), + onClick: _cache[4] || (_cache[4] = $event => _ctx.pause()) + }, null, 8, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_8), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.isPlaying]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "playerAction icon-fast-forward", + title: _ctx.translate('HeatmapSessionRecording_PlayerForwardFast', 10, 'L'), + onClick: _cache[5] || (_cache[5] = $event => _ctx.jumpRelative(10, true)) + }, null, 8, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_9), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "playerAction icon-skip-next", + title: _ctx.translate('HeatmapSessionRecording_PlayerPageViewNext', _ctx.nextRecordingInfo, 'N'), + onClick: _cache[6] || (_cache[6] = $event => _ctx.loadNewRecording(_ctx.nextRecordingId)) + }, null, 8, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_10), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.nextRecordingId]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "changeReplaySpeed", + title: _ctx.translate('HeatmapSessionRecording_ChangeReplaySpeed', 'S'), + onClick: _cache[7] || (_cache[7] = $event => _ctx.increaseReplaySpeed()) + }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])((Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("svg", SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_12, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_14, 512)), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.actualReplaySpeed === 4]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])((Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("svg", SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_15, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_17, 512)), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.actualReplaySpeed === 1]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])((Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("svg", SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_18, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_20, 512)), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.actualReplaySpeed === 2]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])((Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("svg", SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_21, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_23, 512)), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.actualReplaySpeed === 6]])], 8, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_11), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeClass"])(["toggleSkipPause", { + 'active': _ctx.actualSkipPausesEnabled + }]), + title: _ctx.translate('HeatmapSessionRecording_ClickToSkipPauses', _ctx.skipPausesEnabledText, 'B'), + onClick: _cache[8] || (_cache[8] = $event => _ctx.toggleSkipPauses()) + }, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_26, 10, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_24), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeClass"])(["toggleAutoPlay", { + 'active': _ctx.actualAutoPlayEnabled + }]), + title: _ctx.translate('HeatmapSessionRecording_AutoPlayNextPageview', _ctx.autoplayEnabledText, 'A'), + onClick: _cache[9] || (_cache[9] = $event => _ctx.toggleAutoPlay()) + }, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_29, 10, SessionRecordingVisvue_type_template_id_6f77b61e_hoisted_27), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", _hoisted_30, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_PlayerDurationXofY', _ctx.positionPretty, _ctx.durationPretty)), 1)]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_31, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("ul", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("li", null, [_hoisted_32, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(" " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_ActivityClick')), 1)]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("li", null, [_hoisted_33, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(" " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_ActivityMove')), 1)]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("li", null, [_hoisted_34, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(" " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_ActivityScroll')), 1)]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("li", null, [_hoisted_35, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(" " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_ActivityResize')), 1)]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("li", null, [_hoisted_36, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(" " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_ActivityFormChange')), 1)]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("li", null, [_hoisted_37, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(" " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_ActivityPageChange')), 1)])])]), _hoisted_38]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", { + class: "timelineOuter", + onClick: _cache[10] || (_cache[10] = $event => _ctx.seekEvent($event)), + style: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeStyle"])({ + width: `${_ctx.replayWidth}px` + }) + }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", { + class: "timelineInner", + style: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeStyle"])({ + width: `${_ctx.progress}%` + }) + }, null, 4), (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])(external_commonjs_vue_commonjs2_vue_root_Vue_["Fragment"], null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderList"])(_ctx.clues, (clue, index) => { + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", { + title: clue.title, + class: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeClass"])(clue.type), + style: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeStyle"])({ + left: `${clue.left}%` + }), + key: index + }, null, 14, _hoisted_39); + }), 128))], 4), _hoisted_40, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", { + class: "hsrLoadingOuter", + style: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeStyle"])({ + width: `${_ctx.replayWidth}px`, + height: `${_ctx.replayHeight}px` + }) + }, [_hoisted_41, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_42, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", _hoisted_43, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Loading')), 1)])], 4), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.isLoading]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", { + class: "replayContainerOuter", + onClick: _cache[12] || (_cache[12] = $event => _ctx.togglePlay()), + style: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeStyle"])({ + height: `${_ctx.replayHeight}px`, + width: `${_ctx.replayWidth}px` + }) + }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", { + class: "replayContainerInner", + style: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeStyle"])([{ + "transform-origin": "0 0" + }, { + transform: `scale(${_ctx.replayScale})`, + 'margin-left': `${_ctx.replayMarginLeft}px` + }]) + }, [_ctx.embedUrl ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("iframe", { + key: 0, + id: "recordingPlayer", + ref: "recordingPlayer", + onLoad: _cache[11] || (_cache[11] = $event => _ctx.onLoaded()), + scrolling: "no", + sandbox: "allow-scripts allow-same-origin", + referrerpolicy: "no-referrer", + src: _ctx.embedUrl, + width: _ctx.recording.viewport_w_px, + height: _ctx.recording.viewport_h_px + }, null, 40, _hoisted_44)) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true)], 4)], 4)]); +} +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/SessionRecordingVis/SessionRecordingVis.vue?vue&type=template&id=6f77b61e + +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--15-0!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--15-2!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/SessionRecordingVis/SessionRecordingVis.vue?vue&type=script&lang=ts + + + +const FRAME_STEP = 20; +const EVENT_TYPE_MOVEMENT = 1; +const EVENT_TYPE_CLICK = 2; +const EVENT_TYPE_SCROLL = 3; +const EVENT_TYPE_RESIZE = 4; +const EVENT_TYPE_INITIAL_DOM = 5; +const EVENT_TYPE_MUTATION = 6; +const EVENT_TYPE_FORM_TEXT = 9; +const EVENT_TYPE_FORM_VALUE = 10; +const EVENT_TYPE_SCROLL_ELEMENT = 12; +const EVENT_TYPE_TO_NAME = { + [EVENT_TYPE_CLICK]: 'clickEvent', + [EVENT_TYPE_MOVEMENT]: 'moveEvent', + [EVENT_TYPE_SCROLL]: 'scrollEvent', + [EVENT_TYPE_SCROLL_ELEMENT]: 'scrollEvent', + [EVENT_TYPE_RESIZE]: 'resizeEvent', + [EVENT_TYPE_FORM_TEXT]: 'formChange', + [EVENT_TYPE_FORM_VALUE]: 'formChange', + [EVENT_TYPE_INITIAL_DOM]: 'mutationEvent', + [EVENT_TYPE_MUTATION]: 'mutationEvent' +}; +const EVENT_TYPE_TO_TITLE = { + [EVENT_TYPE_CLICK]: Object(external_CoreHome_["translate"])('HeatmapSessionRecording_ActivityClick'), + [EVENT_TYPE_MOVEMENT]: Object(external_CoreHome_["translate"])('HeatmapSessionRecording_ActivityMove'), + [EVENT_TYPE_SCROLL]: Object(external_CoreHome_["translate"])('HeatmapSessionRecording_ActivityScroll'), + [EVENT_TYPE_SCROLL_ELEMENT]: Object(external_CoreHome_["translate"])('HeatmapSessionRecording_ActivityScroll'), + [EVENT_TYPE_RESIZE]: Object(external_CoreHome_["translate"])('HeatmapSessionRecording_ActivityResize'), + [EVENT_TYPE_FORM_TEXT]: Object(external_CoreHome_["translate"])('HeatmapSessionRecording_ActivityFormChange'), + [EVENT_TYPE_FORM_VALUE]: Object(external_CoreHome_["translate"])('HeatmapSessionRecording_ActivityFormChange'), + [EVENT_TYPE_INITIAL_DOM]: Object(external_CoreHome_["translate"])('HeatmapSessionRecording_ActivityPageChange'), + [EVENT_TYPE_MUTATION]: Object(external_CoreHome_["translate"])('HeatmapSessionRecording_ActivityPageChange') +}; +const MOUSE_POINTER_HTML = ` +
    + + + +
    +`; +const { + $: SessionRecordingVisvue_type_script_lang_ts_$, + Mousetrap +} = window; +function intVal(v) { + return typeof v === 'number' ? v : parseInt(v, 10); +} +function getEventTypeId(event) { + if (!(event !== null && event !== void 0 && event.event_type)) { + return undefined; + } + return intVal(event.event_type); +} +function toPrettyTimeFormat(milliseconds) { + const durationSeconds = Math.floor(milliseconds / 1000); + let minutes = Math.floor(durationSeconds / 60); + let secondsLeft = durationSeconds % 60; + if (minutes < 10) { + minutes = `0${minutes}`; + } + if (secondsLeft < 10) { + secondsLeft = `0${secondsLeft}`; + } + return `${minutes}:${secondsLeft}`; +} +// TODO use something like command pattern and redo actions for each action maybe for more effecient +// and better looking eeking to an earlier position in the video etc: Problem mutations can likely +// not be "undone" +/* harmony default export */ var SessionRecordingVisvue_type_script_lang_ts = (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["defineComponent"])({ + props: { + offsetAccuracy: { + type: Number, + required: true + }, + scrollAccuracy: { + type: Number, + required: true + }, + autoPlayEnabled: Boolean, + skipPausesEnabled: Boolean, + replaySpeed: { + type: Number, + default: 1 + } + }, + data() { + return { + isPlaying: false, + progress: 0, + isFinished: false, + isLoading: true, + seekTimeout: null, + lastFramePainted: 0, + recording: JSON.parse(JSON.stringify(window.sessionRecordingData)), + positionPretty: '00:00', + previousRecordingId: null, + previousRecordingInfo: null, + nextRecordingId: null, + nextRecordingInfo: null, + frame: 0, + hasFoundPrevious: false, + hasFoundNext: false, + videoPlayerInterval: null, + lastCanvasCoordinates: false, + actualAutoPlayEnabled: !!this.autoPlayEnabled, + replayWidth: 0, + replayHeight: 0, + replayScale: 0, + replayMarginLeft: 0, + seek: seekToFrame => seekToFrame, + actualSkipPausesEnabled: !!this.skipPausesEnabled, + actualReplaySpeed: this.replaySpeed + }; + }, + setup() { + const iframeLoaded = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["ref"])(false); + let iframeLoadedResolve = null; + const iframeLoadedPromise = new Promise(resolve => { + iframeLoadedResolve = resolve; + iframeLoaded.value = true; + }); + const onLoaded = () => { + setTimeout(() => { + // just to be sure we wait for another 500ms + iframeLoadedResolve('loaded'); + }, 500); + }; + return { + iframeLoadedPromise, + onLoaded, + iframeLoaded + }; + }, + created() { + this.recording.duration = intVal(this.recording.duration); + this.recording.pageviews.forEach(pageview => { + if (!pageview || !pageview.idloghsr) { + return; + } + if (`${pageview.idloghsr}` === `${this.recording.idLogHsr}`) { + this.hasFoundPrevious = true; + } else if (!this.hasFoundPrevious) { + this.previousRecordingId = pageview.idloghsr; + this.previousRecordingInfo = [pageview.label, pageview.server_time_pretty, pageview.time_on_page_pretty].join(' - '); + } else if (!this.hasFoundNext) { + this.hasFoundNext = true; + this.nextRecordingId = pageview.idloghsr; + this.nextRecordingInfo = [pageview.label, pageview.server_time_pretty, pageview.time_on_page_pretty].join(' - '); + } + }); + }, + mounted() { + Mousetrap.bind(['space', 'k'], () => { + this.togglePlay(); + }); + Mousetrap.bind('0', () => { + if (this.isFinished) { + this.replay(); + } + }); + Mousetrap.bind('p', () => { + this.loadNewRecording(this.previousRecordingId); + }); + Mousetrap.bind('n', () => { + this.loadNewRecording(this.nextRecordingId); + }); + Mousetrap.bind('s', () => { + this.increaseReplaySpeed(); + }); + Mousetrap.bind('a', () => { + this.toggleAutoPlay(); + }); + Mousetrap.bind('b', () => { + this.toggleSkipPauses(); + }); + Mousetrap.bind('left', () => { + const numSeconds = 5; + const jumpForward = false; + this.jumpRelative(numSeconds, jumpForward); + }); + Mousetrap.bind('right', () => { + const numSeconds = 5; + const jumpForward = true; + this.jumpRelative(numSeconds, jumpForward); + }); + Mousetrap.bind('j', () => { + const numSeconds = 10; + const jumpForward = false; + this.jumpRelative(numSeconds, jumpForward); + }); + Mousetrap.bind('l', () => { + const numSeconds = 10; + const jumpForward = true; + this.jumpRelative(numSeconds, jumpForward); + }); + this.initViewport(); + SessionRecordingVisvue_type_script_lang_ts_$(window).on('resize', () => this.initViewport()); + this.iframeLoadedPromise.then(() => { + this.initPlayer(); + }); + window.addEventListener('beforeunload', () => { + // should improve reload / go to next page performance + this.isPlaying = false; + if (this.videoPlayerInterval) { + clearInterval(this.videoPlayerInterval); + this.videoPlayerInterval = null; + } + }); + }, + methods: { + initPlayer() { + const iframeElement = this.$refs.recordingPlayer; + const recordingIframe = getIframeWindow(iframeElement).recordingFrame; + if (!recordingIframe || !recordingIframe.isSupportedBrowser()) { + return; + } + recordingIframe.addClass('html', 'piwikSessionRecording'); + recordingIframe.addClass('html', 'matomoSessionRecording'); + let $mousePointerNode = null; + const drawMouseLine = (coordinates, color) => { + if ($mousePointerNode) { + $mousePointerNode.css({ + left: `${coordinates.x - 8}px`, + top: `${coordinates.y - 8}px` + }); + } + if (!this.lastCanvasCoordinates) { + return; + } + recordingIframe.drawLine(this.lastCanvasCoordinates.x, this.lastCanvasCoordinates.y, coordinates.x, coordinates.y, color); + this.lastCanvasCoordinates = coordinates; + }; + const scrollFrameTo = (xPos, yPos) => { + if (!this.lastCanvasCoordinates || !$mousePointerNode) { + // we cannot move the mouse pointer since we do not have the initial mouse position yet + // only perform scroll action instead + recordingIframe.scrollTo(xPos, yPos); + return; + } + // we only move the mouse pointer but not draw a line for the mouse movement eg when user + // scrolls we also make sure that when the next time the user moves the mouse the mouse + // move line will be drawn from this new position + const currentScrollTop = recordingIframe.getScrollTop(); + const currentScrollLeft = recordingIframe.getScrollLeft(); + recordingIframe.scrollTo(xPos, yPos); + // we detect how far down or up user scrolled (or to the left or right) + const diffScrollTop = yPos - currentScrollTop; + const diffScrollLeft = xPos - currentScrollLeft; + // if user scrolled eg 100px down, we also need to move the cursor down + let newMousePointerPosLeft = diffScrollLeft + this.lastCanvasCoordinates.x; + let newMousePointerPosTop = diffScrollTop + this.lastCanvasCoordinates.y; + if (newMousePointerPosLeft <= 0) { + newMousePointerPosLeft = 0; + } + if (newMousePointerPosTop <= 0) { + newMousePointerPosTop = 0; + } + // we make sure to draw the next mouse move line from this position. we use a blue line + // to indicate the mouse was moved by a scroll + drawMouseLine({ + x: newMousePointerPosLeft, + y: newMousePointerPosTop + }, 'blue'); + }; + const scrollElementTo = (element, xPos, yPos) => { + if (element !== null && element !== void 0 && element.scrollTo) { + element.scrollTo(xPos, yPos); + } else { + element.scrollLeft = xPos; + element.scrollTop = yPos; + } + }; + let moveMouseTo = null; + const replayEvent = event => { + // fixes some concurrency problems etc by not continueing in the player until the current + // action is drawn + const { + isPlaying + } = this; + this.isPlaying = false; + const eventType = getEventTypeId(event); + let offset = null; + if (eventType === EVENT_TYPE_MOVEMENT) { + if (event.selector) { + offset = recordingIframe.getCoordinatesInFrame(event.selector, event.x, event.y, this.offsetAccuracy, false); + if (offset) { + moveMouseTo(offset); + } + } + } else if (eventType === EVENT_TYPE_CLICK) { + if (event.selector) { + offset = recordingIframe.getCoordinatesInFrame(event.selector, event.x, event.y, this.offsetAccuracy, false); + if (offset) { + moveMouseTo(offset); + recordingIframe.drawCircle(offset.x, offset.y, '#ff9407'); + } + } + } else if (eventType === EVENT_TYPE_MUTATION) { + if (event.text) { + recordingIframe.applyMutation(event.text); + } + } else if (eventType === EVENT_TYPE_SCROLL) { + const docHeight = recordingIframe.getIframeHeight(); + const docWidth = recordingIframe.getIframeWidth(); + const yPos = parseInt(`${docHeight / this.scrollAccuracy * intVal(event.y)}`, 10); + const xPos = parseInt(`${docWidth / this.scrollAccuracy * intVal(event.x)}`, 10); + scrollFrameTo(xPos, yPos); + } else if (eventType === EVENT_TYPE_SCROLL_ELEMENT) { + if (event.selector) { + const element = recordingIframe.findElement(event.selector); + if (element && element.length && element[0]) { + const eleHeight = Math.max(element[0].scrollHeight, element[0].offsetHeight, element.height(), 0); + const eleWidth = Math.max(element[0].scrollWidth, element[0].offsetWidth, element.width(), 0); + if (eleHeight && eleWidth) { + const yPos = parseInt(`${eleHeight / this.scrollAccuracy * intVal(event.y)}`, 10); + const xPos = parseInt(`${eleWidth / this.scrollAccuracy * intVal(event.x)}`, 10); + scrollElementTo(element[0], xPos, yPos); + } + } + } + } else if (eventType === EVENT_TYPE_RESIZE) { + this.setViewportResolution(event.x, event.y); + } else if (eventType === EVENT_TYPE_FORM_TEXT) { + if (event.selector) { + const formElement = recordingIframe.findElement(event.selector); + if (formElement.length) { + const formAttrType = formElement.attr('type'); + if (formAttrType && `${formAttrType}`.toLowerCase() === 'file') { + // cannot be changed to local file, would result in error + } else { + formElement.val(event.text).change(); + } + } + } + } else if (eventType === EVENT_TYPE_FORM_VALUE) { + if (event.selector) { + const $field = recordingIframe.findElement(event.selector); + if ($field.is('input')) { + $field.prop('checked', event.text === 1 || event.text === '1'); + } else if ($field.is('select')) { + $field.val(event.text).change(); + } + } + } + this.isPlaying = isPlaying; + }; + moveMouseTo = coordinates => { + const resizeStage = () => { + const stageWidth = recordingIframe.getIframeWidth(); + const stageHeight = recordingIframe.getIframeHeight(); + recordingIframe.makeSvg(stageWidth, stageHeight); + for (let crtFrame = 0; crtFrame <= this.frame; crtFrame += FRAME_STEP) { + if (!this.timeFrameBuckets[crtFrame]) { + return; + } + this.timeFrameBuckets[crtFrame].forEach(event => { + const eventType = getEventTypeId(event); + if (eventType === EVENT_TYPE_MOVEMENT || eventType === EVENT_TYPE_SCROLL || eventType === EVENT_TYPE_SCROLL_ELEMENT || eventType === EVENT_TYPE_CLICK) { + this.lastFramePainted = crtFrame; + replayEvent(event); + } + }); + } + }; + // Runs each time the DOM window resize event fires. + // Resets the canvas dimensions to match window, + // then draws the new borders accordingly. + const iframeWindow = recordingIframe.getIframeWindow(); + if (!this.lastCanvasCoordinates) { + const stageHeight = recordingIframe.getIframeHeight(); + const stageWidth = recordingIframe.getIframeWidth(); + recordingIframe.appendContent(MOUSE_POINTER_HTML); + $mousePointerNode = recordingIframe.findElement('.mousePointer'); + recordingIframe.makeSvg(stageWidth, stageHeight); + iframeWindow.removeEventListener('resize', resizeStage, false); + iframeWindow.addEventListener('resize', resizeStage, false); + this.lastCanvasCoordinates = coordinates; + $mousePointerNode.css({ + left: `${coordinates.x - 8}px`, + top: `${coordinates.y - 8}px` + }); + return; + } + let scrollTop = recordingIframe.getScrollTop(); + const scrollLeft = recordingIframe.getScrollLeft(); + if (coordinates.y > scrollTop + intVal(this.recording.viewport_h_px)) { + recordingIframe.scrollTo(scrollLeft, coordinates.y - 10); + } else if (coordinates.y < scrollTop) { + recordingIframe.scrollTo(scrollLeft, coordinates.y - 10); + } + scrollTop = recordingIframe.getScrollTop(); + if (coordinates.x > scrollLeft + intVal(this.recording.viewport_w_px)) { + recordingIframe.scrollTo(coordinates.x - 10, scrollTop); + } else if (coordinates.x < scrollLeft) { + recordingIframe.scrollTo(coordinates.x - 10, scrollTop); + } + drawMouseLine(coordinates, '#ff9407'); + }; + this.seek = seekToFrame => { + if (!this.iframeLoaded) { + return; + } + // this operation may take a while so we want to stop any interval and further action + // until this is completed + this.isLoading = true; + let previousFrame = this.frame; + const executeSeek = thePreviousFrame => { + for (let crtFrame = thePreviousFrame; crtFrame <= this.frame; crtFrame += FRAME_STEP) { + (this.timeFrameBuckets[crtFrame] || []).forEach(event => { + this.lastFramePainted = crtFrame; + replayEvent(event); + }); + } + }; + this.isFinished = false; + this.frame = seekToFrame - seekToFrame % FRAME_STEP; + this.progress = parseFloat(parseFloat(`${this.frame / intVal(this.recording.duration) * 100}`).toFixed(2)); + this.positionPretty = toPrettyTimeFormat(this.frame); + if (previousFrame > this.frame) { + // we start replaying the video from the beginning + previousFrame = 0; + this.lastCanvasCoordinates = false; + if (this.initialMutation) { + recordingIframe.initialMutation(this.initialMutation.text); + } + recordingIframe.scrollTo(0, 0); + this.setViewportResolution(window.sessionRecordingData.viewport_w_px, window.sessionRecordingData.viewport_h_px); + if (this.seekTimeout) { + clearTimeout(this.seekTimeout); + this.seekTimeout = null; + // make sure when user goes to previous position and we have a timeout to not execute + // it multiple times + } + (thePreviousFrame => { + this.seekTimeout = setTimeout(() => { + executeSeek(thePreviousFrame); + this.isLoading = false; + }, 1050); + })(previousFrame); + } else { + // otherwise we instead play fast forward all new actions for faster performance and + // smoother visualization etc + if (this.seekTimeout) { + clearTimeout(this.seekTimeout); + this.seekTimeout = null; + } + executeSeek(previousFrame); + this.isLoading = false; + } + }; + this.isLoading = false; + this.isPlaying = true; + let updateTimeCounter = 0; + const drawFrames = () => { + if (this.isPlaying && !this.isLoading) { + updateTimeCounter += 1; + const duration = intVal(this.recording.duration); + if (this.frame >= duration) { + this.isPlaying = false; + this.progress = 100; + this.isFinished = true; + this.positionPretty = this.durationPretty; + if (this.actualAutoPlayEnabled && this.nextRecordingId) { + this.loadNewRecording(this.nextRecordingId); + } + } else { + this.progress = parseFloat(parseFloat(`${this.frame / duration * 100}`).toFixed(2)); + if (updateTimeCounter === 20) { + updateTimeCounter = 0; + this.positionPretty = toPrettyTimeFormat(this.frame); + } + } + (this.timeFrameBuckets[this.frame] || []).forEach(event => { + // remember when we last painted a frame + this.lastFramePainted = this.frame; + replayEvent(event); + }); + if (this.actualSkipPausesEnabled && this.frame - this.lastFramePainted > 1800) { + // after 1.8 seconds of not painting anything, move forward to next action + let keys = Object.keys(this.timeFrameBuckets).map(k => parseInt(k, 10)); + keys = keys.sort((a, b) => a - b); + const nextFrameKey = keys.find(key => key > this.frame); + const hasNextFrame = !!nextFrameKey; + if (nextFrameKey) { + const isMoreThan1SecInFuture = nextFrameKey - this.frame > 1000; + if (isMoreThan1SecInFuture) { + // we set the pointer foward to the next frame printable + // we only move forward if we can save at least one second. + // we set the cursor to shortly before the next action. + this.frame = nextFrameKey - 20 * FRAME_STEP; + } + } + // if no frame found, skip to the end of the recording + if (!hasNextFrame) { + const isMoreThan1SecInFuture = duration - this.frame > 1000; + if (isMoreThan1SecInFuture) { + // we don't set it to very end to still have something to play + this.frame = duration - 20 * FRAME_STEP; + } + } + } + this.frame += FRAME_STEP; + } + }; + this.videoPlayerInterval = setInterval(() => { + for (let k = 1; k <= this.actualReplaySpeed; k += 1) { + drawFrames(); + } + }, FRAME_STEP); + }, + initViewport() { + this.replayHeight = SessionRecordingVisvue_type_script_lang_ts_$(window).height() - 48 - SessionRecordingVisvue_type_script_lang_ts_$('.sessionRecording .sessionRecordingHead').outerHeight(true) - SessionRecordingVisvue_type_script_lang_ts_$('.sessionRecordingPlayer .controls').outerHeight(true); + this.replayWidth = SessionRecordingVisvue_type_script_lang_ts_$(window).width() - 48; + const viewportwpx = intVal(this.recording.viewport_w_px); + const viewporthpx = intVal(this.recording.viewport_h_px); + const minReplayWidth = 400; + if (this.replayWidth < minReplayWidth && viewportwpx > minReplayWidth) { + this.replayWidth = minReplayWidth; + } + const minReplayHeight = 400; + if (this.replayHeight < minReplayHeight && viewporthpx > minReplayHeight) { + this.replayHeight = minReplayHeight; + } + let widthScale = 1; + let heightScale = 1; + if (viewportwpx > this.replayWidth) { + widthScale = parseFloat(parseFloat(`${this.replayWidth / viewportwpx}`).toFixed(4)); + } + if (viewporthpx > this.replayHeight) { + heightScale = parseFloat(parseFloat(`${this.replayHeight / viewporthpx}`).toFixed(4)); + } + this.replayScale = Math.min(widthScale, heightScale); + this.replayMarginLeft = (this.replayWidth - this.replayScale * viewportwpx) / 2; + }, + setViewportResolution(widthPx, heightPx) { + this.recording.viewport_w_px = parseInt(`${widthPx}`, 10); + this.recording.viewport_h_px = parseInt(`${heightPx}`, 10); + SessionRecordingVisvue_type_script_lang_ts_$('.recordingWidth').text(widthPx); + SessionRecordingVisvue_type_script_lang_ts_$('.recordingHeight').text(heightPx); + this.initViewport(); + }, + increaseReplaySpeed() { + if (this.actualReplaySpeed === 1) { + this.actualReplaySpeed = 2; + } else if (this.actualReplaySpeed === 2) { + this.actualReplaySpeed = 4; + } else if (this.actualReplaySpeed === 4) { + this.actualReplaySpeed = 6; + } else { + this.actualReplaySpeed = 1; + } + this.updateSettings(); + }, + updateSettings() { + external_CoreHome_["AjaxHelper"].fetch({ + module: 'HeatmapSessionRecording', + action: 'saveSessionRecordingSettings', + autoplay: this.actualAutoPlayEnabled ? 1 : 0, + skippauses: this.actualSkipPausesEnabled ? 1 : 0, + replayspeed: this.actualReplaySpeed + }, { + format: 'html' + }); + }, + toggleAutoPlay() { + this.actualAutoPlayEnabled = !this.actualAutoPlayEnabled; + this.updateSettings(); + }, + toggleSkipPauses() { + this.actualSkipPausesEnabled = !this.actualSkipPausesEnabled; + this.updateSettings(); + }, + loadNewRecording(idLogHsr) { + if (idLogHsr) { + this.isPlaying = false; + external_CoreHome_["MatomoUrl"].updateUrl(Object.assign(Object.assign({}, external_CoreHome_["MatomoUrl"].urlParsed.value), {}, { + idLogHsr: parseInt(`${idLogHsr}`, 10), + updated: external_CoreHome_["MatomoUrl"].urlParsed.value.updated ? parseInt(external_CoreHome_["MatomoUrl"].urlParsed.value.updated, 10) + 1 : 1 + })); + } + }, + jumpRelative(numberSeconds, forward) { + const framesToJump = numberSeconds * 1000; + let newPosition; + if (forward) { + newPosition = this.frame + framesToJump; + if (newPosition > this.recording.duration) { + newPosition = intVal(this.recording.duration) - FRAME_STEP; + } + } else { + newPosition = this.frame - framesToJump; + if (newPosition < 0) { + newPosition = 0; + } + } + this.seek(newPosition); + }, + replay() { + this.isFinished = false; + this.lastFramePainted = 0; + this.seek(0); + this.play(); + }, + pause() { + this.isPlaying = false; + }, + togglePlay() { + if (this.isFinished) { + this.replay(); + } else if (this.isPlaying) { + this.pause(); + } else { + this.play(); + } + }, + seekEvent(event) { + const offset = SessionRecordingVisvue_type_script_lang_ts_$(event.currentTarget).offset(); + const selectedPosition = event.pageX - offset.left; + const fullWidth = this.replayWidth; + const seekPercentage = selectedPosition / fullWidth; + const seekPositionTime = intVal(this.recording.duration) * seekPercentage; + this.seek(seekPositionTime); + }, + play() { + this.isPlaying = true; + } + }, + computed: { + durationPretty() { + return toPrettyTimeFormat(intVal(this.recording.duration)); + }, + embedUrl() { + return `?${external_CoreHome_["MatomoUrl"].stringify({ + module: 'HeatmapSessionRecording', + action: 'embedPage', + idSite: this.recording.idSite, + idLogHsr: this.recording.idLogHsr, + idSiteHsr: this.recording.idSiteHsr, + // NOTE: important to get the token_auth from the URL directly, since if there is no + // token_auth there, we should send nothing. In this case, Matomo.token_auth will still + // be set, so we can't check that variable here. + token_auth: external_CoreHome_["MatomoUrl"].urlParsed.value.token_auth || undefined + })}`; + }, + skipPreviousButtonTitle() { + return Object(external_CoreHome_["translate"])('HeatmapSessionRecording_PlayerPageViewPrevious', this.previousRecordingInfo || '', 'P'); + }, + skipPausesEnabledText() { + if (this.actualSkipPausesEnabled) { + return Object(external_CoreHome_["translate"])('HeatmapSessionRecording_disable'); + } + return Object(external_CoreHome_["translate"])('HeatmapSessionRecording_enable'); + }, + autoplayEnabledText() { + if (this.actualAutoPlayEnabled) { + return Object(external_CoreHome_["translate"])('HeatmapSessionRecording_disable'); + } + return Object(external_CoreHome_["translate"])('HeatmapSessionRecording_enable'); + }, + recordingEvents() { + if (!this.recording) { + return []; + } + return this.recording.events.map(theEvent => { + const eventType = getEventTypeId(theEvent); + let { + text + } = theEvent; + if ((eventType === EVENT_TYPE_INITIAL_DOM || eventType === EVENT_TYPE_MUTATION) && typeof text === 'string') { + text = JSON.parse(text); + } + return Object.assign(Object.assign({}, theEvent), {}, { + text + }); + }); + }, + initialMutation() { + const initialEvent = this.recordingEvents.find(e => { + const eventType = getEventTypeId(e); + const isMutation = eventType === EVENT_TYPE_INITIAL_DOM || eventType === EVENT_TYPE_MUTATION; + const isInitialMutation = isMutation && (eventType === EVENT_TYPE_INITIAL_DOM || !e.time_since_load || e.time_since_load === '0'); + return isInitialMutation; + }); + return initialEvent; + }, + timeFrameBuckets() { + const result = {}; + this.recordingEvents.forEach(event => { + if (event === this.initialMutation) { + return; + } + const bucket = Math.round(intVal(event.time_since_load) / FRAME_STEP) * FRAME_STEP; + result[bucket] = result[bucket] || []; + result[bucket].push(event); + }); + return result; + }, + clues() { + const result = []; + this.recordingEvents.forEach(event => { + if (event === this.initialMutation) { + return; + } + const eventTypeId = getEventTypeId(event); + const eventType = EVENT_TYPE_TO_NAME[eventTypeId] || ''; + const eventTitle = EVENT_TYPE_TO_TITLE[eventTypeId] || ''; + if (eventType) { + if ((event.time_since_load === 0 || event.time_since_load === '0') && eventType === 'moveEvent') { + // this is the initial mouse position and we ignore it in the clues since we cannot + // draw a line to it + return; + } + result.push({ + left: parseFloat(`${intVal(event.time_since_load) / intVal(this.recording.duration) * 100}`).toFixed(2), + type: eventType, + title: eventTitle + }); + } + }); + return result; + } + } +})); +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/SessionRecordingVis/SessionRecordingVis.vue?vue&type=script&lang=ts + +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/SessionRecordingVis/SessionRecordingVis.vue + + + +SessionRecordingVisvue_type_script_lang_ts.render = SessionRecordingVisvue_type_template_id_6f77b61e_render + +/* harmony default export */ var SessionRecordingVis = (SessionRecordingVisvue_type_script_lang_ts); +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--13-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/HsrTargetTest/HsrTargetTest.vue?vue&type=template&id=6eb3a085 + +const HsrTargetTestvue_type_template_id_6eb3a085_hoisted_1 = { + class: "form-group hsrTargetTest" +}; +const HsrTargetTestvue_type_template_id_6eb3a085_hoisted_2 = { + class: "loadingPiwik loadingMatchingSteps" +}; +const HsrTargetTestvue_type_template_id_6eb3a085_hoisted_3 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("img", { + src: "plugins/Morpheus/images/loading-blue.gif", + alt: "" +}, null, -1); +const HsrTargetTestvue_type_template_id_6eb3a085_hoisted_4 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", { + id: "hsrTargetValidationError" +}, null, -1); +function HsrTargetTestvue_type_template_id_6eb3a085_render(_ctx, _cache, $props, $setup, $data, $options) { + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", HsrTargetTestvue_type_template_id_6eb3a085_hoisted_1, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("label", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("strong", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_TargetPageTestTitle')) + ":", 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(" " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_TargetPageTestLabel')), 1)]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("input", { + type: "text", + id: "urltargettest", + placeholder: "http://www.example.com/", + "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => _ctx.url = $event), + class: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeClass"])({ + 'invalid': _ctx.url && !_ctx.matches && _ctx.isValid + }) + }, null, 2), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vModelText"], _ctx.url]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "testInfo" + }, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_TargetPageTestErrorInvalidUrl')), 513), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.url && !_ctx.isValid]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "testInfo matches" + }, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_TargetPageTestUrlMatches')), 513), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.url && _ctx.matches && _ctx.isValid]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "testInfo notMatches" + }, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_TargetPageTestUrlNotMatches')), 513), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.url && !_ctx.matches && _ctx.isValid]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", HsrTargetTestvue_type_template_id_6eb3a085_hoisted_2, [HsrTargetTestvue_type_template_id_6eb3a085_hoisted_3, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_LoadingData')), 1)], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.isLoadingTestMatchPage]])]), HsrTargetTestvue_type_template_id_6eb3a085_hoisted_4]); +} +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/HsrTargetTest/HsrTargetTest.vue?vue&type=template&id=6eb3a085 + +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--15-0!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--15-2!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/HsrTargetTest/HsrTargetTest.vue?vue&type=script&lang=ts + + + +function isValidUrl(url) { + return url.indexOf('://') > 3; +} +/* harmony default export */ var HsrTargetTestvue_type_script_lang_ts = (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["defineComponent"])({ + props: { + includedTargets: Array + }, + data() { + return { + url: '', + matches: false, + isLoadingTestMatchPage: false + }; + }, + watch: { + isValid(newVal) { + if (!newVal) { + this.matches = false; + } + }, + includedTargets() { + this.runTest(); + }, + url() { + this.runTest(); + } + }, + setup() { + return { + testUrlMatchPages: oneAtATime('HeatmapSessionRecording.testUrlMatchPages', { + errorElement: '#hsrTargetValidationError' + }) + }; + }, + created() { + // we wait for 200ms before actually sending a request as user might be still typing + this.runTest = Object(external_CoreHome_["debounce"])(this.runTest, 200); + }, + methods: { + checkIsMatchingUrl() { + if (!this.isValid) { + return; + } + const url = this.targetUrl; + const included = this.filteredIncludedTargets; + if (!(included !== null && included !== void 0 && included.length)) { + return; + } + this.isLoadingTestMatchPage = true; + this.testUrlMatchPages({ + url + }, { + matchPageRules: included + }).then(response => { + var _this$filteredInclude; + if (!((_this$filteredInclude = this.filteredIncludedTargets) !== null && _this$filteredInclude !== void 0 && _this$filteredInclude.length) || (response === null || response === void 0 ? void 0 : response.url) !== this.targetUrl) { + return; + } + this.matches = response.matches; + }).finally(() => { + this.isLoadingTestMatchPage = false; + }); + }, + runTest() { + if (!this.isValid) { + return; + } + this.checkIsMatchingUrl(); + } + }, + computed: { + targetUrl() { + return (this.url || '').trim(); + }, + isValid() { + return this.targetUrl && isValidUrl(this.targetUrl); + }, + filteredIncludedTargets() { + if (!this.includedTargets) { + return undefined; + } + return this.includedTargets.filter(target => (target === null || target === void 0 ? void 0 : target.value) || (target === null || target === void 0 ? void 0 : target.type) === 'any').map(target => Object.assign(Object.assign({}, target), {}, { + value: target.value ? target.value.trim() : '' + })); + } + } +})); +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/HsrTargetTest/HsrTargetTest.vue?vue&type=script&lang=ts + +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/HsrTargetTest/HsrTargetTest.vue + + + +HsrTargetTestvue_type_script_lang_ts.render = HsrTargetTestvue_type_template_id_6eb3a085_render + +/* harmony default export */ var HsrTargetTest = (HsrTargetTestvue_type_script_lang_ts); +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--13-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/HsrUrlTarget/HsrUrlTarget.vue?vue&type=template&id=4c1d8b92 + +const HsrUrlTargetvue_type_template_id_4c1d8b92_hoisted_1 = { + style: { + "width": "100%" + } +}; +const HsrUrlTargetvue_type_template_id_4c1d8b92_hoisted_2 = { + name: "targetAttribute" +}; +const HsrUrlTargetvue_type_template_id_4c1d8b92_hoisted_3 = { + name: "targetType" +}; +const HsrUrlTargetvue_type_template_id_4c1d8b92_hoisted_4 = { + name: "targetValue" +}; +const HsrUrlTargetvue_type_template_id_4c1d8b92_hoisted_5 = { + name: "targetValue2" +}; +const HsrUrlTargetvue_type_template_id_4c1d8b92_hoisted_6 = ["title"]; +const HsrUrlTargetvue_type_template_id_4c1d8b92_hoisted_7 = ["title"]; +function HsrUrlTargetvue_type_template_id_4c1d8b92_render(_ctx, _cache, $props, $setup, $data, $options) { + const _component_Field = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("Field"); + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", { + class: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeClass"])(["form-group hsrUrltarget valign-wrapper", { + 'disabled': _ctx.disableIfNoValue && !_ctx.modelValue.value + }]) + }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", HsrUrlTargetvue_type_template_id_4c1d8b92_hoisted_1, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", HsrUrlTargetvue_type_template_id_4c1d8b92_hoisted_2, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, { + uicontrol: "select", + name: "targetAttribute", + "model-value": _ctx.modelValue.attribute, + "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => _ctx.$emit('update:modelValue', Object.assign(Object.assign({}, _ctx.modelValue), {}, { + attribute: $event + }))), + title: _ctx.translate('HeatmapSessionRecording_Rule'), + options: _ctx.targetAttributes, + "full-width": true + }, null, 8, ["model-value", "title", "options"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", HsrUrlTargetvue_type_template_id_4c1d8b92_hoisted_3, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, { + uicontrol: "select", + name: "targetType", + "model-value": _ctx.pattern_type, + "onUpdate:modelValue": _cache[1] || (_cache[1] = $event => { + _ctx.onTypeChange($event); + }), + options: _ctx.targetOptions[_ctx.modelValue.attribute], + "full-width": true + }, null, 8, ["model-value", "options"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", HsrUrlTargetvue_type_template_id_4c1d8b92_hoisted_4, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, { + uicontrol: "text", + name: "targetValue", + placeholder: `eg. ${_ctx.targetExamples[_ctx.modelValue.attribute]}`, + "model-value": _ctx.modelValue.value, + "onUpdate:modelValue": _cache[2] || (_cache[2] = $event => _ctx.$emit('update:modelValue', Object.assign(Object.assign({}, _ctx.modelValue), {}, { + value: $event.trim() + }))), + maxlength: 500, + "full-width": true + }, null, 8, ["placeholder", "model-value"]), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.pattern_type !== 'any']])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", HsrUrlTargetvue_type_template_id_4c1d8b92_hoisted_5, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, { + uicontrol: "text", + name: "targetValue2", + "model-value": _ctx.modelValue.value2, + "onUpdate:modelValue": _cache[3] || (_cache[3] = $event => _ctx.$emit('update:modelValue', Object.assign(Object.assign({}, _ctx.modelValue), {}, { + value2: $event.trim() + }))), + maxlength: 500, + "full-width": true, + placeholder: _ctx.translate('HeatmapSessionRecording_UrlParameterValueToMatchPlaceholder') + }, null, 8, ["model-value", "placeholder"]), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.modelValue.attribute === 'urlparam' && _ctx.pattern_type && _ctx.pattern_type !== 'exists' && _ctx.pattern_type !== 'not_exists']])])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "icon-plus valign", + title: _ctx.translate('General_Add'), + onClick: _cache[4] || (_cache[4] = $event => _ctx.$emit('addUrl')) + }, null, 8, HsrUrlTargetvue_type_template_id_4c1d8b92_hoisted_6), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.showAddUrl]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "icon-minus valign", + title: _ctx.translate('General_Remove'), + onClick: _cache[5] || (_cache[5] = $event => _ctx.$emit('removeUrl')) + }, null, 8, HsrUrlTargetvue_type_template_id_4c1d8b92_hoisted_7), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.canBeRemoved]])], 2); +} +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/HsrUrlTarget/HsrUrlTarget.vue?vue&type=template&id=4c1d8b92 + +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/HsrUrlTarget/AvailableTargetPageRules.store.ts +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } +/** + * Copyright (C) InnoCraft Ltd - All rights reserved. + * + * NOTICE: All information contained herein is, and remains the property of InnoCraft Ltd. + * The intellectual and technical concepts contained herein are protected by trade secret + * or copyright law. Redistribution of this information or reproduction of this material is + * strictly forbidden unless prior written permission is obtained from InnoCraft Ltd. + * + * You shall use this code only in accordance with the license agreement obtained from + * InnoCraft Ltd. + * + * @link https://www.innocraft.com/ + * @license For license details see https://www.innocraft.com/license + */ + + +class AvailableTargetPageRules_store_AvailableTargetPageRulesStore { + constructor() { + _defineProperty(this, "privateState", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["reactive"])({ + rules: [] + })); + _defineProperty(this, "state", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["computed"])(() => Object(external_commonjs_vue_commonjs2_vue_root_Vue_["readonly"])(this.privateState))); + _defineProperty(this, "rules", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["computed"])(() => this.state.value.rules)); + _defineProperty(this, "initPromise", null); + } + init() { + if (this.initPromise) { + return this.initPromise; + } + this.initPromise = external_CoreHome_["AjaxHelper"].fetch({ + method: 'HeatmapSessionRecording.getAvailableTargetPageRules', + filter_limit: '-1' + }).then(response => { + this.privateState.rules = response; + return this.rules.value; + }); + return this.initPromise; + } +} +/* harmony default export */ var AvailableTargetPageRules_store = (new AvailableTargetPageRules_store_AvailableTargetPageRulesStore()); +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--15-0!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--15-2!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/HsrUrlTarget/HsrUrlTarget.vue?vue&type=script&lang=ts + + + + +/* harmony default export */ var HsrUrlTargetvue_type_script_lang_ts = (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["defineComponent"])({ + props: { + modelValue: { + type: Object, + required: true + }, + canBeRemoved: Boolean, + disableIfNoValue: Boolean, + allowAny: Boolean, + showAddUrl: Boolean + }, + components: { + Field: external_CorePluginsAdmin_["Field"] + }, + emits: ['addUrl', 'removeUrl', 'update:modelValue'], + created() { + AvailableTargetPageRules_store.init(); + }, + watch: { + modelValue(newValue) { + if (!newValue.attribute) { + return; + } + const types = this.targetOptions[newValue.attribute]; + const found = types.find(t => t.key === this.pattern_type); + if (!found && types[0]) { + this.onTypeChange(types[0].key); + } + } + }, + computed: { + pattern_type() { + let result = this.modelValue.type; + if (this.modelValue.inverted && this.modelValue.inverted !== '0') { + result = `not_${this.modelValue.type}`; + } + return result; + }, + targetAttributes() { + return AvailableTargetPageRules_store.rules.value.map(r => ({ + key: r.value, + value: r.name + })); + }, + targetOptions() { + const result = {}; + AvailableTargetPageRules_store.rules.value.forEach(r => { + result[r.value] = []; + if (this.allowAny && r.value === 'url') { + result[r.value].push({ + value: Object(external_CoreHome_["translate"])('HeatmapSessionRecording_TargetTypeIsAny'), + key: 'any' + }); + } + r.types.forEach(type => { + result[r.value].push({ + value: type.name, + key: type.value + }); + result[r.value].push({ + value: Object(external_CoreHome_["translate"])('HeatmapSessionRecording_TargetTypeIsNot', type.name), + key: `not_${type.value}` + }); + }); + }); + return result; + }, + targetExamples() { + const result = {}; + AvailableTargetPageRules_store.rules.value.forEach(r => { + result[r.value] = r.example; + }); + return result; + } + }, + methods: { + onTypeChange(newType) { + let inverted = 0; + let type = newType; + if (newType.indexOf('not_') === 0) { + type = newType.substring('not_'.length); + inverted = 1; + } + this.$emit('update:modelValue', Object.assign(Object.assign({}, this.modelValue), {}, { + type, + inverted + })); + } + } +})); +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/HsrUrlTarget/HsrUrlTarget.vue?vue&type=script&lang=ts + +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/HsrUrlTarget/HsrUrlTarget.vue + + + +HsrUrlTargetvue_type_script_lang_ts.render = HsrUrlTargetvue_type_template_id_4c1d8b92_render + +/* harmony default export */ var HsrUrlTarget = (HsrUrlTargetvue_type_script_lang_ts); +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--13-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/ManageHeatmap/Edit.vue?vue&type=template&id=635b8e28 + +const Editvue_type_template_id_635b8e28_hoisted_1 = { + class: "loadingPiwik" +}; +const Editvue_type_template_id_635b8e28_hoisted_2 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("img", { + src: "plugins/Morpheus/images/loading-blue.gif" +}, null, -1); +const Editvue_type_template_id_635b8e28_hoisted_3 = { + class: "loadingPiwik" +}; +const Editvue_type_template_id_635b8e28_hoisted_4 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("img", { + src: "plugins/Morpheus/images/loading-blue.gif" +}, null, -1); +const Editvue_type_template_id_635b8e28_hoisted_5 = { + name: "name" +}; +const Editvue_type_template_id_635b8e28_hoisted_6 = { + name: "sampleLimit" +}; +const Editvue_type_template_id_635b8e28_hoisted_7 = { + class: "form-group row" +}; +const Editvue_type_template_id_635b8e28_hoisted_8 = { + class: "col s12" +}; +const Editvue_type_template_id_635b8e28_hoisted_9 = { + class: "col s12 m6", + style: { + "padding-left": "0" + } +}; +const Editvue_type_template_id_635b8e28_hoisted_10 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("hr", null, null, -1); +const Editvue_type_template_id_635b8e28_hoisted_11 = { + class: "col s12 m6" +}; +const Editvue_type_template_id_635b8e28_hoisted_12 = { + class: "form-help" +}; +const Editvue_type_template_id_635b8e28_hoisted_13 = { + class: "inline-help" +}; +const Editvue_type_template_id_635b8e28_hoisted_14 = { + name: "sampleRate" +}; +const Editvue_type_template_id_635b8e28_hoisted_15 = { + name: "excludedElements" +}; +const Editvue_type_template_id_635b8e28_hoisted_16 = { + name: "screenshotUrl" +}; +const Editvue_type_template_id_635b8e28_hoisted_17 = { + name: "breakpointMobile" +}; +const Editvue_type_template_id_635b8e28_hoisted_18 = { + name: "breakpointTablet" +}; +const Editvue_type_template_id_635b8e28_hoisted_19 = { + name: "trackManually" +}; +const Editvue_type_template_id_635b8e28_hoisted_20 = ["innerHTML"]; +const Editvue_type_template_id_635b8e28_hoisted_21 = { + class: "entityCancel" +}; +function Editvue_type_template_id_635b8e28_render(_ctx, _cache, $props, $setup, $data, $options) { + const _component_Field = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("Field"); + const _component_HsrUrlTarget = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("HsrUrlTarget"); + const _component_HsrTargetTest = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("HsrTargetTest"); + const _component_SaveButton = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("SaveButton"); + const _component_ContentBlock = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("ContentBlock"); + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createBlock"])(_component_ContentBlock, { + class: "editHsr", + "content-title": _ctx.contentTitle + }, { + default: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withCtx"])(() => [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("p", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", Editvue_type_template_id_635b8e28_hoisted_1, [Editvue_type_template_id_635b8e28_hoisted_2, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(" " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_LoadingData')), 1)])], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.isLoading]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("p", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", Editvue_type_template_id_635b8e28_hoisted_3, [Editvue_type_template_id_635b8e28_hoisted_4, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(" " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_UpdatingData')), 1)])], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.isUpdating]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("form", { + onSubmit: _cache[12] || (_cache[12] = $event => _ctx.edit ? _ctx.updateHsr() : _ctx.createHsr()) + }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_635b8e28_hoisted_5, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, { + uicontrol: "text", + name: "name", + "model-value": _ctx.siteHsr.name, + "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => { + _ctx.siteHsr.name = $event; + _ctx.setValueHasChanged(); + }), + title: _ctx.translate('General_Name'), + maxlength: 50, + placeholder: _ctx.translate('HeatmapSessionRecording_FieldNamePlaceholder'), + "inline-help": _ctx.translate('HeatmapSessionRecording_HeatmapNameHelp') + }, null, 8, ["model-value", "title", "placeholder", "inline-help"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_635b8e28_hoisted_6, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, { + uicontrol: "select", + name: "sampleLimit", + "model-value": _ctx.siteHsr.sample_limit, + "onUpdate:modelValue": _cache[1] || (_cache[1] = $event => { + _ctx.siteHsr.sample_limit = $event; + _ctx.setValueHasChanged(); + }), + title: _ctx.translate('HeatmapSessionRecording_HeatmapSampleLimit'), + options: _ctx.sampleLimits, + "inline-help": _ctx.translate('HeatmapSessionRecording_HeatmapSampleLimitHelp') + }, null, 8, ["model-value", "title", "options", "inline-help"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_635b8e28_hoisted_7, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_635b8e28_hoisted_8, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("h3", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_TargetPage')) + ":", 1)]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_635b8e28_hoisted_9, [(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])(external_commonjs_vue_commonjs2_vue_root_Vue_["Fragment"], null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderList"])(_ctx.siteHsr.match_page_rules, (url, index) => { + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", { + class: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeClass"])(`matchPageRules ${index} multiple`), + key: index + }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_HsrUrlTarget, { + "model-value": url, + "onUpdate:modelValue": $event => _ctx.setMatchPageRule($event, index), + onAddUrl: _cache[2] || (_cache[2] = $event => _ctx.addMatchPageRule()), + onRemoveUrl: $event => _ctx.removeMatchPageRule(index), + onAnyChange: _cache[3] || (_cache[3] = $event => _ctx.setValueHasChanged()), + "allow-any": false, + "disable-if-no-value": index > 0, + "can-be-removed": index > 0, + "show-add-url": true + }, null, 8, ["model-value", "onUpdate:modelValue", "onRemoveUrl", "disable-if-no-value", "can-be-removed"])]), Editvue_type_template_id_635b8e28_hoisted_10], 2); + }), 128))]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_635b8e28_hoisted_11, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_635b8e28_hoisted_12, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", Editvue_type_template_id_635b8e28_hoisted_13, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_FieldIncludedTargetsHelp')) + " ", 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_HsrTargetTest, { + "included-targets": _ctx.siteHsr.match_page_rules + }, null, 8, ["included-targets"])])])])])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_635b8e28_hoisted_14, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, { + uicontrol: "select", + name: "sampleRate", + "model-value": _ctx.siteHsr.sample_rate, + "onUpdate:modelValue": _cache[4] || (_cache[4] = $event => { + _ctx.siteHsr.sample_rate = $event; + _ctx.setValueHasChanged(); + }), + title: _ctx.translate('HeatmapSessionRecording_SampleRate'), + options: _ctx.sampleRates, + introduction: _ctx.translate('HeatmapSessionRecording_AdvancedOptions'), + "inline-help": _ctx.translate('HeatmapSessionRecording_HeatmapSampleRateHelp') + }, null, 8, ["model-value", "title", "options", "introduction", "inline-help"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_635b8e28_hoisted_15, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, { + uicontrol: "text", + name: "excludedElements", + "model-value": _ctx.siteHsr.excluded_elements, + "onUpdate:modelValue": _cache[5] || (_cache[5] = $event => { + _ctx.siteHsr.excluded_elements = $event; + _ctx.setValueHasChanged(); + }), + title: _ctx.translate('HeatmapSessionRecording_ExcludedElements'), + maxlength: 1000, + "inline-help": _ctx.translate('HeatmapSessionRecording_ExcludedElementsHelp') + }, null, 8, ["model-value", "title", "inline-help"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_635b8e28_hoisted_16, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, { + uicontrol: "text", + name: "screenshotUrl", + "model-value": _ctx.siteHsr.screenshot_url, + "onUpdate:modelValue": _cache[6] || (_cache[6] = $event => { + _ctx.siteHsr.screenshot_url = $event; + _ctx.setValueHasChanged(); + }), + title: _ctx.translate('HeatmapSessionRecording_ScreenshotUrl'), + maxlength: 300, + disabled: !!_ctx.siteHsr.page_treemirror, + "inline-help": _ctx.translate('HeatmapSessionRecording_ScreenshotUrlHelp') + }, null, 8, ["model-value", "title", "disabled", "inline-help"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_635b8e28_hoisted_17, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, { + uicontrol: "text", + name: "breakpointMobile", + "model-value": _ctx.siteHsr.breakpoint_mobile, + "onUpdate:modelValue": _cache[7] || (_cache[7] = $event => { + _ctx.siteHsr.breakpoint_mobile = $event; + _ctx.setValueHasChanged(); + }), + title: _ctx.translate('HeatmapSessionRecording_BreakpointX', _ctx.translate('General_Mobile')), + maxlength: 4, + "inline-help": _ctx.breakpointMobileInlineHelp + }, null, 8, ["model-value", "title", "inline-help"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_635b8e28_hoisted_18, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, { + uicontrol: "text", + name: "breakpointTablet", + "model-value": _ctx.siteHsr.breakpoint_tablet, + "onUpdate:modelValue": _cache[8] || (_cache[8] = $event => { + _ctx.siteHsr.breakpoint_tablet = $event; + _ctx.setValueHasChanged(); + }), + title: _ctx.translate('HeatmapSessionRecording_BreakpointX', _ctx.translate('DevicesDetection_Tablet')), + maxlength: 4, + "inline-help": _ctx.breakpointGeneralHelp + }, null, 8, ["model-value", "title", "inline-help"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_635b8e28_hoisted_19, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, { + uicontrol: "checkbox", + name: "capture_manually", + title: _ctx.translate('HeatmapSessionRecording_CaptureDomTitle'), + "inline-help": _ctx.captureDomInlineHelp, + "model-value": _ctx.siteHsr.capture_manually, + "onUpdate:modelValue": _cache[9] || (_cache[9] = $event => { + _ctx.siteHsr.capture_manually = $event; + _ctx.setValueHasChanged(); + }) + }, null, 8, ["title", "inline-help", "model-value"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("p", { + innerHTML: _ctx.$sanitize(_ctx.personalInformationNote) + }, null, 8, Editvue_type_template_id_635b8e28_hoisted_20), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_SaveButton, { + class: "createButton", + onConfirm: _cache[10] || (_cache[10] = $event => _ctx.edit ? _ctx.updateHsr() : _ctx.createHsr()), + disabled: _ctx.isUpdating || !_ctx.isDirty, + saving: _ctx.isUpdating, + value: _ctx.saveButtonText + }, null, 8, ["disabled", "saving", "value"]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_635b8e28_hoisted_21, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("a", { + onClick: _cache[11] || (_cache[11] = $event => _ctx.cancel()) + }, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Cancel')), 1)])])], 32)]), + _: 1 + }, 8, ["content-title"]); +} +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/ManageHeatmap/Edit.vue?vue&type=template&id=635b8e28 + +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/HsrStore/HsrStore.store.ts +function HsrStore_store_defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } +/** + * Copyright (C) InnoCraft Ltd - All rights reserved. + * + * NOTICE: All information contained herein is, and remains the property of InnoCraft Ltd. + * The intellectual and technical concepts contained herein are protected by trade secret + * or copyright law. Redistribution of this information or reproduction of this material is + * strictly forbidden unless prior written permission is obtained from InnoCraft Ltd. + * + * You shall use this code only in accordance with the license agreement obtained from + * InnoCraft Ltd. + * + * @link https://www.innocraft.com/ + * @license For license details see https://www.innocraft.com/license + */ + + +class HsrStore_store_HsrStore { + constructor(context) { + HsrStore_store_defineProperty(this, "context", void 0); + HsrStore_store_defineProperty(this, "privateState", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["reactive"])({ + allHsrs: [], + isLoading: false, + isUpdating: false, + filterStatus: '' + })); + HsrStore_store_defineProperty(this, "state", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["computed"])(() => Object(external_commonjs_vue_commonjs2_vue_root_Vue_["readonly"])(this.privateState))); + HsrStore_store_defineProperty(this, "hsrs", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["computed"])(() => { + if (!this.privateState.filterStatus) { + return this.state.value.allHsrs; + } + return this.state.value.allHsrs.filter(hsr => hsr.status === this.privateState.filterStatus); + })); + // used just for the adapter + HsrStore_store_defineProperty(this, "hsrsCloned", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["computed"])(() => Object(external_CoreHome_["clone"])(this.hsrs.value))); + HsrStore_store_defineProperty(this, "statusOptions", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["readonly"])([{ + key: '', + value: Object(external_CoreHome_["translate"])('General_All') + }, { + key: 'active', + value: Object(external_CoreHome_["translate"])('HeatmapSessionRecording_StatusActive') + }, { + key: 'ended', + value: Object(external_CoreHome_["translate"])('HeatmapSessionRecording_StatusEnded') + }, { + key: 'paused', + value: Object(external_CoreHome_["translate"])('HeatmapSessionRecording_StatusPaused') + }])); + HsrStore_store_defineProperty(this, "fetchPromises", {}); + this.context = context; + } + setFilterStatus(status) { + this.privateState.filterStatus = status; + } + reload() { + this.privateState.allHsrs = []; + this.fetchPromises = {}; + return this.fetchHsrs(); + } + filterRules(rules) { + return rules.filter(target => !!target && (target.value || target.type === 'any')); + } + getApiMethodInContext(apiMethod) { + return `${apiMethod}${this.context}`; + } + fetchHsrs() { + let method = 'HeatmapSessionRecording.getHeatmaps'; + if (this.context === 'SessionRecording') { + method = 'HeatmapSessionRecording.getSessionRecordings'; + } + const params = { + method, + filter_limit: '-1' + }; + if (!this.fetchPromises[method]) { + this.fetchPromises[method] = external_CoreHome_["AjaxHelper"].fetch(params); + } + this.privateState.isLoading = true; + this.privateState.allHsrs = []; + return this.fetchPromises[method].then(hsrs => { + this.privateState.allHsrs = hsrs; + return this.state.value.allHsrs; + }).finally(() => { + this.privateState.isLoading = false; + }); + } + findHsr(idSiteHsr) { + // before going through an API request we first try to find it in loaded hsrs + const found = this.state.value.allHsrs.find(hsr => hsr.idsitehsr === idSiteHsr); + if (found) { + return Promise.resolve(found); + } + // otherwise we fetch it via API + this.privateState.isLoading = true; + return external_CoreHome_["AjaxHelper"].fetch({ + idSiteHsr, + method: this.getApiMethodInContext('HeatmapSessionRecording.get'), + filter_limit: '-1' + }).finally(() => { + this.privateState.isLoading = false; + }); + } + deleteHsr(idSiteHsr) { + this.privateState.isUpdating = true; + this.privateState.allHsrs = []; + return external_CoreHome_["AjaxHelper"].fetch({ + idSiteHsr, + method: this.getApiMethodInContext('HeatmapSessionRecording.delete') + }, { + withTokenInUrl: true + }).then(() => ({ + type: 'success' + })).catch(error => ({ + type: 'error', + message: error.message || error + })).finally(() => { + this.privateState.isUpdating = false; + }); + } + completeHsr(idSiteHsr) { + this.privateState.isUpdating = true; + this.privateState.allHsrs = []; + return external_CoreHome_["AjaxHelper"].fetch({ + idSiteHsr, + method: this.getApiMethodInContext('HeatmapSessionRecording.end') + }, { + withTokenInUrl: true + }).then(() => ({ + type: 'success' + })).catch(error => ({ + type: 'error', + message: error.message || error + })).finally(() => { + this.privateState.isUpdating = false; + }); + } + createOrUpdateHsr(hsr, method) { + const params = { + idSiteHsr: hsr.idsitehsr, + sampleLimit: hsr.sample_limit, + sampleRate: hsr.sample_rate, + excludedElements: hsr.excluded_elements ? hsr.excluded_elements.trim() : undefined, + screenshotUrl: hsr.screenshot_url ? hsr.screenshot_url.trim() : undefined, + breakpointMobile: hsr.breakpoint_mobile, + breakpointTablet: hsr.breakpoint_tablet, + minSessionTime: hsr.min_session_time, + requiresActivity: hsr.requires_activity ? 1 : 0, + captureKeystrokes: hsr.capture_keystrokes ? 1 : 0, + captureDomManually: hsr.capture_manually ? 1 : 0, + method, + name: hsr.name.trim() + }; + const postParams = { + matchPageRules: this.filterRules(hsr.match_page_rules) + }; + this.privateState.isUpdating = true; + return external_CoreHome_["AjaxHelper"].post(params, postParams, { + withTokenInUrl: true + }).then(response => ({ + type: 'success', + response + })).catch(error => ({ + type: 'error', + message: error.message || error + })).finally(() => { + this.privateState.isUpdating = false; + }); + } +} +const HeatmapStore = new HsrStore_store_HsrStore('Heatmap'); +const SessionRecordingStore = new HsrStore_store_HsrStore('SessionRecording'); +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--15-0!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--15-2!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/ManageHeatmap/Edit.vue?vue&type=script&lang=ts + + + + + + +const notificationId = 'hsrmanagement'; +/* harmony default export */ var Editvue_type_script_lang_ts = (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["defineComponent"])({ + props: { + idSiteHsr: Number, + breakpointMobile: Number, + breakpointTablet: Number + }, + components: { + ContentBlock: external_CoreHome_["ContentBlock"], + Field: external_CorePluginsAdmin_["Field"], + HsrUrlTarget: HsrUrlTarget, + HsrTargetTest: HsrTargetTest, + SaveButton: external_CorePluginsAdmin_["SaveButton"] + }, + data() { + return { + isDirty: false, + showAdvancedView: false, + siteHsr: {} + }; + }, + created() { + this.init(); + }, + watch: { + idSiteHsr(newValue) { + if (newValue === null) { + return; + } + this.init(); + } + }, + methods: { + removeAnyHsrNotification() { + external_CoreHome_["NotificationsStore"].remove(notificationId); + external_CoreHome_["NotificationsStore"].remove('ajaxHelper'); + }, + showNotification(message, context) { + const instanceId = external_CoreHome_["NotificationsStore"].show({ + message, + context, + id: notificationId, + type: 'transient' + }); + setTimeout(() => { + external_CoreHome_["NotificationsStore"].scrollToNotification(instanceId); + }, 200); + }, + showErrorFieldNotProvidedNotification(title) { + const message = Object(external_CoreHome_["translate"])('HeatmapSessionRecording_ErrorXNotProvided', [title]); + this.showNotification(message, 'error'); + }, + init() { + const { + idSiteHsr + } = this; + this.siteHsr = {}; + this.showAdvancedView = false; + external_CoreHome_["Matomo"].helper.lazyScrollToContent(); + if (this.edit && idSiteHsr) { + HeatmapStore.findHsr(idSiteHsr).then(siteHsr => { + if (!siteHsr) { + return; + } + this.siteHsr = Object(external_CoreHome_["clone"])(siteHsr); + this.siteHsr.sample_rate = `${this.siteHsr.sample_rate}`; + this.addInitialMatchPageRule(); + this.isDirty = false; + }); + return; + } + if (this.create) { + this.siteHsr = { + idSite: external_CoreHome_["Matomo"].idSite, + name: '', + sample_rate: '10.0', + sample_limit: 1000, + breakpoint_mobile: this.breakpointMobile, + breakpoint_tablet: this.breakpointTablet, + capture_manually: 0 + }; + this.isDirty = false; + const hashParams = external_CoreHome_["MatomoUrl"].hashParsed.value; + if (hashParams.name) { + this.siteHsr.name = hashParams.name; + this.isDirty = true; + } + if (hashParams.matchPageRules) { + try { + this.siteHsr.match_page_rules = JSON.parse(hashParams.matchPageRules); + this.isDirty = true; + } catch (e) { + console.log('warning: could not parse matchPageRules query param, expected JSON'); + } + } else { + this.addInitialMatchPageRule(); + } + } + }, + addInitialMatchPageRule() { + var _this$siteHsr$match_p; + if (!this.siteHsr) { + return; + } + if ((_this$siteHsr$match_p = this.siteHsr.match_page_rules) !== null && _this$siteHsr$match_p !== void 0 && _this$siteHsr$match_p.length) { + return; + } + this.addMatchPageRule(); + }, + addMatchPageRule() { + var _this$siteHsr$match_p2; + if (!this.siteHsr) { + return; + } + if (!((_this$siteHsr$match_p2 = this.siteHsr.match_page_rules) !== null && _this$siteHsr$match_p2 !== void 0 && _this$siteHsr$match_p2.length)) { + this.siteHsr.match_page_rules = []; + } + this.siteHsr.match_page_rules.push({ + attribute: 'url', + type: 'equals_simple', + value: '', + inverted: 0 + }); + this.isDirty = true; + }, + removeMatchPageRule(index) { + if (this.siteHsr && index > -1) { + this.siteHsr.match_page_rules = [...this.siteHsr.match_page_rules]; + this.siteHsr.match_page_rules.splice(index, 1); + this.isDirty = true; + } + }, + cancel() { + const newParams = Object.assign({}, external_CoreHome_["MatomoUrl"].hashParsed.value); + delete newParams.idSiteHsr; + external_CoreHome_["MatomoUrl"].updateHash(newParams); + }, + createHsr() { + this.removeAnyHsrNotification(); + if (!this.checkRequiredFieldsAreSet()) { + return; + } + HeatmapStore.createOrUpdateHsr(this.siteHsr, 'HeatmapSessionRecording.addHeatmap').then(response => { + if (!response || response.type === 'error' || !response.response) { + return; + } + this.isDirty = false; + const idSiteHsr = response.response.value; + HeatmapStore.reload().then(() => { + if (external_CoreHome_["Matomo"].helper.isReportingPage()) { + external_CoreHome_["Matomo"].postEvent('updateReportingMenu'); + } + external_CoreHome_["MatomoUrl"].updateHash(Object.assign(Object.assign({}, external_CoreHome_["MatomoUrl"].hashParsed.value), {}, { + idSiteHsr + })); + setTimeout(() => { + this.showNotification(Object(external_CoreHome_["translate"])('HeatmapSessionRecording_HeatmapCreated'), response.type); + }, 200); + }); + }); + }, + setValueHasChanged() { + this.isDirty = true; + }, + updateHsr() { + this.removeAnyHsrNotification(); + if (!this.checkRequiredFieldsAreSet()) { + return; + } + HeatmapStore.createOrUpdateHsr(this.siteHsr, 'HeatmapSessionRecording.updateHeatmap').then(response => { + if (response.type === 'error') { + return; + } + this.isDirty = false; + this.siteHsr = {}; + HeatmapStore.reload().then(() => { + this.init(); + }); + this.showNotification(Object(external_CoreHome_["translate"])('HeatmapSessionRecording_HeatmapUpdated'), response.type); + }); + }, + checkRequiredFieldsAreSet() { + var _this$siteHsr$match_p3; + if (!this.siteHsr.name) { + const title = Object(external_CoreHome_["translate"])('General_Name'); + this.showErrorFieldNotProvidedNotification(title); + return false; + } + if (!((_this$siteHsr$match_p3 = this.siteHsr.match_page_rules) !== null && _this$siteHsr$match_p3 !== void 0 && _this$siteHsr$match_p3.length) || !HeatmapStore.filterRules(this.siteHsr.match_page_rules).length) { + const title = Object(external_CoreHome_["translate"])('HeatmapSessionRecording_ErrorPageRuleRequired'); + this.showNotification(title, 'error'); + return false; + } + return true; + }, + setMatchPageRule(rule, index) { + this.siteHsr.match_page_rules = [...this.siteHsr.match_page_rules]; + this.siteHsr.match_page_rules[index] = rule; + } + }, + computed: { + sampleLimits() { + return [1000, 2000, 5000].map(v => ({ + key: `${v}`, + value: v + })); + }, + sampleRates() { + const values = [0.1, 0.5, 1, 2, 3, 4, 5, 6, 8, 10, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100]; + return values.map(v => ({ + key: v.toFixed(1), + value: `${v}%` + })); + }, + create() { + return !this.idSiteHsr; + }, + edit() { + return !this.create; + }, + editTitle() { + const token = this.create ? 'HeatmapSessionRecording_CreateNewHeatmap' : 'HeatmapSessionRecording_EditHeatmapX'; + return token; + }, + contentTitle() { + return Object(external_CoreHome_["translate"])(this.editTitle, this.siteHsr.name ? `"${this.siteHsr.name}"` : ''); + }, + isLoading() { + return HeatmapStore.state.value.isLoading; + }, + isUpdating() { + return HeatmapStore.state.value.isUpdating; + }, + breakpointMobileInlineHelp() { + const help1 = Object(external_CoreHome_["translate"])('HeatmapSessionRecording_BreakpointGeneralHelp'); + const help2 = Object(external_CoreHome_["translate"])('HeatmapSessionRecording_BreakpointGeneralHelpManage'); + return `${help1} ${help2}`; + }, + breakpointGeneralHelp() { + const help1 = Object(external_CoreHome_["translate"])('HeatmapSessionRecording_BreakpointGeneralHelp'); + const help2 = Object(external_CoreHome_["translate"])('HeatmapSessionRecording_BreakpointGeneralHelpManage'); + return `${help1} ${help2}`; + }, + captureDomInlineHelp() { + const id = this.idSiteHsr ? this.idSiteHsr : '{idHeatmap}'; + const command = `

    _paq.push(['HeatmapSessionRecording::captureInitialDom', ${id}])`; + return Object(external_CoreHome_["translate"])('HeatmapSessionRecording_CaptureDomInlineHelp', command, '

    ', ''); + }, + personalInformationNote() { + const url = 'https://developer.matomo.org/guides/heatmap-session-recording/setup#masking-content-on-your-website'; + return Object(external_CoreHome_["translate"])('HeatmapSessionRecording_PersonalInformationNote', Object(external_CoreHome_["translate"])('HeatmapSessionRecording_Heatmap'), '', '', ``, ''); + }, + saveButtonText() { + return this.edit ? Object(external_CoreHome_["translate"])('CoreUpdater_UpdateTitle') : Object(external_CoreHome_["translate"])('HeatmapSessionRecording_CreateNewHeatmap'); + } + } +})); +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/ManageHeatmap/Edit.vue?vue&type=script&lang=ts + +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/ManageHeatmap/Edit.vue + + + +Editvue_type_script_lang_ts.render = Editvue_type_template_id_635b8e28_render + +/* harmony default export */ var Edit = (Editvue_type_script_lang_ts); +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--13-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/ManageHeatmap/List.vue?vue&type=template&id=669edce3 + +const Listvue_type_template_id_669edce3_hoisted_1 = { + class: "heatmapList" +}; +const Listvue_type_template_id_669edce3_hoisted_2 = { + class: "filterStatus" +}; +const Listvue_type_template_id_669edce3_hoisted_3 = { + class: "hsrSearchFilter", + style: { + "margin-left": "3.5px" + } +}; +const Listvue_type_template_id_669edce3_hoisted_4 = { + class: "index" +}; +const Listvue_type_template_id_669edce3_hoisted_5 = { + class: "name" +}; +const Listvue_type_template_id_669edce3_hoisted_6 = { + class: "creationDate" +}; +const Listvue_type_template_id_669edce3_hoisted_7 = { + class: "sampleLimit" +}; +const Listvue_type_template_id_669edce3_hoisted_8 = { + class: "status" +}; +const Listvue_type_template_id_669edce3_hoisted_9 = { + class: "action" +}; +const Listvue_type_template_id_669edce3_hoisted_10 = { + colspan: "7" +}; +const Listvue_type_template_id_669edce3_hoisted_11 = { + class: "loadingPiwik" +}; +const Listvue_type_template_id_669edce3_hoisted_12 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("img", { + src: "plugins/Morpheus/images/loading-blue.gif" +}, null, -1); +const Listvue_type_template_id_669edce3_hoisted_13 = { + colspan: "7" +}; +const Listvue_type_template_id_669edce3_hoisted_14 = ["id"]; +const Listvue_type_template_id_669edce3_hoisted_15 = { + class: "index" +}; +const Listvue_type_template_id_669edce3_hoisted_16 = { + class: "name" +}; +const Listvue_type_template_id_669edce3_hoisted_17 = { + class: "creationDate" +}; +const Listvue_type_template_id_669edce3_hoisted_18 = { + class: "sampleLimit" +}; +const Listvue_type_template_id_669edce3_hoisted_19 = { + key: 0, + class: "status status-paused" +}; +const Listvue_type_template_id_669edce3_hoisted_20 = ["title"]; +const Listvue_type_template_id_669edce3_hoisted_21 = { + key: 1, + class: "status" +}; +const Listvue_type_template_id_669edce3_hoisted_22 = { + class: "action" +}; +const Listvue_type_template_id_669edce3_hoisted_23 = ["title", "onClick"]; +const Listvue_type_template_id_669edce3_hoisted_24 = ["title", "onClick"]; +const Listvue_type_template_id_669edce3_hoisted_25 = ["title", "href"]; +const Listvue_type_template_id_669edce3_hoisted_26 = ["title", "onClick"]; +const Listvue_type_template_id_669edce3_hoisted_27 = { + class: "tableActionBar" +}; +const Listvue_type_template_id_669edce3_hoisted_28 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "icon-add" +}, null, -1); +const Listvue_type_template_id_669edce3_hoisted_29 = { + class: "ui-confirm", + id: "confirmDeleteHeatmap", + ref: "confirmDeleteHeatmap" +}; +const Listvue_type_template_id_669edce3_hoisted_30 = ["value"]; +const Listvue_type_template_id_669edce3_hoisted_31 = ["value"]; +const Listvue_type_template_id_669edce3_hoisted_32 = { + class: "ui-confirm", + id: "confirmEndHeatmap", + ref: "confirmEndHeatmap" +}; +const Listvue_type_template_id_669edce3_hoisted_33 = ["value"]; +const Listvue_type_template_id_669edce3_hoisted_34 = ["value"]; +function Listvue_type_template_id_669edce3_render(_ctx, _cache, $props, $setup, $data, $options) { + const _component_Field = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("Field"); + const _component_ContentBlock = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("ContentBlock"); + const _directive_content_table = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveDirective"])("content-table"); + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", Listvue_type_template_id_669edce3_hoisted_1, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_ContentBlock, { + "content-title": _ctx.translate('HeatmapSessionRecording_ManageHeatmaps') + }, { + default: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withCtx"])(() => [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("p", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_HeatmapUsageBenefits')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Listvue_type_template_id_669edce3_hoisted_2, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, { + uicontrol: "select", + name: "filterStatus", + "model-value": _ctx.filterStatus, + "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => { + _ctx.setFilterStatus($event); + }), + title: _ctx.translate('HeatmapSessionRecording_Filter'), + "full-width": true, + options: _ctx.statusOptions + }, null, 8, ["model-value", "title", "options"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Listvue_type_template_id_669edce3_hoisted_3, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, { + uicontrol: "text", + name: "hsrSearch", + title: _ctx.translate('General_Search'), + modelValue: _ctx.searchFilter, + "onUpdate:modelValue": _cache[1] || (_cache[1] = $event => _ctx.searchFilter = $event), + "full-width": true + }, null, 8, ["title", "modelValue"]), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.hsrs.length > 0]])])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])((Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("table", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("thead", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("tr", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("th", Listvue_type_template_id_669edce3_hoisted_4, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Id')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("th", Listvue_type_template_id_669edce3_hoisted_5, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Name')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("th", Listvue_type_template_id_669edce3_hoisted_6, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_CreationDate')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("th", Listvue_type_template_id_669edce3_hoisted_7, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_SampleLimit')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("th", Listvue_type_template_id_669edce3_hoisted_8, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CorePluginsAdmin_Status')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("th", Listvue_type_template_id_669edce3_hoisted_9, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Actions')), 1)])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("tbody", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("tr", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("td", Listvue_type_template_id_669edce3_hoisted_10, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", Listvue_type_template_id_669edce3_hoisted_11, [Listvue_type_template_id_669edce3_hoisted_12, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(" " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_LoadingData')), 1)])])], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.isLoading || _ctx.isUpdating]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("tr", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("td", Listvue_type_template_id_669edce3_hoisted_13, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_NoHeatmapsFound')), 1)], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], !_ctx.isLoading && _ctx.hsrs.length === 0]]), (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])(external_commonjs_vue_commonjs2_vue_root_Vue_["Fragment"], null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderList"])(_ctx.sortedHsrs, hsr => { + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("tr", { + id: `hsr${hsr.idsitehsr}`, + class: "hsrs", + key: hsr.idsitehsr + }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("td", Listvue_type_template_id_669edce3_hoisted_15, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(hsr.idsitehsr), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("td", Listvue_type_template_id_669edce3_hoisted_16, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(hsr.name), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("td", Listvue_type_template_id_669edce3_hoisted_17, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(hsr.created_date_pretty), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("td", Listvue_type_template_id_669edce3_hoisted_18, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(hsr.sample_limit), 1), hsr.status === 'paused' ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("td", Listvue_type_template_id_669edce3_hoisted_19, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.ucfirst(hsr.status)) + " ", 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "icon icon-help", + title: _ctx.pauseReason + }, null, 8, Listvue_type_template_id_669edce3_hoisted_20)])) : (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("td", Listvue_type_template_id_669edce3_hoisted_21, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.ucfirst(hsr.status)), 1)), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("td", Listvue_type_template_id_669edce3_hoisted_22, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("a", { + class: "table-action icon-edit", + title: _ctx.translate('HeatmapSessionRecording_EditX', _ctx.translate('HeatmapSessionRecording_Heatmap')), + onClick: $event => _ctx.editHsr(hsr.idsitehsr) + }, null, 8, Listvue_type_template_id_669edce3_hoisted_23), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("a", { + a: "", + class: "table-action stopRecording icon-drop-crossed", + title: _ctx.translate('HeatmapSessionRecording_StopX', _ctx.translate('HeatmapSessionRecording_Heatmap')), + onClick: $event => _ctx.completeHsr(hsr) + }, null, 8, Listvue_type_template_id_669edce3_hoisted_24), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], hsr.status !== 'ended']]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("a", { + target: "_blank", + class: "table-action icon-show", + title: _ctx.translate('HeatmapSessionRecording_ViewReport'), + href: _ctx.getViewReportLink(hsr) + }, null, 8, Listvue_type_template_id_669edce3_hoisted_25), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("a", { + class: "table-action icon-delete", + title: _ctx.translate('HeatmapSessionRecording_DeleteX', _ctx.translate('HeatmapSessionRecording_Heatmap')), + onClick: $event => _ctx.deleteHsr(hsr) + }, null, 8, Listvue_type_template_id_669edce3_hoisted_26)])], 8, Listvue_type_template_id_669edce3_hoisted_14); + }), 128))])])), [[_directive_content_table]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Listvue_type_template_id_669edce3_hoisted_27, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("a", { + class: "createNewHsr", + value: "", + onClick: _cache[2] || (_cache[2] = $event => _ctx.createHsr()) + }, [Listvue_type_template_id_669edce3_hoisted_28, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(" " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_CreateNewHeatmap')), 1)])])]), + _: 1 + }, 8, ["content-title"]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Listvue_type_template_id_669edce3_hoisted_29, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("h2", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_DeleteHeatmapConfirm')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("input", { + role: "yes", + type: "button", + value: _ctx.translate('General_Yes') + }, null, 8, Listvue_type_template_id_669edce3_hoisted_30), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("input", { + role: "no", + type: "button", + value: _ctx.translate('General_No') + }, null, 8, Listvue_type_template_id_669edce3_hoisted_31)], 512), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Listvue_type_template_id_669edce3_hoisted_32, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("h2", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_EndHeatmapConfirm')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("input", { + role: "yes", + type: "button", + value: _ctx.translate('General_Yes') + }, null, 8, Listvue_type_template_id_669edce3_hoisted_33), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("input", { + role: "no", + type: "button", + value: _ctx.translate('General_No') + }, null, 8, Listvue_type_template_id_669edce3_hoisted_34)], 512)]); +} +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/ManageHeatmap/List.vue?vue&type=template&id=669edce3 + +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--15-0!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--15-2!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/ManageHeatmap/List.vue?vue&type=script&lang=ts + + + + +/* harmony default export */ var Listvue_type_script_lang_ts = (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["defineComponent"])({ + props: { + pauseReason: String + }, + components: { + ContentBlock: external_CoreHome_["ContentBlock"], + Field: external_CorePluginsAdmin_["Field"] + }, + directives: { + ContentTable: external_CoreHome_["ContentTable"] + }, + data() { + return { + searchFilter: '' + }; + }, + created() { + HeatmapStore.setFilterStatus(''); + HeatmapStore.fetchHsrs(); + }, + methods: { + createHsr() { + this.editHsr(0); + }, + editHsr(idSiteHsr) { + external_CoreHome_["MatomoUrl"].updateHash(Object.assign(Object.assign({}, external_CoreHome_["MatomoUrl"].hashParsed.value), {}, { + idSiteHsr + })); + }, + deleteHsr(hsr) { + external_CoreHome_["Matomo"].helper.modalConfirm(this.$refs.confirmDeleteHeatmap, { + yes: () => { + HeatmapStore.deleteHsr(hsr.idsitehsr).then(() => { + HeatmapStore.reload(); + external_CoreHome_["Matomo"].postEvent('updateReportingMenu'); + }); + } + }); + }, + completeHsr(hsr) { + external_CoreHome_["Matomo"].helper.modalConfirm(this.$refs.confirmEndHeatmap, { + yes: () => { + HeatmapStore.completeHsr(hsr.idsitehsr).then(() => { + HeatmapStore.reload(); + }); + } + }); + }, + setFilterStatus(filter) { + HeatmapStore.setFilterStatus(filter); + }, + ucfirst(s) { + return `${s[0].toUpperCase()}${s.substr(1)}`; + }, + getViewReportLink(hsr) { + return `?${external_CoreHome_["MatomoUrl"].stringify({ + module: 'Widgetize', + action: 'iframe', + moduleToWidgetize: 'HeatmapSessionRecording', + actionToWidgetize: 'showHeatmap', + idSiteHsr: hsr.idsitehsr, + idSite: hsr.idsite, + period: 'day', + date: 'yesterday' + })}`; + } + }, + computed: { + filterStatus() { + return HeatmapStore.state.value.filterStatus; + }, + statusOptions() { + return HeatmapStore.statusOptions; + }, + hsrs() { + return HeatmapStore.hsrs.value; + }, + isLoading() { + return HeatmapStore.state.value.isLoading; + }, + isUpdating() { + return HeatmapStore.state.value.isUpdating; + }, + sortedHsrs() { + // look through string properties of heatmaps for values that have searchFilter in them + // (mimics angularjs filter() filter) + const result = [...this.hsrs].filter(h => Object.keys(h).some(propName => { + const entity = h; + return typeof entity[propName] === 'string' && entity[propName].indexOf(this.searchFilter) !== -1; + })); + result.sort((lhs, rhs) => rhs.idsitehsr - lhs.idsitehsr); + return result; + } + } +})); +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/ManageHeatmap/List.vue?vue&type=script&lang=ts + +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/ManageHeatmap/List.vue + + + +Listvue_type_script_lang_ts.render = Listvue_type_template_id_669edce3_render + +/* harmony default export */ var List = (Listvue_type_script_lang_ts); +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--13-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/ManageHeatmap/Manage.vue?vue&type=template&id=56c7eaa3 + +const Managevue_type_template_id_56c7eaa3_hoisted_1 = { + class: "manageHsr", + ref: "root" +}; +const Managevue_type_template_id_56c7eaa3_hoisted_2 = { + key: 0 +}; +const Managevue_type_template_id_56c7eaa3_hoisted_3 = { + key: 1 +}; +function Managevue_type_template_id_56c7eaa3_render(_ctx, _cache, $props, $setup, $data, $options) { + const _component_MatomoJsNotWritableAlert = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("MatomoJsNotWritableAlert"); + const _component_HeatmapList = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("HeatmapList"); + const _component_HeatmapEdit = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("HeatmapEdit"); + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])(external_commonjs_vue_commonjs2_vue_root_Vue_["Fragment"], null, [!_ctx.editMode ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createBlock"])(_component_MatomoJsNotWritableAlert, { + key: 0, + "is-matomo-js-writable": _ctx.isMatomoJsWritable, + "recording-type": _ctx.translate('HeatmapSessionRecording_Heatmaps') + }, null, 8, ["is-matomo-js-writable", "recording-type"])) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Managevue_type_template_id_56c7eaa3_hoisted_1, [!_ctx.editMode ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", Managevue_type_template_id_56c7eaa3_hoisted_2, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_HeatmapList, { + "pause-reason": _ctx.pauseReason + }, null, 8, ["pause-reason"])])) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true), _ctx.editMode ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", Managevue_type_template_id_56c7eaa3_hoisted_3, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_HeatmapEdit, { + "breakpoint-mobile": _ctx.breakpointMobile, + "breakpoint-tablet": _ctx.breakpointTablet, + "id-site-hsr": _ctx.idSiteHsr + }, null, 8, ["breakpoint-mobile", "breakpoint-tablet", "id-site-hsr"])])) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true)], 512)], 64); +} +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/ManageHeatmap/Manage.vue?vue&type=template&id=56c7eaa3 + +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--13-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/MatomoJsNotWritable/MatomoJsNotWritableAlert.vue?vue&type=template&id=3eefb154 + +const MatomoJsNotWritableAlertvue_type_template_id_3eefb154_hoisted_1 = ["innerHTML"]; +function MatomoJsNotWritableAlertvue_type_template_id_3eefb154_render(_ctx, _cache, $props, $setup, $data, $options) { + return !_ctx.isMatomoJsWritable ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", { + key: 0, + class: "alert alert-warning", + innerHTML: _ctx.getJsNotWritableErrorMessage() + }, null, 8, MatomoJsNotWritableAlertvue_type_template_id_3eefb154_hoisted_1)) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true); +} +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/MatomoJsNotWritable/MatomoJsNotWritableAlert.vue?vue&type=template&id=3eefb154 + +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--15-0!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--15-2!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/MatomoJsNotWritable/MatomoJsNotWritableAlert.vue?vue&type=script&lang=ts + + +/* harmony default export */ var MatomoJsNotWritableAlertvue_type_script_lang_ts = (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["defineComponent"])({ + props: { + recordingType: { + type: String, + required: true + }, + isMatomoJsWritable: { + type: Boolean, + required: true + } + }, + methods: { + getJsNotWritableErrorMessage() { + return Object(external_CoreHome_["translate"])('HeatmapSessionRecording_MatomoJSNotWritableErrorMessage', this.recordingType, '', ''); + } + } +})); +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/MatomoJsNotWritable/MatomoJsNotWritableAlert.vue?vue&type=script&lang=ts + +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/MatomoJsNotWritable/MatomoJsNotWritableAlert.vue + + + +MatomoJsNotWritableAlertvue_type_script_lang_ts.render = MatomoJsNotWritableAlertvue_type_template_id_3eefb154_render + +/* harmony default export */ var MatomoJsNotWritableAlert = (MatomoJsNotWritableAlertvue_type_script_lang_ts); +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--15-0!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--15-2!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/ManageHeatmap/Manage.vue?vue&type=script&lang=ts + + + + + +const { + $: Managevue_type_script_lang_ts_$ +} = window; +/* harmony default export */ var Managevue_type_script_lang_ts = (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["defineComponent"])({ + props: { + breakpointMobile: Number, + breakpointTablet: Number, + pauseReason: String, + isMatomoJsWritable: { + type: Boolean, + required: true + } + }, + data() { + return { + editMode: false, + idSiteHsr: null + }; + }, + components: { + MatomoJsNotWritableAlert: MatomoJsNotWritableAlert, + HeatmapList: List, + HeatmapEdit: Edit + }, + watch: { + editMode() { + // when changing edit modes, the tooltip can sometimes get stuck on the screen + Managevue_type_script_lang_ts_$('.ui-tooltip').remove(); + } + }, + created() { + // doing this in a watch because we don't want to post an event in a computed property + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["watch"])(() => external_CoreHome_["MatomoUrl"].hashParsed.value.idSiteHsr, idSiteHsr => { + this.initState(idSiteHsr); + }); + this.initState(external_CoreHome_["MatomoUrl"].hashParsed.value.idSiteHsr); + }, + methods: { + removeAnyHsrNotification() { + external_CoreHome_["NotificationsStore"].remove('hsrmanagement'); + }, + initState(idSiteHsr) { + if (idSiteHsr) { + if (idSiteHsr === '0') { + const parameters = { + isAllowed: true + }; + external_CoreHome_["Matomo"].postEvent('HeatmapSessionRecording.initAddHeatmap', parameters); + if (parameters && !parameters.isAllowed) { + this.editMode = false; + this.idSiteHsr = null; + return; + } + } + this.editMode = true; + this.idSiteHsr = parseInt(idSiteHsr, 10); + } else { + this.editMode = false; + this.idSiteHsr = null; + } + this.removeAnyHsrNotification(); + } + } +})); +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/ManageHeatmap/Manage.vue?vue&type=script&lang=ts + +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/ManageHeatmap/Manage.vue + + + +Managevue_type_script_lang_ts.render = Managevue_type_template_id_56c7eaa3_render + +/* harmony default export */ var Manage = (Managevue_type_script_lang_ts); +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--13-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/ManageSessionRecording/Edit.vue?vue&type=template&id=56c3e386 + +const Editvue_type_template_id_56c3e386_hoisted_1 = { + class: "loadingPiwik" +}; +const Editvue_type_template_id_56c3e386_hoisted_2 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("img", { + src: "plugins/Morpheus/images/loading-blue.gif" +}, null, -1); +const Editvue_type_template_id_56c3e386_hoisted_3 = { + class: "loadingPiwik" +}; +const Editvue_type_template_id_56c3e386_hoisted_4 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("img", { + src: "plugins/Morpheus/images/loading-blue.gif" +}, null, -1); +const Editvue_type_template_id_56c3e386_hoisted_5 = { + name: "name" +}; +const Editvue_type_template_id_56c3e386_hoisted_6 = { + name: "sampleLimit" +}; +const Editvue_type_template_id_56c3e386_hoisted_7 = { + class: "form-group row" +}; +const Editvue_type_template_id_56c3e386_hoisted_8 = { + class: "col s12" +}; +const Editvue_type_template_id_56c3e386_hoisted_9 = { + class: "col s12 m6", + style: { + "padding-left": "0" + } +}; +const Editvue_type_template_id_56c3e386_hoisted_10 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("hr", null, null, -1); +const Editvue_type_template_id_56c3e386_hoisted_11 = { + class: "col s12 m6" +}; +const Editvue_type_template_id_56c3e386_hoisted_12 = { + class: "form-help" +}; +const Editvue_type_template_id_56c3e386_hoisted_13 = { + class: "inline-help" +}; +const Editvue_type_template_id_56c3e386_hoisted_14 = { + name: "sampleRate" +}; +const Editvue_type_template_id_56c3e386_hoisted_15 = { + name: "minSessionTime" +}; +const Editvue_type_template_id_56c3e386_hoisted_16 = { + name: "requiresActivity" +}; +const Editvue_type_template_id_56c3e386_hoisted_17 = { + class: "inline-help-node" +}; +const Editvue_type_template_id_56c3e386_hoisted_18 = ["innerHTML"]; +const Editvue_type_template_id_56c3e386_hoisted_19 = ["innerHTML"]; +const Editvue_type_template_id_56c3e386_hoisted_20 = { + class: "entityCancel" +}; +function Editvue_type_template_id_56c3e386_render(_ctx, _cache, $props, $setup, $data, $options) { + const _component_Field = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("Field"); + const _component_HsrUrlTarget = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("HsrUrlTarget"); + const _component_HsrTargetTest = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("HsrTargetTest"); + const _component_SaveButton = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("SaveButton"); + const _component_ContentBlock = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("ContentBlock"); + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createBlock"])(_component_ContentBlock, { + class: "editHsr", + "content-title": _ctx.contentTitle + }, { + default: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withCtx"])(() => [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("p", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", Editvue_type_template_id_56c3e386_hoisted_1, [Editvue_type_template_id_56c3e386_hoisted_2, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(" " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_LoadingData')), 1)])], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.isLoading]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("p", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", Editvue_type_template_id_56c3e386_hoisted_3, [Editvue_type_template_id_56c3e386_hoisted_4, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(" " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_UpdatingData')), 1)])], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.isUpdating]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("form", { + onSubmit: _cache[10] || (_cache[10] = $event => _ctx.edit ? _ctx.updateHsr() : _ctx.createHsr()) + }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_56c3e386_hoisted_5, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, { + uicontrol: "text", + name: "name", + "model-value": _ctx.siteHsr.name, + "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => { + _ctx.siteHsr.name = $event; + _ctx.setValueHasChanged(); + }), + title: _ctx.translate('General_Name'), + maxlength: 50, + placeholder: _ctx.translate('HeatmapSessionRecording_FieldNamePlaceholder'), + "inline-help": _ctx.translate('HeatmapSessionRecording_SessionNameHelp') + }, null, 8, ["model-value", "title", "placeholder", "inline-help"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_56c3e386_hoisted_6, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, { + uicontrol: "select", + name: "sampleLimit", + "model-value": _ctx.siteHsr.sample_limit, + "onUpdate:modelValue": _cache[1] || (_cache[1] = $event => { + _ctx.siteHsr.sample_limit = $event; + _ctx.setValueHasChanged(); + }), + title: _ctx.translate('HeatmapSessionRecording_SessionSampleLimit'), + options: _ctx.sampleLimits, + "inline-help": _ctx.translate('HeatmapSessionRecording_SessionSampleLimitHelp') + }, null, 8, ["model-value", "title", "options", "inline-help"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_56c3e386_hoisted_7, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_56c3e386_hoisted_8, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("h3", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_TargetPages')) + ":", 1)]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_56c3e386_hoisted_9, [(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])(external_commonjs_vue_commonjs2_vue_root_Vue_["Fragment"], null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderList"])(_ctx.siteHsr.match_page_rules, (url, index) => { + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", { + class: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeClass"])(`matchPageRules ${index} multiple`), + key: index + }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_HsrUrlTarget, { + "model-value": url, + "onUpdate:modelValue": $event => _ctx.setMatchPageRule($event, index), + onAddUrl: _cache[2] || (_cache[2] = $event => _ctx.addMatchPageRule()), + onRemoveUrl: $event => _ctx.removeMatchPageRule(index), + onAnyChange: _cache[3] || (_cache[3] = $event => _ctx.setValueHasChanged()), + "allow-any": true, + "disable-if-no-value": index > 0, + "can-be-removed": index > 0, + "show-add-url": true + }, null, 8, ["model-value", "onUpdate:modelValue", "onRemoveUrl", "disable-if-no-value", "can-be-removed"])]), Editvue_type_template_id_56c3e386_hoisted_10], 2); + }), 128))]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_56c3e386_hoisted_11, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_56c3e386_hoisted_12, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", Editvue_type_template_id_56c3e386_hoisted_13, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_FieldIncludedTargetsHelpSessions')) + " ", 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_HsrTargetTest, { + "included-targets": _ctx.siteHsr.match_page_rules + }, null, 8, ["included-targets"])])])])])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_56c3e386_hoisted_14, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, { + uicontrol: "select", + name: "sampleRate", + "model-value": _ctx.siteHsr.sample_rate, + "onUpdate:modelValue": _cache[4] || (_cache[4] = $event => { + _ctx.siteHsr.sample_rate = $event; + _ctx.setValueHasChanged(); + }), + title: _ctx.translate('HeatmapSessionRecording_SampleRate'), + options: _ctx.sampleRates, + introduction: _ctx.translate('HeatmapSessionRecording_AdvancedOptions'), + "inline-help": _ctx.translate('HeatmapSessionRecording_SessionSampleRateHelp') + }, null, 8, ["model-value", "title", "options", "introduction", "inline-help"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_56c3e386_hoisted_15, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, { + uicontrol: "select", + name: "minSessionTime", + "model-value": _ctx.siteHsr.min_session_time, + "onUpdate:modelValue": _cache[5] || (_cache[5] = $event => { + _ctx.siteHsr.min_session_time = $event; + _ctx.setValueHasChanged(); + }), + title: _ctx.translate('HeatmapSessionRecording_MinSessionTime'), + options: _ctx.minSessionTimes, + "inline-help": _ctx.translate('HeatmapSessionRecording_MinSessionTimeHelp') + }, null, 8, ["model-value", "title", "options", "inline-help"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_56c3e386_hoisted_16, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, { + uicontrol: "checkbox", + name: "requiresActivity", + "model-value": _ctx.siteHsr.requires_activity, + "onUpdate:modelValue": _cache[6] || (_cache[6] = $event => { + _ctx.siteHsr.requires_activity = $event; + _ctx.setValueHasChanged(); + }), + title: _ctx.translate('HeatmapSessionRecording_RequiresActivity'), + "inline-help": _ctx.translate('HeatmapSessionRecording_RequiresActivityHelp') + }, null, 8, ["model-value", "title", "inline-help"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, { + uicontrol: "checkbox", + name: "captureKeystrokes", + "model-value": _ctx.siteHsr.capture_keystrokes, + "onUpdate:modelValue": _cache[7] || (_cache[7] = $event => { + _ctx.siteHsr.capture_keystrokes = $event; + _ctx.setValueHasChanged(); + }), + title: _ctx.translate('HeatmapSessionRecording_CaptureKeystrokes') + }, { + "inline-help": Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withCtx"])(() => [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_56c3e386_hoisted_17, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + innerHTML: _ctx.$sanitize(_ctx.captureKeystrokesHelp) + }, null, 8, Editvue_type_template_id_56c3e386_hoisted_18)])]), + _: 1 + }, 8, ["model-value", "title"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("p", { + innerHTML: _ctx.$sanitize(_ctx.personalInformationNote) + }, null, 8, Editvue_type_template_id_56c3e386_hoisted_19), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_SaveButton, { + class: "createButton", + onConfirm: _cache[8] || (_cache[8] = $event => _ctx.edit ? _ctx.updateHsr() : _ctx.createHsr()), + disabled: _ctx.isUpdating || !_ctx.isDirty, + saving: _ctx.isUpdating, + value: _ctx.saveButtonText + }, null, 8, ["disabled", "saving", "value"]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Editvue_type_template_id_56c3e386_hoisted_20, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("a", { + onClick: _cache[9] || (_cache[9] = $event => _ctx.cancel()) + }, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Cancel')), 1)])])], 32)]), + _: 1 + }, 8, ["content-title"]); +} +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/ManageSessionRecording/Edit.vue?vue&type=template&id=56c3e386 + +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--15-0!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--15-2!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/ManageSessionRecording/Edit.vue?vue&type=script&lang=ts + + + + + + +const Editvue_type_script_lang_ts_notificationId = 'hsrmanagement'; +/* harmony default export */ var ManageSessionRecording_Editvue_type_script_lang_ts = (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["defineComponent"])({ + props: { + idSiteHsr: Number + }, + components: { + ContentBlock: external_CoreHome_["ContentBlock"], + Field: external_CorePluginsAdmin_["Field"], + HsrUrlTarget: HsrUrlTarget, + HsrTargetTest: HsrTargetTest, + SaveButton: external_CorePluginsAdmin_["SaveButton"] + }, + data() { + return { + isDirty: false, + showAdvancedView: false, + sampleLimits: [], + siteHsr: {} + }; + }, + created() { + external_CoreHome_["AjaxHelper"].fetch({ + method: 'HeatmapSessionRecording.getAvailableSessionRecordingSampleLimits' + }).then(sampleLimits => { + this.sampleLimits = (sampleLimits || []).map(l => ({ + key: `${l}`, + value: l + })); + }); + this.init(); + }, + watch: { + idSiteHsr(newValue) { + if (newValue === null) { + return; + } + this.init(); + } + }, + methods: { + removeAnyHsrNotification() { + external_CoreHome_["NotificationsStore"].remove(Editvue_type_script_lang_ts_notificationId); + external_CoreHome_["NotificationsStore"].remove('ajaxHelper'); + }, + showNotification(message, context) { + const instanceId = external_CoreHome_["NotificationsStore"].show({ + message, + context, + id: Editvue_type_script_lang_ts_notificationId, + type: 'transient' + }); + setTimeout(() => { + external_CoreHome_["NotificationsStore"].scrollToNotification(instanceId); + }, 200); + }, + showErrorFieldNotProvidedNotification(title) { + const message = Object(external_CoreHome_["translate"])('HeatmapSessionRecording_ErrorXNotProvided', [title]); + this.showNotification(message, 'error'); + }, + init() { + const { + idSiteHsr + } = this; + this.siteHsr = {}; + this.showAdvancedView = false; + external_CoreHome_["Matomo"].helper.lazyScrollToContent(); + if (this.edit && idSiteHsr) { + SessionRecordingStore.findHsr(idSiteHsr).then(siteHsr => { + if (!siteHsr) { + return; + } + this.siteHsr = Object(external_CoreHome_["clone"])(siteHsr); + this.siteHsr.sample_rate = `${this.siteHsr.sample_rate}`; + this.addInitialMatchPageRule(); + this.isDirty = false; + }); + return; + } + if (this.create) { + this.siteHsr = { + idSite: external_CoreHome_["Matomo"].idSite, + name: '', + sample_rate: '10.0', + sample_limit: 250, + min_session_time: 0, + requires_activity: true, + capture_keystrokes: false + }; + this.addInitialMatchPageRule(); + this.isDirty = false; + } + }, + addInitialMatchPageRule() { + var _this$siteHsr$match_p; + if (!this.siteHsr) { + return; + } + if ((_this$siteHsr$match_p = this.siteHsr.match_page_rules) !== null && _this$siteHsr$match_p !== void 0 && _this$siteHsr$match_p.length) { + return; + } + this.siteHsr.match_page_rules = [{ + attribute: 'url', + type: 'any', + value: '', + inverted: 0 + }]; + }, + addMatchPageRule() { + var _this$siteHsr$match_p2; + if (!this.siteHsr) { + return; + } + if (!((_this$siteHsr$match_p2 = this.siteHsr.match_page_rules) !== null && _this$siteHsr$match_p2 !== void 0 && _this$siteHsr$match_p2.length)) { + this.siteHsr.match_page_rules = []; + } + this.siteHsr.match_page_rules.push({ + attribute: 'url', + type: 'equals_simple', + value: '', + inverted: 0 + }); + this.isDirty = true; + }, + removeMatchPageRule(index) { + if (this.siteHsr && index > -1) { + this.siteHsr.match_page_rules = [...this.siteHsr.match_page_rules]; + this.siteHsr.match_page_rules.splice(index, 1); + this.isDirty = true; + } + }, + cancel() { + const newParams = Object.assign({}, external_CoreHome_["MatomoUrl"].hashParsed.value); + delete newParams.idSiteHsr; + external_CoreHome_["MatomoUrl"].updateHash(newParams); + }, + createHsr() { + this.removeAnyHsrNotification(); + if (!this.checkRequiredFieldsAreSet()) { + return; + } + SessionRecordingStore.createOrUpdateHsr(this.siteHsr, 'HeatmapSessionRecording.addSessionRecording').then(response => { + if (!response || response.type === 'error' || !response.response) { + return; + } + this.isDirty = false; + const idSiteHsr = response.response.value; + SessionRecordingStore.reload().then(() => { + if (external_CoreHome_["Matomo"].helper.isReportingPage()) { + external_CoreHome_["Matomo"].postEvent('updateReportingMenu'); + } + external_CoreHome_["MatomoUrl"].updateHash(Object.assign(Object.assign({}, external_CoreHome_["MatomoUrl"].hashParsed.value), {}, { + idSiteHsr + })); + setTimeout(() => { + this.showNotification(Object(external_CoreHome_["translate"])('HeatmapSessionRecording_SessionRecordingCreated'), response.type); + }, 200); + }); + }); + }, + setValueHasChanged() { + this.isDirty = true; + }, + updateHsr() { + this.removeAnyHsrNotification(); + if (!this.checkRequiredFieldsAreSet()) { + return; + } + SessionRecordingStore.createOrUpdateHsr(this.siteHsr, 'HeatmapSessionRecording.updateSessionRecording').then(response => { + if (response.type === 'error') { + return; + } + this.isDirty = false; + this.siteHsr = {}; + SessionRecordingStore.reload().then(() => { + this.init(); + }); + this.showNotification(Object(external_CoreHome_["translate"])('HeatmapSessionRecording_SessionRecordingUpdated'), response.type); + }); + }, + checkRequiredFieldsAreSet() { + var _this$siteHsr$match_p3; + if (!this.siteHsr.name) { + const title = this.translate('General_Name'); + this.showErrorFieldNotProvidedNotification(title); + return false; + } + if (!((_this$siteHsr$match_p3 = this.siteHsr.match_page_rules) !== null && _this$siteHsr$match_p3 !== void 0 && _this$siteHsr$match_p3.length) || !SessionRecordingStore.filterRules(this.siteHsr.match_page_rules).length) { + const title = this.translate('HeatmapSessionRecording_ErrorPageRuleRequired'); + this.showNotification(title, 'error'); + return false; + } + return true; + }, + setMatchPageRule(rule, index) { + this.siteHsr.match_page_rules = [...this.siteHsr.match_page_rules]; + this.siteHsr.match_page_rules[index] = rule; + } + }, + computed: { + minSessionTimes() { + return [0, 5, 10, 15, 20, 30, 45, 60, 90, 120].map(v => ({ + key: `${v}`, + value: `${v} seconds` + })); + }, + sampleRates() { + const rates = [0.1, 0.5, 1, 2, 3, 4, 5, 6, 8, 10, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100]; + return rates.map(v => ({ + key: `${v.toFixed(1)}`, + value: `${v}%` + })); + }, + create() { + return !this.idSiteHsr; + }, + edit() { + return !this.create; + }, + editTitle() { + const token = this.create ? 'HeatmapSessionRecording_CreateNewSessionRecording' : 'HeatmapSessionRecording_EditSessionRecordingX'; + return token; + }, + contentTitle() { + return Object(external_CoreHome_["translate"])(this.editTitle, this.siteHsr.name ? `"${this.siteHsr.name}"` : ''); + }, + isLoading() { + return HeatmapStore.state.value.isLoading; + }, + isUpdating() { + return HeatmapStore.state.value.isUpdating; + }, + captureKeystrokesHelp() { + const link = 'https://developer.matomo.org/guides/heatmap-session-recording/setup#masking-keystrokes-in-form-fields'; + return Object(external_CoreHome_["translate"])('HeatmapSessionRecording_CaptureKeystrokesHelp', ``, ''); + }, + personalInformationNote() { + const link = 'https://developer.matomo.org/guides/heatmap-session-recording/setup#masking-content-on-your-website'; + return Object(external_CoreHome_["translate"])('HeatmapSessionRecording_PersonalInformationNote', Object(external_CoreHome_["translate"])('HeatmapSessionRecording_SessionRecording'), '', '', ``, ''); + }, + saveButtonText() { + return this.edit ? Object(external_CoreHome_["translate"])('CoreUpdater_UpdateTitle') : Object(external_CoreHome_["translate"])('HeatmapSessionRecording_CreateNewSessionRecording'); + } + } +})); +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/ManageSessionRecording/Edit.vue?vue&type=script&lang=ts + +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/ManageSessionRecording/Edit.vue + + + +ManageSessionRecording_Editvue_type_script_lang_ts.render = Editvue_type_template_id_56c3e386_render + +/* harmony default export */ var ManageSessionRecording_Edit = (ManageSessionRecording_Editvue_type_script_lang_ts); +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--13-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/ManageSessionRecording/List.vue?vue&type=template&id=09d6f8c4 + +const Listvue_type_template_id_09d6f8c4_hoisted_1 = { + class: "sessionRecordingList" +}; +const Listvue_type_template_id_09d6f8c4_hoisted_2 = { + class: "filterStatus" +}; +const Listvue_type_template_id_09d6f8c4_hoisted_3 = { + class: "hsrSearchFilter", + style: { + "margin-left": "3.5px" + } +}; +const Listvue_type_template_id_09d6f8c4_hoisted_4 = { + class: "index" +}; +const Listvue_type_template_id_09d6f8c4_hoisted_5 = { + class: "name" +}; +const Listvue_type_template_id_09d6f8c4_hoisted_6 = { + class: "creationDate" +}; +const Listvue_type_template_id_09d6f8c4_hoisted_7 = { + class: "sampleLimit" +}; +const Listvue_type_template_id_09d6f8c4_hoisted_8 = { + class: "status" +}; +const Listvue_type_template_id_09d6f8c4_hoisted_9 = { + class: "action" +}; +const Listvue_type_template_id_09d6f8c4_hoisted_10 = { + colspan: "7" +}; +const Listvue_type_template_id_09d6f8c4_hoisted_11 = { + class: "loadingPiwik" +}; +const Listvue_type_template_id_09d6f8c4_hoisted_12 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("img", { + src: "plugins/Morpheus/images/loading-blue.gif" +}, null, -1); +const Listvue_type_template_id_09d6f8c4_hoisted_13 = { + colspan: "7" +}; +const Listvue_type_template_id_09d6f8c4_hoisted_14 = ["id"]; +const Listvue_type_template_id_09d6f8c4_hoisted_15 = { + class: "index" +}; +const Listvue_type_template_id_09d6f8c4_hoisted_16 = { + class: "name" +}; +const Listvue_type_template_id_09d6f8c4_hoisted_17 = { + class: "creationDate" +}; +const Listvue_type_template_id_09d6f8c4_hoisted_18 = { + class: "sampleLimit" +}; +const Listvue_type_template_id_09d6f8c4_hoisted_19 = { + key: 0, + class: "status status-paused" +}; +const Listvue_type_template_id_09d6f8c4_hoisted_20 = ["title"]; +const Listvue_type_template_id_09d6f8c4_hoisted_21 = { + key: 1, + class: "status" +}; +const Listvue_type_template_id_09d6f8c4_hoisted_22 = { + class: "action" +}; +const Listvue_type_template_id_09d6f8c4_hoisted_23 = ["title", "onClick"]; +const Listvue_type_template_id_09d6f8c4_hoisted_24 = ["title", "onClick"]; +const Listvue_type_template_id_09d6f8c4_hoisted_25 = ["title", "href"]; +const Listvue_type_template_id_09d6f8c4_hoisted_26 = ["title", "onClick"]; +const Listvue_type_template_id_09d6f8c4_hoisted_27 = { + class: "tableActionBar" +}; +const Listvue_type_template_id_09d6f8c4_hoisted_28 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "icon-add" +}, null, -1); +const Listvue_type_template_id_09d6f8c4_hoisted_29 = { + class: "ui-confirm", + ref: "confirmDeleteSessionRecording" +}; +const Listvue_type_template_id_09d6f8c4_hoisted_30 = ["value"]; +const Listvue_type_template_id_09d6f8c4_hoisted_31 = ["value"]; +const Listvue_type_template_id_09d6f8c4_hoisted_32 = { + class: "ui-confirm", + ref: "confirmEndSessionRecording" +}; +const Listvue_type_template_id_09d6f8c4_hoisted_33 = ["value"]; +const Listvue_type_template_id_09d6f8c4_hoisted_34 = ["value"]; +function Listvue_type_template_id_09d6f8c4_render(_ctx, _cache, $props, $setup, $data, $options) { + const _component_Field = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("Field"); + const _component_ContentBlock = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("ContentBlock"); + const _directive_content_table = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveDirective"])("content-table"); + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", Listvue_type_template_id_09d6f8c4_hoisted_1, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_ContentBlock, { + "content-title": _ctx.translate('HeatmapSessionRecording_ManageSessionRecordings') + }, { + default: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withCtx"])(() => [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("p", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_SessionRecordingsUsageBenefits')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Listvue_type_template_id_09d6f8c4_hoisted_2, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, { + uicontrol: "select", + name: "filterStatus", + "model-value": _ctx.filterStatus, + "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => { + _ctx.setFilterStatus($event); + }), + title: _ctx.translate('HeatmapSessionRecording_Filter'), + "full-width": true, + options: _ctx.statusOptions + }, null, 8, ["model-value", "title", "options"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Listvue_type_template_id_09d6f8c4_hoisted_3, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_Field, { + uicontrol: "text", + name: "hsrSearch", + title: _ctx.translate('General_Search'), + modelValue: _ctx.searchFilter, + "onUpdate:modelValue": _cache[1] || (_cache[1] = $event => _ctx.searchFilter = $event), + "full-width": true + }, null, 8, ["title", "modelValue"]), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.hsrs.length > 0]])])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])((Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("table", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("thead", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("tr", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("th", Listvue_type_template_id_09d6f8c4_hoisted_4, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Id')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("th", Listvue_type_template_id_09d6f8c4_hoisted_5, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Name')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("th", Listvue_type_template_id_09d6f8c4_hoisted_6, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_CreationDate')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("th", Listvue_type_template_id_09d6f8c4_hoisted_7, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_SampleLimit')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("th", Listvue_type_template_id_09d6f8c4_hoisted_8, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('CorePluginsAdmin_Status')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("th", Listvue_type_template_id_09d6f8c4_hoisted_9, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Actions')), 1)])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("tbody", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("tr", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("td", Listvue_type_template_id_09d6f8c4_hoisted_10, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", Listvue_type_template_id_09d6f8c4_hoisted_11, [Listvue_type_template_id_09d6f8c4_hoisted_12, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(" " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_LoadingData')), 1)])])], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.isLoading || _ctx.isUpdating]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("tr", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("td", Listvue_type_template_id_09d6f8c4_hoisted_13, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_NoSessionRecordingsFound')), 1)], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], !_ctx.isLoading && _ctx.hsrs.length == 0]]), (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])(external_commonjs_vue_commonjs2_vue_root_Vue_["Fragment"], null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderList"])(_ctx.sortedHsrs, hsr => { + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("tr", { + id: `hsr${hsr.idsitehsr}`, + class: "hsrs", + key: hsr.idsitehsr + }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("td", Listvue_type_template_id_09d6f8c4_hoisted_15, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(hsr.idsitehsr), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("td", Listvue_type_template_id_09d6f8c4_hoisted_16, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(hsr.name), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("td", Listvue_type_template_id_09d6f8c4_hoisted_17, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(hsr.created_date_pretty), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("td", Listvue_type_template_id_09d6f8c4_hoisted_18, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(hsr.sample_limit), 1), hsr.status === 'paused' ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("td", Listvue_type_template_id_09d6f8c4_hoisted_19, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.ucfirst(hsr.status)) + " ", 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "icon icon-help", + title: _ctx.pauseReason + }, null, 8, Listvue_type_template_id_09d6f8c4_hoisted_20)])) : (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("td", Listvue_type_template_id_09d6f8c4_hoisted_21, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.ucfirst(hsr.status)), 1)), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("td", Listvue_type_template_id_09d6f8c4_hoisted_22, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("a", { + class: "table-action icon-edit", + title: _ctx.translate('HeatmapSessionRecording_EditX', _ctx.translate('HeatmapSessionRecording_SessionRecording')), + onClick: $event => _ctx.editHsr(hsr.idsitehsr) + }, null, 8, Listvue_type_template_id_09d6f8c4_hoisted_23), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("a", { + class: "table-action stopRecording icon-drop-crossed", + title: _ctx.translate('HeatmapSessionRecording_StopX', _ctx.translate('HeatmapSessionRecording_SessionRecording')), + onClick: $event => _ctx.completeHsr(hsr) + }, null, 8, Listvue_type_template_id_09d6f8c4_hoisted_24), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], hsr.status !== 'ended']]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("a", { + class: "table-action icon-show", + title: _ctx.translate('HeatmapSessionRecording_ViewReport'), + href: _ctx.getViewReportLink(hsr), + target: "_blank" + }, null, 8, Listvue_type_template_id_09d6f8c4_hoisted_25), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("a", { + class: "table-action icon-delete", + title: _ctx.translate('HeatmapSessionRecording_DeleteX', _ctx.translate('HeatmapSessionRecording_SessionRecording')), + onClick: $event => _ctx.deleteHsr(hsr) + }, null, 8, Listvue_type_template_id_09d6f8c4_hoisted_26)])], 8, Listvue_type_template_id_09d6f8c4_hoisted_14); + }), 128))])])), [[_directive_content_table]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Listvue_type_template_id_09d6f8c4_hoisted_27, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("a", { + class: "createNewHsr", + value: "", + onClick: _cache[2] || (_cache[2] = $event => _ctx.createHsr()) + }, [Listvue_type_template_id_09d6f8c4_hoisted_28, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(" " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_CreateNewSessionRecording')), 1)])])]), + _: 1 + }, 8, ["content-title"]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Listvue_type_template_id_09d6f8c4_hoisted_29, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("h2", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_DeleteSessionRecordingConfirm')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("input", { + role: "yes", + type: "button", + value: _ctx.translate('General_Yes') + }, null, 8, Listvue_type_template_id_09d6f8c4_hoisted_30), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("input", { + role: "no", + type: "button", + value: _ctx.translate('General_No') + }, null, 8, Listvue_type_template_id_09d6f8c4_hoisted_31)], 512), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Listvue_type_template_id_09d6f8c4_hoisted_32, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("h2", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_EndSessionRecordingConfirm')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("input", { + role: "yes", + type: "button", + value: _ctx.translate('General_Yes') + }, null, 8, Listvue_type_template_id_09d6f8c4_hoisted_33), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("input", { + role: "no", + type: "button", + value: _ctx.translate('General_No') + }, null, 8, Listvue_type_template_id_09d6f8c4_hoisted_34)], 512)]); +} +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/ManageSessionRecording/List.vue?vue&type=template&id=09d6f8c4 + +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--15-0!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--15-2!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/ManageSessionRecording/List.vue?vue&type=script&lang=ts + + + + +/* harmony default export */ var ManageSessionRecording_Listvue_type_script_lang_ts = (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["defineComponent"])({ + props: { + pauseReason: String + }, + components: { + ContentBlock: external_CoreHome_["ContentBlock"], + Field: external_CorePluginsAdmin_["Field"] + }, + directives: { + ContentTable: external_CoreHome_["ContentTable"] + }, + data() { + return { + searchFilter: '' + }; + }, + created() { + SessionRecordingStore.setFilterStatus(''); + SessionRecordingStore.fetchHsrs(); + }, + methods: { + createHsr() { + this.editHsr(0); + }, + editHsr(idSiteHsr) { + external_CoreHome_["MatomoUrl"].updateHash(Object.assign(Object.assign({}, external_CoreHome_["MatomoUrl"].hashParsed.value), {}, { + idSiteHsr + })); + }, + deleteHsr(hsr) { + external_CoreHome_["Matomo"].helper.modalConfirm(this.$refs.confirmDeleteSessionRecording, { + yes: () => { + SessionRecordingStore.deleteHsr(hsr.idsitehsr).then(() => { + SessionRecordingStore.reload(); + external_CoreHome_["Matomo"].postEvent('updateReportingMenu'); + }); + } + }); + }, + completeHsr(hsr) { + external_CoreHome_["Matomo"].helper.modalConfirm(this.$refs.confirmEndSessionRecording, { + yes: () => { + SessionRecordingStore.completeHsr(hsr.idsitehsr).then(() => { + SessionRecordingStore.reload(); + }); + } + }); + }, + setFilterStatus(filter) { + SessionRecordingStore.setFilterStatus(filter); + }, + ucfirst(s) { + return `${s[0].toUpperCase()}${s.substr(1)}`; + }, + getViewReportLink(hsr) { + return `?${external_CoreHome_["MatomoUrl"].stringify({ + module: 'CoreHome', + action: 'index', + idSite: hsr.idsite, + period: 'day', + date: 'yesterday' + })}#?${external_CoreHome_["MatomoUrl"].stringify({ + category: 'HeatmapSessionRecording_SessionRecordings', + idSite: hsr.idsite, + period: 'day', + date: 'yesterday', + subcategory: hsr.idsitehsr + })}`; + } + }, + computed: { + filterStatus() { + return SessionRecordingStore.state.value.filterStatus; + }, + statusOptions() { + return SessionRecordingStore.statusOptions; + }, + hsrs() { + return SessionRecordingStore.hsrs.value; + }, + isLoading() { + return SessionRecordingStore.state.value.isLoading; + }, + isUpdating() { + return SessionRecordingStore.state.value.isUpdating; + }, + sortedHsrs() { + // look through string properties of heatmaps for values that have searchFilter in them + // (mimics angularjs filter() filter) + const result = [...this.hsrs].filter(h => Object.keys(h).some(propName => { + const entity = h; + return typeof entity[propName] === 'string' && entity[propName].indexOf(this.searchFilter) !== -1; + })); + result.sort((lhs, rhs) => rhs.idsitehsr - lhs.idsitehsr); + return result; + } + } +})); +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/ManageSessionRecording/List.vue?vue&type=script&lang=ts + +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/ManageSessionRecording/List.vue + + + +ManageSessionRecording_Listvue_type_script_lang_ts.render = Listvue_type_template_id_09d6f8c4_render + +/* harmony default export */ var ManageSessionRecording_List = (ManageSessionRecording_Listvue_type_script_lang_ts); +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--13-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/ManageSessionRecording/Manage.vue?vue&type=template&id=4a6cf182 + +const Managevue_type_template_id_4a6cf182_hoisted_1 = { + class: "manageHsr" +}; +function Managevue_type_template_id_4a6cf182_render(_ctx, _cache, $props, $setup, $data, $options) { + const _component_MatomoJsNotWritableAlert = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("MatomoJsNotWritableAlert"); + const _component_SessionRecordingList = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("SessionRecordingList"); + const _component_SessionRecordingEdit = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("SessionRecordingEdit"); + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])(external_commonjs_vue_commonjs2_vue_root_Vue_["Fragment"], null, [!_ctx.editMode ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createBlock"])(_component_MatomoJsNotWritableAlert, { + key: 0, + "is-matomo-js-writable": _ctx.isMatomoJsWritable, + "recording-type": _ctx.translate('HeatmapSessionRecording_SessionRecordings') + }, null, 8, ["is-matomo-js-writable", "recording-type"])) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Managevue_type_template_id_4a6cf182_hoisted_1, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_SessionRecordingList, { + "pause-reason": _ctx.pauseReason + }, null, 8, ["pause-reason"])], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], !_ctx.editMode]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_SessionRecordingEdit, { + "id-site-hsr": _ctx.idSiteHsr + }, null, 8, ["id-site-hsr"])], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.editMode]])])], 64); +} +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/ManageSessionRecording/Manage.vue?vue&type=template&id=4a6cf182 + +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--15-0!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--15-2!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/ManageSessionRecording/Manage.vue?vue&type=script&lang=ts + + + + + +/* harmony default export */ var ManageSessionRecording_Managevue_type_script_lang_ts = (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["defineComponent"])({ + props: { + pauseReason: String, + isMatomoJsWritable: { + type: Boolean, + required: true + } + }, + data() { + return { + editMode: false, + idSiteHsr: null + }; + }, + components: { + MatomoJsNotWritableAlert: MatomoJsNotWritableAlert, + SessionRecordingEdit: ManageSessionRecording_Edit, + SessionRecordingList: ManageSessionRecording_List + }, + created() { + // doing this in a watch because we don't want to post an event in a computed property + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["watch"])(() => external_CoreHome_["MatomoUrl"].hashParsed.value.idSiteHsr, idSiteHsr => { + this.initState(idSiteHsr); + }); + this.initState(external_CoreHome_["MatomoUrl"].hashParsed.value.idSiteHsr); + }, + methods: { + removeAnyHsrNotification() { + external_CoreHome_["NotificationsStore"].remove('hsrmanagement'); + }, + initState(idSiteHsr) { + if (idSiteHsr) { + if (idSiteHsr === '0') { + const parameters = { + isAllowed: true + }; + external_CoreHome_["Matomo"].postEvent('HeatmapSessionRecording.initAddSessionRecording', parameters); + if (parameters && !parameters.isAllowed) { + this.editMode = false; + this.idSiteHsr = null; + return; + } + } + this.editMode = true; + this.idSiteHsr = parseInt(idSiteHsr, 10); + } else { + this.editMode = false; + this.idSiteHsr = null; + } + this.removeAnyHsrNotification(); + } + } +})); +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/ManageSessionRecording/Manage.vue?vue&type=script&lang=ts + +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/ManageSessionRecording/Manage.vue + + + +ManageSessionRecording_Managevue_type_script_lang_ts.render = Managevue_type_template_id_4a6cf182_render + +/* harmony default export */ var ManageSessionRecording_Manage = (ManageSessionRecording_Managevue_type_script_lang_ts); +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--13-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/ListOfPageviews/ListOfPageviews.vue?vue&type=template&id=fe86de22 + +const ListOfPageviewsvue_type_template_id_fe86de22_hoisted_1 = { + class: "ui-confirm", + id: "listOfPageviews" +}; +const ListOfPageviewsvue_type_template_id_fe86de22_hoisted_2 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("br", null, null, -1); +const ListOfPageviewsvue_type_template_id_fe86de22_hoisted_3 = /*#__PURE__*/Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("br", null, null, -1); +const ListOfPageviewsvue_type_template_id_fe86de22_hoisted_4 = ["onClick"]; +const ListOfPageviewsvue_type_template_id_fe86de22_hoisted_5 = ["title"]; +const ListOfPageviewsvue_type_template_id_fe86de22_hoisted_6 = ["value"]; +function ListOfPageviewsvue_type_template_id_fe86de22_render(_ctx, _cache, $props, $setup, $data, $options) { + const _directive_content_table = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveDirective"])("content-table"); + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", ListOfPageviewsvue_type_template_id_fe86de22_hoisted_1, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("h2", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_PageviewsInVisit')), 1), ListOfPageviewsvue_type_template_id_fe86de22_hoisted_2, ListOfPageviewsvue_type_template_id_fe86de22_hoisted_3, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])((Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("table", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("thead", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("tr", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("th", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_ColumnTime')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("th", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_TimeOnPage')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("th", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('Goals_URL')), 1)])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("tbody", null, [(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])(external_commonjs_vue_commonjs2_vue_root_Vue_["Fragment"], null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderList"])(_ctx.pageviews, pageview => { + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("tr", { + key: pageview.idloghsr, + class: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeClass"])({ + inactive: pageview.idloghsr !== _ctx.idLogHsr + }), + onClick: $event => _ctx.onClickPageView(pageview) + }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("td", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(pageview.server_time_pretty), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("td", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(pageview.time_on_page_pretty), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("td", { + title: pageview.label + }, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])((pageview.label || '').substr(0, 50)), 9, ListOfPageviewsvue_type_template_id_fe86de22_hoisted_5)], 10, ListOfPageviewsvue_type_template_id_fe86de22_hoisted_4); + }), 128))])])), [[_directive_content_table]]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("input", { + role: "close", + type: "button", + value: _ctx.translate('General_Close') + }, null, 8, ListOfPageviewsvue_type_template_id_fe86de22_hoisted_6)]); +} +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/ListOfPageviews/ListOfPageviews.vue?vue&type=template&id=fe86de22 + +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--15-0!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--15-2!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/ListOfPageviews/ListOfPageviews.vue?vue&type=script&lang=ts + + +/* harmony default export */ var ListOfPageviewsvue_type_script_lang_ts = (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["defineComponent"])({ + props: { + pageviews: { + type: Array, + required: true + }, + idLogHsr: { + type: Number, + required: true + } + }, + directives: { + ContentTable: external_CoreHome_["ContentTable"] + }, + methods: { + onClickPageView(pageview) { + if (pageview.idloghsr === this.idLogHsr) { + return; + } + external_CoreHome_["MatomoUrl"].updateUrl(Object.assign(Object.assign({}, external_CoreHome_["MatomoUrl"].urlParsed.value), {}, { + idLogHsr: pageview.idloghsr + }), external_CoreHome_["MatomoUrl"].hashParsed.value.length ? Object.assign(Object.assign({}, external_CoreHome_["MatomoUrl"].hashParsed.value), {}, { + idLogHsr: pageview.idloghsr + }) : undefined); + } + } +})); +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/ListOfPageviews/ListOfPageviews.vue?vue&type=script&lang=ts + +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/ListOfPageviews/ListOfPageviews.vue + + + +ListOfPageviewsvue_type_script_lang_ts.render = ListOfPageviewsvue_type_template_id_fe86de22_render + +/* harmony default export */ var ListOfPageviews = (ListOfPageviewsvue_type_script_lang_ts); +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--13-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/HeatmapVis/HeatmapVisPage.vue?vue&type=template&id=7f5f8230 + +const HeatmapVisPagevue_type_template_id_7f5f8230_hoisted_1 = { + class: "heatmap-vis-title" +}; +const HeatmapVisPagevue_type_template_id_7f5f8230_hoisted_2 = { + key: 0, + class: "alert alert-info heatmap-country-alert" +}; +const HeatmapVisPagevue_type_template_id_7f5f8230_hoisted_3 = { + key: 1 +}; +const HeatmapVisPagevue_type_template_id_7f5f8230_hoisted_4 = { + key: 2 +}; +const HeatmapVisPagevue_type_template_id_7f5f8230_hoisted_5 = ["innerHTML"]; +const HeatmapVisPagevue_type_template_id_7f5f8230_hoisted_6 = { + class: "alert alert-info" +}; +const HeatmapVisPagevue_type_template_id_7f5f8230_hoisted_7 = { + key: 3 +}; +const HeatmapVisPagevue_type_template_id_7f5f8230_hoisted_8 = { + class: "alert alert-info" +}; +function HeatmapVisPagevue_type_template_id_7f5f8230_render(_ctx, _cache, $props, $setup, $data, $options) { + var _ctx$heatmapMetadata; + const _component_EnrichedHeadline = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("EnrichedHeadline"); + const _component_MatomoJsNotWritableAlert = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("MatomoJsNotWritableAlert"); + const _component_HeatmapVis = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("HeatmapVis"); + const _component_ContentBlock = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("ContentBlock"); + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", null, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("h2", HeatmapVisPagevue_type_template_id_7f5f8230_hoisted_1, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_EnrichedHeadline, { + "edit-url": _ctx.editUrl, + "inline-help": _ctx.inlineHelp + }, { + default: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withCtx"])(() => [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_HeatmapX', `"${_ctx.heatmap.name}"`)), 1)]), + _: 1 + }, 8, ["edit-url", "inline-help"])]), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_MatomoJsNotWritableAlert, { + "is-matomo-js-writable": _ctx.isMatomoJsWritable, + "recording-type": _ctx.translate('HeatmapSessionRecording_Heatmaps') + }, null, 8, ["is-matomo-js-writable", "recording-type"]), _ctx.includedCountries ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", HeatmapVisPagevue_type_template_id_7f5f8230_hoisted_2, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('HeatmapSessionRecording_HeatmapInfoTrackVisitsFromCountries', _ctx.includedCountries)), 1)) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true), _ctx.heatmap.page_treemirror ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", HeatmapVisPagevue_type_template_id_7f5f8230_hoisted_3, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_HeatmapVis, { + "created-date": _ctx.createdDate, + "excluded-elements": _ctx.heatmap.excluded_elements, + "num-samples": _ctx.heatmapMetadata, + url: _ctx.heatmap.screenshot_url, + "heatmap-date": _ctx.heatmapDate, + "heatmap-period": _ctx.heatmapPeriod, + "offset-accuracy": _ctx.offsetAccuracy, + "breakpoint-tablet": _ctx.heatmap.breakpoint_tablet, + "breakpoint-mobile": _ctx.heatmap.breakpoint_mobile, + "heatmap-types": _ctx.heatmapTypes, + "device-types": _ctx.deviceTypes, + "id-site-hsr": _ctx.idSiteHsr, + "is-active": _ctx.isActive, + "desktop-preview-size": _ctx.desktopPreviewSize, + "iframe-resolutions-values": _ctx.iframeResolutions + }, null, 8, ["created-date", "excluded-elements", "num-samples", "url", "heatmap-date", "heatmap-period", "offset-accuracy", "breakpoint-tablet", "breakpoint-mobile", "heatmap-types", "device-types", "id-site-hsr", "is-active", "desktop-preview-size", "iframe-resolutions-values"])])) : !((_ctx$heatmapMetadata = _ctx.heatmapMetadata) !== null && _ctx$heatmapMetadata !== void 0 && _ctx$heatmapMetadata.nb_samples_device_all) ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", HeatmapVisPagevue_type_template_id_7f5f8230_hoisted_4, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("p", { + innerHTML: _ctx.$sanitize(_ctx.recordedSamplesTroubleShoot) + }, null, 8, HeatmapVisPagevue_type_template_id_7f5f8230_hoisted_5), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_ContentBlock, null, { + default: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withCtx"])(() => [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", HeatmapVisPagevue_type_template_id_7f5f8230_hoisted_6, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate(_ctx.noDataMessageKey)), 1)]), + _: 1 + })])) : (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", HeatmapVisPagevue_type_template_id_7f5f8230_hoisted_7, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_ContentBlock, null, { + default: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withCtx"])(() => [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", HeatmapVisPagevue_type_template_id_7f5f8230_hoisted_8, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.noHeatmapScreenshotRecordedYetText), 1)]), + _: 1 + })]))]); +} +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/HeatmapVis/HeatmapVisPage.vue?vue&type=template&id=7f5f8230 + +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--15-0!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--15-2!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--1-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--1-1!./plugins/HeatmapSessionRecording/vue/src/HeatmapVis/HeatmapVisPage.vue?vue&type=script&lang=ts + + + + +/* harmony default export */ var HeatmapVisPagevue_type_script_lang_ts = (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["defineComponent"])({ + props: { + idSiteHsr: { + type: Number, + required: true + }, + heatmap: { + type: Object, + required: true + }, + heatmapMetadata: { + type: Object, + required: true + }, + deviceTypes: { + type: Array, + required: true + }, + heatmapTypes: { + type: Array, + required: true + }, + offsetAccuracy: { + type: Number, + required: true + }, + heatmapPeriod: { + type: String, + required: true + }, + heatmapDate: { + type: String, + required: true + }, + isActive: Boolean, + createdDate: { + type: String, + required: true + }, + editUrl: { + type: String, + required: true + }, + inlineHelp: { + type: String, + required: true + }, + includedCountries: { + type: String, + required: true + }, + desktopPreviewSize: { + type: Number, + required: true + }, + iframeResolutions: { + type: Object, + required: true + }, + noDataMessageKey: { + type: String, + required: true + }, + isMatomoJsWritable: { + type: Boolean, + required: true + } + }, + components: { + MatomoJsNotWritableAlert: MatomoJsNotWritableAlert, + ContentBlock: external_CoreHome_["ContentBlock"], + HeatmapVis: HeatmapVis, + EnrichedHeadline: external_CoreHome_["EnrichedHeadline"] + }, + computed: { + noHeatmapScreenshotRecordedYetText() { + return Object(external_CoreHome_["translate"])('HeatmapSessionRecording_NoHeatmapScreenshotRecordedYet', this.heatmapMetadata.nb_samples_device_all, Object(external_CoreHome_["translate"])('HeatmapSessionRecording_ScreenshotUrl')); + }, + recordedSamplesTroubleShoot() { + const linkString = Object(external_CoreHome_["externalLink"])('https://matomo.org/faq/heatmap-session-recording/troubleshooting-heatmaps/'); + return Object(external_CoreHome_["translate"])('HeatmapSessionRecording_HeatmapTroubleshoot', linkString, ''); + } + }, + created() { + // We want the selector hidden for heatmaps. + external_CoreHome_["Matomo"].postEvent('hidePeriodSelector'); + } +})); +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/HeatmapVis/HeatmapVisPage.vue?vue&type=script&lang=ts + +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/HeatmapVis/HeatmapVisPage.vue + + + +HeatmapVisPagevue_type_script_lang_ts.render = HeatmapVisPagevue_type_template_id_7f5f8230_render + +/* harmony default export */ var HeatmapVisPage = (HeatmapVisPagevue_type_script_lang_ts); +// CONCATENATED MODULE: ./plugins/HeatmapSessionRecording/vue/src/index.ts +/** + * Copyright (C) InnoCraft Ltd - All rights reserved. + * + * NOTICE: All information contained herein is, and remains the property of InnoCraft Ltd. + * The intellectual and technical concepts contained herein are protected by trade secret + * or copyright law. Redistribution of this information or reproduction of this material is + * strictly forbidden unless prior written permission is obtained from InnoCraft Ltd. + * + * You shall use this code only in accordance with the license agreement obtained from + * InnoCraft Ltd. + * + * @link https://www.innocraft.com/ + * @license For license details see https://www.innocraft.com/license + */ + + + + + + + + + + + + + + +// CONCATENATED MODULE: ./node_modules/@vue/cli-service/lib/commands/build/entry-lib-no-default.js + + + + +/***/ }) + +/******/ }); +}); +//# sourceMappingURL=HeatmapSessionRecording.umd.js.map \ No newline at end of file diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/vue/dist/HeatmapSessionRecording.umd.min.js b/files/plugin-HeatmapSessionRecording-5.2.4/vue/dist/HeatmapSessionRecording.umd.min.js new file mode 100644 index 0000000..71fc647 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/vue/dist/HeatmapSessionRecording.umd.min.js @@ -0,0 +1,73 @@ +(function(e,t){"object"===typeof exports&&"object"===typeof module?module.exports=t(require("CoreHome"),require("vue"),require("CorePluginsAdmin")):"function"===typeof define&&define.amd?define(["CoreHome",,"CorePluginsAdmin"],t):"object"===typeof exports?exports["HeatmapSessionRecording"]=t(require("CoreHome"),require("vue"),require("CorePluginsAdmin")):e["HeatmapSessionRecording"]=t(e["CoreHome"],e["Vue"],e["CorePluginsAdmin"])})("undefined"!==typeof self?self:this,(function(e,t,a){return function(e){var t={};function a(i){if(t[i])return t[i].exports;var n=t[i]={i:i,l:!1,exports:{}};return e[i].call(n.exports,n,n.exports,a),n.l=!0,n.exports}return a.m=e,a.c=t,a.d=function(e,t,i){a.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},a.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,t){if(1&t&&(e=a(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(a.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)a.d(i,n,function(t){return e[t]}.bind(null,n));return i},a.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return a.d(t,"a",t),t},a.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},a.p="plugins/HeatmapSessionRecording/vue/dist/",a(a.s="fae3")}({"19dc":function(t,a){t.exports=e},"246e":function(e,t,a){var i,n;(function(s,r,o){e.exports?e.exports=o():(i=o,n="function"===typeof i?i.call(t,a,t,e):i,void 0===n||(e.exports=n))})(0,0,(function(){var e={defaultRadius:40,defaultRenderer:"canvas2d",defaultGradient:{.25:"rgb(0,0,255)",.55:"rgb(0,255,0)",.85:"yellow",1:"rgb(255,0,0)"},defaultMaxOpacity:1,defaultMinOpacity:0,defaultBlur:.85,defaultXField:"x",defaultYField:"y",defaultValueField:"value",plugins:{}},t=function(){var t=function(e){this._coordinator={},this._data=[],this._radi=[],this._min=10,this._max=1,this._xField=e["xField"]||e.defaultXField,this._yField=e["yField"]||e.defaultYField,this._valueField=e["valueField"]||e.defaultValueField,e["radius"]&&(this._cfgRadius=e["radius"])},a=e.defaultRadius;return t.prototype={_organiseData:function(e,t){var i=e[this._xField],n=e[this._yField],s=this._radi,r=this._data,o=this._max,l=this._min,c=e[this._valueField]||1,d=e.radius||this._cfgRadius||a;r[i]||(r[i]=[],s[i]=[]),r[i][n]?r[i][n]+=c:(r[i][n]=c,s[i][n]=d);var m=r[i][n];return m>o?(t?this.setDataMax(m):this._max=m,!1):m0){var e=arguments[0],t=e.length;while(t--)this.addData.call(this,e[t])}else{var a=this._organiseData(arguments[0],!0);a&&(0===this._data.length&&(this._min=this._max=a.value),this._coordinator.emit("renderpartial",{min:this._min,max:this._max,data:[a]}))}return this},setData:function(e){var t=e.data,a=t.length;this._data=[],this._radi=[];for(var i=0;i0&&(this._drawAlpha(e),this._colorize())},renderAll:function(e){this._clear(),e.data.length>0&&(this._drawAlpha(a(e)),this._colorize())},_updateGradient:function(t){this._palette=e(t)},updateConfig:function(e){e["gradient"]&&this._updateGradient(e),this._setStyles(e)},setDimensions:function(e,t){this._width=e,this._height=t,this.canvas.width=this.shadowCanvas.width=e,this.canvas.height=this.shadowCanvas.height=t},_clear:function(){this.shadowCtx.clearRect(0,0,this._width,this._height),this.ctx.clearRect(0,0,this._width,this._height)},_setStyles:function(e){this._blur=0==e.blur?0:e.blur||e.defaultBlur,e.backgroundColor&&(this.canvas.style.backgroundColor=e.backgroundColor),this._width=this.canvas.width=this.shadowCanvas.width=e.width||this._width,this._height=this.canvas.height=this.shadowCanvas.height=e.height||this._height,this._opacity=255*(e.opacity||0),this._maxOpacity=255*(e.maxOpacity||e.defaultMaxOpacity),this._minOpacity=255*(e.minOpacity||e.defaultMinOpacity),this._useGradientOpacity=!!e.useGradientOpacity},_drawAlpha:function(e){var a=this._min=e.min,i=this._max=e.max,n=(e=e.data||[],e.length),s=1-this._blur;while(n--){var r,o=e[n],l=o.x,c=o.y,d=o.radius,m=Math.min(o.value,i),p=l-d,h=c-d,u=this.shadowCtx;this._templates[d]?r=this._templates[d]:this._templates[d]=r=t(d,s);var g=(m-a)/(i-a);u.globalAlpha=g<.01?.01:g,u.drawImage(r,p,h),pthis._renderBoundaries[2]&&(this._renderBoundaries[2]=p+2*d),h+2*d>this._renderBoundaries[3]&&(this._renderBoundaries[3]=h+2*d)}},_colorize:function(){var e=this._renderBoundaries[0],t=this._renderBoundaries[1],a=this._renderBoundaries[2]-e,i=this._renderBoundaries[3]-t,n=this._width,s=this._height,r=this._opacity,o=this._maxOpacity,l=this._minOpacity,c=this._useGradientOpacity;e<0&&(e=0),t<0&&(t=0),e+a>n&&(a=n-e),t+i>s&&(i=s-t);for(var d=this.shadowCtx.getImageData(e,t,a,i),m=d.data,p=m.length,h=this._palette,u=3;u0?r:b>0,t},getDataURL:function(){return this.canvas.toDataURL()}},i}(),i=function(){var t=!1;return"canvas2d"===e["defaultRenderer"]&&(t=a),t}(),n={merge:function(){for(var e={},t=arguments.length,a=0;a(Object(s["openBlock"])(),Object(s["createElementBlock"])("span",{class:Object(s["normalizeClass"])(["btn-flat",{visActive:t.key===e.heatmapType,["heatmapType"+t.key]:!0}]),onClick:a=>e.changeHeatmapType(t.key),key:t.key},Object(s["toDisplayString"])(t.name),11,d))),128)),Object(s["createElementVNode"])("h4",m,Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_DeviceType")),1),(Object(s["openBlock"])(!0),Object(s["createElementBlock"])(s["Fragment"],null,Object(s["renderList"])(e.deviceTypesWithSamples,t=>(Object(s["openBlock"])(),Object(s["createElementBlock"])("span",{class:Object(s["normalizeClass"])(["btn-flat",{visActive:t.key===e.deviceType,["deviceType"+t.key]:!0}]),title:t.tooltip,onClick:a=>e.changeDeviceType(t.key),key:t.key},[Object(s["createElementVNode"])("img",{height:"15",src:t.logo,alt:`${e.translate("DevicesDetection_Device")} ${t.name}`},null,8,h),Object(s["createTextVNode"])(),Object(s["createElementVNode"])("span",u,Object(s["toDisplayString"])(t.numSamples),1)],10,p))),128)),Object(s["createElementVNode"])("div",g,[Object(s["createElementVNode"])("h4",null,Object(s["toDisplayString"])(e.translate("Installation_Legend")),1),Object(s["createElementVNode"])("div",b,[v,Object(s["createElementVNode"])("img",{class:"gradient",alt:"gradient",src:e.gradientImgData},null,8,O),j])]),Object(s["createElementVNode"])("div",f,[Object(s["createElementVNode"])("span",{style:{"margin-left":"2.5rem","margin-right":"13.5px"},textContent:Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_Width"))},null,8,y),Object(s["createVNode"])(P,{uicontrol:"select",name:"iframewidth","model-value":e.customIframeWidth,"onUpdate:modelValue":t[0]||(t[0]=t=>{e.customIframeWidth=t,e.changeIframeWidth(e.customIframeWidth,!0)}),options:e.iframeWidthOptions},null,8,["model-value","options"])])]),Object(s["createElementVNode"])("div",S,[Object(s["createElementVNode"])("div",H,[Object(s["createElementVNode"])("div",_,null,512),V]),Object(s["withDirectives"])(Object(s["createElementVNode"])("div",{class:"hsrLoadingOuter",style:Object(s["normalizeStyle"])([{height:"400px"},{width:e.iframeWidth+"px"}])},[N,Object(s["createElementVNode"])("div",E,[Object(s["createElementVNode"])("div",R,Object(s["toDisplayString"])(e.translate("General_Loading")),1)])],4),[[s["vShow"],e.isLoading]]),Object(s["withDirectives"])(Object(s["createElementVNode"])("div",{class:"aboveFoldLine",title:e.translate("HeatmapSessionRecording_AvgAboveFoldDescription"),style:Object(s["normalizeStyle"])({width:e.iframeWidth+"px",top:e.avgFold+"px"})},[Object(s["createElementVNode"])("div",null,Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_AvgAboveFoldTitle",e.avgFold)),1)],12,w),[[s["vShow"],e.avgFold]]),e.embedUrl?(Object(s["openBlock"])(),Object(s["createElementBlock"])("iframe",{key:0,id:"recordingPlayer",ref:"recordingPlayer",sandbox:"allow-scripts allow-same-origin",referrerpolicy:"no-referrer",onLoad:t[1]||(t[1]=t=>e.onLoaded()),height:"400",src:e.embedUrl,width:e.iframeWidth},null,40,k)):Object(s["createCommentVNode"])("",!0)],512),Object(s["withDirectives"])(Object(s["createElementVNode"])("div",x,[Object(s["createVNode"])(B,{style:{display:"block !important"},loading:e.isLoading,onClick:t[2]||(t[2]=t=>e.deleteScreenshot()),value:e.translate("HeatmapSessionRecording_DeleteScreenshot")},null,8,["loading","value"])],512),[[s["vShow"],e.showDeleteScreenshot]]),Object(s["createElementVNode"])("div",C,[Object(s["createElementVNode"])("h2",null,Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_DeleteHeatmapScreenshotConfirm")),1),Object(s["createElementVNode"])("input",{role:"yes",type:"button",value:e.translate("General_Yes")},null,8,T),Object(s["createElementVNode"])("input",{role:"no",type:"button",value:e.translate("General_No")},null,8,D)],512),Object(s["createVNode"])(A,{ref:"tooltip","click-count":e.clickCount,"click-rate":e.clickRate,"is-moves":1===e.heatmapType},null,8,["click-count","click-rate","is-moves"])])}var P=a("246e"),B=a.n(P),A=a("19dc"),U=a("a5a2"); +/** + * Copyright (C) InnoCraft Ltd - All rights reserved. + * + * NOTICE: All information contained herein is, and remains the property of InnoCraft Ltd. + * The intellectual and technical concepts contained herein are protected by trade secret + * or copyright law. Redistribution of this information or reproduction of this material is + * strictly forbidden unless prior written permission is obtained from InnoCraft Ltd. + * + * You shall use this code only in accordance with the license agreement obtained from + * InnoCraft Ltd. + * + * @link https://www.innocraft.com/ + * @license For license details see https://www.innocraft.com/license + */ +function L(e){return e&&e.contentWindow?e.contentWindow:e&&e.contentDocument&&e.contentDocument.defaultView?e.contentDocument.defaultView:void 0} +/** + * Copyright (C) InnoCraft Ltd - All rights reserved. + * + * NOTICE: All information contained herein is, and remains the property of InnoCraft Ltd. + * The intellectual and technical concepts contained herein are protected by trade secret + * or copyright law. Redistribution of this information or reproduction of this material is + * strictly forbidden unless prior written permission is obtained from InnoCraft Ltd. + * + * You shall use this code only in accordance with the license agreement obtained from + * InnoCraft Ltd. + * + * @link https://www.innocraft.com/ + * @license For license details see https://www.innocraft.com/license + */function I(e,t){let a=null;return(i,n)=>(a&&(a.abort(),a=null),a=new AbortController,A["AjaxHelper"].post(Object.assign(Object.assign({},i),{},{method:e}),n,Object.assign(Object.assign({},t),{},{abortController:a})).finally(()=>{a=null}))}const F={class:"tooltip-item"},W={class:"tooltip-label"},q={class:"tooltip-value"},z={class:"tooltip-item"},$={class:"tooltip-label"},G={class:"tooltip-value"};function J(e,t,a,i,n,r){return Object(s["withDirectives"])((Object(s["openBlock"])(),Object(s["createElementBlock"])("div",{ref:"tooltipRef",class:"tooltip",style:Object(s["normalizeStyle"])(e.tooltipStyle)},[Object(s["createElementVNode"])("div",F,[Object(s["createElementVNode"])("span",W,Object(s["toDisplayString"])(e.getClickCountTranslation),1),Object(s["createElementVNode"])("span",q,Object(s["toDisplayString"])(e.getClickCount),1)]),Object(s["createElementVNode"])("div",z,[Object(s["createElementVNode"])("span",$,Object(s["toDisplayString"])(e.getClickRateTranslation),1),Object(s["createElementVNode"])("span",G,Object(s["toDisplayString"])(e.getClickRate),1)])],4)),[[s["vShow"],e.visible]])}var X=Object(s["defineComponent"])({props:{clickCount:{type:Number,required:!0},clickRate:{type:Number,required:!0},isMoves:{type:Boolean,required:!1,default:!1}},setup(){const e=Object(s["reactive"])({visible:!1,position:{top:0,left:0}}),t=Object(s["ref"])(null),a=Object(s["computed"])(()=>({top:e.position.top+"px",left:e.position.left+"px",position:"absolute",zIndex:1e3}));function i(a){const i=window.scrollY||document.documentElement.scrollTop,n=window.scrollX||document.documentElement.scrollLeft;e.position.top=a.clientY+i+10,e.position.left=a.clientX+n+10,e.visible=!0,Object(s["nextTick"])(()=>{const s=t.value;if(s){const{innerWidth:t,innerHeight:r}=window,o=s.getBoundingClientRect();o.right>t&&(e.position.left=a.clientX+n-o.width-10),o.bottom>r&&(e.position.top=a.clientY+i-o.height-10);const l=s.getBoundingClientRect();l.left<0&&(e.position.left=n+10),l.top<0&&(e.position.top=i+10)}})}function n(){e.visible=!1}return Object.assign(Object.assign({},Object(s["toRefs"])(e)),{},{tooltipRef:t,show:i,hide:n,tooltipStyle:a,translate:A["translate"]})},computed:{getClickCount(){return A["NumberFormatter"].formatNumber(this.clickCount)},getClickRate(){return A["NumberFormatter"].formatPercent(this.clickRate)},getClickCountTranslation(){const e=this.isMoves?"HeatmapSessionRecording_Moves":"HeatmapSessionRecording_Clicks";return Object(A["translate"])(e)},getClickRateTranslation(){const e=this.isMoves?"HeatmapSessionRecording_MoveRate":"HeatmapSessionRecording_ClickRate";return Object(A["translate"])(e)}}});X.render=J;var Y=X;const{$:K}=window,Q=1,Z=2,ee=3;let te=32e3;const ae=String(window.navigator.userAgent).toLowerCase();function ie(e,t,a){const i=K(e);i.css("height","400px");const n=a.getIframeHeight();i.css("height",n+"px"),K(t).css("height",n+"px").css("width",i.width()+"px").empty();const s=Math.ceil(n/te);for(let r=1;r<=s;r+=1){let e=te;r===s&&(e=n%te),K(t).append(`
    `),K(t).find("#heatmap"+r).css({height:e+"px"})}return s}function ne(e,t,a,i){const n=K(t);n.css("height","400px");const s=a.getIframeHeight();n.css("height",s+"px");const r=1e3,o=s/r,l=i.reduce((e,t)=>e+parseInt(t.value,10),0),c=[];let d=0,m=null,p=100,h=0;function u(e,t,a,i,n){return i+(e-t)/(a-t)*(n-i)}function g(e,t,a){if(t===a||!t&&!a)return[255,255,0];const i=u(e,t,a,0,255),n=(a-t)/5;return i>204?[255,u(e,a-n,a,255,0),0]:i>153?[u(e,a-2*n,a-n,0,255),255,0]:i>102?[0,255,u(e,a-3*n,a-2*n,255,0)]:i>51?[0,u(e,a-4*n,a-3*n,0,255),255]:[u(e,t,a-4*n,255,0),0,255]}if(i.forEach(e=>{const t=parseInt(e.value,10),a=parseInt(e.label,10),i=Math.round(a*o);m&&m.position===i?d+=t:(0!==l&&(p=(l-d)/l*100),d+=t,m={percentageValue:10*parseFloat(p.toFixed(1)),position:h,percent:p.toFixed(1)},c.push(m)),h=i}),c.length){const e=c.some(e=>0===e.position);e||c.unshift({percent:"100.0",percentageValue:1e3,position:0})}else c.push({percent:"0",percentageValue:0,position:0});let b=0;const v=1e3;c&&c.length&&c[0]&&(b=c[c.length-1].percentageValue);const O=n.width();let j=null;for(let f=0;f`)}K(".scrollHeatmapLeaf",e).tooltip({track:!0,items:"*",tooltipClass:"heatmapTooltip",show:!1,hide:!1}),K(".legend-area .min").text((b/10).toFixed(1)+"%"),K(".legend-area .max").text((v/10).toFixed(1)+"%")}function se(e,t,a,i){const n=ie(e,t,a),s=document.createElement("canvas");s.width=100,s.height=10;const r=document.querySelector(".legend-area .min"),o=document.querySelector(".legend-area .max"),l=document.querySelector(".legend-area .gradient"),c=s.getContext("2d");let d={};function m(e){if(r.innerHTML=""+e.min,o.innerHTML=""+e.max,e.gradient&&e.gradient!==d){d=e.gradient;const t=c.createLinearGradient(0,0,100,1);Object.keys(d).forEach(e=>{t.addColorStop(parseFloat(e),d[e])}),c.fillStyle=t,c.fillRect(0,0,100,10),l.src=s.toDataURL()}}const p=[];for(let h=1;h<=n;h+=1){const e={min:i.min,max:i.max,data:[]},t={container:document.getElementById("heatmap"+h),radius:10,maxOpacity:.5,minOpacity:0,blur:.75};if(1===h&&(t.onExtremaChange=m),i&&i.data&&i.data.length>=2e4?t.radius=8:i&&i.data&&i.data.length>=2e3&&(t.radius=9),1===n)e.data=i.data;else{const t=(h-1)*te,a=t+te-1;i.data.forEach(i=>{if(i.y>=t&&i.y<=a){const a=Object.assign(Object.assign({},i),{},{y:i.y-t});e.data.push(a)}})}const a=B.a.create(t);a.setData(e),p.push(a)}return p}ae.match(/(iPod|iPhone|iPad|Android|IEMobile|Windows Phone)/i)?te=2e3:(ae.indexOf("msie ")>0||ae.indexOf("trident/")>0||ae.indexOf("edge")>0)&&(te=8e3);var re=Object(s["defineComponent"])({props:{idSiteHsr:{type:Number,required:!0},deviceTypes:{type:Array,required:!0},heatmapTypes:{type:Array,required:!0},breakpointMobile:{type:Number,required:!0},breakpointTablet:{type:Number,required:!0},offsetAccuracy:{type:Number,required:!0},heatmapPeriod:{type:String,required:!0},heatmapDate:{type:String,required:!0},url:{type:String,required:!0},isActive:Boolean,numSamples:{type:Object,required:!0},excludedElements:{type:String,required:!0},createdDate:{type:String,required:!0},desktopPreviewSize:{type:Number,required:!0},iframeResolutionsValues:{type:Object,required:!0}},components:{Field:U["Field"],SaveButton:U["SaveButton"],Tooltip:Y},data(){return{isLoading:!1,iframeWidth:this.desktopPreviewSize,customIframeWidth:this.desktopPreviewSize,avgFold:0,heatmapType:this.heatmapTypes[0].key,deviceType:this.deviceTypes[0].key,iframeResolutions:this.iframeResolutionsValues,actualNumSamples:this.numSamples,dataCoordinates:[],currentElement:null,totalClicks:0,tooltipShowTimeoutId:null,clickCount:0,clickRate:0}},setup(e){const t=Object(s["ref"])(null);let a=null;const i=new Promise(e=>{a=e});let n=null;const r=t=>(n||(n=L(t).recordingFrame,n.excludeElements(e.excludedElements),n.addClass("html","piwikHeatmap"),n.addClass("html","matomoHeatmap"),n.addWorkaroundForSharepointHeatmaps()),n),o=Object(s["ref"])(null),l=(e,t,a,i)=>{o.value=se(e,t,a,i)};return{iframeLoadedPromise:i,onLoaded:a,getRecordedHeatmap:I("HeatmapSessionRecording.getRecordedHeatmap"),getRecordedHeatmapMetadata:I("HeatmapSessionRecording.getRecordedHeatmapMetadata"),getRecordingIframe:r,heatmapInstances:o,renderHeatmap:l,tooltip:t}},created(){-1===this.iframeResolutions.indexOf(this.breakpointMobile)&&this.iframeResolutions.push(this.breakpointMobile),-1===this.iframeResolutions.indexOf(this.breakpointTablet)&&this.iframeResolutions.push(this.breakpointTablet),this.iframeResolutions=this.iframeResolutions.sort((e,t)=>e-t),this.fetchHeatmap(),A["Matomo"].postEvent("hidePeriodSelector")},watch:{isLoading(){if(!0===this.isLoading)return;const e=window.document.getElementById("heatmapContainer");e&&(e.addEventListener("mouseleave",e=>{this.tooltipShowTimeoutId&&(clearTimeout(this.tooltipShowTimeoutId),this.tooltipShowTimeoutId=null),this.currentElement=null,this.handleTooltip(e,0,0,"hide");const t=window.document.getElementById("highlightDiv");t&&(t.hidden=!0)}),e.addEventListener("mousemove",e=>{this.handleMouseMove(e)}))}},beforeUnmount(){this.removeScrollHeatmap()},methods:{removeScrollHeatmap(){const e=this.$refs.iframeRecordingContainer;K(e).find(".scrollHeatmapLeaf").remove()},deleteScreenshot(){A["Matomo"].helper.modalConfirm(this.$refs.confirmDeleteHeatmapScreenshot,{yes:()=>{this.isLoading=!0,A["AjaxHelper"].fetch({method:"HeatmapSessionRecording.deleteHeatmapScreenshot",idSiteHsr:this.idSiteHsr}).then(()=>{this.isLoading=!1,window.location.reload()})}})},fetchHeatmap(){if(this.removeScrollHeatmap(),this.heatmapInstances){const e=this.heatmapInstances;e.forEach(e=>{e.setData({max:1,min:0,data:[]})})}this.isLoading=!0,this.avgFold=0;const e=A["MatomoUrl"].parsed.value.segment?decodeURIComponent(A["MatomoUrl"].parsed.value.segment):void 0,t={idSiteHsr:this.idSiteHsr,heatmapType:this.heatmapType,deviceType:this.deviceType,period:this.heatmapPeriod,date:this.heatmapDate,filter_limit:-1,segment:e},a=this.getRecordedHeatmap(t),i=this.getRecordedHeatmapMetadata(t);Promise.all([a,i,this.iframeLoadedPromise]).then(e=>{const t=this.$refs.recordingPlayer,a=this.getRecordingIframe(t);ie(this.$refs.recordingPlayer,this.$refs.heatmapContainer,a),this.removeScrollHeatmap();const i=e[0],n=e[1];if(Array.isArray(n)&&n[0]?[this.actualNumSamples]=n:this.actualNumSamples=n,this.isLoading=!1,this.isScrollHeatmapType)ne(this.$refs.iframeRecordingContainer,t,a,i);else{var s;const e={min:0,max:0,data:[]};for(let t=0;t{null!==e&&void 0!==e&&e.value&&parseInt(e.value,10)>1&&(t+=1)}),t/e.data.length>=.1&&e.data.length>120?e.max=2:e.max=1}else{const t=10,a={};if(e.data.forEach(i=>{if(!i||!i.value)return;let n=parseInt(i.value,10);n>e.max&&(e.max=n),n>t&&(n=t);const s=""+n;s in a?a[s]+=1:a[s]=0}),e.max>t){let i=0;for(let n=t;n>1;n-=1){const t=""+n;if(t in a&&(i+=a[t]),i/e.data.length>=.2){e.max=n;break}}if(e.max>t){e.max=5;for(let t=5;t>0;t-=1){const i=""+t;if(i in a){e.max=t;break}}}}}if(this.renderHeatmap(this.$refs.recordingPlayer,this.$refs.heatmapContainer,a,e),null!==(s=this.actualNumSamples)&&void 0!==s&&s["avg_fold_device_"+this.deviceType]){const e=this.actualNumSamples["avg_fold_device_"+this.deviceType],t=a.getIframeHeight();t&&(this.avgFold=parseInt(""+e/100*t,10))}}}).finally(()=>{this.isLoading=!1})},changeDeviceType(e){this.deviceType=e,this.deviceType===Q?this.changeIframeWidth(this.desktopPreviewSize,!1):this.deviceType===Z?this.changeIframeWidth(this.breakpointTablet||960,!1):this.deviceType===ee&&this.changeIframeWidth(this.breakpointMobile||600,!1)},changeIframeWidth(e,t){this.iframeWidth=e,this.customIframeWidth=this.iframeWidth,this.totalClicks=0,this.dataCoordinates=[],this.fetchHeatmap(),t&&A["Matomo"].helper.lazyScrollToContent()},changeHeatmapType(e){this.heatmapType=e,this.totalClicks=0,this.clickCount=0,this.clickRate=0,this.dataCoordinates=[],this.fetchHeatmap()},handleMouseMove(e){const t=window.document.getElementById("highlightDiv");if(!t)return;this.tooltipShowTimeoutId&&(clearTimeout(this.tooltipShowTimeoutId),this.tooltipShowTimeoutId=null,this.currentElement=null),t.hidden||this.handleTooltip(e,0,0,"move");const a=this.lookUpRecordedElementAtEventLocation(e);if(!a||a===this.currentElement)return;this.handleTooltip(e,0,0,"hide"),t.hidden=!0;const i=a.getBoundingClientRect();let n=0;this.dataCoordinates.forEach(e=>{e.yi.bottom||e.xi.right||(n+=parseInt(e.value,10))}),this.tooltipShowTimeoutId=setTimeout(()=>{this.currentElement=a,t.hidden=!1;const i=this.totalClicks?Math.round(n/this.totalClicks*1e4)/100:0,s=a.getBoundingClientRect();t.style.top=s.top+"px",t.style.left=s.left+"px",t.style.width=s.width+"px",t.style.height=s.height+"px",this.handleTooltip(e,n,i,"show"),this.tooltipShowTimeoutId=null},100)},lookUpRecordedElementAtEventLocation(e){const t=e.target;if(!t)return null;const a=window.document.getElementById("recordingPlayer");if(!a)return null;const i=a.contentWindow?a.contentWindow.document:a.contentDocument;if(!i)return null;const n=t.getBoundingClientRect();return i.elementFromPoint(e.clientX-n.left,e.clientY-n.top)},handleTooltip(e,t,a,i){this.tooltip&&("show"===i?(this.clickCount=t,this.clickRate=a,this.tooltip.show(e)):"move"===i?this.tooltip.show(e):this.tooltip.hide())}},computed:{isScrollHeatmapType(){return 3===this.heatmapType},tokenAuth(){return A["MatomoUrl"].parsed.value.token_auth},embedUrl(){return"?"+A["MatomoUrl"].stringify({module:"HeatmapSessionRecording",action:"embedPage",idSite:A["Matomo"].idSite,idSiteHsr:this.idSiteHsr,token_auth:this.tokenAuth||void 0})},iframeWidthOptions(){return this.iframeResolutions.map(e=>({key:e,value:e+"px"}))},recordedSamplesSince(){const e=Object(A["translate"])("HeatmapSessionRecording_HeatmapXRecordedSamplesSince",`${this.actualNumSamples.nb_samples_device_all}`,this.createdDate),t=Object(A["externalLink"])("https://matomo.org/faq/heatmap-session-recording/troubleshooting-heatmaps/"),a=Object(A["translate"])("HeatmapSessionRecording_HeatmapTroubleshoot",t,"");return`${e} ${a}`},deviceTypesWithSamples(){return this.deviceTypes.map(e=>{let t;t=this.actualNumSamples["nb_samples_device_"+e.key]?this.actualNumSamples["nb_samples_device_"+e.key]:0;const a=Object(A["translate"])("HeatmapSessionRecording_XSamples",`${e.name} - ${t}`);return Object.assign(Object.assign({},e),{},{numSamples:t,tooltip:a})})},hasWriteAccess(){return!(null===A["Matomo"]||void 0===A["Matomo"]||!A["Matomo"].heatmapWriteAccess)},showDeleteScreenshot(){return this.isActive&&this.hasWriteAccess},gradientImgData(){return"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAKCAYAAABCHPt+AAAAnklEQVRYR+2WQQqDQBAES5wB/f8/Y05RcMWwSu6JIT0Dm4WlH1DUdHew7/z6WYFhhnGRpnlhAEaQpi/ADbh/np0MiBhGhW+2ymFU+DZfg1EhaoB4jCFuMYYcQKZrXwPEVvm5Og0pcYakBvI35G1jNIZ4jCHexxjSpz9ZFUjAynLbpOvqteaODkm9sloz5JF+ZTVmSAWSu9Qb65AvgDwBQoLgVDlWfAQAAAAASUVORK5CYII="}}});re.render=M;var oe=re;const le={class:"sessionRecordingPlayer"},ce={class:"controls"},de={class:"playerActions"},me=["title"],pe=["title"],he=["title"],ue=["title"],ge=["title"],be=["title"],ve=["title"],Oe=["title"],je={version:"1.1",xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 768 768"},fe=Object(s["createElementVNode"])("path",{d:"M480 576.5v-321h-64.5v129h-63v-129h-64.5v192h127.5v129h64.5zM607.5 127.999c34.5 0\n 64.5 30 64.5 64.5v447c0 34.5-30 64.5-64.5 64.5h-447c-34.5\n 0-64.5-30-64.5-64.5v-447c0-34.5 30-64.5 64.5-64.5h447z"},null,-1),ye=[fe],Se={version:"1.1",xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 768 768"},He=Object(s["createElementVNode"])("path",{d:"M448.5 576.5v-321h-129v64.5h64.5v256.5h64.5zM607.5 127.999c34.5 0 64.5 30 64.5\n 64.5v447c0 34.5-30 64.5-64.5 64.5h-447c-34.5 0-64.5-30-64.5-64.5v-447c0-34.5\n 30-64.5 64.5-64.5h447z"},null,-1),_e=[He],Ve={version:"1.1",xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 768 768"},Ne=Object(s["createElementVNode"])("path",{d:"M480 384.5v-64.5c0-36-30-64.5-64.5-64.5h-127.5v64.5h127.5v64.5h-63c-34.5 0-64.5\n 27-64.5 63v129h192v-64.5h-127.5v-64.5h63c34.5 0 64.5-27 64.5-63zM607.5 127.999c34.5\n 0 64.5 30 64.5 64.5v447c0 34.5-30 64.5-64.5 64.5h-447c-34.5\n 0-64.5-30-64.5-64.5v-447c0-34.5 30-64.5 64.5-64.5h447z"},null,-1),Ee=[Ne],Re={version:"1.1",xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 768 768"},we=Object(s["createElementVNode"])("path",{d:"M480 320v-64.5h-127.5c-34.5 0-64.5 28.5-64.5 64.5v192c0 36 30 64.5 64.5\n 64.5h63c34.5 0 64.5-28.5 64.5-64.5v-64.5c0-36-30-63-64.5-63h-63v-64.5h127.5zM607.5\n 127.999c34.5 0 64.5 30 64.5 64.5v447c0 34.5-30 64.5-64.5 64.5h-447c-34.5\n 0-64.5-30-64.5-64.5v-447c0-34.5 30-64.5 64.5-64.5h447zM352.5 512v-64.5h63v64.5h-63z"},null,-1),ke=[we],xe=["title"],Ce=Object(s["createElementVNode"])("svg",{version:"1.1",xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 768 768"},[Object(s["createElementVNode"])("path",{d:"M223.5 415.5h111l-64.5-63h-46.5v63zM72 72l624 624-42 40.5-88.5-90c-51 36-114\n 57-181.5 57-177 0-319.5-142.5-319.5-319.5 0-67.5 21-130.5 57-181.5l-90-88.5zM544.5\n 352.5h-111l-231-231c51-36 114-57 181.5-57 177 0 319.5 142.5 319.5 319.5 0 67.5-21\n 130.5-57 181.5l-148.5-150h46.5v-63z"})],-1),Te=[Ce],De=["title"],Me=Object(s["createElementVNode"])("svg",{version:"1.1",xmlns:"http://www.w3.org/2000/svg",width:"22",height:"22",viewBox:"0 0 768 768"},[Object(s["createElementVNode"])("path",{d:"M544.5 609v-129h63v192h-384v96l-127.5-127.5 127.5-127.5v96h321zM223.5\n 288v129h-63v-192h384v-96l127.5 127.5-127.5 127.5v-96h-321z"})],-1),Pe=[Me],Be={class:"duration"},Ae={class:"playerHelp"},Ue=Object(s["createElementVNode"])("span",{class:"clickEvent"},null,-1),Le=Object(s["createElementVNode"])("span",{class:"moveEvent"},null,-1),Ie=Object(s["createElementVNode"])("span",{class:"scrollEvent"},null,-1),Fe=Object(s["createElementVNode"])("span",{class:"resizeEvent"},null,-1),We=Object(s["createElementVNode"])("span",{class:"formChange"},null,-1),qe=Object(s["createElementVNode"])("span",{class:"mutationEvent"},null,-1),ze=Object(s["createElementVNode"])("br",{style:{clear:"right"}},null,-1),$e=["title"],Ge=Object(s["createElementVNode"])("br",null,null,-1),Je=Object(s["createElementVNode"])("div",{class:"loadingUnderlay"},null,-1),Xe={class:"valign-wrapper loadingInner"},Ye={class:"loadingContent"},Ke=["src","width","height"];function Qe(e,t,a,i,n,r){return Object(s["openBlock"])(),Object(s["createElementBlock"])("div",le,[Object(s["createElementVNode"])("div",ce,[Object(s["createElementVNode"])("span",de,[Object(s["withDirectives"])(Object(s["createElementVNode"])("span",{class:"playerAction icon-skip-previous",title:e.skipPreviousButtonTitle,onClick:t[0]||(t[0]=t=>e.loadNewRecording(e.previousRecordingId))},null,8,me),[[s["vShow"],e.previousRecordingId]]),Object(s["createElementVNode"])("span",{class:"playerAction icon-fast-rewind",title:e.translate("HeatmapSessionRecording_PlayerRewindFast",10,"J"),onClick:t[1]||(t[1]=t=>e.jumpRelative(10,!1))},null,8,pe),Object(s["withDirectives"])(Object(s["createElementVNode"])("span",{class:"playerAction icon-play",title:e.translate("HeatmapSessionRecording_PlayerPlay","K"),onClick:t[2]||(t[2]=t=>e.play())},null,8,he),[[s["vShow"],!e.isPlaying&&!e.isFinished]]),Object(s["withDirectives"])(Object(s["createElementVNode"])("span",{class:"playerAction icon-replay",title:e.translate("HeatmapSessionRecording_PlayerReplay","K"),onClick:t[3]||(t[3]=t=>e.replay())},null,8,ue),[[s["vShow"],!e.isPlaying&&e.isFinished]]),Object(s["withDirectives"])(Object(s["createElementVNode"])("span",{class:"playerAction icon-pause",title:e.translate("HeatmapSessionRecording_PlayerPause","K"),onClick:t[4]||(t[4]=t=>e.pause())},null,8,ge),[[s["vShow"],e.isPlaying]]),Object(s["createElementVNode"])("span",{class:"playerAction icon-fast-forward",title:e.translate("HeatmapSessionRecording_PlayerForwardFast",10,"L"),onClick:t[5]||(t[5]=t=>e.jumpRelative(10,!0))},null,8,be),Object(s["withDirectives"])(Object(s["createElementVNode"])("span",{class:"playerAction icon-skip-next",title:e.translate("HeatmapSessionRecording_PlayerPageViewNext",e.nextRecordingInfo,"N"),onClick:t[6]||(t[6]=t=>e.loadNewRecording(e.nextRecordingId))},null,8,ve),[[s["vShow"],e.nextRecordingId]]),Object(s["createElementVNode"])("span",{class:"changeReplaySpeed",title:e.translate("HeatmapSessionRecording_ChangeReplaySpeed","S"),onClick:t[7]||(t[7]=t=>e.increaseReplaySpeed())},[Object(s["withDirectives"])((Object(s["openBlock"])(),Object(s["createElementBlock"])("svg",je,ye,512)),[[s["vShow"],4===e.actualReplaySpeed]]),Object(s["withDirectives"])((Object(s["openBlock"])(),Object(s["createElementBlock"])("svg",Se,_e,512)),[[s["vShow"],1===e.actualReplaySpeed]]),Object(s["withDirectives"])((Object(s["openBlock"])(),Object(s["createElementBlock"])("svg",Ve,Ee,512)),[[s["vShow"],2===e.actualReplaySpeed]]),Object(s["withDirectives"])((Object(s["openBlock"])(),Object(s["createElementBlock"])("svg",Re,ke,512)),[[s["vShow"],6===e.actualReplaySpeed]])],8,Oe),Object(s["createElementVNode"])("span",{class:Object(s["normalizeClass"])(["toggleSkipPause",{active:e.actualSkipPausesEnabled}]),title:e.translate("HeatmapSessionRecording_ClickToSkipPauses",e.skipPausesEnabledText,"B"),onClick:t[8]||(t[8]=t=>e.toggleSkipPauses())},Te,10,xe),Object(s["createElementVNode"])("span",{class:Object(s["normalizeClass"])(["toggleAutoPlay",{active:e.actualAutoPlayEnabled}]),title:e.translate("HeatmapSessionRecording_AutoPlayNextPageview",e.autoplayEnabledText,"A"),onClick:t[9]||(t[9]=t=>e.toggleAutoPlay())},Pe,10,De),Object(s["createElementVNode"])("span",Be,Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_PlayerDurationXofY",e.positionPretty,e.durationPretty)),1)]),Object(s["createElementVNode"])("div",Ae,[Object(s["createElementVNode"])("ul",null,[Object(s["createElementVNode"])("li",null,[Ue,Object(s["createTextVNode"])(" "+Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_ActivityClick")),1)]),Object(s["createElementVNode"])("li",null,[Le,Object(s["createTextVNode"])(" "+Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_ActivityMove")),1)]),Object(s["createElementVNode"])("li",null,[Ie,Object(s["createTextVNode"])(" "+Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_ActivityScroll")),1)]),Object(s["createElementVNode"])("li",null,[Fe,Object(s["createTextVNode"])(" "+Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_ActivityResize")),1)]),Object(s["createElementVNode"])("li",null,[We,Object(s["createTextVNode"])(" "+Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_ActivityFormChange")),1)]),Object(s["createElementVNode"])("li",null,[qe,Object(s["createTextVNode"])(" "+Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_ActivityPageChange")),1)])])]),ze]),Object(s["createElementVNode"])("div",{class:"timelineOuter",onClick:t[10]||(t[10]=t=>e.seekEvent(t)),style:Object(s["normalizeStyle"])({width:e.replayWidth+"px"})},[Object(s["createElementVNode"])("div",{class:"timelineInner",style:Object(s["normalizeStyle"])({width:e.progress+"%"})},null,4),(Object(s["openBlock"])(!0),Object(s["createElementBlock"])(s["Fragment"],null,Object(s["renderList"])(e.clues,(e,t)=>(Object(s["openBlock"])(),Object(s["createElementBlock"])("div",{title:e.title,class:Object(s["normalizeClass"])(e.type),style:Object(s["normalizeStyle"])({left:e.left+"%"}),key:t},null,14,$e))),128))],4),Ge,Object(s["withDirectives"])(Object(s["createElementVNode"])("div",{class:"hsrLoadingOuter",style:Object(s["normalizeStyle"])({width:e.replayWidth+"px",height:e.replayHeight+"px"})},[Je,Object(s["createElementVNode"])("div",Xe,[Object(s["createElementVNode"])("div",Ye,Object(s["toDisplayString"])(e.translate("General_Loading")),1)])],4),[[s["vShow"],e.isLoading]]),Object(s["createElementVNode"])("div",{class:"replayContainerOuter",onClick:t[12]||(t[12]=t=>e.togglePlay()),style:Object(s["normalizeStyle"])({height:e.replayHeight+"px",width:e.replayWidth+"px"})},[Object(s["createElementVNode"])("div",{class:"replayContainerInner",style:Object(s["normalizeStyle"])([{"transform-origin":"0 0"},{transform:`scale(${e.replayScale})`,"margin-left":e.replayMarginLeft+"px"}])},[e.embedUrl?(Object(s["openBlock"])(),Object(s["createElementBlock"])("iframe",{key:0,id:"recordingPlayer",ref:"recordingPlayer",onLoad:t[11]||(t[11]=t=>e.onLoaded()),scrolling:"no",sandbox:"allow-scripts allow-same-origin",referrerpolicy:"no-referrer",src:e.embedUrl,width:e.recording.viewport_w_px,height:e.recording.viewport_h_px},null,40,Ke)):Object(s["createCommentVNode"])("",!0)],4)],4)])}const Ze=20,et=1,tt=2,at=3,it=4,nt=5,st=6,rt=9,ot=10,lt=12,ct={[tt]:"clickEvent",[et]:"moveEvent",[at]:"scrollEvent",[lt]:"scrollEvent",[it]:"resizeEvent",[rt]:"formChange",[ot]:"formChange",[nt]:"mutationEvent",[st]:"mutationEvent"},dt={[tt]:Object(A["translate"])("HeatmapSessionRecording_ActivityClick"),[et]:Object(A["translate"])("HeatmapSessionRecording_ActivityMove"),[at]:Object(A["translate"])("HeatmapSessionRecording_ActivityScroll"),[lt]:Object(A["translate"])("HeatmapSessionRecording_ActivityScroll"),[it]:Object(A["translate"])("HeatmapSessionRecording_ActivityResize"),[rt]:Object(A["translate"])("HeatmapSessionRecording_ActivityFormChange"),[ot]:Object(A["translate"])("HeatmapSessionRecording_ActivityFormChange"),[nt]:Object(A["translate"])("HeatmapSessionRecording_ActivityPageChange"),[st]:Object(A["translate"])("HeatmapSessionRecording_ActivityPageChange")},mt='\n
    \n \n \n \n
    \n',{$:pt,Mousetrap:ht}=window;function ut(e){return"number"===typeof e?e:parseInt(e,10)}function gt(e){if(null!==e&&void 0!==e&&e.event_type)return ut(e.event_type)}function bt(e){const t=Math.floor(e/1e3);let a=Math.floor(t/60),i=t%60;return a<10&&(a="0"+a),i<10&&(i="0"+i),`${a}:${i}`}var vt=Object(s["defineComponent"])({props:{offsetAccuracy:{type:Number,required:!0},scrollAccuracy:{type:Number,required:!0},autoPlayEnabled:Boolean,skipPausesEnabled:Boolean,replaySpeed:{type:Number,default:1}},data(){return{isPlaying:!1,progress:0,isFinished:!1,isLoading:!0,seekTimeout:null,lastFramePainted:0,recording:JSON.parse(JSON.stringify(window.sessionRecordingData)),positionPretty:"00:00",previousRecordingId:null,previousRecordingInfo:null,nextRecordingId:null,nextRecordingInfo:null,frame:0,hasFoundPrevious:!1,hasFoundNext:!1,videoPlayerInterval:null,lastCanvasCoordinates:!1,actualAutoPlayEnabled:!!this.autoPlayEnabled,replayWidth:0,replayHeight:0,replayScale:0,replayMarginLeft:0,seek:e=>e,actualSkipPausesEnabled:!!this.skipPausesEnabled,actualReplaySpeed:this.replaySpeed}},setup(){const e=Object(s["ref"])(!1);let t=null;const a=new Promise(a=>{t=a,e.value=!0}),i=()=>{setTimeout(()=>{t("loaded")},500)};return{iframeLoadedPromise:a,onLoaded:i,iframeLoaded:e}},created(){this.recording.duration=ut(this.recording.duration),this.recording.pageviews.forEach(e=>{e&&e.idloghsr&&(""+e.idloghsr===""+this.recording.idLogHsr?this.hasFoundPrevious=!0:this.hasFoundPrevious?this.hasFoundNext||(this.hasFoundNext=!0,this.nextRecordingId=e.idloghsr,this.nextRecordingInfo=[e.label,e.server_time_pretty,e.time_on_page_pretty].join(" - ")):(this.previousRecordingId=e.idloghsr,this.previousRecordingInfo=[e.label,e.server_time_pretty,e.time_on_page_pretty].join(" - ")))})},mounted(){ht.bind(["space","k"],()=>{this.togglePlay()}),ht.bind("0",()=>{this.isFinished&&this.replay()}),ht.bind("p",()=>{this.loadNewRecording(this.previousRecordingId)}),ht.bind("n",()=>{this.loadNewRecording(this.nextRecordingId)}),ht.bind("s",()=>{this.increaseReplaySpeed()}),ht.bind("a",()=>{this.toggleAutoPlay()}),ht.bind("b",()=>{this.toggleSkipPauses()}),ht.bind("left",()=>{const e=5,t=!1;this.jumpRelative(e,t)}),ht.bind("right",()=>{const e=5,t=!0;this.jumpRelative(e,t)}),ht.bind("j",()=>{const e=10,t=!1;this.jumpRelative(e,t)}),ht.bind("l",()=>{const e=10,t=!0;this.jumpRelative(e,t)}),this.initViewport(),pt(window).on("resize",()=>this.initViewport()),this.iframeLoadedPromise.then(()=>{this.initPlayer()}),window.addEventListener("beforeunload",()=>{this.isPlaying=!1,this.videoPlayerInterval&&(clearInterval(this.videoPlayerInterval),this.videoPlayerInterval=null)})},methods:{initPlayer(){const e=this.$refs.recordingPlayer,t=L(e).recordingFrame;if(!t||!t.isSupportedBrowser())return;t.addClass("html","piwikSessionRecording"),t.addClass("html","matomoSessionRecording");let a=null;const i=(e,i)=>{a&&a.css({left:e.x-8+"px",top:e.y-8+"px"}),this.lastCanvasCoordinates&&(t.drawLine(this.lastCanvasCoordinates.x,this.lastCanvasCoordinates.y,e.x,e.y,i),this.lastCanvasCoordinates=e)},n=(e,n)=>{if(!this.lastCanvasCoordinates||!a)return void t.scrollTo(e,n);const s=t.getScrollTop(),r=t.getScrollLeft();t.scrollTo(e,n);const o=n-s,l=e-r;let c=l+this.lastCanvasCoordinates.x,d=o+this.lastCanvasCoordinates.y;c<=0&&(c=0),d<=0&&(d=0),i({x:c,y:d},"blue")},s=(e,t,a)=>{null!==e&&void 0!==e&&e.scrollTo?e.scrollTo(t,a):(e.scrollLeft=t,e.scrollTop=a)};let r=null;const o=e=>{const{isPlaying:a}=this;this.isPlaying=!1;const i=gt(e);let o=null;if(i===et)e.selector&&(o=t.getCoordinatesInFrame(e.selector,e.x,e.y,this.offsetAccuracy,!1),o&&r(o));else if(i===tt)e.selector&&(o=t.getCoordinatesInFrame(e.selector,e.x,e.y,this.offsetAccuracy,!1),o&&(r(o),t.drawCircle(o.x,o.y,"#ff9407")));else if(i===st)e.text&&t.applyMutation(e.text);else if(i===at){const a=t.getIframeHeight(),i=t.getIframeWidth(),s=parseInt(""+a/this.scrollAccuracy*ut(e.y),10),r=parseInt(""+i/this.scrollAccuracy*ut(e.x),10);n(r,s)}else if(i===lt){if(e.selector){const a=t.findElement(e.selector);if(a&&a.length&&a[0]){const t=Math.max(a[0].scrollHeight,a[0].offsetHeight,a.height(),0),i=Math.max(a[0].scrollWidth,a[0].offsetWidth,a.width(),0);if(t&&i){const n=parseInt(""+t/this.scrollAccuracy*ut(e.y),10),r=parseInt(""+i/this.scrollAccuracy*ut(e.x),10);s(a[0],r,n)}}}}else if(i===it)this.setViewportResolution(e.x,e.y);else if(i===rt){if(e.selector){const a=t.findElement(e.selector);if(a.length){const t=a.attr("type");t&&"file"===(""+t).toLowerCase()||a.val(e.text).change()}}}else if(i===ot&&e.selector){const a=t.findElement(e.selector);a.is("input")?a.prop("checked",1===e.text||"1"===e.text):a.is("select")&&a.val(e.text).change()}this.isPlaying=a};r=e=>{const n=()=>{const e=t.getIframeWidth(),a=t.getIframeHeight();t.makeSvg(e,a);for(let t=0;t<=this.frame;t+=Ze){if(!this.timeFrameBuckets[t])return;this.timeFrameBuckets[t].forEach(e=>{const a=gt(e);a!==et&&a!==at&&a!==lt&&a!==tt||(this.lastFramePainted=t,o(e))})}},s=t.getIframeWindow();if(!this.lastCanvasCoordinates){const i=t.getIframeHeight(),r=t.getIframeWidth();return t.appendContent(mt),a=t.findElement(".mousePointer"),t.makeSvg(r,i),s.removeEventListener("resize",n,!1),s.addEventListener("resize",n,!1),this.lastCanvasCoordinates=e,void a.css({left:e.x-8+"px",top:e.y-8+"px"})}let r=t.getScrollTop();const l=t.getScrollLeft();(e.y>r+ut(this.recording.viewport_h_px)||e.yl+ut(this.recording.viewport_w_px)||e.x{if(!this.iframeLoaded)return;this.isLoading=!0;let a=this.frame;const i=e=>{for(let t=e;t<=this.frame;t+=Ze)(this.timeFrameBuckets[t]||[]).forEach(e=>{this.lastFramePainted=t,o(e)})};this.isFinished=!1,this.frame=e-e%Ze,this.progress=parseFloat(parseFloat(""+this.frame/ut(this.recording.duration)*100).toFixed(2)),this.positionPretty=bt(this.frame),a>this.frame?(a=0,this.lastCanvasCoordinates=!1,this.initialMutation&&t.initialMutation(this.initialMutation.text),t.scrollTo(0,0),this.setViewportResolution(window.sessionRecordingData.viewport_w_px,window.sessionRecordingData.viewport_h_px),this.seekTimeout&&(clearTimeout(this.seekTimeout),this.seekTimeout=null),(e=>{this.seekTimeout=setTimeout(()=>{i(e),this.isLoading=!1},1050)})(a)):(this.seekTimeout&&(clearTimeout(this.seekTimeout),this.seekTimeout=null),i(a),this.isLoading=!1)},this.isLoading=!1,this.isPlaying=!0;let l=0;const c=()=>{if(this.isPlaying&&!this.isLoading){l+=1;const e=ut(this.recording.duration);if(this.frame>=e?(this.isPlaying=!1,this.progress=100,this.isFinished=!0,this.positionPretty=this.durationPretty,this.actualAutoPlayEnabled&&this.nextRecordingId&&this.loadNewRecording(this.nextRecordingId)):(this.progress=parseFloat(parseFloat(""+this.frame/e*100).toFixed(2)),20===l&&(l=0,this.positionPretty=bt(this.frame))),(this.timeFrameBuckets[this.frame]||[]).forEach(e=>{this.lastFramePainted=this.frame,o(e)}),this.actualSkipPausesEnabled&&this.frame-this.lastFramePainted>1800){let t=Object.keys(this.timeFrameBuckets).map(e=>parseInt(e,10));t=t.sort((e,t)=>e-t);const a=t.find(e=>e>this.frame),i=!!a;if(a){const e=a-this.frame>1e3;e&&(this.frame=a-20*Ze)}if(!i){const t=e-this.frame>1e3;t&&(this.frame=e-20*Ze)}}this.frame+=Ze}};this.videoPlayerInterval=setInterval(()=>{for(let e=1;e<=this.actualReplaySpeed;e+=1)c()},Ze)},initViewport(){this.replayHeight=pt(window).height()-48-pt(".sessionRecording .sessionRecordingHead").outerHeight(!0)-pt(".sessionRecordingPlayer .controls").outerHeight(!0),this.replayWidth=pt(window).width()-48;const e=ut(this.recording.viewport_w_px),t=ut(this.recording.viewport_h_px),a=400;this.replayWidtha&&(this.replayWidth=a);const i=400;this.replayHeighti&&(this.replayHeight=i);let n=1,s=1;e>this.replayWidth&&(n=parseFloat(parseFloat(""+this.replayWidth/e).toFixed(4))),t>this.replayHeight&&(s=parseFloat(parseFloat(""+this.replayHeight/t).toFixed(4))),this.replayScale=Math.min(n,s),this.replayMarginLeft=(this.replayWidth-this.replayScale*e)/2},setViewportResolution(e,t){this.recording.viewport_w_px=parseInt(""+e,10),this.recording.viewport_h_px=parseInt(""+t,10),pt(".recordingWidth").text(e),pt(".recordingHeight").text(t),this.initViewport()},increaseReplaySpeed(){1===this.actualReplaySpeed?this.actualReplaySpeed=2:2===this.actualReplaySpeed?this.actualReplaySpeed=4:4===this.actualReplaySpeed?this.actualReplaySpeed=6:this.actualReplaySpeed=1,this.updateSettings()},updateSettings(){A["AjaxHelper"].fetch({module:"HeatmapSessionRecording",action:"saveSessionRecordingSettings",autoplay:this.actualAutoPlayEnabled?1:0,skippauses:this.actualSkipPausesEnabled?1:0,replayspeed:this.actualReplaySpeed},{format:"html"})},toggleAutoPlay(){this.actualAutoPlayEnabled=!this.actualAutoPlayEnabled,this.updateSettings()},toggleSkipPauses(){this.actualSkipPausesEnabled=!this.actualSkipPausesEnabled,this.updateSettings()},loadNewRecording(e){e&&(this.isPlaying=!1,A["MatomoUrl"].updateUrl(Object.assign(Object.assign({},A["MatomoUrl"].urlParsed.value),{},{idLogHsr:parseInt(""+e,10),updated:A["MatomoUrl"].urlParsed.value.updated?parseInt(A["MatomoUrl"].urlParsed.value.updated,10)+1:1})))},jumpRelative(e,t){const a=1e3*e;let i;t?(i=this.frame+a,i>this.recording.duration&&(i=ut(this.recording.duration)-Ze)):(i=this.frame-a,i<0&&(i=0)),this.seek(i)},replay(){this.isFinished=!1,this.lastFramePainted=0,this.seek(0),this.play()},pause(){this.isPlaying=!1},togglePlay(){this.isFinished?this.replay():this.isPlaying?this.pause():this.play()},seekEvent(e){const t=pt(e.currentTarget).offset(),a=e.pageX-t.left,i=this.replayWidth,n=a/i,s=ut(this.recording.duration)*n;this.seek(s)},play(){this.isPlaying=!0}},computed:{durationPretty(){return bt(ut(this.recording.duration))},embedUrl(){return"?"+A["MatomoUrl"].stringify({module:"HeatmapSessionRecording",action:"embedPage",idSite:this.recording.idSite,idLogHsr:this.recording.idLogHsr,idSiteHsr:this.recording.idSiteHsr,token_auth:A["MatomoUrl"].urlParsed.value.token_auth||void 0})},skipPreviousButtonTitle(){return Object(A["translate"])("HeatmapSessionRecording_PlayerPageViewPrevious",this.previousRecordingInfo||"","P")},skipPausesEnabledText(){return this.actualSkipPausesEnabled?Object(A["translate"])("HeatmapSessionRecording_disable"):Object(A["translate"])("HeatmapSessionRecording_enable")},autoplayEnabledText(){return this.actualAutoPlayEnabled?Object(A["translate"])("HeatmapSessionRecording_disable"):Object(A["translate"])("HeatmapSessionRecording_enable")},recordingEvents(){return this.recording?this.recording.events.map(e=>{const t=gt(e);let{text:a}=e;return t!==nt&&t!==st||"string"!==typeof a||(a=JSON.parse(a)),Object.assign(Object.assign({},e),{},{text:a})}):[]},initialMutation(){const e=this.recordingEvents.find(e=>{const t=gt(e),a=t===nt||t===st,i=a&&(t===nt||!e.time_since_load||"0"===e.time_since_load);return i});return e},timeFrameBuckets(){const e={};return this.recordingEvents.forEach(t=>{if(t===this.initialMutation)return;const a=Math.round(ut(t.time_since_load)/Ze)*Ze;e[a]=e[a]||[],e[a].push(t)}),e},clues(){const e=[];return this.recordingEvents.forEach(t=>{if(t===this.initialMutation)return;const a=gt(t),i=ct[a]||"",n=dt[a]||"";if(i){if((0===t.time_since_load||"0"===t.time_since_load)&&"moveEvent"===i)return;e.push({left:parseFloat(""+ut(t.time_since_load)/ut(this.recording.duration)*100).toFixed(2),type:i,title:n})}}),e}}});vt.render=Qe;var Ot=vt;const jt={class:"form-group hsrTargetTest"},ft={class:"loadingPiwik loadingMatchingSteps"},yt=Object(s["createElementVNode"])("img",{src:"plugins/Morpheus/images/loading-blue.gif",alt:""},null,-1),St=Object(s["createElementVNode"])("div",{id:"hsrTargetValidationError"},null,-1);function Ht(e,t,a,i,n,r){return Object(s["openBlock"])(),Object(s["createElementBlock"])("div",jt,[Object(s["createElementVNode"])("label",null,[Object(s["createElementVNode"])("strong",null,Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_TargetPageTestTitle"))+":",1),Object(s["createTextVNode"])(" "+Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_TargetPageTestLabel")),1)]),Object(s["withDirectives"])(Object(s["createElementVNode"])("input",{type:"text",id:"urltargettest",placeholder:"http://www.example.com/","onUpdate:modelValue":t[0]||(t[0]=t=>e.url=t),class:Object(s["normalizeClass"])({invalid:e.url&&!e.matches&&e.isValid})},null,2),[[s["vModelText"],e.url]]),Object(s["createElementVNode"])("div",null,[Object(s["withDirectives"])(Object(s["createElementVNode"])("span",{class:"testInfo"},Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_TargetPageTestErrorInvalidUrl")),513),[[s["vShow"],e.url&&!e.isValid]]),Object(s["withDirectives"])(Object(s["createElementVNode"])("span",{class:"testInfo matches"},Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_TargetPageTestUrlMatches")),513),[[s["vShow"],e.url&&e.matches&&e.isValid]]),Object(s["withDirectives"])(Object(s["createElementVNode"])("span",{class:"testInfo notMatches"},Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_TargetPageTestUrlNotMatches")),513),[[s["vShow"],e.url&&!e.matches&&e.isValid]]),Object(s["withDirectives"])(Object(s["createElementVNode"])("span",ft,[yt,Object(s["createTextVNode"])(Object(s["toDisplayString"])(e.translate("General_LoadingData")),1)],512),[[s["vShow"],e.isLoadingTestMatchPage]])]),St])}function _t(e){return e.indexOf("://")>3}var Vt=Object(s["defineComponent"])({props:{includedTargets:Array},data(){return{url:"",matches:!1,isLoadingTestMatchPage:!1}},watch:{isValid(e){e||(this.matches=!1)},includedTargets(){this.runTest()},url(){this.runTest()}},setup(){return{testUrlMatchPages:I("HeatmapSessionRecording.testUrlMatchPages",{errorElement:"#hsrTargetValidationError"})}},created(){this.runTest=Object(A["debounce"])(this.runTest,200)},methods:{checkIsMatchingUrl(){if(!this.isValid)return;const e=this.targetUrl,t=this.filteredIncludedTargets;null!==t&&void 0!==t&&t.length&&(this.isLoadingTestMatchPage=!0,this.testUrlMatchPages({url:e},{matchPageRules:t}).then(e=>{var t;null!==(t=this.filteredIncludedTargets)&&void 0!==t&&t.length&&(null===e||void 0===e?void 0:e.url)===this.targetUrl&&(this.matches=e.matches)}).finally(()=>{this.isLoadingTestMatchPage=!1}))},runTest(){this.isValid&&this.checkIsMatchingUrl()}},computed:{targetUrl(){return(this.url||"").trim()},isValid(){return this.targetUrl&&_t(this.targetUrl)},filteredIncludedTargets(){if(this.includedTargets)return this.includedTargets.filter(e=>(null===e||void 0===e?void 0:e.value)||"any"===(null===e||void 0===e?void 0:e.type)).map(e=>Object.assign(Object.assign({},e),{},{value:e.value?e.value.trim():""}))}}});Vt.render=Ht;var Nt=Vt;const Et={style:{width:"100%"}},Rt={name:"targetAttribute"},wt={name:"targetType"},kt={name:"targetValue"},xt={name:"targetValue2"},Ct=["title"],Tt=["title"];function Dt(e,t,a,i,n,r){const o=Object(s["resolveComponent"])("Field");return Object(s["openBlock"])(),Object(s["createElementBlock"])("div",{class:Object(s["normalizeClass"])(["form-group hsrUrltarget valign-wrapper",{disabled:e.disableIfNoValue&&!e.modelValue.value}])},[Object(s["createElementVNode"])("div",Et,[Object(s["createElementVNode"])("div",Rt,[Object(s["createVNode"])(o,{uicontrol:"select",name:"targetAttribute","model-value":e.modelValue.attribute,"onUpdate:modelValue":t[0]||(t[0]=t=>e.$emit("update:modelValue",Object.assign(Object.assign({},e.modelValue),{},{attribute:t}))),title:e.translate("HeatmapSessionRecording_Rule"),options:e.targetAttributes,"full-width":!0},null,8,["model-value","title","options"])]),Object(s["createElementVNode"])("div",wt,[Object(s["createVNode"])(o,{uicontrol:"select",name:"targetType","model-value":e.pattern_type,"onUpdate:modelValue":t[1]||(t[1]=t=>{e.onTypeChange(t)}),options:e.targetOptions[e.modelValue.attribute],"full-width":!0},null,8,["model-value","options"])]),Object(s["createElementVNode"])("div",kt,[Object(s["withDirectives"])(Object(s["createVNode"])(o,{uicontrol:"text",name:"targetValue",placeholder:"eg. "+e.targetExamples[e.modelValue.attribute],"model-value":e.modelValue.value,"onUpdate:modelValue":t[2]||(t[2]=t=>e.$emit("update:modelValue",Object.assign(Object.assign({},e.modelValue),{},{value:t.trim()}))),maxlength:500,"full-width":!0},null,8,["placeholder","model-value"]),[[s["vShow"],"any"!==e.pattern_type]])]),Object(s["createElementVNode"])("div",xt,[Object(s["withDirectives"])(Object(s["createVNode"])(o,{uicontrol:"text",name:"targetValue2","model-value":e.modelValue.value2,"onUpdate:modelValue":t[3]||(t[3]=t=>e.$emit("update:modelValue",Object.assign(Object.assign({},e.modelValue),{},{value2:t.trim()}))),maxlength:500,"full-width":!0,placeholder:e.translate("HeatmapSessionRecording_UrlParameterValueToMatchPlaceholder")},null,8,["model-value","placeholder"]),[[s["vShow"],"urlparam"===e.modelValue.attribute&&e.pattern_type&&"exists"!==e.pattern_type&&"not_exists"!==e.pattern_type]])])]),Object(s["withDirectives"])(Object(s["createElementVNode"])("span",{class:"icon-plus valign",title:e.translate("General_Add"),onClick:t[4]||(t[4]=t=>e.$emit("addUrl"))},null,8,Ct),[[s["vShow"],e.showAddUrl]]),Object(s["withDirectives"])(Object(s["createElementVNode"])("span",{class:"icon-minus valign",title:e.translate("General_Remove"),onClick:t[5]||(t[5]=t=>e.$emit("removeUrl"))},null,8,Tt),[[s["vShow"],e.canBeRemoved]])],2)}function Mt(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e} +/** + * Copyright (C) InnoCraft Ltd - All rights reserved. + * + * NOTICE: All information contained herein is, and remains the property of InnoCraft Ltd. + * The intellectual and technical concepts contained herein are protected by trade secret + * or copyright law. Redistribution of this information or reproduction of this material is + * strictly forbidden unless prior written permission is obtained from InnoCraft Ltd. + * + * You shall use this code only in accordance with the license agreement obtained from + * InnoCraft Ltd. + * + * @link https://www.innocraft.com/ + * @license For license details see https://www.innocraft.com/license + */class Pt{constructor(){Mt(this,"privateState",Object(s["reactive"])({rules:[]})),Mt(this,"state",Object(s["computed"])(()=>Object(s["readonly"])(this.privateState))),Mt(this,"rules",Object(s["computed"])(()=>this.state.value.rules)),Mt(this,"initPromise",null)}init(){return this.initPromise||(this.initPromise=A["AjaxHelper"].fetch({method:"HeatmapSessionRecording.getAvailableTargetPageRules",filter_limit:"-1"}).then(e=>(this.privateState.rules=e,this.rules.value))),this.initPromise}}var Bt=new Pt,At=Object(s["defineComponent"])({props:{modelValue:{type:Object,required:!0},canBeRemoved:Boolean,disableIfNoValue:Boolean,allowAny:Boolean,showAddUrl:Boolean},components:{Field:U["Field"]},emits:["addUrl","removeUrl","update:modelValue"],created(){Bt.init()},watch:{modelValue(e){if(!e.attribute)return;const t=this.targetOptions[e.attribute],a=t.find(e=>e.key===this.pattern_type);!a&&t[0]&&this.onTypeChange(t[0].key)}},computed:{pattern_type(){let e=this.modelValue.type;return this.modelValue.inverted&&"0"!==this.modelValue.inverted&&(e="not_"+this.modelValue.type),e},targetAttributes(){return Bt.rules.value.map(e=>({key:e.value,value:e.name}))},targetOptions(){const e={};return Bt.rules.value.forEach(t=>{e[t.value]=[],this.allowAny&&"url"===t.value&&e[t.value].push({value:Object(A["translate"])("HeatmapSessionRecording_TargetTypeIsAny"),key:"any"}),t.types.forEach(a=>{e[t.value].push({value:a.name,key:a.value}),e[t.value].push({value:Object(A["translate"])("HeatmapSessionRecording_TargetTypeIsNot",a.name),key:"not_"+a.value})})}),e},targetExamples(){const e={};return Bt.rules.value.forEach(t=>{e[t.value]=t.example}),e}},methods:{onTypeChange(e){let t=0,a=e;0===e.indexOf("not_")&&(a=e.substring("not_".length),t=1),this.$emit("update:modelValue",Object.assign(Object.assign({},this.modelValue),{},{type:a,inverted:t}))}}});At.render=Dt;var Ut=At;const Lt={class:"loadingPiwik"},It=Object(s["createElementVNode"])("img",{src:"plugins/Morpheus/images/loading-blue.gif"},null,-1),Ft={class:"loadingPiwik"},Wt=Object(s["createElementVNode"])("img",{src:"plugins/Morpheus/images/loading-blue.gif"},null,-1),qt={name:"name"},zt={name:"sampleLimit"},$t={class:"form-group row"},Gt={class:"col s12"},Jt={class:"col s12 m6",style:{"padding-left":"0"}},Xt=Object(s["createElementVNode"])("hr",null,null,-1),Yt={class:"col s12 m6"},Kt={class:"form-help"},Qt={class:"inline-help"},Zt={name:"sampleRate"},ea={name:"excludedElements"},ta={name:"screenshotUrl"},aa={name:"breakpointMobile"},ia={name:"breakpointTablet"},na={name:"trackManually"},sa=["innerHTML"],ra={class:"entityCancel"};function oa(e,t,a,i,n,r){const o=Object(s["resolveComponent"])("Field"),l=Object(s["resolveComponent"])("HsrUrlTarget"),c=Object(s["resolveComponent"])("HsrTargetTest"),d=Object(s["resolveComponent"])("SaveButton"),m=Object(s["resolveComponent"])("ContentBlock");return Object(s["openBlock"])(),Object(s["createBlock"])(m,{class:"editHsr","content-title":e.contentTitle},{default:Object(s["withCtx"])(()=>[Object(s["withDirectives"])(Object(s["createElementVNode"])("p",null,[Object(s["createElementVNode"])("span",Lt,[It,Object(s["createTextVNode"])(" "+Object(s["toDisplayString"])(e.translate("General_LoadingData")),1)])],512),[[s["vShow"],e.isLoading]]),Object(s["withDirectives"])(Object(s["createElementVNode"])("p",null,[Object(s["createElementVNode"])("span",Ft,[Wt,Object(s["createTextVNode"])(" "+Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_UpdatingData")),1)])],512),[[s["vShow"],e.isUpdating]]),Object(s["createElementVNode"])("form",{onSubmit:t[12]||(t[12]=t=>e.edit?e.updateHsr():e.createHsr())},[Object(s["createElementVNode"])("div",null,[Object(s["createElementVNode"])("div",qt,[Object(s["createVNode"])(o,{uicontrol:"text",name:"name","model-value":e.siteHsr.name,"onUpdate:modelValue":t[0]||(t[0]=t=>{e.siteHsr.name=t,e.setValueHasChanged()}),title:e.translate("General_Name"),maxlength:50,placeholder:e.translate("HeatmapSessionRecording_FieldNamePlaceholder"),"inline-help":e.translate("HeatmapSessionRecording_HeatmapNameHelp")},null,8,["model-value","title","placeholder","inline-help"])]),Object(s["createElementVNode"])("div",zt,[Object(s["createVNode"])(o,{uicontrol:"select",name:"sampleLimit","model-value":e.siteHsr.sample_limit,"onUpdate:modelValue":t[1]||(t[1]=t=>{e.siteHsr.sample_limit=t,e.setValueHasChanged()}),title:e.translate("HeatmapSessionRecording_HeatmapSampleLimit"),options:e.sampleLimits,"inline-help":e.translate("HeatmapSessionRecording_HeatmapSampleLimitHelp")},null,8,["model-value","title","options","inline-help"])]),Object(s["createElementVNode"])("div",$t,[Object(s["createElementVNode"])("div",Gt,[Object(s["createElementVNode"])("h3",null,Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_TargetPage"))+":",1)]),Object(s["createElementVNode"])("div",Jt,[(Object(s["openBlock"])(!0),Object(s["createElementBlock"])(s["Fragment"],null,Object(s["renderList"])(e.siteHsr.match_page_rules,(a,i)=>(Object(s["openBlock"])(),Object(s["createElementBlock"])("div",{class:Object(s["normalizeClass"])(`matchPageRules ${i} multiple`),key:i},[Object(s["createElementVNode"])("div",null,[Object(s["createVNode"])(l,{"model-value":a,"onUpdate:modelValue":t=>e.setMatchPageRule(t,i),onAddUrl:t[2]||(t[2]=t=>e.addMatchPageRule()),onRemoveUrl:t=>e.removeMatchPageRule(i),onAnyChange:t[3]||(t[3]=t=>e.setValueHasChanged()),"allow-any":!1,"disable-if-no-value":i>0,"can-be-removed":i>0,"show-add-url":!0},null,8,["model-value","onUpdate:modelValue","onRemoveUrl","disable-if-no-value","can-be-removed"])]),Xt],2))),128))]),Object(s["createElementVNode"])("div",Yt,[Object(s["createElementVNode"])("div",Kt,[Object(s["createElementVNode"])("span",Qt,[Object(s["createTextVNode"])(Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_FieldIncludedTargetsHelp"))+" ",1),Object(s["createElementVNode"])("div",null,[Object(s["createVNode"])(c,{"included-targets":e.siteHsr.match_page_rules},null,8,["included-targets"])])])])])]),Object(s["createElementVNode"])("div",Zt,[Object(s["createVNode"])(o,{uicontrol:"select",name:"sampleRate","model-value":e.siteHsr.sample_rate,"onUpdate:modelValue":t[4]||(t[4]=t=>{e.siteHsr.sample_rate=t,e.setValueHasChanged()}),title:e.translate("HeatmapSessionRecording_SampleRate"),options:e.sampleRates,introduction:e.translate("HeatmapSessionRecording_AdvancedOptions"),"inline-help":e.translate("HeatmapSessionRecording_HeatmapSampleRateHelp")},null,8,["model-value","title","options","introduction","inline-help"])]),Object(s["createElementVNode"])("div",ea,[Object(s["createVNode"])(o,{uicontrol:"text",name:"excludedElements","model-value":e.siteHsr.excluded_elements,"onUpdate:modelValue":t[5]||(t[5]=t=>{e.siteHsr.excluded_elements=t,e.setValueHasChanged()}),title:e.translate("HeatmapSessionRecording_ExcludedElements"),maxlength:1e3,"inline-help":e.translate("HeatmapSessionRecording_ExcludedElementsHelp")},null,8,["model-value","title","inline-help"])]),Object(s["createElementVNode"])("div",ta,[Object(s["createVNode"])(o,{uicontrol:"text",name:"screenshotUrl","model-value":e.siteHsr.screenshot_url,"onUpdate:modelValue":t[6]||(t[6]=t=>{e.siteHsr.screenshot_url=t,e.setValueHasChanged()}),title:e.translate("HeatmapSessionRecording_ScreenshotUrl"),maxlength:300,disabled:!!e.siteHsr.page_treemirror,"inline-help":e.translate("HeatmapSessionRecording_ScreenshotUrlHelp")},null,8,["model-value","title","disabled","inline-help"])]),Object(s["createElementVNode"])("div",aa,[Object(s["createVNode"])(o,{uicontrol:"text",name:"breakpointMobile","model-value":e.siteHsr.breakpoint_mobile,"onUpdate:modelValue":t[7]||(t[7]=t=>{e.siteHsr.breakpoint_mobile=t,e.setValueHasChanged()}),title:e.translate("HeatmapSessionRecording_BreakpointX",e.translate("General_Mobile")),maxlength:4,"inline-help":e.breakpointMobileInlineHelp},null,8,["model-value","title","inline-help"])]),Object(s["createElementVNode"])("div",ia,[Object(s["createVNode"])(o,{uicontrol:"text",name:"breakpointTablet","model-value":e.siteHsr.breakpoint_tablet,"onUpdate:modelValue":t[8]||(t[8]=t=>{e.siteHsr.breakpoint_tablet=t,e.setValueHasChanged()}),title:e.translate("HeatmapSessionRecording_BreakpointX",e.translate("DevicesDetection_Tablet")),maxlength:4,"inline-help":e.breakpointGeneralHelp},null,8,["model-value","title","inline-help"])]),Object(s["createElementVNode"])("div",na,[Object(s["createVNode"])(o,{uicontrol:"checkbox",name:"capture_manually",title:e.translate("HeatmapSessionRecording_CaptureDomTitle"),"inline-help":e.captureDomInlineHelp,"model-value":e.siteHsr.capture_manually,"onUpdate:modelValue":t[9]||(t[9]=t=>{e.siteHsr.capture_manually=t,e.setValueHasChanged()})},null,8,["title","inline-help","model-value"])]),Object(s["createElementVNode"])("p",{innerHTML:e.$sanitize(e.personalInformationNote)},null,8,sa),Object(s["createVNode"])(d,{class:"createButton",onConfirm:t[10]||(t[10]=t=>e.edit?e.updateHsr():e.createHsr()),disabled:e.isUpdating||!e.isDirty,saving:e.isUpdating,value:e.saveButtonText},null,8,["disabled","saving","value"]),Object(s["createElementVNode"])("div",ra,[Object(s["createElementVNode"])("a",{onClick:t[11]||(t[11]=t=>e.cancel())},Object(s["toDisplayString"])(e.translate("General_Cancel")),1)])])],32)]),_:1},8,["content-title"])}function la(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e} +/** + * Copyright (C) InnoCraft Ltd - All rights reserved. + * + * NOTICE: All information contained herein is, and remains the property of InnoCraft Ltd. + * The intellectual and technical concepts contained herein are protected by trade secret + * or copyright law. Redistribution of this information or reproduction of this material is + * strictly forbidden unless prior written permission is obtained from InnoCraft Ltd. + * + * You shall use this code only in accordance with the license agreement obtained from + * InnoCraft Ltd. + * + * @link https://www.innocraft.com/ + * @license For license details see https://www.innocraft.com/license + */class ca{constructor(e){la(this,"context",void 0),la(this,"privateState",Object(s["reactive"])({allHsrs:[],isLoading:!1,isUpdating:!1,filterStatus:""})),la(this,"state",Object(s["computed"])(()=>Object(s["readonly"])(this.privateState))),la(this,"hsrs",Object(s["computed"])(()=>this.privateState.filterStatus?this.state.value.allHsrs.filter(e=>e.status===this.privateState.filterStatus):this.state.value.allHsrs)),la(this,"hsrsCloned",Object(s["computed"])(()=>Object(A["clone"])(this.hsrs.value))),la(this,"statusOptions",Object(s["readonly"])([{key:"",value:Object(A["translate"])("General_All")},{key:"active",value:Object(A["translate"])("HeatmapSessionRecording_StatusActive")},{key:"ended",value:Object(A["translate"])("HeatmapSessionRecording_StatusEnded")},{key:"paused",value:Object(A["translate"])("HeatmapSessionRecording_StatusPaused")}])),la(this,"fetchPromises",{}),this.context=e}setFilterStatus(e){this.privateState.filterStatus=e}reload(){return this.privateState.allHsrs=[],this.fetchPromises={},this.fetchHsrs()}filterRules(e){return e.filter(e=>!!e&&(e.value||"any"===e.type))}getApiMethodInContext(e){return`${e}${this.context}`}fetchHsrs(){let e="HeatmapSessionRecording.getHeatmaps";"SessionRecording"===this.context&&(e="HeatmapSessionRecording.getSessionRecordings");const t={method:e,filter_limit:"-1"};return this.fetchPromises[e]||(this.fetchPromises[e]=A["AjaxHelper"].fetch(t)),this.privateState.isLoading=!0,this.privateState.allHsrs=[],this.fetchPromises[e].then(e=>(this.privateState.allHsrs=e,this.state.value.allHsrs)).finally(()=>{this.privateState.isLoading=!1})}findHsr(e){const t=this.state.value.allHsrs.find(t=>t.idsitehsr===e);return t?Promise.resolve(t):(this.privateState.isLoading=!0,A["AjaxHelper"].fetch({idSiteHsr:e,method:this.getApiMethodInContext("HeatmapSessionRecording.get"),filter_limit:"-1"}).finally(()=>{this.privateState.isLoading=!1}))}deleteHsr(e){return this.privateState.isUpdating=!0,this.privateState.allHsrs=[],A["AjaxHelper"].fetch({idSiteHsr:e,method:this.getApiMethodInContext("HeatmapSessionRecording.delete")},{withTokenInUrl:!0}).then(()=>({type:"success"})).catch(e=>({type:"error",message:e.message||e})).finally(()=>{this.privateState.isUpdating=!1})}completeHsr(e){return this.privateState.isUpdating=!0,this.privateState.allHsrs=[],A["AjaxHelper"].fetch({idSiteHsr:e,method:this.getApiMethodInContext("HeatmapSessionRecording.end")},{withTokenInUrl:!0}).then(()=>({type:"success"})).catch(e=>({type:"error",message:e.message||e})).finally(()=>{this.privateState.isUpdating=!1})}createOrUpdateHsr(e,t){const a={idSiteHsr:e.idsitehsr,sampleLimit:e.sample_limit,sampleRate:e.sample_rate,excludedElements:e.excluded_elements?e.excluded_elements.trim():void 0,screenshotUrl:e.screenshot_url?e.screenshot_url.trim():void 0,breakpointMobile:e.breakpoint_mobile,breakpointTablet:e.breakpoint_tablet,minSessionTime:e.min_session_time,requiresActivity:e.requires_activity?1:0,captureKeystrokes:e.capture_keystrokes?1:0,captureDomManually:e.capture_manually?1:0,method:t,name:e.name.trim()},i={matchPageRules:this.filterRules(e.match_page_rules)};return this.privateState.isUpdating=!0,A["AjaxHelper"].post(a,i,{withTokenInUrl:!0}).then(e=>({type:"success",response:e})).catch(e=>({type:"error",message:e.message||e})).finally(()=>{this.privateState.isUpdating=!1})}}const da=new ca("Heatmap"),ma=new ca("SessionRecording"),pa="hsrmanagement";var ha=Object(s["defineComponent"])({props:{idSiteHsr:Number,breakpointMobile:Number,breakpointTablet:Number},components:{ContentBlock:A["ContentBlock"],Field:U["Field"],HsrUrlTarget:Ut,HsrTargetTest:Nt,SaveButton:U["SaveButton"]},data(){return{isDirty:!1,showAdvancedView:!1,siteHsr:{}}},created(){this.init()},watch:{idSiteHsr(e){null!==e&&this.init()}},methods:{removeAnyHsrNotification(){A["NotificationsStore"].remove(pa),A["NotificationsStore"].remove("ajaxHelper")},showNotification(e,t){const a=A["NotificationsStore"].show({message:e,context:t,id:pa,type:"transient"});setTimeout(()=>{A["NotificationsStore"].scrollToNotification(a)},200)},showErrorFieldNotProvidedNotification(e){const t=Object(A["translate"])("HeatmapSessionRecording_ErrorXNotProvided",[e]);this.showNotification(t,"error")},init(){const{idSiteHsr:e}=this;if(this.siteHsr={},this.showAdvancedView=!1,A["Matomo"].helper.lazyScrollToContent(),this.edit&&e)da.findHsr(e).then(e=>{e&&(this.siteHsr=Object(A["clone"])(e),this.siteHsr.sample_rate=""+this.siteHsr.sample_rate,this.addInitialMatchPageRule(),this.isDirty=!1)});else if(this.create){this.siteHsr={idSite:A["Matomo"].idSite,name:"",sample_rate:"10.0",sample_limit:1e3,breakpoint_mobile:this.breakpointMobile,breakpoint_tablet:this.breakpointTablet,capture_manually:0},this.isDirty=!1;const e=A["MatomoUrl"].hashParsed.value;if(e.name&&(this.siteHsr.name=e.name,this.isDirty=!0),e.matchPageRules)try{this.siteHsr.match_page_rules=JSON.parse(e.matchPageRules),this.isDirty=!0}catch(t){console.log("warning: could not parse matchPageRules query param, expected JSON")}else this.addInitialMatchPageRule()}},addInitialMatchPageRule(){var e;this.siteHsr&&(null!==(e=this.siteHsr.match_page_rules)&&void 0!==e&&e.length||this.addMatchPageRule())},addMatchPageRule(){var e;this.siteHsr&&(null!==(e=this.siteHsr.match_page_rules)&&void 0!==e&&e.length||(this.siteHsr.match_page_rules=[]),this.siteHsr.match_page_rules.push({attribute:"url",type:"equals_simple",value:"",inverted:0}),this.isDirty=!0)},removeMatchPageRule(e){this.siteHsr&&e>-1&&(this.siteHsr.match_page_rules=[...this.siteHsr.match_page_rules],this.siteHsr.match_page_rules.splice(e,1),this.isDirty=!0)},cancel(){const e=Object.assign({},A["MatomoUrl"].hashParsed.value);delete e.idSiteHsr,A["MatomoUrl"].updateHash(e)},createHsr(){this.removeAnyHsrNotification(),this.checkRequiredFieldsAreSet()&&da.createOrUpdateHsr(this.siteHsr,"HeatmapSessionRecording.addHeatmap").then(e=>{if(!e||"error"===e.type||!e.response)return;this.isDirty=!1;const t=e.response.value;da.reload().then(()=>{A["Matomo"].helper.isReportingPage()&&A["Matomo"].postEvent("updateReportingMenu"),A["MatomoUrl"].updateHash(Object.assign(Object.assign({},A["MatomoUrl"].hashParsed.value),{},{idSiteHsr:t})),setTimeout(()=>{this.showNotification(Object(A["translate"])("HeatmapSessionRecording_HeatmapCreated"),e.type)},200)})})},setValueHasChanged(){this.isDirty=!0},updateHsr(){this.removeAnyHsrNotification(),this.checkRequiredFieldsAreSet()&&da.createOrUpdateHsr(this.siteHsr,"HeatmapSessionRecording.updateHeatmap").then(e=>{"error"!==e.type&&(this.isDirty=!1,this.siteHsr={},da.reload().then(()=>{this.init()}),this.showNotification(Object(A["translate"])("HeatmapSessionRecording_HeatmapUpdated"),e.type))})},checkRequiredFieldsAreSet(){var e;if(!this.siteHsr.name){const e=Object(A["translate"])("General_Name");return this.showErrorFieldNotProvidedNotification(e),!1}if(null===(e=this.siteHsr.match_page_rules)||void 0===e||!e.length||!da.filterRules(this.siteHsr.match_page_rules).length){const e=Object(A["translate"])("HeatmapSessionRecording_ErrorPageRuleRequired");return this.showNotification(e,"error"),!1}return!0},setMatchPageRule(e,t){this.siteHsr.match_page_rules=[...this.siteHsr.match_page_rules],this.siteHsr.match_page_rules[t]=e}},computed:{sampleLimits(){return[1e3,2e3,5e3].map(e=>({key:""+e,value:e}))},sampleRates(){const e=[.1,.5,1,2,3,4,5,6,8,10,15,20,30,40,50,60,70,80,90,100];return e.map(e=>({key:e.toFixed(1),value:e+"%"}))},create(){return!this.idSiteHsr},edit(){return!this.create},editTitle(){const e=this.create?"HeatmapSessionRecording_CreateNewHeatmap":"HeatmapSessionRecording_EditHeatmapX";return e},contentTitle(){return Object(A["translate"])(this.editTitle,this.siteHsr.name?`"${this.siteHsr.name}"`:"")},isLoading(){return da.state.value.isLoading},isUpdating(){return da.state.value.isUpdating},breakpointMobileInlineHelp(){const e=Object(A["translate"])("HeatmapSessionRecording_BreakpointGeneralHelp"),t=Object(A["translate"])("HeatmapSessionRecording_BreakpointGeneralHelpManage");return`${e} ${t}`},breakpointGeneralHelp(){const e=Object(A["translate"])("HeatmapSessionRecording_BreakpointGeneralHelp"),t=Object(A["translate"])("HeatmapSessionRecording_BreakpointGeneralHelpManage");return`${e} ${t}`},captureDomInlineHelp(){const e=this.idSiteHsr?this.idSiteHsr:"{idHeatmap}",t=`

    _paq.push(['HeatmapSessionRecording::captureInitialDom', ${e}])`;return Object(A["translate"])("HeatmapSessionRecording_CaptureDomInlineHelp",t,"

    ","")},personalInformationNote(){const e="https://developer.matomo.org/guides/heatmap-session-recording/setup#masking-content-on-your-website";return Object(A["translate"])("HeatmapSessionRecording_PersonalInformationNote",Object(A["translate"])("HeatmapSessionRecording_Heatmap"),"","",``,"")},saveButtonText(){return this.edit?Object(A["translate"])("CoreUpdater_UpdateTitle"):Object(A["translate"])("HeatmapSessionRecording_CreateNewHeatmap")}}});ha.render=oa;var ua=ha;const ga={class:"heatmapList"},ba={class:"filterStatus"},va={class:"hsrSearchFilter",style:{"margin-left":"3.5px"}},Oa={class:"index"},ja={class:"name"},fa={class:"creationDate"},ya={class:"sampleLimit"},Sa={class:"status"},Ha={class:"action"},_a={colspan:"7"},Va={class:"loadingPiwik"},Na=Object(s["createElementVNode"])("img",{src:"plugins/Morpheus/images/loading-blue.gif"},null,-1),Ea={colspan:"7"},Ra=["id"],wa={class:"index"},ka={class:"name"},xa={class:"creationDate"},Ca={class:"sampleLimit"},Ta={key:0,class:"status status-paused"},Da=["title"],Ma={key:1,class:"status"},Pa={class:"action"},Ba=["title","onClick"],Aa=["title","onClick"],Ua=["title","href"],La=["title","onClick"],Ia={class:"tableActionBar"},Fa=Object(s["createElementVNode"])("span",{class:"icon-add"},null,-1),Wa={class:"ui-confirm",id:"confirmDeleteHeatmap",ref:"confirmDeleteHeatmap"},qa=["value"],za=["value"],$a={class:"ui-confirm",id:"confirmEndHeatmap",ref:"confirmEndHeatmap"},Ga=["value"],Ja=["value"];function Xa(e,t,a,i,n,r){const o=Object(s["resolveComponent"])("Field"),l=Object(s["resolveComponent"])("ContentBlock"),c=Object(s["resolveDirective"])("content-table");return Object(s["openBlock"])(),Object(s["createElementBlock"])("div",ga,[Object(s["createVNode"])(l,{"content-title":e.translate("HeatmapSessionRecording_ManageHeatmaps")},{default:Object(s["withCtx"])(()=>[Object(s["createElementVNode"])("p",null,Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_HeatmapUsageBenefits")),1),Object(s["createElementVNode"])("div",null,[Object(s["createElementVNode"])("div",ba,[Object(s["createVNode"])(o,{uicontrol:"select",name:"filterStatus","model-value":e.filterStatus,"onUpdate:modelValue":t[0]||(t[0]=t=>{e.setFilterStatus(t)}),title:e.translate("HeatmapSessionRecording_Filter"),"full-width":!0,options:e.statusOptions},null,8,["model-value","title","options"])]),Object(s["createElementVNode"])("div",va,[Object(s["withDirectives"])(Object(s["createVNode"])(o,{uicontrol:"text",name:"hsrSearch",title:e.translate("General_Search"),modelValue:e.searchFilter,"onUpdate:modelValue":t[1]||(t[1]=t=>e.searchFilter=t),"full-width":!0},null,8,["title","modelValue"]),[[s["vShow"],e.hsrs.length>0]])])]),Object(s["withDirectives"])((Object(s["openBlock"])(),Object(s["createElementBlock"])("table",null,[Object(s["createElementVNode"])("thead",null,[Object(s["createElementVNode"])("tr",null,[Object(s["createElementVNode"])("th",Oa,Object(s["toDisplayString"])(e.translate("General_Id")),1),Object(s["createElementVNode"])("th",ja,Object(s["toDisplayString"])(e.translate("General_Name")),1),Object(s["createElementVNode"])("th",fa,Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_CreationDate")),1),Object(s["createElementVNode"])("th",ya,Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_SampleLimit")),1),Object(s["createElementVNode"])("th",Sa,Object(s["toDisplayString"])(e.translate("CorePluginsAdmin_Status")),1),Object(s["createElementVNode"])("th",Ha,Object(s["toDisplayString"])(e.translate("General_Actions")),1)])]),Object(s["createElementVNode"])("tbody",null,[Object(s["withDirectives"])(Object(s["createElementVNode"])("tr",null,[Object(s["createElementVNode"])("td",_a,[Object(s["createElementVNode"])("span",Va,[Na,Object(s["createTextVNode"])(" "+Object(s["toDisplayString"])(e.translate("General_LoadingData")),1)])])],512),[[s["vShow"],e.isLoading||e.isUpdating]]),Object(s["withDirectives"])(Object(s["createElementVNode"])("tr",null,[Object(s["createElementVNode"])("td",Ea,Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_NoHeatmapsFound")),1)],512),[[s["vShow"],!e.isLoading&&0===e.hsrs.length]]),(Object(s["openBlock"])(!0),Object(s["createElementBlock"])(s["Fragment"],null,Object(s["renderList"])(e.sortedHsrs,t=>(Object(s["openBlock"])(),Object(s["createElementBlock"])("tr",{id:"hsr"+t.idsitehsr,class:"hsrs",key:t.idsitehsr},[Object(s["createElementVNode"])("td",wa,Object(s["toDisplayString"])(t.idsitehsr),1),Object(s["createElementVNode"])("td",ka,Object(s["toDisplayString"])(t.name),1),Object(s["createElementVNode"])("td",xa,Object(s["toDisplayString"])(t.created_date_pretty),1),Object(s["createElementVNode"])("td",Ca,Object(s["toDisplayString"])(t.sample_limit),1),"paused"===t.status?(Object(s["openBlock"])(),Object(s["createElementBlock"])("td",Ta,[Object(s["createTextVNode"])(Object(s["toDisplayString"])(e.ucfirst(t.status))+" ",1),Object(s["createElementVNode"])("span",{class:"icon icon-help",title:e.pauseReason},null,8,Da)])):(Object(s["openBlock"])(),Object(s["createElementBlock"])("td",Ma,Object(s["toDisplayString"])(e.ucfirst(t.status)),1)),Object(s["createElementVNode"])("td",Pa,[Object(s["createElementVNode"])("a",{class:"table-action icon-edit",title:e.translate("HeatmapSessionRecording_EditX",e.translate("HeatmapSessionRecording_Heatmap")),onClick:a=>e.editHsr(t.idsitehsr)},null,8,Ba),Object(s["withDirectives"])(Object(s["createElementVNode"])("a",{a:"",class:"table-action stopRecording icon-drop-crossed",title:e.translate("HeatmapSessionRecording_StopX",e.translate("HeatmapSessionRecording_Heatmap")),onClick:a=>e.completeHsr(t)},null,8,Aa),[[s["vShow"],"ended"!==t.status]]),Object(s["createElementVNode"])("a",{target:"_blank",class:"table-action icon-show",title:e.translate("HeatmapSessionRecording_ViewReport"),href:e.getViewReportLink(t)},null,8,Ua),Object(s["createElementVNode"])("a",{class:"table-action icon-delete",title:e.translate("HeatmapSessionRecording_DeleteX",e.translate("HeatmapSessionRecording_Heatmap")),onClick:a=>e.deleteHsr(t)},null,8,La)])],8,Ra))),128))])])),[[c]]),Object(s["createElementVNode"])("div",Ia,[Object(s["createElementVNode"])("a",{class:"createNewHsr",value:"",onClick:t[2]||(t[2]=t=>e.createHsr())},[Fa,Object(s["createTextVNode"])(" "+Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_CreateNewHeatmap")),1)])])]),_:1},8,["content-title"]),Object(s["createElementVNode"])("div",Wa,[Object(s["createElementVNode"])("h2",null,Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_DeleteHeatmapConfirm")),1),Object(s["createElementVNode"])("input",{role:"yes",type:"button",value:e.translate("General_Yes")},null,8,qa),Object(s["createElementVNode"])("input",{role:"no",type:"button",value:e.translate("General_No")},null,8,za)],512),Object(s["createElementVNode"])("div",$a,[Object(s["createElementVNode"])("h2",null,Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_EndHeatmapConfirm")),1),Object(s["createElementVNode"])("input",{role:"yes",type:"button",value:e.translate("General_Yes")},null,8,Ga),Object(s["createElementVNode"])("input",{role:"no",type:"button",value:e.translate("General_No")},null,8,Ja)],512)])}var Ya=Object(s["defineComponent"])({props:{pauseReason:String},components:{ContentBlock:A["ContentBlock"],Field:U["Field"]},directives:{ContentTable:A["ContentTable"]},data(){return{searchFilter:""}},created(){da.setFilterStatus(""),da.fetchHsrs()},methods:{createHsr(){this.editHsr(0)},editHsr(e){A["MatomoUrl"].updateHash(Object.assign(Object.assign({},A["MatomoUrl"].hashParsed.value),{},{idSiteHsr:e}))},deleteHsr(e){A["Matomo"].helper.modalConfirm(this.$refs.confirmDeleteHeatmap,{yes:()=>{da.deleteHsr(e.idsitehsr).then(()=>{da.reload(),A["Matomo"].postEvent("updateReportingMenu")})}})},completeHsr(e){A["Matomo"].helper.modalConfirm(this.$refs.confirmEndHeatmap,{yes:()=>{da.completeHsr(e.idsitehsr).then(()=>{da.reload()})}})},setFilterStatus(e){da.setFilterStatus(e)},ucfirst(e){return`${e[0].toUpperCase()}${e.substr(1)}`},getViewReportLink(e){return"?"+A["MatomoUrl"].stringify({module:"Widgetize",action:"iframe",moduleToWidgetize:"HeatmapSessionRecording",actionToWidgetize:"showHeatmap",idSiteHsr:e.idsitehsr,idSite:e.idsite,period:"day",date:"yesterday"})}},computed:{filterStatus(){return da.state.value.filterStatus},statusOptions(){return da.statusOptions},hsrs(){return da.hsrs.value},isLoading(){return da.state.value.isLoading},isUpdating(){return da.state.value.isUpdating},sortedHsrs(){const e=[...this.hsrs].filter(e=>Object.keys(e).some(t=>{const a=e;return"string"===typeof a[t]&&-1!==a[t].indexOf(this.searchFilter)}));return e.sort((e,t)=>t.idsitehsr-e.idsitehsr),e}}});Ya.render=Xa;var Ka=Ya;const Qa={class:"manageHsr",ref:"root"},Za={key:0},ei={key:1};function ti(e,t,a,i,n,r){const o=Object(s["resolveComponent"])("MatomoJsNotWritableAlert"),l=Object(s["resolveComponent"])("HeatmapList"),c=Object(s["resolveComponent"])("HeatmapEdit");return Object(s["openBlock"])(),Object(s["createElementBlock"])(s["Fragment"],null,[e.editMode?Object(s["createCommentVNode"])("",!0):(Object(s["openBlock"])(),Object(s["createBlock"])(o,{key:0,"is-matomo-js-writable":e.isMatomoJsWritable,"recording-type":e.translate("HeatmapSessionRecording_Heatmaps")},null,8,["is-matomo-js-writable","recording-type"])),Object(s["createElementVNode"])("div",Qa,[e.editMode?Object(s["createCommentVNode"])("",!0):(Object(s["openBlock"])(),Object(s["createElementBlock"])("div",Za,[Object(s["createVNode"])(l,{"pause-reason":e.pauseReason},null,8,["pause-reason"])])),e.editMode?(Object(s["openBlock"])(),Object(s["createElementBlock"])("div",ei,[Object(s["createVNode"])(c,{"breakpoint-mobile":e.breakpointMobile,"breakpoint-tablet":e.breakpointTablet,"id-site-hsr":e.idSiteHsr},null,8,["breakpoint-mobile","breakpoint-tablet","id-site-hsr"])])):Object(s["createCommentVNode"])("",!0)],512)],64)}const ai=["innerHTML"];function ii(e,t,a,i,n,r){return e.isMatomoJsWritable?Object(s["createCommentVNode"])("",!0):(Object(s["openBlock"])(),Object(s["createElementBlock"])("div",{key:0,class:"alert alert-warning",innerHTML:e.getJsNotWritableErrorMessage()},null,8,ai))}var ni=Object(s["defineComponent"])({props:{recordingType:{type:String,required:!0},isMatomoJsWritable:{type:Boolean,required:!0}},methods:{getJsNotWritableErrorMessage(){return Object(A["translate"])("HeatmapSessionRecording_MatomoJSNotWritableErrorMessage",this.recordingType,'',"")}}});ni.render=ii;var si=ni;const{$:ri}=window;var oi=Object(s["defineComponent"])({props:{breakpointMobile:Number,breakpointTablet:Number,pauseReason:String,isMatomoJsWritable:{type:Boolean,required:!0}},data(){return{editMode:!1,idSiteHsr:null}},components:{MatomoJsNotWritableAlert:si,HeatmapList:Ka,HeatmapEdit:ua},watch:{editMode(){ri(".ui-tooltip").remove()}},created(){Object(s["watch"])(()=>A["MatomoUrl"].hashParsed.value.idSiteHsr,e=>{this.initState(e)}),this.initState(A["MatomoUrl"].hashParsed.value.idSiteHsr)},methods:{removeAnyHsrNotification(){A["NotificationsStore"].remove("hsrmanagement")},initState(e){if(e){if("0"===e){const e={isAllowed:!0};if(A["Matomo"].postEvent("HeatmapSessionRecording.initAddHeatmap",e),e&&!e.isAllowed)return this.editMode=!1,void(this.idSiteHsr=null)}this.editMode=!0,this.idSiteHsr=parseInt(e,10)}else this.editMode=!1,this.idSiteHsr=null;this.removeAnyHsrNotification()}}});oi.render=ti;var li=oi;const ci={class:"loadingPiwik"},di=Object(s["createElementVNode"])("img",{src:"plugins/Morpheus/images/loading-blue.gif"},null,-1),mi={class:"loadingPiwik"},pi=Object(s["createElementVNode"])("img",{src:"plugins/Morpheus/images/loading-blue.gif"},null,-1),hi={name:"name"},ui={name:"sampleLimit"},gi={class:"form-group row"},bi={class:"col s12"},vi={class:"col s12 m6",style:{"padding-left":"0"}},Oi=Object(s["createElementVNode"])("hr",null,null,-1),ji={class:"col s12 m6"},fi={class:"form-help"},yi={class:"inline-help"},Si={name:"sampleRate"},Hi={name:"minSessionTime"},_i={name:"requiresActivity"},Vi={class:"inline-help-node"},Ni=["innerHTML"],Ei=["innerHTML"],Ri={class:"entityCancel"};function wi(e,t,a,i,n,r){const o=Object(s["resolveComponent"])("Field"),l=Object(s["resolveComponent"])("HsrUrlTarget"),c=Object(s["resolveComponent"])("HsrTargetTest"),d=Object(s["resolveComponent"])("SaveButton"),m=Object(s["resolveComponent"])("ContentBlock");return Object(s["openBlock"])(),Object(s["createBlock"])(m,{class:"editHsr","content-title":e.contentTitle},{default:Object(s["withCtx"])(()=>[Object(s["withDirectives"])(Object(s["createElementVNode"])("p",null,[Object(s["createElementVNode"])("span",ci,[di,Object(s["createTextVNode"])(" "+Object(s["toDisplayString"])(e.translate("General_LoadingData")),1)])],512),[[s["vShow"],e.isLoading]]),Object(s["withDirectives"])(Object(s["createElementVNode"])("p",null,[Object(s["createElementVNode"])("span",mi,[pi,Object(s["createTextVNode"])(" "+Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_UpdatingData")),1)])],512),[[s["vShow"],e.isUpdating]]),Object(s["createElementVNode"])("form",{onSubmit:t[10]||(t[10]=t=>e.edit?e.updateHsr():e.createHsr())},[Object(s["createElementVNode"])("div",null,[Object(s["createElementVNode"])("div",hi,[Object(s["createVNode"])(o,{uicontrol:"text",name:"name","model-value":e.siteHsr.name,"onUpdate:modelValue":t[0]||(t[0]=t=>{e.siteHsr.name=t,e.setValueHasChanged()}),title:e.translate("General_Name"),maxlength:50,placeholder:e.translate("HeatmapSessionRecording_FieldNamePlaceholder"),"inline-help":e.translate("HeatmapSessionRecording_SessionNameHelp")},null,8,["model-value","title","placeholder","inline-help"])]),Object(s["createElementVNode"])("div",ui,[Object(s["createVNode"])(o,{uicontrol:"select",name:"sampleLimit","model-value":e.siteHsr.sample_limit,"onUpdate:modelValue":t[1]||(t[1]=t=>{e.siteHsr.sample_limit=t,e.setValueHasChanged()}),title:e.translate("HeatmapSessionRecording_SessionSampleLimit"),options:e.sampleLimits,"inline-help":e.translate("HeatmapSessionRecording_SessionSampleLimitHelp")},null,8,["model-value","title","options","inline-help"])]),Object(s["createElementVNode"])("div",gi,[Object(s["createElementVNode"])("div",bi,[Object(s["createElementVNode"])("h3",null,Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_TargetPages"))+":",1)]),Object(s["createElementVNode"])("div",vi,[(Object(s["openBlock"])(!0),Object(s["createElementBlock"])(s["Fragment"],null,Object(s["renderList"])(e.siteHsr.match_page_rules,(a,i)=>(Object(s["openBlock"])(),Object(s["createElementBlock"])("div",{class:Object(s["normalizeClass"])(`matchPageRules ${i} multiple`),key:i},[Object(s["createElementVNode"])("div",null,[Object(s["createVNode"])(l,{"model-value":a,"onUpdate:modelValue":t=>e.setMatchPageRule(t,i),onAddUrl:t[2]||(t[2]=t=>e.addMatchPageRule()),onRemoveUrl:t=>e.removeMatchPageRule(i),onAnyChange:t[3]||(t[3]=t=>e.setValueHasChanged()),"allow-any":!0,"disable-if-no-value":i>0,"can-be-removed":i>0,"show-add-url":!0},null,8,["model-value","onUpdate:modelValue","onRemoveUrl","disable-if-no-value","can-be-removed"])]),Oi],2))),128))]),Object(s["createElementVNode"])("div",ji,[Object(s["createElementVNode"])("div",fi,[Object(s["createElementVNode"])("span",yi,[Object(s["createTextVNode"])(Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_FieldIncludedTargetsHelpSessions"))+" ",1),Object(s["createElementVNode"])("div",null,[Object(s["createVNode"])(c,{"included-targets":e.siteHsr.match_page_rules},null,8,["included-targets"])])])])])]),Object(s["createElementVNode"])("div",Si,[Object(s["createVNode"])(o,{uicontrol:"select",name:"sampleRate","model-value":e.siteHsr.sample_rate,"onUpdate:modelValue":t[4]||(t[4]=t=>{e.siteHsr.sample_rate=t,e.setValueHasChanged()}),title:e.translate("HeatmapSessionRecording_SampleRate"),options:e.sampleRates,introduction:e.translate("HeatmapSessionRecording_AdvancedOptions"),"inline-help":e.translate("HeatmapSessionRecording_SessionSampleRateHelp")},null,8,["model-value","title","options","introduction","inline-help"])]),Object(s["createElementVNode"])("div",Hi,[Object(s["createVNode"])(o,{uicontrol:"select",name:"minSessionTime","model-value":e.siteHsr.min_session_time,"onUpdate:modelValue":t[5]||(t[5]=t=>{e.siteHsr.min_session_time=t,e.setValueHasChanged()}),title:e.translate("HeatmapSessionRecording_MinSessionTime"),options:e.minSessionTimes,"inline-help":e.translate("HeatmapSessionRecording_MinSessionTimeHelp")},null,8,["model-value","title","options","inline-help"])]),Object(s["createElementVNode"])("div",_i,[Object(s["createVNode"])(o,{uicontrol:"checkbox",name:"requiresActivity","model-value":e.siteHsr.requires_activity,"onUpdate:modelValue":t[6]||(t[6]=t=>{e.siteHsr.requires_activity=t,e.setValueHasChanged()}),title:e.translate("HeatmapSessionRecording_RequiresActivity"),"inline-help":e.translate("HeatmapSessionRecording_RequiresActivityHelp")},null,8,["model-value","title","inline-help"])]),Object(s["createElementVNode"])("div",null,[Object(s["createVNode"])(o,{uicontrol:"checkbox",name:"captureKeystrokes","model-value":e.siteHsr.capture_keystrokes,"onUpdate:modelValue":t[7]||(t[7]=t=>{e.siteHsr.capture_keystrokes=t,e.setValueHasChanged()}),title:e.translate("HeatmapSessionRecording_CaptureKeystrokes")},{"inline-help":Object(s["withCtx"])(()=>[Object(s["createElementVNode"])("div",Vi,[Object(s["createElementVNode"])("span",{innerHTML:e.$sanitize(e.captureKeystrokesHelp)},null,8,Ni)])]),_:1},8,["model-value","title"])]),Object(s["createElementVNode"])("p",{innerHTML:e.$sanitize(e.personalInformationNote)},null,8,Ei),Object(s["createVNode"])(d,{class:"createButton",onConfirm:t[8]||(t[8]=t=>e.edit?e.updateHsr():e.createHsr()),disabled:e.isUpdating||!e.isDirty,saving:e.isUpdating,value:e.saveButtonText},null,8,["disabled","saving","value"]),Object(s["createElementVNode"])("div",Ri,[Object(s["createElementVNode"])("a",{onClick:t[9]||(t[9]=t=>e.cancel())},Object(s["toDisplayString"])(e.translate("General_Cancel")),1)])])],32)]),_:1},8,["content-title"])}const ki="hsrmanagement";var xi=Object(s["defineComponent"])({props:{idSiteHsr:Number},components:{ContentBlock:A["ContentBlock"],Field:U["Field"],HsrUrlTarget:Ut,HsrTargetTest:Nt,SaveButton:U["SaveButton"]},data(){return{isDirty:!1,showAdvancedView:!1,sampleLimits:[],siteHsr:{}}},created(){A["AjaxHelper"].fetch({method:"HeatmapSessionRecording.getAvailableSessionRecordingSampleLimits"}).then(e=>{this.sampleLimits=(e||[]).map(e=>({key:""+e,value:e}))}),this.init()},watch:{idSiteHsr(e){null!==e&&this.init()}},methods:{removeAnyHsrNotification(){A["NotificationsStore"].remove(ki),A["NotificationsStore"].remove("ajaxHelper")},showNotification(e,t){const a=A["NotificationsStore"].show({message:e,context:t,id:ki,type:"transient"});setTimeout(()=>{A["NotificationsStore"].scrollToNotification(a)},200)},showErrorFieldNotProvidedNotification(e){const t=Object(A["translate"])("HeatmapSessionRecording_ErrorXNotProvided",[e]);this.showNotification(t,"error")},init(){const{idSiteHsr:e}=this;this.siteHsr={},this.showAdvancedView=!1,A["Matomo"].helper.lazyScrollToContent(),this.edit&&e?ma.findHsr(e).then(e=>{e&&(this.siteHsr=Object(A["clone"])(e),this.siteHsr.sample_rate=""+this.siteHsr.sample_rate,this.addInitialMatchPageRule(),this.isDirty=!1)}):this.create&&(this.siteHsr={idSite:A["Matomo"].idSite,name:"",sample_rate:"10.0",sample_limit:250,min_session_time:0,requires_activity:!0,capture_keystrokes:!1},this.addInitialMatchPageRule(),this.isDirty=!1)},addInitialMatchPageRule(){var e;this.siteHsr&&(null!==(e=this.siteHsr.match_page_rules)&&void 0!==e&&e.length||(this.siteHsr.match_page_rules=[{attribute:"url",type:"any",value:"",inverted:0}]))},addMatchPageRule(){var e;this.siteHsr&&(null!==(e=this.siteHsr.match_page_rules)&&void 0!==e&&e.length||(this.siteHsr.match_page_rules=[]),this.siteHsr.match_page_rules.push({attribute:"url",type:"equals_simple",value:"",inverted:0}),this.isDirty=!0)},removeMatchPageRule(e){this.siteHsr&&e>-1&&(this.siteHsr.match_page_rules=[...this.siteHsr.match_page_rules],this.siteHsr.match_page_rules.splice(e,1),this.isDirty=!0)},cancel(){const e=Object.assign({},A["MatomoUrl"].hashParsed.value);delete e.idSiteHsr,A["MatomoUrl"].updateHash(e)},createHsr(){this.removeAnyHsrNotification(),this.checkRequiredFieldsAreSet()&&ma.createOrUpdateHsr(this.siteHsr,"HeatmapSessionRecording.addSessionRecording").then(e=>{if(!e||"error"===e.type||!e.response)return;this.isDirty=!1;const t=e.response.value;ma.reload().then(()=>{A["Matomo"].helper.isReportingPage()&&A["Matomo"].postEvent("updateReportingMenu"),A["MatomoUrl"].updateHash(Object.assign(Object.assign({},A["MatomoUrl"].hashParsed.value),{},{idSiteHsr:t})),setTimeout(()=>{this.showNotification(Object(A["translate"])("HeatmapSessionRecording_SessionRecordingCreated"),e.type)},200)})})},setValueHasChanged(){this.isDirty=!0},updateHsr(){this.removeAnyHsrNotification(),this.checkRequiredFieldsAreSet()&&ma.createOrUpdateHsr(this.siteHsr,"HeatmapSessionRecording.updateSessionRecording").then(e=>{"error"!==e.type&&(this.isDirty=!1,this.siteHsr={},ma.reload().then(()=>{this.init()}),this.showNotification(Object(A["translate"])("HeatmapSessionRecording_SessionRecordingUpdated"),e.type))})},checkRequiredFieldsAreSet(){var e;if(!this.siteHsr.name){const e=this.translate("General_Name");return this.showErrorFieldNotProvidedNotification(e),!1}if(null===(e=this.siteHsr.match_page_rules)||void 0===e||!e.length||!ma.filterRules(this.siteHsr.match_page_rules).length){const e=this.translate("HeatmapSessionRecording_ErrorPageRuleRequired");return this.showNotification(e,"error"),!1}return!0},setMatchPageRule(e,t){this.siteHsr.match_page_rules=[...this.siteHsr.match_page_rules],this.siteHsr.match_page_rules[t]=e}},computed:{minSessionTimes(){return[0,5,10,15,20,30,45,60,90,120].map(e=>({key:""+e,value:e+" seconds"}))},sampleRates(){const e=[.1,.5,1,2,3,4,5,6,8,10,15,20,30,40,50,60,70,80,90,100];return e.map(e=>({key:""+e.toFixed(1),value:e+"%"}))},create(){return!this.idSiteHsr},edit(){return!this.create},editTitle(){const e=this.create?"HeatmapSessionRecording_CreateNewSessionRecording":"HeatmapSessionRecording_EditSessionRecordingX";return e},contentTitle(){return Object(A["translate"])(this.editTitle,this.siteHsr.name?`"${this.siteHsr.name}"`:"")},isLoading(){return da.state.value.isLoading},isUpdating(){return da.state.value.isUpdating},captureKeystrokesHelp(){const e="https://developer.matomo.org/guides/heatmap-session-recording/setup#masking-keystrokes-in-form-fields";return Object(A["translate"])("HeatmapSessionRecording_CaptureKeystrokesHelp",``,"")},personalInformationNote(){const e="https://developer.matomo.org/guides/heatmap-session-recording/setup#masking-content-on-your-website";return Object(A["translate"])("HeatmapSessionRecording_PersonalInformationNote",Object(A["translate"])("HeatmapSessionRecording_SessionRecording"),"","",``,"")},saveButtonText(){return this.edit?Object(A["translate"])("CoreUpdater_UpdateTitle"):Object(A["translate"])("HeatmapSessionRecording_CreateNewSessionRecording")}}});xi.render=wi;var Ci=xi;const Ti={class:"sessionRecordingList"},Di={class:"filterStatus"},Mi={class:"hsrSearchFilter",style:{"margin-left":"3.5px"}},Pi={class:"index"},Bi={class:"name"},Ai={class:"creationDate"},Ui={class:"sampleLimit"},Li={class:"status"},Ii={class:"action"},Fi={colspan:"7"},Wi={class:"loadingPiwik"},qi=Object(s["createElementVNode"])("img",{src:"plugins/Morpheus/images/loading-blue.gif"},null,-1),zi={colspan:"7"},$i=["id"],Gi={class:"index"},Ji={class:"name"},Xi={class:"creationDate"},Yi={class:"sampleLimit"},Ki={key:0,class:"status status-paused"},Qi=["title"],Zi={key:1,class:"status"},en={class:"action"},tn=["title","onClick"],an=["title","onClick"],nn=["title","href"],sn=["title","onClick"],rn={class:"tableActionBar"},on=Object(s["createElementVNode"])("span",{class:"icon-add"},null,-1),ln={class:"ui-confirm",ref:"confirmDeleteSessionRecording"},cn=["value"],dn=["value"],mn={class:"ui-confirm",ref:"confirmEndSessionRecording"},pn=["value"],hn=["value"];function un(e,t,a,i,n,r){const o=Object(s["resolveComponent"])("Field"),l=Object(s["resolveComponent"])("ContentBlock"),c=Object(s["resolveDirective"])("content-table");return Object(s["openBlock"])(),Object(s["createElementBlock"])("div",Ti,[Object(s["createVNode"])(l,{"content-title":e.translate("HeatmapSessionRecording_ManageSessionRecordings")},{default:Object(s["withCtx"])(()=>[Object(s["createElementVNode"])("p",null,Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_SessionRecordingsUsageBenefits")),1),Object(s["createElementVNode"])("div",null,[Object(s["createElementVNode"])("div",Di,[Object(s["createVNode"])(o,{uicontrol:"select",name:"filterStatus","model-value":e.filterStatus,"onUpdate:modelValue":t[0]||(t[0]=t=>{e.setFilterStatus(t)}),title:e.translate("HeatmapSessionRecording_Filter"),"full-width":!0,options:e.statusOptions},null,8,["model-value","title","options"])]),Object(s["createElementVNode"])("div",Mi,[Object(s["withDirectives"])(Object(s["createVNode"])(o,{uicontrol:"text",name:"hsrSearch",title:e.translate("General_Search"),modelValue:e.searchFilter,"onUpdate:modelValue":t[1]||(t[1]=t=>e.searchFilter=t),"full-width":!0},null,8,["title","modelValue"]),[[s["vShow"],e.hsrs.length>0]])])]),Object(s["withDirectives"])((Object(s["openBlock"])(),Object(s["createElementBlock"])("table",null,[Object(s["createElementVNode"])("thead",null,[Object(s["createElementVNode"])("tr",null,[Object(s["createElementVNode"])("th",Pi,Object(s["toDisplayString"])(e.translate("General_Id")),1),Object(s["createElementVNode"])("th",Bi,Object(s["toDisplayString"])(e.translate("General_Name")),1),Object(s["createElementVNode"])("th",Ai,Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_CreationDate")),1),Object(s["createElementVNode"])("th",Ui,Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_SampleLimit")),1),Object(s["createElementVNode"])("th",Li,Object(s["toDisplayString"])(e.translate("CorePluginsAdmin_Status")),1),Object(s["createElementVNode"])("th",Ii,Object(s["toDisplayString"])(e.translate("General_Actions")),1)])]),Object(s["createElementVNode"])("tbody",null,[Object(s["withDirectives"])(Object(s["createElementVNode"])("tr",null,[Object(s["createElementVNode"])("td",Fi,[Object(s["createElementVNode"])("span",Wi,[qi,Object(s["createTextVNode"])(" "+Object(s["toDisplayString"])(e.translate("General_LoadingData")),1)])])],512),[[s["vShow"],e.isLoading||e.isUpdating]]),Object(s["withDirectives"])(Object(s["createElementVNode"])("tr",null,[Object(s["createElementVNode"])("td",zi,Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_NoSessionRecordingsFound")),1)],512),[[s["vShow"],!e.isLoading&&0==e.hsrs.length]]),(Object(s["openBlock"])(!0),Object(s["createElementBlock"])(s["Fragment"],null,Object(s["renderList"])(e.sortedHsrs,t=>(Object(s["openBlock"])(),Object(s["createElementBlock"])("tr",{id:"hsr"+t.idsitehsr,class:"hsrs",key:t.idsitehsr},[Object(s["createElementVNode"])("td",Gi,Object(s["toDisplayString"])(t.idsitehsr),1),Object(s["createElementVNode"])("td",Ji,Object(s["toDisplayString"])(t.name),1),Object(s["createElementVNode"])("td",Xi,Object(s["toDisplayString"])(t.created_date_pretty),1),Object(s["createElementVNode"])("td",Yi,Object(s["toDisplayString"])(t.sample_limit),1),"paused"===t.status?(Object(s["openBlock"])(),Object(s["createElementBlock"])("td",Ki,[Object(s["createTextVNode"])(Object(s["toDisplayString"])(e.ucfirst(t.status))+" ",1),Object(s["createElementVNode"])("span",{class:"icon icon-help",title:e.pauseReason},null,8,Qi)])):(Object(s["openBlock"])(),Object(s["createElementBlock"])("td",Zi,Object(s["toDisplayString"])(e.ucfirst(t.status)),1)),Object(s["createElementVNode"])("td",en,[Object(s["createElementVNode"])("a",{class:"table-action icon-edit",title:e.translate("HeatmapSessionRecording_EditX",e.translate("HeatmapSessionRecording_SessionRecording")),onClick:a=>e.editHsr(t.idsitehsr)},null,8,tn),Object(s["withDirectives"])(Object(s["createElementVNode"])("a",{class:"table-action stopRecording icon-drop-crossed",title:e.translate("HeatmapSessionRecording_StopX",e.translate("HeatmapSessionRecording_SessionRecording")),onClick:a=>e.completeHsr(t)},null,8,an),[[s["vShow"],"ended"!==t.status]]),Object(s["createElementVNode"])("a",{class:"table-action icon-show",title:e.translate("HeatmapSessionRecording_ViewReport"),href:e.getViewReportLink(t),target:"_blank"},null,8,nn),Object(s["createElementVNode"])("a",{class:"table-action icon-delete",title:e.translate("HeatmapSessionRecording_DeleteX",e.translate("HeatmapSessionRecording_SessionRecording")),onClick:a=>e.deleteHsr(t)},null,8,sn)])],8,$i))),128))])])),[[c]]),Object(s["createElementVNode"])("div",rn,[Object(s["createElementVNode"])("a",{class:"createNewHsr",value:"",onClick:t[2]||(t[2]=t=>e.createHsr())},[on,Object(s["createTextVNode"])(" "+Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_CreateNewSessionRecording")),1)])])]),_:1},8,["content-title"]),Object(s["createElementVNode"])("div",ln,[Object(s["createElementVNode"])("h2",null,Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_DeleteSessionRecordingConfirm")),1),Object(s["createElementVNode"])("input",{role:"yes",type:"button",value:e.translate("General_Yes")},null,8,cn),Object(s["createElementVNode"])("input",{role:"no",type:"button",value:e.translate("General_No")},null,8,dn)],512),Object(s["createElementVNode"])("div",mn,[Object(s["createElementVNode"])("h2",null,Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_EndSessionRecordingConfirm")),1),Object(s["createElementVNode"])("input",{role:"yes",type:"button",value:e.translate("General_Yes")},null,8,pn),Object(s["createElementVNode"])("input",{role:"no",type:"button",value:e.translate("General_No")},null,8,hn)],512)])}var gn=Object(s["defineComponent"])({props:{pauseReason:String},components:{ContentBlock:A["ContentBlock"],Field:U["Field"]},directives:{ContentTable:A["ContentTable"]},data(){return{searchFilter:""}},created(){ma.setFilterStatus(""),ma.fetchHsrs()},methods:{createHsr(){this.editHsr(0)},editHsr(e){A["MatomoUrl"].updateHash(Object.assign(Object.assign({},A["MatomoUrl"].hashParsed.value),{},{idSiteHsr:e}))},deleteHsr(e){A["Matomo"].helper.modalConfirm(this.$refs.confirmDeleteSessionRecording,{yes:()=>{ma.deleteHsr(e.idsitehsr).then(()=>{ma.reload(),A["Matomo"].postEvent("updateReportingMenu")})}})},completeHsr(e){A["Matomo"].helper.modalConfirm(this.$refs.confirmEndSessionRecording,{yes:()=>{ma.completeHsr(e.idsitehsr).then(()=>{ma.reload()})}})},setFilterStatus(e){ma.setFilterStatus(e)},ucfirst(e){return`${e[0].toUpperCase()}${e.substr(1)}`},getViewReportLink(e){return`?${A["MatomoUrl"].stringify({module:"CoreHome",action:"index",idSite:e.idsite,period:"day",date:"yesterday"})}#?${A["MatomoUrl"].stringify({category:"HeatmapSessionRecording_SessionRecordings",idSite:e.idsite,period:"day",date:"yesterday",subcategory:e.idsitehsr})}`}},computed:{filterStatus(){return ma.state.value.filterStatus},statusOptions(){return ma.statusOptions},hsrs(){return ma.hsrs.value},isLoading(){return ma.state.value.isLoading},isUpdating(){return ma.state.value.isUpdating},sortedHsrs(){const e=[...this.hsrs].filter(e=>Object.keys(e).some(t=>{const a=e;return"string"===typeof a[t]&&-1!==a[t].indexOf(this.searchFilter)}));return e.sort((e,t)=>t.idsitehsr-e.idsitehsr),e}}});gn.render=un;var bn=gn;const vn={class:"manageHsr"};function On(e,t,a,i,n,r){const o=Object(s["resolveComponent"])("MatomoJsNotWritableAlert"),l=Object(s["resolveComponent"])("SessionRecordingList"),c=Object(s["resolveComponent"])("SessionRecordingEdit");return Object(s["openBlock"])(),Object(s["createElementBlock"])(s["Fragment"],null,[e.editMode?Object(s["createCommentVNode"])("",!0):(Object(s["openBlock"])(),Object(s["createBlock"])(o,{key:0,"is-matomo-js-writable":e.isMatomoJsWritable,"recording-type":e.translate("HeatmapSessionRecording_SessionRecordings")},null,8,["is-matomo-js-writable","recording-type"])),Object(s["createElementVNode"])("div",vn,[Object(s["withDirectives"])(Object(s["createElementVNode"])("div",null,[Object(s["createVNode"])(l,{"pause-reason":e.pauseReason},null,8,["pause-reason"])],512),[[s["vShow"],!e.editMode]]),Object(s["withDirectives"])(Object(s["createElementVNode"])("div",null,[Object(s["createVNode"])(c,{"id-site-hsr":e.idSiteHsr},null,8,["id-site-hsr"])],512),[[s["vShow"],e.editMode]])])],64)}var jn=Object(s["defineComponent"])({props:{pauseReason:String,isMatomoJsWritable:{type:Boolean,required:!0}},data(){return{editMode:!1,idSiteHsr:null}},components:{MatomoJsNotWritableAlert:si,SessionRecordingEdit:Ci,SessionRecordingList:bn},created(){Object(s["watch"])(()=>A["MatomoUrl"].hashParsed.value.idSiteHsr,e=>{this.initState(e)}),this.initState(A["MatomoUrl"].hashParsed.value.idSiteHsr)},methods:{removeAnyHsrNotification(){A["NotificationsStore"].remove("hsrmanagement")},initState(e){if(e){if("0"===e){const e={isAllowed:!0};if(A["Matomo"].postEvent("HeatmapSessionRecording.initAddSessionRecording",e),e&&!e.isAllowed)return this.editMode=!1,void(this.idSiteHsr=null)}this.editMode=!0,this.idSiteHsr=parseInt(e,10)}else this.editMode=!1,this.idSiteHsr=null;this.removeAnyHsrNotification()}}});jn.render=On;var fn=jn;const yn={class:"ui-confirm",id:"listOfPageviews"},Sn=Object(s["createElementVNode"])("br",null,null,-1),Hn=Object(s["createElementVNode"])("br",null,null,-1),_n=["onClick"],Vn=["title"],Nn=["value"];function En(e,t,a,i,n,r){const o=Object(s["resolveDirective"])("content-table");return Object(s["openBlock"])(),Object(s["createElementBlock"])("div",yn,[Object(s["createElementVNode"])("h2",null,Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_PageviewsInVisit")),1),Sn,Hn,Object(s["withDirectives"])((Object(s["openBlock"])(),Object(s["createElementBlock"])("table",null,[Object(s["createElementVNode"])("thead",null,[Object(s["createElementVNode"])("tr",null,[Object(s["createElementVNode"])("th",null,Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_ColumnTime")),1),Object(s["createElementVNode"])("th",null,Object(s["toDisplayString"])(e.translate("General_TimeOnPage")),1),Object(s["createElementVNode"])("th",null,Object(s["toDisplayString"])(e.translate("Goals_URL")),1)])]),Object(s["createElementVNode"])("tbody",null,[(Object(s["openBlock"])(!0),Object(s["createElementBlock"])(s["Fragment"],null,Object(s["renderList"])(e.pageviews,t=>(Object(s["openBlock"])(),Object(s["createElementBlock"])("tr",{key:t.idloghsr,class:Object(s["normalizeClass"])({inactive:t.idloghsr!==e.idLogHsr}),onClick:a=>e.onClickPageView(t)},[Object(s["createElementVNode"])("td",null,Object(s["toDisplayString"])(t.server_time_pretty),1),Object(s["createElementVNode"])("td",null,Object(s["toDisplayString"])(t.time_on_page_pretty),1),Object(s["createElementVNode"])("td",{title:t.label},Object(s["toDisplayString"])((t.label||"").substr(0,50)),9,Vn)],10,_n))),128))])])),[[o]]),Object(s["createElementVNode"])("input",{role:"close",type:"button",value:e.translate("General_Close")},null,8,Nn)])}var Rn=Object(s["defineComponent"])({props:{pageviews:{type:Array,required:!0},idLogHsr:{type:Number,required:!0}},directives:{ContentTable:A["ContentTable"]},methods:{onClickPageView(e){e.idloghsr!==this.idLogHsr&&A["MatomoUrl"].updateUrl(Object.assign(Object.assign({},A["MatomoUrl"].urlParsed.value),{},{idLogHsr:e.idloghsr}),A["MatomoUrl"].hashParsed.value.length?Object.assign(Object.assign({},A["MatomoUrl"].hashParsed.value),{},{idLogHsr:e.idloghsr}):void 0)}}});Rn.render=En;var wn=Rn;const kn={class:"heatmap-vis-title"},xn={key:0,class:"alert alert-info heatmap-country-alert"},Cn={key:1},Tn={key:2},Dn=["innerHTML"],Mn={class:"alert alert-info"},Pn={key:3},Bn={class:"alert alert-info"};function An(e,t,a,i,n,r){var o;const l=Object(s["resolveComponent"])("EnrichedHeadline"),c=Object(s["resolveComponent"])("MatomoJsNotWritableAlert"),d=Object(s["resolveComponent"])("HeatmapVis"),m=Object(s["resolveComponent"])("ContentBlock");return Object(s["openBlock"])(),Object(s["createElementBlock"])("div",null,[Object(s["createElementVNode"])("h2",kn,[Object(s["createVNode"])(l,{"edit-url":e.editUrl,"inline-help":e.inlineHelp},{default:Object(s["withCtx"])(()=>[Object(s["createTextVNode"])(Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_HeatmapX",`"${e.heatmap.name}"`)),1)]),_:1},8,["edit-url","inline-help"])]),Object(s["createVNode"])(c,{"is-matomo-js-writable":e.isMatomoJsWritable,"recording-type":e.translate("HeatmapSessionRecording_Heatmaps")},null,8,["is-matomo-js-writable","recording-type"]),e.includedCountries?(Object(s["openBlock"])(),Object(s["createElementBlock"])("div",xn,Object(s["toDisplayString"])(e.translate("HeatmapSessionRecording_HeatmapInfoTrackVisitsFromCountries",e.includedCountries)),1)):Object(s["createCommentVNode"])("",!0),e.heatmap.page_treemirror?(Object(s["openBlock"])(),Object(s["createElementBlock"])("div",Cn,[Object(s["createVNode"])(d,{"created-date":e.createdDate,"excluded-elements":e.heatmap.excluded_elements,"num-samples":e.heatmapMetadata,url:e.heatmap.screenshot_url,"heatmap-date":e.heatmapDate,"heatmap-period":e.heatmapPeriod,"offset-accuracy":e.offsetAccuracy,"breakpoint-tablet":e.heatmap.breakpoint_tablet,"breakpoint-mobile":e.heatmap.breakpoint_mobile,"heatmap-types":e.heatmapTypes,"device-types":e.deviceTypes,"id-site-hsr":e.idSiteHsr,"is-active":e.isActive,"desktop-preview-size":e.desktopPreviewSize,"iframe-resolutions-values":e.iframeResolutions},null,8,["created-date","excluded-elements","num-samples","url","heatmap-date","heatmap-period","offset-accuracy","breakpoint-tablet","breakpoint-mobile","heatmap-types","device-types","id-site-hsr","is-active","desktop-preview-size","iframe-resolutions-values"])])):null!==(o=e.heatmapMetadata)&&void 0!==o&&o.nb_samples_device_all?(Object(s["openBlock"])(),Object(s["createElementBlock"])("div",Pn,[Object(s["createVNode"])(m,null,{default:Object(s["withCtx"])(()=>[Object(s["createElementVNode"])("div",Bn,Object(s["toDisplayString"])(e.noHeatmapScreenshotRecordedYetText),1)]),_:1})])):(Object(s["openBlock"])(),Object(s["createElementBlock"])("div",Tn,[Object(s["createElementVNode"])("p",{innerHTML:e.$sanitize(e.recordedSamplesTroubleShoot)},null,8,Dn),Object(s["createVNode"])(m,null,{default:Object(s["withCtx"])(()=>[Object(s["createElementVNode"])("div",Mn,Object(s["toDisplayString"])(e.translate(e.noDataMessageKey)),1)]),_:1})]))])}var Un=Object(s["defineComponent"])({props:{idSiteHsr:{type:Number,required:!0},heatmap:{type:Object,required:!0},heatmapMetadata:{type:Object,required:!0},deviceTypes:{type:Array,required:!0},heatmapTypes:{type:Array,required:!0},offsetAccuracy:{type:Number,required:!0},heatmapPeriod:{type:String,required:!0},heatmapDate:{type:String,required:!0},isActive:Boolean,createdDate:{type:String,required:!0},editUrl:{type:String,required:!0},inlineHelp:{type:String,required:!0},includedCountries:{type:String,required:!0},desktopPreviewSize:{type:Number,required:!0},iframeResolutions:{type:Object,required:!0},noDataMessageKey:{type:String,required:!0},isMatomoJsWritable:{type:Boolean,required:!0}},components:{MatomoJsNotWritableAlert:si,ContentBlock:A["ContentBlock"],HeatmapVis:oe,EnrichedHeadline:A["EnrichedHeadline"]},computed:{noHeatmapScreenshotRecordedYetText(){return Object(A["translate"])("HeatmapSessionRecording_NoHeatmapScreenshotRecordedYet",this.heatmapMetadata.nb_samples_device_all,Object(A["translate"])("HeatmapSessionRecording_ScreenshotUrl"))},recordedSamplesTroubleShoot(){const e=Object(A["externalLink"])("https://matomo.org/faq/heatmap-session-recording/troubleshooting-heatmaps/");return Object(A["translate"])("HeatmapSessionRecording_HeatmapTroubleshoot",e,"")}},created(){A["Matomo"].postEvent("hidePeriodSelector")}});Un.render=An;var Ln=Un; +/** + * Copyright (C) InnoCraft Ltd - All rights reserved. + * + * NOTICE: All information contained herein is, and remains the property of InnoCraft Ltd. + * The intellectual and technical concepts contained herein are protected by trade secret + * or copyright law. Redistribution of this information or reproduction of this material is + * strictly forbidden unless prior written permission is obtained from InnoCraft Ltd. + * + * You shall use this code only in accordance with the license agreement obtained from + * InnoCraft Ltd. + * + * @link https://www.innocraft.com/ + * @license For license details see https://www.innocraft.com/license + */}})})); +//# sourceMappingURL=HeatmapSessionRecording.umd.min.js.map \ No newline at end of file diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/vue/dist/umd.metadata.json b/files/plugin-HeatmapSessionRecording-5.2.4/vue/dist/umd.metadata.json new file mode 100644 index 0000000..dce4477 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/vue/dist/umd.metadata.json @@ -0,0 +1,6 @@ +{ + "dependsOn": [ + "CoreHome", + "CorePluginsAdmin" + ] +} \ No newline at end of file diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/vue/src/HeatmapVis/HeatmapVis.less b/files/plugin-HeatmapSessionRecording-5.2.4/vue/src/HeatmapVis/HeatmapVis.less new file mode 100644 index 0000000..746f9ab --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/vue/src/HeatmapVis/HeatmapVis.less @@ -0,0 +1,99 @@ +.heatmapVis { + + .aboveFoldLine { + height: 4px; + background: orange; + position: absolute; + + div { + color: orange; + margin-top: 4px; + margin-left: 4px; + } + } + + .iframeRecordingContainer { + position: relative; + } + + .numSamples { + position: relative; + top: -2px; + } + + .customIframeWidth { + display: inline-block; + margin-bottom: -3rem; + margin-right: -6rem; + + .matomo-form-field { + margin-top: -3.2rem; + margin-left: 5rem; + } + } + + .heatmapTile { + width: 100%; + } + + .heatmapWrapper { + position: absolute; + + #heatmapContainer { + position:absolute; + width: 1000px; + height: 1000px; + } + } + + .heatmapSelection { + margin-bottom: 16px; + white-space: nowrap; + } + + .legendOuter { + white-space: nowrap; + display:inline; + + h4 { + display: inline; + margin-left: 2.5rem; + margin-right: 10px; + } + } + + .legend-area { + display: inline; + .min { + margin-left: 8px; + margin-right: 8px; + } + .max { + margin-left: 16px; + } + } + + .btn-flat { + border: 1px solid #ccc; + border-radius: 0 !important; + margin-left: -1px; + } + + .visActive { + background-color: #ddd; + } + + #highlightDiv { + position: absolute; + background-color: #424242; + opacity: 0.5; + z-index: 999; + pointer-events: none; + } +} + +.heatmap-vis-title { + .title { + color: @theme-color-headline-alternative !important; + } +} \ No newline at end of file diff --git a/files/plugin-HeatmapSessionRecording-5.2.4/vue/src/HeatmapVis/HeatmapVis.vue b/files/plugin-HeatmapSessionRecording-5.2.4/vue/src/HeatmapVis/HeatmapVis.vue new file mode 100644 index 0000000..c184f59 --- /dev/null +++ b/files/plugin-HeatmapSessionRecording-5.2.4/vue/src/HeatmapVis/HeatmapVis.vue @@ -0,0 +1,1102 @@ + + +