├── .eslintignore ├── .githooks ├── install_hooks.sh └── pre-commit ├── .github └── workflows │ └── call-plugin-workflow.yml ├── .gitignore ├── .gitlab-ci.yml ├── .php-cs-fixer.php ├── LICENSE ├── Makefile ├── README.md ├── Resources ├── config.xml ├── frontend │ └── js │ │ ├── google_adds.js │ │ ├── google_analytics.js │ │ ├── jquery.google_analytics_plugin.js │ │ └── universal_analytics.js └── views │ └── frontend │ ├── index │ ├── google_analytics.tpl │ ├── header.tpl │ └── index.tpl │ └── swag_google │ └── optout.tpl ├── SwagGoogle.php ├── composer.json ├── composer.lock ├── phpstan.neon ├── plugin.png └── plugin.xml /.eslintignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shopware5/SwagGoogle/1622474f4dc5473cb04fd18d1e459533ff260556/.eslintignore -------------------------------------------------------------------------------- /.githooks/install_hooks.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | writeln(); 23 | $this->writeln('Checking commit requirements', 0); 24 | $this->writeln(); 25 | 26 | if ($this->isRebase()) { 27 | echo 'Not on branch' . PHP_EOL; 28 | 29 | return 0; 30 | } 31 | 32 | $this->runPhpLint($this->getCommittedFileList()); 33 | $this->runPhpCsFixer($this->getCommittedFileList()); 34 | $this->runPHPStan($this->getCommittedFileList()); 35 | $this->runEsLint($this->getCommittedFileList('js')); 36 | 37 | if ($this->error) { 38 | $this->writeln("If you are ABSOLUTELY sure your code is correct, you can use 'git commit --no-verify' to bypass this validation", 0); 39 | } 40 | 41 | exit((int) $this->error); 42 | } 43 | 44 | /** 45 | * @param string $output 46 | * @param int $level 47 | */ 48 | private function writeln($output = '', $level = 1) 49 | { 50 | $this->write($output, $level); 51 | echo PHP_EOL; 52 | } 53 | 54 | /** 55 | * @param string $output 56 | * @param int $level 57 | */ 58 | private function write($output = '', $level = 1) 59 | { 60 | $spaces = $level * 3; 61 | 62 | echo str_pad($output, strlen($output) + $spaces, ' ', STR_PAD_LEFT); 63 | } 64 | 65 | /** 66 | * @return bool 67 | */ 68 | private function isRebase() 69 | { 70 | $output = []; 71 | exec('git symbolic-ref --short -q HEAD', $output); 72 | 73 | return empty($output); 74 | } 75 | 76 | /** 77 | * @param string $extension 78 | * @return string[] 79 | */ 80 | private function getCommittedFileList($extension = 'php') 81 | { 82 | exec("git diff --name-only --diff-filter=ACMRTUXB \"HEAD\" | grep -e '\." . $extension . "$'", $fileList); 83 | 84 | return $fileList; 85 | } 86 | 87 | /** 88 | * @param array $fileList 89 | */ 90 | private function runPhpLint(array $fileList) 91 | { 92 | $this->writeln('# Checking php syntax'); 93 | $this->writeln('> php -l'); 94 | 95 | foreach ($fileList as $file) { 96 | exec('php -l ' . escapeshellarg($file) . ' 2> /dev/null', $output, $return); 97 | if ($return !== 0) { 98 | $this->writeln('- ' . $output[1], 2); 99 | $this->error = true; 100 | } 101 | } 102 | 103 | $this->writeln(); 104 | } 105 | 106 | /** 107 | * @param array $fileList 108 | */ 109 | private function runPhpCsFixer(array $fileList) 110 | { 111 | $this->writeln('# Checking php code style'); 112 | $this->writeln('> php-cs-fixer fix -v --no-ansi --dry-run'); 113 | 114 | if (!$this->isPHPCSFixerAvailable()) { 115 | $this->error = true; 116 | $this->writeln('- php-cs-fixer is NOT installed. Please install composer with dev dependencies.', 2); 117 | $this->writeln(); 118 | 119 | return; 120 | } 121 | 122 | foreach ($fileList as $file) { 123 | exec('./../../../vendor/bin/php-cs-fixer fix -v --no-ansi --dry-run ' . escapeshellarg($file) . ' 2>&1', $output, $return); 124 | 125 | if ($return !== 0) { 126 | $this->writeln('- ' . preg_replace('#^(\s+)?\d\)\s#', '', $output[3]), 2); 127 | $fixes[] = './../../../vendor/bin/php-cs-fixer fix -v ' . escapeshellarg($file); 128 | $this->error = true; 129 | } 130 | } 131 | 132 | if (!empty($fixes)) { 133 | $this->writeln(); 134 | $this->writeln('Help:', 2); 135 | foreach ($fixes as $fix) { 136 | $this->writeln($fix, 3); 137 | } 138 | } 139 | 140 | $this->writeln(); 141 | } 142 | 143 | private function runPHPStan(array $fileList) 144 | { 145 | $this->writeln('# Checking code with PHPStan'); 146 | $this->writeln('> phpstan analyse'); 147 | $this->writeln(); 148 | 149 | if ($fileList === []) { 150 | return; 151 | } 152 | 153 | if (!$this->isPHPStanAvailable()) { 154 | $this->writeln('- PHPStan is NOT installed. Please install composer with dev dependencies or use higher Shopware version.', 2); 155 | $this->writeln(); 156 | 157 | return; 158 | } 159 | 160 | exec('./../../../vendor/bin/phpstan analyse .', $output, $return); 161 | if ($return !== 0) { 162 | $this->error = true; 163 | $this->writeln('> PHPStan errors found'); 164 | } 165 | 166 | $this->writeln(); 167 | } 168 | 169 | /** 170 | * @param array $fileList 171 | */ 172 | private function runEsLint(array $fileList) 173 | { 174 | $this->writeln('# Checking javascript code style'); 175 | $this->writeln('> eslint.js --ignore-path .eslintignore'); 176 | 177 | if (!$this->isESLintAvailable()) { 178 | $this->writeln('- eslint.js not found. Skipping javascript code style check.', 2); 179 | $this->writeln(); 180 | 181 | return; 182 | } 183 | 184 | $this->checkESLint($fileList); 185 | 186 | $this->writeln(); 187 | } 188 | 189 | public function isPHPCSFixerAvailable(): bool 190 | { 191 | return is_executable('./../../../vendor/bin/php-cs-fixer'); 192 | } 193 | 194 | private function isPHPStanAvailable(): bool 195 | { 196 | return is_executable('./../../../vendor/bin/phpstan'); 197 | } 198 | 199 | public function isESLintAvailable(): bool 200 | { 201 | return is_executable('./../../../themes/node_modules/eslint/bin/eslint.js'); 202 | } 203 | 204 | /** 205 | * @param array $fileList 206 | */ 207 | private function checkESLint(array $fileList = []) 208 | { 209 | $output = []; 210 | $return = 0; 211 | exec( 212 | './../../../themes/node_modules/eslint/bin/eslint.js ' . 213 | '--ignore-path .eslintignore ' . 214 | '-c ./../../../themes/.eslintrc.js ' . 215 | '--global "Ext" ' . 216 | implode(' ', $fileList), 217 | $output, 218 | $return 219 | ); 220 | $return = !(bool) $return; 221 | 222 | if (!$return) { 223 | $this->error = true; 224 | 225 | foreach ($output as $line) { 226 | $this->writeln($line, 2); 227 | } 228 | 229 | $this->writeln('Help:', 2); 230 | $this->writeln( 231 | './../../../themes/node_modules/eslint/bin/eslint.js ' . 232 | '--fix --ignore-path .eslintignore ' . 233 | '-c ./../../../themes/.eslintrc.js ' . 234 | '--global "Ext" ' . 235 | implode(' ', $fileList), 236 | 3 237 | ); 238 | } 239 | } 240 | } 241 | 242 | $checks = new PreCommitChecks(); 243 | $checks->run(); 244 | -------------------------------------------------------------------------------- /.github/workflows/call-plugin-workflow.yml: -------------------------------------------------------------------------------- 1 | name: Run plugin workflow 2 | 3 | on: 4 | pull_request: 5 | workflow_dispatch: 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | 12 | call-analyse-workflow: 13 | name: Analyse code for SwagGoogle 14 | uses: shopware5/docker-images-testing/.github/workflows/php-code-analysis.yml@main 15 | with: 16 | plugin-name: SwagGoogle 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | .psh.yml 3 | .php-cs-fixer.cache 4 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | variables: 2 | DOCKER_DRIVER: overlay2 3 | DOCKER_HOST: "tcp://docker:2375" 4 | DOCKER_TLS_CERTDIR: "/certs" 5 | MYSQL_ROOT_PASSWORD: root 6 | MYSQL_USER: app 7 | MYSQL_PASSWORD: app 8 | MYSQL_DATABASE: shopware 9 | WEB_DOCUMENT_ROOT: $CI_PROJECT_DIR/ 10 | GIT_STRATEGY: clone 11 | SHOPWARE_ENV: swaggoogletest 12 | CHECKOUT_SHOPWARE_BRANCH: "5.7" 13 | PLUGIN_NAME: SwagGoogle 14 | 15 | default: 16 | tags: 17 | - t3.nano 18 | 19 | stages: 20 | - Code Analysis 21 | 22 | PHP analyze: 23 | stage: Code Analysis 24 | tags: 25 | - t3.medium 26 | image: gitlab.shopware.com:5005/shopware/5/product/image/continuous:7.4 27 | services: 28 | - name: mysql:5.7 29 | alias: mysql 30 | before_script: 31 | - zip -rq plugin.zip . 32 | - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.shopware.com/shopware/5/product/shopware.git shopware --depth=1 -b ${CHECKOUT_SHOPWARE_BRANCH} 33 | - unzip -q plugin.zip -d shopware/custom/plugins/${PLUGIN_NAME} 34 | - cd shopware 35 | - cp .env.dist .env 36 | - make init 37 | - php bin/console sw:warm:http:cache 38 | - /entrypoint supervisord &>/dev/null & 39 | script: 40 | - cd custom/plugins/${PLUGIN_NAME} 41 | - make fix-cs-dry 42 | - make phpstan 43 | -------------------------------------------------------------------------------- /.php-cs-fixer.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * For the full copyright and license information, please view the LICENSE 6 | * file that was distributed with this source code. 7 | * 8 | */ 9 | 10 | use PhpCsFixer\Config; 11 | use PhpCsFixerCustomFixers\Fixer\NoSuperfluousConcatenationFixer; 12 | use PhpCsFixerCustomFixers\Fixer\NoUselessCommentFixer; 13 | use PhpCsFixerCustomFixers\Fixer\NoUselessParenthesisFixer; 14 | use PhpCsFixerCustomFixers\Fixer\NoUselessStrlenFixer; 15 | use PhpCsFixerCustomFixers\Fixer\PhpdocParamTypeFixer; 16 | use PhpCsFixerCustomFixers\Fixer\SingleSpaceAfterStatementFixer; 17 | use PhpCsFixerCustomFixers\Fixer\SingleSpaceBeforeStatementFixer; 18 | use PhpCsFixerCustomFixers\Fixers; 19 | 20 | $finder = PhpCsFixer\Finder::create() 21 | ->in(__DIR__); 22 | 23 | $header = << 25 | 26 | For the full copyright and license information, please view the LICENSE 27 | file that was distributed with this source code. 28 | 29 | EOF; 30 | 31 | return (new Config()) 32 | ->registerCustomFixers(new Fixers()) 33 | ->setRiskyAllowed(true) 34 | ->setRules([ 35 | '@PSR12' => true, 36 | '@Symfony' => true, 37 | 38 | 'array_syntax' => ['syntax' => 'short'], 39 | 'blank_line_after_opening_tag' => false, 40 | 'class_attributes_separation' => ['elements' => ['method' => 'one', 'property' => 'one']], 41 | 'concat_space' => ['spacing' => 'one'], 42 | 'doctrine_annotation_indentation' => true, 43 | 'doctrine_annotation_spaces' => true, 44 | 'general_phpdoc_annotation_remove' => ['annotations' => ['copyright', 'category']], 45 | 'header_comment' => ['header' => $header, 'separate' => 'bottom', 'comment_type' => 'PHPDoc'], 46 | 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'], 47 | 'native_constant_invocation' => true, 48 | 'native_function_invocation' => ['scope' => 'all', 'strict' => false], 49 | 'no_superfluous_phpdoc_tags' => true, 50 | 'no_useless_else' => true, 51 | 'no_useless_return' => true, 52 | 'operator_linebreak' => ['only_booleans' => true], 53 | 'ordered_class_elements' => true, 54 | 'ordered_imports' => true, 55 | 'phpdoc_order' => true, 56 | 'phpdoc_summary' => false, 57 | 'phpdoc_var_annotation_correct_order' => true, 58 | 'php_unit_test_case_static_method_calls' => true, 59 | 'single_line_throw' => false, 60 | 'yoda_style' => ['equal' => false, 'identical' => false, 'less_and_greater' => false], 61 | 62 | NoSuperfluousConcatenationFixer::name() => true, 63 | NoUselessCommentFixer::name() => true, 64 | NoUselessStrlenFixer::name() => true, 65 | NoUselessParenthesisFixer::name() => true, 66 | PhpdocParamTypeFixer::name() => true, 67 | SingleSpaceAfterStatementFixer::name() => true, 68 | SingleSpaceBeforeStatementFixer::name() => true, 69 | ])->setFinder($finder); 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) shopware AG 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := help 2 | 3 | filter := "default" 4 | dirname := $(notdir $(CURDIR)) 5 | envprefix := $(shell echo "$(dirname)" | tr A-Z a-z) 6 | envname := $(envprefix)test 7 | 8 | help: 9 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 10 | .PHONY: help 11 | 12 | init: composer-install install-hooks install-plugin install-test-environment ## install the plugin with pre commit hook, requirements and the test environment 13 | 14 | composer-install: ## Install composer requirements 15 | @echo "Install composer requirements" 16 | composer install 17 | 18 | install-hooks: ## Install pre commit hooks 19 | @echo "Install pre commit hooks" 20 | .githooks/install_hooks.sh 21 | 22 | install-plugin: .refresh-plugin-list ## Install and activate the plugin 23 | @echo "Install the plugin" 24 | ./../../../bin/console sw:plugin:install $(dirname) --activate -c 25 | 26 | install-test-environment: ## Installs the plugin test environment 27 | @echo "Install the test environment" 28 | ./psh local:init 29 | 30 | run-tests: ## Execute the php unit tests... (You can use the filter parameter "make run-tests filter=yourFilterPhrase") 31 | ifeq ($(filter), "default") 32 | SHOPWARE_ENV=$(envname) ./../../../vendor/phpunit/phpunit/phpunit --verbose 33 | else 34 | SHOPWARE_ENV=$(envname) ./../../../vendor/phpunit/phpunit/phpunit --verbose --filter $(filter) 35 | endif 36 | 37 | fix-cs: ## Run the code style fixer 38 | ./../../../vendor/bin/php-cs-fixer fix 39 | 40 | fix-cs-dry: ## Run the code style fixer in dry mode 41 | ./../../../vendor/bin/php-cs-fixer fix --dry-run -v 42 | 43 | phpstan: ## Run PHPStan 44 | ./../../../vendor/bin/phpstan analyse . 45 | 46 | phpstan-generate-baseline: ## Run PHPStan and generate a baseline file 47 | ./../../../vendor/bin/phpstan analyse . --generate-baseline 48 | 49 | .refresh-plugin-list: 50 | @echo "Refresh the plugin list" 51 | ./../../../bin/console sw:plugin:refresh 52 | 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwagGoogle 2 | 3 | ## Shopware integration for Google Analytics, Universal Analytics and Google Adwords services 4 | 5 | ## License 6 | 7 | The MIT License (MIT). Please see [License File](LICENSE) for more information. 8 | -------------------------------------------------------------------------------- /Resources/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | tracking_code 8 | 9 | 10 | 11 | 12 | 13 | conversion_code 14 | 15 | 16 | 17 | 18 | 19 | conversion_label 20 | 21 | 22 | 23 | 24 | 25 | anonymize_ip 26 | 27 | 28 | true 29 | 30 | 31 | 32 | trackingLib 33 | 34 | 35 | ua 36 | Welche Tracking Bibliothek soll benutzt werden? Standardmäßig wird die veraltete Google Analytics verwendet. Der Wechsel zur Universal-Analytics-Bibliothek erfordert, das Sie Ihre Google Analytics Einstellungen aktualisieren. Für mehr Informationen besuchen Sie die offizielle Google-Dokumentation. 37 | Tracking library to use. Defaults to legacy Google Analytics. Switching to Universal Analytics requires that you update you settings in your Google Analytics Admin page. Please check Google's official documentation for more info. 38 | 39 | 43 | 47 | 48 | 49 | 50 | 51 | 52 | include_opt_out_cookie 53 | 54 | 55 | false 56 | Opt-Out Cookie setzen, so dass der Datenfluss nach Google unterbochen werden kann: https://developers.google.com/analytics/devguides/collection/gajs/ 57 | The tracking snippet includes a window property disables the tracking snippet from sending data to Google Analytics: https://developers.google.com/analytics/devguides/collection/gajs/ 58 | 59 | 60 | 61 | include_header 62 | 63 | 64 | false 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /Resources/frontend/js/google_adds.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 3 | GoogleAdds = function(opts) { 4 | var me = this, 5 | url, img; 6 | 7 | me.opts = opts; 8 | 9 | url = me.createUrl(); 10 | img = me.createImage(url); 11 | 12 | me.addVariables(); 13 | 14 | $('') 15 | .appendTo('body'); 16 | 17 | $('').append($(img)).appendTo('body'); 18 | }; 19 | 20 | GoogleAdds.prototype = { 21 | addVariables: function() { 22 | var me = this, 23 | data = [ 24 | 'var google_conversion_id="' + me.opts.googleConversionID + '"', 25 | 'google_conversion_language="' + me.opts.googleConversionLanguage + '"', 26 | 'google_conversion_value="' + me.opts.realAmount + '"', 27 | 'google_conversion_label="' + me.opts.googleConversionLabel + '"', 28 | 'google_conversion_currency="' + me.opts.currency + '"', 29 | 'google_conversion_format="1"', 30 | 'google_conversion_color = "FFFFFF"', 31 | 'google_remarketing_only=false;' 32 | ].join(','); 33 | 34 | $('').append(data).appendTo('body'); 35 | }, 36 | 37 | createImage: function(url) { 38 | return [ 39 | '' 42 | ].join(''); 43 | }, 44 | 45 | createUrl: function() { 46 | var me = this; 47 | 48 | return [ 49 | 'https://www.googleadservices.com/pagead/conversion/', 50 | me.opts.googleConversionID, 51 | '/?value=', 52 | me.opts.realAmount, 53 | '¤cy_code=', 54 | me.opts.currency, 55 | '&label=', 56 | me.opts.googleConversionLabel, 57 | '&guid=ON&script=0' 58 | ].join(''); 59 | }, 60 | }; 61 | 62 | })(jQuery); 63 | -------------------------------------------------------------------------------- /Resources/frontend/js/google_analytics.js: -------------------------------------------------------------------------------- 1 | (function($, window, document) { 2 | 3 | /** 4 | * @param { object } opts 5 | */ 6 | GoogleAnalytics = function(opts) { 7 | var me = this; 8 | 9 | me.opts = opts; 10 | 11 | if (me.opts.doNotTrack) { 12 | return; 13 | } 14 | 15 | me.addHeadScript(); 16 | me.initScript(); 17 | }; 18 | 19 | GoogleAnalytics.prototype = { 20 | initScript: function() { 21 | var ga = document.createElement('script'); 22 | ga.async = true; 23 | ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; 24 | (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(ga); 25 | }, 26 | 27 | addHeadScript: function() { 28 | var me = this, 29 | script = [ 30 | "var _gaq = _gaq || [];", 31 | [ 32 | "_gaq.push(['_setAccount',", 33 | '"', 34 | me.opts.googleTrackingID, 35 | '"', 36 | "]);" 37 | ].join(''), 38 | ], 39 | entity; 40 | 41 | if (me.opts.googleAnonymizeIp) { 42 | script.push("_gaq.push(['_gat._anonymizeIp']);"); 43 | } 44 | 45 | if (window.basketData.hasData) { 46 | me.addTransaction(script); 47 | me.addTransactionData(script); 48 | new GoogleAdds(me.opts); 49 | script.push("_gaq.push(['_trackTrans']);"); 50 | } 51 | 52 | script.push("_gaq.push(['_setDomainName', 'none']);"); 53 | script.push("_gaq.push(['_trackPageview']);"); 54 | 55 | entity = [ 56 | '' 59 | ]; 60 | 61 | $(entity.join('')).appendTo('head'); 62 | }, 63 | 64 | addTransaction: function(script) { 65 | var me = this, 66 | data = [ 67 | '"' + me.opts.orderNumber + '"', 68 | '"' + escape(me.opts.affiliation) + '"', 69 | '"' + me.opts.revenue + '"', 70 | '"' + me.opts.tax + '"', 71 | '"' + me.opts.shipping + '"', 72 | '"' + me.opts.city + '"', 73 | "", 74 | '"' + me.opts.country + '"', 75 | ].join(','); 76 | 77 | script.push([ 78 | "_gaq.push(['_addTrans',", 79 | data, 80 | "]);"].join('') 81 | ); 82 | }, 83 | 84 | addTransactionData: function(script) { 85 | var me = this, itemString; 86 | 87 | $.each(window.basketData.data, function(index, item) { 88 | itemString = [ 89 | '"' + me.opts.orderNumber + '"', 90 | '"' + item.sku + '"', 91 | '"' + escape(item.name) + '"', 92 | "", 93 | '"' + item.price + '"', 94 | '"' + item.quantity + '"', 95 | ].join(','); 96 | 97 | script.push([ 98 | "_gaq.push(['_addItem',", 99 | itemString, 100 | "]);" 101 | ].join('')); 102 | }); 103 | }, 104 | }; 105 | 106 | })(jQuery, window, document); 107 | -------------------------------------------------------------------------------- /Resources/frontend/js/jquery.google_analytics_plugin.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 3 | $.plugin('SwagGoogleAnalytics', { 4 | 5 | defaults: { 6 | realAmount: 0.0, 7 | 8 | googleTrackingID: '', 9 | 10 | googleConversionID: '', 11 | 12 | googleConversionLabel: '', 13 | 14 | googleConversionLanguage: '', 15 | 16 | googleAnonymizeIp: '', 17 | 18 | googleOptOutCookie: '', 19 | 20 | googleTrackingLibrary: '', 21 | 22 | googleOptOutCookie: '', 23 | 24 | createEcommerceTransaction: false, 25 | 26 | cookieNoteMode: null, 27 | 28 | showCookieNote: null, 29 | 30 | orderNumber: null, 31 | 32 | affiliation: null, 33 | 34 | revenue: null, 35 | 36 | tax: null, 37 | 38 | shipping: null, 39 | 40 | currency: null, 41 | 42 | city: null, 43 | 44 | country: null, 45 | 46 | doNotTrack: false 47 | }, 48 | 49 | init: function() { 50 | var me = this; 51 | 52 | me.applyDataAttributes(); 53 | 54 | me.checkGetCookiePreference(); 55 | me.opts.doNotTrack = me.checkDoNotTrack(); 56 | 57 | if (me.isGoogleAllowed() || me.evaluateCookieHint()) { 58 | me.createLibrary(); 59 | return; 60 | } 61 | 62 | me.createCheckTimer(); 63 | }, 64 | 65 | isGoogleAllowed: function() { 66 | var me = this; 67 | 68 | me.cookieValue = me.getCookie(); 69 | 70 | return me.cookieValue || $.getCookiePreference(me.getCookieKey()); 71 | }, 72 | 73 | checkDoNotTrack: function() { 74 | if (window.doNotTrack || navigator.doNotTrack || navigator.msDoNotTrack) { 75 | if (window.doNotTrack == "1" || navigator.doNotTrack === "yes" || navigator.doNotTrack == "1" || navigator.msDoNotTrack == "1") { 76 | return true; 77 | } 78 | } 79 | 80 | return false; 81 | }, 82 | 83 | evaluateCookieHint: function() { 84 | var me = this; 85 | 86 | if (!me.opts.showCookieNote) { 87 | return true; 88 | } 89 | 90 | return me.opts.showCookieNote === 1 && me.opts.cookieNoteMode === 0; 91 | }, 92 | 93 | createCheckTimer: function() { 94 | var me = this; 95 | 96 | me.interval = window.setInterval($.proxy(me.onCheckCookie, me), 1000); 97 | }, 98 | 99 | onCheckCookie: function() { 100 | var me = this; 101 | 102 | if (me.isGoogleAllowed()) { 103 | window.clearInterval(me.interval); 104 | me.createLibrary(); 105 | } 106 | }, 107 | 108 | createLibrary: function() { 109 | var me = this; 110 | 111 | if (me.opts.googleTrackingLibrary === 'ga') { 112 | new GoogleAnalytics(me.opts); 113 | return; 114 | } 115 | 116 | new UniversalAnalytics(me.opts); 117 | }, 118 | 119 | getCookie: function() { 120 | var name = "allowCookie=", 121 | decodedCookie = decodeURIComponent(document.cookie), 122 | cookieArray = decodedCookie.split(';'); 123 | 124 | for (var i = 0; i < cookieArray.length; i++) { 125 | var cookie = cookieArray[i]; 126 | while (cookie.charAt(0) == ' ') { 127 | cookie = cookie.substring(1); 128 | } 129 | if (cookie.indexOf(name) == 0) { 130 | return cookie.substring(name.length, cookie.length); 131 | } 132 | } 133 | 134 | return null; 135 | }, 136 | 137 | /** 138 | * Polyfill for older shopware versions 139 | */ 140 | checkGetCookiePreference: function() { 141 | if ($.isFunction($.getCookiePreference)) { 142 | return; 143 | } 144 | 145 | $.getCookiePreference = function() { 146 | return false; 147 | }; 148 | }, 149 | 150 | getCookieKey: function() { 151 | if (this.opts.googleTrackingLibrary === 'ga') { 152 | return '__utm' 153 | } 154 | 155 | return '_ga'; 156 | }, 157 | }); 158 | 159 | $(document).ready(function() { 160 | $('div[data-googleAnalytics="true"]').SwagGoogleAnalytics(); 161 | }); 162 | 163 | })(jQuery, window, document); 164 | -------------------------------------------------------------------------------- /Resources/frontend/js/universal_analytics.js: -------------------------------------------------------------------------------- 1 | (function($, window, document) { 2 | 3 | /** 4 | * @param { object } opts 5 | */ 6 | UniversalAnalytics = function(opts) { 7 | var me = this; 8 | 9 | me.opts = opts; 10 | 11 | me.analytics = me.createUniversalAnalytics(); 12 | me.startUniversalAnalytics(); 13 | me.sendUniversalECommerce(); 14 | 15 | me.analytics('send', 'pageview'); 16 | }; 17 | 18 | UniversalAnalytics.prototype = { 19 | createUniversalAnalytics: function() { 20 | (function(i, s, o, g, r, a, m) { 21 | i['GoogleAnalyticsObject'] = r; 22 | i[r] = i[r] || function() { 23 | (i[r].q = i[r].q || []).push(arguments) 24 | }, i[r].l = 1 * new Date(); 25 | a = s.createElement(o), 26 | m = s.getElementsByTagName(o)[0]; 27 | a.async = 1; 28 | a.src = g; 29 | m.parentNode.insertBefore(a, m) 30 | })(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'universalAnalytics'); 31 | 32 | return universalAnalytics; 33 | }, 34 | 35 | startUniversalAnalytics: function() { 36 | var me = this; 37 | 38 | me.analytics('create', me.opts.googleTrackingID, {'cookieDomain': 'none'}); 39 | if (me.opts.googleAnonymizeIp) { 40 | me.analytics('set', 'anonymizeIp', true); 41 | } 42 | 43 | if (me.opts.doNotTrack) { 44 | me.analytics('require', 'dnt'); 45 | 46 | $('') 47 | .append('body'); 48 | } 49 | }, 50 | 51 | sendUniversalECommerce: function() { 52 | var me = this; 53 | 54 | if (!me.opts.createEcommerceTransaction) { 55 | return; 56 | } 57 | 58 | me.analytics('require', 'ecommerce', 'ecommerce.js'); 59 | me.addUniversalTransaction(); 60 | }, 61 | 62 | addUniversalTransaction: function() { 63 | var me = this; 64 | 65 | if (!window.basketData.hasData) { 66 | return; 67 | } 68 | 69 | me.analytics('ecommerce:addTransaction', { 70 | id: me.opts.orderNumber, 71 | affiliation: me.opts.affiliation, 72 | revenue: me.opts.revenue, 73 | tax: me.opts.tax, 74 | shipping: me.opts.shipping, 75 | currency: me.opts.currency 76 | }); 77 | 78 | me.addUniversalECommerceItems(); 79 | 80 | me.analytics('ecommerce:send'); 81 | 82 | new GoogleAdds(me.opts); 83 | }, 84 | 85 | addUniversalECommerceItems: function() { 86 | var me = this; 87 | 88 | $.each(window.basketData.data, function(index, item) { 89 | me.analytics('ecommerce:addItem', item); 90 | }); 91 | }, 92 | }; 93 | 94 | })(jQuery, window, document); 95 | 96 | 97 | -------------------------------------------------------------------------------- /Resources/views/frontend/index/google_analytics.tpl: -------------------------------------------------------------------------------- 1 | {block name="google_analytics_data"} 2 | 25 | {/block} 26 | 27 | {block name="google_analytics_amount"} 28 | {if $sAmountWithTax} 29 | {assign var="sRealAmount" value=$sAmountWithTax|replace:",":"."} 30 | {else} 31 | {assign var="sRealAmount" value=$sAmount|replace:",":"."} 32 | {/if} 33 | {/block} 34 | 35 | {block name="google_analytics_opt_out"} 36 | {if $GoogleOptOutCookie} 37 | {include 'frontend/swag_google/optout.tpl'} 38 | {/if} 39 | {/block} 40 | 41 | {block name="google_analytics_container"} 42 |
72 |
73 | {/block} 74 | 75 | 76 | -------------------------------------------------------------------------------- /Resources/views/frontend/index/header.tpl: -------------------------------------------------------------------------------- 1 | {extends file="parent:frontend/index/header.tpl"} 2 | 3 | {block name="frontend_index_header_javascript_tracking"} 4 | {$smarty.block.parent} 5 | 6 | {if $GoogleIncludeInHead && $GoogleTrackingID} 7 | {include 'frontend/index/google_analytics.tpl'} 8 | {/if} 9 | {/block} 10 | -------------------------------------------------------------------------------- /Resources/views/frontend/index/index.tpl: -------------------------------------------------------------------------------- 1 | {extends file="parent:frontend/index/index.tpl"} 2 | 3 | {block name="frontend_index_header_javascript_jquery"} 4 | {$smarty.block.parent} 5 | 6 | {if !$GoogleIncludeInHead && $GoogleTrackingID} 7 | {include 'frontend/index/google_analytics.tpl'} 8 | {/if} 9 | {/block} 10 | -------------------------------------------------------------------------------- /Resources/views/frontend/swag_google/optout.tpl: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /SwagGoogle.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * For the full copyright and license information, please view the LICENSE 6 | * file that was distributed with this source code. 7 | * 8 | */ 9 | 10 | namespace SwagGoogle; 11 | 12 | use Shopware\Bundle\CookieBundle\CookieCollection; 13 | use Shopware\Bundle\CookieBundle\Structs\CookieGroupStruct; 14 | use Shopware\Bundle\CookieBundle\Structs\CookieStruct; 15 | use Shopware\Components\Plugin; 16 | use Shopware\Components\Plugin\Configuration\CachedReader; 17 | use Shopware\Components\Plugin\Configuration\ReaderInterface; 18 | use Shopware\Components\Plugin\Context\ActivateContext; 19 | use Shopware\Components\Plugin\Context\DeactivateContext; 20 | use Shopware\Components\Plugin\Context\InstallContext; 21 | use Shopware\Components\Plugin\Context\UninstallContext; 22 | use Shopware\Models\Shop\Shop; 23 | use Zend_Locale as Locale; 24 | 25 | class SwagGoogle extends Plugin 26 | { 27 | public function activate(ActivateContext $context) 28 | { 29 | $context->scheduleClearCache(InstallContext::CACHE_LIST_ALL); 30 | } 31 | 32 | public function deactivate(DeactivateContext $context) 33 | { 34 | $context->scheduleClearCache(InstallContext::CACHE_LIST_ALL); 35 | } 36 | 37 | public function uninstall(UninstallContext $context) 38 | { 39 | $context->scheduleClearCache(InstallContext::CACHE_LIST_ALL); 40 | } 41 | 42 | public static function getSubscribedEvents() 43 | { 44 | return [ 45 | 'Enlight_Controller_Action_PostDispatchSecure_Frontend' => 'onPostDispatch', 46 | 'Enlight_Controller_Action_PostDispatchSecure_Widgets' => 'onPostDispatch', 47 | 'CookieCollector_Collect_Cookies' => 'addGoogleAnalyticsCookie', 48 | ]; 49 | } 50 | 51 | public function onPostDispatch(\Enlight_Event_EventArgs $args): void 52 | { 53 | $controller = $args->get('subject'); 54 | 55 | /** @var \Enlight_Controller_Request_Request $request */ 56 | $request = $controller->Request(); 57 | 58 | /** @var \Enlight_View_Default $view */ 59 | $view = $controller->View(); 60 | 61 | $view->addTemplateDir($this->getPath() . '/Resources/views'); 62 | 63 | if ($request->isXmlHttpRequest()) { 64 | return; 65 | } 66 | 67 | $config = $this->getConfig(); 68 | if (!empty($config['conversion_code'])) { 69 | $this->handleConversionCode($view, $config); 70 | } 71 | 72 | if (!empty($config['tracking_code'])) { 73 | $this->handleTrackingCode($view, $config); 74 | } 75 | } 76 | 77 | /** 78 | * @return CookieCollection 79 | */ 80 | public function addGoogleAnalyticsCookie() 81 | { 82 | $config = $this->getConfig(); 83 | 84 | $collection = new CookieCollection(); 85 | $trackingLib = isset($config['trackingLib']) ? $config['trackingLib'] : 'ua'; 86 | $collection->add($this->getCookieStruct($trackingLib)); 87 | 88 | return $collection; 89 | } 90 | 91 | /** 92 | * @param string $usedLibraryKey 93 | * 94 | * @return CookieStruct 95 | */ 96 | private function getCookieStruct($usedLibraryKey) 97 | { 98 | if ($usedLibraryKey === 'ga') { 99 | return new CookieStruct( 100 | '__utm', 101 | '/^__utm.*$/', 102 | 'Google Analytics', 103 | CookieGroupStruct::STATISTICS 104 | ); 105 | } 106 | 107 | return new CookieStruct( 108 | '_ga', 109 | '/(^_g(a|at|id)$)|AMP_TOKEN|^_gac_.*$/', 110 | 'Google Analytics', 111 | CookieGroupStruct::STATISTICS 112 | ); 113 | } 114 | 115 | /** 116 | * @return array 117 | */ 118 | private function getConfig(): array 119 | { 120 | $shop = $this->container->get('shop'); 121 | if (!$shop instanceof Shop) { 122 | throw new \RuntimeException('Shop not found'); 123 | } 124 | 125 | $config = $this->container->get(CachedReader::class); 126 | if (!$config instanceof ReaderInterface) { 127 | throw new \RuntimeException('CachedConfigReader not found'); 128 | } 129 | 130 | return $config->getByPluginName($this->getName(), $shop->getId()); 131 | } 132 | 133 | /** 134 | * @param array $config 135 | */ 136 | private function handleConversionCode(\Enlight_View_Default $view, array $config): void 137 | { 138 | $locale = $this->container->get('locale'); 139 | if (!$locale instanceof Locale) { 140 | throw new \RuntimeException('Locale not found'); 141 | } 142 | 143 | $view->assign('GoogleConversionID', trim($config['conversion_code'])); 144 | $view->assign('GoogleConversionLabel', trim($config['conversion_label'])); 145 | $view->assign('GoogleConversionLanguage', $locale->getLanguage()); 146 | $view->assign('GoogleIncludeInHead', $config['include_header']); 147 | } 148 | 149 | /** 150 | * @param array $config 151 | */ 152 | private function handleTrackingCode(\Enlight_View_Default $view, array $config): void 153 | { 154 | $view->assign('GoogleTrackingID', trim($config['tracking_code'])); 155 | $view->assign('GoogleAnonymizeIp', $config['anonymize_ip']); 156 | $view->assign('GoogleOptOutCookie', $config['include_opt_out_cookie']); 157 | $view->assign('GoogleTrackingLibrary', $config['trackingLib']); 158 | $view->assign('GoogleIncludeInHead', $config['include_header']); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shopware-labs/swag-google", 3 | "type": "shopware-plugin", 4 | "description": "Plugin that adds google analytics features to Shopware.", 5 | "license": "MIT", 6 | "extra": { 7 | "installer-name": "SwagGoogle" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "69bea4222655a5feb9e8af1c939f47c1", 8 | "packages": [], 9 | "packages-dev": [ 10 | { 11 | "name": "shopware/plugin-dev-tools", 12 | "version": "dev-master", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/shopwareLabs/plugin-dev-tools.git", 16 | "reference": "78fef1ba3a52fa97c3f00940236c9e8455e012f1" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/shopwareLabs/plugin-dev-tools/zipball/78fef1ba3a52fa97c3f00940236c9e8455e012f1", 21 | "reference": "78fef1ba3a52fa97c3f00940236c9e8455e012f1", 22 | "shasum": "" 23 | }, 24 | "type": "application", 25 | "authors": [ 26 | { 27 | "name": "Simon Bäumer", 28 | "email": "s.baeumer@shopware.com" 29 | } 30 | ], 31 | "description": "Tools for plugin development.", 32 | "support": { 33 | "source": "https://github.com/shopwareLabs/plugin-dev-tools/tree/master", 34 | "issues": "https://github.com/shopwareLabs/plugin-dev-tools/issues" 35 | }, 36 | "time": "2017-12-20T07:47:21+00:00" 37 | } 38 | ], 39 | "aliases": [], 40 | "minimum-stability": "stable", 41 | "stability-flags": { 42 | "shopware/plugin-dev-tools": 20 43 | }, 44 | "prefer-stable": false, 45 | "prefer-lowest": false, 46 | "platform": [], 47 | "platform-dev": [] 48 | } 49 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 8 3 | reportUnmatchedIgnoredErrors: true 4 | bootstrapFiles: 5 | - ../../../tests/phpstan-dba-bootstrap.php 6 | excludePaths: 7 | - .githooks 8 | - vendor 9 | -------------------------------------------------------------------------------- /plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shopware5/SwagGoogle/1622474f4dc5473cb04fd18d1e459533ff260556/plugin.png -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 3.1.7 8 | (c) by Shopware AG 9 | MIT 10 | http://store.shopware.com 11 | Shopware AG 12 | 13 | 14 | 15 | PT-10770 - Composer 2 Kompatibilität; 16 | PT-10770 - Composer 2 compatibility; 17 | 18 | 19 | 20 | PT-12131 - Shopware 5.7 und PHP8 Kompatibilität; 21 | PT-12131 - Shopware 5.7 and PHP8 compatibility; 22 | 23 | 24 | 25 | PT-11327 - Installationsprozess optimiert; 26 | PT-11327 - Optimizes installation process; 27 | 28 | 29 | 30 | PT-11243 - Behebt Fehler im Conversion Tracking; 31 | PT-11243 - Fix error in conversion tracking; 32 | 33 | 34 | 35 | PT-11085 - Kompatibilität mit Cookie Consent Tool; 36 | PT-11085 - Compatibility with cookie consent tool; 37 | 38 | 39 | 40 | 41 | PT-10947 - Verhindert Leerzeichen am Ende der Google ID; 42 | PT-11004 - Behebt Fehler im Javascript; 43 | 44 | 45 | PT-10947 - Prevents spaces at the end of the Google ID; 46 | PT-11004 - Fixes javascript error; 47 | 48 | 49 | 50 | 51 | PT-10877 - Fehler mit falscher Conversion auf jeder Seite behoben. 52 | PT-10877 - Fixed wrong conversion on each page. 53 | 54 | 55 | 56 | PT-10866 - Problem beim Übertragen vom Warenwert und Artikelnummer behoben; 57 | PT-10866 - Fixed problem transferring product value and product order number; 58 | 59 | 60 | 61 | PT-10785 - Berücksichtigt nun die Cookie-Modus Einstellungen; 62 | PT-10785 - Considers now the cookie mode settings; 63 | 64 | 65 | 66 | PT-9940 - Verhindert Smarty-Security Fehler; 67 | PT-9940 - Prevents smarty security errors; 68 | 69 | 70 | 71 | PT-9709 - Verhindert Smarty-Security Fehler; 72 | PT-9709 - Prevents smarty security errors; 73 | 74 | 75 | 76 | PT-9556 - Shopware 5.5 Kompatibilität und Migration auf das Shopware 5.2 Plugin System; 77 | PT-9556 - Shopware 5.5 compatibility and migration to the Shopware 5.2 plugin system; 78 | 79 | 80 | 81 | PT-8810 - Shopware 5.4 Kompatibilität; 82 | PT-8810 - Shopware 5.4 compatibility; 83 | 84 | 85 | 86 | PT-8543 - Bugfix: Die Template Ordner werden früher registriert um den SmartySecurity Fehler zu verhindern; 87 | PT-8543 - Bugfix: The template folders are registered earlier to prevent the SmartySecurity error; 88 | 89 | 90 | 91 | PT-7264 - Bugfix: GA-Tracking über ein Cookie unterbinden; 92 | PT-7264 - Bugfix: GA-Tracking über ein Cookie unterbinden; 93 | 94 | 95 | 96 | PT-7264 - Neue Funktion: GA-Tracking über ein Cookie unterbinden. 97 | PT-7264 - New feature: GA-Tracking can be disabled via Cookie. 98 | 99 | 100 | 101 | PT-6248 - Shopware 5.2 Kompatibilität und Entfernung des Emotion Templates 102 | PT-6248 - Shopware 5.2 compatibility and remove of the emotion template 103 | 104 | 105 | 106 | PT-3836 - Bugfix: Möglichkeit Tracking Code an verschiedenen Stellen einzubinden; 107 | PT-3836 - Bugfix: Possibility to add tracking code at different places; 108 | 109 | 110 | 111 | PT-3637 - Bugfix: Übergabe der Bestellnummer; 112 | PT-3637 - Bugfix: Tranfer of the order number; 113 | 114 | 115 | 116 | PT-2991 - Bugfix: Adwords Conversions wurden nicht getrackt; 117 | PT-2991 - Bugfix: Adwords Conversions were not tracked; 118 | 119 | 120 | 121 | Shopware 5 Kompatibilität 122 | SW5 compatibility 123 | 124 | 125 | 126 | Als Plugin ausgelagert; 127 | Extracted as Plugin; 128 | 129 | 130 | Mit diesem Plugin haben Sie alle Aktivitäten Ihres Shops immer im Blick. Die "Google Integration" ermöglicht es Ihnen, Google Analytics, Google Universal Analytics und das Conversiontracking mit nur einem Plugin einzubinden. 131 | With this plugin, you have all the activites of your store in view. "Google Integration" allows you, to take advantage of Google Analytics, google universal analytics as well as the conversion tracking in your shop. 132 | 133 | 134 | --------------------------------------------------------------------------------