diff --git a/bundles/PersonalizationBundle/.editorconfig b/bundles/PersonalizationBundle/.editorconfig deleted file mode 100644 index 724304db261..00000000000 --- a/bundles/PersonalizationBundle/.editorconfig +++ /dev/null @@ -1,21 +0,0 @@ -root = true - -[*] -charset = utf-8 -end_of_line = lf -indent_style = space -indent_size = 4 - -[*.php] -insert_final_newline = true -trim_trailing_whitespace = true - -[*.md] -trim_trailing_whitespace = false - -[*.yml] -indent_size = 4 - -[composer.json] -indent_style = space -indent_size = 2 \ No newline at end of file diff --git a/bundles/PersonalizationBundle/.gitattributes b/bundles/PersonalizationBundle/.gitattributes deleted file mode 100644 index 3569d634e76..00000000000 --- a/bundles/PersonalizationBundle/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ - -* -text diff --git a/bundles/PersonalizationBundle/.github/ci/files/.env b/bundles/PersonalizationBundle/.github/ci/files/.env deleted file mode 100644 index 007e4e91eb4..00000000000 --- a/bundles/PersonalizationBundle/.github/ci/files/.env +++ /dev/null @@ -1,2 +0,0 @@ -APP_ENV=test -APP_DEBUG=true \ No newline at end of file diff --git a/bundles/PersonalizationBundle/.github/ci/files/.my.cnf b/bundles/PersonalizationBundle/.github/ci/files/.my.cnf deleted file mode 100644 index fd25c4ef5de..00000000000 --- a/bundles/PersonalizationBundle/.github/ci/files/.my.cnf +++ /dev/null @@ -1,5 +0,0 @@ -[client] -host=127.0.0.1 -port=33006 -user=root -password= \ No newline at end of file diff --git a/bundles/PersonalizationBundle/.github/ci/files/bin/console b/bundles/PersonalizationBundle/.github/ci/files/bin/console deleted file mode 100644 index 232606f7c69..00000000000 --- a/bundles/PersonalizationBundle/.github/ci/files/bin/console +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env php -getParameterOption(['--env', '-e'], null, true)) { - putenv('APP_ENV='.$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env); -} - -if ($input->hasParameterOption('--no-debug', true)) { - putenv('APP_DEBUG='.$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0'); -} - -/** @var \Pimcore\Kernel $kernel */ -$kernel = \Pimcore\Bootstrap::startupCli(); -$application = new \Pimcore\Console\Application($kernel); -$application->run(); \ No newline at end of file diff --git a/bundles/PersonalizationBundle/.github/ci/files/config/bundles.php b/bundles/PersonalizationBundle/.github/ci/files/config/bundles.php deleted file mode 100644 index d4d63ced02c..00000000000 --- a/bundles/PersonalizationBundle/.github/ci/files/config/bundles.php +++ /dev/null @@ -1,5 +0,0 @@ - ['all' => true], -]; diff --git a/bundles/PersonalizationBundle/.github/ci/files/config/config.yaml b/bundles/PersonalizationBundle/.github/ci/files/config/config.yaml deleted file mode 100644 index 459522eee27..00000000000 --- a/bundles/PersonalizationBundle/.github/ci/files/config/config.yaml +++ /dev/null @@ -1,3 +0,0 @@ -imports: - - { resource: services.yaml } - - { resource: system.yaml } \ No newline at end of file diff --git a/bundles/PersonalizationBundle/.github/ci/files/config/packages/security.yaml b/bundles/PersonalizationBundle/.github/ci/files/config/packages/security.yaml deleted file mode 100644 index 8a02965d1af..00000000000 --- a/bundles/PersonalizationBundle/.github/ci/files/config/packages/security.yaml +++ /dev/null @@ -1,59 +0,0 @@ -security: - providers: - pimcore_admin: - id: Pimcore\Bundle\AdminBundle\Security\User\UserProvider - - firewalls: - dev: - pattern: ^/(_(profiler|wdt)|css|images|js)/ - security: false - - # Pimcore WebDAV HTTP basic // DO NOT CHANGE! - pimcore_admin_webdav: - pattern: ^/admin/asset/webdav - provider: pimcore_admin - http_basic: ~ - - # Pimcore admin form login // DO NOT CHANGE! - pimcore_admin: - pattern: ^/admin(/.*)?$ - user_checker: Pimcore\Bundle\AdminBundle\Security\User\UserChecker - provider: pimcore_admin - login_throttling: - max_attempts: 3 - interval: '5 minutes' - logout: - path: pimcore_admin_logout - target: pimcore_admin_login - form_login: - default_target_path: pimcore_admin_index - always_use_default_target_path: true - login_path: pimcore_admin_login - check_path: pimcore_admin_login_check - username_parameter: username - password_parameter: password - custom_authenticators: - - Pimcore\Bundle\AdminBundle\Security\Authenticator\AdminTokenAuthenticator - two_factor: - auth_form_path: /admin/login/2fa # Path or route name of the two-factor form - check_path: /admin/login/2fa-verify # Path or route name of the two-factor code check - default_target_path: /admin # Where to redirect by default after successful authentication - always_use_default_target_path: false # If it should always redirect to default_target_path - auth_code_parameter_name: _auth_code # Name of the parameter for the two-factor authentication code - trusted_parameter_name: _trusted # Name of the parameter for the trusted device option - multi_factor: false # If ALL active two-factor methods need to be fulfilled (multi-factor authentication) - - - access_control: - # Pimcore admin ACl // DO NOT CHANGE! - - { path: ^/admin/settings/display-custom-logo, roles: PUBLIC_ACCESS } - - { path: ^/admin/login/2fa-verify, roles: IS_AUTHENTICATED_2FA_IN_PROGRESS } - - { path: ^/admin/login/2fa-setup, roles: ROLE_PIMCORE_USER } - - { path: ^/admin/login/2fa, roles: IS_AUTHENTICATED_2FA_IN_PROGRESS } - - { path: ^/admin/login$, roles: PUBLIC_ACCESS } - - { path: ^/admin/login/(login|lostpassword|deeplink|csrf-token)$, roles: PUBLIC_ACCESS } - - { path: ^/admin, roles: ROLE_PIMCORE_USER } - - role_hierarchy: - # Pimcore admin // DO NOT CHANGE! - ROLE_PIMCORE_ADMIN: [ROLE_PIMCORE_USER] diff --git a/bundles/PersonalizationBundle/.github/ci/files/config/packages/test/config.yaml b/bundles/PersonalizationBundle/.github/ci/files/config/packages/test/config.yaml deleted file mode 100644 index 0afd2a0987f..00000000000 --- a/bundles/PersonalizationBundle/.github/ci/files/config/packages/test/config.yaml +++ /dev/null @@ -1,32 +0,0 @@ -imports: - - { resource: ../../config.yaml } - -# this cache is used during tests when setting up pimcore -framework: - cache: - pools: - pimcore.cache.pool: - public: true - tags: true - default_lifetime: 31536000 # 1 year - adapter: cache.adapter.array - - -doctrine: - dbal: - connections: - default: - url: '%pimcore_test.db.dsn%' - host: ~ - port: ~ - dbname: ~ - user: ~ - password: ~ - mapping_types: - enum: string - bit: boolean - -parameters: - pimcore_test.db.dsn: '%env(PIMCORE_TEST_DB_DSN)%' - env(PIMCORE_TEST_DB_DSN): ~ - pimcore.encryption.secret: 'def00000fc1e34a17a03e2ef85329325b0736a5941633f8062f6b0a1a20f416751af119256bea0abf83ac33ef656b3fff087e1ce71fa6b8810d7f854fe2781f3fe4507f6' \ No newline at end of file diff --git a/bundles/PersonalizationBundle/.github/ci/files/config/services.yaml b/bundles/PersonalizationBundle/.github/ci/files/config/services.yaml deleted file mode 100644 index cbcba33df6e..00000000000 --- a/bundles/PersonalizationBundle/.github/ci/files/config/services.yaml +++ /dev/null @@ -1,31 +0,0 @@ -parameters: - secret: ThisTokenIsNotSoSecretChangeIt - -services: - # default configuration for services in *this* file - _defaults: - # automatically injects dependencies in your services - autowire: true - # automatically registers your services as commands, event subscribers, etc. - autoconfigure: true - # this means you cannot fetch services directly from the container via $container->get() - # if you need to do this, you can override this setting on individual services - public: true - - # Example custom templating helper - # AppBundle\Templating\Helper\Example: - # # templating helpers need to be public as they - # # are fetched from the container on demand - # public: true - # tags: - # - { name: templating.helper, alias: fooBar } - - # Example event listener for objects - # AppBundle\EventListener\TestListener: - # tags: - # - { name: kernel.event_listener, event: pimcore.dataobject.preUpdate, method: onObjectPreUpdate } - - Pimcore\PersonalizationBundle\Installer: - public: true - arguments: - $bundle: "@=service('kernel').getBundle('PimcorePersonalizationBundle')" \ No newline at end of file diff --git a/bundles/PersonalizationBundle/.github/ci/files/config/system.yaml b/bundles/PersonalizationBundle/.github/ci/files/config/system.yaml deleted file mode 100644 index 35240dd79cd..00000000000 --- a/bundles/PersonalizationBundle/.github/ci/files/config/system.yaml +++ /dev/null @@ -1,76 +0,0 @@ -pimcore: - general: - timezone: Europe/Berlin - path_variable: '' - domain: pimcore-test.dev - redirect_to_maindomain: false - language: en - valid_languages: - - en - - de - fallback_languages: - en: '' - de: '' - default_language: '' - disable_usage_statistics: false - documents: - versions: - days: null - steps: 10 - error_pages: - default: /error - allow_trailing_slash: 'no' - generate_preview: true - objects: - versions: - days: null - steps: 10 - assets: - versions: - days: null - steps: 10 - icc_rgb_profile: '' - icc_cmyk_profile: '' - hide_edit_image: false - disable_tree_preview: false - full_page_cache: - enabled: false - lifetime: null - exclude_patterns: '' - exclude_cookie: '' - httpclient: - adapter: Socket - proxy_host: '' - proxy_port: '' - proxy_user: '' - proxy_pass: '' - email: - sender: - name: pimcore - email: pimcore@example.com - return: - name: pimcore - email: pimcore@example.com - debug: - email_addresses: '' - newsletter: - sender: - name: '' - email: '' - return: - name: '' - email: '' - debug: null - use_specific: true - applicationlog: - mail_notification: - send_log_summary: false - filter_priority: null - mail_receiver: '' - archive_treshold: '30' - archive_alternative_database: '' -pimcore_admin: - branding: - color_login_screen: '' - color_admin_interface: '' - login_screen_custom_image: '' diff --git a/bundles/PersonalizationBundle/.github/ci/files/kernel/Kernel.php b/bundles/PersonalizationBundle/.github/ci/files/kernel/Kernel.php deleted file mode 100644 index c801b2773c5..00000000000 --- a/bundles/PersonalizationBundle/.github/ci/files/kernel/Kernel.php +++ /dev/null @@ -1,22 +0,0 @@ - - - Options -MultiViews - - - - - - -# mime types - -AddType video/mp4 .mp4 - -AddType video/webm .webm - -AddType image/jpeg .pjpeg - - - - -Options +SymLinksIfOwnerMatch - - - - -# Use UTF-8 encoding for anything served text/plain or text/html - -AddDefaultCharset utf-8 - - - - -RewriteEngine On - - - - - - - - - Header always unset X-Content-Type-Options - - - - - - - - -# Determine the RewriteBase automatically and set it as environment variable. - -# If you are using Apache aliases to do mass virtual hosting or installed the - -# project in a subdirectory, the base path will be prepended to allow proper - -# resolution of the app.php file and to redirect to the correct URI. It will - -# work in environments without path prefix as well, providing a safe, one-size - -# fits all solution. But as you do not need it in this case, you can comment - -# the following 2 lines to eliminate the overhead. - -RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$ - -RewriteRule ^(.*) - [E=BASE:%1] - - - - -# Sets the HTTP_AUTHORIZATION header removed by Apache - -RewriteCond %{HTTP:Authorization} . - -RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] - - - - -# Redirect to URI without front controller to prevent duplicate content - -# (with and without `/app.php`). Only do this redirect on the initial - -# rewrite by Apache and not on subsequent cycles. Otherwise we would get an - -# endless redirect loop (request -> rewrite to front controller -> - -# redirect -> request -> ...). - -# So in case you get a "too many redirects" error or you always get redirected - -# to the start page because your Apache does not expose the REDIRECT_STATUS - -# environment variable, you have 2 choices: - -# - disable this feature by commenting the following 2 lines or - -# - use Apache >= 2.3.9 and replace all L flags by END flags and remove the - -# following RewriteCond (best solution) - -RewriteCond %{ENV:REDIRECT_STATUS} ^$ - -RewriteRule ^app\.php(?:/(.*)|$) %{ENV:BASE}/$1 [R=301,L] - - - - - - - RewriteCond %{REQUEST_URI} ^/(fpm|server)-(info|status|ping) - - RewriteRule . - [L] - - - - - - -# restrict access to dotfiles - -RewriteCond %{REQUEST_FILENAME} -d [OR] - -RewriteCond %{REQUEST_FILENAME} -l [OR] - -RewriteCond %{REQUEST_FILENAME} -f - -RewriteRule /\.|^\.(?!well-known/) - [F,L] - - - - -# ASSETS: check if request method is GET (because of WebDAV) and if the requested file (asset) exists on the filesystem, if both match, deliver the asset directly - -RewriteCond %{REQUEST_METHOD} ^(GET|HEAD) - -RewriteCond %{DOCUMENT_ROOT}/var/assets%{REQUEST_URI} -f - -RewriteRule ^(.*)$ /var/assets%{REQUEST_URI} [PT,L] - - - - -# Thumbnails - -RewriteCond %{REQUEST_URI} .*/(image|video)-thumb__[\d]+__.* - -RewriteCond %{DOCUMENT_ROOT}/var/tmp/%1-thumbnails%{REQUEST_URI} -f - -RewriteRule ^(.*)$ /var/tmp/%1-thumbnails%{REQUEST_URI} [PT,L] - - - - -# cache-buster rule for scripts & stylesheets embedded using view helpers - -RewriteRule ^cache-buster\-[\d]+/(.*) $1 [PT,L] - - - - -# If the requested filename exists, simply serve it. - -# We only want to let Apache serve files and not directories. - -RewriteCond %{REQUEST_FILENAME} -f - -RewriteRule ^ - [L] - - - - -# Rewrite all other queries to the front controller. - -RewriteRule ^ %{ENV:BASE}/index_test.php [L] - - - - - - - - - - - - - -########################################## - -### OPTIONAL PERFORMANCE OPTIMIZATIONS ### - -########################################## - - - - - - - # Force compression for mangled headers. - - # http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping - - - - - - SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding - - RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding - - - - - - - - - # Compress all output labeled with one of the following MIME-types - - # (for Apache versions below 2.3.7, you don't need to enable `mod_filter` - - # and can remove the `` and `` lines - - # as `AddOutputFilterByType` is still in the core directives). - - - - AddOutputFilterByType DEFLATE application/atom+xml application/javascript application/json \ - - application/vnd.ms-fontobject application/x-font-ttf application/rss+xml \ - - application/x-web-app-manifest+json application/xhtml+xml \ - - application/xml font/opentype image/svg+xml image/x-icon \ - - text/css text/html text/plain text/x-component text/xml text/javascript - - - - - - - - - - - ExpiresActive on - - ExpiresDefault "access plus 31536000 seconds" - - - - - # specific overrides - - #ExpiresByType text/css "access plus 1 year" - - - - - - - - - # pimcore mod_pagespeed integration - - # pimcore automatically disables mod_pagespeed in the following situations: debug-mode on, /admin, preview, editmode, ... - - # if you want to disable pagespeed for specific actions in pimcore you can use $this->disableBrowserCache() in your action - - RewriteCond %{REQUEST_URI} ^/(mod_)?pagespeed_(statistics|message|console|beacon|admin|global_admin) - - RewriteRule . - [L] - - - - - ModPagespeed Off - - AddOutputFilterByType MOD_PAGESPEED_OUTPUT_FILTER text/html - - ModPagespeedModifyCachingHeaders off - - ModPagespeedRewriteLevel PassThrough - - # low risk filters - - ModPagespeedEnableFilters remove_comments,recompress_images - - # low and moderate filters, recommended filters, but can cause problems - - ModPagespeedEnableFilters lazyload_images,extend_cache_images,inline_preview_images,sprite_images - - ModPagespeedEnableFilters combine_css,rewrite_css,move_css_to_head,flatten_css_imports,extend_cache_css,prioritize_critical_css - - ModPagespeedEnableFilters extend_cache_scripts,combine_javascript,canonicalize_javascript_libraries,rewrite_javascript - - # high risk - - #ModPagespeedEnableFilters defer_javascript,local_storage_cache - - \ No newline at end of file diff --git a/bundles/PersonalizationBundle/.github/ci/files/public/index_test.php b/bundles/PersonalizationBundle/.github/ci/files/public/index_test.php deleted file mode 100644 index 5ade43af06f..00000000000 --- a/bundles/PersonalizationBundle/.github/ci/files/public/index_test.php +++ /dev/null @@ -1,41 +0,0 @@ -handle ($request); -$response->send (); - -$kernel->terminate ($request, $response); diff --git a/bundles/PersonalizationBundle/.github/ci/files/templates/.gitkeep b/bundles/PersonalizationBundle/.github/ci/files/templates/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/bundles/PersonalizationBundle/.github/ci/scripts/setup-pimcore-environment.sh b/bundles/PersonalizationBundle/.github/ci/scripts/setup-pimcore-environment.sh deleted file mode 100755 index d22d2bce43d..00000000000 --- a/bundles/PersonalizationBundle/.github/ci/scripts/setup-pimcore-environment.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -set -eu - -#cp -rv .github/ci/files/var . -mkdir -p var/config -mkdir -p bin - -cp .github/ci/files/.env . -cp -r .github/ci/files/config/. config -cp -r .github/ci/files/templates/. templates -cp -r .github/ci/files/bin/console bin/console -chmod 755 bin/console -cp -r .github/ci/files/kernel/. kernel -cp -r .github/ci/files/public/. public \ No newline at end of file diff --git a/bundles/PersonalizationBundle/.gitignore b/bundles/PersonalizationBundle/.gitignore deleted file mode 100644 index de789b9fea0..00000000000 --- a/bundles/PersonalizationBundle/.gitignore +++ /dev/null @@ -1,68 +0,0 @@ -.DS_Store -Thumbs.db -*.log - -# symfony default -/.web-server-pid -/app/config/parameters.yml -/build/ -/phpunit.xml -/web/bundles/ - -# local config -!/app/config/local -/app/config/local/* -!app/config/local/.gitkeep - -# pimcore legacy (remove this for your own development) -!/legacy -/legacy/* -!legacy/.gitkeep -!legacy/bundle - -/var/* -!/var/.gitkeep -!/var/classes/ -/var/classes/DataObject - -!/var/config -/var/config/system.php -/var/config/debug-mode.php -/var/config/maintenance.php - -# project specific recommendations -/var/config/tag-manager.php -/var/config/reports.php - - -/web/var/ - -# PHP-CS-Fixer -/.php_cs -/.php_cs.cache - -# composer -/composer.lock -!/vendor -/vendor/* -!/vendor/.gitkeep - -# PhpStorm / IDEA -.idea -.idea_modules -# NetBeans -nbproject - -node_modules/ - -# codeception (only stage *.dist.yml config files) -/codeception.yml -/pimcore/codeception.yml -/pimcore/tests/*.suite.yml -/pimcore/tests/_output/* -/pimcore/tests/_support/_generated/* - -# keep legacy paths ignored for easier migration -/plugins/ -/tools/ -/website/ \ No newline at end of file diff --git a/bundles/PersonalizationBundle/.php_cs.dist b/bundles/PersonalizationBundle/.php_cs.dist deleted file mode 100644 index 7eb28332c11..00000000000 --- a/bundles/PersonalizationBundle/.php_cs.dist +++ /dev/null @@ -1,71 +0,0 @@ -in([__DIR__ . '/src']) - - // do not fix views - ->notName('*.html.php'); - -return PhpCsFixer\Config::create() - ->setRules([ - '@PSR1' => true, - '@PSR2' => true, - 'array_syntax' => ['syntax' => 'short'], - - 'header_comment' => [ - 'commentType' => 'PHPDoc', - 'header' => 'Pimcore' . PHP_EOL . PHP_EOL . - 'This source file is available under two different licenses:' . PHP_EOL . - '- GNU General Public License version 3 (GPLv3)' . PHP_EOL . - '- Pimcore Commercial License (PCL)' . PHP_EOL . - 'Full copyright and license information is available in' . PHP_EOL . - 'LICENSE.md which is distributed with this source code.' . PHP_EOL . - PHP_EOL . - ' @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)' . PHP_EOL . - ' @license http://www.pimcore.org/license GPLv3 and PCL' - ], - - // keep aligned = and => operators as they are: do not force aligning, but do not remove it - 'binary_operator_spaces' => ['align_double_arrow' => null, 'align_equals' => null], - - 'blank_line_before_return' => true, - 'encoding' => true, - 'function_typehint_space' => true, - 'hash_to_slash_comment' => true, - 'lowercase_cast' => true, - 'magic_constant_casing' => true, - 'method_argument_space' => ['ensure_fully_multiline' => false], - 'method_separation' => true, - 'native_function_casing' => true, - 'no_blank_lines_after_class_opening' => true, - 'no_blank_lines_after_phpdoc' => true, - 'no_empty_comment' => true, - 'no_empty_phpdoc' => true, - 'no_empty_statement' => true, - 'no_extra_consecutive_blank_lines' => true, - 'no_leading_import_slash' => true, - 'no_leading_namespace_whitespace' => true, - 'no_short_bool_cast' => true, - 'no_spaces_around_offset' => true, - 'no_unneeded_control_parentheses' => true, - 'no_unused_imports' => true, - 'no_whitespace_before_comma_in_array' => true, - 'no_whitespace_in_blank_line' => true, - 'object_operator_without_whitespace' => true, - 'ordered_imports' => true, - 'phpdoc_indent' => true, - 'phpdoc_no_useless_inheritdoc' => true, - 'phpdoc_scalar' => true, - 'phpdoc_separation' => true, - 'phpdoc_single_line_var_spacing' => true, - 'return_type_declaration' => true, - 'self_accessor' => true, - 'short_scalar_cast' => true, - 'single_blank_line_before_namespace' => true, - 'single_quote' => true, - 'space_after_semicolon' => true, - 'standardize_not_equals' => true, - 'ternary_operator_spaces' => true, - 'whitespace_after_comma_in_array' => true, - ]) - ->setFinder($finder); \ No newline at end of file diff --git a/bundles/PersonalizationBundle/LICENSE.md b/bundles/PersonalizationBundle/LICENSE.md deleted file mode 100644 index 4dd4db4dfea..00000000000 --- a/bundles/PersonalizationBundle/LICENSE.md +++ /dev/null @@ -1,36 +0,0 @@ -# License -Copyright (C) Pimcore GmbH - -This software is available under two different licenses: -* GNU General Public License version 3 (GPLv3) as Pimcore Community Edition -* Pimcore Commercial License (PCL) - -The default Pimcore license, without a valid Pimcore Commercial License agreement, is the Open-Source GPLv3 license. - -## GNU General Public License version 3 (GPLv3) -If you decide to choose the GPLv3 license, you must comply with the following terms: - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -## Pimcore Commercial License (PCL) -Alternatively, commercial and supported versions of the program - also known as -Commercial Distributions - must be used in accordance with the terms and conditions -contained in a separate written agreement between you and Pimcore GmbH. -For more information about the Pimcore Commercial License (PCL) please contact info@pimcore.com. - - - -Please see also (files in this directory): -- [Licensing FAQ - license-faq.md](https://github.com/pimcore/pimcore/blob/master/license-faq.md) -- [GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - gpl-3.0.txt](gpl-3.0.txt) \ No newline at end of file diff --git a/bundles/PersonalizationBundle/README.md b/bundles/PersonalizationBundle/README.md deleted file mode 100644 index ae8a9f5664b..00000000000 --- a/bundles/PersonalizationBundle/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Behavioral Targeting and Personalization - -Pimcore provides a very powerful integrated behavioral targeting and personalization engine. With this toolkit it is -possible to profile visitors based on their behavior, assign target groups to them and provide personalized content to -these target groups. This makes it possible to show contextual, relevant, and personalized content to your customers. - -Before starting with Personalization you should know about following Pimcore concepts: - -* [Target Group](./doc/User_Documentation/01_Concepts.md#page_Target-Group) -* [Global Targeting Rule](./doc/User_Documentation/01_Concepts.md#page_Global-Targeting-Rule) -* [Personalized Document Content](./doc/User_Documentation/01_Concepts.md#page_Personalized-Document-Content) - - -After reading everything about the base concepts, you are -[ready to get started with personalizing content](./doc/User_Documentation/03_How_to_Personalize_Content/README.md). - - - > For details on how to extend Pimcores targeting engine and use targeting information in custom modules - > see our [Dev Docs](./doc/README.md) diff --git a/bundles/PersonalizationBundle/SECURITY.md b/bundles/PersonalizationBundle/SECURITY.md deleted file mode 100644 index 53173b7ce02..00000000000 --- a/bundles/PersonalizationBundle/SECURITY.md +++ /dev/null @@ -1,20 +0,0 @@ -# Security Policy - -## Reporting a Vulnerability - -If you think that you have found a security issue, -don’t use the bug tracker and don’t publish it publicly. -Instead, all security issues must be reported via 📫 to [security-issue@pimcore.com](mailto:security-issue@pimcore.com). - - -## Resolving Process -Every submitted security issue is handled with top priority by following these steps: - -1. Confirm the vulnerability -2. Determine the severity -3. Contact reporter -4. Work on a patch -5. Get a CVE identification number (may be done by the reporter or a security service provider) -6. Patch reviewing -7. Tagging a new release for supported versions -8. Publish security announcement \ No newline at end of file diff --git a/bundles/PersonalizationBundle/codeception.dist.yml b/bundles/PersonalizationBundle/codeception.dist.yml deleted file mode 100644 index e3a88caa27c..00000000000 --- a/bundles/PersonalizationBundle/codeception.dist.yml +++ /dev/null @@ -1,17 +0,0 @@ -namespace: Pimcore\Bundle\PersonalizationBundle\Tests -actor_suffix: Tester -paths: - tests: tests - output: tests/_output - data: tests/Support/Data - support: tests/Support - envs: tests/_envs -settings: - colors: true - memory_limit: 1024M -bootstrap: _bootstrap.php -params: - - env -extensions: - enabled: - - Codeception\Extension\RunFailed \ No newline at end of file diff --git a/bundles/PersonalizationBundle/composer.json b/bundles/PersonalizationBundle/composer.json deleted file mode 100644 index 065817953a8..00000000000 --- a/bundles/PersonalizationBundle/composer.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "pimcore/personalization-bundle", - "license": "GPL-3.0+", - "type": "pimcore-bundle", - "description": "Pimcore Personalization Bundle", - "config": { - "sort-packages": true, - "preferred-install": { - "pimcore/pimcore": "source", - "*": "dist" - } - }, - "prefer-stable": true, - "minimum-stability": "dev", - "require": { - "pimcore/pimcore": "^11.0", - "geoip2/geoip2": "^2.9", - "symfony/stopwatch": "^6.2" - }, - "require-dev": { - "phpstan/phpstan": "^1.9", - "codeception/codeception": "^5.0.3", - "codeception/phpunit-wrapper": "^9" - }, - "autoload": { - "psr-4": { - "Pimcore\\Bundle\\PersonalizationBundle\\": "src/", - "Pimcore\\Model\\DataObject\\": "src/Pimcore/Model/DataObject" - } - }, - "autoload-dev": { - "psr-4": { - "Pimcore\\Bundle\\PersonalizationBundle\\Tests\\": "tests" - }, - "files": [ - "kernel/Kernel.php" - ] - }, - "extra": { - "pimcore": { - "bundles": [ - "Pimcore\\Bundle\\PersonalizationBundle\\PimcorePersonalizationBundle" - ] - } - } -} \ No newline at end of file diff --git a/bundles/PersonalizationBundle/config/pimcore/config.yaml b/bundles/PersonalizationBundle/config/pimcore/config.yaml deleted file mode 100644 index 74f488660fa..00000000000 --- a/bundles/PersonalizationBundle/config/pimcore/config.yaml +++ /dev/null @@ -1,2 +0,0 @@ -imports: - - { resource: default.yaml } \ No newline at end of file diff --git a/bundles/PersonalizationBundle/config/pimcore/default.yaml b/bundles/PersonalizationBundle/config/pimcore/default.yaml deleted file mode 100644 index 0eae9f8eeb7..00000000000 --- a/bundles/PersonalizationBundle/config/pimcore/default.yaml +++ /dev/null @@ -1,39 +0,0 @@ -pimcore_personalization: - targeting: - data_providers: - device: Pimcore\Bundle\PersonalizationBundle\Targeting\DataProvider\Device - geoip: Pimcore\Bundle\PersonalizationBundle\Targeting\DataProvider\GeoIp - geolocation: Pimcore\Bundle\PersonalizationBundle\Targeting\DataProvider\GeoLocation - targeting_storage: Pimcore\Bundle\PersonalizationBundle\Targeting\DataProvider\TargetingStorage - visited_pages_counter: Pimcore\Bundle\PersonalizationBundle\Targeting\DataProvider\VisitedPagesCounter - conditions: - browser: Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\Browser - country: Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\Country - geopoint: Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\GeoPoint - hardwareplatform: Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\HardwarePlatform - language: Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\Language - operatingsystem: Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\OperatingSystem - referringsite: Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\ReferringSite - searchengine: Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\SearchEngine - target_group: Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\TargetGroup - timeonsite: Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\TimeOnSite - url: Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\Url - visitedpagesbefore: Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\VisitedPagesBefore - action_handlers: - assign_target_group: Pimcore\Bundle\PersonalizationBundle\Targeting\ActionHandler\AssignTargetGroup - codesnippet: Pimcore\Bundle\PersonalizationBundle\Targeting\ActionHandler\CodeSnippet - redirect: Pimcore\Bundle\PersonalizationBundle\Targeting\ActionHandler\Redirect -pimcore: - documents: - type_definitions: - map: - snippet: - class: Pimcore\Bundle\PersonalizationBundle\Model\Document\Snippet - page: - class: Pimcore\Bundle\PersonalizationBundle\Model\Document\Page - objects: - class_definitions: - data: - map: - targetGroup: Pimcore\Model\DataObject\ClassDefinition\Data\TargetGroup - targetGroupMultiselect: Pimcore\Model\DataObject\ClassDefinition\Data\TargetGroupMultiselect diff --git a/bundles/PersonalizationBundle/config/pimcore/routing.yaml b/bundles/PersonalizationBundle/config/pimcore/routing.yaml deleted file mode 100644 index 2fc5eb624cc..00000000000 --- a/bundles/PersonalizationBundle/config/pimcore/routing.yaml +++ /dev/null @@ -1,6 +0,0 @@ -_pimcore_bundle_personalization_backend: - resource: "../../src/Controller/" - type: annotation - prefix: /admin/bundle/personalization - options: - expose: true \ No newline at end of file diff --git a/bundles/PersonalizationBundle/config/services.yaml b/bundles/PersonalizationBundle/config/services.yaml deleted file mode 100644 index b772302ea04..00000000000 --- a/bundles/PersonalizationBundle/config/services.yaml +++ /dev/null @@ -1,25 +0,0 @@ -services: - _defaults: - autowire: true - autoconfigure: true - public: false - - # - # INSTALLER - # - Pimcore\Bundle\PersonalizationBundle\Installer: - public: true - arguments: - $bundle: "@=service('kernel').getBundle('PimcorePersonalizationBundle')" - - # auto-register all controllers as services - Pimcore\Bundle\PersonalizationBundle\Controller\: - resource: '../src/Controller' - public: true - tags: [ 'controller.service_arguments' ] - - # Newsletter Adapter - pimcore.document.newsletter.factory.default: - class: Pimcore\Document\Newsletter\DefaultAddressSourceAdapterFactory - arguments: - - 'Pimcore\Bundle\PersonalizationBundle\Document\Newsletter\AddressSourceAdapter\DefaultAdapter' \ No newline at end of file diff --git a/bundles/PersonalizationBundle/config/targeting.yaml b/bundles/PersonalizationBundle/config/targeting.yaml deleted file mode 100644 index 0239d46347a..00000000000 --- a/bundles/PersonalizationBundle/config/targeting.yaml +++ /dev/null @@ -1,190 +0,0 @@ -services: - _defaults: - autowire: true - autoconfigure: true - public: false - - # - # TARGETING STORAGE - # - # The TargetingStorageInterface alias will be set to whatever service_id is configured - # in pimcore_personalization.targeting.service_id. The services below are just the core storages, but - # it's possible to define a custom service which can be configured via service_id. - # - - # Cookie - Pimcore\Bundle\PersonalizationBundle\Targeting\Storage\Cookie\JWTCookieSaveHandler: - arguments: - $secret: '%kernel.secret%' - - # NOTE: using this save handler is inherently insecure and can open vulnerabilities by injecting malicious data into the - # client cookie. Use only for testing! - Pimcore\Bundle\PersonalizationBundle\Targeting\Storage\Cookie\JsonCookieSaveHandler: ~ - - Pimcore\Bundle\PersonalizationBundle\Targeting\Storage\CookieStorage: - arguments: - $saveHandler: '@Pimcore\Bundle\PersonalizationBundle\Targeting\Storage\Cookie\JWTCookieSaveHandler' - - # Session Storage. If this is used, the pimcore_personalization.targeting.session.enabled entry - # must be set to true to load the session configurator. - Pimcore\Bundle\PersonalizationBundle\Targeting\Storage\SessionStorage: ~ - - # Database - Pimcore\Bundle\PersonalizationBundle\Targeting\Storage\DbStorage: ~ - - # Example for redis - # pimcore_personalization.targeting.storage.redis.connection: - # class: Credis_Client - # factory: [Pimcore\Storage\Redis\ConnectionFactory, createConnection] - # arguments: - # - { server: 127.0.0.1, database: 7 } - - # Pimcore\Bundle\PersonalizationBundle\Targeting\Storage\RedisStorage: - # arguments: - # $redis: '@pimcore_personalization.targeting.storage.redis.connection' - - # Example for fallback - # Pimcore\Bundle\PersonalizationBundle\Targeting\Storage\FallbackStorage: - # arguments: - # $primaryStorage: '@Pimcore\Bundle\PersonalizationBundle\Targeting\Storage\DbStorage' - # $fallbackStorage: '@Pimcore\Bundle\PersonalizationBundle\Targeting\Storage\CookieStorage' - - - # - # VISITOR INFO - # - - Pimcore\Bundle\PersonalizationBundle\Targeting\VisitorInfoStorageInterface: '@Pimcore\Bundle\PersonalizationBundle\Targeting\VisitorInfoStorage' - Pimcore\Bundle\PersonalizationBundle\Targeting\VisitorInfoStorage: ~ - Pimcore\Bundle\PersonalizationBundle\Targeting\VisitorInfoResolver: ~ - - - # - # DATA PROVIDERS - # - - GeoIp2\ProviderInterface: '@GeoIp2\Database\Reader' - GeoIp2\Database\Reader: - arguments: - $filename: '%pimcore.geoip.db_file%' - - Pimcore\Bundle\PersonalizationBundle\Targeting\DataLoaderInterface: '@Pimcore\Bundle\PersonalizationBundle\Targeting\DataLoader' - Pimcore\Bundle\PersonalizationBundle\Targeting\DataLoader: ~ - - Pimcore\Bundle\PersonalizationBundle\Targeting\DataProvider\GeoIp: - calls: - - [ setCache, [ '@Pimcore\Cache\Core\CoreCacheHandler' ] ] - - Pimcore\Bundle\PersonalizationBundle\Targeting\DataProvider\GeoLocation: ~ - - Pimcore\Bundle\PersonalizationBundle\Targeting\DataProvider\Device: - calls: - - [ setCache, [ '@Pimcore\Cache\Core\CoreCacheHandler' ] ] - - [ setCachePool, [ '@pimcore.cache.pool' ] ] - - Pimcore\Bundle\PersonalizationBundle\Targeting\DataProvider\TargetingStorage: ~ - - Pimcore\Bundle\PersonalizationBundle\Targeting\Service\VisitedPagesCounter: ~ - Pimcore\Bundle\PersonalizationBundle\Targeting\DataProvider\VisitedPagesCounter: ~ - - - # - # CONDITIONS - # - - Pimcore\Bundle\PersonalizationBundle\Targeting\ConditionFactoryInterface: '@Pimcore\Bundle\PersonalizationBundle\Targeting\ConditionFactory' - Pimcore\Bundle\PersonalizationBundle\Targeting\ConditionFactory: - arguments: - $conditions: '%pimcore_personalization.targeting.conditions%' - - pimcore_personalization.targeting.condition_matcher.expression_language: - class: Symfony\Component\ExpressionLanguage\ExpressionLanguage - arguments: - $cache: '@pimcore.cache.pool' - - Pimcore\Bundle\PersonalizationBundle\Targeting\ConditionMatcherInterface: '@Pimcore\Bundle\PersonalizationBundle\Targeting\ConditionMatcher' - Pimcore\Bundle\PersonalizationBundle\Targeting\ConditionMatcher: - arguments: - $expressionLanguage: '@pimcore_personalization.targeting.condition_matcher.expression_language' - - - # - # ACTION HANDLERS - # - - Pimcore\Bundle\PersonalizationBundle\Targeting\ActionHandler\ActionHandlerInterface: '@Pimcore\Bundle\PersonalizationBundle\Targeting\ActionHandler\DelegatingActionHandler' - Pimcore\Bundle\PersonalizationBundle\Targeting\ActionHandler\DelegatingActionHandler: ~ - Pimcore\Bundle\PersonalizationBundle\Targeting\ActionHandler\AssignTargetGroup: ~ - Pimcore\Bundle\PersonalizationBundle\Targeting\ActionHandler\Redirect: ~ - Pimcore\Bundle\PersonalizationBundle\Targeting\ActionHandler\CodeSnippet: ~ - - - # - # DOCUMENT HANDLING - # - - Pimcore\Bundle\PersonalizationBundle\Targeting\Document\DocumentTargetingConfigurator: - public: true - - - # - # TARGETING CODE - # - - Pimcore\Bundle\PersonalizationBundle\Targeting\Code\TargetingCodeGenerator: ~ - - # - # TOOLBAR AND PROFILER - # - - Pimcore\Bundle\PersonalizationBundle\Targeting\Debug\TargetingDataCollector: ~ - - Pimcore\Bundle\PersonalizationBundle\Targeting\Debug\OverrideHandler: - lazy: true - arguments: - # injected by TargetingOverrideHandlersPass - $overrideHandlers: '?@' - - Pimcore\Bundle\PersonalizationBundle\Targeting\Debug\Override\DocumentTargetingOverrideHandler: - tags: - - { name: pimcore_personalization.targeting.override_handler, priority: 500 } - - Pimcore\Bundle\PersonalizationBundle\Targeting\Debug\Override\LanguageOverrideHandler: - tags: - - { name: pimcore_personalization.targeting.override_handler, priority: 475 } - - Pimcore\Bundle\PersonalizationBundle\Targeting\Debug\Override\DeviceOverrideHandler: - tags: - - { name: pimcore_personalization.targeting.override_handler, priority: 450 } - - Pimcore\Bundle\PersonalizationBundle\Targeting\Debug\Override\LocationOverrideHandler: - tags: - - { name: pimcore_personalization.targeting.override_handler, priority: 425 } - - Pimcore\Bundle\CoreBundle\EventListener\Frontend\FullPageCacheListener: - class: Pimcore\Bundle\PersonalizationBundle\Targeting\EventListener\TargetingFullPageCacheListener - public: true - tags: - - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 120 } - - { name: kernel.event_listener, event: kernel.response, method: onKernelResponse, priority: -120 } - - { name: kernel.event_listener, event: kernel.response, method: stopPropagationCheck, priority: 100 } - - Pimcore\Bundle\PersonalizationBundle\DataCollector\PimcoreTargetingDataCollector: - tags: - - name: data_collector - template: '@PimcorePersonalization/Profiler/targeting_data_collector.html.twig' - id: 'pimcore_targeting' - priority: 312 - - # - # Event Listeners - # - - Pimcore\Bundle\PersonalizationBundle\EventListener\IndexSettingsListener: - tags: - - { name: kernel.event_listener, event: pimcore.admin.indexAction.settings, method: indexSettings } - - Pimcore\Bundle\PersonalizationBundle\Targeting\EventListener\Frontend\TargetingElementListener: ~ - Pimcore\Bundle\PersonalizationBundle\Targeting\EventListener\Frontend\TargetingDocumentRendererListener: ~ - Pimcore\Bundle\PersonalizationBundle\Targeting\EventListener\RenderletListener: ~ - diff --git a/bundles/PersonalizationBundle/config/targeting/listeners.yaml b/bundles/PersonalizationBundle/config/targeting/listeners.yaml deleted file mode 100644 index 3a50dac60f8..00000000000 --- a/bundles/PersonalizationBundle/config/targeting/listeners.yaml +++ /dev/null @@ -1,14 +0,0 @@ -services: - _defaults: - autowire: true - autoconfigure: true - public: false - - Pimcore\Bundle\PersonalizationBundle\Targeting\EventListener\TargetingListener: ~ - Pimcore\Bundle\PersonalizationBundle\Targeting\EventListener\DocumentTargetGroupListener: ~ - Pimcore\Bundle\PersonalizationBundle\Targeting\EventListener\Frontend\FullPageCacheCookieCleanupListener: ~ - Pimcore\Bundle\PersonalizationBundle\Targeting\EventListener\VisitedPagesCountListener: ~ - Pimcore\Bundle\PersonalizationBundle\Targeting\EventListener\ToolbarListener: ~ - Pimcore\Bundle\PersonalizationBundle\Targeting\EventListener\TargetingSessionBagListener: ~ - - diff --git a/bundles/PersonalizationBundle/config/targeting/services.yaml b/bundles/PersonalizationBundle/config/targeting/services.yaml deleted file mode 100644 index f26f681c88a..00000000000 --- a/bundles/PersonalizationBundle/config/targeting/services.yaml +++ /dev/null @@ -1,10 +0,0 @@ -services: - _defaults: - public: false - autowire: true - autoconfigure: true - - - - Pimcore\Bundle\PersonalizationBundle\Targeting\Maintenance\TargetingStorageTask: ~ - Pimcore\Bundle\PersonalizationBundle\Targeting\Service\TargetingEnableService: ~ diff --git a/bundles/PersonalizationBundle/doc/Development_Documentation/01_Visitor_Info.md b/bundles/PersonalizationBundle/doc/Development_Documentation/01_Visitor_Info.md deleted file mode 100644 index 8a298147251..00000000000 --- a/bundles/PersonalizationBundle/doc/Development_Documentation/01_Visitor_Info.md +++ /dev/null @@ -1,126 +0,0 @@ -# Visitor Info - -The `VisitorInfo` is the central data object which is passed to every part of the targeting system. A new `VisitorInfo` -is created for every request and enriched with data throughout the matching process. Starting from an empty object, data -will be added to the `VisitorInfo` from several components, making it hold all relevant targeting data for the current -request. - -## Visitor ID - -To identify returning visitors, the engine needs some kind of unique identificator which will be assigned to each visitor. -This identificator, internally referred to as `visitor ID`, is expected to be generated or set by the browser and to be -stored as a cookie. When creating a `VisitorInfo` the targeting engine tries to load a visitor ID from the `_pc_vis` cookie. -This ID is (by default) generated by the frontend on event: - -* when loading a site with targeting enabled and no visitor ID was previously set, a random string is generated - -If you want to manually set the visitor ID in your frontend code you can do so with the following call which is exposed -by the targeting JS implementation: - -```js -_ptg.api.setVisitorId('my-custom-visitor-id'); -``` - -The visitor ID will be used for every action where a unique identificator is needed, e.g. when identifying returning visitors -or when persisting data for a specific visitor to a targeting storage. - -Upon setting a visitor ID, it is stored in 2 places of the user's browser: - -* a `_pc_vis` cookie -* a `_ptg.user` local storage entry. This entry contains some persistent data about the user (e.g. an activity log and a list - of previous visitor IDs) which may be used in later enhancements of the targeting framework. If the visitor ID cookie - is missing but the local storage entry contains an ID that ID will be used to write a new cookie. - - -## Accessing the Visitor Info in your code - -Similar to the security token in Symfony's security system, you can fetch the current `VisitorInfo` from a `VisitorInfoStorage`. -This storage is defined as service an can simply be injected into your services or used as controller action argument when -your controllers are registered as service. You can just define the `VisitorInfoStorageInterface` as dependency and Symfony -and the service configuration take care of the rest. - -As an example, a sample service working with the `VisitorInfo`: - -```php -visitorInfoStorage = $visitorInfoStorage; - } - - public function getVisitorId(): ?string - { - // always check if there is a visitor info before trying to fetch it - if (!$this->visitorInfoStorage->hasVisitorInfo()) { - return null; - } - - $visitorInfo = $this->visitorInfoStorage->getVisitorInfo(); - - return $visitorInfo->getVisitorId(); - } -} - -``` - -And the matching service definition: - -```yaml -services: - _defaults: - autowire: true - autoconfigure: true - public: false - - # if using autowiring you're already set by just defining your service - the - # type hint against the VisitorInfoStorageInterface is enough to enable autowiring - App\Targeting\MyService: ~ - - # if you don't use autowiring you need to manually wire your dependency - App\Targeting\MyService: - arguments: - $visitorInfoStorage: '@Pimcore\Bundle\PersonalizationBundle\Targeting\VisitorInfoStorageInterface' -``` - -If your controllers are defined as services, you can make use of argument injection: - -```php - null - ]; - - // always check if there is a visitor info before trying to fetch it - if ($visitorInfoStorage->hasVisitorInfo()) { - $visitorInfo = $visitorInfoStorage->getVisitorInfo(); - - $data['visitorId'] = $visitorInfo->getVisitorId(); - } - - return new JsonResponse($data); - } -} -``` diff --git a/bundles/PersonalizationBundle/doc/Development_Documentation/03_Conditions.md b/bundles/PersonalizationBundle/doc/Development_Documentation/03_Conditions.md deleted file mode 100644 index 6f69a980697..00000000000 --- a/bundles/PersonalizationBundle/doc/Development_Documentation/03_Conditions.md +++ /dev/null @@ -1,229 +0,0 @@ -# Conditions - -Conditions are logical blocks which can be combined with other conditions inside a target rule. A condition is expected -to implement `match()` method which always returns a boolean. - -To implement a condition, you need to implement 2 parts: - -* A PHP class implementing the [`ConditionInterface`](https://github.com/pimcore/personalization-bundle/blob/main/src/Targeting/Condition/ConditionInterface.php). - Have a look at [existing implementations](https://github.com/pimcore/personalization-bundle/tree/main/src/Targeting/Condition) - to get an idea how to implement your own conditions. -* A frontend JS class defining the admin UI for your condition. You can have a look at [this bundle's conditions](https://github.com/pimcore/personalization-bundle/blob/main/public/js/settings/conditions.js) - for UI examples and at the [Customer Management Framework](https://github.com/pimcore/customer-data-framework/blob/master/src/Resources/public/js/pimcore/targeting/conditions.js) - as example for a third-party integration. - - -## Implementing a Condition - -As stated before, a condition needs to implement the [`ConditionInterface`](https://github.com/pimcore/personalization-bundle/blob/main/src/Targeting/Condition/ConditionInterface.php). -The most important method in the interface is the `match()` method which receives the current `VisitorInfo` instance and -is expected to return a boolean which indicates if the condition matches or not. - -As an example let's build a condition which matches if the current time of the day is later than the configured one. E.g. -if configured to `15:00`, the condition would start to match at `15:00` until midnight. - -Start by creating a condition class implementing the `ConditionInterface`. For simplicity's sake we just check the current -hour, not the full time. - -```php -hour = $hour; - } - - public static function fromConfig(array $config): self - { - $hour = $config['hour'] ?? null; - if (!empty($hour)) { - $hour = (int)$hour; - } - - // build an instance from the config as configured - // in the admin UI - return new self($hour); - } - - public function canMatch(): bool - { - // basic validation if the condition is able to match - return null !== $this->hour && $this->hour >= 0 && $this->hour <= 23; - } - - public function match(VisitorInfo $visitorInfo): bool - { - $hour = (int)(new \DateTime())->format('H'); - - return $hour >= $this->hour; - } -} -``` - -After implementing your condition, you need to register it to the system with the following configuration. The identifier -`timeoftheday` will later be used from your JS implementation, so make sure you choose a unique name and to reuse the same -name when implementing the JS class. - -```yaml -pimcore_personalization: - targeting: - conditions: - timeoftheday: App\Targeting\Condition\TimeOfTheDay -``` - - -### Building a Condition Instance - -When an instance of your condition is build, by default the [`ConditionFactory`](https://github.com/pimcore/personalization-bundle/blob/main/src/Targeting/ConditionFactory.php) -will call the static `fromConfig()` method with the data configured in the admin UI. Avoid injecting any services or -custom data into your condition and use the data provider system instead to add data to the `VisitorInfo`. However, if -you need more control over how your condition is built you can either: - -* overwrite the `ConditionFactory` service definition (not recommended) and implement your own logic instead of calling - `fromConfig()` -* or handle the `TargetingEvents::BUILD_CONDITION` event and set an instance of your condition on the event. The `BuildConditionEvent` - contains all the info you need to build a condition instance (type, class name, config data). If you set a condition on - the event via `setCondition`, the standard logic will be omitted and the event condition will be used. - - -## Condition Data - -If your condition needs any outside data, implement the [`DataProviderDependentInterface`](https://github.com/pimcore/personalization-bundle/blob/main/src/Targeting/DataProviderDependentInterface.php) -and define a list of data provider keys which need to be set on the `VisitorInfo` before matching. We'll enhance our `TimeOfTheDay` -condition on the [Data Providers](./05_Data_Providers.md) chapter. For further examples, you can take a look at [existing core conditions](https://github.com/pimcore/personalization-bundle/blob/main/src/Targeting/Condition). - - -## Condition Variables - -An important part are [variable conditions](https://github.com/pimcore/personalization-bundle/blob/main/src/Targeting/Condition/VariableConditionInterface.php) -which support the `session_with_variables` rule matching scope. A condition implementing this interface is expected to return -an array of the variables which led to match the condition in the `getMatchedVariables()` method. This data will be used -to determine if the rule was already applied with the exact same data. - -You should implement this interface whenever possible. To get started, you can use the [`AbstractVariableCondition`](https://github.com/pimcore/personalization-bundle/blob/main/src/Targeting/Condition/AbstractVariableCondition.php) -which contains helper methods to collect variable data. Make sure you build your data in a deterministic way (e.g. using -the same order of keys in an array structure or the same format when serializing data) as a hash of this data is used to -compare it to previous evaluations in order to decide if a rule needs to be applied. - -As example: the country condition sets the ISO country code which led to match as its data (based on GeoLocation). If a -rule is executed in the `session_with_variables` scope the the country condition is the only condition on that rule, it won't -be executed twice for the same resolved country. - -Taking our `TimeOfTheDay` condition, it can easily be enhanced to store variables. The variable we'll store will be the -resolved current hour. - -```php -format('H'); - - if ($hour >= $this->hour) { - $this->setMatchedVariable('hour', $hour); - - return true; - } - - return false; - } -} -``` - -## Admin UI - -To make your condition configurable, you need to create a JS class defining the admin interface for your condition. To do -so, create a class extending `pimcore.bundle.personalization.settings.condition.abstract` and register it to the system by calling -`pimcore.bundle.personalization.settings.conditions.register()`. - -Have a look at [this bundle's conditions](https://github.com/pimcore/personalization-bundle/blob/main/public/js/settings/conditions.js) -and the [Customer Management Framework](https://github.com/pimcore/customer-data-framework/blob/master/src/Resources/public/js/pimcore/targeting/conditions.js) -for examples. - -Start by adding a new JS file implementing the admin UI panel for your condition: - -```javascript -// /public/js/targeting/conditions.js - -(function () { - 'use strict'; - - pimcore.bundle.personalization.settings.conditions.register( - 'timeoftheday', - Class.create(pimcore.bundle.personalization.settings.condition.abstract, { - getName: function () { - return 'Time of the Day'; - }, - - getPanel: function (panel, data) { - var id = Ext.id(); - - var storeData = []; - for (var i = 0; i < 24; i++) { - storeData.push([i, i]); - } - - return new Ext.form.FormPanel({ - id: id, - forceLayout: true, - style: 'margin: 10px 0 0 0', - bodyStyle: 'padding: 10px 30px 10px 30px; min-height:40px;', - tbar: pimcore.bundle.personalization.settings.conditions.getTopBar(this, id, panel, data), - items: [ - { - name: 'hour', - fieldLabel: 'Hour', - xtype: 'combo', - store: storeData, - mode: 'local', - width: 300, - value: ('undefined' !== typeof data.hour) ? data.hour : 0, - editable: false, - triggerAction: 'all' - }, - { - xtype: 'displayfield', - hideLabel: true, - value: 'Matches if the current hour is >= the configured one.', - cls: 'pimcore_extra_label' - }, - { - xtype: 'hidden', - name: 'type', - value: 'timeoftheday' // the identifier chosen before when registering the PHP class - } - ] - }); - } - }) - ); -}()); -``` - -As soon as you [configured Pimcore to load the newly created file](../../20_Extending_Pimcore/13_Bundle_Developers_Guide/13_Loading_Admin_UI_Assets.md) -you should see your new condition in the list of available conditions: - -![Time of the Day Condition](../../img/targeting_custom_condition_timeoftheday.png) diff --git a/bundles/PersonalizationBundle/doc/Development_Documentation/05_Data_Providers.md b/bundles/PersonalizationBundle/doc/Development_Documentation/05_Data_Providers.md deleted file mode 100644 index f3320623187..00000000000 --- a/bundles/PersonalizationBundle/doc/Development_Documentation/05_Data_Providers.md +++ /dev/null @@ -1,132 +0,0 @@ -# Data Providers - -A data provider is a service implementing the [`DataProviderInterface`](https://github.com/pimcore/personalization-bundle/blob/main/src/Targeting/DataProvider/DataProviderInterface.php). -Components (e.g. conditions) which implement the [`DataProviderDependentInterface`](https://github.com/pimcore/personalization-bundle/blob/main/src/Targeting/DataProviderDependentInterface.php) -can define a set of data providers they depend on, triggering the data provider to load its data before the component -is used. - -A data provider does not directly return its value, but is expected to set it on the `VisitorInfo` instance instead. As -best practice, the core data providers expose their storage key as constant. This constant is used to store and retrieve -the data from the `VisitorInfo` storage. As example: the [GeoIP](https://github.com/pimcore/personalization-bundle/blob/main/src/Targeting/DataProvider/GeoIp.php) -data provider defines the [GeoIP::PROVIDER_KEY](https://github.com/pimcore/personalization-bundle/blob/main/src/Targeting/DataProvider/GeoIp.php#L28) -constant which is used when storing and retrieving the data. - -## Implementing a Data Provider - -A data provider is simply a class implementing the `DataProviderInterface` which is registered as service. Basically a -data provider can do anything, however the core data providers do the following: - -* They store their information on a storage key which is exposed as constant -* They always set their content key. If no data can be resolved (e.g. GeoIP is unable to resolve a location), `null` is set. -* Before loading data, core providers check if there already is an entry for the own storage key and abort loading if that - is the case. - -As an example let's assume the `DateTime` used in the `TimeOfTheDay` condition (as implemented on the [Conditions](./03_Conditions.md) -page) is more complex than a simple `new DateTime()`, i.e. because the date is fetched from a third party or involves -calculation logic. Instead of creating it inside the condition which does not have access to services we move it to a -reusable `DateTime` data provider which stores the current `DateTime` on the `VisitorInfo`. - -```php -has(self::PROVIDER_KEY)) { - // abort if there already is data for this provider - return; - } - - // assume creating the date is more complex (e.g. involves other services - // which are injected via DI) - $visitorInfo->set(self::PROVIDER_KEY, new \DateTimeImmutable()); - } -} -``` - -Next, register your new data provider as service: - -```yaml -services: - _defaults: - autowire: true - autoconfigure: true - public: false - - App\Targeting\DataProvider\DateTime: ~ -``` - -And register the provider to the targeting engine with its provider key: - -```yaml -pimcore_personalization: - targeting: - data_providers: - datetime: App\Targeting\DataProvider\DateTime -``` - - -## Consuming a Data Provider - -To consume a data provider, implement the `DataProviderDependentInterface` in your components and specify a list of data -providers to use. As an example, let's update the `TimeOfTheDay` condition to fetch the current `DateTime` from our new -provider: - -```php -get(DateTime::PROVIDER_KEY); - if (!$dateTime) { - // provider did not provide a valid date - nothing to match against - return false; - } - - $hour = (int)$dateTime->format('H'); - - if ($hour >= $this->hour) { - $this->setMatchedVariable('hour', $hour); - - return true; - } - - return false; - } -} -``` - -As you can see, instead of creating a new `DateTime` instance, the condition now expects an instance on the `DateTime::PROVIDER_KEY` -storage on the `VisitorInfo`. The targeting engine takes care of loading every provider the condition depends on before -starting to match. - -The `DataProviderDependentInterface` can not only be used from conditions, but also from action handlers and other -data providers (a data provider can depend on another data providers' data). diff --git a/bundles/PersonalizationBundle/doc/Development_Documentation/07_Action_Handlers.md b/bundles/PersonalizationBundle/doc/Development_Documentation/07_Action_Handlers.md deleted file mode 100644 index 129c6a09c8d..00000000000 --- a/bundles/PersonalizationBundle/doc/Development_Documentation/07_Action_Handlers.md +++ /dev/null @@ -1,171 +0,0 @@ -# Action Handlers - -After a targeting rule matched it executes one or more actions as configured in the admin UI. These actions are actually -executed by action handlers, which are services implementing the [`ActionHandlerInterface`](https://github.com/pimcore/personalization-bundle/blob/main/src/Targeting/ActionHandler/ActionHandlerInterface.php). - -As with conditions, an action handler consists of 2 parts: - -* A PHP class implementing the [`ActionHandlerInterface`](https://github.com/pimcore/personalization-bundle/blob/main/src/Targeting/ActionHandler/ActionHandlerInterface.php). - Have a look at [existing implementations](https://github.com/pimcore/personalization-bundle/tree/main/src/Targeting/Targeting/ActionHandler) - to get an idea how to implement your own action handlers. -* A frontend JS class defining the admin UI for your action handler as shown on the actions tab of a targeting rule. You - can have a look at [Pimcore's core actions](https://github.com/pimcore/personalization-bundle/blob/main/public/js/settings/actions.js) - for UI examples and at the [Customer Management Framework](https://github.com/pimcore/customer-data-framework/blob/master/src/Resources/public/js/pimcore/targeting/actions.js) - as example for a third-party integration. - - -## Implementing an Action Handler - -As example, we'll implement a simple `Log` action handler which will log each matched rule with a configured log level. -The action handler is a normal service which can depend on other services (as we do with the logger here). - -
-You can't rely on a visitor ID being set or a rule being passed. The visitor ID might be inexistent on the first -request to the site and action handlers might be executed from outside the matching engine without passing a rule. Always -check those values before using them! -
- -```php -logger = $logger; - } - - public function apply(VisitorInfo $visitorInfo, array $action, Rule $rule = null): void - { - $level = $action['level'] ?? 'info'; - - $this->logger->log( - $level, - 'Matched target rule {ruleName} for visitor ID {visitorID} with config {config}', - [ - 'visitorID' => $visitorInfo->hasVisitorId() ? $visitorInfo->getVisitorId() : '(no visitor ID)', - 'ruleName' => $rule ? $rule->getName() : '(no rule)', - 'config' => json_encode($action) - ] - ); - } -} -``` - -Next, register your action handler as service. **Note:** we're using autowiring here to make sure the logger is automatically -injected. If you don't use autowiring, enhance the service definition accordingly. - -```yaml -services: - _defaults: - autowire: true - autoconfigure: true - public: false - - App\Targeting\ActionHandler\Log: ~ -``` - -As last step, register the action handler to the targeting engine. Make sure the identifier is unique to your provider -as you'll need to use it when implementing the admin UI JS part. - -```yaml -pimcore_personalization: - targeting: - action_handlers: - log: App\Targeting\ActionHandler\Log -``` - -### Postponed Actions - -If your action handler needs to apply data in a later stage of the request/response cycle it can register an `action` on -the `VisitorInfo` which can be consumed later. Currently only the `response` action scope is defined which is executed -in the `onKernelResponse` event, but more action scopes might be added in the future. - -Have a look at the [CodeSnippet](https://github.com/pimcore/personalization-bundle/blob/main/src/Targeting/ActionHandler/CodeSnippet.php) -action handler as example. It registers an action via `$visitorInfo->addAction()` and implements the `ResponseTransformingActionHandlerInterface::transformResponse()` -which is called by the targeting engine for every action registered with the `response` scope. - - -## Admin UI - -To make your action handler appear in the admin UI, you need to create and register a JS class defining the admin interface -for your action. Create a class extending `pimcore.settings.targeting.action.abstract` and register it to the system by -calling `pimcore.settings.targeting.actions.register()`. - -Have a look at [this bundle's actions](https://github.com/pimcore/personalization-bundle/blob/main/public/js/settings/actions.js) -and the [Customer Management Framework](https://github.com/pimcore/customer-data-framework/blob/master/src/Resources/public/js/pimcore/targeting/actions.js) -for examples. - -Start by adding a new JS file implementing the admin UI panel for your action: - -```javascript -// public/js/targeting/actions.js - -(function () { - 'use strict'; - - pimcore.bundle.personalization.settings.actions.register( - 'log', - Class.create(pimcore.bundle.personalization.settings.action.abstract, { - getName: function () { - return 'Log'; - }, - - getPanel: function (panel, data) { - var id = Ext.id(); - - return new Ext.form.FormPanel({ - id: id, - forceLayout: true, - style: 'margin: 10px 0 0 0', - bodyStyle: 'padding: 10px 30px 10px 30px; min-height:40px;', - tbar: pimcore.bundle.personalization.settings.actions.getTopBar(this, id, panel), - items: [ - { - name: 'level', - fieldLabel: 'Level', - xtype: 'combo', - store: [ - ['debug', 'Debug'], - ['info', 'Info'], - ['notice', 'Notice'], - ['warning', 'Warning'], - ['error', 'Error'], - ['critical', 'Critical'], - ['alert', 'Alert'], - ['emergency', 'Emergency'] - ], - mode: 'local', - width: 300, - value: ('undefined' !== typeof data.level) ? data.level : 'info', - editable: false, - triggerAction: 'all' - }, - { - xtype: 'hidden', - name: 'type', - value: 'log' - } - ] - }); - } - }) - ); -}()); -``` - -As soon as you [configured Pimcore to load the newly created file](../../20_Extending_Pimcore/13_Bundle_Developers_Guide/13_Loading_Admin_UI_Assets.md) -you should see your new action in the list of available actions: - -![Log Action](../../img/targeting_custom_action_log.png) diff --git a/bundles/PersonalizationBundle/doc/Development_Documentation/09_Targeting_Storage.md b/bundles/PersonalizationBundle/doc/Development_Documentation/09_Targeting_Storage.md deleted file mode 100644 index 5d54d4fdb82..00000000000 --- a/bundles/PersonalizationBundle/doc/Development_Documentation/09_Targeting_Storage.md +++ /dev/null @@ -1,146 +0,0 @@ -# Targeting Storage - -[TOC] - -To persist data between requests, the targeting engine makes use of a targeting storage service which is responsible for -persisting the given data to a storage. In general, the storage always receives an instance of the `VisitorInfo` it should -store/retrieve data for and a `scope` which defines how data is handled. - -There are 2 fixed scopes which are used depending on what is needed: - -| Scope | Description | -|-----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `session` | Valid for the current session, expires afterwards. Depending on the implementation, a session is either defined by a timeout of inactivity or by natively being handled by the storage system. E.g. the DB and Redis storage handle expiration by expiring data after a certain amount of time while Session and Cookie storage just make use of the session and cookie lifetime. | -| `visitor` | Valid for the whole lifetime of the visitor. When a visitor returns with its unique ID, its data will still be usable while session data would have expired. | - - -## Configuring the Targeting Storage - -The targeting storage needs to be defined as service and its service ID needs to be configured with the following config -entry: - -```yaml -pimcore_personalization: - targeting: - storage_id: Pimcore\Bundle\PersonalizationBundle\Targeting\Storage\CookieStorage -``` - - -## Implement a Custom Targeting Storage - -Basically, a targeting storage is a class implementing the [`TargetingStorageInterface`](https://github.com/pimcore/personalization-bundle/blob/main/src/Targeting/Storage/TargetingStorageInterface.php) -which is registered as service. Details how to handle data varies heavily on the underlying storage, but you can take the -[core storages](https://github.com/pimcore/personalization-bundle/tree/blob/main/src/Targeting/Storage) as starting point. - - -## Core Storage Implementations - -Pimcore implements different storage engines which each has its pros and cons. In general it is recommended to start with -the default implementation - **JWT signed cookie** - and subsequently choose the engine which fits your requirements best. - -In the future more storage implementations are planned which combine features of multiple engines together, e.g. a storage -handling multiple backends which are selected depending on if the visitor already has a visitor ID or not. - - -### Cookie (default) - -Stores data in a cookie in the user's browser. Can either be used with a plaintext cookie or with a JWT signed one to make -sure the cookie data isn't being tampered with. The cookie storage delegates the actual cookie write/read operation to a -`CookieSaveHandler` which does the actual cookie work. Below is an overview of save handlers: - -| Handler | Description | Notes | -|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------| -| `JWT` (default) | Stores cookie data as JWT signed JSON using the `kernel.secret` parameter to sign and verify the data. This is done to make sure the data can't be altered on the client side to inject malicious data into the targeting engine. | | -| `JSON` | Stores cookie data as JSON string. | Use only for testing! | - -To change the save handler, override the [service definition](https://github.com/pimcore/personalization-bundle/blob/main/config/targeting.yaml#L24) -and set your own handler. - -
-Note that using plain text cookie data is inherently insecure and can open vulnerabilities by injecting malicious data into -the client cookie. Use only for testing! -
- -Default session scope timeout: 30 minutes. This is also enforced in the JWT handler by setting an expiration value for the -signed data to the same timeout as the cookie. This means even if somebody changes the cookie lifetime on the client side -the data will still expire when loaded on the backend. - -Pros - -* Easy to use as no additional config is needed and it can store data without needing a visitor ID -* Fast and easy to debug - -Cons - -* Cookie size is limited and bloats requests - can only be used when the amount of generated targeting data is limited and - implementations take care of keeping data to a minimum. -* Inherently insecure when used with an unsigned cookie. - - -### Db - -Stores data in the database. - -Default session scope timeout: 30 minutes - -Pros - -* Easy to use as no additional config is needed - -Cons - -* Can only store data when a visitor ID is present as the ID is part of the primary key -* DB can fill up quickly - not to be used on large sites - - -### Redis - -Stores data in a redis DB. To use this storage, define a service using the storage implementation as class and add connection -details to the service definition. An example is shipped with the [core service definitions](https://github.com/pimcore/personalization-bundle/blob/main/config/targeting.yaml#L35). - -Default session scope timeout: 30 minutes - -Pros - -* Can efficiently handle large amounts of data -* Natively supports data expiration - -Cons - -* Can only store data when a visitor ID is present as the ID is part of the key -* Needs a dedicated Redis DB independent of the cache one (needs to be configured on the service definition) - - -### Session - -Stores data in the session. - -Default session scope timeout: PHP session timeout - -To use the session storage, an additional config entry is needed as the session listeners are disabled by default for -performance reasons: - -```yaml -pimcore_personalization: - targeting: - # enable session support - session: - enabled: true - - # use the session storage - storage_id: Pimcore\Bundle\PersonalizationBundle\Targeting\Storage\SessionStorage -``` - -Pros - -* Easy to use as no additional config is needed -* Can store data without a visitor ID - -Cons - -* Can't persistently store data for a visitor -* Session size is limited -* Slow depending on session storage -* Might not work properly in conjunction with full page caches. Pimcore's full page cache is disabled when the session - contains targeting data, but if using something else it might be difficult to handle. Use with care when using full - page caches! diff --git a/bundles/PersonalizationBundle/doc/Development_Documentation/11_Frontend_Javascript.md b/bundles/PersonalizationBundle/doc/Development_Documentation/11_Frontend_Javascript.md deleted file mode 100644 index 01d00bccd1f..00000000000 --- a/bundles/PersonalizationBundle/doc/Development_Documentation/11_Frontend_Javascript.md +++ /dev/null @@ -1,134 +0,0 @@ -# Frontend JavaScript - -When targeting is enabled, a snippet of JavaScript in injected into every response. This snippet contains some information -needed by the frontend regarding the application state (e.g. if debug mode is enabled) and includes the script -[`pimcore/static6/js/frontend/targeting.js`](https://github.com/pimcore/personalization-bundle/blob/main/public/js/targeting.js) -which handles the frontend behaviour of the targeting engine (e.g. exposes an API to set the visitor ID). The whole targeting -logic is handled inside the `window._ptg` object. - -The frontend code provides a couple of extension points which are documented on this page. - - -## Setting a custom Visitor ID - -The `targeting.js` exposes a method to set a visitor ID programmatically. By doing so, the visitor ID will be set and stored -in the visitor ID cookie which is delivered to the backend. - -```js -_ptg.api.setVisitorId('my-custom-visitor-id'); -``` - -
-Make sure to use a unique visitor ID for each visitor! -
- - -## Change/extend the code snippet injected into the Response - -The code which is injected into the response is generated by the [`TargetingCodeGenerator`](https://github.com/pimcore/personalization-bundle/blob/main/src/Targeting/Code/TargetingCodeGenerator.php) -which fires a [TargetingEvents::TARGETING_CODE](https://github.com/pimcore/personalization-bundle/blob/main/src/Event/TargetingEvents.php#L30) -event. This event can be used to influence data and the used template which is used to render the code snippet: - - -```php - 'onTargetingCode', - ]; - } - - public function onTargetingCode(TargetingCodeEvent $event): void - { - // add code to a code block (see TargetingCodeGenerator and the default - // template for a list of blocks and their location) - $event - ->getBlock(TargetingCodeGenerator::BLOCK_BEFORE_SCRIPT) - ->append([ - 'console.log("Custom targeting code");' - ]); - - // completely override the rendered template - $event->setTemplate('@App/Targeting/targetingCode.html.twig'); - } -} -``` - -The listener above sets a custom template which can either extend the core one or define a completely custom output: - -```twig -{# templates/Targeting/targetingCode.html.twig #} - -{% extends '@PimcorePersonalization/Targeting/targetingCode.html.twig' %} - -{% block beforeScriptTag %} - {{ parent() }} - - -{% endblock %} -``` - -## Frontend Data Providers - -Some conditions or data providers might need data from the frontend. To make this possible, a backend implementation can -call `$visitorInfo->addFrontendDataProvider()` during the matching process to inform the frontend that it needs data from -a specific provider. These frontend data providers need to be implemented and registered to the frontend JS and are expected -to deliver their data to the backend in some way (e.g. by storing data in a cookie or by sending it through an async request). - -Currently, this feature is only sparsely used, but the [`GeoLocation`](https://github.com/pimcore/personalization-bundle/blob/main/src/Targeting/DataProvider/GeoLocation.php) -data provider which can read the visitor location from browser geolocation data informs the frontend that it needs data -from the `geolocation` frontend data provider. This information is added to the browser response, triggering the `targeting.js` -to execute this frontend data provider. The data provider in turn stores its data as cookie which is consumed by the (backend) -data provider on the next request. - - -### Implementing a Custom Frontend Data Provider - -Simply register your frontend data provider on the `window._ptg.dataProviders` object before `targeting.js` is loaded. As -example a simple provider which does nothing more than logging the current user agent to the console: - -```js -// /public/js/targeting/frontend.js - -(function () { - window._ptg = window._ptg || {}; - window._ptg.dataProviders = window._ptg.dataProviders || {}; - - window._ptg.dataProviders.userAgent = function() { - console.log('The current user agent is ' + navigator.userAgent); - }; -}()); -``` - -Taking the example listener above, we use a custom code snippet template to load our script: - -```twig -{# templates/Targeting/targetingCode.html.twig #} - -{% block targetingScript %} - - {{ parent() }} -{% endblock %} -``` - -To actually execute the provider, add the following call somewhere in your matching process (e.g. while loading data from -a data provider): - -```php -$visitorInfo->addFrontendDataProvider('userAgent'); -``` diff --git a/bundles/PersonalizationBundle/doc/Development_Documentation/README.md b/bundles/PersonalizationBundle/doc/Development_Documentation/README.md deleted file mode 100644 index 58d0262077f..00000000000 --- a/bundles/PersonalizationBundle/doc/Development_Documentation/README.md +++ /dev/null @@ -1,175 +0,0 @@ -# Targeting and Personalization - -The following section describes the technical concepts and aspects of the Pimcore targeting engine. For usage -description and feature listing see your [user docs](../../../User_Documentation/08_Targeting_and_Personalization/README.md) -first. - -## Setup - -You need to enable targeting either by setting the following config - -```yaml - pimcore_personalization: - targeting: - enabled: true -``` - -or using the cookie `pimcore_targeting_enabled=1`. - -if you'd like to use geo-related conditions in your -targeting rules it's necessary to configure the underlying data provider first. - -### Configuring the MaxMind GeoIP Data Provider - -Follow the [official instructions](https://dev.maxmind.com/geoip/geoipupdate/) for obtaining and updating the GeoIP database. -Store the database file at the location of your choice, the default location used by _geoipupdate_ is `/usr/share/GeoIP/GeoLite2-City.mmdb` - -Set the path to the database file in your `parameters.yaml` to enable the geo support in Pimcore: -```yaml -pimcore.geoip.db_file: /usr/share/GeoIP/GeoLite2-City.mmdb -``` - - -## Basic concepts - -| Concept | Description | -|------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `VisitorInfo` | Contains run-time information on the current visitor and is used by all conditions and action handlers to collect/apply data. | -| `DataProvider` | Can be used by other components such as conditions to fetch data about the current visitor. E.g. the `Device` data provider loads device info from the user-agent string. Data is only loaded on demand if really needed by a component. | -| `Condition` | A condition which is being matched. Conditions are configured in the Admin UI for global targeting rules. | -| `ActionHandler` | A targeting rule can have one or multiple actions which are executed when its conditions match. These actions are executed by an action handler. E.g. the `Redirect` action handler creates a `RedirectResponse` which is used to redirect the visitor to another page. | -| `Storage` | Where data about the visitor is persisted to. Each storage implementation supports storage for the `session` and the `visitor` scope. Session scoped data is only valid for the current session - either defined by a expiry time or by technical implications of the storage (e.g. the Cookie storage just sets a session cookie for the session storage). Visitor data is valid for the lifetime of the visitor and should be persisted, but depending on the used storage (e.g. Session Storage) this might not be possible. Different storages exist, e.g. cookie, JWT signed cookie, DB, session and redis which all have their advantages and disadvantages. | -| `Targeting Rule` | A condition/action combination executing one or more actions if a set of conditions match (similar to pricing rules in the ecommerce framework). Targeting rules are usually executed on every request but depending on their scope (hit, visitor, session, session with variables) this can be limited. | -| `Target Group` | An entity which is used to target content for. A document/snippet can define personalized content for a specific target group (e.g. a special banner for a target group "Outdoor Interested"). By matching targeting rules and by configuring target groups to be tracked when visiting documents, a visitor will be assigned a list of target groups (they will be stored to storage and be added to the `VisitorInfo` object at runtime). When rendering the document, a `DocumentTargetingConfigurator` selects the content which matches best for the target groups which are available on the `VisitorInfo`. | - - -## VisitorInfo and Visitor ID - -When a request is handled, the targeting engine first creates a new `VisitorInfo` instance. This instance is a data container -which will be used throughout the targeting process to store and pass data between different components of the system. - -See [Visitor Info](./01_Visitor_Info.md) for details. - - -## Target Rule Matching - -After building the `VisitorInfo` and resolving the visitor ID, the matching engine processes every defined targeting rule. -Based on the rule's scope a rule might be skipped (e.g. a `session` rule is applied only once per session). Every condition -gets the instance of the `VisitorInfo` and needs to decide if it matches its configured data. - -To fetch additional data about the visitor, a condition can request data from one or more `DataProvider` implementations. -As an example the `Device` data provider is able to resolve and cache device info from the user agent string by using the -`DeviceDetector` library. The `Operating System` condition just defines the `Device` data provider as dependency and can -rely on the device data being added the the `VisitorInfo` before matching. The targeting engine takes care of requesting -data from a data provider only once per request, even if multiple conditions rely on its data. - -If a rule matches, a list of actions is applied. Example actions are issuing a redirect or assigning a target group to the -current visitor. Actions (which are executed by action handlers) can request data from data providers in the same way as -conditions. - - -## Targeting Storage - -Some data needs to be persisted between requests. To do so, the targeting implementations makes use of a `TargetingStorage` -which implements a key-value store for targeting data. Data can be set for the `visitor` (valid for the whole lifetime of a -visitor) or for the `session` level. When storing data to an external storage, the previously resolved visitor ID is used -to store data for a specific visitor. - -Examples for data which is persisted to storage: - -* assigned target groups - either set from targeting rules or automatically tracked when visiting a document which defines - target groups to apply (as set on the document settings tab) -* already matched rules with `session` scope to avoid to repeatedly execute them for the same visitor -* first request to site in the current session (e.g. to calculate the time on site) - -For further details see [Targeting Storage](./09_Targeting_Storage.md). - - -## Content Targeting - -After matching all rules, a `VisitorInfo` may have different assigned target groups. When rendering a document which has -targeted content for multiple target groups, the following logic is applied to determine which version to use for the -current visitor: - -* If the visitor has only one assigned target group and content exists for that target group, the personalized version for - that target group will be used -* If the visitor has multiple assigned target groups, they will be sorted by their assignment count. The first target group - in the sorted list which has personalized content for the document will be used. - -This logic is repeated for every document and sub-request. This means you can show personalized content for a given target -group on a document, but have content for different target groups in snippets or renderlets rendered on the same page. - -You can see which target groups were applied to which document in the profiler. As you can see in the screenshot below, -the main document used the target group `basketball` while the footer snippet was rendered with the target group `female`. - -![Targeting Profiler Target Groups](../../img/targeting_profiler_target_groups.png) - - -### Manually applying Content Targeting - -The logic defined above can be applied to any document. Documents which are loaded through Pimcore's standard routing and -rendering features will be automatically configured, but you also manually configure any document to use personalized content -by using the `DocumentTargetingConfigurator` service. As example, when handling a document inside a controller which is -registered as service: - - -```php -configureTargetGroup($document); - - //... - } -} -``` - -## Extending the Targeting Engine - -The targeting engine is designed in a way to be easily extendable by third party code to extend and customize the engine's -behaviour. - -See the following resources for further details: - -* [Conditions](./03_Conditions.md) -* [Data Providers](./05_Data_Providers.md) -* [Action Handlers](./07_Action_Handlers.md) -* [Targeting Storage](./09_Targeting_Storage.md) -* [Frontend Javascript](./11_Frontend_Javascript.md) - - -## Debugging Targeting Data - -As shown above, targeting date is added to the Symfony profiler. In addition you can enable a dedicated targeting toolbar -which also works outside the `dev` environment when you are logged into the admin interface. - -

Targeting Debug Toolbar

- -The toolbar is only shown if a `pimcore_targeting_debug` cookie exists and is set and its value evaluates to true. You can -set the cookie with the following [bookmarklet](https://en.wikipedia.org/wiki/Bookmarklet) (just drag the link to your bookmarks -bar): - -* Enable Pimcore Targeting Toolbar - -## Opt-in for Targeting - -
-A user has the possibility to enable targeting at any time, by setting the following cookie `pimcore_targeting_enabled=1` . -
- -> Since privacy laws vary from country to country, we recommend that you consult with your legal team or company to check what needs to be done before using this features. diff --git a/bundles/PersonalizationBundle/doc/User_Documentation/01_Concepts.md b/bundles/PersonalizationBundle/doc/User_Documentation/01_Concepts.md deleted file mode 100644 index f17ead75af3..00000000000 --- a/bundles/PersonalizationBundle/doc/User_Documentation/01_Concepts.md +++ /dev/null @@ -1,59 +0,0 @@ -# Pimcore Concepts for Personalization - -The following core concepts are the essential parts of Pimcores personalization engine. - -## Target Group - -It is recommended to group your visitors into target groups. They help you to keep track and have an overview about what -content you need to provide and about which content is delivered to what visitor. - -So, a *Target Group* is an entity which is used to personalize content for. - -Target groups are defined centrally, have an unique name and can have a description. - -![Target Groups](../img/targeting/target-groups.jpg) - -See [Define Target Groups](./03_How_to_Personalize_Content/01_Define_Target_Groups.md) for further information. - - -## Global Targeting Rule - -*Global Targeting Rules* define the rule set based on which visitor profiling and content personalization takes place. -Thus they allow to assign target groups, redirect the visitor to certain pages or inject code snippets (special -java scripts, etc.) to the response based on behavioral conditions. - -Available conditions are Browser, Country, GeoPoint, Device, Language, Operating System, Referring Site, Search Engine, -Target Group, Time On Site, URL and Visited Pages Before. They can be combined with boolean operators (AND, OR, NOT) and -brackets to archive complex conditions. - -![Targeting Rules Conditions](../img/targeting/targeting-rules-conditions.jpg) - -Each targeting rule has name, may have a description and a scope: -* **Hit**: Actions are executed on every request. -* **Session**: Actions are executed once a session. -* **Session (with variables)**: Actions are executed once a session and when visitor information changes, e.g. every time -when assigned target groups of current visitor change. -* **Visitor**: Actions are executed once per visitor. - -The session expiry depends on the used storage engine. In default configuration the session expires when browser is -closed. See [Dev Docs](../../18_Tools_and_Features/37_Targeting_and_Personalization/09_Targeting_Storage.md) for details. - -The visitor is identified via a browser cookie (named `_pc_vis`). As long as this cookie is available, a visitor is identified as unique. - -See [Visitor Profiling](./03_How_to_Personalize_Content/03_Visitor_Profiling.md) and - [Create Personalized Content](./03_How_to_Personalize_Content/05_Create_Personalized_Content.md) for further information. - -## Personalized Document Content - -Pimcore Documents provide the possibility to create varying content variants for each defined target group. Once a target -group is selected, all content elements within the document can be overwritten and personalized. For not overwritten -content elements, the content is inherited from the default variant of the document. - -When delivering content to the visitor, the best matching variant is picked by Pimcore. *Best matching* is in this case -is the variant of the target group with highest assignment count of the current visitor that has content set. - -![Personalized Document Content](../img/targeting/personalized-documents.jpg) - -See [Create Personalized Content](./03_How_to_Personalize_Content/05_Create_Personalized_Content.md) and -[Deliver Personalized Content](./03_How_to_Personalize_Content/07_Deliver_Personalized_Content_and_Debug.md) -for further information. diff --git a/bundles/PersonalizationBundle/doc/User_Documentation/03_How_to_Personalize_Content/01_Define_Target_Groups.md b/bundles/PersonalizationBundle/doc/User_Documentation/03_How_to_Personalize_Content/01_Define_Target_Groups.md deleted file mode 100644 index c5e05ab25b4..00000000000 --- a/bundles/PersonalizationBundle/doc/User_Documentation/03_How_to_Personalize_Content/01_Define_Target_Groups.md +++ /dev/null @@ -1,18 +0,0 @@ -# Define Target Groups - -It is recommended to group your visitors into target groups. They help you to keep track and have an overview about what -content you need to provide and about which content is delivered to what visitor. - -Thus, *Target Groups* are the core entities for personalizing content. - -Target groups are defined centrally, have an unique name, can have a description and may be deactivated. - -![Target Groups](../../img/targeting/target-groups.jpg) - -The threshold is needed in combination with [visitor profiling](./03_Visitor_Profiling.md). Based on the behavior, a -visitor can get target groups assigned. Thus a visitor can belong to multiple target groups with a specific assignment -count (= times a target group got assigned and represents a certain relevance). Once the assignment count passes the -configured threshold, the visitor really belongs to the target groups and it is used for personalized content. - -See [visitor profiling](./03_Visitor_Profiling.md) for further information on profiling visitors and assigning target -groups. diff --git a/bundles/PersonalizationBundle/doc/User_Documentation/03_How_to_Personalize_Content/03_Visitor_Profiling.md b/bundles/PersonalizationBundle/doc/User_Documentation/03_How_to_Personalize_Content/03_Visitor_Profiling.md deleted file mode 100644 index be3b2317453..00000000000 --- a/bundles/PersonalizationBundle/doc/User_Documentation/03_How_to_Personalize_Content/03_Visitor_Profiling.md +++ /dev/null @@ -1,39 +0,0 @@ -# Visitor Profiling - -Visitor profiling is the process of getting to know visitors and assign one or multiple target groups based on their -behaviour. - -The result of the ongoing profiling process is a set of target groups, that are assigned with a certain assignment -relevance to the current visitor. - -![Associated Target Groups](../../img/targeting/target-groups-assignment.jpg) - - -Pimcore provides two ways for profiling visitors based on their behavior. - - -### Associate Target Groups directly with Pimcore Documents - -It is possible to associate Pimcore documents directly with target groups. This can be configured in `Settings` tab of -each document as soon as there are target groups defined in in the system. - -![Associate Target Groups with Documents](../../img/targeting/target-groups-documents.jpg) - -Once one or more target groups are associated with a document, the selected target groups are assigned to the visitor -and the assignment count is increased by one, each time this document opened by the user. - - -### Use Global Targeting Rules to Assign Target Groups - -*Global Targeting Rules* define a rule set based on which visitors can be profiled. Multiple conditions can be combined -to complex criteria for assigning target groups to the current visitor based on their behavior. - -![Targeting Rules Action](../../img/targeting/targeting-rules-action.jpg) - -When assigning a target group via global targeting rules, also a assignment weight can be specified. This increases the -assignment count of the target group respectively. - - -For more fine-grained profiling possibilities see our [Customer Management Framework](https://github.com/pimcore/customer-data-framework/blob/master/README.md) -with its [Customer Segment functionality](https://github.com/pimcore/customer-data-framework/blob/master/doc/11_CustomerSegments.md) -and its [extensions to the Pimcore targeting engine](https://github.com/pimcore/customer-data-framework/blob/master/doc/30_Personalization.md). diff --git a/bundles/PersonalizationBundle/doc/User_Documentation/03_How_to_Personalize_Content/05_Create_Personalized_Content.md b/bundles/PersonalizationBundle/doc/User_Documentation/03_How_to_Personalize_Content/05_Create_Personalized_Content.md deleted file mode 100644 index 35d40469c65..00000000000 --- a/bundles/PersonalizationBundle/doc/User_Documentation/03_How_to_Personalize_Content/05_Create_Personalized_Content.md +++ /dev/null @@ -1,16 +0,0 @@ -# Create Personalized Content - -The simplest way to create personalized content is with Pimcore documents. They provide the possibility to create -varying content variants for each defined target group. - -![Personalized Document Content](../../img/targeting/personalized-documents.jpg) - -Once a target group is selected, all content elements within the document can be overwritten and personalized. To -overwrite a certain element, use context menu of the element and select overwrite. - -![Personalized Document Content](../../img/targeting/personalized-documents-overwrite.jpg) - -All not overwritten content elements are greyed and inherit the content from the default variant of the document. - - - diff --git a/bundles/PersonalizationBundle/doc/User_Documentation/03_How_to_Personalize_Content/07_Deliver_Personalized_Content_and_Debug.md b/bundles/PersonalizationBundle/doc/User_Documentation/03_How_to_Personalize_Content/07_Deliver_Personalized_Content_and_Debug.md deleted file mode 100644 index c499d49c2c8..00000000000 --- a/bundles/PersonalizationBundle/doc/User_Documentation/03_How_to_Personalize_Content/07_Deliver_Personalized_Content_and_Debug.md +++ /dev/null @@ -1,56 +0,0 @@ -# Deliver Personalized Content - -Once target groups are defined, visitor profiling rules are set and personalized content is created, it is time to -deliver the personalized content to the right people. - -Pimcore provides two ways to deliver personalized Content: - - -### Delivering Personalized Content with Documents - -When a document is delivered to the visitor, the best matching variant is picked by Pimcore based on following rule: - -* If the visitor has only one assigned target group and content exists for that target group, the personalized variant - for that target group will be used -* If the visitor has multiple assigned target groups, they will be sorted by their assignment count. The first target - group in the sorted list which has personalized content for the document will be used. - -This logic is repeated for every document and sub-request. This means you can show personalized content for a given target -group on a document, but have content for different target groups in snippets or renderlets rendered on the same page. - -You can see which target groups were applied to which document in the profiler. As you can see in the screenshot below, -the main document used the target group `basketball` while the footer snippet was rendered with the target group `female`. - -![Targeting Profiler Target Groups](../../../img/targeting_profiler_target_groups.png) - -For further debug information see below. - -### Delivering Personalized Content with Global Targeting Rules - -In addition to Pimcore document delivery, global targeting rules provide two actions with further possibilities to -deliver personalized content to the visitor. - -1) **Redirect**: The visitor can be redirected when certain conditions match. E.g. redirect visitor on its first visit - to your site to a special landing page. - -2) **Code-Snippet**: The code snippet action allows to inject a code snippet to the response when certain conditions match. - This opens up lots of possibilities such as restyling the page by injecting CSS, adding additional functionality by - adding JS Snippets or even injecting whole HTML code blocks. - -![Targeting Rule Code Snippet Action](../../img/targeting/targeting-rules-action2.jpg) - - - -## Verify What Happens - aka Debug - -The personalization configuration can become quite complex really quick. To get to know what happens, which rules matched -for a certain request, which target groups are assigned and what data is collected, Pimcore injects several information -the Symfony profiler bar. - -![Profiler Bar Popup](../../img/targeting/profiler1.jpg) - -On the detail page you can find all information about matching targeting rules, assigned target groups, chosen target -groups for document delivery, all stored visitor information and content of the visitor storage. - -![Profiler Information](../../img/targeting/profiler2.jpg) - diff --git a/bundles/PersonalizationBundle/doc/User_Documentation/03_How_to_Personalize_Content/README.md b/bundles/PersonalizationBundle/doc/User_Documentation/03_How_to_Personalize_Content/README.md deleted file mode 100644 index c4f33ffc412..00000000000 --- a/bundles/PersonalizationBundle/doc/User_Documentation/03_How_to_Personalize_Content/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# How to Personalize Content - -On the following pages you find a short tutorial for how to personalize content. - -As the Pimcore behavioral targeting engine is very powerful and flexible, there are multiple ways to archive a result -and the outlined way is just one of many. - -The typical workflow of personalizing content contains following steps: -1) [Define Target Groups](./01_Define_Target_Groups.md) -2) [Setup Visitor Profiling](./03_Visitor_Profiling.md) -3) [Create Personalized Content](./05_Create_Personalized_Content.md) -4) [Deliver Personalized Content](./07_Deliver_Personalized_Content_and_Debug.md) - -For a live demo with a predefined rule set see our [Demo](https://demo.pimcore.fun). \ No newline at end of file diff --git a/bundles/PersonalizationBundle/doc/User_Documentation/05_Examples/README.md b/bundles/PersonalizationBundle/doc/User_Documentation/05_Examples/README.md deleted file mode 100644 index 51c2b6bb675..00000000000 --- a/bundles/PersonalizationBundle/doc/User_Documentation/05_Examples/README.md +++ /dev/null @@ -1,112 +0,0 @@ -# Examples for Personalization and Targeting - -The following pages show a few examples to better understand how things can be achieved with the Pimcore targeting -engine. - -All mentioned rules here are configured and set up in our [demo](https://demo.pimcore.fun) and can be -tested there. - - -## Visitor Profiling - -The result of the ongoing profiling process is a set of target groups, that are assigned with a certain assignment -relevance to the current visitor. So visitor profiling rules are always some conditions that result in assignment of -an target group. - -### Learning about customer interests based on behaviour - -##### Assign Target Groups to Documents -If there is a document, directly associate this document with a target group. - -![Tenant Switches Settings](../../img/targeting/examples_technical-guy1.jpg) - -Every time a visitor visits that page, it gets the `technical-guy` target group assigned. Since one visit might be a -coincidence, multiple visits might hint for a technical interest of that visitor. -To filter out the coincidence visitors, a threshold can be defined at the target group. So not before the third visit, -the target group is actually assigned to the visitors profile. - -![Technical Guy Settings](../../img/targeting/examples_technical-guy2.jpg) - -For a real-life example please have a look at our [public demo instance](https://demo.pimcore.fun/en/More-Stuff/Demo-Features/Personalization). - -##### Global Targeting Rules with simple Conditions & Actions - -Regular visits in a certain shopping category (e.g. football) indicate an interest in football. To track that information, -add a global targeting rule with a URL condition and an action to assign the corresponding target group. - -To eliminate coincidence visits, e.g. an additional `time on site` condition can be added, like at the `profiling_football` -targeting rule at the demo. - -![Technical Guy Settings](../../img/targeting/examples_football1.jpg) - -The action should be executed on every request that matches the criteria - so use the scope `Hit` here. By doing so, -the assignment count of the target group gets increased every time and so a certain relevance for the target groups -can be identified - e.g. visitor is more interested in football that in basketball. - - -### Guessing customer characteristics based on behaviour - -##### Global Targeting Rule with more complex Conditions - -Similar to the category interest tracking, customer characteristics like favorite color or even gender can be guessed. -To do so, add for example a targeting rule that tracks product filtering for blue products and assigns corresponding -target groups like the `profiling_blue-lover` rule does in the demo. - -![Profiling Blue Lover](../../img/targeting/examples_bluelover.jpg) - - -### Classify customers based on behaviour - -##### Identify regular customers - -In addition to collecting customer characteristics, you also could classify customers based on number of orders they made. -For example once a new customer creates an additional order, it becomes a regular customer. See the `profiling_regular-customer` -rule in the demo for details. - -![Classify Regular Customer](../../img/targeting/examples_regular-customer.jpg) - -In this case, it is important that the action is executed only once. Therefore choose `Visitor` as scope. - -![Scope to Visitor](../../img/targeting/examples_regular-customer2.jpg) - - -These are only a few simple examples for visitor profiling with Pimcores targeting engine. - - -## Personalize Content - -### Personalize content using global targeting rules - -##### Redirect visitor to a certain landing page - -One example for content personalization is to redirect a visitor to a special landing page that is answering this questions. -In our demo the example `personalize_redirect-to-landingpage` redirects visitors with language german that visited 5 pages -on our website to a landing page. - -![Redirect to Landingpage](../../img/targeting/examples_redirect.jpg) - -Of course this should happen not after every hit but only once per session, so scope is set to `session`. - - -##### Inject special content to mobile visitors - -Another use case could be, that special users (e.g. mobile users from a certain country) should see a slightly different -layout. See the `personalize_device-mobile` rule of our demo how this can be archived by injecting additional CSS code -to the page. - -![Inject CSS](../../img/targeting/examples_mobile-device.jpg) - - -### Personalized content documents - -The most common and most flexible way to customize content is to create customized variants of documents for certain -target groups. See [Create Personalized Content](../03_How_to_Personalize_Content/05_Create_Personalized_Content.md) -and [Deliver Personalized Content](../03_How_to_Personalize_Content/07_Deliver_Personalized_Content_and_Debug.md) for -details. - -In our demo several different variants of the portal page are set up - e.g. for target groups `football`, `basketball`, -`handball` and others. -Just try archive the corresponding target groups and see what happens. - -![Personalized Variant for Basketball](../../img/targeting/examples_basketball.jpg) - diff --git a/bundles/PersonalizationBundle/gpl-3.0.txt b/bundles/PersonalizationBundle/gpl-3.0.txt deleted file mode 100644 index 9e419e04214..00000000000 --- a/bundles/PersonalizationBundle/gpl-3.0.txt +++ /dev/null @@ -1,674 +0,0 @@ -GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. \ No newline at end of file diff --git a/bundles/PersonalizationBundle/public/css/icons.css b/bundles/PersonalizationBundle/public/css/icons.css deleted file mode 100644 index 55f336a3a8f..00000000000 --- a/bundles/PersonalizationBundle/public/css/icons.css +++ /dev/null @@ -1,27 +0,0 @@ -.pimcore_nav_icon_targeting { - background: url(/bundles/pimcoreadmin/img/flat-white-icons/person.svg) center center no-repeat !important; -} - -.pimcore_nav_icon_target_groups { - background: url(/bundles/pimcoreadmin/img/flat-white-icons/targetgroup.svg) center center no-repeat !important; -} - -.pimcore_nav_icon_targeting_toolbar { - background: url(/bundles/pimcoreadmin/img/flat-white-icons/ok.svg) center center no-repeat !important; -} - -.pimcore_icon_targeting { - background: url(/bundles/pimcoreadmin/img/flat-color-icons/person.svg) center center no-repeat !important; -} - -.pimcore_icon_targeting_toolbar_enable { - background: url(/bundles/pimcoreadmin/img/flat-color-icons/ok.svg) center center no-repeat !important; -} - -.pimcore_icon_targeting_toolbar_disable { - background: url(/bundles/pimcoreadmin/img/flat-color-icons/stop-circle.svg) center center no-repeat !important; -} - -.pimcore_icon_targeting_toolbar { - background: url(/bundles/pimcoreadmin/img/flat-color-icons/list.svg) center center no-repeat !important; -} diff --git a/bundles/PersonalizationBundle/public/css/targeting.css b/bundles/PersonalizationBundle/public/css/targeting.css deleted file mode 100644 index 4f1e146604e..00000000000 --- a/bundles/PersonalizationBundle/public/css/targeting.css +++ /dev/null @@ -1,25 +0,0 @@ -.pimcore_targeting_bracket { - position: absolute; - top: 64px; - cursor: pointer; - - color: #CCCCCC; - font-family: sans-serif; - font-size: 50px; -} - -.pimcore_targeting_bracket:hover { - color: #B4B4B4; -} - -.pimcore_targeting_bracket_active { - color: #000 !important; -} - -.pimcore_targeting_bracket_left { - left: 5px; -} - -.pimcore_targeting_bracket_right { - right: 5px; -} \ No newline at end of file diff --git a/bundles/PersonalizationBundle/public/js/document/areatoolbar.js b/bundles/PersonalizationBundle/public/js/document/areatoolbar.js deleted file mode 100644 index 6ea99480338..00000000000 --- a/bundles/PersonalizationBundle/public/js/document/areatoolbar.js +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Pimcore - * - * This source file is available under two different licenses: - * - GNU General Public License version 3 (GPLv3) - * - Pimcore Commercial License (PCL) - * Full copyright and license information is available in - * LICENSE.md which is distributed with this source code. - * - * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org) - * @license http://www.pimcore.org/license GPLv3 and PCL - */ - -pimcore.registerNS("pimcore.bundle.personalization.document.areatoolbar"); -/** - * @private - */ -pimcore.bundle.personalization.document.areatoolbar = Class.create({ - - initialize: function (document , lbar) { - this.addTargetingPanel(document , lbar); - }, - - addTargetingPanel: function (document , lbar) { - if (!Ext.Array.contains(['page', 'snippet'], document.getType())) { - return; - } - - if (pimcore.globalmanager.get("target_group_store").getCount() === 0) { - return; - } - - var cleanupFunction = function () { - - Ext.Ajax.request({ - url: Routing.generate('pimcore_bundle_personalization_clear_targeting_page_editable_data'), - method: "PUT", - params: { - targetGroup: this["targetGroup"] ? this.targetGroup.getValue() : "", - id: document.id - }, - success: function () { - docEdit.reload(true); - }.bind(this) - }); - }; - - var docEdit = pimcore.globalmanager.get("document_" + document.id).edit; - - this.targetGroupText = Ext.create('Ext.toolbar.TextItem', { - scale: "medium", - style: "-webkit-transform: rotate(270deg); -moz-transform: rotate(270deg); -o-transform: rotate(270deg); writing-mode: lr-tb;" - }); - - this.targetGroupStore = Ext.create('Ext.data.JsonStore', { - proxy: { - type: 'ajax', - url: Routing.generate('pimcore_bundle_personalization_targeting_targetgrouplist', {'add-default': true}) - }, - fields: ["id", "text"], - listeners: { - load: function () { - this.updateTargetGroupText(this.targetGroup.getValue()); - }.bind(this) - } - }); - - // add target group selection to toolbar - this.targetGroup = new Ext.form.ComboBox({ - displayField: 'text', - valueField: "id", - store: this.targetGroupStore, - editable: false, - triggerAction: 'all', - width: 240, - listeners: { - select: function (el) { - if (document.isDirty()) { - Ext.Msg.confirm(t('warning'), t('you_have_unsaved_changes') - + "
" + t("continue") + "?", - function (btn) { - if (btn === 'yes') { - docEdit.reload(true); - this.updateTargetGroupText(this.targetGroup.getValue()); - } - }.bind(this) - ); - } else { - docEdit.reload(); - this.updateTargetGroupText(this.targetGroup.getValue()); - } - }.bind(this) - } - }); - - this.targetGroupStore.load(); - - lbar.push("->", - this.targetGroupText, - { - tooltip: t("edit_content_for_target_group"), - iconCls: "pimcore_icon_target_groups", - arrowVisible: false, - menuAlign: "tl", - menu: [this.targetGroup] - }, - { - tooltip: t("clear_content_of_selected_target_group"), - iconCls: "pimcore_icon_cleanup", - handler: cleanupFunction.bind(this) - } - ); - }, - updateTargetGroupText: function (targetgroup) { - var record = this.targetGroupStore.getById(targetgroup); - - if (record) { - this.targetGroupText.update('    ' - + record.data.text); - } else { - this.targetGroupText.update(''); - } - }, - -}); diff --git a/bundles/PersonalizationBundle/public/js/object/classes/data/targetGroup.js b/bundles/PersonalizationBundle/public/js/object/classes/data/targetGroup.js deleted file mode 100644 index a4f01d6a639..00000000000 --- a/bundles/PersonalizationBundle/public/js/object/classes/data/targetGroup.js +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Pimcore - * - * This source file is available under two different licenses: - * - GNU General Public License version 3 (GPLv3) - * - Pimcore Commercial License (PCL) - * Full copyright and license information is available in - * LICENSE.md which is distributed with this source code. - * - * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org) - * @license http://www.pimcore.org/license GPLv3 and PCL - */ - -pimcore.registerNS("pimcore.object.classes.data.targetGroup"); -/** - * @private - */ -pimcore.object.classes.data.targetGroup = Class.create(pimcore.object.classes.data.data, { - - type: "targetGroup", - - /** - * define where this datatype is allowed - */ - allowIn: { - object: true, - objectbrick: true, - fieldcollection: true, - localizedfield: false, - classificationstore: false, - block: true, - encryptedField: true - }, - - initialize: function (treeNode, initData) { - this.type = "targetGroup"; - - if (!initData["name"]) { - initData = { - title: t("target_group") - }; - } - - initData.fieldtype = "targetGroup"; - initData.datatype = "data"; - initData.name = "targetGroup"; - treeNode.set("text", "targetGroup"); - - this.initData(initData); - - this.treeNode = treeNode; - }, - - getTypeName: function () { - return t("target_group"); - }, - - getLayout: function ($super) { - - $super(); - - const nameField = this.layout.getComponent("standardSettings").getComponent("name"); - nameField.disable(); - - return this.layout; - }, - - getGroup: function () { - return "crm"; - }, - - getIconClass: function () { - return "pimcore_icon_targetGroup"; - } -}); diff --git a/bundles/PersonalizationBundle/public/js/object/classes/data/targetGroupMultiselect.js b/bundles/PersonalizationBundle/public/js/object/classes/data/targetGroupMultiselect.js deleted file mode 100644 index 5aa8c960c0a..00000000000 --- a/bundles/PersonalizationBundle/public/js/object/classes/data/targetGroupMultiselect.js +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Pimcore - * - * This source file is available under two different licenses: - * - GNU General Public License version 3 (GPLv3) - * - Pimcore Commercial License (PCL) - * Full copyright and license information is available in - * LICENSE.md which is distributed with this source code. - * - * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org) - * @license http://www.pimcore.org/license GPLv3 and PCL - */ - -pimcore.registerNS("pimcore.object.classes.data.targetGroupMultiselect"); -/** - * @private - */ -pimcore.object.classes.data.targetGroupMultiselect = Class.create(pimcore.object.classes.data.multiselect, { - - type: "targetGroupMultiselect", - - /** - * define where this datatype is allowed - */ - allowIn: { - object: true, - objectbrick: true, - fieldcollection: true, - localizedfield: true, - classificationstore: false, - block: true, - encryptedField: true - }, - - initialize: function (treeNode, initData) { - this.type = "targetGroupMultiselect"; - - this.initData(initData); - - this.treeNode = treeNode; - }, - - getGroup: function () { - return "crm"; - }, - - getTypeName: function () { - return t("target_group_multiselect"); - }, - - getIconClass: function () { - return "pimcore_icon_targetGroupMultiselect"; - }, - - getLayout: function ($super) { - $super(); - - this.specificPanel.removeAll(); - var specificItems = this.getSpecificPanelItems(this.datax, false); - this.specificPanel.add(specificItems); - - return this.layout; - }, - - getSpecificPanelItems: function (datax, inEncryptedField) { - return [ - { - xtype: "textfield", - fieldLabel: t("width"), - name: "width", - value: datax.width - }, - { - xtype: "displayfield", - hideLabel: true, - value: t('width_explanation') - }, - { - xtype: "textfield", - fieldLabel: t("height"), - name: "height", - value: datax.height - }, - { - xtype: "displayfield", - hideLabel: true, - value: t('height_explanation') - } - ]; - }, - - - - applyData: function ($super) { - $super(); - delete this.datax.options; - }, - - applySpecialData: function (source) { - if (source.datax) { - if (!this.datax) { - this.datax = {}; - } - - Ext.apply(this.datax, { - options: source.datax.options, - width: source.datax.width, - height: source.datax.height - }); - } - } -}); diff --git a/bundles/PersonalizationBundle/public/js/object/tags/targetGroup.js b/bundles/PersonalizationBundle/public/js/object/tags/targetGroup.js deleted file mode 100644 index d5225d551a1..00000000000 --- a/bundles/PersonalizationBundle/public/js/object/tags/targetGroup.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Pimcore - * - * This source file is available under two different licenses: - * - GNU General Public License version 3 (GPLv3) - * - Pimcore Commercial License (PCL) - * Full copyright and license information is available in - * LICENSE.md which is distributed with this source code. - * - * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org) - * @license http://www.pimcore.org/license GPLv3 and PCL - */ - -pimcore.registerNS("pimcore.object.tags.targetGroup"); -/** - * @private - */ -pimcore.object.tags.targetGroup = Class.create(pimcore.object.tags.select, { - - type: "targetGroup", - - initialize: function (data, fieldConfig) { - this.data = data; - this.fieldConfig = fieldConfig; - this.fieldConfig.width = 300; - }, - - getGridColumnFilter: function (field) { - return null; - } -}); diff --git a/bundles/PersonalizationBundle/public/js/object/tags/targetGroupMultiselect.js b/bundles/PersonalizationBundle/public/js/object/tags/targetGroupMultiselect.js deleted file mode 100644 index 2dd36af7e5e..00000000000 --- a/bundles/PersonalizationBundle/public/js/object/tags/targetGroupMultiselect.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Pimcore - * - * This source file is available under two different licenses: - * - GNU General Public License version 3 (GPLv3) - * - Pimcore Commercial License (PCL) - * Full copyright and license information is available in - * LICENSE.md which is distributed with this source code. - * - * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org) - * @license http://www.pimcore.org/license GPLv3 and PCL - */ - -pimcore.registerNS("pimcore.object.tags.targetGroupMultiselect"); -/** - * @private - */ -pimcore.object.tags.targetGroupMultiselect = Class.create(pimcore.object.tags.multiselect, { - - type: "targetGroupMultiselect", - - getGridColumnFilter: function (field) { - return null; - } -}); diff --git a/bundles/PersonalizationBundle/public/js/settings/action/abstract.js b/bundles/PersonalizationBundle/public/js/settings/action/abstract.js deleted file mode 100644 index 21fa6c5c985..00000000000 --- a/bundles/PersonalizationBundle/public/js/settings/action/abstract.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Pimcore - * - * This source file is available under two different licenses: - * - GNU General Public License version 3 (GPLv3) - * - Pimcore Commercial License (PCL) - * Full copyright and license information is available in - * LICENSE.md which is distributed with this source code. - * - * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org) - * @license http://www.pimcore.org/license GPLv3 and PCL - */ - -pimcore.registerNS("pimcore.bundle.personalization.settings.action.abstract"); -/** - * @private - */ -pimcore.bundle.personalization.settings.action.abstract = Class.create({ - getName: function () { - console.error('Name is not set for action', this); - }, - - getIconCls: function () { - return 'pimcore_icon_add'; - }, - - getPanel: function () { - console.error('You have to implement the getPanel() method in action', this); - } -}); diff --git a/bundles/PersonalizationBundle/public/js/settings/actions.js b/bundles/PersonalizationBundle/public/js/settings/actions.js deleted file mode 100644 index c15c3b889cb..00000000000 --- a/bundles/PersonalizationBundle/public/js/settings/actions.js +++ /dev/null @@ -1,283 +0,0 @@ -/** - * Pimcore - * - * This source file is available under two different licenses: - * - GNU General Public License version 3 (GPLv3) - * - Pimcore Commercial License (PCL) - * Full copyright and license information is available in - * LICENSE.md which is distributed with this source code. - * - * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org) - * @license http://www.pimcore.org/license GPLv3 and PCL - */ - -/** - * ACTION TYPES - */ -pimcore.registerNS("pimcore.bundle.personalization.settings.actions"); -/** - * @private - */ -pimcore.bundle.personalization.settings.actions = (function () { - var actions = { - redirect: Class.create(pimcore.bundle.personalization.settings.action.abstract, { - getName: function () { - return t("redirect"); - }, - - getPanel: function (panel, data) { - var id = Ext.id(); - - return new Ext.form.FormPanel({ - id: id, - forceLayout: true, - border: true, - style: "margin: 10px 0 0 0", - bodyStyle: "padding: 10px 30px 10px 30px; min-height:40px;", - tbar: pimcore.bundle.personalization.settings.actions.getTopBar(this, id, panel), - items: [ - { - xtype: "textfield", - width: 450, - fieldLabel: "URL", - name: "url", - value: data.url, - fieldCls: "input_drop_target", - listeners: { - "render": function (el) { - new Ext.dd.DropZone(el.getEl(), { - reference: this, - ddGroup: "element", - - getTargetFromEvent: function (e) { - return this.getEl(); - }.bind(el), - - onNodeOver: function (target, dd, e, data) { - if (data.records.length == 1 && data.records[0].data.elementType === "document") { - return Ext.dd.DropZone.prototype.dropAllowed; - } - }, - - onNodeDrop: function (target, dd, e, data) { - if (pimcore.helpers.dragAndDropValidateSingleItem(data)) { - var nodeData = data.records[0].data; - if (nodeData.elementType === "document") { - this.setValue(nodeData.path); - return true; - } - } - - return false; - }.bind(el) - }); - } - } - }, - { - xtype: "hidden", - name: "type", - value: "redirect" - } - ] - }); - } - }), - - codesnippet: Class.create(pimcore.bundle.personalization.settings.action.abstract, { - getName: function () { - return t("code_snippet"); - }, - - getPanel: function (panel, data) { - var id = Ext.id(); - - return new Ext.form.FormPanel({ - id: id, - forceLayout: true, - border: true, - style: "margin: 10px 0 0 0", - bodyStyle: "padding: 10px 30px 10px 30px; min-height:40px;", - tbar: pimcore.bundle.personalization.settings.actions.getTopBar(this, id, panel), - items: [ - { - xtype: "textarea", - width: 600, - height: 200, - fieldLabel: t("code"), - name: "code", - value: data.code - }, - { - xtype: 'combo', - fieldLabel: t('element_css_selector'), - name: "selector", - disableKeyFilter: true, - store: [["body", "body"], ["head", "head"]], - triggerAction: "all", - mode: "local", - width: 350, - value: data.selector - }, - { - xtype: 'combo', - fieldLabel: t('insert_position'), - name: "position", - store: [["beginning", t("beginning")], ["end", t("end")], ["replace", t("replace")]], - triggerAction: "all", - typeAhead: false, - editable: false, - forceSelection: true, - mode: "local", - width: 350, - value: data.position - }, - { - xtype: "hidden", - name: "type", - value: "codesnippet" - } - ] - }); - } - }), - - assign_target_group: Class.create(pimcore.bundle.personalization.settings.action.abstract, { - getName: function () { - return t('assign_target_group'); - }, - - getPanel: function (panel, data) { - var id = Ext.id(); - - return new Ext.form.FormPanel({ - id: id, - forceLayout: true, - border: true, - style: "margin: 10px 0 0 0", - bodyStyle: "padding: 10px 30px 10px 30px; min-height:40px;", - tbar: pimcore.bundle.personalization.settings.actions.getTopBar(this, id, panel), - items: [ - { - xtype: "combo", - fieldLabel: t('target_group'), - name: "targetGroup", - displayField: 'text', - valueField: "id", - store: pimcore.globalmanager.get("target_group_store"), - editable: false, - width: 400, - triggerAction: 'all', - listWidth: 200, - mode: "local", - value: data.targetGroup, - emptyText: t("select_a_target_group") - }, - { - xtype: 'numberfield', - fieldLabel: t('assign_target_group_weight'), - name: "weight", - value: data.weight ? data.weight : 1, - width: 200, - minValue: 1, - allowDecimals: false - }, - { - xtype: "hidden", - name: "type", - value: "assign_target_group" - } - ] - }); - } - }) - }; - - return { - register: function (name, action) { - actions[name] = action; - }, - - create: function (name) { - var actionClass = this.get(name); - - return new actionClass(); - }, - - get: function (name) { - if ('undefined' === typeof actions[name]) { - throw new Error('Action ' + name + ' is not defined', name); - } - - return actions[name]; - }, - - getKeys: function () { - return Object.keys(actions); - }, - - getTopBar: function (action, index, parent) { - var detectBlockIndex = function(blockElement, container) { - var index; - for (var s = 0; s < container.items.items.length; s++) { - if (container.items.items[s].getId() === blockElement.getId()) { - index = s; - break; - } - } - - return index; - }; - - return [ - { - iconCls: action.getIconCls(), - disabled: true - }, - { - xtype: "tbtext", - text: "" + action.getName() + "" - }, - "-", - { - iconCls: "pimcore_icon_up", - handler: function (blockId, parent) { - var container = parent.actionsContainer; - var blockElement = Ext.getCmp(blockId); - var index = detectBlockIndex(blockElement, container); - - var newIndex = index - 1; - if (newIndex < 0) { - newIndex = 0; - } - - container.remove(blockElement, false); - container.insert(newIndex, blockElement); - - pimcore.layout.refresh(); - }.bind(window, index, parent) - }, - { - iconCls: "pimcore_icon_down", - handler: function (blockId, parent) { - var container = parent.actionsContainer; - var blockElement = Ext.getCmp(blockId); - var index = detectBlockIndex(blockElement, container); - - container.remove(blockElement, false); - container.insert(index + 1, blockElement); - - pimcore.layout.refresh(); - }.bind(window, index, parent) - }, - "->", - { - iconCls: "pimcore_icon_delete", - handler: function (index, parent) { - parent.actionsContainer.remove(Ext.getCmp(index)); - }.bind(window, index, parent) - } - ]; - } - }; -}()); diff --git a/bundles/PersonalizationBundle/public/js/settings/condition/abstract.js b/bundles/PersonalizationBundle/public/js/settings/condition/abstract.js deleted file mode 100644 index e00cdc3fd45..00000000000 --- a/bundles/PersonalizationBundle/public/js/settings/condition/abstract.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Pimcore - * - * This source file is available under two different licenses: - * - GNU General Public License version 3 (GPLv3) - * - Pimcore Commercial License (PCL) - * Full copyright and license information is available in - * LICENSE.md which is distributed with this source code. - * - * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org) - * @license http://www.pimcore.org/license GPLv3 and PCL - */ - -pimcore.registerNS("pimcore.bundle.personalization.settings.condition.abstract"); -/** - * @private - */ -pimcore.bundle.personalization.settings.condition.abstract = Class.create({ - matchesScope: function (scope) { - return 'targeting_rule' === scope; - }, - - getName: function () { - console.error('Name is not set for condition', this); - }, - - getIconCls: function () { - return 'pimcore_icon_add'; - }, - - getPanel: function () { - console.error('You have to implement the getPanel() method in condition', this); - }, - - isAvailable: function () { - return true; - } -}); diff --git a/bundles/PersonalizationBundle/public/js/settings/conditions.js b/bundles/PersonalizationBundle/public/js/settings/conditions.js deleted file mode 100644 index f6abb27190e..00000000000 --- a/bundles/PersonalizationBundle/public/js/settings/conditions.js +++ /dev/null @@ -1,862 +0,0 @@ -/** - * Pimcore - * - * This source file is available under two different licenses: - * - GNU General Public License version 3 (GPLv3) - * - Pimcore Commercial License (PCL) - * Full copyright and license information is available in - * LICENSE.md which is distributed with this source code. - * - * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org) - * @license http://www.pimcore.org/license GPLv3 and PCL - */ - - -/** - * CONDITION TYPES - */ -pimcore.registerNS("pimcore.bundle.personalization.settings.conditions"); -/** - * @private - */ -pimcore.bundle.personalization.settings.conditions = (function () { - var conditions = { - url: Class.create(pimcore.bundle.personalization.settings.condition.abstract, { - getName: function () { - return t('targeting_condition_url_pattern'); - }, - - getPanel: function (panel, data) { - var id = Ext.id(); - - return new Ext.form.FormPanel({ - id: id, - forceLayout: true, - style: "margin: 10px 0 0 0", - bodyStyle: "padding: 10px 30px 10px 30px; min-height:40px;", - tbar: pimcore.bundle.personalization.settings.conditions.getTopBar(this, id, panel, data), - items: [{ - xtype: "textfield", - fieldLabel: t('targeting_condition_url_pattern'), - name: "url", - value: data.url, - width: 500 - }, { - xtype: "hidden", - name: "type", - value: "url" - }] - }); - } - }), - - browser: Class.create(pimcore.bundle.personalization.settings.condition.abstract, { - getName: function () { - return t("browser"); - }, - - matchesScope: function (scope) { - return in_array(scope, ['targeting_rule', 'targeting_group_entry_condition']); - }, - - getPanel: function (panel, data) { - var id = Ext.id(); - - return new Ext.form.FormPanel({ - id: id, - forceLayout: true, - style: "margin: 10px 0 0 0", - bodyStyle: "padding: 10px 30px 10px 30px; min-height:40px;", - tbar: pimcore.bundle.personalization.settings.conditions.getTopBar(this, id, panel, data), - items: [{ - xtype: "combo", - fieldLabel: t("browser"), - store: [ - ["ie", "Internet Explorer"], - ["firefox", "Firefox"], - ["chrome", "Google Chrome"], - ["safari", "Safari"], - ["opera", "Opera"] - ], - name: "browser", - mode: "local", - width: 300, - value: data.browser, - editable: false, - triggerAction: "all" - }, { - xtype: "hidden", - name: "type", - value: "browser" - }] - }); - } - }), - - country: Class.create(pimcore.bundle.personalization.settings.condition.abstract, { - - isAvailable : function () { - return pimcore.settings['maxmind_geoip_installed']; - }, - - getName: function () { - return t("country"); - }, - - matchesScope: function (scope) { - return in_array(scope, ['targeting_rule', 'targeting_group_entry_condition']); - }, - - getPanel: function (panel, data) { - var id = Ext.id(); - - return new Ext.form.FormPanel({ - id: id, - forceLayout: true, - style: "margin: 10px 0 0 0", - bodyStyle: "padding: 10px 30px 10px 30px; min-height:40px;", - tbar: pimcore.bundle.personalization.settings.conditions.getTopBar(this, id, panel, data), - items: [{ - xtype: 'combo', - fieldLabel: t('country'), - displayField: 'name', - valueField: 'code', - name: "country", - store: new Ext.data.JsonStore({ - autoDestroy: true, - proxy: { - type: 'ajax', - url: Routing.generate('pimcore_admin_misc_countrylist'), - reader: { - type: 'json', - rootProperty: 'data' - } - }, - fields: ["code", "name"] - }), - triggerAction: "all", - mode: "local", - forceSelection: true, - queryMode: 'local', - autoComplete: false, - width: 350, - value: data.country, - listeners: { - afterrender: function (el) { - el.getStore().load(); - } - } - }, { - xtype: "hidden", - name: "type", - value: "country" - }] - }); - } - }), - - language: Class.create(pimcore.bundle.personalization.settings.condition.abstract, { - getName: function () { - return t("language"); - }, - - matchesScope: function (scope) { - return in_array(scope, ['targeting_rule', 'targeting_group_entry_condition']); - }, - - getPanel: function (panel, data) { - var id = Ext.id(); - - return new Ext.form.FormPanel({ - id: id, - forceLayout: true, - style: "margin: 10px 0 0 0", - bodyStyle: "padding: 10px 30px 10px 30px; min-height:40px;", - tbar: pimcore.bundle.personalization.settings.conditions.getTopBar(this, id, panel, data), - items: [{ - xtype: 'combo', - fieldLabel: t('language'), - displayField: 'name', - valueField: 'code', - name: "language", - store: new Ext.data.JsonStore({ - autoDestroy: true, - proxy: { - type: 'ajax', - url: Routing.generate('pimcore_admin_misc_languagelist'), - reader: { - type: 'json', - rootProperty: 'data' - } - }, - fields: ["code", "name"] - }), - triggerAction: "all", - mode: "local", - forceSelection: true, - queryMode: 'local', - autoComplete: false, - width: 350, - value: data.language, - listeners: { - afterrender: function (el) { - el.getStore().load(); - } - } - }, { - xtype: "hidden", - name: "type", - value: "language" - }] - }); - } - }), - - geopoint: Class.create(pimcore.bundle.personalization.settings.condition.abstract, { - - isAvailable : function () { - return pimcore.settings['maxmind_geoip_installed']; - }, - - getName: function () { - return t("geopoint"); - }, - - matchesScope: function (scope) { - return in_array(scope, ['targeting_rule', 'targeting_group_entry_condition']); - }, - - getPanel: function (panel, data) { - var id = Ext.id(); - - var longitude = new Ext.form.NumberField({ - decimalPrecision: 20, - fieldLabel: t('longitude'), - name: "longitude", - value: data.longitude, - width: 350 - }); - - var latitude = new Ext.form.NumberField({ - decimalPrecision: 20, - fieldLabel: t('latitude'), - name: "latitude", - value: data.latitude, - width: 350 - }); - - var radius = new Ext.form.NumberField({ - decimalPrecision: 0, - fieldLabel: t('radius_in_km'), - name: "radius", - value: data.radius, - width: 200 - }); - - var createSearchButton = function() { - var handler = function () { - var leafletMap, marker, circle; - - var searchHandler = function() { - var address = searchfield.getValue(); - Ext.Ajax.request({ - url: pimcore.bundle.personalization.settings.conditions.getSearchUrl(address), - method: "GET", - success: function (response, opts) { - var data = Ext.decode(response.responseText); - if (data[0].lat !== null && data[0].lon !== null) { - marker.setLatLng(L.latLng(data[0].lat, data[0].lon)); - leafletMap.setView(L.latLng(data[0].lat, data[0].lon), 7); - } - }.bind(this), - }); - }; - - var searchfield = new Ext.form.TextField({ - width: 400, - name: "mapSearch", - style: "float: left;", - fieldLabel: t("search"), - listeners: { - specialkey: function (f, e) { - if (e.getKey() === e.ENTER) { - searchHandler() - } - } - } - }); - - var searchWindow = new Ext.Window({ - modal: true, - width: 700, - height: 500, - resizable: false, - html: '
', - bbar: [searchfield, { - xtype: "button", - text: t("search"), - iconCls: "pimcore_icon_search", - handler: searchHandler - }, "->", { - xtype: "button", - text: t("cancel"), - iconCls: "pimcore_icon_cancel", - handler: function () { - searchWindow.close(); - } - }, { - xtype: "button", - text: t("OK"), - iconCls: "pimcore_icon_save", - handler: function () { - var point = marker.getLatLng(); - latitude.setValue(point.lat); - longitude.setValue(point.lng); - radius.setValue(Math.round(circle.getRadius() / 1000)); - - searchWindow.close(); - } - }], - plain: true - }); - - searchWindow.on("afterrender", function () { - var latitudeMap = 0; - var longitudeMap = 0; - var radiusMap = 100000; - var mapZoom = 1; - if (latitude.getValue() && longitude.getValue()) { - latitudeMap = latitude.getValue(); - longitudeMap = longitude.getValue(); - mapZoom = 14; - } - if (radius.getValue()) { - radiusMap = radius.getValue() * 1000; - } - - document.getElementById('leaflet_maps_container').innerHTML = '
'; - leafletMap = new L.Map("leafletmap").setView([latitudeMap, longitudeMap], mapZoom); - L.tileLayer(pimcore.settings.tile_layer_url_template, { - attribution: '© OpenStreetMap contributors' - }).addTo(leafletMap); - - marker = new L.Marker([latitudeMap, longitudeMap], {draggable: true}).addTo(leafletMap); - - circle = new L.Circle( - [latitudeMap, longitudeMap], { - editable: true, - radius: radiusMap, - fillColor: "#ff6600", - fillOpacity: 0.5, - strokeWeight: 2, - strokeOpacity: 1, - strokeColor: "#000000" - }).addTo(leafletMap); - - marker.on("move", function () { - circle.editing.disable(); - leafletMap.removeLayer(circle); - circle.setLatLng(marker.getLatLng()); - circle.editing.enable(); - circle.addTo(leafletMap); - }.bind(this)); - - var GLOBE_WIDTH = 256; // a constant in Google's map projection - var west = circle.getBounds().getSouthWest().lng; - var east = circle.getBounds().getNorthEast().lng; - var angle = east - west; - if (angle < 0) { - angle += 360; - } - var zoom = Math.round(Math.log(searchWindow.body.getWidth() * 360 / angle / GLOBE_WIDTH) / Math.LN2); - - leafletMap.setZoom(zoom - 1); - }); - - searchWindow.show(); - }; - - return { - xtype: "button", - text: t("open_search_editor"), - iconCls: "pimcore_icon_search", - handler: handler - }; - }; - - var items = [ - longitude, - latitude, - radius - ]; - - items.push(createSearchButton()); - - items.push({ - xtype: "displayfield", - style: "margin-top:10px;", - html: 'This product includes GeoLite2 data created by MaxMind, available from http://www.maxmind.com.' - }); - - items.push({ - xtype: "hidden", - name: "type", - value: "geopoint" - }); - - return new Ext.form.FormPanel({ - id: id, - forceLayout: true, - style: "margin: 10px 0 0 0", - bodyStyle: "padding: 10px 30px 10px 30px; min-height:40px;", - tbar: pimcore.bundle.personalization.settings.conditions.getTopBar(this, id, panel, data), - items: items - }); - } - }), - - referringsite: Class.create(pimcore.bundle.personalization.settings.condition.abstract, { - getName: function () { - return t("referring_site"); - }, - - matchesScope: function (scope) { - return in_array(scope, ['targeting_rule', 'targeting_group_entry_condition']); - }, - - getPanel: function (panel, data) { - var id = Ext.id(); - - return new Ext.form.FormPanel({ - id: id, - forceLayout: true, - style: "margin: 10px 0 0 0", - bodyStyle: "padding: 10px 30px 10px 30px; min-height:40px;", - tbar: pimcore.bundle.personalization.settings.conditions.getTopBar(this, id, panel, data), - items: [{ - xtype: 'textfield', - fieldLabel: t('referrer') + ' (' + t("regex") + ')', - name: "referrer", - value: data.referrer, - labelWidth: 170, - width: 450 - }, { - xtype: "hidden", - name: "type", - value: "referringsite" - }] - }); - } - }), - - searchengine: Class.create(pimcore.bundle.personalization.settings.condition.abstract, { - getName: function () { - return t("searchengine"); - }, - - matchesScope: function (scope) { - return in_array(scope, ['targeting_rule', 'targeting_group_entry_condition']); - }, - - getPanel: function (panel, data) { - var id = Ext.id(); - - return new Ext.form.FormPanel({ - id: id, - forceLayout: true, - style: "margin: 10px 0 0 0", - bodyStyle: "padding: 10px 30px 10px 30px; min-height:40px;", - tbar: pimcore.bundle.personalization.settings.conditions.getTopBar(this, id, panel, data), - items: [{ - xtype: 'combo', - fieldLabel: t('searchengine'), - name: "searchengine", - disableKeyFilter: true, - store: [["all", t("all")], ["google", "Google"], ["bing", "Bing"], ["yahoo", "Yahoo!"]], - triggerAction: "all", - mode: "local", - width: 350, - value: data.searchengine ? data.searchengine : "all" - }, { - xtype: "hidden", - name: "type", - value: "searchengine" - }] - }) - } - }), - - visitedpagebefore: Class.create(pimcore.bundle.personalization.settings.condition.abstract, { - getName: function () { - return t("visited_page_before"); - }, - - getPanel: function (panel, data) { - var id = Ext.id(); - - var warningIconHtml = '  '; - - var items = [ - { - xtype: 'textfield', - fieldLabel: t('targeting_condition_url_pattern'), - name: "url", - value: data.url, - width: 450 - }, - { - xtype: "hidden", - name: "type", - value: "visitedpagebefore" - }, - { - xtype: "displayfield", - hideLabel: true, - value: warningIconHtml + ' DEPRECATED! Will be removed in Pimcore 10', - cls: "pimcore_extra_label" - }, - { - xtype: "displayfield", - hideLabel: true, - value: warningIconHtml + t('targeting_condition_visited_page_before_piwik_data_warning'), - cls: "pimcore_extra_label" - } - ]; - - if ('undefined' === typeof pimcore.settings.piwik || !pimcore.settings.piwik.configured || !pimcore.settings.piwik.report_token_configured) { - items.push({ - xtype: "displayfield", - hideLabel: true, - value: warningIconHtml + t('targeting_condition_visited_page_before_piwik_not_configured_warning'), - cls: "pimcore_extra_label" - }); - } - - return new Ext.form.FormPanel({ - id: id, - forceLayout: true, - style: "margin: 10px 0 0 0", - bodyStyle: "padding: 10px 30px 10px 30px; min-height:40px;", - tbar: pimcore.bundle.personalization.settings.conditions.getTopBar(this, id, panel, data), - items: items - }); - } - }), - - visitedpagesbefore: Class.create(pimcore.bundle.personalization.settings.condition.abstract, { - getName: function () { - return t("visited_pages_before_number"); - }, - - getPanel: function (panel, data) { - var id = Ext.id(); - - return new Ext.form.FormPanel({ - id: id, - forceLayout: true, - style: "margin: 10px 0 0 0", - bodyStyle: "padding: 10px 30px 10px 30px; min-height:40px;", - tbar: pimcore.bundle.personalization.settings.conditions.getTopBar(this, id, panel, data), - items: [{ - xtype: 'numberfield', - fieldLabel: t("number"), - name: "number", - value: data.number, - width: 200 - }, { - xtype: "hidden", - name: "type", - value: "visitedpagesbefore" - }] - }); - } - }), - - timeonsite: Class.create(pimcore.bundle.personalization.settings.condition.abstract, { - getName: function () { - return t("time_on_site"); - }, - - getPanel: function (panel, data) { - var id = Ext.id(); - - return new Ext.form.FormPanel({ - id: id, - forceLayout: true, - style: "margin: 10px 0 0 0", - bodyStyle: "padding: 10px 30px 10px 30px; min-height:40px;", - tbar: pimcore.bundle.personalization.settings.conditions.getTopBar(this, id, panel, data), - items: [{ - xtype: 'numberfield', - fieldLabel: t("hours"), - name: "hours", - value: data.hours ? data.hours : 0, - width: 200 - }, { - xtype: 'numberfield', - fieldLabel: t("minutes"), - name: "minutes", - value: data.minutes ? data.minutes : 0, - width: 200 - }, { - xtype: 'numberfield', - fieldLabel: t("seconds"), - name: "seconds", - value: data.seconds ? data.seconds : 0, - width: 200 - }, { - xtype: "hidden", - name: "type", - value: "timeonsite" - }] - }); - } - }), - - hardwareplatform: Class.create(pimcore.bundle.personalization.settings.condition.abstract, { - getName: function () { - return t("hardware_platform"); - }, - - matchesScope: function (scope) { - return in_array(scope, ['targeting_rule', 'targeting_group_entry_condition']); - }, - - getPanel: function (panel, data) { - var id = Ext.id(); - - return new Ext.form.FormPanel({ - id: id, - forceLayout: true, - style: "margin: 10px 0 0 0", - bodyStyle: "padding: 10px 30px 10px 30px; min-height:40px;", - tbar: pimcore.bundle.personalization.settings.conditions.getTopBar(this, id, panel, data), - items: [{ - xtype: 'combo', - fieldLabel: t('hardware_platform'), - name: "platform", - disableKeyFilter: true, - store: [["all", t("all")], ["desktop", t("desktop")], ["tablet", t("tablet")], ["mobile", t("mobile")]], - triggerAction: "all", - mode: "local", - width: 350, - value: data.platform ? data.platform : "all" - }, { - xtype: "hidden", - name: "type", - value: "hardwareplatform" - }] - }); - } - }), - - operatingsystem: Class.create(pimcore.bundle.personalization.settings.condition.abstract, { - getName: function () { - return t("operating_system"); - }, - - matchesScope: function (scope) { - return in_array(scope, ['targeting_rule', 'targeting_group_entry_condition']); - }, - - getPanel: function (panel, data) { - var id = Ext.id(); - - return new Ext.form.FormPanel({ - id: id, - forceLayout: true, - style: "margin: 10px 0 0 0", - bodyStyle: "padding: 10px 30px 10px 30px; min-height:40px;", - tbar: pimcore.bundle.personalization.settings.conditions.getTopBar(this, id, panel, data), - items: [{ - xtype: 'combo', - fieldLabel: t('operating_system'), - name: "system", - disableKeyFilter: true, - store: [["all", t("all")], ["windows", "Windows"], ["macos", "Mac OS"], ["linux", "Linux"], - ["android", "Android"], ["ios", "iOS"]], - triggerAction: "all", - mode: "local", - width: 350, - value: data.system ? data.system : "all" - }, { - xtype: "hidden", - name: "type", - value: "operatingsystem" - }] - }); - } - }), - - target_group: Class.create(pimcore.bundle.personalization.settings.condition.abstract, { - getName: function () { - return t("target_group"); - }, - - getPanel: function (panel, data) { - var id = Ext.id(); - - return new Ext.form.FormPanel({ - id: id, - forceLayout: true, - style: "margin: 10px 0 0 0", - bodyStyle: "padding: 10px 30px 10px 30px; min-height:40px;", - tbar: pimcore.bundle.personalization.settings.conditions.getTopBar(this, id, panel, data), - items: [{ - xtype: "combo", - name: "targetGroup", - displayField: 'text', - valueField: "id", - store: pimcore.globalmanager.get("target_group_store"), - width: 400, - triggerAction: 'all', - listWidth: 200, - mode: "local", - forceSelection: true, - queryMode: 'local', - autoComplete: false, - value: data.targetGroup, - emptyText: t("select_a_target_group"), - fieldLabel: t("target_group") - }, { - xtype: "hidden", - name: "type", - value: "target_group" - }] - }); - } - }) - }; - - return { - register: function (name, condition) { - conditions[name] = condition; - }, - - create: function (name) { - var conditionClass = this.get(name); - - return new conditionClass(); - }, - - getSearchUrl: function (query) { - var url = pimcore.settings.geocoding_url_template.replace('{q}', urlencode(query)); - return url; - }, - - get: function (name) { - if ('undefined' === typeof conditions[name]) { - throw new Error('Condition ' + name + ' is not defined', name); - } - - return conditions[name]; - }, - - getConditions: function () { - return conditions; - }, - - getKeys: function() { - return Object.keys(conditions); - }, - - getTopBar: function (condition, index, parent, data) { - var detectBlockIndex = function(blockElement, container) { - var index; - for (var s = 0; s < container.items.items.length; s++) { - if (container.items.items[s].getId() === blockElement.getId()) { - index = s; - break; - } - } - - return index; - }; - - var toggleGroup = "g_" + index + parent.data.id; - if (!data["operator"]) { - data.operator = "and"; - } - - return [ - { - iconCls: condition.getIconCls(), - disabled: true - }, - { - xtype: "tbtext", - text: "" + condition.getName() + "" - }, - "-", - { - iconCls: "pimcore_icon_up", - handler: function (blockId, parent) { - - var container = parent.conditionsContainer; - var blockElement = Ext.getCmp(blockId); - var index = detectBlockIndex(blockElement, container); - - var newIndex = index - 1; - if (newIndex < 0) { - newIndex = 0; - } - - container.remove(blockElement, false); - container.insert(newIndex, blockElement); - - parent.recalculateButtonStatus(); - parent.recalculateBracketIdent(parent.conditionsContainer.items); - - pimcore.layout.refresh(); - }.bind(window, index, parent) - }, - { - iconCls: "pimcore_icon_down", - handler: function (blockId, parent) { - var container = parent.conditionsContainer; - var blockElement = Ext.getCmp(blockId); - var index = detectBlockIndex(blockElement, container); - - container.remove(blockElement, false); - container.insert(index + 1, blockElement); - - parent.recalculateButtonStatus(); - parent.recalculateBracketIdent(parent.conditionsContainer.items); - - pimcore.layout.refresh(); - }.bind(window, index, parent) - }, - "-", - { - text: t("AND"), - toggleGroup: toggleGroup, - enableToggle: true, - itemId: "toggle_and", - pressed: (data.operator === "and") - }, - { - text: t("OR"), - toggleGroup: toggleGroup, - enableToggle: true, - itemId: "toggle_or", - pressed: (data.operator === "or") - }, { - text: t("AND_NOT"), - toggleGroup: toggleGroup, - enableToggle: true, - itemId: "toggle_and_not", - pressed: (data.operator === "and_not") - }, - "->", - { - iconCls: "pimcore_icon_delete", - handler: function (index, parent) { - parent.conditionsContainer.remove(Ext.getCmp(index)); - parent.recalculateButtonStatus(); - parent.recalculateBracketIdent(parent.conditionsContainer.items); - }.bind(window, index, parent) - } - ]; - } - }; -}()); diff --git a/bundles/PersonalizationBundle/public/js/settings/rules/item.js b/bundles/PersonalizationBundle/public/js/settings/rules/item.js deleted file mode 100644 index d76f2b404e4..00000000000 --- a/bundles/PersonalizationBundle/public/js/settings/rules/item.js +++ /dev/null @@ -1,462 +0,0 @@ -/** - * Pimcore - * - * This source file is available under two different licenses: - * - GNU General Public License version 3 (GPLv3) - * - Pimcore Commercial License (PCL) - * Full copyright and license information is available in - * LICENSE.md which is distributed with this source code. - * - * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org) - * @license http://www.pimcore.org/license GPLv3 and PCL - */ - -pimcore.registerNS("pimcore.bundle.personalization.settings.rules.item"); -/** - * @private - */ -pimcore.bundle.personalization.settings.rules.item = Class.create({ - initialize: function(parent, data) { - this.parent = parent; - this.data = data; - this.currentIndex = 0; - - this.tabPanel = new Ext.TabPanel({ - activeTab: 0, - title: this.data.name, - closable: true, - deferredRender: false, - forceLayout: true, - id: "pimcore_targeting_panel_" + this.data.id, - buttons: [{ - text: t("save"), - iconCls: "pimcore_icon_apply", - handler: this.save.bind(this) - }], - items: [ - this.getSettings(), - this.getConditions(), - this.getActions() - ] - }); - - // fill data into conditions and actions - this.initializeConditions(); - this.initializeActions(); - - this.parent.panel.add(this.tabPanel); - this.parent.panel.setActiveTab(this.tabPanel); - this.parent.panel.updateLayout(); - }, - - initializeConditions: function() { - var condition; - if (this.data.conditions && this.data.conditions.length > 0) { - for (var i = 0; i < this.data.conditions.length; i++) { - try { - condition = pimcore.bundle.personalization.settings.conditions.create(this.data.conditions[i].type); - } catch (e) { - console.error(e); - continue; - } - - if (!condition.matchesScope('targeting_rule')) { - console.error('Condition ', this.data.conditions[i].type, 'does not match rule scope'); - continue; - } - - this.addCondition(condition, this.data.conditions[i]); - } - } - }, - - initializeActions: function() { - var action; - if (this.data.actions && this.data.actions.length > 0) { - for (var i = 0; i < this.data.actions.length; i++) { - try { - action = pimcore.bundle.personalization.settings.actions.create(this.data.actions[i].type); - } catch (e) { - console.error(e); - continue; - } - - this.addAction(action, this.data.actions[i]); - } - } - }, - - getSettings: function () { - this.settingsForm = new Ext.form.FormPanel({ - title: t("settings"), - bodyStyle: "padding:10px;", - autoScroll: true, - border:false, - items: [{ - xtype: "textfield", - fieldLabel: t("name"), - name: "name", - width: 350, - value: this.data.name - }, { - name: "description", - fieldLabel: t("description"), - xtype: "textarea", - width: 500, - height: 100, - value: this.data.description - }, { - name: "scope", - fieldLabel: t("action_scope"), - xtype: "combo", - width: 350, - value: this.data["scope"], - mode: "local", - triggerAction: "all", - editable: false, - store: [ - ["hit", t("hit")], - ["session", t("session")], - ["session_with_variables", t("session_with_variables")], - ["visitor", t("targeting_visitor")] - ] - }, { - name: "active", - fieldLabel: t("active"), - xtype: "checkbox", - checked: this.data["active"] - }] - }); - - return this.settingsForm; - }, - - getConditions: function() { - var createHandler = function(condition) { - return this.addCondition.bind(this, condition); - }.bind(this); - - var addMenu = []; - Ext.Array.forEach(pimcore.bundle.personalization.settings.conditions.getKeys(), function(key) { - var condition; - - try { - condition = pimcore.bundle.personalization.settings.conditions.create(key); - } catch (e) { - console.error(e); - return; - } - - if (!condition.matchesScope('targeting_rule')) { - return; - } - - addMenu.push({ - iconCls: condition.getIconCls(), - text: condition.getName(), - disabled: !condition.isAvailable(), - handler: createHandler(condition) - }); - }); - - this.sortAddMenu(addMenu); - - this.conditionsContainer = new Ext.Panel({ - title: t("conditions"), - autoScroll: true, - forceLayout: true, - tbar: [{ - iconCls: "pimcore_icon_add", - menu: addMenu - }], - border: false - }); - - return this.conditionsContainer; - }, - - getActions: function () { - var createHandler = function(action) { - return this.addAction.bind(this, action); - }.bind(this); - - var addMenu = []; - Ext.Array.forEach(pimcore.bundle.personalization.settings.actions.getKeys(), function(key) { - var action; - - try { - action = pimcore.bundle.personalization.settings.actions.create(key); - } catch (e) { - console.error(e); - return; - } - - addMenu.push({ - iconCls: action.getIconCls(), - text: action.getName(), - handler: createHandler(action) - }); - }); - - this.sortAddMenu(addMenu); - - this.actionsContainer = new Ext.Panel({ - title: t("actions"), - autoScroll: true, - forceLayout: true, - bodyStyle: 'padding: 0 10px 10px 10px;', - tbar: [{ - iconCls: "pimcore_icon_add", - menu: addMenu - }], - border: false - }); - - return this.actionsContainer; - }, - - sortAddMenu: function(menu) { - menu.sort(function(a, b) { - var nameA = a.text.toUpperCase(); - var nameB = b.text.toUpperCase(); - - if (nameA < nameB) { - return -1; - } - - if (nameA > nameB) { - return 1; - } - - return 0; - }); - }, - - addCondition: function (condition, data) { - if ('undefined' === typeof data) { - data = {}; - } - - var item = condition.getPanel(this, data); - - // add logic for brackets - var tab = this; - item.on("afterrender", function (el) { - el.getEl().applyStyles({position: "relative", "min-height": "40px", "border-bottom": "1px solid #d0d0d0"}); - var leftBracket = el.getEl().insertHtml("beforeEnd", - '
(
', true); - var rightBracket = el.getEl().insertHtml("beforeEnd", - '
)
', true); - - if (data["bracketLeft"]) { - leftBracket.addCls("pimcore_targeting_bracket_active"); - } - - if (data["bracketRight"]) { - rightBracket.addCls("pimcore_targeting_bracket_active"); - } - - // open - leftBracket.on("click", function (ev, el) { - var bracket = Ext.get(el); - bracket.toggleCls("pimcore_targeting_bracket_active"); - - tab.recalculateBracketIdent(tab.conditionsContainer.items); - }); - - // close - rightBracket.on("click", function (ev, el) { - var bracket = Ext.get(el); - bracket.toggleCls("pimcore_targeting_bracket_active"); - - tab.recalculateBracketIdent(tab.conditionsContainer.items); - }); - - // make ident - tab.recalculateBracketIdent(tab.conditionsContainer.items); - }); - - this.conditionsContainer.add(item); - item.updateLayout(); - this.conditionsContainer.updateLayout(); - - this.currentIndex++; - - this.recalculateButtonStatus(); - }, - - addAction: function(action, data) { - if ('undefined' === typeof data) { - data = {}; - } - - var item = action.getPanel(this, data); - - this.actionsContainer.add(item); - item.updateLayout(); - this.actionsContainer.updateLayout(); - }, - - save: function () { - var saveData = { - settings: this.settingsForm.getForm().getFieldValues(), - conditions: this.getConditionData(), - actions: this.getActionData() - }; - - Ext.Ajax.request({ - url: Routing.generate('pimcore_bundle_personalization_targeting_rulesave'), - method: 'PUT', - params: { - id: this.data.id, - data: Ext.encode(saveData) - }, - success: function () { - this.parent.getTree().getStore().load(); - pimcore.helpers.showNotification(t("success"), t("saved_successfully"), "success"); - }.bind(this) - }); - }, - - getConditionData: function () { - var condition, - tb; - - var conditions = this.conditionsContainer.items.getRange(); - - var conditionData = []; - for (var i = 0; i < conditions.length; i++) { - condition = conditions[i].getForm().getFieldValues(); - - // get the operator (AND, OR, AND_NOT) - tb = conditions[i].getDockedItems()[0]; - if (tb.getComponent("toggle_or").pressed) { - condition.operator = "or"; - } else if (tb.getComponent("toggle_and_not").pressed) { - condition.operator = "and_not"; - } else { - condition.operator = "and"; - } - - // get the brackets - condition.bracketLeft = Ext.get(conditions[i].getEl().query(".pimcore_targeting_bracket_left")[0]) - .hasCls("pimcore_targeting_bracket_active"); - - condition.bracketRight = Ext.get(conditions[i].getEl().query(".pimcore_targeting_bracket_right")[0]) - .hasCls("pimcore_targeting_bracket_active"); - - conditionData.push(condition); - } - - return conditionData; - }, - - getActionData: function() { - var actions = this.actionsContainer.items.getRange(); - - var actionData = []; - for (var i = 0; i < actions.length; i++) { - actionData.push(actions[i].getForm().getFieldValues()); - } - - return actionData; - }, - - recalculateButtonStatus: function () { - var conditions = this.conditionsContainer.items.getRange(); - var tb; - - for (var i = 0; i < conditions.length; i++) { - tb = conditions[i].getDockedItems()[0]; - - if (i === 0) { - tb.getComponent("toggle_and").hide(); - tb.getComponent("toggle_or").hide(); - tb.getComponent("toggle_and_not").hide(); - } else { - tb.getComponent("toggle_and").show(); - tb.getComponent("toggle_or").show(); - tb.getComponent("toggle_and_not").show(); - } - } - }, - - /** - * make ident for bracket - * @param list - */ - recalculateBracketIdent: function(list) { - var ident = 0, lastIdent = 0, margin = 20; - var colors = ["transparent","#007bff", "#00ff99", "#e1a6ff", "#ff3c00", "#000000"]; - - list.each(function (condition) { - - // only rendered conditions - if(condition.rendered == false) { - return; - } - - // html from this condition - var item = condition.getEl(); - - - // apply ident margin - item.applyStyles({ - "margin-left": margin * ident + "px", - "margin-right": margin * ident + "px" - }); - - - // apply colors - if(ident > 0) { - item.applyStyles({ - "border-left": "1px solid " + colors[ident], - "border-right": "1px solid " + colors[ident] - }); - } else { - item.applyStyles({ - "border-left": "0px", - "border-right": "0px" - }); - } - - - // apply specials :-) - if(ident == 0) { - item.applyStyles({ - "margin-top": "10px" - }); - } else if(ident == lastIdent) { - item.applyStyles({ - "margin-top": "0px", - "margin-bottom": "0px" - }); - } else { - item.applyStyles({ - "margin-top": "5px" - }); - } - - - // remember current ident - lastIdent = ident; - - - // check if a bracket is open - if(item.select('.pimcore_targeting_bracket_left.pimcore_targeting_bracket_active').getCount() == 1) - { - ident++; - } - // check if a bracket is close - else if(item.select('.pimcore_targeting_bracket_right.pimcore_targeting_bracket_active').getCount() == 1) - { - if(ident > 0) { - ident--; - } - } - }); - - this.conditionsContainer.updateLayout(); - } -}); - diff --git a/bundles/PersonalizationBundle/public/js/settings/rules/panel.js b/bundles/PersonalizationBundle/public/js/settings/rules/panel.js deleted file mode 100644 index 16a5f29d199..00000000000 --- a/bundles/PersonalizationBundle/public/js/settings/rules/panel.js +++ /dev/null @@ -1,275 +0,0 @@ -/** - * Pimcore - * - * This source file is available under two different licenses: - * - GNU General Public License version 3 (GPLv3) - * - Pimcore Commercial License (PCL) - * Full copyright and license information is available in - * LICENSE.md which is distributed with this source code. - * - * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org) - * @license http://www.pimcore.org/license GPLv3 and PCL - */ - -pimcore.registerNS("pimcore.bundle.personalization.settings.rules.panel"); -/** - * @private - */ -pimcore.bundle.personalization.settings.rules.panel= Class.create({ - - initialize: function() { - this.treeDataUrl = Routing.generate('pimcore_bundle_personalization_targeting_rulelist'); - }, - - - getLayout: function () { - - if (this.layout == null) { - this.layout = new Ext.Panel({ - title: t('global_targeting_rules'), - layout: "border", - closable: true, - border: false, - iconCls: "pimcore_icon_targeting", - items: [ - this.getTree(), - this.getTabPanel() - ] - }); - } - - return this.layout; - }, - - getTree: function () { - if (!this.tree) { - this.saveButton = new Ext.Button({ - // save button - hidden: true, - text: t("save_order"), - iconCls: "pimcore_icon_save", - handler: function () { - // this - var button = this; - - // get current order - var prio = 0; - var rules = {}; - - this.ownerCt.ownerCt.getRootNode().eachChild(function (rule) { - prio++; - rules[rule.id] = prio; - }); - - // save order - Ext.Ajax.request({ - url: Routing.generate('pimcore_bundle_personalization_targeting_ruleorder'), - params: { - rules: Ext.encode(rules) - }, - method: "post", - success: function () { - button.hide(); - } - }); - } - }); - - var store = Ext.create('Ext.data.TreeStore', { - autoLoad: false, - autoSync: true, - proxy: { - type: 'ajax', - url: this.treeDataUrl, - reader: { - type: 'json' - } - } - }); - - this.tree = new Ext.tree.TreePanel({ - store: store, - region: "west", - useArrows: true, - autoScroll: true, - animate: true, - containerScroll: true, - width: 200, - split: true, - rootVisible: false, - root: { - id: '0' - }, - viewConfig: { - plugins: { - ptype: 'treeviewdragdrop' - } - }, - tbar: { - cls: 'pimcore_toolbar_border_bottom', - items: [ - { - text: t("add"), - iconCls: "pimcore_icon_add", - handler: this.addTarget.bind(this) - }, - this.saveButton - ] - }, - listeners: this.getTreeNodeListeners() - }); - - } - - return this.tree; - }, - - getTreeNodeListeners: function () { - var treeNodeListeners = { - 'itemclick': this.onTreeNodeClick.bind(this), - "itemcontextmenu": this.onTreeNodeContextmenu.bind(this), - "itemmove": function(tree, oldParent, newParent, index, eOpts ) { - this.saveButton.show(); - }.bind(this), - "render": function () { - this.getRootNode().expand(); - }, - 'beforeitemappend': function (thisNode, newChildNode, index, eOpts) { - var classes = []; - var iconClasses = ['pimcore_icon_targeting']; - - if (!newChildNode.data.active) { - classes.push('pimcore_unpublished'); - } - - //newChildNode.data.expanded = true; - newChildNode.data.leaf = true; - newChildNode.data.cls = classes.join(' '); - newChildNode.data.iconCls = iconClasses.join(' '); - } - }; - - return treeNodeListeners; - }, - - addTarget: function () { - Ext.MessageBox.prompt(' ', t('enter_the_name_of_the_new_item'), - this.addTargetComplete.bind(this), null, null, ""); - }, - - addTargetComplete: function (button, value, object) { - - var regresult = value.match(/[a-zA-Z0-9_\-]+/); - if (button == "ok" && value.length > 2 && regresult == value) { - Ext.Ajax.request({ - url: Routing.generate('pimcore_bundle_personalization_targeting_ruleadd'), - method: 'POST', - params: { - name: value - }, - success: function (response) { - var data = Ext.decode(response.responseText); - - this.tree.getStore().load(); - - if(!data || !data.success) { - Ext.Msg.alert(' ', t('failed_to_create_new_item')); - } else { - this.openTarget(intval(data.id)); - } - }.bind(this) - }); - } else if (button == "cancel") { - return; - } - else { - Ext.Msg.alert(' ', t('naming_requirements_3chars')); - } - }, - - - - onTreeNodeClick: function (tree, record, item, index, e, eOpts ) { - this.openTarget(record.data); - }, - - - deleteTarget: function (tree, record) { - pimcore.helpers.deleteConfirm(t('global_targeting_rule'), record.data.text, function () { - Ext.Ajax.request({ - url: Routing.generate('pimcore_bundle_personalization_targeting_ruledelete'), - method: 'DELETE', - params: { - id: record.data.id - }, - success: function () { - this.tree.getStore().load(); - }.bind(this) - }); - }.bind(this)); - }, - - onTreeNodeContextmenu: function (tree, record, item, index, e, eOpts ) { - tree.select(); - - var menu = new Ext.menu.Menu(); - menu.add(new Ext.menu.Item({ - text: t('delete'), - iconCls: "pimcore_icon_delete", - handler: this.deleteTarget.bind(this, tree, record) - })); - - e.stopEvent(); - menu.showAt(e.pageX, e.pageY); - }, - - - openTarget: function (node) { - - if(!is_numeric(node)) { - node = node.id; - } - - - var existingPanel = Ext.getCmp("pimcore_targeting_panel_" + node); - if(existingPanel) { - this.panel.setActiveItem(existingPanel); - return; - } - - Ext.Ajax.request({ - url: Routing.generate('pimcore_bundle_personalization_targeting_ruleget'), - params: { - id: node - }, - success: function (response) { - try { - var res = Ext.decode(response.responseText); - var item = new pimcore.bundle.personalization.settings.rules.item(this, res); - } catch (e) { - console.log(e); - } - }.bind(this) - }); - - }, - - getTabPanel: function () { - if (!this.panel) { - this.panel = new Ext.TabPanel({ - region: "center", - border: false, - plugins: - [ - Ext.create('Ext.ux.TabCloseMenu', { - showCloseAll: true, - showCloseOthers: true - }), - Ext.create('Ext.ux.TabReorderer', {}) - ] - }); - } - - return this.panel; - } -}); diff --git a/bundles/PersonalizationBundle/public/js/settings/targetGroups/item.js b/bundles/PersonalizationBundle/public/js/settings/targetGroups/item.js deleted file mode 100644 index 1967aef1ce6..00000000000 --- a/bundles/PersonalizationBundle/public/js/settings/targetGroups/item.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Pimcore - * - * This source file is available under two different licenses: - * - GNU General Public License version 3 (GPLv3) - * - Pimcore Commercial License (PCL) - * Full copyright and license information is available in - * LICENSE.md which is distributed with this source code. - * - * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org) - * @license http://www.pimcore.org/license GPLv3 and PCL - */ - -pimcore.registerNS("pimcore.bundle.personalization.settings.targetGroups.item"); -/** - * @private - */ -pimcore.bundle.personalization.settings.targetGroups.item = Class.create({ - - initialize: function(parent, data) { - this.parent = parent; - this.data = data; - this.currentIndex = 0; - - var panel = this.getSettings(); - - this.parent.panel.add(panel); - this.parent.panel.setActiveTab(panel); - this.parent.panel.updateLayout(); - }, - - getSettings: function () { - this.settingsForm = new Ext.form.FormPanel({ - id: "pimcore_target_groups_panel_" + this.data.id, - title: this.data.name, - closable: true, - deferredRender: false, - forceLayout: true, - bodyStyle: "padding:10px;", - autoScroll: true, - border:false, - buttons: [{ - text: t("save"), - iconCls: "pimcore_icon_apply", - handler: this.save.bind(this) - }], - items: [{ - xtype: "textfield", - fieldLabel: t("name"), - name: "name", - width: 350, - disabled: true, - value: this.data.name - }, { - name: "description", - fieldLabel: t("description"), - xtype: "textarea", - width: 500, - height: 100, - value: this.data.description - }, { - name: "threshold", - fieldLabel: t("threshold"), - xtype: "numberfield", - value: this.data["threshold"], - minValue: 1, - allowDecimals: false - }, { - name: "active", - fieldLabel: t("active"), - xtype: "checkbox", - checked: this.data["active"] - }] - }); - - return this.settingsForm; - }, - - save: function () { - var saveData = { - settings: this.settingsForm.getForm().getFieldValues() - }; - - Ext.Ajax.request({ - url: Routing.generate('pimcore_bundle_personalization_targeting_targetgroupsave'), - method: 'PUT', - params: { - id: this.data.id, - data: Ext.encode(saveData) - }, - success: function () { - this.parent.getTree().getStore().load(); - pimcore.helpers.showNotification(t("success"), t("saved_successfully"), "success"); - }.bind(this) - }); - } -}); - diff --git a/bundles/PersonalizationBundle/public/js/settings/targetGroups/panel.js b/bundles/PersonalizationBundle/public/js/settings/targetGroups/panel.js deleted file mode 100644 index a3a98fc5a47..00000000000 --- a/bundles/PersonalizationBundle/public/js/settings/targetGroups/panel.js +++ /dev/null @@ -1,227 +0,0 @@ -/** - * Pimcore - * - * This source file is available under two different licenses: - * - GNU General Public License version 3 (GPLv3) - * - Pimcore Commercial License (PCL) - * Full copyright and license information is available in - * LICENSE.md which is distributed with this source code. - * - * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org) - * @license http://www.pimcore.org/license GPLv3 and PCL - */ - -pimcore.registerNS("pimcore.bundle.personalization.settings.targetGroups.panel"); -/** - * @private - */ -pimcore.bundle.personalization.settings.targetGroups.panel = Class.create({ - - initialize: function() { - this.treeDataUrl = Routing.generate('pimcore_bundle_personalization_targeting_targetgrouplist'); - }, - - getLayout: function () { - - if (this.layout == null) { - this.layout = new Ext.Panel({ - title: t('target_groups'), - layout: "border", - closable: true, - border: false, - iconCls: "pimcore_icon_target_groups", - items: [this.getTree(), this.getTabPanel()] - }); - } - - return this.layout; - }, - - getTree: function () { - if (!this.tree) { - var store = Ext.create('Ext.data.TreeStore', { - autoLoad: false, - autoSync: true, - proxy: { - type: 'ajax', - url: this.treeDataUrl, - reader: { - type: 'json' - } - } - }); - - this.tree = new Ext.tree.TreePanel({ - store: store, - region: "west", - autoScroll:true, - animate:false, - containerScroll: true, - width: 200, - split: true, - root: { - id: '0' - }, - listeners: this.getTreeNodeListeners(), - rootVisible: false, - tbar: { - cls: 'pimcore_toolbar_border_bottom', - items: [ - { - text: t("add"), - iconCls: "pimcore_icon_add", - handler: this.addTargetGroup.bind(this) - } - ] - } - }); - - } - - return this.tree; - }, - - getTreeNodeListeners: function () { - var treeNodeListeners = { - 'itemclick': this.onTreeNodeClick.bind(this), - "itemcontextmenu": this.onTreeNodeContextmenu.bind(this), - "render": function () { - this.getRootNode().expand(); - }, - 'beforeitemappend': function (thisNode, newChildNode, index, eOpts) { - var classes = []; - var iconClasses = ['pimcore_icon_target_groups']; - - if (!newChildNode.data.active) { - classes.push('pimcore_unpublished'); - } - - //newChildNode.data.expanded = true; - newChildNode.data.leaf = true; - newChildNode.data.cls = classes.join(' '); - newChildNode.data.iconCls = iconClasses.join(' '); - } - }; - return treeNodeListeners; - }, - - - - onTreeNodeContextmenu: function (tree, record, item, index, e, eOpts ) { - tree.select(); - - var menu = new Ext.menu.Menu(); - menu.add(new Ext.menu.Item({ - text: t('delete'), - iconCls: "pimcore_icon_delete", - handler: this.deleteTargetGroup.bind(this, tree, record) - })); - - e.stopEvent(); - menu.showAt(e.pageX, e.pageY); - }, - - addTargetGroup: function () { - Ext.MessageBox.prompt(' ', t('enter_the_name_of_the_new_item'), - this.addTargetGroupComplete.bind(this), null, null, ""); - }, - - - onTreeNodeClick: function (tree, record, item, index, e, eOpts ) { - this.openTargetGroup(record.data); - }, - - - addTargetGroupComplete: function (button, value, object) { - - if (button == "ok" && value.length > 2) { - Ext.Ajax.request({ - url: Routing.generate('pimcore_bundle_personalization_targeting_targetgroupadd'), - method: 'POST', - params: { - name: value - }, - success: function (response) { - var data = Ext.decode(response.responseText); - - this.tree.getStore().reload(); - - if(!data || !data.success) { - Ext.Msg.alert(' ', t('failed_to_create_new_item')); - } else { - this.openTargetGroup(intval(data.id)); - - pimcore.globalmanager.get("target_group_store").reload(); - } - }.bind(this) - }); - } else if (button == "cancel") { - return; - } - else { - Ext.Msg.alert(' ', t('naming_requirements_3chars')); - } - }, - - deleteTargetGroup: function (tree, record) { - pimcore.helpers.deleteConfirm(t('target_group'), record.data.text, function () { - Ext.Ajax.request({ - url: Routing.generate('pimcore_bundle_personalization_targeting_targetgroupdelete'), - method: 'DELETE', - params: { - id: record.data.id - }, - success: function () { - this.tree.getStore().load(); - - pimcore.globalmanager.get("target_group_store").reload(); - }.bind(this) - }); - }.bind(this)); - }, - - openTargetGroup: function (node) { - - if(!is_numeric(node)) { - node = node.id; - } - - - var existingPanel = Ext.getCmp("pimcore_target_groups_panel_" + node); - if(existingPanel) { - this.panel.setActiveItem(existingPanel); - return; - } - - Ext.Ajax.request({ - url: Routing.generate('pimcore_bundle_personalization_targeting_targetgroupget'), - params: { - id: node - }, - success: function (response) { - var res = Ext.decode(response.responseText); - var item = new pimcore.bundle.personalization.settings.targetGroups.item(this, res); - }.bind(this) - }); - - }, - - getTabPanel: function () { - if (!this.panel) { - this.panel = new Ext.TabPanel({ - region: "center", - border: false, - plugins: - [ - Ext.create('Ext.ux.TabCloseMenu', { - showCloseAll: true, - showCloseOthers: true - }), - Ext.create('Ext.ux.TabReorderer', {}) - ] - }); - } - - return this.panel; - } -}); diff --git a/bundles/PersonalizationBundle/public/js/settings/targetingtoolbar.js b/bundles/PersonalizationBundle/public/js/settings/targetingtoolbar.js deleted file mode 100644 index 0dbd6ed7308..00000000000 --- a/bundles/PersonalizationBundle/public/js/settings/targetingtoolbar.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Pimcore - * - * This source file is available under two different licenses: - * - GNU General Public License version 3 (GPLv3) - * - Pimcore Commercial License (PCL) - * Full copyright and license information is available in - * LICENSE.md which is distributed with this source code. - * - * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org) - * @license http://www.pimcore.org/license GPLv3 and PCL - */ - -pimcore.registerNS("pimcore.bundle.personalization.settings.targetingtoolbar"); -/** - * @private - */ -pimcore.bundle.personalization.settings.targetingtoolbar = Class.create({ - initialize: function () { - var that = this; - var cookieName = "pimcore_targeting_debug"; - - var buttons = []; - if ("1" === Ext.util.Cookies.get(cookieName)) { - buttons.push({ - text: t("deactivate"), - iconCls: "pimcore_icon_targeting_toolbar_disable", - handler: function () { - Ext.util.Cookies.clear(cookieName); - that.window.close(); - } - }); - } else { - buttons.push({ - text: t("activate"), - iconCls: "pimcore_icon_targeting_toolbar_enable", - handler: function () { - Ext.util.Cookies.set(cookieName, "1"); - that.window.close(); - } - }); - } - - this.window = new Ext.Window({ - layout: "fit", - width: 500, - closeAction: "close", - modal: true, - items: [{ - xtype: "panel", - border: false, - bodyStyle: "padding: 20px; font-size: 14px;", - html: t("targeting_toolbar_browser_note", null, - { - targetingLink: 'https://pimcore.com/docs/pimcore/11.0/Development_Documentation/Tools_and_Features/Targeting_and_Personalization/index.html#page_Debugging-Targeting-Data' - }) - }], - buttons: buttons - }); - - pimcore.viewport.add(this.window); - this.window.show(); - } -}); diff --git a/bundles/PersonalizationBundle/public/js/startup.js b/bundles/PersonalizationBundle/public/js/startup.js deleted file mode 100644 index 259408684e9..00000000000 --- a/bundles/PersonalizationBundle/public/js/startup.js +++ /dev/null @@ -1,142 +0,0 @@ -pimcore.registerNS("pimcore.bundle.personalization.startup"); - -pimcore.bundle.personalization.startup = Class.create({ - initialize: function () { - - // target groups - Ext.define('pimcore.model.target_groups', { - extend: 'Ext.data.Model', - fields: ["id", "text"] - }); - - var targetGroupStore = Ext.create('Ext.data.JsonStore', { - model: "pimcore.model.target_groups", - proxy: { - type: 'ajax', - url: Routing.generate('pimcore_bundle_personalization_targeting_targetgrouplist'), - reader: { - type: 'json' - } - } - }); - - targetGroupStore.load(); - pimcore.globalmanager.add("target_group_store", targetGroupStore); - - document.addEventListener(pimcore.events.preMenuBuild, this.preMenuBuild.bind(this)); - - document.addEventListener(pimcore.events.prepareDocumentPageSettingsLayout, this.assignTargetGroupToPage.bind(this)); - }, - - - preMenuBuild: function (e) { - let menu = e.detail.menu; - const user = pimcore.globalmanager.get('user'); - const perspectiveCfg = pimcore.globalmanager.get("perspective"); - - if (user.isAllowed("targeting") && perspectiveCfg.inToolbar("marketing.targeting")) { - menu.marketing.items.push({ - text: t("personalization") + " / " + t("targeting"), - iconCls: "pimcore_nav_icon_usergroup", - itemId: 'pimcore_menu_marketing_personalization', - hideOnClick: false, - menu: { - cls: "pimcore_navigation_flyout", - shadow: false, - items: [ - { - text: t("global_targeting_rules"), - iconCls: "pimcore_nav_icon_targeting", - itemId: 'pimcore_menu_marketing_personalization_global_targeting_rules', - handler: this.showTargetingRules - }, { - text: t('target_groups'), - iconCls: "pimcore_nav_icon_target_groups", - itemId: 'pimcore_menu_marketing_personalization_target_groups', - handler: this.showTargetGroups - }, { - text: t("targeting_toolbar"), - iconCls: "pimcore_nav_icon_targeting_toolbar", - itemId: 'pimcore_menu_marketing_personalization_targeting_toolbar', - handler: this.showTargetingToolbarSettings - } - ] - } - }); - } - }, - - showTargetingRules: function () { - var tabPanel = Ext.getCmp("pimcore_panel_tabs"); - try { - tabPanel.setActiveTab(pimcore.globalmanager.get("targeting").getLayout()); - } catch (e) { - var targeting = new pimcore.bundle.personalization.settings.rules.panel(); - pimcore.globalmanager.add("targeting", targeting); - - tabPanel.add(targeting.getLayout()); - tabPanel.setActiveTab(targeting.getLayout()); - - targeting.getLayout().on("destroy", function () { - pimcore.globalmanager.remove("targeting"); - }.bind(this)); - - pimcore.layout.refresh(); - } - }, - - showTargetGroups: function () { - var tabPanel = Ext.getCmp("pimcore_panel_tabs"); - try { - tabPanel.setActiveTab(pimcore.globalmanager.get("targetGroupsPanel").getLayout()); - } catch (e) { - var targetGroups = new pimcore.bundle.personalization.settings.targetGroups.panel(); - pimcore.globalmanager.add("targetGroupsPanel", targetGroups); - - tabPanel.add(targetGroups.getLayout()); - tabPanel.setActiveTab(targetGroups.getLayout()); - - targetGroups.getLayout().on("destroy", function () { - pimcore.globalmanager.remove("targetGroupsPanel"); - }.bind(this)); - - pimcore.layout.refresh(); - } - }, - - showTargetingToolbarSettings: function () { - new pimcore.bundle.personalization.settings.targetingtoolbar(); - }, - - assignTargetGroupToPage: function (e) { - const document = e.detail.document; - - const assignTargetGroupBlock = { - xtype: 'fieldset', - title: t('assign_target_groups'), - collapsible: true, - autoHeight: true, - defaults: { - labelWidth: 300 - }, - defaultType: 'textfield', - items: [ - Ext.create('Ext.ux.form.MultiSelect', { - fieldLabel: t('visitors_of_this_page_will_be_automatically_associated_with_the_selected_target_groups'), - store: pimcore.globalmanager.get("target_group_store"), - displayField: "text", - valueField: "id", - name: 'targetGroupIds', - width: 700, - //listWidth: 200, - value: document.data["targetGroupIds"].split(',').map(Number).filter(item => item), - minHeight: 100 - }) - ] - }; - - e.detail.layout.add(assignTargetGroupBlock); - } -}) - -const personalization = new pimcore.bundle.personalization.startup(); diff --git a/bundles/PersonalizationBundle/public/js/targeting.js b/bundles/PersonalizationBundle/public/js/targeting.js deleted file mode 100644 index c1edd4df584..00000000000 --- a/bundles/PersonalizationBundle/public/js/targeting.js +++ /dev/null @@ -1,405 +0,0 @@ -(function () { - window._ptg = window._ptg || {}; - - var cookieNames = { - visitorId: '_pc_vis', - sessionId: '_pc_ses', - visitorIdHistory: '_pc_vis_h' - }; - - // see http://clubmate.fi/setting-and-reading-cookies-with-javascript/ - var Cookie = { - set: function (name, value, days) { - var expires; - if (days) { - var date = new Date(); - date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); - expires = "; expires=" + date.toGMTString(); - } - else { - expires = ""; - } - - document.cookie = name + "=" + value + expires + "; path=/"; - }, - - get: function (name) { - var nameEQ = name + "="; - var ca = document.cookie.split(';'); - for (var i = 0; i < ca.length; i++) { - var c = ca[i]; - while (c.charAt(0) === ' ') { - c = c.substring(1, c.length); - } - if (c.indexOf(nameEQ) === 0) { - return c.substring(nameEQ.length, c.length); - } - } - - return null; - } - }; - - var util = { - merge: function () { - if (arguments.length === 0) { - return {}; - } - - var result = arguments[0], - obj, prop; - - for (var i = 1; i < arguments.length; i++) { - obj = arguments[i]; - - for (prop in obj) { - if (obj.hasOwnProperty(prop)) { - result[prop] = obj[prop]; - } - } - } - - return result; - }, - - featureDetect: (function () { - // most tests come from to modernizr (MIT license) - // https://github.com/Modernizr/Modernizr/tree/master/feature-detects - var tests = { - localStorage: function () { - var v = 'test'; - - try { - localStorage.setItem(v, v); - localStorage.removeItem(v); - - return true; - } catch (e) { - return false; - } - }, - - sessionStorage: function () { - var v = 'test'; - - try { - sessionStorage.setItem(v, v); - sessionStorage.removeItem(v); - - return true; - } catch (e) { - return false; - } - }, - - json: function () { - return 'JSON' in window && 'parse' in JSON && 'stringify' in JSON; - }, - - geolocation: function () { - return 'geolocation' in navigator; - } - }; - - var results = {}; - - return function (type) { - if ('undefined' === typeof tests[type]) { - throw new Error('Test ' + type + ' is not defined'); - } - - if ('undefined' === typeof results[type]) { - results[type] = tests[type].call(); - } - - return results[type]; - }; - }()), - - logger: { - canLog: function (type) { - if (!_ptg.options.log) { - return false; - } - - if ('undefined' === typeof type) { - type = 'log'; - } - - return ('undefined' !== typeof window.console && 'function' === typeof window.console[type]); - } - }, - - listen: function (elem, evnt, func) { - if (elem.addEventListener) { // W3C DOM - elem.addEventListener(evnt, func, false); - } else if (elem.attachEvent) { // IE DOM - return elem.attachEvent("on" + evnt, func); - } - }, - - contentLoaded: function (win, fn) { - var done = false, top = true, - - doc = win.document, root = doc.documentElement, - - add = doc.addEventListener ? 'addEventListener' : 'attachEvent', - rem = doc.addEventListener ? 'removeEventListener' : 'detachEvent', - pre = doc.addEventListener ? '' : 'on', - - init = function (e) { - if (e.type === 'readystatechange' && doc.readyState !== 'complete') { - return; - } - (e.type === 'load' ? win : doc)[rem](pre + e.type, init, false); - if (!done && (done = true)) { - fn.call(win, e.type || e); - } - }, - - poll = function () { - try { - root.doScroll('left'); - } catch (e) { - setTimeout(poll, 50); - return; - } - init('poll'); - }; - - if (doc.readyState === 'complete') { - fn.call(win, 'lazy'); - } else { - if (doc.createEventObject && root.doScroll) { - try { - top = !win.frameElement; - } catch (e) { - } - if (top) { - poll(); - } - } - doc[add](pre + 'DOMContentLoaded', init, false); - doc[add](pre + 'readystatechange', init, false); - win[add](pre + 'load', init, false); - } - } - }; - - var User = (function () { - var generateVisitorId = function (length) { - var chars = '0123456789abcdef'; - - var result = ''; - for (var i = length; i > 0; --i) { - result += chars[Math.floor(Math.random() * chars.length)]; - } - - return result; - }; - - var User = function () { - this.data = { - sessionId: null, - visitorId: null, - visitorIds: [], - activityLog: [] - }; - - this.load(); - - if (!this.data.visitorId) { - this.setVisitorId(generateVisitorId(16)); - this.save(); - } - }; - - User.prototype.setVisitorId = function (id) { - if (!id) { - return; - } - - if (this.data.visitorId) { - // don't do anything if ID is already set - if (id === this.data.visitorId) { - return; - } else { - // store last visitor ID in list - this.data.visitorIds.push(this.data.visitorId); - } - } - - util.logger.canLog('info') && console.info('[TARGETING] Setting visitor ID to', id); - - this.data.visitorId = id; - }; - - User.prototype.addActivityLog = function (data) { - data.sessionId = this.data.sessionId; - data.visitorId = this.data.visitorId; - data.timestamp = (new Date()).getTime(); - - util.logger.canLog('info') && console.info('[TARGETING] Track activity', data, this); - - this.data.activityLog.push(data); - - return this; - }; - - User.prototype.load = function () { - var data = {}; - - if (util.featureDetect('localStorage')) { - var storedData = localStorage.getItem("_ptg.user"); - data = JSON.parse(storedData); - } - - if (data) { - for (var key in data) { - if (data.hasOwnProperty(key)) { - this.data[key] = data[key]; - } - } - } - - var cookieVisitorId = Cookie.get(cookieNames.visitorId); - if (cookieVisitorId) { - this.setVisitorId(cookieVisitorId); - } - - // check / generate sessionId - var nowTimestamp = (new Date()).getTime(); - - if (0 === this.data.activityLog.length) { - this.data.sessionId = nowTimestamp; - util.logger.canLog('info') && console.info("[TARGETING] No previous activity. New sessionId: " + this.data.sessionId); - } else { - var lastActivity = this.data.activityLog.slice(-1)[0]; - - var sessionLength = _ptg.options.sessionLength; - if (!sessionLength) { - sessionLength = 30; - } - - if (lastActivity.timestamp < (nowTimestamp - (sessionLength * 60 * 1000))) { - this.data.sessionId = nowTimestamp; // session expired - util.logger.canLog('info') && console.info("[TARGETING] Previous session expired. New sessionId: " + this.data.sessionId); - } else { - this.data.sessionId = lastActivity.sessionId; - util.logger.canLog('info') && console.info("[TARGETING] SessionId present: " + this.data.sessionId); - } - } - }; - - User.prototype.save = function () { - if (util.featureDetect('localStorage')) { - localStorage.setItem("_ptg.user", JSON.stringify(this.data)); - } - - // set visitor ID cookie - if (this.data.visitorId) { - Cookie.set(cookieNames.visitorId, this.data.visitorId, 365); - } - - // set session ID cookie - if (this.data.sessionId) { - Cookie.set(cookieNames.sessionId, this.data.sessionId); - } - - // set cookie with last 10 visitor IDs - if (this.data.visitorIds.length > 0) { - Cookie.set(cookieNames.visitorIdHistory, this.data.visitorIds.slice(-5), 365); - } - }; - - return User; - }()); - - var dataProviders = { - geolocation: function () { - util.logger.canLog('info') && console.info('[TARGETING] Loading geolocation'); - - if (!util.featureDetect('geolocation') || !util.featureDetect('json') || !util.featureDetect('sessionStorage')) { - return; - } - - var cookieName = '_pc_tgl'; - - var location; - if (location = sessionStorage.getItem(cookieName)) { - Cookie.set(cookieName, location); - } else { - navigator.geolocation.getCurrentPosition(function (position) { - var serialized = JSON.stringify({ - lat: position.coords.latitude, - long: position.coords.longitude, - alt: position.coords.altitude - }); - - Cookie.set(cookieName, serialized); - sessionStorage.setItem(cookieName, serialized); // store to session storage for further usage - }, function (error) { - util.logger.canLog('error') && console.error('[TARGETING] Failed to load geolocation', error); - }); - } - } - }; - - _ptg.options = util.merge({ - log: false, - sessionLength: 30 - }, _ptg.options || {}); - - // merge core data providers with custom ones - _ptg.dataProviders = util.merge(dataProviders, _ptg.dataProviders || {}); - - var user = new User(); - - _ptg.api = { - setVisitorId: function (id) { - user.setVisitorId(id); - user.save(); - } - }; - - (function () { - if (_ptg.dataProviderKeys && _ptg.dataProviderKeys.length > 0) { - for (var dp = 0; dp < _ptg.dataProviderKeys.length; dp++) { - if (_ptg.dataProviders.hasOwnProperty(_ptg.dataProviderKeys[dp])) { - util.logger.canLog('info') && console.info("[TARGETING] Loading data from provider " + _ptg.dataProviderKeys[dp]); - _ptg.dataProviders[_ptg.dataProviderKeys[dp]].apply(this); - } - } - } - }()); - - util.contentLoaded(window, function () { - // track page views - user.addActivityLog({ - type: "pageView", - url: location.href - }).save(); - - // track links - try { - var linkElements = document.querySelectorAll("a"); - var linkClickHandler = function (ev) { - var el = ev.target ? ev.target : ev.srcElement; - var href = el.getAttribute("href"); - - if (href) { - user.addActivityLog({ - type: "linkClicked", - href: href - }).save(); - } - }; - - for (var le = 0; le < linkElements.length; le++) { - util.listen(linkElements[le], "click", linkClickHandler); - } - } catch (e) { - util.logger.canLog('error') && console.error(e); - } - }); -}()); diff --git a/bundles/PersonalizationBundle/src/Controller/Admin/TargetingController.php b/bundles/PersonalizationBundle/src/Controller/Admin/TargetingController.php deleted file mode 100644 index 87c28172e62..00000000000 --- a/bundles/PersonalizationBundle/src/Controller/Admin/TargetingController.php +++ /dev/null @@ -1,324 +0,0 @@ -|"%&@=;+]/', '-', $name); - } - - /** - * @Route("/rule/list", name="pimcore_bundle_personalization_targeting_rulelist", methods={"GET"}) - * - * @param Request $request - * - * @return JsonResponse - */ - public function ruleListAction(Request $request): JsonResponse - { - $targets = []; - - $list = new Targeting\Rule\Listing(); - $list->setOrderKey('prio'); - $list->setOrder('ASC'); - - foreach ($list->load() as $target) { - $targets[] = [ - 'id' => $target->getId(), - 'text' => htmlspecialchars($target->getName()), - 'active' => $target->getActive(), - 'qtip' => 'ID: ' . $target->getId(), - ]; - } - - return $this->adminJson($targets); - } - - /** - * @Route("/rule/add", name="pimcore_bundle_personalization_targeting_ruleadd", methods={"POST"}) - * - * @param Request $request - * - * @return JsonResponse - */ - public function ruleAddAction(Request $request): JsonResponse - { - $target = new Targeting\Rule(); - $target->setName($this->correctName($request->get('name'))); - $target->save(); - - return $this->adminJson(['success' => true, 'id' => $target->getId()]); - } - - /** - * @Route("/rule/delete", name="pimcore_bundle_personalization_targeting_ruledelete", methods={"DELETE"}) - * - * @param Request $request - * - * @return JsonResponse - */ - public function ruleDeleteAction(Request $request): JsonResponse - { - $success = false; - - $target = Targeting\Rule::getById((int) $request->get('id')); - if ($target) { - $target->delete(); - $success = true; - } - - return $this->adminJson(['success' => $success]); - } - - /** - * @Route("/rule/get", name="pimcore_bundle_personalization_targeting_ruleget", methods={"GET"}) - * - * @param Request $request - * - * @return JsonResponse - */ - public function ruleGetAction(Request $request): JsonResponse - { - $target = Targeting\Rule::getById((int) $request->get('id')); - if (!$target) { - throw $this->createNotFoundException(); - } - $target = $target->getObjectVars(); - - return $this->adminJson($target); - } - - /** - * @Route("/rule/save", name="pimcore_bundle_personalization_targeting_rulesave", methods={"PUT"}) - * - * @param Request $request - * - * @return JsonResponse - */ - public function ruleSaveAction(Request $request): JsonResponse - { - $data = $this->decodeJson($request->get('data')); - - $target = Targeting\Rule::getById((int) $request->get('id')); - if (!$target) { - throw $this->createNotFoundException(); - } - $target->setValues($data['settings']); - $target->setName($this->correctName($target->getName())); - $target->setConditions($data['conditions']); - $target->setActions($data['actions']); - $target->save(); - - return $this->adminJson(['success' => true]); - } - - /** - * @Route("/rule/order", name="pimcore_bundle_personalization_targeting_ruleorder", methods={"POST"}) - * - * @param Request $request - * - * @return JsonResponse - */ - public function ruleOrderAction(Request $request): JsonResponse - { - $return = [ - 'success' => false, - 'message' => '', - ]; - - $rules = $this->decodeJson($request->get('rules')); - - /** @var Targeting\Rule[] $changedRules */ - $changedRules = []; - foreach ($rules as $id => $prio) { - $rule = Targeting\Rule::getById((int)$id); - $prio = (int)$prio; - - if ($rule) { - if ($rule->getPrio() !== $prio) { - $rule->setPrio((int)$prio); - $changedRules[] = $rule; - } - } else { - $return['message'] = sprintf('Rule %d was not found', (int)$id); - - return $this->adminJson($return, 400); - } - } - - // save only changed rules - foreach ($changedRules as $changedRule) { - $changedRule->save(); - } - - $return['success'] = true; - - return $this->adminJson($return); - } - - // TARGET GROUPS - - /** - * @Route("/target-group/list", name="pimcore_bundle_personalization_targeting_targetgrouplist", methods={"GET"}) - * - * @param Request $request - * - * @return JsonResponse - */ - public function targetGroupListAction(Request $request): JsonResponse - { - $targetGroups = []; - - /** @var TargetGroup\Listing|TargetGroup\Listing\Dao $list */ - $list = new TargetGroup\Listing(); - - if ($request->get('add-default')) { - $targetGroups[] = [ - 'id' => 0, - 'text' => 'default', - 'active' => true, - 'qtip' => 0, - ]; - } - - foreach ($list->load() as $targetGroup) { - $targetGroups[] = [ - 'id' => $targetGroup->getId(), - 'text' => htmlspecialchars($targetGroup->getName()), - 'active' => $targetGroup->getActive(), - 'qtip' => $targetGroup->getId(), - ]; - } - - return $this->adminJson($targetGroups); - } - - /** - * @Route("/target-group/add", name="pimcore_bundle_personalization_targeting_targetgroupadd", methods={"POST"}) - * - * @param Request $request - * @param CoreCacheHandler $cache - * - * @return JsonResponse - */ - public function targetGroupAddAction(Request $request, CoreCacheHandler $cache): JsonResponse - { - /** @var TargetGroup|TargetGroup\Dao $targetGroup */ - $targetGroup = new TargetGroup(); - $targetGroup->setName($this->correctName($request->get('name'))); - $targetGroup->save(); - - $cache->clearTag('target_groups'); - - return $this->adminJson(['success' => true, 'id' => $targetGroup->getId()]); - } - - /** - * @Route("/target-group/delete", name="pimcore_bundle_personalization_targeting_targetgroupdelete", methods={"DELETE"}) - * - * @param Request $request - * @param CoreCacheHandler $cache - * - * @return JsonResponse - */ - public function targetGroupDeleteAction(Request $request, CoreCacheHandler $cache): JsonResponse - { - $success = false; - - $targetGroup = TargetGroup::getById((int) $request->get('id')); - if ($targetGroup) { - $targetGroup->delete(); - $success = true; - } - - $cache->clearTag('target_groups'); - - return $this->adminJson(['success' => $success]); - } - - /** - * @Route("/target-group/get", name="pimcore_bundle_personalization_targeting_targetgroupget", methods={"GET"}) - * - * @param Request $request - * - * @return JsonResponse - */ - public function targetGroupGetAction(Request $request): JsonResponse - { - $targetGroup = TargetGroup::getById((int) $request->get('id')); - if (!$targetGroup) { - throw $this->createNotFoundException(); - } - $targetGroup = $targetGroup->getObjectVars(); - - return $this->adminJson($targetGroup); - } - - /** - * @Route("/target-group/save", name="pimcore_bundle_personalization_targeting_targetgroupsave", methods={"PUT"}) - * - * @param Request $request - * @param CoreCacheHandler $cache - * - * @return JsonResponse - */ - public function targetGroupSaveAction(Request $request, CoreCacheHandler $cache): JsonResponse - { - $data = $this->decodeJson($request->get('data')); - - $targetGroup = TargetGroup::getById((int) $request->get('id')); - if (!$targetGroup) { - throw $this->createNotFoundException(); - } - $targetGroup->setValues($data['settings']); - $targetGroup->setName($this->correctName($targetGroup->getName())); - $targetGroup->save(); - - $cache->clearTag('target_groups'); - - return $this->adminJson(['success' => true]); - } - - public function onKernelControllerEvent(ControllerEvent $event): void - { - if (!$event->isMainRequest()) { - return; - } - - // check permissions - $this->checkActionPermission($event, 'targeting', ['targetGroupListAction']); - } -} diff --git a/bundles/PersonalizationBundle/src/Controller/Admin/TargetingPageController.php b/bundles/PersonalizationBundle/src/Controller/Admin/TargetingPageController.php deleted file mode 100644 index c8c0e021f0d..00000000000 --- a/bundles/PersonalizationBundle/src/Controller/Admin/TargetingPageController.php +++ /dev/null @@ -1,106 +0,0 @@ -request->getInt('targetGroup'); - $docId = $request->request->getInt('id'); - - $doc = Document\PageSnippet::getById($docId); - - if (!$doc) { - throw $this->createNotFoundException('Document not found'); - } - - foreach ($doc->getEditables() as $editable) { - if ($targetGroupId && $doc instanceof TargetingDocumentInterface) { - // remove target group specific elements - if (preg_match('/^' . preg_quote($doc->getTargetGroupEditablePrefix($targetGroupId), '/') . '/', $editable->getName())) { - $doc->removeEditable($editable->getName()); - } - } - } - - $this->saveToSession($doc, $request->getSession(), true); - - return $this->adminJson([ - 'success' => true, - ]); - } - - /** - * @Route("/save", name="pimcore_admin_document_page_save", methods={"PUT", "POST"}) - * - * @param Request $request - * @param StaticPageGenerator $staticPageGenerator - * - * @return JsonResponse - * - * @throws \Exception - */ - public function saveAction(Request $request, StaticPageGenerator $staticPageGenerator): JsonResponse - { - return parent::saveAction($request, $staticPageGenerator); - } - - protected function addDataToDocument(Request $request, Document $document): void - { - if ($document instanceof Document\PageSnippet) { - // if a target group variant get's saved, we have to load all other editables first, otherwise they will get deleted - - if ($request->get('appendEditables') - || ($document instanceof TargetingDocumentInterface)) { // ensure editable are loaded - $document->getEditables(); - } else { - // ensure no editables (e.g. from session, version, ...) are still referenced - $document->setEditables(null); - } - - if ($request->get('data')) { - $data = $this->decodeJson($request->get('data')); - foreach ($data as $name => $value) { - $data = $value['data'] ?? null; - $type = $value['type']; - $document->setRawEditable($name, $type, $data); - } - } - } - } -} diff --git a/bundles/PersonalizationBundle/src/Controller/Admin/TargetingSnippetController.php b/bundles/PersonalizationBundle/src/Controller/Admin/TargetingSnippetController.php deleted file mode 100644 index e873b336b81..00000000000 --- a/bundles/PersonalizationBundle/src/Controller/Admin/TargetingSnippetController.php +++ /dev/null @@ -1,104 +0,0 @@ -request->getInt('targetGroup'); - $docId = $request->request->getInt('id'); - - $doc = Document\PageSnippet::getById($docId); - - if (!$doc) { - throw $this->createNotFoundException('Document not found'); - } - - foreach ($doc->getEditables() as $editable) { - if ($targetGroupId && $doc instanceof TargetingDocumentInterface) { - // remove target group specific elements - if (preg_match('/^' . preg_quote($doc->getTargetGroupEditablePrefix($targetGroupId), '/') . '/', $editable->getName())) { - $doc->removeEditable($editable->getName()); - } - } - } - - $this->saveToSession($doc, $request->getSession(), true); - - return $this->adminJson([ - 'success' => true, - ]); - } - - /** - * @Route("/save", name="pimcore_admin_document_snippet_save", methods={"PUT", "POST"}) - * - * @param Request $request - * - * @return JsonResponse - * - * @throws \Exception - */ - public function saveAction(Request $request): JsonResponse - { - return parent::saveAction($request); - } - - protected function addDataToDocument(Request $request, Document $document): void - { - if ($document instanceof Document\PageSnippet) { - // if a target group variant get's saved, we have to load all other editables first, otherwise they will get deleted - - if ($request->get('appendEditables') - || ($document instanceof TargetingDocumentInterface)) { // ensure editable are loaded - $document->getEditables(); - } else { - // ensure no editables (e.g. from session, version, ...) are still referenced - $document->setEditables(null); - } - - if ($request->get('data')) { - $data = $this->decodeJson($request->get('data')); - foreach ($data as $name => $value) { - $data = $value['data'] ?? null; - $type = $value['type']; - $document->setRawEditable($name, $type, $data); - } - } - } - } -} diff --git a/bundles/PersonalizationBundle/src/DataCollector/PimcoreTargetingDataCollector.php b/bundles/PersonalizationBundle/src/DataCollector/PimcoreTargetingDataCollector.php deleted file mode 100644 index 6148fc71591..00000000000 --- a/bundles/PersonalizationBundle/src/DataCollector/PimcoreTargetingDataCollector.php +++ /dev/null @@ -1,109 +0,0 @@ -data = []; - - if (!$this->visitorInfoStorage->hasVisitorInfo()) { - return; - } - - $document = $this->documentResolver->getDocument($request); - $visitorInfo = $this->visitorInfoStorage->getVisitorInfo(); - $tdc = $this->targetingDataCollector; - - $data = [ - 'visitor_info' => $tdc->collectVisitorInfo($visitorInfo), - 'storage' => $tdc->collectStorage($visitorInfo), - 'rules' => $tdc->collectMatchedRules($visitorInfo), - 'target_groups' => $tdc->collectTargetGroups($visitorInfo), - 'document_target_group' => $tdc->collectDocumentTargetGroup($document), - 'document_target_groups' => $tdc->collectDocumentTargetGroupMapping(), - ]; - - $this->data = $this->cloneVar($data); - } - - public function reset(): void - { - $this->data = []; - } - - public function getVisitorInfo(): array|Data - { - return $this->data['visitor_info']; - } - - public function getStorage(): array|Data - { - return $this->data['storage']; - } - - public function getRules(): array|Data - { - return $this->data['rules']; - } - - public function getTargetGroups(): array|Data - { - return $this->data['target_groups']; - } - - public function getDocumentTargetGroup(): null|array|Data - { - return $this->data['document_target_group']; - } - - public function getDocumentTargetGroups(): array|Data - { - return $this->data['document_target_groups']; - } - - public function hasData(): bool - { - return !empty($this->data); - } -} diff --git a/bundles/PersonalizationBundle/src/Debug/Traits/StopwatchTrait.php b/bundles/PersonalizationBundle/src/Debug/Traits/StopwatchTrait.php deleted file mode 100644 index c026b8829d8..00000000000 --- a/bundles/PersonalizationBundle/src/Debug/Traits/StopwatchTrait.php +++ /dev/null @@ -1,56 +0,0 @@ -stopwatch = $stopwatch; - } - - private function startStopwatch(string $name, string $category): void - { - if ($this->stopwatch) { - $this->stopwatch->start($name, $category); - } - } - - private function stopStopwatch(string $name): void - { - if ($this->stopwatch) { - $this->stopwatch->stop($name); - } - } -} diff --git a/bundles/PersonalizationBundle/src/DependencyInjection/Compiler/DebugStopwatchPass.php b/bundles/PersonalizationBundle/src/DependencyInjection/Compiler/DebugStopwatchPass.php deleted file mode 100644 index e9f621da6bd..00000000000 --- a/bundles/PersonalizationBundle/src/DependencyInjection/Compiler/DebugStopwatchPass.php +++ /dev/null @@ -1,68 +0,0 @@ -getParameter('kernel.debug'); - if (!$debug) { - return; - } - - if (!$container->hasDefinition('debug.stopwatch')) { - return; - } - - $services = [ - DataLoader::class, - VisitorInfoResolver::class, - TargetingListener::class, - TargetingDataCollector::class, - ]; - - foreach ($services as $service) { - if ($container->hasDefinition($service)) { - $container - ->getDefinition($service) - ->addMethodCall('setStopwatch', [ - new Reference( - 'debug.stopwatch', - ContainerInterface::IGNORE_ON_INVALID_REFERENCE - ), - ]); - } - } - } -} diff --git a/bundles/PersonalizationBundle/src/DependencyInjection/Compiler/TargetingOverrideHandlersPass.php b/bundles/PersonalizationBundle/src/DependencyInjection/Compiler/TargetingOverrideHandlersPass.php deleted file mode 100644 index 2ae5ec19161..00000000000 --- a/bundles/PersonalizationBundle/src/DependencyInjection/Compiler/TargetingOverrideHandlersPass.php +++ /dev/null @@ -1,39 +0,0 @@ -findAndSortTaggedServices('pimcore_personalization.targeting.override_handler', $container); - - $overrideHandler = $container->getDefinition(OverrideHandler::class); - $overrideHandler->setArgument('$overrideHandlers', $handlers); - } -} diff --git a/bundles/PersonalizationBundle/src/DependencyInjection/Configuration.php b/bundles/PersonalizationBundle/src/DependencyInjection/Configuration.php deleted file mode 100644 index e5ed3322d89..00000000000 --- a/bundles/PersonalizationBundle/src/DependencyInjection/Configuration.php +++ /dev/null @@ -1,74 +0,0 @@ -getRootNode(); - - $rootNode - ->children() - ->arrayNode('targeting') - ->addDefaultsIfNotSet() - ->children() - ->booleanNode('enabled') - ->defaultFalse() - ->end() - ->scalarNode('storage_id') - ->info('Service ID of the targeting storage which should be used. This ID will be aliased to ' . TargetingStorageInterface::class) - ->defaultValue(CookieStorage::class) - ->cannotBeEmpty() - ->end() - ->arrayNode('session') - ->info('Enables HTTP session support by configuring session bags and the full page cache') - ->canBeEnabled() - ->end() - ->arrayNode('data_providers') - ->useAttributeAsKey('key') - ->prototype('scalar') - ->end() - ->end() - ->arrayNode('conditions') - ->useAttributeAsKey('key') - ->prototype('scalar') - ->end() - ->end() - ->arrayNode('action_handlers') - ->useAttributeAsKey('name') - ->prototype('scalar') - ->end() - ->end() - ->end() - ->end() - ->end(); - - return $treeBuilder; - } -} diff --git a/bundles/PersonalizationBundle/src/DependencyInjection/PimcorePersonalizationExtension.php b/bundles/PersonalizationBundle/src/DependencyInjection/PimcorePersonalizationExtension.php deleted file mode 100644 index 4cc72dd1668..00000000000 --- a/bundles/PersonalizationBundle/src/DependencyInjection/PimcorePersonalizationExtension.php +++ /dev/null @@ -1,97 +0,0 @@ -configureTargeting($container, $loader, $config['targeting']); - } - - private function configureTargeting(ContainerBuilder $container, LoaderInterface $loader, array $config): void - { - $container->setParameter('pimcore_personalization.targeting.enabled', $config['enabled']); - $container->setParameter('pimcore_personalization.targeting.conditions', $config['conditions']); - // @phpstan-ignore-next-line - if (!$container->hasParameter('pimcore.geoip.db_file')) { - $container->setParameter('pimcore.geoip.db_file', ''); - } - - $loader->load('targeting.yaml'); - $loader->load('services.yaml'); - - // set TargetingStorageInterface type hint to the configured service ID - $container->setAlias(TargetingStorageInterface::class, $config['storage_id']); - - //register listeners - $loader->load('targeting/services.yaml'); - $loader->load('targeting/listeners.yaml'); - - $dataProviders = []; - foreach ($config['data_providers'] as $dataProviderKey => $dataProviderServiceId) { - $dataProviders[$dataProviderKey] = new Reference($dataProviderServiceId); - } - - $dataProviderLocator = new Definition(ServiceLocator::class, [$dataProviders]); - $dataProviderLocator - ->setPublic(false) - ->addTag('container.service_locator'); - - $container - ->findDefinition(DataLoaderInterface::class) - ->setArgument('$dataProviders', $dataProviderLocator); - - $actionHandlers = []; - foreach ($config['action_handlers'] as $actionHandlerKey => $actionHandlerServiceId) { - $actionHandlers[$actionHandlerKey] = new Reference($actionHandlerServiceId); - } - - $actionHandlerLocator = new Definition(ServiceLocator::class, [$actionHandlers]); - $actionHandlerLocator - ->setPublic(false) - ->addTag('container.service_locator'); - - $container - ->getDefinition(DelegatingActionHandler::class) - ->setArgument('$actionHandlers', $actionHandlerLocator); - - $container->setParameter('pimcore_personalization.targeting.session.enabled', $config['session']['enabled'] ?? false); - - $container - ->findDefinition(TargetingEnableService::class) - ->setArgument('$enabled', $config['enabled'] ?? false); - } -} diff --git a/bundles/PersonalizationBundle/src/Document/Newsletter/AddressSourceAdapter/DefaultAdapter.php b/bundles/PersonalizationBundle/src/Document/Newsletter/AddressSourceAdapter/DefaultAdapter.php deleted file mode 100644 index add77f239d2..00000000000 --- a/bundles/PersonalizationBundle/src/Document/Newsletter/AddressSourceAdapter/DefaultAdapter.php +++ /dev/null @@ -1,104 +0,0 @@ -targetGroups = $params['target_groups'] ?? []; - - parent::__construct($params); - } - - protected function getListing(): ?Listing - { - if (empty($this->list)) { - $objectList = '\\Pimcore\\Model\\DataObject\\' . ucfirst($this->class) . '\\Listing'; - $this->list = new $objectList(); - - $conditions = ['(newsletterActive = 1 AND newsletterConfirmed = 1)']; - if ($this->condition) { - $conditions[] = '(' . $this->condition . ')'; - } - - if ($this->targetGroups) { - $class = ClassDefinition::getByName($this->class); - - if ($class) { - $conditions = $this->addTargetGroupConditions($class, $conditions); - } - } - - $this->list->setCondition(implode(' AND ', $conditions)); - $this->list->setOrderKey('email'); - $this->list->setOrder('ASC'); - - $this->elementsTotal = $this->list->getTotalCount(); - } - - return $this->list; - } - - /** - * Handle target group filters - * - * @param ClassDefinition $class - * @param array $conditions - * - * @return array - */ - protected function addTargetGroupConditions(ClassDefinition $class, array $conditions): array - { - if (!$class->getFieldDefinition('targetGroup')) { - return $conditions; - } - - $fieldDefinition = $class->getFieldDefinition('targetGroup'); - if ($fieldDefinition instanceof ClassDefinition\Data\TargetGroup) { - $targetGroups = []; - foreach ($this->targetGroups as $value) { - if (!empty($value)) { - $targetGroups[] = $this->list->quote($value); - } - } - - $conditions[] = 'targetGroup IN (' . implode(',', $targetGroups) . ')'; - } elseif ($fieldDefinition instanceof ClassDefinition\Data\TargetGroupMultiselect) { - $targetGroupsCondition = []; - foreach ($this->targetGroups as $value) { - $targetGroupsCondition[] = 'targetGroup LIKE ' . $this->list->quote('%,' . $value . ',%'); - } - - $conditions[] = '(' . implode(' OR ', $targetGroupsCondition) . ')'; - } - - return $conditions; - } -} diff --git a/bundles/PersonalizationBundle/src/Event/Model/TargetGroupEvent.php b/bundles/PersonalizationBundle/src/Event/Model/TargetGroupEvent.php deleted file mode 100644 index c5b5c56115c..00000000000 --- a/bundles/PersonalizationBundle/src/Event/Model/TargetGroupEvent.php +++ /dev/null @@ -1,54 +0,0 @@ -targetGroup = $targetGroup; - $this->arguments = $arguments; - } - - public function getTargetGroup(): TargetGroup - { - return $this->targetGroup; - } - - public function setTargetGroup(TargetGroup $targetGroup): void - { - $this->targetGroup = $targetGroup; - } - - public function getElement(): TargetGroup - { - return $this->getTargetGroup(); - } -} diff --git a/bundles/PersonalizationBundle/src/Event/TargetGroupEvents.php b/bundles/PersonalizationBundle/src/Event/TargetGroupEvents.php deleted file mode 100644 index a0699cd43b7..00000000000 --- a/bundles/PersonalizationBundle/src/Event/TargetGroupEvents.php +++ /dev/null @@ -1,41 +0,0 @@ -document = $document; - $this->targetGroup = $targetGroup; - } - - public function getDocument(): Document - { - return $this->document; - } - - public function getTargetGroup(): TargetGroup - { - return $this->targetGroup; - } -} diff --git a/bundles/PersonalizationBundle/src/Event/Targeting/BuildConditionEvent.php b/bundles/PersonalizationBundle/src/Event/Targeting/BuildConditionEvent.php deleted file mode 100644 index 892b7626e64..00000000000 --- a/bundles/PersonalizationBundle/src/Event/Targeting/BuildConditionEvent.php +++ /dev/null @@ -1,71 +0,0 @@ -type = $type; - $this->class = $class; - $this->config = $config; - } - - public function getType(): string - { - return $this->type; - } - - public function getClass(): string - { - return $this->class; - } - - public function getConfig(): array - { - return $this->config; - } - - public function hasCondition(): bool - { - return null !== $this->condition; - } - - public function getCondition(): ?ConditionInterface - { - return $this->condition; - } - - public function setCondition(ConditionInterface $condition): void - { - $this->condition = $condition; - - $this->stopPropagation(); - } -} diff --git a/bundles/PersonalizationBundle/src/Event/Targeting/OverrideEvent.php b/bundles/PersonalizationBundle/src/Event/Targeting/OverrideEvent.php deleted file mode 100644 index c271f9a67df..00000000000 --- a/bundles/PersonalizationBundle/src/Event/Targeting/OverrideEvent.php +++ /dev/null @@ -1,43 +0,0 @@ -type = $type; - $this->data = $data; - } - - public function getType(): string - { - return $this->type; - } - - public function getData(): array - { - return $this->data; - } -} diff --git a/bundles/PersonalizationBundle/src/Event/Targeting/RenderToolbarEvent.php b/bundles/PersonalizationBundle/src/Event/Targeting/RenderToolbarEvent.php deleted file mode 100644 index 12807c0ef98..00000000000 --- a/bundles/PersonalizationBundle/src/Event/Targeting/RenderToolbarEvent.php +++ /dev/null @@ -1,53 +0,0 @@ -template = $template; - $this->data = $data; - } - - public function getTemplate(): string - { - return $this->template; - } - - public function setTemplate(string $template): void - { - $this->template = $template; - } - - public function getData(): array - { - return $this->data; - } - - public function setData(array $data): void - { - $this->data = $data; - } -} diff --git a/bundles/PersonalizationBundle/src/Event/Targeting/TargetingCodeEvent.php b/bundles/PersonalizationBundle/src/Event/Targeting/TargetingCodeEvent.php deleted file mode 100644 index 7d1223e13b6..00000000000 --- a/bundles/PersonalizationBundle/src/Event/Targeting/TargetingCodeEvent.php +++ /dev/null @@ -1,85 +0,0 @@ -template = $template; - $this->blocks = $blocks; - $this->data = $data; - } - - public function getTemplate(): string - { - return $this->template; - } - - public function setTemplate(string $template): void - { - $this->template = $template; - } - - /** - * @return CodeBlock[] - */ - public function getBlocks(): array - { - return $this->blocks; - } - - public function getBlock(string $block): CodeBlock - { - if (!isset($this->blocks[$block])) { - throw new \InvalidArgumentException(sprintf('Invalid block "%s"', $block)); - } - - return $this->blocks[$block]; - } - - public function getData(): array - { - return $this->data; - } - - public function setData(array $data): void - { - $this->data = $data; - } -} diff --git a/bundles/PersonalizationBundle/src/Event/Targeting/TargetingEvent.php b/bundles/PersonalizationBundle/src/Event/Targeting/TargetingEvent.php deleted file mode 100644 index b98b3eef594..00000000000 --- a/bundles/PersonalizationBundle/src/Event/Targeting/TargetingEvent.php +++ /dev/null @@ -1,42 +0,0 @@ -visitorInfo = $visitorInfo; - } - - public function getVisitorInfo(): VisitorInfo - { - return $this->visitorInfo; - } - - public function getRequest(): Request - { - return $this->visitorInfo->getRequest(); - } -} diff --git a/bundles/PersonalizationBundle/src/Event/Targeting/TargetingResolveVisitorInfoEvent.php b/bundles/PersonalizationBundle/src/Event/Targeting/TargetingResolveVisitorInfoEvent.php deleted file mode 100644 index 4300286f0e4..00000000000 --- a/bundles/PersonalizationBundle/src/Event/Targeting/TargetingResolveVisitorInfoEvent.php +++ /dev/null @@ -1,28 +0,0 @@ -visitorInfo = $visitorInfo; - } -} diff --git a/bundles/PersonalizationBundle/src/Event/Targeting/TargetingRuleEvent.php b/bundles/PersonalizationBundle/src/Event/Targeting/TargetingRuleEvent.php deleted file mode 100644 index af7a0791f40..00000000000 --- a/bundles/PersonalizationBundle/src/Event/Targeting/TargetingRuleEvent.php +++ /dev/null @@ -1,38 +0,0 @@ -rule = $rule; - } - - public function getRule(): Rule - { - return $this->rule; - } -} diff --git a/bundles/PersonalizationBundle/src/Event/TargetingEvents.php b/bundles/PersonalizationBundle/src/Event/TargetingEvents.php deleted file mode 100644 index a1b1c8b3d0a..00000000000 --- a/bundles/PersonalizationBundle/src/Event/TargetingEvents.php +++ /dev/null @@ -1,109 +0,0 @@ -addSetting('maxmind_geoip_installed', (bool) \Pimcore::getContainer()->getParameter('pimcore.geoip.db_file')); - } -} diff --git a/bundles/PersonalizationBundle/src/Installer.php b/bundles/PersonalizationBundle/src/Installer.php deleted file mode 100644 index 4cc89c37556..00000000000 --- a/bundles/PersonalizationBundle/src/Installer.php +++ /dev/null @@ -1,105 +0,0 @@ -installDatabaseTable(); - $this->addUserPermission(); - parent::install(); - } - - public function uninstall(): void - { - // Cleanup should be done manually - - $output = new ConsoleOutput(); - $style = new SymfonyStyle(new StringInput(''), $output); - - if (!($style->confirm( - "[WARNING] Before Uninstalling the bundle, 'Target Group' references must be removed from DataObject classes,\n" . - "Custom services and Ecommerce Pricing Rules manually.\n\n" . - 'Do you want to continue the uninstall?', false))) { - $output->writeln('Uninstall Aborted.'); - exit; - } - - $this->uninstallDatabaseTable(); - $this->removeUserPermission(); - parent::uninstall(); - } - - private function addUserPermission(): void - { - $db = \Pimcore\Db::get(); - - foreach (self::USER_PERMISSIONS as $permission) { - $db->insert('users_permission_definitions', [ - $db->quoteIdentifier('key') => $permission, - $db->quoteIdentifier('category') => self::USER_PERMISSIONS_CATEGORY, - ]); - } - } - - private function removeUserPermission(): void - { - $db = \Pimcore\Db::get(); - - foreach (self::USER_PERMISSIONS as $permission) { - $db->delete('users_permission_definitions', [ - $db->quoteIdentifier('key') => $permission, - ]); - } - } - - private function installDatabaseTable(): void - { - $sqlPath = __DIR__ . '/Resources/install/'; - $sqlFileNames = ['install.sql']; - $db = \Pimcore\Db::get(); - - foreach ($sqlFileNames as $fileName) { - $statement = file_get_contents($sqlPath . $fileName); - $db->executeQuery($statement); - } - } - - private function uninstallDatabaseTable(): void - { - $sqlPath = __DIR__ . '/Resources/uninstall/'; - $sqlFileNames = ['uninstall.sql']; - $db = \Pimcore\Db::get(); - - foreach ($sqlFileNames as $fileName) { - $statement = file_get_contents($sqlPath . $fileName); - $db->executeQuery($statement); - } - } -} diff --git a/bundles/PersonalizationBundle/src/Model/Document/Page.php b/bundles/PersonalizationBundle/src/Model/Document/Page.php deleted file mode 100644 index 92e90357a9c..00000000000 --- a/bundles/PersonalizationBundle/src/Model/Document/Page.php +++ /dev/null @@ -1,116 +0,0 @@ -targetGroupIds = $targetGroupIds; - } - - /** - * Get serialized list of Target Group IDs - * - * @return string - */ - public function getTargetGroupIds(): string - { - return $this->targetGroupIds; - } - - /** - * Set assigned target groups - * - * @param TargetGroup[]|int[] $targetGroups - */ - public function setTargetGroups(array $targetGroups): void - { - $ids = array_map(function ($targetGroup) { - if (is_numeric($targetGroup)) { - return (int)$targetGroup; - } elseif ($targetGroup instanceof TargetGroup) { - return $targetGroup->getId(); - } - }, $targetGroups); - - $ids = array_filter($ids, function ($id) { - return null !== $id && $id > 0; - }); - - $this->setTargetGroupIds($ids); - } - - /** - * Return list of assigned target groups (via properties panel) - * - * @return TargetGroup[] - */ - public function getTargetGroups(): array - { - $ids = explode(',', $this->targetGroupIds); - - $targetGroups = array_map(function ($id) { - $id = trim($id); - if (!empty($id)) { - $targetGroup = TargetGroup::getById((int)$id); - if ($targetGroup) { - return $targetGroup; - } - } - }, $ids); - - $targetGroups = array_filter($targetGroups); - - return $targetGroups; - } -} diff --git a/bundles/PersonalizationBundle/src/Model/Document/Page/Dao.php b/bundles/PersonalizationBundle/src/Model/Document/Page/Dao.php deleted file mode 100644 index 774e41bc6f2..00000000000 --- a/bundles/PersonalizationBundle/src/Model/Document/Page/Dao.php +++ /dev/null @@ -1,30 +0,0 @@ -db->fetchOne( - 'SELECT count(*) FROM documents_editables WHERE documentId = ? AND name LIKE ?', - [ - $this->model->getId(), - '%' . TargetingDocumentInterface::TARGET_GROUP_EDITABLE_PREFIX . '%' . TargetingDocumentInterface::TARGET_GROUP_EDITABLE_SUFFIX . '%', - ] - ); - - return $count > 0; - } - - public function getTargetGroupSpecificEditableNames(): array - { - /** @var PageSnippet\Dao $this */ - $names = $this->db->fetchFirstColumn( - 'SELECT name FROM documents_editables WHERE documentId = ? AND name LIKE ?', - [ - $this->model->getId(), - '%' . TargetingDocumentInterface::TARGET_GROUP_EDITABLE_PREFIX . '%' . TargetingDocumentInterface::TARGET_GROUP_EDITABLE_SUFFIX . '%', - ] - ); - - return $names; - } -} diff --git a/bundles/PersonalizationBundle/src/Model/Document/Targeting/TargetingDocumentInterface.php b/bundles/PersonalizationBundle/src/Model/Document/Targeting/TargetingDocumentInterface.php deleted file mode 100644 index 823bea635c6..00000000000 --- a/bundles/PersonalizationBundle/src/Model/Document/Targeting/TargetingDocumentInterface.php +++ /dev/null @@ -1,76 +0,0 @@ -useTargetGroup = $useTargetGroup; - } - - /** - * {@inheritdoc} - */ - public function getUseTargetGroup(): ?int - { - return $this->useTargetGroup; - } - - /** - * {@inheritdoc} - */ - public function getTargetGroupEditablePrefix(int $targetGroupId = null): string - { - $prefix = ''; - - if (!$targetGroupId) { - $targetGroupId = $this->getUseTargetGroup(); - } - - if ($targetGroupId) { - $prefix = self::TARGET_GROUP_EDITABLE_PREFIX . $targetGroupId . self::TARGET_GROUP_EDITABLE_SUFFIX; - } - - return $prefix; - } - - /** - * {@inheritdoc} - */ - public function getTargetGroupEditableName(string $name): string - { - if (!$this->getUseTargetGroup()) { - return $name; - } - - $prefix = $this->getTargetGroupEditablePrefix(); - if (!preg_match('/^' . preg_quote($prefix, '/') . '/', $name)) { - $name = $prefix . $name; - } - - return $name; - } - - /** - * {@inheritdoc} - */ - public function hasTargetGroupSpecificEditables(): bool - { - return $this->getDao()->hasTargetGroupSpecificEditables(); - } - - /** - * {@inheritdoc} - */ - public function getTargetGroupSpecificEditableNames(): array - { - return $this->getDao()->getTargetGroupSpecificEditableNames(); - } - - /** - * {@inheritdoc} - */ - public function setEditable(Editable $editable): static - { - if ($this->getUseTargetGroup()) { - $name = $this->getTargetGroupEditableName($editable->getName()); - $editable->setName($name); - } - - parent::setEditable($editable); - - return $this; - } - - /** - * Get an editable with the given key/name - * - * @param string $name - * - * @return Editable|null - */ - public function getEditable(string $name): ?Editable - { - // check if a target group is requested for this page, if yes deliver a different version of the editable (prefixed) - if ($this->getUseTargetGroup()) { - $targetGroupEditableName = $this->getTargetGroupEditableName($name); - - if ($editable = parent::getEditable($targetGroupEditableName)) { - return $editable; - } else { - // if there's no dedicated content for this target group, inherit from the "original" content (unprefixed) - // and mark it as inherited so it is clear in the ui that the content is not specific to the selected target group - // replace all occurrences of the target group prefix, this is needed because of block-prefixes - $inheritedName = str_replace($this->getTargetGroupEditablePrefix(), '', $name); - $inheritedEditable = parent::getEditable($inheritedName); - - if ($inheritedEditable) { - $inheritedEditable = clone $inheritedEditable; - $inheritedEditable->setDao(null); - $inheritedEditable->setName($targetGroupEditableName); - $inheritedEditable->setInherited(true); - - $this->setEditable($inheritedEditable); - - return $inheritedEditable; - } - } - } - - // delegate to default - return parent::getEditable($name); - } - - /** - * {@inheritdoc} - */ - public function __sleep(): array - { - $finalVars = []; - $parentVars = parent::__sleep(); - - $blockedVars = ['useTargetGroup']; - - foreach ($parentVars as $key) { - if (!in_array($key, $blockedVars)) { - $finalVars[] = $key; - } - } - - return $finalVars; - } -} diff --git a/bundles/PersonalizationBundle/src/Model/Tool/Targeting/Rule.php b/bundles/PersonalizationBundle/src/Model/Tool/Targeting/Rule.php deleted file mode 100644 index e4646158951..00000000000 --- a/bundles/PersonalizationBundle/src/Model/Tool/Targeting/Rule.php +++ /dev/null @@ -1,229 +0,0 @@ -getId(); - } elseif (is_string($target)) { - $target = self::getByName($target); - if (!$target) { - return false; - } else { - $targetId = $target->getId(); - } - } else { - $targetId = (int) $target; - } - - if (array_key_exists('_ptc', $_GET) && (int)$targetId == (int)$_GET['_ptc']) { - return true; - } - - return false; - } - - /** - * Static helper to retrieve an instance of Tool\Targeting\Rule by the given ID - * - * @param int $id - * - * @return self|null - */ - public static function getById(int $id): ?Rule - { - try { - $target = new self(); - $target->getDao()->getById((int)$id); - - return $target; - } catch (Model\Exception\NotFoundException $e) { - return null; - } - } - - /** - * @param string $name - * - * @return self|null - * - * @throws \Exception - */ - public static function getByName(string $name): ?Rule - { - try { - $target = new self(); - $target->getDao()->getByName($name); - - return $target; - } catch (Model\Exception\NotFoundException $e) { - return null; - } - } - - /** - * @return $this - */ - public function setDescription(string $description): static - { - $this->description = $description; - - return $this; - } - - public function getDescription(): string - { - return $this->description; - } - - /** - * @return $this - */ - public function setId(int $id): static - { - $this->id = $id; - - return $this; - } - - public function getId(): ?int - { - return $this->id; - } - - /** - * @return $this - */ - public function setName(string $name): static - { - $this->name = $name; - - return $this; - } - - public function getName(): string - { - return $this->name; - } - - /** - * @return $this - */ - public function setActions(array $actions): static - { - if (!$actions) { - $actions = []; - } - - $this->actions = $actions; - - return $this; - } - - public function getActions(): array - { - return $this->actions; - } - - /** - * @return $this - */ - public function setConditions(array $conditions): static - { - if (!$conditions) { - $conditions = []; - } - - $this->conditions = $conditions; - - return $this; - } - - public function getConditions(): array - { - return $this->conditions; - } - - public function setScope(string $scope): void - { - if (!empty($scope)) { - $this->scope = $scope; - } - } - - public function getScope(): string - { - return $this->scope; - } - - public function setActive(bool $active): void - { - $this->active = (bool) $active; - } - - public function getActive(): bool - { - return $this->active; - } - - public function getPrio(): int - { - return $this->prio; - } - - public function setPrio(int $prio): void - { - $this->prio = $prio; - } -} diff --git a/bundles/PersonalizationBundle/src/Model/Tool/Targeting/Rule/Dao.php b/bundles/PersonalizationBundle/src/Model/Tool/Targeting/Rule/Dao.php deleted file mode 100644 index 8cc43bbe421..00000000000 --- a/bundles/PersonalizationBundle/src/Model/Tool/Targeting/Rule/Dao.php +++ /dev/null @@ -1,123 +0,0 @@ -model->setId($id); - } - - $data = $this->db->fetchAssociative('SELECT * FROM targeting_rules WHERE id = ?', [$this->model->getId()]); - - if (!empty($data['id'])) { - $data['conditions'] = (isset($data['conditions']) ? Serialize::unserialize($data['conditions']) : []); - $data['actions'] = (isset($data['actions']) ? Serialize::unserialize($data['actions']) : []); - - $this->assignVariablesToModel($data); - } else { - throw new Model\Exception\NotFoundException('target with id ' . $this->model->getId() . " doesn't exist"); - } - } - - /** - * @param string|null $name - * - * @throws \Exception - */ - public function getByName(string $name = null): void - { - if ($name != null) { - $this->model->setName($name); - } - - $data = $this->db->fetchAllAssociative('SELECT id FROM targeting_rules WHERE name = ?', [$this->model->getName()]); - - if (count($data) === 1) { - $this->getById($data[0]['id']); - } else { - throw new Model\Exception\NotFoundException(sprintf( - 'Targeting rule with name "%s" does not exist.', - $this->model->getName() - )); - } - } - - /** - * Save object to database - */ - public function save(): void - { - if (!$this->model->getId()) { - $this->create(); - } - - $this->update(); - } - - /** - * Deletes object from database - */ - public function delete(): void - { - $this->db->delete('targeting_rules', ['id' => $this->model->getId()]); - } - - /** - * @throws \Exception - */ - public function update(): void - { - $type = $this->model->getObjectVars(); - $data = []; - - foreach ($type as $key => $value) { - if (in_array($key, $this->getValidTableColumns('targeting_rules'))) { - if (is_array($value) || is_object($value)) { - $value = Serialize::serialize($value); - } - if (is_bool($value)) { - $value = (int) $value; - } - $data[$key] = $value; - } - } - - $this->db->update('targeting_rules', $data, ['id' => $this->model->getId()]); - } - - public function create(): void - { - $this->db->insert('targeting_rules', []); - $this->model->setId((int) $this->db->lastInsertId()); - } -} diff --git a/bundles/PersonalizationBundle/src/Model/Tool/Targeting/Rule/Listing.php b/bundles/PersonalizationBundle/src/Model/Tool/Targeting/Rule/Listing.php deleted file mode 100644 index d1f6edb5147..00000000000 --- a/bundles/PersonalizationBundle/src/Model/Tool/Targeting/Rule/Listing.php +++ /dev/null @@ -1,48 +0,0 @@ -setData($targets); - } - - /** - * @return Rule[] - */ - public function getTargets(): array - { - return $this->getData(); - } -} diff --git a/bundles/PersonalizationBundle/src/Model/Tool/Targeting/Rule/Listing/Dao.php b/bundles/PersonalizationBundle/src/Model/Tool/Targeting/Rule/Listing/Dao.php deleted file mode 100644 index 25f789bf0d7..00000000000 --- a/bundles/PersonalizationBundle/src/Model/Tool/Targeting/Rule/Listing/Dao.php +++ /dev/null @@ -1,54 +0,0 @@ -db->fetchFirstColumn('SELECT id FROM targeting_rules' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables()); - - $targets = []; - foreach ($ids as $id) { - $targets[] = Rule::getById($id); - } - - $this->model->setTargets($targets); - - return $targets; - } - - public function getTotalCount(): int - { - try { - return (int) $this->db->fetchOne('SELECT COUNT(*) FROM targeting_rules ' . $this->getCondition(), $this->model->getConditionVariables()); - } catch (\Exception $e) { - return 0; - } - } -} diff --git a/bundles/PersonalizationBundle/src/Model/Tool/Targeting/TargetGroup.php b/bundles/PersonalizationBundle/src/Model/Tool/Targeting/TargetGroup.php deleted file mode 100644 index 9fc5871038e..00000000000 --- a/bundles/PersonalizationBundle/src/Model/Tool/Targeting/TargetGroup.php +++ /dev/null @@ -1,166 +0,0 @@ -getDao()->getById((int)$id); - - return $targetGroup; - } catch (Model\Exception\NotFoundException $e) { - return null; - } - } - - public static function getByName(string $name): ?TargetGroup - { - try { - $target = new self(); - $target->getDao()->getByName($name); - - return $target; - } catch (Model\Exception\NotFoundException $e) { - return null; - } - } - - public static function isIdActive(int $id): bool - { - $targetGroup = TargetGroup::getById($id); - - if ($targetGroup) { - return $targetGroup->getActive(); - } - - return false; - } - - /** - * @return $this - */ - public function setDescription(string $description): static - { - $this->description = $description; - - return $this; - } - - public function getDescription(): string - { - return $this->description; - } - - /** - * @return $this - */ - public function setId(int $id): static - { - $this->id = $id; - - return $this; - } - - public function getId(): ?int - { - return $this->id; - } - - /** - * @return $this - */ - public function setName(string $name): static - { - $this->name = $name; - - return $this; - } - - public function getName(): string - { - return $this->name; - } - - public function setThreshold(int $threshold): void - { - $this->threshold = $threshold; - } - - public function getThreshold(): int - { - return $this->threshold; - } - - public function setActive(bool $active): void - { - $this->active = (bool)$active; - } - - public function getActive(): bool - { - return $this->active; - } - - public function delete(): void - { - $this->getDao()->delete(); - $this->dispatchEvent(new TargetGroupEvent($this), TargetGroupEvents::POST_DELETE); - } - - public function save(): void - { - $isUpdate = false; - if ($this->getId()) { - $isUpdate = true; - } - - $this->getDao()->save(); - - if ($isUpdate) { - $this->dispatchEvent(new TargetGroupEvent($this), TargetGroupEvents::POST_UPDATE); - } else { - $this->dispatchEvent(new TargetGroupEvent($this), TargetGroupEvents::POST_ADD); - } - } -} - -@class_alias(TargetGroup::class, 'Pimcore\Model\Tool\Targeting\TargetGroup'); diff --git a/bundles/PersonalizationBundle/src/Model/Tool/Targeting/TargetGroup/Dao.php b/bundles/PersonalizationBundle/src/Model/Tool/Targeting/TargetGroup/Dao.php deleted file mode 100644 index 6657cb79ee6..00000000000 --- a/bundles/PersonalizationBundle/src/Model/Tool/Targeting/TargetGroup/Dao.php +++ /dev/null @@ -1,115 +0,0 @@ -model->setId($id); - } - - $data = $this->db->fetchAssociative('SELECT * FROM targeting_target_groups WHERE id = ?', [$this->model->getId()]); - - if (!empty($data['id'])) { - $data['actions'] = (isset($data['actions']) ? Serialize::unserialize($data['actions']) : []); - - $this->assignVariablesToModel($data); - } else { - throw new Model\Exception\NotFoundException('Target Group with id ' . $this->model->getId() . " doesn't exist"); - } - } - - /** - * @param string|null $name - * - * @throws Model\Exception\NotFoundException - */ - public function getByName(string $name = null): void - { - if (null !== $name) { - $this->model->setName($name); - } - - $data = $this->db->fetchAllAssociative('SELECT id FROM targeting_target_groups WHERE name = ?', [$this->model->getName()]); - - if (count($data) === 1) { - $this->getById($data[0]['id']); - } else { - throw new Model\Exception\NotFoundException(sprintf( - 'Targeting group with name "%s" does not exist or is not unique.', - $this->model->getName() - )); - } - } - - public function save(): void - { - if (!$this->model->getId()) { - $this->create(); - } - - $this->update(); - } - - public function delete(): void - { - $this->db->delete('targeting_target_groups', ['id' => $this->model->getId()]); - } - - public function update(): void - { - $type = $this->model->getObjectVars(); - $data = []; - - foreach ($type as $key => $value) { - if (in_array($key, $this->getValidTableColumns('targeting_target_groups'))) { - if (is_array($value) || is_object($value)) { - $value = Serialize::serialize($value); - } - - if (is_bool($value)) { - $value = (int)$value; - } - - $data[$key] = $value; - } - } - - $this->db->update('targeting_target_groups', $data, ['id' => $this->model->getId()]); - } - - public function create(): void - { - $this->db->insert('targeting_target_groups', []); - $this->model->setId((int) $this->db->lastInsertId()); - } -} diff --git a/bundles/PersonalizationBundle/src/Model/Tool/Targeting/TargetGroup/Listing.php b/bundles/PersonalizationBundle/src/Model/Tool/Targeting/TargetGroup/Listing.php deleted file mode 100644 index 9e191aa13a6..00000000000 --- a/bundles/PersonalizationBundle/src/Model/Tool/Targeting/TargetGroup/Listing.php +++ /dev/null @@ -1,48 +0,0 @@ -setData($targetGroups); - } - - /** - * @return TargetGroup[] - */ - public function getTargetGroups(): array - { - return $this->getData(); - } -} diff --git a/bundles/PersonalizationBundle/src/Model/Tool/Targeting/TargetGroup/Listing/Dao.php b/bundles/PersonalizationBundle/src/Model/Tool/Targeting/TargetGroup/Listing/Dao.php deleted file mode 100644 index d468e941658..00000000000 --- a/bundles/PersonalizationBundle/src/Model/Tool/Targeting/TargetGroup/Listing/Dao.php +++ /dev/null @@ -1,53 +0,0 @@ -db->fetchFirstColumn('SELECT id FROM targeting_target_groups' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables()); - - $targetGroups = []; - foreach ($ids as $id) { - $targetGroups[] = TargetGroup::getById($id); - } - - $this->model->setTargetGroups($targetGroups); - - return $targetGroups; - } - - public function getTotalCount(): int - { - try { - return (int) $this->db->fetchOne('SELECT COUNT(*) FROM targeting_target_groups ' . $this->getCondition(), $this->model->getConditionVariables()); - } catch (\Exception $e) { - return 0; - } - } -} diff --git a/bundles/PersonalizationBundle/src/Pimcore/Model/DataObject/ClassDefinition/Data/TargetGroup.php b/bundles/PersonalizationBundle/src/Pimcore/Model/DataObject/ClassDefinition/Data/TargetGroup.php deleted file mode 100644 index becbc5970d9..00000000000 --- a/bundles/PersonalizationBundle/src/Pimcore/Model/DataObject/ClassDefinition/Data/TargetGroup.php +++ /dev/null @@ -1,146 +0,0 @@ -checkValidity($data, true, $params); - } catch (\Exception $e) { - $data = null; - } - } - - return $data; - } - - /** - * @param mixed $data - * @param DataObject\Concrete|null $object - * @param array $params - * - * @return null|string|int - * - * @see ResourcePersistenceAwareInterface::getDataForResource - */ - public function getDataForResource(mixed $data, DataObject\Concrete $object = null, array $params = []): null|string|int - { - if (!empty($data)) { - try { - $this->checkValidity($data, true, $params); - } catch (\Exception $e) { - $data = null; - } - } - - return $data; - } - - /** - * @internal - */ - public function configureOptions(): void - { - /** @var Tool\Targeting\TargetGroup\Listing|Tool\Targeting\TargetGroup\Listing\Dao $list */ - $list = new Tool\Targeting\TargetGroup\Listing(); - $list->setOrder('asc'); - $list->setOrderKey('name'); - - $targetGroups = $list->load(); - - $options = []; - foreach ($targetGroups as $targetGroup) { - $options[] = [ - 'value' => $targetGroup->getId(), - 'key' => $targetGroup->getName(), - ]; - } - - $this->setOptions($options); - } - - /** - * {@inheritdoc} - */ - public function checkValidity(mixed $data, bool $omitMandatoryCheck = false, array $params = []): void - { - if (!$omitMandatoryCheck && $this->getMandatory() && empty($data)) { - throw new Model\Element\ValidationException('Empty mandatory field [ '.$this->getName().' ]'); - } - - if (!empty($data)) { - $targetGroup = Tool\Targeting\TargetGroup::getById((int)$data); - - if (!$targetGroup instanceof Tool\Targeting\TargetGroup) { - throw new Model\Element\ValidationException('Invalid target group reference'); - } - } - } - - public static function __set_state(array $data): static - { - $obj = parent::__set_state($data); - $options = $obj->getOptions(); - if (\Pimcore::inAdmin() || empty($options)) { - $obj->configureOptions(); - } - - return $obj; - } - - public function jsonSerialize(): mixed - { - if (Service::doRemoveDynamicOptions()) { - $this->options = null; - } - - return parent::jsonSerialize(); - } - - /** - * {@inheritdoc} - */ - public function resolveBlockedVars(): array - { - $blockedVars = parent::resolveBlockedVars(); - $blockedVars[] = 'options'; - - return $blockedVars; - } - - public function getFieldType(): string - { - return 'targetGroup'; - } -} diff --git a/bundles/PersonalizationBundle/src/Pimcore/Model/DataObject/ClassDefinition/Data/TargetGroupMultiselect.php b/bundles/PersonalizationBundle/src/Pimcore/Model/DataObject/ClassDefinition/Data/TargetGroupMultiselect.php deleted file mode 100644 index a94e9f289ac..00000000000 --- a/bundles/PersonalizationBundle/src/Pimcore/Model/DataObject/ClassDefinition/Data/TargetGroupMultiselect.php +++ /dev/null @@ -1,83 +0,0 @@ -setOrder('asc'); - $list->setOrderKey('name'); - - $targetGroups = $list->load(); - - $options = []; - foreach ($targetGroups as $targetGroup) { - $options[] = [ - 'value' => $targetGroup->getId(), - 'key' => $targetGroup->getName(), - ]; - } - - $this->setOptions($options); - } - - public static function __set_state(array $data): static - { - $obj = parent::__set_state($data); - $options = $obj->getOptions(); - if (\Pimcore::inAdmin() || empty($options)) { - $obj->configureOptions(); - } - - return $obj; - } - - public function jsonSerialize(): mixed - { - if (Service::doRemoveDynamicOptions()) { - $this->options = null; - } - - return parent::jsonSerialize(); - } - - /** - * {@inheritdoc} - */ - public function resolveBlockedVars(): array - { - $blockedVars = parent::resolveBlockedVars(); - $blockedVars[] = 'options'; - - return $blockedVars; - } - - public function getFieldType(): string - { - return 'targetGroupMultiselect'; - } -} diff --git a/bundles/PersonalizationBundle/src/PimcorePersonalizationBundle.php b/bundles/PersonalizationBundle/src/PimcorePersonalizationBundle.php deleted file mode 100644 index 9905e12451f..00000000000 --- a/bundles/PersonalizationBundle/src/PimcorePersonalizationBundle.php +++ /dev/null @@ -1,90 +0,0 @@ -container->get(Installer::class); - } - - /** - * {@inheritdoc} - */ - public function build(ContainerBuilder $container): void - { - $container->addCompilerPass(new TargetingOverrideHandlersPass()); - $container->addCompilerPass(new DebugStopwatchPass()); - } - - public function getPath(): string - { - return \dirname(__DIR__); - } -} diff --git a/bundles/PersonalizationBundle/src/Resources/install/install.sql b/bundles/PersonalizationBundle/src/Resources/install/install.sql deleted file mode 100644 index e110104d5e8..00000000000 --- a/bundles/PersonalizationBundle/src/Resources/install/install.sql +++ /dev/null @@ -1,35 +0,0 @@ -CREATE TABLE IF NOT EXISTS `targeting_rules` -( - `id` int(11) NOT NULL AUTO_INCREMENT, - `name` varchar(255) NOT NULL DEFAULT '', - `description` text, - `scope` varchar(50) DEFAULT NULL, - `active` tinyint(1) DEFAULT NULL, - `prio` smallint(5) unsigned NOT NULL DEFAULT '0', - `conditions` longtext, - `actions` longtext, - PRIMARY KEY (`id`) -) DEFAULT CHARSET=utf8mb4; - -CREATE TABLE IF NOT EXISTS `targeting_storage` -( - `visitorId` varchar(100) NOT NULL, - `scope` varchar(50) NOT NULL, - `name` varchar(100) NOT NULL, - `value` text, - `creationDate` datetime DEFAULT NULL, - `modificationDate` datetime DEFAULT NULL, - PRIMARY KEY (`visitorId`, `scope`, `name`), - KEY `targeting_storage_scope_index` (`scope`), - KEY `targeting_storage_name_index` (`name`) -) DEFAULT CHARSET=utf8mb4; - -CREATE TABLE IF NOT EXISTS `targeting_target_groups` -( - `id` int(11) NOT NULL AUTO_INCREMENT, - `name` varchar(255) NOT NULL DEFAULT '', - `description` text, - `threshold` int(11) DEFAULT NULL, - `active` tinyint(1) DEFAULT NULL, - PRIMARY KEY (`id`) -) DEFAULT CHARSET=utf8mb4; diff --git a/bundles/PersonalizationBundle/src/Resources/uninstall/uninstall.sql b/bundles/PersonalizationBundle/src/Resources/uninstall/uninstall.sql deleted file mode 100644 index 7d86300b578..00000000000 --- a/bundles/PersonalizationBundle/src/Resources/uninstall/uninstall.sql +++ /dev/null @@ -1,3 +0,0 @@ -DROP TABLE IF EXISTS `targeting_rules`; -DROP TABLE IF EXISTS `targeting_storage`; -DROP TABLE IF EXISTS `targeting_target_groups`; \ No newline at end of file diff --git a/bundles/PersonalizationBundle/src/Targeting/ActionHandler/ActionHandlerInterface.php b/bundles/PersonalizationBundle/src/Targeting/ActionHandler/ActionHandlerInterface.php deleted file mode 100644 index d368036457d..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/ActionHandler/ActionHandlerInterface.php +++ /dev/null @@ -1,29 +0,0 @@ -getActive()) { - return; - } - - $count = $this->storeAssignments($visitorInfo, $targetGroup, $weight); - - $this->assignToVisitor($visitorInfo, $targetGroup, $count); - } - - public function reset(VisitorInfo $visitorInfo): void - { - $success = $this->deleteAssignments($visitorInfo); - } - - /** - * Loads stored assignments from storage and applies it to visitor info - * - * @param VisitorInfo $visitorInfo - */ - public function loadStoredAssignments(VisitorInfo $visitorInfo): void - { - $data = $this->storage->get( - $visitorInfo, - TargetingStorageInterface::SCOPE_VISITOR, - self::STORAGE_KEY, - [] - ); - - foreach ($data as $targetGroupId => $count) { - $targetGroup = TargetGroup::getById($targetGroupId); - if ($targetGroup && $targetGroup->getActive()) { - $this->assignToVisitor($visitorInfo, $targetGroup, $count); - } - } - } - - protected function storeAssignments(VisitorInfo $visitorInfo, TargetGroup $targetGroup, int $weight): int - { - $data = $this->storage->get( - $visitorInfo, - TargetingStorageInterface::SCOPE_VISITOR, - self::STORAGE_KEY, - [] - ); - - $count = $data[$targetGroup->getId()] ?? 0; - $count += $weight; - - $data[$targetGroup->getId()] = $count; - - $this->storage->set( - $visitorInfo, - TargetingStorageInterface::SCOPE_VISITOR, - self::STORAGE_KEY, - $data - ); - - return $count; - } - - protected function deleteAssignments(VisitorInfo $visitorInfo): bool - { - $data = []; - - try { - $this->storage->set( - $visitorInfo, - TargetingStorageInterface::SCOPE_VISITOR, - self::STORAGE_KEY, - $data - ); - } catch (\Exception $e) { - return false; - } - - return true; - } - - protected function assignToVisitor(VisitorInfo $visitorInfo, TargetGroup $targetGroup, int $count): void - { - $threshold = $targetGroup->getThreshold(); - - // only assign if count reached the threshold if threshold is > 1 - if ($threshold <= 1 || $count >= $threshold) { - $visitorInfo->assignTargetGroup($targetGroup, $count, true); - } - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/ActionHandler/CodeSnippet.php b/bundles/PersonalizationBundle/src/Targeting/ActionHandler/CodeSnippet.php deleted file mode 100644 index 5ed5f358776..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/ActionHandler/CodeSnippet.php +++ /dev/null @@ -1,67 +0,0 @@ -codeInjector = $codeInjector; - } - - /** - * {@inheritdoc} - */ - public function apply(VisitorInfo $visitorInfo, array $action, Rule $rule = null): void - { - $code = $action['code'] ?? ''; - $selector = $action['selector'] ?? ''; - $position = $action['position'] ?? ''; - - if (empty($code) || empty($selector) || empty($position)) { - return; - } - - $visitorInfo->addAction([ - 'type' => 'codesnippet', - 'scope' => VisitorInfo::ACTION_SCOPE_RESPONSE, - 'code' => $code, - 'selector' => $selector, - 'position' => $position, - ]); - } - - public function transformResponse(VisitorInfo $visitorInfo, Response $response, array $actions): void - { - foreach ($actions as $action) { - $this->codeInjector->inject( - $response, - $action['code'], - $action['selector'], - $action['position'] - ); - } - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/ActionHandler/DelegatingActionHandler.php b/bundles/PersonalizationBundle/src/Targeting/ActionHandler/DelegatingActionHandler.php deleted file mode 100644 index f56fc916a73..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/ActionHandler/DelegatingActionHandler.php +++ /dev/null @@ -1,78 +0,0 @@ -actionHandlers = $actionHandlers; - $this->dataLoader = $dataLoader; - } - - /** - * {@inheritdoc} - */ - public function apply(VisitorInfo $visitorInfo, array $action, Rule $rule = null): void - { - /** @var string $type */ - $type = $action['type'] ?? null; - - if (empty($type)) { - throw new \InvalidArgumentException('Invalid action: type is not set'); - } - - $actionHandler = $this->getActionHandler($type); - - // load data providers if necessary - if ($actionHandler instanceof DataProviderDependentInterface) { - $this->dataLoader->loadDataFromProviders($visitorInfo, $actionHandler->getDataProviderKeys()); - } - - $actionHandler->apply($visitorInfo, $action, $rule); - } - - public function hasActionHandler(string $type): bool - { - return $this->actionHandlers->has($type); - } - - public function getActionHandler(string $type): ActionHandlerInterface - { - if (!$this->actionHandlers->has($type)) { - throw new \InvalidArgumentException(sprintf( - 'Invalid condition: there is no action handler registered for type "%s"', - $type - )); - } - - return $this->actionHandlers->get($type); - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/ActionHandler/Redirect.php b/bundles/PersonalizationBundle/src/Targeting/ActionHandler/Redirect.php deleted file mode 100644 index 1644735ae65..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/ActionHandler/Redirect.php +++ /dev/null @@ -1,82 +0,0 @@ -getRequest(); - - // only redirect GET requests - if ($request->getMethod() !== 'GET') { - return; - } - - // don't redirect multiple times to avoid loops - if (!empty($request->get('_ptr'))) { - return; - } - - if (is_numeric($url)) { - $document = Document::getById($url); - if (!$document) { - return; - } - - $url = $document->getRealFullPath(); - } - - if ($rule) { - $url = $this->addUrlParam($url, '_ptr', $rule->getId()); - } else { - $url = $this->addUrlParam($url, '_ptr', 0); - } - - $code = $action['code'] ?? RedirectResponse::HTTP_FOUND; - - $visitorInfo->setResponse(new RedirectResponse($url, $code)); - } - - private function addUrlParam(string $url, string $param, int $value): string - { - // add _ptr parameter - if (false !== strpos($url, '?')) { - $url .= '&'; - } else { - $url .= '?'; - } - - $url .= sprintf('%s=%d', $param, $value); - - return $url; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/ActionHandler/ResponseTransformingActionHandlerInterface.php b/bundles/PersonalizationBundle/src/Targeting/ActionHandler/ResponseTransformingActionHandlerInterface.php deleted file mode 100644 index 996169af0ef..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/ActionHandler/ResponseTransformingActionHandlerInterface.php +++ /dev/null @@ -1,29 +0,0 @@ -parts = $parts; - } - - /** - * @param string[] $parts - */ - public function setParts(array $parts): void - { - $this->parts = $parts; - } - - /** - * @return string[] - */ - public function getParts(): array - { - return $this->parts; - } - - /** - * @param string[]|string $parts - */ - public function append(array|string $parts): void - { - $parts = (array)$parts; - - foreach ($parts as $part) { - $this->parts[] = $part; - } - } - - /** - * @param string[]|string $parts - */ - public function prepend(array|string $parts): void - { - $parts = (array)$parts; - $parts = array_reverse($parts); // prepend parts in the order they were passed - - foreach ($parts as $part) { - array_unshift($this->parts, $part); - } - } - - public function asString(): string - { - $string = implode("\n", $this->parts); - $string = trim($string); - - return $string; - } - - public function __toString(): string - { - return $this->asString(); - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Code/TargetingCodeGenerator.php b/bundles/PersonalizationBundle/src/Targeting/Code/TargetingCodeGenerator.php deleted file mode 100644 index c60d30b7320..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Code/TargetingCodeGenerator.php +++ /dev/null @@ -1,97 +0,0 @@ -eventDispatcher = $eventDispatcher; - $this->templatingEngine = $templatingEngine; - } - - public function generateCode(VisitorInfo $visitorInfo): string - { - $data = [ - 'inDebugMode' => \Pimcore::inDebugMode(), - 'dataProviderKeys' => $visitorInfo->getFrontendDataProviders(), - ]; - - $event = new TargetingCodeEvent( - '@PimcorePersonalization/Targeting/targetingCode.html.twig', - $this->buildCodeBlocks(), - $data - ); - - $this->eventDispatcher->dispatch($event, TargetingEvents::TARGETING_CODE); - - return $this->renderTemplate($event); - } - - private function renderTemplate(TargetingCodeEvent $event): string - { - $data = $event->getData(); - $data['blocks'] = $event->getBlocks(); - - $code = $this->templatingEngine->render( - $event->getTemplate(), - $data - ); - - $code = trim($code); - - return $code; - } - - private function buildCodeBlocks(): array - { - $blocks = []; - foreach ($this->blocks as $block) { - $blocks[$block] = new CodeBlock(); - } - - return $blocks; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Condition/AbstractVariableCondition.php b/bundles/PersonalizationBundle/src/Targeting/Condition/AbstractVariableCondition.php deleted file mode 100644 index 43eb0658a52..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Condition/AbstractVariableCondition.php +++ /dev/null @@ -1,44 +0,0 @@ - - */ - private array $variables = []; - - /** - * {@inheritdoc} - */ - public function getMatchedVariables(): array - { - return $this->variables; - } - - final protected function setMatchedVariables(array $variables): void - { - $this->variables = $variables; - } - - final protected function setMatchedVariable(string $key, mixed $value): void - { - $this->variables[$key] = $value; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Condition/Browser.php b/bundles/PersonalizationBundle/src/Targeting/Condition/Browser.php deleted file mode 100644 index 01aab592b5c..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Condition/Browser.php +++ /dev/null @@ -1,91 +0,0 @@ -browser = $browser; - } - - /** - * {@inheritdoc} - */ - public static function fromConfig(array $config): static - { - return new static($config['browser'] ?? null); - } - - /** - * {@inheritdoc} - */ - public function getDataProviderKeys(): array - { - return [Device::PROVIDER_KEY]; - } - - /** - * {@inheritdoc} - */ - public function canMatch(): bool - { - return !empty($this->browser); - } - - /** - * {@inheritdoc} - */ - public function match(VisitorInfo $visitorInfo): bool - { - $device = $visitorInfo->get(Device::PROVIDER_KEY); - - if (!$device || true === ($device['is_bot'] ?? false)) { - return false; - } - - $client = $device['client'] ?? null; - if (!$client) { - return false; - } - - $type = $client['type'] ?? null; - $name = $client['name'] ?? null; - - if ($this->browser === 'ie') { - $this->browser = 'Internet Explorer'; - } - - if ('browser' === $type && strtolower($name ?? '') === strtolower($this->browser)) { - $this->setMatchedVariables([ - 'type' => $type, - 'name' => $name, - ]); - - return true; - } - - return false; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Condition/ConditionInterface.php b/bundles/PersonalizationBundle/src/Targeting/Condition/ConditionInterface.php deleted file mode 100644 index d1f1b30cadc..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Condition/ConditionInterface.php +++ /dev/null @@ -1,47 +0,0 @@ -country = $country; - } - - /** - * {@inheritdoc} - */ - public static function fromConfig(array $config): static - { - return new static($config['country'] ?? null); - } - - /** - * {@inheritdoc} - */ - public function getDataProviderKeys(): array - { - return [GeoIp::PROVIDER_KEY]; - } - - /** - * {@inheritdoc} - */ - public function canMatch(): bool - { - return !empty($this->country); - } - - /** - * {@inheritdoc} - */ - public function match(VisitorInfo $visitorInfo): bool - { - $city = $visitorInfo->get(GeoIp::PROVIDER_KEY); - - if (!$city || ! isset($city['country'])) { - return false; - } - - if ($city['country']['iso_code'] === $this->country) { - $this->setMatchedVariable('iso_code', $city['country']['iso_code']); - - return true; - } - - return false; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Condition/EventDispatchingConditionInterface.php b/bundles/PersonalizationBundle/src/Targeting/Condition/EventDispatchingConditionInterface.php deleted file mode 100644 index 42d71801963..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Condition/EventDispatchingConditionInterface.php +++ /dev/null @@ -1,34 +0,0 @@ -latitude = $latitude; - $this->longitude = $longitude; - $this->radius = $radius; - } - - /** - * {@inheritdoc} - */ - public static function fromConfig(array $config): static - { - return new static( - $config['latitude'] ? (float)$config['latitude'] : null, - $config['longitude'] ? (float)$config['longitude'] : null, - $config['radius'] ? (int)$config['radius'] : null - ); - } - - /** - * {@inheritdoc} - */ - public function getDataProviderKeys(): array - { - return [GeoLocation::PROVIDER_KEY]; - } - - /** - * {@inheritdoc} - */ - public function canMatch(): bool - { - return !empty($this->latitude) && !empty($this->longitude) && !empty($this->radius); - } - - /** - * {@inheritdoc} - */ - public function match(VisitorInfo $visitorInfo): bool - { - /** @var GeoLocationModel|null $location */ - $location = $visitorInfo->get(GeoLocation::PROVIDER_KEY); - - if (!$location) { - return false; - } - - $distance = $this->calculateDistance( - $this->latitude, $this->longitude, - $location->getLatitude(), $location->getLongitude() - ); - - if ($distance < ($this->radius * 1000)) { - $this->setMatchedVariables([ - 'latitude' => $location->getLatitude(), - 'longitude' => $location->getLongitude(), - ]); - - return true; - } - - return false; - } - - private function calculateDistance(float $latA, float $longA, float $latB, float $longB): float - { - $coordA = new Coordinate($latA, $longA); - $coordB = new Coordinate($latB, $longB); - - $calculator = new Haversine(); - $distance = $calculator->getDistance($coordA, $coordB); - - return $distance; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Condition/HardwarePlatform.php b/bundles/PersonalizationBundle/src/Targeting/Condition/HardwarePlatform.php deleted file mode 100644 index ffc7e24aa5a..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Condition/HardwarePlatform.php +++ /dev/null @@ -1,111 +0,0 @@ - 'mobile', - 'phablet' => 'mobile', - 'feature phone' => 'mobile', - ]; - - public function __construct(string $platform = null) - { - $this->platform = $platform; - } - - /** - * {@inheritdoc} - */ - public static function fromConfig(array $config): static - { - return new static($config['platform'] ?? null); - } - - /** - * {@inheritdoc} - */ - public function getDataProviderKeys(): array - { - return [Device::PROVIDER_KEY]; - } - - /** - * {@inheritdoc} - */ - public function canMatch(): bool - { - return !empty($this->platform); - } - - /** - * {@inheritdoc} - */ - public function match(VisitorInfo $visitorInfo): bool - { - $device = $visitorInfo->get(Device::PROVIDER_KEY); - - if (!$device || true === ($device['is_bot'] ?? false)) { - return false; - } - - $deviceInfo = $device['device'] ?? null; - if (!$deviceInfo) { - return false; - } - - $platform = $deviceInfo['type'] ?? null; - if (!empty($platform) && isset(static::$deviceMapping[$platform])) { - $platform = static::$deviceMapping[$platform]; - } - - if ($this->matchesPlatform($platform)) { - $this->setMatchedVariable('platform', $platform); - - return true; - } - - return false; - } - - private function matchesPlatform(string $platform = null): bool - { - if (empty($platform)) { - return false; - } - - if ('all' === $this->platform) { - return true; - } - - return $platform === $this->platform; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Condition/Language.php b/bundles/PersonalizationBundle/src/Targeting/Condition/Language.php deleted file mode 100644 index d36f23fb168..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Condition/Language.php +++ /dev/null @@ -1,94 +0,0 @@ -language = $language; - } - - /** - * {@inheritdoc} - */ - public static function fromConfig(array $config): static - { - return new static($config['language'] ?? null); - } - - /** - * {@inheritdoc} - */ - public function canMatch(): bool - { - return !empty($this->language); - } - - /** - * {@inheritdoc} - */ - public function match(VisitorInfo $visitorInfo): bool - { - $request = $visitorInfo->getRequest(); - - $language = $this->loadLanguage($request); - if (empty($language)) { - return false; - } - - if ($language === $this->language) { - $this->setMatchedVariable('language', $language); - - return true; - } - - // only check the language without territory if configured - if (false === strpos($this->language, '_') && false !== strpos($language, '_')) { - $normalizedLanguage = explode('_', $language)[0]; - - if ($normalizedLanguage === $this->language) { - $this->setMatchedVariable('language', $language); - - return true; - } - } - - return false; - } - - protected function loadLanguage(Request $request): ?string - { - // handle override - $language = OverrideAttributeResolver::getOverrideValue($request, 'language'); - if (!empty($language)) { - return $language; - } - - return $request->getPreferredLanguage(); - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Condition/OperatingSystem.php b/bundles/PersonalizationBundle/src/Targeting/Condition/OperatingSystem.php deleted file mode 100644 index 927bada6b3a..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Condition/OperatingSystem.php +++ /dev/null @@ -1,112 +0,0 @@ - 'macos', - 'WIN' => 'windows', - 'LIN' => 'linux', - 'AND' => 'android', - 'IOS' => 'ios', - ]; - - public function __construct(string $system = null) - { - $this->system = $system; - } - - /** - * {@inheritdoc} - */ - public static function fromConfig(array $config): static - { - return new static($config['system'] ?? null); - } - - /** - * {@inheritdoc} - */ - public function getDataProviderKeys(): array - { - return [Device::PROVIDER_KEY]; - } - - /** - * {@inheritdoc} - */ - public function canMatch(): bool - { - return !empty($this->system); - } - - /** - * {@inheritdoc} - */ - public function match(VisitorInfo $visitorInfo): bool - { - $device = $visitorInfo->get(Device::PROVIDER_KEY); - - if (!$device || true === ($device['is_bot'] ?? false)) { - return false; - } - - $osInfo = $device['os'] ?? null; - if (!$osInfo) { - return false; - } - - $os = $osInfo['short_name'] ?? null; - if (!empty($os) && isset(static::$osMapping[$os])) { - $os = static::$osMapping[$os]; - } - - if ($this->matchesOperatingSystem($os)) { - $this->setMatchedVariable('os', $os); - - return true; - } - - return false; - } - - private function matchesOperatingSystem(string $os = null): bool - { - if (empty($os)) { - return false; - } - - if ('all' === $this->system) { - return true; - } - - return $os === $this->system; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Condition/ReferringSite.php b/bundles/PersonalizationBundle/src/Targeting/Condition/ReferringSite.php deleted file mode 100644 index 85b1fd62924..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Condition/ReferringSite.php +++ /dev/null @@ -1,67 +0,0 @@ -pattern = $pattern; - } - - /** - * {@inheritdoc} - */ - public static function fromConfig(array $config): static - { - return new static($config['referrer'] ?? null); - } - - /** - * {@inheritdoc} - */ - public function canMatch(): bool - { - return !empty($this->pattern); - } - - /** - * {@inheritdoc} - */ - public function match(VisitorInfo $visitorInfo): bool - { - $request = $visitorInfo->getRequest(); - $referrer = $request->headers->get('Referer', 'direct'); - - $result = preg_match($this->pattern, $referrer); - if ($result) { - $this->setMatchedVariable('referrer', $referrer); - - return true; - } - - return false; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Condition/SearchEngine.php b/bundles/PersonalizationBundle/src/Targeting/Condition/SearchEngine.php deleted file mode 100644 index f117d2f6d50..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Condition/SearchEngine.php +++ /dev/null @@ -1,100 +0,0 @@ -validEngines); - - if (!in_array($engine, $validEngines, true)) { - throw new \InvalidArgumentException(sprintf( - 'Invalid engine: "%s"', - $engine - )); - } - } - - $this->engine = $engine; - } - - /** - * {@inheritdoc} - */ - public static function fromConfig(array $config): static - { - return new static($config['searchengine'] ?? null); - } - - /** - * {@inheritdoc} - */ - public function canMatch(): bool - { - $validEngines = array_merge(['all'], $this->validEngines); - - return !empty($this->engine) && in_array($this->engine, $validEngines, true); - } - - /** - * {@inheritdoc} - */ - public function match(VisitorInfo $visitorInfo): bool - { - $request = $visitorInfo->getRequest(); - $referrer = $request->headers->get('Referrer'); - - if (empty($referrer)) { - return false; - } - - $pattern = null; - - if ('all' === $this->engine) { - $engines = array_map(function (string $engine) { - return preg_quote($engine, '/'); - }, $this->validEngines); - - $pattern = '/(' . implode('|', $engines) . ')/i'; - } else { - $pattern = '/(' . preg_quote($this->engine, '/') . ')/i'; - } - - if (preg_match($pattern, $referrer)) { - $this->setMatchedVariable('referrer', $referrer); - - return true; - } - - return false; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Condition/TargetGroup.php b/bundles/PersonalizationBundle/src/Targeting/Condition/TargetGroup.php deleted file mode 100644 index 62f0272847f..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Condition/TargetGroup.php +++ /dev/null @@ -1,62 +0,0 @@ -targetGroupId = $targetGroupId; - } - - /** - * @return self - */ - public static function fromConfig(array $config): self - { - return new self($config['targetGroup'] ?? null); - } - - /** - * {@inheritdoc} - */ - public function canMatch(): bool - { - return null !== $this->targetGroupId && $this->targetGroupId > 0; - } - - /** - * {@inheritdoc} - */ - public function match(VisitorInfo $visitorInfo): bool - { - foreach ($visitorInfo->getAssignedTargetGroups() as $targetGroup) { - if ($targetGroup->getId() === $this->targetGroupId) { - $this->setMatchedVariable('target_group_id', $targetGroup->getId()); - - return true; - } - } - - return false; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Condition/TimeOnSite.php b/bundles/PersonalizationBundle/src/Targeting/Condition/TimeOnSite.php deleted file mode 100644 index 33764f38535..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Condition/TimeOnSite.php +++ /dev/null @@ -1,91 +0,0 @@ -seconds = $seconds; - } - - /** - * {@inheritdoc} - */ - public static function fromConfig(array $config): static - { - $seconds = $config['seconds'] ?? 0; - $seconds += ($config['minutes'] ?? 0) * 60; - $seconds += ($config['hours'] ?? 0) * 60 * 60; - - return new static($seconds); - } - - /** - * {@inheritdoc} - */ - public function getDataProviderKeys(): array - { - return [TargetingStorage::PROVIDER_KEY]; - } - - /** - * {@inheritdoc} - */ - public function canMatch(): bool - { - return $this->seconds > 0; - } - - /** - * {@inheritdoc} - */ - public function match(VisitorInfo $visitorInfo): bool - { - /** @var TargetingStorageInterface $storage */ - $storage = $visitorInfo->get(TargetingStorage::PROVIDER_KEY); - - // set/update meta value to make sure storage updates/creates its timestamps - $storage->set( - $visitorInfo, - TargetingStorageInterface::SCOPE_SESSION, - TargetingStorageInterface::STORAGE_KEY_META_ENTRY, - 1 - ); - - $createdAt = $storage->getCreatedAt($visitorInfo, TargetingStorageInterface::SCOPE_SESSION); - if (null === $createdAt) { - return false; - } - - $timeOnSite = time() - $createdAt->getTimestamp(); - - return $timeOnSite >= $this->seconds; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Condition/Url.php b/bundles/PersonalizationBundle/src/Targeting/Condition/Url.php deleted file mode 100644 index a70440fda61..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Condition/Url.php +++ /dev/null @@ -1,68 +0,0 @@ -pattern = $pattern; - } - - /** - * {@inheritdoc} - */ - public static function fromConfig(array $config): static - { - return new static($config['url'] ?? null); - } - - /** - * {@inheritdoc} - */ - public function canMatch(): bool - { - return !empty($this->pattern); - } - - /** - * {@inheritdoc} - */ - public function match(VisitorInfo $visitorInfo): bool - { - $request = $visitorInfo->getRequest(); - - $uri = $request->getUri(); - $result = preg_match($this->pattern, $uri); - - if ($result) { - $this->setMatchedVariable('uri', $uri); - - return true; - } - - return false; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Condition/VariableConditionInterface.php b/bundles/PersonalizationBundle/src/Targeting/Condition/VariableConditionInterface.php deleted file mode 100644 index 1d5c36a3c81..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Condition/VariableConditionInterface.php +++ /dev/null @@ -1,35 +0,0 @@ -count = $count; - } - - /** - * {@inheritdoc} - */ - public static function fromConfig(array $config): static - { - return new static($config['number'] ?? 0); - } - - /** - * {@inheritdoc} - */ - public function getDataProviderKeys(): array - { - return [VisitedPagesCounter::PROVIDER_KEY]; - } - - /** - * {@inheritdoc} - */ - public function canMatch(): bool - { - return $this->count > 0; - } - - /** - * {@inheritdoc} - */ - public function match(VisitorInfo $visitorInfo): bool - { - /** @var VisitedPagesCounterService $counter */ - $counter = $visitorInfo->get(VisitedPagesCounter::PROVIDER_KEY); - $count = $counter->getCount($visitorInfo); - - if ($count >= $this->count) { - $this->setMatchedVariable('visited_pages_count', $count); - - return true; - } - - return false; - } - - /** - * {@inheritdoc} - */ - public function postMatch(VisitorInfo $visitorInfo, EventDispatcherInterface $eventDispatcher): void - { - // emit event which instructs VisitedPagesCountListener to increment the count after matching - $eventDispatcher->dispatch(new GenericEvent(), TargetingEvents::VISITED_PAGES_COUNT_MATCH); - } - - /** - * {@inheritdoc} - */ - public function preMatch(VisitorInfo $visitorInfo, EventDispatcherInterface $eventDispatcher): void - { - // noop - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/ConditionFactory.php b/bundles/PersonalizationBundle/src/Targeting/ConditionFactory.php deleted file mode 100644 index 6cf619d523a..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/ConditionFactory.php +++ /dev/null @@ -1,102 +0,0 @@ -eventDispatcher = $eventDispatcher; - $this->conditions = $conditions; - } - - /** - * {@inheritdoc} - */ - public function build(array $config): ConditionInterface - { - /** @var string|null $type */ - $type = $config['type'] ?? null; - - if (empty($type)) { - throw new \InvalidArgumentException('Invalid condition: Type is not set'); - } - - if (!isset($this->conditions[$type])) { - throw new \InvalidArgumentException(sprintf( - 'Invalid condition: Condition with type "%s" is not registered', - $type - )); - } - - $typeConfig = array_filter($config, function ($v, $k) { - return !in_array($k, $this->blacklistedKeys); - }, ARRAY_FILTER_USE_BOTH); - - $event = new BuildConditionEvent($type, $this->conditions[$type], $typeConfig); - $this->eventDispatcher->dispatch($event, TargetingEvents::BUILD_CONDITION); - - if ($event->hasCondition()) { - return $event->getCondition(); - } - - return $this->buildInstance($type, $typeConfig); - } - - protected function buildInstance(string $type, array $config): ConditionInterface - { - $class = $this->conditions[$type]; - - if (!class_exists($class)) { - throw new \RuntimeException(sprintf( - 'Configured condition class "%s" for type "%s" does not exist', - $class, - $type - )); - } - - if (!is_subclass_of($class, ConditionInterface::class)) { - throw new \RuntimeException(sprintf( - 'Configured condition class "%s" for type "%s" has not the ConditionInterface', - $class, - $type - )); - } - - return $class::fromConfig($config); - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/ConditionFactoryInterface.php b/bundles/PersonalizationBundle/src/Targeting/ConditionFactoryInterface.php deleted file mode 100644 index 07170da1320..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/ConditionFactoryInterface.php +++ /dev/null @@ -1,33 +0,0 @@ -conditionFactory = $conditionFactory; - $this->dataLoader = $dataLoader; - $this->eventDispatcher = $eventDispatcher; - $this->expressionLanguage = $expressionLanguage; - $this->logger = $logger; - } - - /** - * {@inheritdoc} - */ - public function match(VisitorInfo $visitorInfo, array $conditions, bool $collectVariables = false): bool - { - // reset internal state - $this->collectedVariables = []; - - $count = count($conditions); - if (0 === $count) { - // no conditions -> rule matches - return true; - } elseif (1 === $count) { - // no need to build up expression if there's only one condition - return $this->matchCondition($visitorInfo, $conditions[0], $collectVariables); - } - - $expressionBuilder = new ExpressionBuilder(); - - foreach ($conditions as $conditionConfig) { - $conditionResult = $this->matchCondition($visitorInfo, $conditionConfig, $collectVariables); - - $expressionBuilder->addCondition($conditionConfig, $conditionResult); - } - - $expression = $expressionBuilder->getExpression(); - $values = $expressionBuilder->getValues(); - $result = $this->expressionLanguage->evaluate($expression, $values); - - return (bool)$result; - } - - /** - * {@inheritdoc} - */ - public function getCollectedVariables(): array - { - return $this->collectedVariables; - } - - private function matchCondition(VisitorInfo $visitorInfo, array $config, bool $collectVariables = false): bool - { - try { - $condition = $this->conditionFactory->build($config); - } catch (\Throwable $e) { - $this->logger->error((string) $e); - - return false; - } - - // check prerequisites - e.g. a condition without a value - // (= all values match) does not need to fetch provider data - // as location or browser - if (!$condition->canMatch()) { - return false; - } - - if ($condition instanceof DataProviderDependentInterface) { - $this->dataLoader->loadDataFromProviders($visitorInfo, $condition->getDataProviderKeys()); - } - - if ($condition instanceof EventDispatchingConditionInterface) { - $condition->preMatch($visitorInfo, $this->eventDispatcher); - } - - try { - $result = $condition->match($visitorInfo); - } catch (\Throwable $e) { - $this->logger->error((string) $e); - - return false; - } - - if ($collectVariables) { - $this->collectConditionVariables($config, $condition); - } - - if ($condition instanceof EventDispatchingConditionInterface) { - $condition->postMatch($visitorInfo, $this->eventDispatcher); - } - - return $result; - } - - private function collectConditionVariables(array $config, ConditionInterface $condition): void - { - $data = [ - 'type' => $config['type'], - ]; - - if ($condition instanceof VariableConditionInterface) { - $variables = $condition->getMatchedVariables(); - - if (!empty($variables)) { - $data['data'] = $variables; - } - } - - $this->collectedVariables[] = $data; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/ConditionMatcher/ExpressionBuilder.php b/bundles/PersonalizationBundle/src/Targeting/ConditionMatcher/ExpressionBuilder.php deleted file mode 100644 index a0249a82f52..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/ConditionMatcher/ExpressionBuilder.php +++ /dev/null @@ -1,76 +0,0 @@ -parts); - } - - public function getValues(): array - { - return $this->values; - } - - public function addCondition(array $config, bool $result): void - { - if (!empty($this->parts)) { - $this->parts[] = $this->normalizeOperator($config['operator']); - } - - if ($config['bracketLeft']) { - $this->parts[] = ' ('; - } - - $valueKey = $config['type'] . '_' . $this->valueIndex++; - - $this->values[$valueKey] = $result; - $this->parts[] = $valueKey; - - if ($config['bracketRight']) { - $this->parts[] = ') '; - } - } - - private function normalizeOperator(string $operator = null): string - { - if (empty($operator)) { - $operator = 'and'; - } - - $mapping = [ - 'and_not' => 'and not', - ]; - - if (isset($mapping[$operator])) { - $operator = $mapping[$operator]; - } - - $operator = sprintf(' %s ', $operator); - - return $operator; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/ConditionMatcherInterface.php b/bundles/PersonalizationBundle/src/Targeting/ConditionMatcherInterface.php deleted file mode 100644 index e07afa43a55..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/ConditionMatcherInterface.php +++ /dev/null @@ -1,41 +0,0 @@ -dataProviders = $dataProviders; - } - - /** - * {@inheritdoc} - */ - public function loadDataFromProviders(VisitorInfo $visitorInfo, array|string $providerKeys): void - { - if (!is_array($providerKeys)) { - $providerKeys = [(string)$providerKeys]; - } - - foreach ($providerKeys as $providerKey) { - $loadedProviders = $visitorInfo->get('_data_providers', []); - - // skip already loaded providers to avoid circular reference loops - if (in_array($providerKey, $loadedProviders)) { - continue; - } - - $loadedProviders[] = $providerKey; - $visitorInfo->set('_data_providers', $loadedProviders); - - $dataProvider = $this->dataProviders->get($providerKey); - - // load data from required providers - if ($dataProvider instanceof DataProviderDependentInterface) { - $this->loadDataFromProviders( - $visitorInfo, - $dataProvider->getDataProviderKeys() - ); - } - - $this->startStopwatch('Targeting:load:' . $providerKey, 'targeting'); - - $dataProvider->load($visitorInfo); - - $this->stopStopwatch('Targeting:load:' . $providerKey); - } - } - - /** - * {@inheritdoc} - */ - public function hasDataProvider(string $type): bool - { - return $this->dataProviders->has($type); - } - - /** - * {@inheritdoc} - */ - public function getDataProvider(string $type): DataProviderInterface - { - if (!$this->dataProviders->has($type)) { - throw new \InvalidArgumentException(sprintf( - 'There is no data provider registered for type "%s"', - $type - )); - } - - return $this->dataProviders->get($type); - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/DataLoaderInterface.php b/bundles/PersonalizationBundle/src/Targeting/DataLoaderInterface.php deleted file mode 100644 index 65355fbc197..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/DataLoaderInterface.php +++ /dev/null @@ -1,40 +0,0 @@ -logger = $logger; - } - - public function setCache(CoreCacheHandler $cache): void - { - $this->cache = $cache; - } - - public function setCachePool(TagAwareAdapterInterface $cachePool): void - { - $this->cachePool = $cachePool; - } - - /** - * {@inheritdoc} - */ - public function load(VisitorInfo $visitorInfo): void - { - if ($visitorInfo->has(self::PROVIDER_KEY)) { - return; - } - - $userAgent = $visitorInfo->getRequest()->headers->get('User-Agent', ''); - - $result = $this->loadData($userAgent); - $result = $this->handleOverrides($visitorInfo->getRequest(), $result); - - $visitorInfo->set( - self::PROVIDER_KEY, - $result - ); - } - - private function handleOverrides(Request $request, array $result = null): ?array - { - $overrides = OverrideAttributeResolver::getOverrideValue($request, 'device'); - if (empty($overrides)) { - return $result; - } - - $result = $result ?? []; - - if (isset($overrides['hardwarePlatform']) && !empty($overrides['hardwarePlatform'])) { - $result['device'] = array_merge($result['device'] ?? [], [ - 'type' => $overrides['hardwarePlatform'], - ]); - } - - if (isset($overrides['operatingSystem']) && !empty($overrides['operatingSystem'])) { - $result['os'] = array_merge($result['os'] ?? [], [ - 'short_name' => $overrides['operatingSystem'], - ]); - } - - if (isset($overrides['browser']) && !empty($overrides['browser'])) { - $result['client'] = array_merge($result['client'] ?? [], [ - 'type' => 'browser', - 'name' => $overrides['browser'], - ]); - } - - return $result; - } - - private function loadData(string $userAgent): ?array - { - if (null === $this->cache) { - return $this->doLoadData($userAgent); - } - - $cacheKey = implode('_', ['targeting', self::PROVIDER_KEY, sha1($userAgent)]); - - if ($result = $this->cache->load($cacheKey)) { - return $result; - } - - $result = $this->doLoadData($userAgent); - if (!$result) { - return $result; - } - - $this->cache->save($cacheKey, $result, ['targeting', 'targeting_' . self::PROVIDER_KEY]); - - return $result; - } - - private function doLoadData(string $userAgent): ?array - { - try { - $dd = new DeviceDetector($userAgent); - - if (null !== $this->cachePool) { - $dd->setCache(new PSR6Bridge($this->cachePool)); - } - - $dd->parse(); - } catch (\Throwable $e) { - $this->logger->error((string) $e); - - return null; - } - - return $this->extractData($dd); - } - - protected function extractData(DeviceDetector $dd): array - { - if ($dd->isBot()) { - return [ - 'user_agent' => $dd->getUserAgent(), - 'bot' => $dd->getBot(), - 'is_bot' => true, - ]; - } - - $osFamily = OperatingSystem::getOsFamily($dd->getOs('short_name')); - $browserFamily = Browser::getBrowserFamily($dd->getClient('short_name')); - - $processed = [ - 'user_agent' => $dd->getUserAgent(), - 'bot' => $dd->getBot(), - 'is_bot' => $dd->isBot(), - 'os' => $dd->getOs(), - 'os_family' => $osFamily ?: 'Unknown', - 'client' => $dd->getClient(), - 'device' => [ - 'type' => $dd->getDeviceName(), - 'brand' => $dd->getBrandName(), - 'model' => $dd->getModel(), - ], - 'browser_family' => $browserFamily ?: 'Unknown', - ]; - - return $processed; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/DataProvider/GeoIp.php b/bundles/PersonalizationBundle/src/Targeting/DataProvider/GeoIp.php deleted file mode 100644 index 76df42fed67..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/DataProvider/GeoIp.php +++ /dev/null @@ -1,168 +0,0 @@ -geoIpProvider = $geoIpProvider; - $this->logger = $logger; - } - - public function setCache(CoreCacheHandler $cache): void - { - $this->cache = $cache; - } - - /** - * {@inheritdoc} - */ - public function load(VisitorInfo $visitorInfo): void - { - if ($visitorInfo->has(self::PROVIDER_KEY)) { - return; - } - - $result = $this->loadData($visitorInfo); - - $visitorInfo->set( - self::PROVIDER_KEY, - $result - ); - } - - public function loadData(VisitorInfo $visitorInfo): ?array - { - $result = null; - $request = $visitorInfo->getRequest(); - - $ip = $request->getClientIp(); - - if ($this->isPublicIp($ip)) { - $result = $this->resolveIp($ip); - } - - $result = $this->handleOverrides($request, $result); - - return $result; - } - - private function handleOverrides(Request $request, array $result = null): ?array - { - $overrides = OverrideAttributeResolver::getOverrideValue($request, 'location'); - if (empty($overrides)) { - return $result; - } - - $result = $result ?? []; - - if (isset($overrides['country']) && !empty($overrides['country'])) { - $result['country'] = array_merge($result['country'] ?? [], [ - 'iso_code' => $overrides['country'], - ]); - } - - return $result; - } - - private function isPublicIp(string $ip): bool - { - $result = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE); - - return $result === $ip; - } - - private function resolveIp(string $ip): ?array - { - if (null === $this->cache) { - return $this->doResolveIp($ip); - } - - $cacheKey = implode('_', ['targeting', self::PROVIDER_KEY, sha1($ip)]); - - if ($result = $this->cache->load($cacheKey)) { - return $result; - } - - $result = $this->doResolveIp($ip); - if (!$result) { - return $result; - } - - $this->cache->save($cacheKey, $result, ['targeting', 'targeting_' . self::PROVIDER_KEY]); - - return $result; - } - - private function doResolveIp(string $ip): ?array - { - try { - $city = $this->geoIpProvider->city($ip); - } catch (\Throwable $e) { - $this->logger->error((string) $e); - - return null; - } - - return $this->extractData($city); - } - - protected function extractData(City $city): array - { - $data = $city->jsonSerialize(); - - // remove localized names as we don't need them - $filter = function ($key) { - return 'names' !== $key; - }; - - foreach (array_keys($data) as $section) { - $data[$section] = array_filter($data[$section], $filter, ARRAY_FILTER_USE_KEY); - } - - if (isset($data['subdivisions']) && count($data['subdivisions']) > 0) { - foreach ($data['subdivisions'] as $idx => $subdivision) { - $data['subdivisions'][$idx] = array_filter($data['subdivisions'][$idx], $filter, ARRAY_FILTER_USE_KEY); - } - } - - return $data; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/DataProvider/GeoLocation.php b/bundles/PersonalizationBundle/src/Targeting/DataProvider/GeoLocation.php deleted file mode 100644 index 1f2b6e4e825..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/DataProvider/GeoLocation.php +++ /dev/null @@ -1,159 +0,0 @@ -geoIpDataProvider = $geoIpProvider; - $this->logger = $logger; - } - - public function load(VisitorInfo $visitorInfo): void - { - $location = $this->loadLocation($visitorInfo); - $location = $this->handleOverrides($visitorInfo->getRequest(), $location); - - $visitorInfo->set( - self::PROVIDER_KEY, - $location - ); - } - - private function handleOverrides(Request $request, GeoLocationModel $location = null): ?GeoLocationModel - { - $overrides = OverrideAttributeResolver::getOverrideValue($request, 'location'); - if (empty($overrides)) { - return $location; - } - - $overrides = array_filter($overrides, function ($key) { - return in_array($key, ['latitude', 'longitude', 'altitude']); - }, ARRAY_FILTER_USE_KEY); - - $data = array_merge([ - 'latitude' => $location ? $location->getLatitude() : null, - 'longitude' => $location ? $location->getLongitude() : null, - 'altitude' => $location ? $location->getAltitude() : null, - ], $overrides); - - if (null !== $data['latitude'] && null !== $data['longitude']) { - return GeoLocationModel::build( - $data['latitude'], - $data['longitude'], - $data['altitude'] - ); - } - - return null; - } - - private function loadLocation(VisitorInfo $visitorInfo): ?GeoLocationModel - { - $location = $this->loadGeolocationData($visitorInfo); - if ($location) { - return $location; - } - - // no location found - try to load from GeoIP - return $this->loadGeoIpData($visitorInfo); - } - - private function loadGeolocationData(VisitorInfo $visitorInfo): ?GeoLocationModel - { - // inform frontend that geolocation is wanted - this will work after the first request - $visitorInfo->addFrontendDataProvider(self::PROVIDER_KEY); - - $request = $visitorInfo->getRequest(); - - if (!$request->cookies->has(self::COOKIE_NAME_GEOLOCATION)) { - return null; - } - - $cookie = $request->cookies->get(self::COOKIE_NAME_GEOLOCATION); - if (empty($cookie)) { - return null; - } - - $json = json_decode($cookie, true, 2); - if (JSON_ERROR_NONE !== json_last_error()) { - return null; - } - - $floatFromJson = function (string $property) use ($json) { - if (!isset($json[$property]) || empty($json[$property])) { - return null; - } - - if (!is_numeric($json[$property])) { - return null; - } - - return (float)$json[$property]; - }; - - $latitude = $floatFromJson('lat'); - $longitude = $floatFromJson('long'); - $altitude = $floatFromJson('alt'); - - if (null !== $latitude && null !== $longitude) { - try { - return new GeoLocationModel($latitude, $longitude, $altitude); - } catch (\Throwable $e) { - $this->logger->error((string) $e); - } - } - - return null; - } - - private function loadGeoIpData(VisitorInfo $visitorInfo): ?GeoLocationModel - { - $city = $this->geoIpDataProvider->loadData($visitorInfo); - - if (!$city || !$city['location']['latitude'] || !$city['location']['longitude']) { - return null; - } - - return new GeoLocationModel( - (float)$city['location']['latitude'], - (float)$city['location']['longitude'] - ); - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/DataProvider/TargetingStorage.php b/bundles/PersonalizationBundle/src/Targeting/DataProvider/TargetingStorage.php deleted file mode 100644 index 339d7d3c0df..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/DataProvider/TargetingStorage.php +++ /dev/null @@ -1,41 +0,0 @@ -storage = $storage; - } - - /** - * {@inheritdoc} - */ - public function load(VisitorInfo $visitorInfo): void - { - $visitorInfo->set(self::PROVIDER_KEY, $this->storage); - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/DataProvider/VisitedPagesCounter.php b/bundles/PersonalizationBundle/src/Targeting/DataProvider/VisitedPagesCounter.php deleted file mode 100644 index 5517582b3c4..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/DataProvider/VisitedPagesCounter.php +++ /dev/null @@ -1,41 +0,0 @@ -service = $service; - } - - /** - * {@inheritdoc} - */ - public function load(VisitorInfo $visitorInfo): void - { - $visitorInfo->set(self::PROVIDER_KEY, $this->service); - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/DataProviderDependentInterface.php b/bundles/PersonalizationBundle/src/Targeting/DataProviderDependentInterface.php deleted file mode 100644 index 1ce1d63e170..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/DataProviderDependentInterface.php +++ /dev/null @@ -1,35 +0,0 @@ -add('hardwarePlatform', ChoiceType::class, [ - 'label' => 'Hardware Platform', - 'required' => false, - 'choices' => [ - 'Desktop' => 'desktop', - 'Tablet' => 'tablet', - 'Mobile' => 'mobile', - ], - ]); - - $builder->add('operatingSystem', ChoiceType::class, [ - 'label' => 'Operating System', - 'required' => false, - 'choices' => [ - 'Windows' => 'windows', - 'Mac OS' => 'macos', - 'Linux' => 'linux', - 'Android' => 'android', - 'iOS' => 'ios', - ], - ]); - - $builder->add('browser', ChoiceType::class, [ - 'label' => 'Browser', - 'required' => false, - 'choices' => [ - 'Internet Explorer' => 'ie', - 'Firefox' => 'firefox', - 'Google Chrome' => 'chrome', - 'Safari' => 'safari', - 'Opera' => 'opera', - ], - ]); - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Debug/Form/LocationType.php b/bundles/PersonalizationBundle/src/Targeting/Debug/Form/LocationType.php deleted file mode 100644 index e68f9f12561..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Debug/Form/LocationType.php +++ /dev/null @@ -1,44 +0,0 @@ -add('country', CountryType::class, [ - 'label' => 'Country', - 'required' => false, - ]); - - $builder->add('latitude', TextType::class, [ - 'label' => 'Latitude', - 'required' => false, - ]); - - $builder->add('longitude', TextType::class, [ - 'label' => 'Longitude', - 'required' => false, - ]); - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Debug/Override/DeviceOverrideHandler.php b/bundles/PersonalizationBundle/src/Targeting/Debug/Override/DeviceOverrideHandler.php deleted file mode 100644 index bef4b260c92..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Debug/Override/DeviceOverrideHandler.php +++ /dev/null @@ -1,48 +0,0 @@ -add('device', DeviceType::class, [ - 'label' => 'Device', - 'required' => false, - 'attr' => [ - 'class' => '_ptgtb__override-form__collapse-section', - ], - ]); - } - - public function overrideFromRequest(array $overrides, Request $request): void - { - $device = $overrides['device'] ?? []; - if (empty($device)) { - return; - } - - OverrideAttributeResolver::setOverrideValue($request, 'device', $device); - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Debug/Override/DocumentTargetingOverrideHandler.php b/bundles/PersonalizationBundle/src/Targeting/Debug/Override/DocumentTargetingOverrideHandler.php deleted file mode 100644 index c9ec4b8ec24..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Debug/Override/DocumentTargetingOverrideHandler.php +++ /dev/null @@ -1,61 +0,0 @@ -documentTargetingConfigurator = $documentTargetingConfigurator; - } - - public function buildOverrideForm(FormBuilderInterface $form, Request $request): void - { - $form->add('documentTargetGroup', ChoiceType::class, [ - 'label' => 'Document Target Group', - 'required' => false, - 'choice_loader' => new CallbackChoiceLoader(function () { - return (new TargetGroup\Listing())->load(); - }), - 'choice_value' => function (TargetGroup $targetGroup = null) { - return $targetGroup ? $targetGroup->getId() : ''; - }, - 'choice_label' => function (TargetGroup $targetGroup = null, $key, $index) { - return $targetGroup ? $targetGroup->getName() : ''; - }, - ]); - } - - public function overrideFromRequest(array $overrides, Request $request): void - { - $targetGroup = $overrides['documentTargetGroup'] ?? null; - if ($targetGroup && $targetGroup instanceof TargetGroup) { - $this->documentTargetingConfigurator->setOverrideTargetGroup($targetGroup); - } - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Debug/Override/LanguageOverrideHandler.php b/bundles/PersonalizationBundle/src/Targeting/Debug/Override/LanguageOverrideHandler.php deleted file mode 100644 index 8de41809da9..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Debug/Override/LanguageOverrideHandler.php +++ /dev/null @@ -1,44 +0,0 @@ -add('language', LanguageType::class, [ - 'required' => false, - ]); - } - - public function overrideFromRequest(array $overrides, Request $request): void - { - $language = $overrides['language'] ?? null; - if (empty($language)) { - return; - } - - OverrideAttributeResolver::setOverrideValue($request, 'language', $language); - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Debug/Override/LocationOverrideHandler.php b/bundles/PersonalizationBundle/src/Targeting/Debug/Override/LocationOverrideHandler.php deleted file mode 100644 index 51910f32997..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Debug/Override/LocationOverrideHandler.php +++ /dev/null @@ -1,48 +0,0 @@ -add('location', LocationType::class, [ - 'label' => 'Location', - 'required' => false, - 'attr' => [ - 'class' => '_ptgtb__override-form__collapse-section', - ], - ]); - } - - public function overrideFromRequest(array $overrides, Request $request): void - { - $location = $overrides['location'] ?? []; - if (empty($location)) { - return; - } - - OverrideAttributeResolver::setOverrideValue($request, 'location', $location); - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Debug/OverrideHandler.php b/bundles/PersonalizationBundle/src/Targeting/Debug/OverrideHandler.php deleted file mode 100644 index 5e385d1dbec..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Debug/OverrideHandler.php +++ /dev/null @@ -1,98 +0,0 @@ -formFactory = $formFactory; - $this->overrideHandlers = $overrideHandlers; - } - - public function getForm(Request $request): FormInterface - { - if ($request->attributes->has('pimcore_targeting_override_form')) { - /** @var FormInterface $form */ - $form = $request->attributes->get('pimcore_targeting_override_form'); - - return $form; - } - - $form = $this->buildForm($request); - - $request->attributes->set('pimcore_targeting_override_form', $form); - - return $form; - } - - protected function buildForm(Request $request): FormInterface - { - $formBuilder = $this->formFactory->createNamedBuilder('_ptg_overrides', FormType::class, null, [ - 'csrf_protection' => false, - ]); - - $formBuilder->setMethod('GET'); - - foreach ($this->overrideHandlers as $handler) { - $handler->buildOverrideForm($formBuilder, $request); - } - - return $formBuilder->getForm(); - } - - public function handleRequest(Request $request): void - { - $form = $this->getForm($request); - - $this->handleForm($form, $request); - } - - public function handleForm(FormInterface $form, Request $request): void - { - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - $data = $form->getData(); - if (!empty($data)) { - foreach ($this->overrideHandlers as $handler) { - $handler->overrideFromRequest($data, $request); - } - } - } - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Debug/TargetingDataCollector.php b/bundles/PersonalizationBundle/src/Targeting/Debug/TargetingDataCollector.php deleted file mode 100644 index 1e2971c4b92..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Debug/TargetingDataCollector.php +++ /dev/null @@ -1,196 +0,0 @@ -targetingStorage = $targetingStorage; - $this->targetingConfigurator = $targetingConfigurator; - } - - public function collectVisitorInfo(VisitorInfo $visitorInfo): array - { - return [ - 'visitorId' => $visitorInfo->getVisitorId(), - 'sessionId' => $visitorInfo->getSessionId(), - 'actions' => $visitorInfo->getActions(), - 'data' => $this->filterVisitorInfoData($visitorInfo->getData()), - ]; - } - - public function getFilteredVisitorInfoDataObjecKeys(): array - { - return $this->filteredVisitorInfoDataObjecKeys; - } - - public function setFilteredVisitorInfoDataObjecKeys(array $filteredVisitorInfoDataObjecKeys): void - { - $this->filteredVisitorInfoDataObjecKeys = $filteredVisitorInfoDataObjecKeys; - } - - protected function filterVisitorInfoData(array $data): array - { - // only show a string reference naming the class instead of serializing objects in the list - foreach ($this->filteredVisitorInfoDataObjecKeys as $key) { - if (isset($data[$key]) && is_object($data[$key])) { - $data[$key] = sprintf( - 'object(%s)', - (new \ReflectionObject($data[$key]))->getShortName() - ); - } - } - - return $data; - } - - public function collectStorage(VisitorInfo $visitorInfo): array - { - $storage = []; - - foreach (TargetingStorageInterface::VALID_SCOPES as $scope) { - $created = $this->targetingStorage->getCreatedAt($visitorInfo, $scope); - $updated = $this->targetingStorage->getCreatedAt($visitorInfo, $scope); - - $storage[$scope] = array_merge([ - 'created' => $created ? $created->format('c') : null, - 'updated' => $updated ? $updated->format('c') : null, - ], $this->targetingStorage->all($visitorInfo, $scope)); - } - - return $storage; - } - - public function collectMatchedRules(VisitorInfo $visitorInfo): array - { - $rules = []; - - foreach ($visitorInfo->getMatchingTargetingRules() as $rule) { - $duration = null; - if (null !== $this->stopwatch) { - try { - $event = $this->stopwatch->getEvent(sprintf('Targeting:match:%s', $rule->getName())); - $duration = $event->getDuration(); - } catch (\Throwable $e) { - // noop - } - } - - $rules[] = [ - 'id' => $rule->getId(), - 'name' => $rule->getName(), - 'duration' => $duration, - 'conditions' => $rule->getConditions(), - 'actions' => $rule->getActions(), - ]; - } - - return $rules; - } - - public function collectTargetGroups(VisitorInfo $visitorInfo): array - { - $targetGroups = []; - - foreach ($visitorInfo->getTargetGroupAssignments() as $assignment) { - $targetGroups[] = [ - 'id' => $assignment->getTargetGroup()->getId(), - 'name' => $assignment->getTargetGroup()->getName(), - 'threshold' => $assignment->getTargetGroup()->getThreshold(), - 'count' => $assignment->getCount(), - ]; - } - - return $targetGroups; - } - - /** - * @param Document|null $document - * - * @return array|null - */ - public function collectDocumentTargetGroup(Document $document = null): ?array - { - if (!$document instanceof TargetingDocumentInterface) { - return null; - } - - $targetGroupId = $document->getUseTargetGroup(); - if (!$targetGroupId) { - return null; - } - - $targetGroup = TargetGroup::getById($targetGroupId); - if ($targetGroup) { - return [ - 'id' => $targetGroup->getId(), - 'name' => $targetGroup->getName(), - ]; - } - - return null; - } - - public function collectDocumentTargetGroupMapping(): array - { - $resolvedMapping = $this->targetingConfigurator->getResolvedTargetGroupMapping(); - $mapping = []; - - /** @var TargetGroup $targetGroup */ - foreach ($resolvedMapping as $documentId => $targetGroup) { - $document = Document::getById($documentId); - - $mapping[] = [ - 'document' => [ - 'id' => $document->getId(), - 'path' => $document->getRealFullPath(), - ], - 'targetGroup' => [ - 'id' => $targetGroup->getId(), - 'name' => $targetGroup->getName(), - ], - ]; - } - - return $mapping; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Debug/Util/OverrideAttributeResolver.php b/bundles/PersonalizationBundle/src/Targeting/Debug/Util/OverrideAttributeResolver.php deleted file mode 100644 index aeb23aa1ee3..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Debug/Util/OverrideAttributeResolver.php +++ /dev/null @@ -1,46 +0,0 @@ -attributes->get(OverrideHandlerInterface::REQUEST_ATTRIBUTE, []); - $overrides[$key] = $value; - - $request->attributes->set(OverrideHandlerInterface::REQUEST_ATTRIBUTE, $overrides); - } - - /** - * @param Request $request - * @param string $key - * @param mixed $default - * - * @return mixed - */ - public static function getOverrideValue(Request $request, string $key, mixed $default = null): mixed - { - $overrides = $request->attributes->get(OverrideHandlerInterface::REQUEST_ATTRIBUTE, []); - - return $overrides[$key] ?? $default; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Document/DocumentTargetingConfigurator.php b/bundles/PersonalizationBundle/src/Targeting/Document/DocumentTargetingConfigurator.php deleted file mode 100644 index 5dc26cae799..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Document/DocumentTargetingConfigurator.php +++ /dev/null @@ -1,235 +0,0 @@ -visitorInfoStorage = $visitorInfoStorage; - $this->requestHelper = $requestHelper; - $this->userLoader = $userLoader; - $this->cache = $cache; - } - - /** - * Configure target group to use on the document by reading the most relevant - * target group from the visitor info. - * - * @param Document $document - */ - public function configureTargetGroup(Document $document): void - { - if (!$document instanceof TargetingDocumentInterface) { - return; - } - - // already configured - if (isset($this->targetGroupMapping[$document->getId()])) { - return; - } - - if ($this->isConfiguredByAdminParam($document)) { - return; - } - - if ($this->isConfiguredByOverride($document)) { - return; - } - - $matchingTargetGroups = $this->getMatchingTargetGroups($document); - if (count($matchingTargetGroups) > 0) { - $targetGroup = $matchingTargetGroups[0]; - - $this->targetGroupMapping[$document->getId()] = $targetGroup; - $document->setUseTargetGroup($targetGroup->getId()); - } - } - - /** - * Handle _ptg admin param here only if there's a valid user session - * - * @param TargetingDocumentInterface $document - * - * @return bool - */ - private function isConfiguredByAdminParam(TargetingDocumentInterface $document): bool - { - if (!$this->requestHelper->hasMainRequest()) { - return false; - } - - $request = $this->requestHelper->getMainRequest(); - if (!$this->requestHelper->isFrontendRequestByAdmin($request)) { - return false; - } - - // IMPORTANT: check there is an authenticated admin user before allowing - // to set target groups via parameter - $user = $this->userLoader->getUser(); - if (!$user) { - return false; - } - - // ptg = pimcore target group = will be used from the admin UI to show target specific data - // in editmode - if ($ptg = $request->get('_ptg')) { - $targetGroup = TargetGroup::getById((int)$ptg); - - if ($targetGroup) { - $this->targetGroupMapping[$document->getId()] = $targetGroup; - $document->setUseTargetGroup($targetGroup->getId()); - - return true; - } - } - - return false; - } - - private function isConfiguredByOverride(TargetingDocumentInterface $document): bool - { - if (null !== $this->overrideTargetGroup) { - $this->targetGroupMapping[$document->getId()] = $this->overrideTargetGroup; - $document->setUseTargetGroup($this->overrideTargetGroup->getId()); - - return true; - } - - return false; - } - - public function getConfiguredTargetGroup(Document $document): ?TargetGroup - { - if (isset($this->targetGroupMapping[$document->getId()])) { - return $this->targetGroupMapping[$document->getId()]; - } - - return null; - } - - public function getResolvedTargetGroupMapping(): array - { - return $this->targetGroupMapping; - } - - /** - * Resolve all target groups which were matched and which are valid for - * the document - * - * @param Document $document - * - * @return TargetGroup[] - */ - public function getMatchingTargetGroups(Document $document): array - { - if (!$this->visitorInfoStorage->hasVisitorInfo()) { - return []; - } - - $configuredTargetGroups = $this->getTargetGroupsForDocument($document); - if (empty($configuredTargetGroups)) { - return []; - } - - $visitorInfo = $this->visitorInfoStorage->getVisitorInfo(); - - $result = []; - foreach ($visitorInfo->getAssignedTargetGroups() as $targetGroup) { - if (in_array($targetGroup->getId(), $configuredTargetGroups)) { - $result[$targetGroup->getId()] = $targetGroup; - } - } - - return array_values($result); - } - - /** - * Resolves valid target groups for a document. A target group is seen as valid - * if it has at least one element configured for that target group. - * - * @param Document $document - * - * @return array - */ - public function getTargetGroupsForDocument(Document $document): array - { - if (!$document instanceof TargetingDocumentInterface) { - return []; - } - - /** @var PageSnippet $document */ - $cacheKey = sprintf('document_target_groups_%d', $document->getId()); - - if ($targetGroups = $this->cache->load($cacheKey)) { - return $targetGroups; - } - - $targetGroups = []; - foreach ($document->getEditables() as $key => $tag) { - $pattern = '/^' . preg_quote(TargetingDocumentInterface::TARGET_GROUP_EDITABLE_PREFIX, '/') . '([0-9]+)' . preg_quote(TargetingDocumentInterface::TARGET_GROUP_EDITABLE_SUFFIX, '/') . '/'; - if (preg_match($pattern, (string) $key, $matches)) { - $targetGroups[] = (int)$matches[1]; - } - } - - $targetGroups = array_unique($targetGroups); - $targetGroups = array_filter($targetGroups, function ($id) { - return TargetGroup::isIdActive($id); - }); - - $this->cache->save($cacheKey, $targetGroups, [sprintf('document_%d', $document->getId()), 'target_groups']); - - return $targetGroups; - } - - public function setOverrideTargetGroup(TargetGroup $overrideTargetGroup = null): void - { - $this->overrideTargetGroup = $overrideTargetGroup; - } - - public function getOverrideTargetGroup(): ?TargetGroup - { - return $this->overrideTargetGroup; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/EventListener/DocumentTargetGroupListener.php b/bundles/PersonalizationBundle/src/Targeting/EventListener/DocumentTargetGroupListener.php deleted file mode 100644 index 66c675c6f7a..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/EventListener/DocumentTargetGroupListener.php +++ /dev/null @@ -1,105 +0,0 @@ -documentResolver = $documentResolver; - $this->actionHandler = $actionHandler; - $this->eventDispatcher = $eventDispatcher; - } - - /** - * @return string[] - */ - public static function getSubscribedEvents(): array - { - return [ - TargetingEvents::PRE_RESOLVE => 'onVisitorInfoResolve', - ]; - } - - public function onVisitorInfoResolve(TargetingEvent $event): void - { - $request = $event->getRequest(); - $document = $this->documentResolver->getDocument($request); - - if ($document) { - $this->assignDocumentTargetGroups($document, $event->getVisitorInfo()); - } - } - - private function assignDocumentTargetGroups(Document $document, VisitorInfo $visitorInfo): void - { - if (!$document instanceof Page) { - return; - } - - if (class_exists(Staticroute::class) && null !== Staticroute::getCurrentRoute()) { - return; - } - - // get target groups from document - $targetGroups = $document->getTargetGroups(); - - if (empty($targetGroups)) { - return; - } - - foreach ($targetGroups as $targetGroup) { - $this->actionHandler->apply($visitorInfo, [ - 'type' => 'assign_target_group', - 'targetGroup' => $targetGroup, - ]); - - $this->eventDispatcher->dispatch( - new AssignDocumentTargetGroupEvent($visitorInfo, $document, $targetGroup), - TargetingEvents::ASSIGN_DOCUMENT_TARGET_GROUP - ); - } - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/EventListener/Frontend/FullPageCacheCookieCleanupListener.php b/bundles/PersonalizationBundle/src/Targeting/EventListener/Frontend/FullPageCacheCookieCleanupListener.php deleted file mode 100644 index 320524c159c..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/EventListener/Frontend/FullPageCacheCookieCleanupListener.php +++ /dev/null @@ -1,73 +0,0 @@ -targetingEnableService = $targetingEnableService; - } - - /** - * @return string[] - */ - public static function getSubscribedEvents(): array - { - return [ - FullPageCacheEvents::PREPARE_RESPONSE => 'onPrepareFullPageCacheResponse', - ]; - } - - public function onPrepareFullPageCacheResponse(PrepareResponseEvent $event): void - { - if (!$this->targetingEnableService->isTargetingEnabled()) { - return; - } - $response = $event->getResponse(); - $cookies = $response->headers->getCookies(); - - $blacklist = [ - CookieStorage::COOKIE_NAME_VISITOR, - CookieStorage::COOKIE_NAME_SESSION, - ]; - - foreach ($cookies as $cookie) { - if (in_array($cookie->getName(), $blacklist)) { - $response->headers->removeCookie( - $cookie->getName(), - $cookie->getPath(), - $cookie->getDomain() - ); - } - } - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/EventListener/Frontend/TargetingDocumentRendererListener.php b/bundles/PersonalizationBundle/src/Targeting/EventListener/Frontend/TargetingDocumentRendererListener.php deleted file mode 100644 index 427bc0a9cdf..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/EventListener/Frontend/TargetingDocumentRendererListener.php +++ /dev/null @@ -1,54 +0,0 @@ - 'onPreRender', - DocumentEvents::INCLUDERENDERER_PRE_RENDER => 'onPreRender', - ]; - } - - public function onPreRender(DocumentEvent $event): void - { - $document = $event->getDocument(); - $this->targetingConfigurator->configureTargetGroup($document); - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/EventListener/Frontend/TargetingElementListener.php b/bundles/PersonalizationBundle/src/Targeting/EventListener/Frontend/TargetingElementListener.php deleted file mode 100644 index 9b2fbbd1a6c..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/EventListener/Frontend/TargetingElementListener.php +++ /dev/null @@ -1,101 +0,0 @@ - ['onKernelController', 30], // has to be after DocumentFallbackListener - ]; - } - - public function onKernelController(ControllerEvent $event): void - { - if ($event->isMainRequest()) { - $request = $event->getRequest(); - if (!$this->matchesPimcoreContext($request, PimcoreContextResolver::CONTEXT_DEFAULT)) { - return; - } - - if ($request->attributes->get('_route') === 'fos_js_routing_js') { - return; - } - - $document = $this->documentResolver->getDocument($request); - - if ($document) { - // apply target group configuration - $this->applyTargetGroups($request, $document); - $this->documentResolver->setDocument($request, $document); - } - } - } - - protected function applyTargetGroups(Request $request, Document $document): void - { - if (!$document instanceof TargetingDocumentInterface) { - return; - } - - if (class_exists(Staticroute::class) && null !== Staticroute::getCurrentRoute()) { - return; - } - - // reset because of preview and editmode (saved in session) - $document->setUseTargetGroup(null); - - $this->targetingConfigurator->configureTargetGroup($document); - - if ($document->getUseTargetGroup()) { - $this->logger->info('Setting target group to {targetGroup} for document {document}', [ - 'targetGroup' => $document->getUseTargetGroup(), - 'document' => $document->getFullPath(), - ]); - } - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/EventListener/RenderletListener.php b/bundles/PersonalizationBundle/src/Targeting/EventListener/RenderletListener.php deleted file mode 100644 index ef430a8af7b..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/EventListener/RenderletListener.php +++ /dev/null @@ -1,59 +0,0 @@ - 'configureElementTargeting', - ]; - } - - public function configureElementTargeting(GenericEvent $event): void - { - $requestParams = $event->getArgument('requestParams'); - $element = $event->getArgument('element'); - if (!$element instanceof TargetingDocumentInterface) { - return; - } - - // set selected target group on element - if ($requestParams['_ptg'] ?? false) { - $targetGroup = TargetGroup::getById((int)$requestParams['_ptg']); - if ($targetGroup) { - $element->setUseTargetGroup($targetGroup->getId()); - } - } - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/EventListener/TargetingFullPageCacheListener.php b/bundles/PersonalizationBundle/src/Targeting/EventListener/TargetingFullPageCacheListener.php deleted file mode 100644 index 9af17ebbf20..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/EventListener/TargetingFullPageCacheListener.php +++ /dev/null @@ -1,81 +0,0 @@ -isMainRequest()) { - return; - } - - $request = $event->getRequest(); - if (!\Pimcore\Tool::isFrontend() || \Pimcore\Tool::isFrontendRequestByAdmin($request)) { - return; - } - - if (!$this->matchesPimcoreContext($request, PimcoreContextResolver::CONTEXT_DEFAULT)) { - return; - } - - // check if targeting matched anything and disable cache - if ($this->disabledByTargeting()) { - $this->disable('Targeting matched rules/target groups'); - - return; - } - - parent::onKernelResponse($event); - } - - public function disabledByTargeting(): bool - { - if (!$this->visitorInfoStorage->hasVisitorInfo()) { - return false; - } - - $visitorInfo = $this->visitorInfoStorage->getVisitorInfo(); - - if (!empty($visitorInfo->getMatchingTargetingRules())) { - return true; - } - - if (!empty($visitorInfo->getTargetGroupAssignments())) { - return true; - } - - return false; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/EventListener/TargetingListener.php b/bundles/PersonalizationBundle/src/Targeting/EventListener/TargetingListener.php deleted file mode 100644 index 2ff93fba809..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/EventListener/TargetingListener.php +++ /dev/null @@ -1,259 +0,0 @@ -visitorInfoResolver = $visitorInfoResolver; - $this->actionHandler = $actionHandler; - $this->visitorInfoStorage = $visitorInfoStorage; - $this->requestHelper = $requestHelper; - $this->codeGenerator = $codeGenerator; - $this->targetingEnableService = $targetingEnableService; - } - - public static function getSubscribedEvents(): array - { - return [ - // needs to run before ElementListener to make sure there's a - // resolved VisitorInfo when the document is loaded - KernelEvents::REQUEST => ['onKernelRequest', 7], - KernelEvents::RESPONSE => ['onKernelResponse', -115], - TargetingEvents::PRE_RESOLVE => 'onPreResolve', - ]; - } - - public function onKernelRequest(RequestEvent $event): void - { - if (!$this->targetingEnableService->isTargetingEnabled()) { - return; - } - - $request = $event->getRequest(); - - if (!$event->isMainRequest() && !$this->matchesStaticPageContext($request)) { - return; - } - - // only apply targeting for GET requests - // this may revised in later versions - if ('GET' !== $request->getMethod()) { - return; - } - - if (!$this->matchesPimcoreContext($request, PimcoreContextResolver::CONTEXT_DEFAULT) - && !$this->matchesStaticPageContext($request)) { - return; - } - - if ((!$this->requestHelper->isFrontendRequest($request) && !$this->matchesStaticPageContext($request)) || $this->requestHelper->isFrontendRequestByAdmin($request)) { - return; - } - - if (!$this->visitorInfoResolver->isTargetingConfigured()) { - return; - } - - $this->startStopwatch('Targeting:resolveVisitorInfo', 'targeting'); - - $visitorInfo = $this->visitorInfoResolver->resolve($request); - - $this->stopStopwatch('Targeting:resolveVisitorInfo'); - - // propagate response (e.g. redirect) to request handling - if ($visitorInfo->hasResponse()) { - $event->setResponse($visitorInfo->getResponse()); - } - } - - public function onPreResolve(TargetingEvent $event): void - { - $this->startStopwatch('Targeting:loadStoredAssignments', 'targeting'); - - if (method_exists($this->actionHandler, 'getActionHandler')) { - /** @var AssignTargetGroup $assignTargetGroupHandler */ - $assignTargetGroupHandler = $this->actionHandler->getActionHandler('assign_target_group'); - - $assignTargetGroupHandler->loadStoredAssignments($event->getVisitorInfo()); // load previously assigned target groups - } - - $this->stopStopwatch('Targeting:loadStoredAssignments'); - } - - public function onKernelResponse(ResponseEvent $event): void - { - if (!$this->targetingEnableService->isTargetingEnabled()) { - return; - } - - if (!$this->visitorInfoStorage->hasVisitorInfo()) { - return; - } - - $visitorInfo = $this->visitorInfoStorage->getVisitorInfo(); - $response = $event->getResponse(); - - if ($event->isMainRequest() || $this->matchesStaticPageContext($event->getRequest())) { - $this->startStopwatch('Targeting:responseActions', 'targeting'); - - // handle recorded actions on response - $this->handleResponseActions($visitorInfo, $response); - - $this->stopStopwatch('Targeting:responseActions'); - - if ($this->visitorInfoResolver->isTargetingConfigured()) { - $this->injectTargetingCode($response, $visitorInfo); - } - } - - // check if the visitor info influences the response - if ($this->appliesPersonalization($visitorInfo)) { - // set response to private as soon as we apply personalization - $response->setPrivate(); - } - } - - private function injectTargetingCode(Response $response, VisitorInfo $visitorInfo): void - { - if (!$this->isHtmlResponse($response)) { - return; - } - - $code = $this->codeGenerator->generateCode($visitorInfo); - if (empty($code)) { - return; - } - - $this->injectBeforeHeadEnd($response, $code); - } - - private function handleResponseActions(VisitorInfo $visitorInfo, Response $response): void - { - $actions = $this->getResponseActions($visitorInfo); - if (empty($actions)) { - return; - } - - foreach ($actions as $type => $typeActions) { - $handler = null; - if (method_exists($this->actionHandler, 'getActionHandler')) { - $handler = $this->actionHandler->getActionHandler($type); - } - - if (!$handler instanceof ResponseTransformingActionHandlerInterface) { - throw new \RuntimeException(sprintf( - 'The "%s" action handler does not implement ResponseTransformingActionHandlerInterface', - $type - )); - } - - $handler->transformResponse($visitorInfo, $response, $typeActions); - } - } - - private function getResponseActions(VisitorInfo $visitorInfo): array - { - $actions = []; - - if (!$visitorInfo->hasActions()) { - return $actions; - } - - foreach ($visitorInfo->getActions() as $action) { - $type = $action['type'] ?? null; - $scope = $action['scope'] ?? null; - - if (empty($type) || empty($scope) || $scope !== VisitorInfo::ACTION_SCOPE_RESPONSE) { - continue; - } - - if (!isset($actions[$type])) { - $actions[$type] = [$action]; - } else { - $actions[$type][] = $action; - } - } - - return $actions; - } - - private function appliesPersonalization(VisitorInfo $visitorInfo): bool - { - if (count($visitorInfo->getTargetGroupAssignments()) > 0) { - return true; - } - - if ($visitorInfo->hasActions()) { - return true; - } - - if ($visitorInfo->hasResponse()) { - return true; - } - - return false; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/EventListener/TargetingSessionBagListener.php b/bundles/PersonalizationBundle/src/Targeting/EventListener/TargetingSessionBagListener.php deleted file mode 100644 index 9e291c81ecf..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/EventListener/TargetingSessionBagListener.php +++ /dev/null @@ -1,156 +0,0 @@ -targetingEnableService = $targetingEnableService; - } - - /** - * {@inheritdoc} - * - * @return array - */ - public static function getSubscribedEvents(): array - { - return [ - FullPageCacheEvents::IGNORED_SESSION_KEYS => 'configureIgnoredSessionKeys', - FullPageCacheEvents::PREPARE_RESPONSE => 'prepareFullPageCacheResponse', - // add session support by registering the session configurator and session storage - KernelEvents::REQUEST => ['onKernelRequest', 127], - ]; - } - - public function onKernelRequest(RequestEvent $event): void - { - if (!$this->targetingEnableService->isTargetingEnabled()) { - return; - } - - if (!$this->isEnabled()) { - return; - } - - if (!$event->isMainRequest()) { - return; - } - - $session = $event->getRequest()->getSession(); - - //do not register bags, if session is already started - if ($session->isStarted()) { - return; - } - - $this->configure($session); - } - - public function configure(SessionInterface $session): void - { - $sessionBag = new AttributeBag('_' . self::TARGETING_BAG_SESSION); - $sessionBag->setName(self::TARGETING_BAG_SESSION); - - $visitorBag = new AttributeBag('_' . self::TARGETING_BAG_VISITOR); - $visitorBag->setName(self::TARGETING_BAG_VISITOR); - - $session->registerBag($sessionBag); - $session->registerBag($visitorBag); - } - - public function configureIgnoredSessionKeys(IgnoredSessionKeysEvent $event): void - { - if (!$this->targetingEnableService->isTargetingEnabled()) { - return; - } - - if (!$this->isEnabled()) { - return; - } - - // configures full page cache to ignore session data in targeting storage - $event->setKeys(array_merge($event->getKeys(), [ - '_' . self::TARGETING_BAG_SESSION, - '_' . self::TARGETING_BAG_VISITOR, - ])); - } - - /** - * Removes session cookie from cached response - * - * @param PrepareResponseEvent $event - */ - public function prepareFullPageCacheResponse(PrepareResponseEvent $event): void - { - if (!$this->targetingEnableService->isTargetingEnabled()) { - return; - } - - if (!$this->isEnabled()) { - return; - } - - $request = $event->getRequest(); - $response = $event->getResponse(); - - if (!$request->hasSession()) { - return; - } - - $sessionName = $request->getSession()->getName(); - if (empty($sessionName)) { - return; - } - - $cookies = $response->headers->getCookies(); - - foreach ($cookies as $cookie) { - if ($cookie->getName() === $sessionName) { - $response->headers->removeCookie( - $cookie->getName(), - $cookie->getPath(), - $cookie->getDomain() - ); - } - } - } - - protected function isEnabled(): bool - { - return \Pimcore::getKernel()->getContainer()->getParameter('pimcore_personalization.targeting.session.enabled'); - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/EventListener/ToolbarListener.php b/bundles/PersonalizationBundle/src/Targeting/EventListener/ToolbarListener.php deleted file mode 100644 index efc9b6af16b..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/EventListener/ToolbarListener.php +++ /dev/null @@ -1,199 +0,0 @@ -visitorInfoStorage = $visitorInfoStorage; - $this->documentResolver = $documentResolver; - $this->targetingDataCollector = $targetingDataCollector; - $this->overrideHandler = $overrideHandler; - $this->eventDispatcher = $eventDispatcher; - $this->templatingEngine = $templatingEngine; - $this->codeInjector = $codeInjector; - $this->targetingEnableService = $targetingEnableService; - } - - /** - * @return array[] - */ - public static function getSubscribedEvents(): array - { - return [ - TargetingEvents::PRE_RESOLVE => ['onPreResolve', -10], - KernelEvents::RESPONSE => ['onKernelResponse', -127], - ]; - } - - public function onPreResolve(TargetingEvent $event): void - { - $request = $event->getRequest(); - if (!$this->requestCanDebug($request)) { - return; - } - - // handle overrides from request data - $this->overrideHandler->handleRequest($request); - } - - public function onKernelResponse(ResponseEvent $event): void - { - if (!$this->targetingEnableService->isTargetingEnabled()) { - return; - } - if (!$event->isMainRequest()) { - return; - } - - $request = $event->getRequest(); - if (!$this->requestCanDebug($request)) { - return; - } - - // only inject toolbar if there's a visitor info - if (!$this->visitorInfoStorage->hasVisitorInfo()) { - return; - } - - $document = $this->documentResolver->getDocument($request); - $visitorInfo = $this->visitorInfoStorage->getVisitorInfo(); - $data = $this->collectTemplateData($visitorInfo, $document); - - $overrideForm = $this->overrideHandler->getForm($request); - $data['overrideForm'] = $overrideForm->createView(); - - $this->injectToolbar( - $event->getResponse(), - $data - ); - } - - private function requestCanDebug(Request $request): bool - { - if ($request->attributes->has('pimcore_targeting_debug')) { - return (bool)$request->attributes->get('pimcore_targeting_debug'); - } - - if (!$this->matchesPimcoreContext($request, PimcoreContextResolver::CONTEXT_DEFAULT)) { - return false; - } - - // only inject toolbar for logged in admin users - $adminUser = Authentication::authenticateSession($request); - if (!$adminUser) { - return false; - } - - $cookieValue = (bool)$request->cookies->get('pimcore_targeting_debug'); - if (!$cookieValue) { - return false; - } - - $request->attributes->set('pimcore_targeting_debug', true); - - return true; - } - - private function collectTemplateData(VisitorInfo $visitorInfo, Document $document = null): array - { - $token = substr(hash('sha256', uniqid((string)mt_rand(), true)), 0, 6); - - $tdc = $this->targetingDataCollector; - - $data = [ - 'token' => $token, - 'visitorInfo' => $tdc->collectVisitorInfo($visitorInfo), - 'targetGroups' => $tdc->collectTargetGroups($visitorInfo), - 'rules' => $tdc->collectMatchedRules($visitorInfo), - 'documentTargetGroup' => $tdc->collectDocumentTargetGroup($document), - 'documentTargetGroups' => $tdc->collectDocumentTargetGroupMapping(), - 'storage' => $tdc->collectStorage($visitorInfo), - ]; - - return $data; - } - - private function injectToolbar(Response $response, array $data): void - { - $event = new RenderToolbarEvent('@PimcorePersonalization/Targeting/toolbar/toolbar.html.twig', $data); - - $this->eventDispatcher->dispatch($event, TargetingEvents::RENDER_TOOLBAR); - - $code = $this->templatingEngine->render( - $event->getTemplate(), - $event->getData() - ); - - $this->codeInjector->inject( - $response, - $code, - CodeInjector::SELECTOR_BODY, - CodeInjector::POSITION_END - ); - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/EventListener/VisitedPagesCountListener.php b/bundles/PersonalizationBundle/src/Targeting/EventListener/VisitedPagesCountListener.php deleted file mode 100644 index 91d711b2520..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/EventListener/VisitedPagesCountListener.php +++ /dev/null @@ -1,68 +0,0 @@ -visitedPagesCounter = $visitedPagesCounter; - } - - /** - * {@inheritdoc} - * - * @return array - */ - public static function getSubscribedEvents(): array - { - return [ - TargetingEvents::VISITED_PAGES_COUNT_MATCH => 'onVisitedPagesCountMatch', // triggered from conditions depending on page count - TargetingEvents::POST_RESOLVE => 'onPostResolveVisitorInfo', - ]; - } - - public function onVisitedPagesCountMatch(): void - { - // increment page count after matching proceeded - $this->recordPageCount = true; - } - - public function onPostResolveVisitorInfo(TargetingEvent $event): void - { - // TODO currently the pages count is only recorded if there's a condition depending on - // the count. This is good for minimizing storage data and writes, but implies that the - // page count is not recorded if there's no rule with a condition depending on the page - // count. Alternatively this could be done blindly after resolving the visitor info, but - // that would trigger a write/increment on every request without actually needing the data. - if (!$this->recordPageCount) { - return; - } - - $this->visitedPagesCounter->increment($event->getVisitorInfo()); - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Maintenance/TargetingStorageTask.php b/bundles/PersonalizationBundle/src/Targeting/Maintenance/TargetingStorageTask.php deleted file mode 100644 index db01168f728..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Maintenance/TargetingStorageTask.php +++ /dev/null @@ -1,41 +0,0 @@ -targetingStorage = $targetingStorage; - } - - public function execute(): void - { - if (!$this->targetingStorage instanceof MaintenanceStorageInterface) { - return; - } - - $this->targetingStorage->maintenance(); - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Model/GeoLocation.php b/bundles/PersonalizationBundle/src/Targeting/Model/GeoLocation.php deleted file mode 100644 index 19e16773296..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Model/GeoLocation.php +++ /dev/null @@ -1,66 +0,0 @@ -= -90 && $latitude <= 90)) { - throw new \InvalidArgumentException('Latitude is invalid'); - } - - if (!($longitude >= -180 && $longitude <= 180)) { - throw new \InvalidArgumentException('Longitude is invalid'); - } - - $this->latitude = $latitude; - $this->longitude = $longitude; - $this->altitude = $altitude; - } - - public static function build(float $latitude, float $longitude, float $altitude = null): self - { - return new self( - (float)$latitude, - (float)$longitude, - null !== $altitude ? (float)$altitude : null - ); - } - - public function getLatitude(): float - { - return $this->latitude; - } - - public function getLongitude(): float - { - return $this->longitude; - } - - public function getAltitude(): ?float - { - return $this->altitude; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Model/TargetGroupAssignment.php b/bundles/PersonalizationBundle/src/Targeting/Model/TargetGroupAssignment.php deleted file mode 100644 index 3ccf3bc0c79..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Model/TargetGroupAssignment.php +++ /dev/null @@ -1,58 +0,0 @@ -targetGroup = $targetGroup; - - $this->setCount($count); - } - - public function getTargetGroup(): TargetGroup - { - return $this->targetGroup; - } - - public function getCount(): int - { - return $this->count; - } - - public function setCount(int $count): void - { - if ($count < 0) { - throw new \OutOfBoundsException('Count must be a positive integer'); - } - - $this->count = $count; - } - - public function inc(int $amount = 1): void - { - $this->setCount($this->count += $amount); - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Model/VisitorInfo.php b/bundles/PersonalizationBundle/src/Targeting/Model/VisitorInfo.php deleted file mode 100644 index 38cfc651aae..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Model/VisitorInfo.php +++ /dev/null @@ -1,322 +0,0 @@ -request = $request; - $this->visitorId = $visitorId; - $this->sessionId = $sessionId; - } - - public static function fromRequest(Request $request): static - { - $visitorId = $request->cookies->get(self::VISITOR_ID_COOKIE_NAME); - if (!empty($visitorId)) { - $visitorId = (string)$visitorId; - } else { - $visitorId = null; - } - - $sessionId = $request->cookies->get(self::SESSION_ID_COOKIE_NAME); - if (!empty($sessionId)) { - $sessionId = (string)$sessionId; - } else { - $sessionId = null; - } - - return new static($request, $visitorId, $sessionId); - } - - public function getRequest(): Request - { - return $this->request; - } - - public function hasVisitorId(): bool - { - return !empty($this->visitorId); - } - - public function getVisitorId(): ?string - { - return $this->visitorId; - } - - public function hasSessionId(): bool - { - return !empty($this->sessionId); - } - - public function getSessionId(): ?string - { - return $this->sessionId; - } - - /** - * @return Rule[] - */ - public function getMatchingTargetingRules(): array - { - return $this->matchingTargetingRules; - } - - /** - * @param Rule[] $targetingRules - */ - public function setMatchingTargetingRules(array $targetingRules = []): void - { - $this->matchingTargetingRules = []; - foreach ($targetingRules as $targetingRule) { - $this->addMatchingTargetingRule($targetingRule); - } - } - - public function addMatchingTargetingRule(Rule $targetingRule): void - { - if (!in_array($targetingRule, $this->matchingTargetingRules, true)) { - $this->matchingTargetingRules[] = $targetingRule; - } - } - - /** - * Returns target group assignments ordered by assignment count - * - * @return TargetGroupAssignment[] - */ - public function getTargetGroupAssignments(): array - { - if (null !== $this->sortedTargetGroupAssignments) { - return $this->sortedTargetGroupAssignments; - } - - $assignments = array_values($this->targetGroupAssignments); - - // sort reverse (highest count first) - usort($assignments, function (TargetGroupAssignment $a, TargetGroupAssignment $b) { - $aCount = $a->getCount(); - $bCount = $b->getCount(); - - if ($aCount === $bCount) { - return 0; - } - - return $aCount < $bCount ? 1 : -1; - }); - - $this->sortedTargetGroupAssignments = $assignments; - - return $this->sortedTargetGroupAssignments; - } - - public function hasTargetGroupAssignment(TargetGroup $targetGroup): bool - { - return isset($this->targetGroupAssignments[$targetGroup->getId()]); - } - - public function getTargetGroupAssignment(TargetGroup $targetGroup): TargetGroupAssignment - { - return $this->targetGroupAssignments[$targetGroup->getId()]; - } - - public function assignTargetGroup(TargetGroup $targetGroup, int $count = 1, bool $overwrite = false): void - { - if ($count < 1) { - throw new \InvalidArgumentException('Count must be greater than 0'); - } - - if (isset($this->targetGroupAssignments[$targetGroup->getId()])) { - if ($overwrite) { - $this->targetGroupAssignments[$targetGroup->getId()]->setCount($count); - } else { - $this->targetGroupAssignments[$targetGroup->getId()]->inc($count); - } - } else { - $this->targetGroupAssignments[$targetGroup->getId()] = new TargetGroupAssignment($targetGroup, $count); - } - - $this->targetGroups = null; - $this->sortedTargetGroupAssignments = null; - } - - public function clearAssignedTargetGroup(TargetGroup $targetGroup): void - { - if (isset($this->targetGroupAssignments[$targetGroup->getId()])) { - unset($this->targetGroupAssignments[$targetGroup->getId()]); - - $this->targetGroups = null; - $this->sortedTargetGroupAssignments = null; - } - } - - /** - * Returns assigned target groups ordered by assignment count - * - * @return TargetGroup[] - */ - public function getAssignedTargetGroups(): array - { - if (null === $this->targetGroups) { - $this->targetGroups = array_map(function (TargetGroupAssignment $assignment) { - return $assignment->getTargetGroup(); - }, $this->getTargetGroupAssignments()); - } - - return $this->targetGroups; - } - - public function getFrontendDataProviders(): array - { - return $this->frontendDataProviders; - } - - public function setFrontendDataProviders(array $providers): void - { - $this->frontendDataProviders = []; - foreach ($providers as $provider) { - $this->addFrontendDataProvider($provider); - } - } - - public function addFrontendDataProvider(string $key): void - { - if (!in_array($key, $this->frontendDataProviders, true)) { - $this->frontendDataProviders[] = $key; - } - } - - public function hasResponse(): bool - { - return null !== $this->response; - } - - public function getResponse(): ?Response - { - return $this->response; - } - - public function setResponse(Response $response): void - { - $this->response = $response; - } - - public function getData(): array - { - return $this->data; - } - - public function setData(array $data): void - { - $this->data = $data; - } - - public function getIterator(): \ArrayIterator - { - return new \ArrayIterator($this->data); - } - - public function has(int|string $key): bool - { - return isset($this->data[$key]); - } - - public function get(int|string $key, mixed $default = null): mixed - { - return $this->data[$key] ?? $default; - } - - public function set(int|string $key, mixed $value): void - { - $this->data[$key] = $value; - } - - public function addAction(array $action): void - { - $this->actions[] = $action; - } - - public function getActions(): array - { - return $this->actions; - } - - public function hasActions(): bool - { - return count($this->actions) > 0; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/OverrideHandlerInterface.php b/bundles/PersonalizationBundle/src/Targeting/OverrideHandlerInterface.php deleted file mode 100644 index abf6071752f..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/OverrideHandlerInterface.php +++ /dev/null @@ -1,40 +0,0 @@ -enabled = $enabled; - $this->requestHelper = $requestHelper; - } - - public function isTargetingEnabled(): bool - { - $request = $this->requestHelper->getCurrentRequest(); - - if ($this->enabled || $request->cookies->getBoolean('pimcore_targeting_enabled')) { - return true; - } - - return false; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Service/VisitedPagesCounter.php b/bundles/PersonalizationBundle/src/Targeting/Service/VisitedPagesCounter.php deleted file mode 100644 index 28284ce7e2f..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Service/VisitedPagesCounter.php +++ /dev/null @@ -1,59 +0,0 @@ -targetingStorage = $targetingStorage; - } - - public function getCount(VisitorInfo $visitorInfo, string $scope = TargetingStorageInterface::SCOPE_VISITOR): int - { - return $this->targetingStorage->get($visitorInfo, $scope, self::STORAGE_KEY, 0); - } - - public function increment(VisitorInfo $visitorInfo, string $scope = TargetingStorageInterface::SCOPE_VISITOR, bool $force = false): void - { - if ($this->incremented && !$force) { - return; - } - - // TODO to make sure this works in concurrent request we probably need - // to support some kind of transactional updates on the storage - $count = $this->getCount($visitorInfo, $scope); - $count++; - - $this->targetingStorage->set($visitorInfo, $scope, self::STORAGE_KEY, $count); - - $this->incremented = true; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Storage/Cookie/AbstractCookieSaveHandler.php b/bundles/PersonalizationBundle/src/Targeting/Storage/Cookie/AbstractCookieSaveHandler.php deleted file mode 100644 index b0a58420641..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Storage/Cookie/AbstractCookieSaveHandler.php +++ /dev/null @@ -1,97 +0,0 @@ -configureOptions($resolver); - - $this->options = $resolver->resolve($options); - } - - protected function configureOptions(OptionsResolver $resolver): void - { - $resolver->setDefaults([ - 'domain' => null, - 'secure' => false, - 'httpOnly' => true, - ]); - - $resolver->setAllowedTypes('domain', ['null', 'string']); - $resolver->setAllowedTypes('secure', ['bool']); - $resolver->setAllowedTypes('httpOnly', ['bool']); - } - - /** - * {@inheritdoc} - */ - public function load(Request $request, string $scope, string $name): array - { - $data = $request->cookies->get($name, null); - $result = $this->parseData($scope, $name, $data); - - return $result; - } - - /** - * {@inheritdoc} - */ - public function save(Response $response, string $scope, string $name, \DateTimeInterface|int|string $expire, ?array $data): void - { - $value = $this->prepareData($scope, $name, $expire, $data); - - $response->headers->setCookie(new Cookie( - $name, - $value, - $expire - )); - } - - /** - * Parse loaded data - * - * @param string $scope - * @param string $name - * @param string|null $data - * - * @return array - */ - abstract protected function parseData(string $scope, string $name, ?string $data): array; - - /** - * Prepare data for saving - * - * @param string $scope - * @param string $name - * @param \DateTimeInterface|int|string $expire - * @param array|null $data - * - * @return bool|string|null - */ - abstract protected function prepareData(string $scope, string $name, \DateTimeInterface|int|string $expire, ?array $data): bool|string|null; -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Storage/Cookie/CookieSaveHandlerInterface.php b/bundles/PersonalizationBundle/src/Targeting/Storage/Cookie/CookieSaveHandlerInterface.php deleted file mode 100644 index 6bea8dc7b8c..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Storage/Cookie/CookieSaveHandlerInterface.php +++ /dev/null @@ -1,34 +0,0 @@ -setValidationConstraints(new SignedWith($config->signer(), $config->verificationKey())); - $this->config = $config; - $this->logger = $logger ?? new NullLogger(); - } - - /** - * {@inheritdoc} - */ - protected function parseData(string $scope, string $name, ?string $data): array - { - if (null === $data) { - return []; - } - - try { - /** @var Plain $token */ - $token = $this->config->parser()->parse($data); - $validator = $this->config->validator(); - - // validate token (expiry, ...) - if (!$validator->validate($token, ...$this->config->validationConstraints())) { - return []; - } - } catch (\Throwable $e) { - $this->logger->error((string) $e); - - return []; - } - - $data = $token->claims()->get(self::CLAIM_TARGETING_DATA, []); - - if (!is_array($data)) { - $data = []; - } - - return $data; - } - - /** - * {@inheritdoc} - */ - protected function prepareData(string $scope, string $name, \DateTimeInterface|int|string $expire, ?array $data): bool|string|null - { - if (empty($data)) { - return null; - } - - $builder = $this->createTokenBuilder($scope, $name, $expire, $data); - $token = $builder->getToken($this->config->signer(), $this->config->signingKey()); - $result = $token->toString(); - - return $result; - } - - /** - * @param string $scope - * @param string $name - * @param \DateTimeInterface|int|string $expire - * @param array|null $data - * - * @return Builder - * - * @throws \Exception - */ - protected function createTokenBuilder(string $scope, string $name, \DateTimeInterface|int|string $expire, ?array $data): Builder - { - $time = new \DateTimeImmutable(); - - $builder = $this->config->builder(); - $builder - ->issuedAt($time) - ->withClaim(self::CLAIM_TARGETING_DATA, $data); - - if (0 === $expire) { - $builder->expiresAt($time->modify('+30 minutes')); // expire in 30 min - } elseif (is_int($expire) && $expire > 0) { - $expire = new \DateTimeImmutable('@'. $expire); - $builder->expiresAt($expire); - } elseif ($expire instanceof \DateTimeInterface) { - $expire = new \DateTimeImmutable('@'. $expire->getTimestamp()); - $builder->expiresAt($expire); - } - - $builder->getToken($this->config->signer(), $this->config->signingKey()); - - return $builder; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Storage/Cookie/JsonCookieSaveHandler.php b/bundles/PersonalizationBundle/src/Targeting/Storage/Cookie/JsonCookieSaveHandler.php deleted file mode 100644 index e4898bf6c08..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Storage/Cookie/JsonCookieSaveHandler.php +++ /dev/null @@ -1,55 +0,0 @@ - self::COOKIE_NAME_SESSION, - self::SCOPE_VISITOR => self::COOKIE_NAME_VISITOR, - ]; - - public function __construct( - CookieSaveHandlerInterface $saveHandler, - EventDispatcherInterface $eventDispatcher - ) { - $this->saveHandler = $saveHandler; - $this->eventDispatcher = $eventDispatcher; - } - - public function all(VisitorInfo $visitorInfo, string $scope): array - { - $this->loadData($visitorInfo, $scope); - - $blacklist = [ - self::STORAGE_KEY_CREATED_AT, - self::STORAGE_KEY_UPDATED_AT, - self::STORAGE_KEY_META_ENTRY, - ]; - - // filter internal values - $result = array_filter($this->data[$scope], function ($key) use ($blacklist) { - return !in_array($key, $blacklist, true); - }, ARRAY_FILTER_USE_KEY); - - return $result; - } - - public function has(VisitorInfo $visitorInfo, string $scope, string $name): bool - { - $this->loadData($visitorInfo, $scope); - - return isset($this->data[$scope][$name]); - } - - /** - * {@inheritdoc} - */ - public function get(VisitorInfo $visitorInfo, string $scope, string $name, mixed $default = null): mixed - { - $this->loadData($visitorInfo, $scope); - - if (isset($this->data[$scope][$name])) { - return $this->data[$scope][$name]; - } - - return $default; - } - - public function set(VisitorInfo $visitorInfo, string $scope, string $name, mixed $value): void - { - $this->loadData($visitorInfo, $scope); - - $this->data[$scope][$name] = $value; - - $this->updateTimestamps($scope); - $this->addSaveListener($visitorInfo); - } - - /** - * {@inheritdoc } - */ - public function clear(VisitorInfo $visitorInfo, string $scope = null): void - { - if (null === $scope) { - $this->data = []; - } else { - if (isset($this->data[$scope])) { - unset($this->data[$scope]); - } - } - - $this->addSaveListener($visitorInfo); - } - - public function migrateFromStorage(TargetingStorageInterface $storage, VisitorInfo $visitorInfo, string $scope): void - { - $values = $storage->all($visitorInfo, $scope); - - $this->loadData($visitorInfo, $scope); - - foreach ($values as $name => $value) { - $this->data[$scope][$name] = $value; - } - - // update created/updated at from storage - $this->updateTimestamps( - $scope, - $storage->getCreatedAt($visitorInfo, $scope), - $storage->getUpdatedAt($visitorInfo, $scope) - ); - - $this->addSaveListener($visitorInfo); - } - - public function getCreatedAt(VisitorInfo $visitorInfo, string $scope): ?\DateTimeImmutable - { - $this->loadData($visitorInfo, $scope); - - if (!isset($this->data[$scope][self::STORAGE_KEY_CREATED_AT])) { - return null; - } - - return \DateTimeImmutable::createFromFormat('U', (string)$this->data[$scope][self::STORAGE_KEY_CREATED_AT]); - } - - public function getUpdatedAt(VisitorInfo $visitorInfo, string $scope): ?\DateTimeImmutable - { - $this->loadData($visitorInfo, $scope); - - if (!isset($this->data[$scope][self::STORAGE_KEY_UPDATED_AT])) { - return null; - } - - return \DateTimeImmutable::createFromFormat('U', (string)$this->data[$scope][self::STORAGE_KEY_CREATED_AT]); - } - - private function loadData(VisitorInfo $visitorInfo, string $scope): array - { - if (!isset($this->scopeCookieMapping[$scope])) { - throw new \InvalidArgumentException(sprintf('Scope "%s" is not supported', $scope)); - } - - if (isset($this->data[$scope]) && null !== $this->data[$scope]) { - return $this->data[$scope]; - } - - $request = $visitorInfo->getRequest(); - - $this->data[$scope] = $this->saveHandler->load($request, $scope, $this->scopeCookieMapping[$scope]); - - return $this->data[$scope]; - } - - private function addSaveListener(VisitorInfo $visitorInfo): void - { - if ($this->changed) { - return; - } - - $this->changed = true; - - // adds a response listener setting the storage cookie - $listener = function (ResponseEvent $event) use ($visitorInfo) { - // only handle event for the visitor info which triggered the save - if ($event->getRequest() !== $visitorInfo->getRequest()) { - return; - } - - $response = $event->getResponse(); - foreach (array_keys($this->scopeCookieMapping) as $scope) { - $this->saveHandler->save( - $response, - $scope, - $this->scopeCookieMapping[$scope], - $this->expiryFor($scope), - $this->data[$scope] ?? null - ); - } - }; - - $this->eventDispatcher->addListener(KernelEvents::RESPONSE, $listener); - } - - private function updateTimestamps( - string $scope, - \DateTimeInterface $createdAt = null, - \DateTimeInterface $updatedAt = null - ): void { - $timestamps = $this->normalizeTimestamps($createdAt, $updatedAt); - - if (!isset($this->data[$scope][self::STORAGE_KEY_CREATED_AT])) { - $this->data[$scope][self::STORAGE_KEY_CREATED_AT] = $timestamps['createdAt']->getTimestamp(); - $this->data[$scope][self::STORAGE_KEY_UPDATED_AT] = $timestamps['updatedAt']->getTimestamp(); - } else { - $this->data[$scope][self::STORAGE_KEY_UPDATED_AT] = $timestamps['updatedAt']->getTimestamp(); - } - } - - protected function expiryFor(string $scope): \DateTime|int - { - $expiry = 0; - if (self::SCOPE_VISITOR === $scope) { - $expiry = new \DateTime('+1 year'); - } elseif (self::SCOPE_SESSION === $scope) { - $expiry = new \DateTime('+30 minutes'); - } - - return $expiry; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Storage/DbStorage.php b/bundles/PersonalizationBundle/src/Targeting/Storage/DbStorage.php deleted file mode 100644 index bc75e9e0816..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Storage/DbStorage.php +++ /dev/null @@ -1,397 +0,0 @@ -db = $db; - - $resolver = new OptionsResolver(); - $this->configureOptions($resolver); - - $this->handleOptions($resolver->resolve($options)); - } - - protected function handleOptions(array $options): void - { - $this->tableName = $options['tableName']; - } - - protected function configureOptions(OptionsResolver $resolver): void - { - $resolver->setDefaults([ - 'tableName' => 'targeting_storage', - ]); - - $resolver->setRequired(['tableName']); - $resolver->setAllowedTypes('tableName', 'string'); - } - - public function all(VisitorInfo $visitorInfo, string $scope): array - { - if (!$visitorInfo->hasVisitorId()) { - return []; - } - - $qb = $this->db->createQueryBuilder(); - $qb - ->select('name', 'value') - ->from($this->tableName) - ->where('visitorId = :visitorId') - ->andWhere('scope = :scope') - ->andWhere('name != :metaKey'); - - $qb->setParameters([ - 'visitorId' => $visitorInfo->getVisitorId(), - 'scope' => $scope, - 'metaKey' => self::STORAGE_KEY_META_ENTRY, - ]); - - $this->addExpiryParam($qb, $scope); - - $stmt = $qb->executeQuery(); - $data = []; - - if ($stmt instanceof Result) { - while ($row = $stmt->fetchAssociative()) { - $data[$row['name']] = json_decode($row['value'], true); - } - } - - return $data; - } - - public function has(VisitorInfo $visitorInfo, string $scope, string $name): bool - { - if (!$visitorInfo->hasVisitorId()) { - return false; - } - - $qb = $this->db->createQueryBuilder(); - $qb - ->select('COUNT(name) as count') - ->from($this->tableName) - ->where('visitorId = :visitorId') - ->andWhere('scope = :scope') - ->andWhere('name = :name'); - - $qb->setParameters([ - 'visitorId' => $visitorInfo->getVisitorId(), - 'scope' => $scope, - 'name' => $name, - ]); - - $this->addExpiryParam($qb, $scope); - - $stmt = $qb->executeQuery(); - $result = 0; - - if ($stmt instanceof Result) { - $result = (int)$stmt->fetchOne(); - } - - return 1 === $result; - } - - public function set(VisitorInfo $visitorInfo, string $scope, string $name, mixed $value): void - { - if (!$visitorInfo->hasVisitorId()) { - return; - } - - $json = json_encode($value); - - $query = <<tableName} - (visitorId, scope, name, value, creationDate, modificationDate) -VALUES - (:visitorId, :scope, :name, :value, NOW(), NOW()) -ON DUPLICATE KEY UPDATE - value = :value, modificationDate = NOW(); -EOF; - - $this->db->executeQuery( - $query, - [ - 'visitorId' => $visitorInfo->getVisitorId(), - 'scope' => $scope, - 'name' => $name, - 'value' => $json, - ] - ); - - $this->cleanup($scope); - } - - /** - * {@inheritdoc} - */ - public function get(VisitorInfo $visitorInfo, string $scope, string $name, mixed $default = null): mixed - { - if (!$visitorInfo->hasVisitorId()) { - return $default; - } - - $qb = $this->db->createQueryBuilder(); - $qb - ->select('value') - ->from($this->tableName) - ->where('visitorId = :visitorId') - ->andWhere('scope = :scope') - ->andWhere('name = :name'); - - $qb->setParameters([ - 'visitorId' => $visitorInfo->getVisitorId(), - 'scope' => $scope, - 'name' => $name, - ]); - - $this->addExpiryParam($qb, $scope); - - $stmt = $qb->executeQuery(); - $result = false; - - if ($stmt instanceof Result) { - $result = $stmt->fetchOne(); - } - - if (!$result) { - return $default; - } - - $decoded = json_decode($result, true); - if (!$decoded) { - return $default; - } - - return $decoded; - } - - /** - * {@inheritdoc } - */ - public function clear(VisitorInfo $visitorInfo, string $scope = null): void - { - if (!$visitorInfo->hasVisitorId()) { - return; - } - - if (null === $scope) { - $this->db->executeQuery( - 'DELETE FROM ' . $this->tableName . ' WHERE visitorId = :visitorId', - [ - 'visitorId' => $visitorInfo->getVisitorId(), - ] - ); - } else { - $this->db->executeQuery( - 'DELETE FROM ' . $this->tableName . ' WHERE visitorId = :visitorId AND scope = :scope', - [ - 'visitorId' => $visitorInfo->getVisitorId(), - 'scope' => $scope, - ] - ); - } - } - - public function migrateFromStorage(TargetingStorageInterface $storage, VisitorInfo $visitorInfo, string $scope): void - { - // only allow migration if a visitor ID is available as otherwise the fallback - // would clear the original storage although data was not stored - if (!$visitorInfo->hasVisitorId()) { - throw new \LogicException('Can\'t migrate to DB storage as no visitor ID is set'); - } - - $values = $storage->all($visitorInfo, $scope); - - $this->db->beginTransaction(); - - try { - foreach ($values as $name => $value) { - $this->set($visitorInfo, $scope, $name, $value); - } - - $this->updateTimestamps( - $visitorInfo, - $scope, - $storage->getCreatedAt($visitorInfo, $scope), - $storage->getUpdatedAt($visitorInfo, $scope) - ); - - $this->cleanup($scope); - - $this->db->commit(); - } catch (\Exception $e) { - $this->db->rollBack(); - - throw $e; - } - } - - public function getCreatedAt(VisitorInfo $visitorInfo, string $scope): ?\DateTimeImmutable - { - return $this->loadDate($visitorInfo, $scope, 'MIN(creationDate)'); - } - - public function getUpdatedAt(VisitorInfo $visitorInfo, string $scope): ?\DateTimeImmutable - { - return $this->loadDate($visitorInfo, $scope, 'MAX(modificationDate)'); - } - - /** - * {@inheritdoc } - */ - public function maintenance(): void - { - // clean up expired keys scopes with an expiration - foreach (self::VALID_SCOPES as $scope) { - $expiry = $this->expiryFor($scope); - - if ($expiry > 0) { - $this->cleanup($scope); - } - } - } - - private function loadDate(VisitorInfo $visitorInfo, string $scope, string $select): ?\DateTimeImmutable - { - if (!$visitorInfo->hasVisitorId()) { - return null; - } - - $qb = $this->db->createQueryBuilder(); - $qb - ->select($select) - ->from($this->tableName) - ->where('visitorId = :visitorId') - ->andWhere('scope = :scope'); - - $qb->setParameters([ - 'visitorId' => $visitorInfo->getVisitorId(), - 'scope' => $scope, - ]); - - $this->addExpiryParam($qb, $scope); - - $stmt = $qb->executeQuery(); - - if ($stmt instanceof Result) { - return $this->convertToDateTime($stmt->fetchOne()); - } - - return null; - } - - private function convertToDateTime(mixed $result = null): ?\DateTimeImmutable - { - if (!$result) { - return null; - } - - $dateTime = $this->db->convertToPHPValue($result, Types::DATETIME_MUTABLE); - - return \DateTimeImmutable::createFromMutable($dateTime); - } - - private function updateTimestamps( - VisitorInfo $visitorInfo, - string $scope, - \DateTimeInterface $createdAt = null, - \DateTimeInterface $updatedAt = null - ): void { - $timestamps = $this->normalizeTimestamps($createdAt, $updatedAt); - - $query = <<tableName} - (visitorId, scope, name, value, creationDate, modificationDate) -VALUES - (:visitorId, :scope, :name, :value, :creationDate, :modificationDate) -ON DUPLICATE KEY UPDATE - value = :value, creationDate = :creationDate, modificationDate = :modificationDate; -EOF; - - $this->db->executeQuery( - $query, - [ - 'visitorId' => $visitorInfo->getVisitorId(), - 'scope' => $scope, - 'name' => self::STORAGE_KEY_META_ENTRY, - 'value' => 1, - 'creationDate' => $timestamps['createdAt'], - 'modificationDate' => $timestamps['updatedAt'], - ], - [ - 'creationDate' => Types::DATETIME_MUTABLE, - 'modificationDate' => Types::DATETIME_MUTABLE, - ] - ); - } - - protected function expiryFor(string $scope): int - { - $expiry = 0; - if (self::SCOPE_SESSION === $scope) { - $expiry = 30 * 60; // 30 minutes - } - - return $expiry; - } - - private function addExpiryParam(QueryBuilder $qb, string $scope): void - { - $expiry = $this->expiryFor($scope); - if (0 === $expiry) { - return; - } - - $qb->andWhere('modificationDate >= (NOW() - INTERVAL :expiry SECOND)'); - $qb->setParameter('expiry', $expiry); - } - - private function cleanup(string $scope): void - { - $expiry = $this->expiryFor($scope); - if (0 === $expiry) { - return; - } - - $this->db->executeQuery( - 'DELETE FROM ' . $this->tableName . ' WHERE scope = :scope AND modificationDate < (NOW() - INTERVAL :expiry SECOND)', - [ - 'scope' => $scope, - 'expiry' => $expiry, - ] - ); - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Storage/FallbackStorage.php b/bundles/PersonalizationBundle/src/Targeting/Storage/FallbackStorage.php deleted file mode 100644 index 4e0fa4c614c..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Storage/FallbackStorage.php +++ /dev/null @@ -1,168 +0,0 @@ -primaryStorage = $primaryStorage; - $this->fallbackStorage = $fallbackStorage; - $this->logger = $logger; - - $resolver = new OptionsResolver(); - $this->configureOptions($resolver); - - $this->options = $resolver->resolve($options); - } - - protected function configureOptions(OptionsResolver $resolver): void - { - $resolver->setDefaults([ - 'clear_after_migration' => false, - ]); - - $resolver->setAllowedTypes('clear_after_migration', 'bool'); - } - - public function all(VisitorInfo $visitorInfo, string $scope): array - { - if ($visitorInfo->hasVisitorId()) { - $this->migrateFromFallback($visitorInfo, $scope); - - return $this->primaryStorage->all($visitorInfo, $scope); - } else { - return $this->fallbackStorage->all($visitorInfo, $scope); - } - } - - public function has(VisitorInfo $visitorInfo, string $scope, string $name): bool - { - if ($visitorInfo->hasVisitorId()) { - if (!$this->primaryStorage->has($visitorInfo, $scope, $name)) { - $this->migrateFromFallback($visitorInfo, $scope); - } - - return $this->primaryStorage->has($visitorInfo, $scope, $name); - } else { - return $this->fallbackStorage->has($visitorInfo, $scope, $name); - } - } - - public function set(VisitorInfo $visitorInfo, string $scope, string $name, mixed $value): void - { - if ($visitorInfo->hasVisitorId()) { - $this->primaryStorage->set($visitorInfo, $scope, $name, $value); - } else { - $this->fallbackStorage->set($visitorInfo, $scope, $name, $value); - } - } - - /** - * {@inheritdoc } - */ - public function get(VisitorInfo $visitorInfo, string $scope, string $name, mixed $default = null): mixed - { - if ($visitorInfo->hasVisitorId()) { - if (!$this->primaryStorage->has($visitorInfo, $scope, $name)) { - $this->migrateFromFallback($visitorInfo, $scope); - } - - return $this->primaryStorage->get($visitorInfo, $scope, $name, $default); - } else { - return $this->fallbackStorage->get($visitorInfo, $scope, $name, $default); - } - } - - /** - * {@inheritdoc } - */ - public function clear(VisitorInfo $visitorInfo, string $scope = null): void - { - $this->fallbackStorage->clear($visitorInfo, $scope); - - if ($visitorInfo->hasVisitorId()) { - $this->primaryStorage->clear($visitorInfo, $scope); - } - } - - public function migrateFromStorage(TargetingStorageInterface $storage, VisitorInfo $visitorInfo, string $scope): void - { - throw new \LogicException('migrateFromStorage() is not supported in FallbackStorage'); - } - - public function getCreatedAt(VisitorInfo $visitorInfo, string $scope): ?\DateTimeImmutable - { - if ($visitorInfo->hasVisitorId()) { - return $this->primaryStorage->getCreatedAt($visitorInfo, $scope); - } else { - return $this->fallbackStorage->getCreatedAt($visitorInfo, $scope); - } - } - - public function getUpdatedAt(VisitorInfo $visitorInfo, string $scope): ?\DateTimeImmutable - { - if ($visitorInfo->hasVisitorId()) { - return $this->primaryStorage->getUpdatedAt($visitorInfo, $scope); - } else { - return $this->fallbackStorage->getUpdatedAt($visitorInfo, $scope); - } - } - - private function migrateFromFallback(VisitorInfo $visitorInfo, string $scope): void - { - try { - $this->primaryStorage->migrateFromStorage($this->fallbackStorage, $visitorInfo, $scope); - - if ($this->options['clear_after_migration']) { - // clear fallback after successful migration - $this->fallbackStorage->clear($visitorInfo, $scope); - } - } catch (\Throwable $e) { - $this->logger->error((string) $e); - } - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Storage/MaintenanceStorageInterface.php b/bundles/PersonalizationBundle/src/Targeting/Storage/MaintenanceStorageInterface.php deleted file mode 100644 index 2fed274b296..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Storage/MaintenanceStorageInterface.php +++ /dev/null @@ -1,27 +0,0 @@ -redis = $redis; - } - - public function all(VisitorInfo $visitorInfo, string $scope): array - { - if (!$visitorInfo->hasVisitorId()) { - return []; - } - - $key = $this->buildKey($visitorInfo, $scope); - $result = $this->redis->hGetAll($key); - - $blacklist = [ - self::STORAGE_KEY_CREATED_AT, - self::STORAGE_KEY_UPDATED_AT, - self::STORAGE_KEY_META_ENTRY, - ]; - - $data = []; - foreach ($result as $key => $value) { - // filter internal values - if (in_array($key, $blacklist, true)) { - continue; - } - - $data[$key] = json_decode($value, true); - } - - return $data; - } - - public function has(VisitorInfo $visitorInfo, string $scope, string $name): bool - { - if (!$visitorInfo->hasVisitorId()) { - return false; - } - - $key = $this->buildKey($visitorInfo, $scope); - $result = $this->redis->hExists($key, $name); - - return (bool)$result; - } - - public function set(VisitorInfo $visitorInfo, string $scope, string $name, mixed $value): void - { - if (!$visitorInfo->hasVisitorId()) { - return; - } - - $json = json_encode($value); - - $key = $this->buildKey($visitorInfo, $scope); - - $currentCreatedAt = $this->getCurrentCreatedAt($key); - - $multi = $this->redis->multi(); - $multi->hSet($key, $name, $json); - - $this->updateTimestamps($multi, $key, $currentCreatedAt); - $this->updateExpiry($multi, $scope, $key); - - $multi->exec(); - } - - /** - * {@inheritdoc } - */ - public function get(VisitorInfo $visitorInfo, string $scope, string $name, mixed $default = null): mixed - { - if (!$visitorInfo->hasVisitorId()) { - return $default; - } - - $key = $this->buildKey($visitorInfo, $scope); - $result = $this->redis->hGet($key, $name); - - if (!$result) { - return $default; - } - - $decoded = json_decode($result, true); - if (!$decoded) { - return $default; - } - - return $decoded; - } - - /** - * {@inheritdoc } - */ - public function clear(VisitorInfo $visitorInfo, string $scope = null): void - { - $scopes = []; - if (null !== $scope) { - $scopes = [$scope]; - } else { - $scopes = self::VALID_SCOPES; - } - - foreach ($scopes as $sc) { - $key = $this->buildKey($visitorInfo, $sc); - $this->redis->del($key); - } - } - - public function migrateFromStorage(TargetingStorageInterface $storage, VisitorInfo $visitorInfo, string $scope): void - { - // only allow migration if a visitor ID is available as otherwise the fallback - // would clear the original storage although data was not stored - if (!$visitorInfo->hasVisitorId()) { - throw new \LogicException('Can\'t migrate to Redis storage as no visitor ID is set'); - } - - $values = $storage->all($visitorInfo, $scope); - $createdAt = $storage->getCreatedAt($visitorInfo, $scope); - $updatedAt = $storage->getUpdatedAt($visitorInfo, $scope); - - // nothing to migrate - if (empty($values) && null === $createdAt && null === $updatedAt) { - return; - } - - $key = $this->buildKey($visitorInfo, $scope); - - $currentCreatedAt = $this->getCurrentCreatedAt($key); - - $multi = $this->redis->multi(); - - if (!empty($values)) { - $data = []; - foreach ($values as $name => $value) { - $data[$name] = json_encode($value); - } - - $multi->hMSet($key, $data); - } - - // update created/updated at from storage - $this->updateTimestamps( - $multi, - $key, - $currentCreatedAt, - $storage->getCreatedAt($visitorInfo, $scope), - $storage->getUpdatedAt($visitorInfo, $scope) - ); - - $this->updateExpiry($multi, $scope, $key); - - $multi->exec(); - } - - public function getCreatedAt(VisitorInfo $visitorInfo, string $scope): ?\DateTimeImmutable - { - return $this->loadDate($visitorInfo, $scope, self::STORAGE_KEY_CREATED_AT); - } - - public function getUpdatedAt(VisitorInfo $visitorInfo, string $scope): ?\DateTimeImmutable - { - return $this->loadDate($visitorInfo, $scope, self::STORAGE_KEY_UPDATED_AT); - } - - private function loadDate(VisitorInfo $visitorInfo, string $scope, string $storageKey): ?\DateTimeImmutable - { - if (!$visitorInfo->hasVisitorId()) { - return null; - } - - $key = $this->buildKey($visitorInfo, $scope); - $timestamp = $this->redis->hGet($key, $storageKey); - - if (empty($timestamp)) { - return null; - } - - return \DateTimeImmutable::createFromFormat('U', $timestamp) ?: null; - } - - private function buildKey(VisitorInfo $visitorInfo, string $scope): string - { - return sprintf('%s:%s', $visitorInfo->getVisitorId(), $scope); - } - - private function getCurrentCreatedAt(string $key): int - { - return (int)$this->redis->hGet($key, self::STORAGE_KEY_CREATED_AT); - } - - private function updateTimestamps( - \Credis_Client $multi, - string $key, - int $currentCreatedAt, - \DateTimeInterface $createdAt = null, - \DateTimeInterface $updatedAt = null - ): void { - $timestamps = $this->normalizeTimestamps($createdAt, $updatedAt); - - if (0 === $currentCreatedAt) { - $multi->hSet($key, self::STORAGE_KEY_CREATED_AT, (string)($timestamps['createdAt']->getTimestamp())); - } - - $multi->hSet($key, self::STORAGE_KEY_UPDATED_AT, (string)($timestamps['updatedAt']->getTimestamp())); - } - - private function updateExpiry(\Credis_Client $multi, string $scope, string $key): void - { - $expiry = $this->expiryFor($scope); - if ($expiry > 0) { - $multi->expire($key, $expiry); - } - } - - protected function expiryFor(string $scope): int - { - $expiry = 0; - if (self::SCOPE_SESSION === $scope) { - $expiry = 30 * 60; // 30 minutes - } - - return $expiry; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Storage/SessionStorage.php b/bundles/PersonalizationBundle/src/Targeting/Storage/SessionStorage.php deleted file mode 100644 index 77b8cb4e812..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Storage/SessionStorage.php +++ /dev/null @@ -1,210 +0,0 @@ -getSessionBag($visitorInfo, $scope, true); - if (null === $bag) { - return []; - } - - $blacklist = [ - self::STORAGE_KEY_CREATED_AT, - self::STORAGE_KEY_UPDATED_AT, - self::STORAGE_KEY_META_ENTRY, - ]; - - // filter internal values - $result = array_filter($bag->all(), function ($key) use ($blacklist) { - return !in_array($key, $blacklist, true); - }, ARRAY_FILTER_USE_KEY); - - return $result; - } - - public function has(VisitorInfo $visitorInfo, string $scope, string $name): bool - { - $bag = $this->getSessionBag($visitorInfo, $scope, true); - if (null === $bag) { - return false; - } - - return $bag->has($name); - } - - public function set(VisitorInfo $visitorInfo, string $scope, string $name, mixed $value): void - { - $bag = $this->getSessionBag($visitorInfo, $scope); - if (null === $bag) { - return; - } - - $bag->set($name, $value); - - $this->updateTimestamps($bag); - } - - /** - * {@inheritdoc } - */ - public function get(VisitorInfo $visitorInfo, string $scope, string $name, mixed $default = null): mixed - { - $bag = $this->getSessionBag($visitorInfo, $scope, true); - if (null === $bag) { - return $default; - } - - return $bag->get($name, $default); - } - - /** - * {@inheritdoc } - */ - public function clear(VisitorInfo $visitorInfo, string $scope = null): void - { - if (null !== $scope) { - $bag = $this->getSessionBag($visitorInfo, $scope, true); - if (null !== $bag) { - $bag->clear(); - } - } else { - foreach (self::VALID_SCOPES as $sc) { - $bag = $this->getSessionBag($visitorInfo, $sc, true); - if (null !== $bag) { - $bag->clear(); - } - } - } - } - - public function migrateFromStorage(TargetingStorageInterface $storage, VisitorInfo $visitorInfo, string $scope): void - { - // only allow migration if a session bag is available as otherwise the fallback - // would clear the original storage although data was not stored - $bag = $this->getSessionBag($visitorInfo, $scope); - if (null === $bag) { - throw new \LogicException('Can\'t migrate to Session storage as session bag could not be loaded'); - } - - $values = $storage->all($visitorInfo, $scope); - foreach ($values as $name => $value) { - $bag->set($name, $value); - } - - // update created/updated at from storage - $this->updateTimestamps( - $bag, - $storage->getCreatedAt($visitorInfo, $scope), - $storage->getUpdatedAt($visitorInfo, $scope) - ); - } - - public function getCreatedAt(VisitorInfo $visitorInfo, string $scope): ?\DateTimeImmutable - { - $bag = $this->getSessionBag($visitorInfo, $scope); - if (null === $bag || !$bag->has(self::STORAGE_KEY_CREATED_AT)) { - return null; - } - - return \DateTimeImmutable::createFromFormat('U', (string)$bag->get(self::STORAGE_KEY_CREATED_AT)); - } - - public function getUpdatedAt(VisitorInfo $visitorInfo, string $scope): ?\DateTimeImmutable - { - $bag = $this->getSessionBag($visitorInfo, $scope); - - if (null === $bag || !$bag->has(self::STORAGE_KEY_UPDATED_AT)) { - return null; - } - - return \DateTimeImmutable::createFromFormat('U', (string)$bag->get(self::STORAGE_KEY_UPDATED_AT)); - } - - /** - * Loads a session bag - * - * @throws \Exception - */ - private function getSessionBag(VisitorInfo $visitorInfo, string $scope, bool $checkPreviousSession = false): ?AttributeBag - { - $request = $visitorInfo->getRequest(); - - if (!$request->hasSession()) { - return null; - } - - if ($checkPreviousSession && !$request->hasPreviousSession()) { - return null; - } - - $session = $request->getSession(); - - switch ($scope) { - case self::SCOPE_SESSION: - $bag = $session->getBag(TargetingSessionBagListener::TARGETING_BAG_SESSION); - - break; - - case self::SCOPE_VISITOR: - $bag = $session->getBag(TargetingSessionBagListener::TARGETING_BAG_VISITOR); - - break; - - default: - throw new \InvalidArgumentException(sprintf( - 'The session storage is not able to handle the "%s" scope', - $scope - )); - } - - if ($bag instanceof AttributeBag) { - return $bag; - } - - throw new \Exception('wrong type'); - } - - private function updateTimestamps( - AttributeBag $bag, - \DateTimeInterface $createdAt = null, - \DateTimeInterface $updatedAt = null - ): void { - $timestamps = $this->normalizeTimestamps($createdAt, $updatedAt); - - if (!$bag->has(self::STORAGE_KEY_CREATED_AT)) { - $bag->set(self::STORAGE_KEY_CREATED_AT, $timestamps['createdAt']->getTimestamp()); - $bag->set(self::STORAGE_KEY_UPDATED_AT, $timestamps['updatedAt']->getTimestamp()); - } else { - $bag->set(self::STORAGE_KEY_UPDATED_AT, $timestamps['updatedAt']->getTimestamp()); - } - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/Storage/TargetingStorageInterface.php b/bundles/PersonalizationBundle/src/Targeting/Storage/TargetingStorageInterface.php deleted file mode 100644 index 7865f87ecfa..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/Storage/TargetingStorageInterface.php +++ /dev/null @@ -1,60 +0,0 @@ - $now, - 'updatedAt' => $now, - ]; - - if (null !== $createdAt) { - $timestamps['createdAt'] = $createdAt; - } - - if (null !== $updatedAt) { - $timestamps['updatedAt'] = $updatedAt; - } - - return $timestamps; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/VisitorInfoResolver.php b/bundles/PersonalizationBundle/src/Targeting/VisitorInfoResolver.php deleted file mode 100644 index 858b0b66a5b..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/VisitorInfoResolver.php +++ /dev/null @@ -1,304 +0,0 @@ -targetingStorage = $targetingStorage; - $this->visitorInfoStorage = $visitorInfoStorage; - $this->conditionMatcher = $conditionMatcher; - $this->actionHandler = $actionHandler; - $this->eventDispatcher = $eventDispatcher; - $this->db = $db; - } - - public function resolve(Request $request): VisitorInfo - { - if ($this->visitorInfoStorage->hasVisitorInfo()) { - return $this->visitorInfoStorage->getVisitorInfo(); - } - - $visitorInfo = VisitorInfo::fromRequest($request); - - if (!$this->isTargetingConfigured()) { - return $visitorInfo; - } - - $event = new TargetingResolveVisitorInfoEvent($visitorInfo); - - $this->eventDispatcher->dispatch($event, TargetingEvents::PRE_RESOLVE); - - $visitorInfo = $event->getVisitorInfo(); - - $this->matchTargetingRuleConditions($visitorInfo); - - $this->eventDispatcher->dispatch(new TargetingEvent($visitorInfo), TargetingEvents::POST_RESOLVE); - - $this->visitorInfoStorage->setVisitorInfo($visitorInfo); - - return $visitorInfo; - } - - public function isTargetingConfigured(): bool - { - if (null !== $this->targetingConfigured) { - return $this->targetingConfigured; - } - - try { - $configuredRules = $this->db->fetchOne('SELECT id FROM targeting_target_groups UNION SELECT id FROM targeting_rules LIMIT 1'); - } catch (\Exception $exception) { - return false; - } - - $this->targetingConfigured = $configuredRules && (int)$configuredRules > 0; - - return $this->targetingConfigured; - } - - private function matchTargetingRuleConditions(VisitorInfo $visitorInfo): void - { - $rules = $this->getTargetingRules(); - - foreach ($rules as $rule) { - if (Rule::SCOPE_SESSION === $rule->getScope()) { - if ($this->ruleWasMatchedInSession($visitorInfo, $rule)) { - continue; - } - } elseif (Rule::SCOPE_VISITOR === $rule->getScope()) { - if ($this->ruleWasMatchedForVisitor($visitorInfo, $rule)) { - continue; - } - } - - $this->matchTargetingRuleCondition($visitorInfo, $rule); - } - } - - private function matchTargetingRuleCondition(VisitorInfo $visitorInfo, Rule $rule): void - { - $scopeWithVariables = Rule::SCOPE_SESSION_WITH_VARIABLES === $rule->getScope(); - - $this->startStopwatch('Targeting:match:' . $rule->getName(), 'targeting'); - - $match = $this->conditionMatcher->match( - $visitorInfo, - $rule->getConditions(), - $scopeWithVariables - ); - - $this->stopStopwatch('Targeting:match:' . $rule->getName()); - - if (!$match) { - return; - } - - if ($scopeWithVariables) { - $collectedVariables = $this->conditionMatcher->getCollectedVariables(); - - // match only once with the same variables - if ($this->ruleWasMatchedInSessionWithVariables($visitorInfo, $rule, $collectedVariables)) { - return; - } - } - - if (Rule::SCOPE_SESSION === $rule->getScope()) { - // record the rule as matched for the current session - $this->markRuleAsMatchedInSession($visitorInfo, $rule); - } elseif (Rule::SCOPE_VISITOR === $rule->getScope()) { - // record the rule as matched for the visitor - $this->markRuleAsMatchedForVisitor($visitorInfo, $rule); - } - - // store info about matched rule - $visitorInfo->addMatchingTargetingRule($rule); - - $this->eventDispatcher->dispatch(new TargetingRuleEvent($visitorInfo, $rule), TargetingEvents::PRE_RULE_ACTIONS); - - // execute rule actions - $this->handleTargetingRuleActions($visitorInfo, $rule); - - $this->eventDispatcher->dispatch(new TargetingRuleEvent($visitorInfo, $rule), TargetingEvents::POST_RULE_ACTIONS); - } - - private function handleTargetingRuleActions(VisitorInfo $visitorInfo, Rule $rule): void - { - $actions = $rule->getActions(); - if (!$actions || !is_array($actions)) { - return; - } - - foreach ($actions as $action) { - if (!is_array($action)) { - continue; - } - - $this->actionHandler->apply($visitorInfo, $action, $rule); - } - } - - /** - * @return Rule[] - */ - private function getTargetingRules(): array - { - if (null !== $this->targetingRules) { - return $this->targetingRules; - } - - /** @var Rule\Listing|Rule\Listing\Dao $list */ - $list = new Rule\Listing(); - $list->setCondition('active = 1'); - $list->setOrderKey('prio'); - $list->setOrder('ASC'); - - $this->targetingRules = $list->load(); - - return $this->targetingRules; - } - - private function ruleWasMatchedInSession(VisitorInfo $visitorInfo, Rule $rule): bool - { - return $this->ruleWasMatched( - $visitorInfo, $rule, - TargetingStorageInterface::SCOPE_SESSION, self::STORAGE_KEY_MATCHED_SESSION_RULES - ); - } - - private function markRuleAsMatchedInSession(VisitorInfo $visitorInfo, Rule $rule): void - { - $this->markRuleAsMatched( - $visitorInfo, $rule, - TargetingStorageInterface::SCOPE_SESSION, self::STORAGE_KEY_MATCHED_SESSION_RULES - ); - } - - private function ruleWasMatchedForVisitor(VisitorInfo $visitorInfo, Rule $rule): bool - { - return $this->ruleWasMatched( - $visitorInfo, $rule, - TargetingStorageInterface::SCOPE_VISITOR, self::STORAGE_KEY_MATCHED_VISITOR_RULES - ); - } - - private function markRuleAsMatchedForVisitor(VisitorInfo $visitorInfo, Rule $rule): void - { - $this->markRuleAsMatched( - $visitorInfo, $rule, - TargetingStorageInterface::SCOPE_VISITOR, self::STORAGE_KEY_MATCHED_VISITOR_RULES - ); - } - - private function ruleWasMatched(VisitorInfo $visitorInfo, Rule $rule, string $scope, string $storageKey): bool - { - $matchedRules = $this->targetingStorage->get($visitorInfo, $scope, $storageKey, []); - - return in_array($rule->getId(), $matchedRules); - } - - private function markRuleAsMatched(VisitorInfo $visitorInfo, Rule $rule, string $scope, string $storageKey): void - { - $matchedRules = $this->targetingStorage->get($visitorInfo, $scope, $storageKey, []); - - if (!in_array($rule->getId(), $matchedRules)) { - $matchedRules[] = $rule->getId(); - } - - $this->targetingStorage->set($visitorInfo, $scope, $storageKey, $matchedRules); - } - - private function ruleWasMatchedInSessionWithVariables(VisitorInfo $visitorInfo, Rule $rule, array $variables): bool - { - $hash = sha1(serialize($variables)); - - $storedVariables = $this->targetingStorage->get( - $visitorInfo, - TargetingStorageInterface::SCOPE_SESSION, - self::STORAGE_KEY_RULE_CONDITION_VARIABLES, - [] - ); - - // hash was already matched - if (isset($storedVariables[$rule->getId()]) && $storedVariables[$rule->getId()] === $hash) { - return true; - } - - // store hash to storage - $storedVariables[$rule->getId()] = $hash; - - $this->targetingStorage->set( - $visitorInfo, - TargetingStorageInterface::SCOPE_SESSION, - self::STORAGE_KEY_RULE_CONDITION_VARIABLES, - $storedVariables - ); - - return false; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/VisitorInfoStorage.php b/bundles/PersonalizationBundle/src/Targeting/VisitorInfoStorage.php deleted file mode 100644 index 6bc7255c6a9..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/VisitorInfoStorage.php +++ /dev/null @@ -1,40 +0,0 @@ -visitorInfo; - } - - public function setVisitorInfo(VisitorInfo $visitorInfo): void - { - $this->visitorInfo = $visitorInfo; - } - - public function hasVisitorInfo(): bool - { - return null !== $this->visitorInfo; - } -} diff --git a/bundles/PersonalizationBundle/src/Targeting/VisitorInfoStorageInterface.php b/bundles/PersonalizationBundle/src/Targeting/VisitorInfoStorageInterface.php deleted file mode 100644 index 388aebaed90..00000000000 --- a/bundles/PersonalizationBundle/src/Targeting/VisitorInfoStorageInterface.php +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - diff --git a/bundles/PersonalizationBundle/templates/Profiler/targeting_data_collector.html.twig b/bundles/PersonalizationBundle/templates/Profiler/targeting_data_collector.html.twig deleted file mode 100644 index b30d0d2cedc..00000000000 --- a/bundles/PersonalizationBundle/templates/Profiler/targeting_data_collector.html.twig +++ /dev/null @@ -1,206 +0,0 @@ -{% extends '@WebProfiler/Profiler/layout.html.twig' %} - -{% block toolbar %} - {% if collector.hasData %} - {% set icon %} - {{ include("@PimcorePersonalization/Profiler/target.svg.twig") }} - - {% if collector.documentTargetGroup is not null %} - {{ collector.documentTargetGroup.name }} - {% endif %} - {% endset %} - - {% set text %} -
- {% if collector.documentTargetGroup is not null %} -
- Document Target Group - {{ collector.documentTargetGroup.name }} -
- {% endif %} - -
- Matched Rules - {{ collector.rules|length }} -
- - {% if collector.targetGroups is not empty %} -
-
Target Groups
-
- - {% for targetGroup in collector.targetGroups %} -
- {{ targetGroup.name }} - {{ targetGroup.count }} -
- {% endfor %} - {% else %} -
- Target Groups - 0 -
- {% endif %} -
- {% endset %} - - {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: true }) }} - {% endif %} -{% endblock %} - -{% block menu %} - - - {{ include("@PimcorePersonalization/Profiler/target.svg.twig") }} - - Targeting - -{% endblock %} - -{% block panel %} -

Targeting

- - {% if not collector.hasData %} -
-

No targeting data available.

-
- {% else %} - -
- {% if collector.documentTargetGroup is not null %} -
- {{ collector.documentTargetGroup.name }} - Document Target Group -
- {% endif %} - -
- {{ collector.rules|length }} - Matched Rules -
- -
- {{ collector.targetGroups|length }} - Assigned Target Groups -
-
- -
-
-

Results

- -
-

Matched Targeting Rules

- - - - - - - - - - - - - {% for rule in collector.rules %} - - - - - - - - {% else %} - - - - {% endfor %} - -
IDNameDurationConditionsActions
{{ rule.id }}{{ rule.name }}{{ rule.duration|round(2) }} ms{{ profiler_dump(rule.conditions, maxDepth=maxDepth|default(0)) }}{{ profiler_dump(rule.actions, maxDepth=maxDepth|default(0)) }}
(no rules matched)
- -

Assigned Target Groups

- - - - - - - - - - - - {% for targetGroup in collector.targetGroups %} - - - - - - - {% else %} - - - - {% endfor %} - -
IDNameThresholdAssignment Count
{{ targetGroup.id }}{{ targetGroup.name }}{{ targetGroup.threshold }}{{ targetGroup.count }}
(no target group assignments)
- -

Target Groups assigned to Documents

- - - - - - - - - - - - {% for assignment in collector.documentTargetGroups %} - - - - - - - {% else %} - - - - {% endfor %} - -
Document IDPathTarget Group IDName
{{ assignment.document.id }}{{ assignment.document.path }}{{ assignment.targetGroup.id }}{{ assignment.targetGroup.name }}
(no target groups were assigned to documents)
-
-
- -
-

Visitor Info

- -
-

Visitor Info

- - {{ include('@PimcorePersonalization/Profiler/key_value_table.html.twig', { data: collector.visitorInfo }, with_context = false) }} -
-
- -
-

Storage

- -
-

Session Storage

- - {{ include('@PimcorePersonalization/Profiler/key_value_table.html.twig', { data: collector.storage.session }, with_context = false) }} -
- -
-

Visitor Storage

- - {{ include('@PimcorePersonalization/Profiler/key_value_table.html.twig', { data: collector.storage.visitor }, with_context = false) }} -
-
-
- - {% endif %} -{% endblock %} diff --git a/bundles/PersonalizationBundle/templates/Targeting/targetingCode.html.twig b/bundles/PersonalizationBundle/templates/Targeting/targetingCode.html.twig deleted file mode 100644 index 1a18f7c0be4..00000000000 --- a/bundles/PersonalizationBundle/templates/Targeting/targetingCode.html.twig +++ /dev/null @@ -1,28 +0,0 @@ -{% block beforeScriptTag %}{{ blocks.beforeScriptTag|raw }}{% endblock %} - - - -{% block afterScriptTag %}{{ blocks.afterScriptTag|raw }}{% endblock %} - -{% block targetingScript %} - -{% endblock %} diff --git a/bundles/PersonalizationBundle/templates/Targeting/toolbar/icon/advanced_features.svg.twig b/bundles/PersonalizationBundle/templates/Targeting/toolbar/icon/advanced_features.svg.twig deleted file mode 100644 index b3fb4dd2cd2..00000000000 --- a/bundles/PersonalizationBundle/templates/Targeting/toolbar/icon/advanced_features.svg.twig +++ /dev/null @@ -1,10 +0,0 @@ -{# original file: data_configuration.svg from Pimcore's icon set #} - - - - - - - - - diff --git a/bundles/PersonalizationBundle/templates/Targeting/toolbar/icon/close.svg.twig b/bundles/PersonalizationBundle/templates/Targeting/toolbar/icon/close.svg.twig deleted file mode 100644 index d356caea1d7..00000000000 --- a/bundles/PersonalizationBundle/templates/Targeting/toolbar/icon/close.svg.twig +++ /dev/null @@ -1,6 +0,0 @@ -{# this file was copied from Symfony's WebProfilerBundle - see vendor/symfony/symfony/src/Symfony/Bundle/WebProfilerBundle/LICENSE #} - - - diff --git a/bundles/PersonalizationBundle/templates/Targeting/toolbar/icon/toolbar-collapse.svg.twig b/bundles/PersonalizationBundle/templates/Targeting/toolbar/icon/toolbar-collapse.svg.twig deleted file mode 100644 index 02bdfeadf3b..00000000000 --- a/bundles/PersonalizationBundle/templates/Targeting/toolbar/icon/toolbar-collapse.svg.twig +++ /dev/null @@ -1,4 +0,0 @@ -{# original file: previous.svg from Pimcore's icon set #} - - - diff --git a/bundles/PersonalizationBundle/templates/Targeting/toolbar/macros.html.twig b/bundles/PersonalizationBundle/templates/Targeting/toolbar/macros.html.twig deleted file mode 100644 index 2240b6b47fb..00000000000 --- a/bundles/PersonalizationBundle/templates/Targeting/toolbar/macros.html.twig +++ /dev/null @@ -1,26 +0,0 @@ -{% macro label(value, classes, attributes) %} - {{ value }} -{% endmacro %} - -{% macro trigger_label(value, target, classes, attributes) %} - {% import _self as toolbar %} - - {% set attributes = { - 'data-ptgtb-toggle': 'collapse', - 'data-ptgtb-target': target, - 'data-ptgtb-collapse-default': 'collapse' - }|merge(attributes|default({})) %} - - {{ toolbar.label(value, classes, attributes) }} -{% endmacro %} - -{% macro metric(label, value) %} - - {{ label }} - {{ value }} - -{% endmacro %} - -{%- macro identifier(token, name, suffix) -%} - _ptgb-{{ name }}-{{ token }}{% if suffix is defined and suffix is not empty %}-{{ suffix }}{% endif %} -{%- endmacro -%} diff --git a/bundles/PersonalizationBundle/templates/Targeting/toolbar/toolbar.css b/bundles/PersonalizationBundle/templates/Targeting/toolbar/toolbar.css deleted file mode 100644 index 2d4781c81d0..00000000000 --- a/bundles/PersonalizationBundle/templates/Targeting/toolbar/toolbar.css +++ /dev/null @@ -1,237 +0,0 @@ -._ptgtb { - display: block; - width: 450px; - height: 100%; - overflow: auto; - position: fixed; - right: 0; - top: 0; - z-index: 10000; - background: rgba(68, 68, 68, 0.97); - border-bottom-left-radius: 4px; - font: 12px Arial, "Helvetica Neue", Helvetica, sans-serif; - color: #fff; -} -._ptgtb._ptgtb--collapsed { - right: -414px; -} -._ptgtb ._ptgtb--hidden { - display: none !important; -} -._ptgtb ._ptgtb__trigger { - position: absolute; - left: 0; - top: 0; - width: 36px; - height: 100%; - background: #222; - border-bottom-left-radius: 4px; - cursor: pointer; -} -._ptgtb ._ptgtb__toolbar-icon { - display: block; - position: absolute; - width: 36px; - height: 36px; - text-align: center; -} -._ptgtb ._ptgtb__toolbar-icon._ptgtb__toolbar-icon--target { - top: 5px; - left: 0; -} -._ptgtb ._ptgtb__toolbar-icon._ptgtb__toolbar-icon--collapse { - bottom: 0; - left: 0; - transform: rotate(180deg); - transition: 0.3s ease-in-out; -} -._ptgtb ._ptgtb__toolbar-icon._ptgtb__toolbar-icon--close, ._ptgtb ._ptgtb__toolbar-icon._ptgtb__toolbar-icon--advanced-features { - top: 0; - cursor: pointer; -} -._ptgtb ._ptgtb__toolbar-icon._ptgtb__toolbar-icon--close:hover, ._ptgtb ._ptgtb__toolbar-icon._ptgtb__toolbar-icon--advanced-features:hover { - background: #626262; -} -._ptgtb ._ptgtb__toolbar-icon._ptgtb__toolbar-icon--close { - right: 0; -} -._ptgtb ._ptgtb__toolbar-icon._ptgtb__toolbar-icon--advanced-features { - right: 36px; - background: #626262; -} -._ptgtb ._ptgtb__toolbar-icon._ptgtb__toolbar-icon--advanced-features._ptgtb__collapse__trigger--collapsed { - background: transparent; -} -._ptgtb ._ptgtb__toolbar-icon svg { - display: inline-block; - height: 18px; - max-height: 18px; - margin-top: 10px; -} -._ptgtb._ptgtb--collapsed ._ptgtb__toolbar-icon._ptgtb__toolbar-icon--collapse { - transform: rotate(0); -} -._ptgtb ._ptgtb__content { - margin-left: 36px; -} -._ptgtb ._ptgtb__content ._ptgtb__content-inner { - padding: 15px; -} -._ptgtb ._ptgtb__content h1 { - font-size: 18px; - margin-top: 0; -} -._ptgtb ._ptgtb__content h2, ._ptgtb ._ptgtb__content h3 { - padding-bottom: 3px; - border-bottom: 1px dotted #686868; - line-height: normal; - letter-spacing: normal; -} -._ptgtb ._ptgtb__content h2 ._ptgtb__label, ._ptgtb ._ptgtb__content h3 ._ptgtb__label { - position: relative; - top: -1px; - margin-left: 3px; -} -._ptgtb ._ptgtb__content h2 { - font-size: 14px; - margin: 20px 0 5px 0; -} -._ptgtb ._ptgtb__content h3 { - font-size: 13px; - margin: 15px 0 5px 0; -} -._ptgtb ._ptgtb__content table { - width: 100%; -} -._ptgtb ._ptgtb__content table td, ._ptgtb ._ptgtb__content table th { - padding: 3px 0; -} -._ptgtb ._ptgtb__table__col-number { - text-align: right; -} -._ptgtb ._ptgtb__table__col-right { - text-align: right; -} -._ptgtb ._ptgtb__table__row-details td:first-child { - padding-left: 10px; -} -._ptgtb ._ptgtb__storage { - padding: 0 0 0 15px; -} -._ptgtb ._ptgtb__collapse--collapsed { - display: none; -} -._ptgtb ._ptgtb__collapse__arrow { - display: inline-block; - margin-left: 4px; -} -._ptgtb ._ptgtb__collapse__trigger:hover { - cursor: pointer; -} -._ptgtb ._ptgtb__collapse__trigger._ptgtb__collapse__trigger--block { - display: block; - clear: both; -} -._ptgtb ._ptgtb__collapse__trigger._ptgtb__collapse__trigger--block ._ptgtb__collapse__arrow { - float: right; -} -._ptgtb ._ptgtb__label, -._ptgtb ._ptgtb__metric { - font: 11px Menlo, Monaco, Consolas, "Courier New", monospace; -} -._ptgtb ._ptgtb__label { - display: inline-block; - padding: 3px 5px; - background: #888; - border-radius: 1px; -} -._ptgtb ._ptgtb__label._ptgtb__label--target-group { - background: #4f805d; -} -._ptgtb ._ptgtb__label._ptgtb__label--rule { - background: #a46a1f; -} -._ptgtb ._ptgtb__metric { - display: inline-block; - margin: 0 5px 3px 0; - border-radius: 1px; -} -._ptgtb ._ptgtb__metric__label, -._ptgtb ._ptgtb__metric__value { - display: inline-block; - padding: 3px 5px; - background: #333; - border-top-left-radius: 1px; - border-bottom-left-radius: 1px; -} -._ptgtb ._ptgtb__metric__label { - background: #656565; - border-top-right-radius: 1px; - border-bottom-right-radius: 1px; -} -._ptgtb ._ptgtb__override-form label { - display: block; - margin: 10px 0 3px 0; -} -._ptgtb ._ptgtb__override-form input, -._ptgtb ._ptgtb__override-form select, -._ptgtb ._ptgtb__override-form textarea { - color: #000; - background: #fff; - padding: 5px 10px; - width: 100%; - border: 1px solid #ccc; - border-radius: 2px; -} -._ptgtb ._ptgtb__override-form input:disabled, -._ptgtb ._ptgtb__override-form select:disabled, -._ptgtb ._ptgtb__override-form textarea:disabled { - cursor: not-allowed; - background: #d3d3d3; -} -._ptgtb ._ptgtb__override-form input, -._ptgtb ._ptgtb__override-form textarea { - font: 11px Menlo, Monaco, Consolas, "Courier New", monospace; -} -._ptgtb ._ptgtb__override-form button { - display: inline-block; - margin: 0 0 0 3px; - padding: 6px 15px; - background: #838383; - border-radius: 2px; - border: 0; - color: #f5f5f5; - font-size: 12px; -} -._ptgtb ._ptgtb__override-form button:hover { - cursor: pointer; - opacity: 0.8; - text-decoration: none; -} -._ptgtb ._ptgtb__override-form ._ptgtb__override-form__button-row { - margin-top: 12px; - text-align: right; -} -._ptgtb ._ptgtb__override-form ._ptgtb__override-form__button-row [type=submit] { - background: #4f805d; - color: #fff; -} -._ptgtb ._ptgtb__override-form ._ptgtb__override-form__collapse-section-container { - margin-top: 20px; - margin-bottom: 10px; -} -._ptgtb ._ptgtb__override-form ._ptgtb__override-form__collapse-section-container:first-child { - margin-top: 10px; -} -._ptgtb ._ptgtb__override-form ._ptgtb__override-form__collapse-section-container > label { - margin-top: 0; - padding-bottom: 3px; - border-bottom: 1px dotted #686868; -} -._ptgtb .sf-dump { - font-size: 11px; - background: #2f2f2f; - border: none; -} - -/*# sourceMappingURL=toolbar.css.map */ diff --git a/bundles/PersonalizationBundle/templates/Targeting/toolbar/toolbar.html.twig b/bundles/PersonalizationBundle/templates/Targeting/toolbar/toolbar.html.twig deleted file mode 100644 index c6add7611d5..00000000000 --- a/bundles/PersonalizationBundle/templates/Targeting/toolbar/toolbar.html.twig +++ /dev/null @@ -1,297 +0,0 @@ -{% import '@PimcorePersonalization/Targeting/toolbar/macros.html.twig' as toolbar %} - -{% block toolbar %} - {% apply spaceless %} -
-
- - {{ include('@PimcorePersonalization/Profiler/target.svg.twig') }} - - - - {{ include('@PimcorePersonalization/Targeting/toolbar/icon/toolbar-collapse.svg.twig') }} - -
- - - {{ include('@PimcorePersonalization/Targeting/toolbar/icon/close.svg.twig') }} - - - - {{ include('@PimcorePersonalization/Targeting/toolbar/icon/advanced_features.svg.twig') }} - - -
-
-

- Targeting -

- - {% block overview %} - - {% block overviewTable %} - {% if documentTargetGroup is not null %} - - - - - {% endif %} - - - - - - - - - - - {% endblock %} -
Document Target Group - {{ toolbar.label(documentTargetGroup.name, ['_ptgtb__label--target-group']) }} -
Visitor ID - {% if visitorInfo.visitorId is not empty %} - {{ toolbar.label(visitorInfo.visitorId) }} - {% else %} - - - {% endif %} -
Session ID - {% if visitorInfo.sessionId is not empty %} - {{ toolbar.label(visitorInfo.sessionId) }} - {% else %} - - - {% endif %} -
- {% endblock %} - - {% block rules %} - {% if rules is not empty %} -

- Matched Rules - {{ toolbar.label(rules|length) }} -

- - - {% block rulesTable %} - {% for rule in rules %} - {% set rowIdentifier = toolbar.identifier(token, 'rules-details', loop.index) %} - - - - - - - - - {% endfor %} - {% endblock %} -
- {{ toolbar.trigger_label(rule.name, '#' ~ rowIdentifier, ['_ptgtb__label--rule']) }} -
- {{ toolbar.metric('Rule ID', rule.id) }} - - {% if rule.duration is not null %} - {{ toolbar.metric('Duration', rule.duration|round(2) ~ ' ms') }} - {% endif %} - - {{ toolbar.metric('Conditions', rule.conditions|length) }} - {{ toolbar.metric('Actions', rule.actions|length) }} -
- {% endif %} - {% endblock %} - - {% block targetGroups %} - {% if targetGroups is not empty %} -

- Assigned Target Groups - {{ toolbar.label(targetGroups|length) }} -

- - - {% block targetGroupsTable %} - {% for targetGroup in targetGroups %} - {% set rowIdentifier = toolbar.identifier(token, 'target-groups-details', loop.index) %} - - - - - - - - - - {% endfor %} - {% endblock %} -
- {{ toolbar.trigger_label(targetGroup.name, '#' ~ rowIdentifier, ['_ptgtb__label--target-group']) }} - - {{ toolbar.label(targetGroup.count) }} -
- {{ toolbar.metric('Target Group ID', targetGroup.id) }} - {{ toolbar.metric('Threshold', targetGroup.threshold) }} -
- {% endif %} - {% endblock %} - - {% block documentTargetGroups %} - {% if documentTargetGroups is not empty %} -

- Document Target Groups - {{ toolbar.label(documentTargetGroups|length) }} -

- - - {% block documentTargetGroupsTable %} - {% for assignment in documentTargetGroups %} - {% set rowIdentifier = toolbar.identifier(token, 'document-target-groups-details', loop.index) %} - - - - - - - - - - {% endfor %} - {% endblock %} -
- {{ toolbar.trigger_label(assignment.document.path|u.truncate(32), '#' ~ rowIdentifier, [], { title: assignment.document.path }) }} - - {{ toolbar.label(assignment.targetGroup.name, ['_ptgtb__label--target-group']) }} -
- {{ toolbar.metric('Document ID', assignment.document.id) }} - {{ toolbar.metric('Target Group ID', assignment.targetGroup.id) }} -
- {% endif %} - {% endblock %} - -
- {% block advanced %} - {% block visitorInfo %} -

- Visitor Info -

-
- {{ pimcore_dump({ data: visitorInfo.data, actions: visitorInfo.actions }) }} -
- {% endblock %} - - {% block storage %} -

- Storage -

-
- - {% block visitorStorage %} -

- Visitor Storage -

-
- {{ pimcore_dump(storage.visitor) }} -
- {% endblock %} - - {% block sessionStorage %} -

- Session Storage -

-
- {{ pimcore_dump(storage.session) }} -
- {% endblock %} -
- {% endblock %} - {% endblock %} -
- - {% block overrides %} -

- Overrides -

- - {% block overrideForm %} - {% form_theme overrideForm 'form_div_layout.html.twig' %} - {{ form_start(overrideForm, { attr: { id: toolbar.identifier(token, 'overrides'), class: '_ptgtb__override-form' }}) }} - {{ form_widget(overrideForm) }} - -
- - -
- - {{ form_end(overrideForm) }} - {% endblock %} - {% endblock %} -
-
-
- {% endapply %} -{% endblock %} - -{% block css %} - {%- apply spaceless -%} - - {%- endapply -%} -{% endblock %} - -{% block js %} - {{ include('@PimcorePersonalization/Targeting/toolbar/toolbar_js.html.twig') }} -{% endblock %} diff --git a/bundles/PersonalizationBundle/templates/Targeting/toolbar/toolbar.scss b/bundles/PersonalizationBundle/templates/Targeting/toolbar/toolbar.scss deleted file mode 100644 index d56cb87be8d..00000000000 --- a/bundles/PersonalizationBundle/templates/Targeting/toolbar/toolbar.scss +++ /dev/null @@ -1,300 +0,0 @@ -// to build the CSS file use -// `sass --sourcemap=none toolbar.scss toolbar.css` -// and commit the built CSS file - -$width: 450px; -$triggerWidth: 36px; -$color-target-group: #4f805d; -$color-rule: #a46a1f; -$font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace; - -._ptgtb { - display: block; - width: $width; - height: 100%; - overflow: auto; - position: fixed; - right: 0; - top: 0; - z-index: 10000; - background: rgba(68, 68, 68, 0.97); // #444 - border-bottom-left-radius: 4px; - font: 12px Arial, 'Helvetica Neue', Helvetica, sans-serif; - color: #fff; - - &._ptgtb--collapsed { - right: $triggerWidth - $width; - } - - ._ptgtb--hidden { - display: none !important; - } - - ._ptgtb__trigger { - position: absolute; - left: 0; - top: 0; - width: $triggerWidth; - height: 100%; - background: #222; - border-bottom-left-radius: 4px; - cursor: pointer; - } - - ._ptgtb__toolbar-icon { - display: block; - position: absolute; - width: $triggerWidth; - height: $triggerWidth; - text-align: center; - - &._ptgtb__toolbar-icon--target { - top: 5px; - left: 0; - } - - &._ptgtb__toolbar-icon--collapse { - bottom: 0; - left: 0; - transform: rotate(180deg); - transition: 0.3s ease-in-out; - } - - &._ptgtb__toolbar-icon--close, - &._ptgtb__toolbar-icon--advanced-features { - top: 0; - cursor: pointer; - - &:hover { - background: #626262; - } - } - - &._ptgtb__toolbar-icon--close { - right: 0; - } - - &._ptgtb__toolbar-icon--advanced-features { - right: $triggerWidth; - background: #626262; - - &._ptgtb__collapse__trigger--collapsed { - background: transparent; - } - } - - svg { - display: inline-block; - height: 18px; - max-height: 18px; - margin-top: 10px; - } - } - - &._ptgtb--collapsed ._ptgtb__toolbar-icon._ptgtb__toolbar-icon--collapse { - transform: rotate(0); - } - - ._ptgtb__content { - margin-left: $triggerWidth; - - ._ptgtb__content-inner { - padding: 15px; - } - - h1 { - font-size: 18px; - margin-top: 0; - } - - h2, h3 { - padding-bottom: 3px; - border-bottom: 1px dotted #686868; - line-height: normal; - letter-spacing: normal; - - ._ptgtb__label { - position: relative; - top: -1px; - margin-left: 3px; - } - } - - h2 { - font-size: 14px; - margin: 20px 0 5px 0; - } - - h3 { - font-size: 13px; - margin: 15px 0 5px 0; - } - - table { - width: 100%; - - td, th { - padding: 3px 0; - } - } - } - - ._ptgtb__table__col-number { - text-align: right; - } - - ._ptgtb__table__col-right { - text-align: right; - } - - ._ptgtb__table__row-details { - td:first-child { - padding-left: 10px; - } - } - - ._ptgtb__storage { - padding: 0 0 0 15px; - } - - ._ptgtb__collapse--collapsed { - display: none; - } - - ._ptgtb__collapse__arrow { - display: inline-block; - margin-left: 4px; - } - - ._ptgtb__collapse__trigger { - &:hover { - cursor: pointer; - } - - &._ptgtb__collapse__trigger--block { - display: block; - clear: both; - - ._ptgtb__collapse__arrow { - float: right; - } - } - } - - ._ptgtb__label, - ._ptgtb__metric { - font: 11px $font-family-monospace; - } - - ._ptgtb__label { - display: inline-block; - padding: 3px 5px; - background: #888; - border-radius: 1px; - - &._ptgtb__label--target-group { - background: $color-target-group; - } - - &._ptgtb__label--rule { - background: $color-rule; - } - } - - ._ptgtb__metric { - display: inline-block; - margin: 0 5px 3px 0; - border-radius: 1px; - } - - ._ptgtb__metric__label, - ._ptgtb__metric__value { - display: inline-block; - padding: 3px 5px; - background: #333; - border-top-left-radius: 1px; - border-bottom-left-radius: 1px; - } - - ._ptgtb__metric__label { - background: #656565; - border-top-right-radius: 1px; - border-bottom-right-radius: 1px; - } - - ._ptgtb__override-form { - label { - display: block; - margin: 10px 0 3px 0; - } - - input, - select, - textarea { - color: #000; - background: #fff; - padding: 5px 10px; - width: 100%; - border: 1px solid #ccc; - border-radius: 2px; - - &:disabled { - cursor: not-allowed; - background: #d3d3d3; - } - } - - input, - textarea { - font: 11px $font-family-monospace; - } - - button { - display: inline-block; - margin: 0 0 0 3px; - padding: 6px 15px; - background: #838383; - border-radius: 2px; - border: 0; - color: #f5f5f5; - font-size: 12px; - - &:hover { - cursor: pointer; - opacity: 0.8; - text-decoration: none; - } - } - - ._ptgtb__override-form__button-row { - margin-top: 12px; - text-align: right; - - [type="submit"] { - background: $color-target-group; - color: #fff; - } - } - - ._ptgtb__override-form__collapse-section-container { - margin-top: 20px; - margin-bottom: 10px; - - &:first-child { - margin-top: 10px; - } - - > label { - margin-top: 0; - padding-bottom: 3px; - border-bottom: 1px dotted #686868; - } - } - } - - .sf-dump { - font-size: 11px; - background: #2f2f2f; - border: none; - } -} diff --git a/bundles/PersonalizationBundle/templates/Targeting/toolbar/toolbar_js.html.twig b/bundles/PersonalizationBundle/templates/Targeting/toolbar/toolbar_js.html.twig deleted file mode 100644 index a16e164ddff..00000000000 --- a/bundles/PersonalizationBundle/templates/Targeting/toolbar/toolbar_js.html.twig +++ /dev/null @@ -1,329 +0,0 @@ -{# - CAUTION: The pimcoreassetcompress tag just blindly strips all newlines from the generated code. This means you can't - use // comments here as they would break the generated code. Use /* */ comments if needed or refactor the "minifying" - to use a decent JS processor such as uglify or webpack. -#} -{%- apply spaceless -%} - -{%- endapply -%} diff --git a/bundles/PersonalizationBundle/tests/Readme.md b/bundles/PersonalizationBundle/tests/Readme.md deleted file mode 100644 index 79861a42758..00000000000 --- a/bundles/PersonalizationBundle/tests/Readme.md +++ /dev/null @@ -1,8 +0,0 @@ -# Running and creating tests - -Tests can be executed locally in a separate docker-compose. This docker-compose also setup database and elastic search -accordingly. - -To run, just execute [/bin/init-tests.sh](./bin/init-tests.sh) script and follow instructions there. - -Additional tests may be added following codeception best practises. \ No newline at end of file diff --git a/bundles/PersonalizationBundle/tests/Support/Data/.gitkeep b/bundles/PersonalizationBundle/tests/Support/Data/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/bundles/PersonalizationBundle/tests/Support/UnitTester.php b/bundles/PersonalizationBundle/tests/Support/UnitTester.php deleted file mode 100644 index 2e3c3f6c5a4..00000000000 --- a/bundles/PersonalizationBundle/tests/Support/UnitTester.php +++ /dev/null @@ -1,43 +0,0 @@ -defaultParts); - - $this->assertEquals($this->defaultResult, $block->asString()); - $this->assertEquals($this->defaultResult, (string)$block); - $this->assertEquals($this->defaultResult, $block->__toString()); - } - - public function testSetParts(): void - { - $block = new CodeBlock(); - - $this->assertEmpty($block->getParts()); - - $block->setParts($this->defaultParts); - - $this->assertEquals($this->defaultParts, $block->getParts()); - $this->assertEquals($this->defaultResult, $block->asString()); - } - - public function testAppend(): void - { - $block = new CodeBlock($this->defaultParts); - - $block->append('foofoo'); - - $expected = $this->defaultResult . "\nfoofoo"; - - $this->assertEquals($expected, $block->asString()); - - $block->append(['123', '456']); - - $expected = $expected. "\n123\n456"; - - $this->assertEquals($expected, $block->asString()); - } - - public function testPrepend(): void - { - $block = new CodeBlock($this->defaultParts); - - $block->prepend('barbar'); - - $expected = "barbar\n" . $this->defaultResult; - - $this->assertEquals($expected, $block->asString()); - - $block->prepend(['654', '321']); - - $expected = "654\n321\n" . $expected; - - $this->assertEquals($expected, $block->asString()); - } -} diff --git a/bundles/PersonalizationBundle/tests/Unit/Targeting/Condition/GeoPointTest.php b/bundles/PersonalizationBundle/tests/Unit/Targeting/Condition/GeoPointTest.php deleted file mode 100755 index 9830973a46c..00000000000 --- a/bundles/PersonalizationBundle/tests/Unit/Targeting/Condition/GeoPointTest.php +++ /dev/null @@ -1,163 +0,0 @@ - [ - 47.83610443106286, - 13.062701225280762, - ], - - // munich olympiapark - 'muc' => [ - 48.17546460000001, - 11.551796999999965, - ], - - // berlin alexanderplatz - 'ber' => [ - 52.5219184, - 13.413214700000026, - ], - - // bangkok grand palace - 'bkk' => [ - 13.75005680956885, - 100.49125671386719, - ], - ]; - - /** - * @dataProvider matchProvider - */ - public function testMatch(GeoPoint $condition, VisitorInfo $visitorInfo, bool $expected): void - { - $this->assertEquals($expected, $condition->match($visitorInfo)); - } - - /** - * @dataProvider noMatchProvider - */ - public function testCannotMatchIfOptionsEmpty(GeoPoint $condition): void - { - $this->assertFalse($condition->canMatch()); - } - - public function matchProvider(): \Generator - { - yield [ - $this->createCondition('sbg', 110), - $this->createVisitorInfo('muc'), - false, - ]; - - yield [ - $this->createCondition('sbg', 120), - $this->createVisitorInfo('muc'), - true, - ]; - - yield [ - $this->createCondition('sbg', 500), - $this->createVisitorInfo('ber'), - false, - ]; - - yield [ - $this->createCondition('sbg', 600), - $this->createVisitorInfo('ber'), - true, - ]; - - yield [ - $this->createCondition('sbg', 100), - $this->createVisitorInfo('bkk'), - false, - ]; - - yield [ - $this->createCondition('sbg', 8000), - $this->createVisitorInfo('bkk'), - false, - ]; - - yield [ - $this->createCondition('sbg', 9000), - $this->createVisitorInfo('bkk'), - true, - ]; - } - - public function noMatchProvider(): \Generator - { - yield [new GeoPoint(1.2, 2.3, null)]; - yield [new GeoPoint(1.2, null, 4)]; - yield [new GeoPoint(null, 2.3, 4)]; - yield [new GeoPoint(1.2, null, null)]; - yield [new GeoPoint(null, 2.3, null)]; - yield [new GeoPoint(null, null, 4)]; - yield [new GeoPoint(null, null, null)]; - } - - private function createCondition(string $point, int $radius): GeoPoint - { - if (!isset($this->points[$point])) { - throw new \InvalidArgumentException(sprintf('Point "%s" is not defined', $point)); - } - - return new GeoPoint( - $this->points[$point][0], - $this->points[$point][1], - $radius - ); - } - - private function createVisitorInfo(string $point): VisitorInfo - { - if (!isset($this->points[$point])) { - throw new \InvalidArgumentException(sprintf('Point "%s" is not defined', $point)); - } - - $geoLocation = new GeoLocationModel($this->points[$point][0], $this->points[$point][1]); - - // create visitor info and set geolocation as GeoLocation provider key - $visitorInfo = new VisitorInfo(new Request()); - $visitorInfo->set(GeoLocation::PROVIDER_KEY, $geoLocation); - - return $visitorInfo; - } -} diff --git a/bundles/PersonalizationBundle/tests/Unit/Targeting/Document/TargetGroupEditableTest.php b/bundles/PersonalizationBundle/tests/Unit/Targeting/Document/TargetGroupEditableTest.php deleted file mode 100644 index dd9243b084b..00000000000 --- a/bundles/PersonalizationBundle/tests/Unit/Targeting/Document/TargetGroupEditableTest.php +++ /dev/null @@ -1,108 +0,0 @@ -testDataHelper = $testData; - } - - public function testTargetGroupsEditable(): void - { - $defaultEditableName = 'inputEditable'; - $defaultEditableData = $this->seed; - - $this->createTestPage(); - $this->testDataHelper->fillInput($this->testPage, $defaultEditableName, $defaultEditableData); - $this->testPage->save(); - - $targetGroup1 = 'testGroup1'; - $targetGroup2 = 'testGroup2'; - - // Create 2 different Target Groups - $this->createTargetGroup($targetGroup1); - $this->createTargetGroup($targetGroup2); - - $targetGroup1 = TargetGroup::getByName($targetGroup1); - $targetGroup1EditableSeed = $this->seed + 1; - $targetGroupEditableName1 = $this->saveTargetGroupEditable($targetGroup1, $defaultEditableName, $targetGroup1EditableSeed); - - $this->testDataHelper->assertInput($this->testPage, $targetGroupEditableName1, $targetGroup1EditableSeed); - - $targetGroup2 = TargetGroup::getByName($targetGroup2); - $targetGroup2EditableSeed = $this->seed + 2; - $targetGroupEditableName2 = $this->saveTargetGroupEditable($targetGroup2, $defaultEditableName, $targetGroup2EditableSeed); - - $this->testDataHelper->assertInput($this->testPage, $targetGroupEditableName2, $targetGroup2EditableSeed); - - //Test the value of first target group editable again - $this->testDataHelper->assertInput($this->testPage, $targetGroupEditableName1, $targetGroup1EditableSeed); - - $this->reloadPage(); - - // Test after reloading - $this->testDataHelper->assertInput($this->testPage, $targetGroupEditableName1, $targetGroup1EditableSeed); - $this->testDataHelper->assertInput($this->testPage, $targetGroupEditableName2, $targetGroup2EditableSeed); - } - - protected function createTestPage(): void - { - /** @var Page $testPage */ - $testPage = TestHelper::createEmptyDocument(type: Page::class); - $this->testPage = $testPage; - } - - // Save the editable using the target specific prefix - protected function saveTargetGroupEditable(TargetGroup $targetGroup, string $editableName, int $targetGroupSeed): string - { - $targetGroupData = 'content' . $targetGroupSeed; - $this->testPage->setUseTargetGroup($targetGroup->getId()); - $targetGroupEditableName = $this->testPage->getTargetGroupEditableName($editableName); - - $this->testPage->setRawEditable($targetGroupEditableName, 'input', $targetGroupData); - $this->testPage->save(); - - return $targetGroupEditableName; - } - - public function reloadPage(): void - { - $this->testPage = Page::getById($this->testPage->getId(), ['force' => true]); - } - - // Create Target Group - public function createTargetGroup(string $name): void - { - /** @var TargetGroup|TargetGroup\Dao $targetGroup */ - $targetGroup = new TargetGroup(); - $targetGroup->setName($name); - $targetGroup->save(); - } -} diff --git a/bundles/PersonalizationBundle/tests/_bootstrap.php b/bundles/PersonalizationBundle/tests/_bootstrap.php deleted file mode 100644 index 854eb56cbe9..00000000000 --- a/bundles/PersonalizationBundle/tests/_bootstrap.php +++ /dev/null @@ -1,53 +0,0 @@ -- - This condition fetches data synchronously from Piwik which can be quite slow. - Use with care! -targeting_condition_visited_page_before_piwik_not_configured_warning: >- - This condition cannot be matched as Piwik is not configured and will always - resolve to false. -targeting_condition_url_pattern: URL (RegExp) -targeting_toolbar_browser_note: >- - NOTE: Enabling the targeting toolbar affects only the browser you are - currently using. If you want to use the toolbar on another browser you need to - enable it again. See the - documentation for details. diff --git a/composer.json b/composer.json index 8d3c69cc130..b8848fb0b13 100644 --- a/composer.json +++ b/composer.json @@ -60,7 +60,6 @@ "egulias/email-validator": "^3.0.0", "endroid/qr-code": "^4", "friendsofsymfony/jsrouting-bundle": "^3.2.1", - "geoip2/geoip2": "^2.9", "guzzlehttp/guzzle": "^7.2", "http-interop/http-factory-guzzle": "^1.0.0", "lcobucci/jwt": "^4.0", @@ -190,9 +189,7 @@ "Pimcore\\Bundle\\StaticRoutesBundle\\": "bundles/StaticRoutesBundle/src/", "Pimcore\\Bundle\\WordExportBundle\\": "bundles/WordExportBundle/src/", "Pimcore\\Bundle\\WebToPrintBundle\\": "bundles/WebToPrintBundle/src/", - "Pimcore\\Bundle\\TinymceBundle\\": "bundles/TinymceBundle/src/", - "Pimcore\\Bundle\\PersonalizationBundle\\": "bundles/PersonalizationBundle/src/", - "Pimcore\\Model\\DataObject\\": "bundles/PersonalizationBundle/src/Pimcore/Model/DataObject" + "Pimcore\\Bundle\\TinymceBundle\\": "bundles/TinymceBundle/src/" }, "classmap": [ "lib/Pimcore.php" diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index c3d5c79614f..7c3f37a5709 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -15,16 +15,6 @@ parameters: count: 2 path: lib/Mail.php - - - message: "#^Parameter \\#2 \\$eventDispatcher of method Pimcore\\\\Bundle\\\\PersonalizationBundle\\\\Targeting\\\\Condition\\\\EventDispatchingConditionInterface\\:\\:postMatch\\(\\) expects Symfony\\\\Component\\\\EventDispatcher\\\\EventDispatcherInterface, Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface given\\.$#" - count: 1 - path: bundles/PersonalizationBundle/src/Targeting/ConditionMatcher.php - - - - message: "#^Parameter \\#2 \\$eventDispatcher of method Pimcore\\\\Bundle\\\\PersonalizationBundle\\\\Targeting\\\\Condition\\\\EventDispatchingConditionInterface\\:\\:preMatch\\(\\) expects Symfony\\\\Component\\\\EventDispatcher\\\\EventDispatcherInterface, Symfony\\\\Contracts\\\\EventDispatcher\\\\EventDispatcherInterface given\\.$#" - count: 1 - path: bundles/PersonalizationBundle/src/Targeting/ConditionMatcher.php - - message: "#^Call to an undefined method COM\\:\\:Run\\(\\)\\.$#" count: 1 @@ -58,3 +48,18 @@ parameters: message: "#^Method Pimcore\\\\Bundle\\\\SimpleBackendSearchBundle\\\\MessageHandler\\\\SearchBackendHandler\\:\\:shouldFlush\\(\\) is unused\\.$#" count: 1 path: bundles/SimpleBackendSearchBundle/src/MessageHandler/SearchBackendHandler.php + + - + message: "#^Class Pimcore\\\\Bundle\\\\PersonalizationBundle\\\\Model\\\\Document\\\\Targeting\\\\TargetingDocumentInterface not found\\.$#" + count: 1 + path: models/Document/Editable/Renderlet.php + + - + message: "#^Class Pimcore\\\\Bundle\\\\PersonalizationBundle\\\\Targeting\\\\Document\\\\DocumentTargetingConfigurator not found\\.$#" + count: 1 + path: models/Document/Editable/Renderlet.php + + - + message: "#^Class Pimcore\\\\Bundle\\\\PersonalizationBundle\\\\Targeting\\\\Document\\\\DocumentTargetingConfigurator not found\\.$#" + count: 1 + path: models/Document/Editable/Snippet.php diff --git a/phpstan-parameters.neon b/phpstan-parameters.neon index 5ab948786f5..9ff31ceee10 100644 --- a/phpstan-parameters.neon +++ b/phpstan-parameters.neon @@ -15,8 +15,6 @@ parameters: excludePaths: - '**/Processor/PdfReactor.php' - '**/PDFreactor.class.php' - - 'bundles/PersonalizationBundle/src/Targeting/DataProvider/GeoIp.php' - - 'bundles/PersonalizationBundle/tests/Support/UnitTester.php' ignoreErrors: - '~^Unsafe usage of new static\(\)~'