├── .craftplugin ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── boilerplate ├── _core.scss ├── bin │ ├── compile.js │ ├── dev-server.js │ └── watch.js ├── config │ ├── .babelrc │ ├── eslint.js │ ├── project.config.js │ ├── stylelint.js │ └── webpack.config.js ├── core │ └── scss │ │ ├── _settings.scss │ │ └── components │ │ ├── _buttons.scss │ │ ├── _forms.scss │ │ ├── _general.scss │ │ └── _typography.scss └── server │ └── main.js ├── composer.json ├── composer.lock ├── package-lock.json ├── package.json ├── resources └── img │ └── screenshot.png ├── src ├── FathomAnalytics.php ├── assetbundles │ ├── fathomanalytics │ │ ├── FathomAnalyticsAsset.php │ │ ├── dist │ │ │ ├── css │ │ │ │ ├── fathomanalytics.css │ │ │ │ └── fathomanalytics.min.css │ │ │ ├── img │ │ │ │ ├── FathomAnalytics-icon.svg │ │ │ │ └── Widget.svg │ │ │ └── js │ │ │ │ ├── fathomanalytics.js │ │ │ │ └── fathomanalytics.min.js │ │ └── src │ │ │ ├── js │ │ │ └── fathomanalytics.js │ │ │ └── scss │ │ │ └── fathomanalytics.scss │ ├── fathomsettings │ │ ├── SettingsAsset.php │ │ ├── dist │ │ │ ├── css │ │ │ │ ├── fathomsettings.css │ │ │ │ └── fathomsettings.min.css │ │ │ └── js │ │ │ │ ├── fathomsettings.js │ │ │ │ └── fathomsettings.min.js │ │ └── src │ │ │ ├── js │ │ │ └── fathomsettings.js │ │ │ └── scss │ │ │ └── fathomsettings.scss │ ├── manifest.json │ ├── reportwidget │ │ ├── ReportWidgetAsset.php │ │ ├── dist │ │ │ ├── css │ │ │ │ ├── reportwidget.css │ │ │ │ └── reportwidget.min.css │ │ │ └── js │ │ │ │ ├── reportwidget.js │ │ │ │ └── reportwidget.min.js │ │ └── src │ │ │ ├── js │ │ │ └── reportwidget.js │ │ │ └── scss │ │ │ └── reportwidget.scss │ ├── statisticswidget │ │ ├── StatisticsWidgetAsset.php │ │ ├── dist │ │ │ ├── css │ │ │ │ ├── statisticswidget.css │ │ │ │ └── statisticswidget.min.css │ │ │ └── js │ │ │ │ ├── statisticswidget.js │ │ │ │ └── statisticswidget.min.js │ │ └── src │ │ │ ├── js │ │ │ └── statisticswidget.js │ │ │ └── scss │ │ │ └── statisticswidget.scss │ └── toppageswidget │ │ ├── TopPagesWidgetAsset.php │ │ ├── dist │ │ ├── css │ │ │ ├── toppageswidget.css │ │ │ └── toppageswidget.min.css │ │ └── js │ │ │ ├── toppageswidget.js │ │ │ └── toppageswidget.min.js │ │ └── src │ │ ├── js │ │ └── toppageswidget.js │ │ └── scss │ │ └── toppageswidget.scss ├── config.php ├── controllers │ ├── ReportsController.php │ └── SettingsController.php ├── icon-mask.svg ├── icon.svg ├── models │ └── Settings.php ├── services │ ├── Reports.php │ └── Tags.php ├── templates │ ├── _components │ │ └── widgets │ │ │ ├── report │ │ │ ├── body.twig │ │ │ └── settings.twig │ │ │ ├── statistics │ │ │ ├── body.twig │ │ │ └── settings.twig │ │ │ └── top-pages │ │ │ ├── body.twig │ │ │ └── settings.twig │ └── settings.twig ├── translations │ ├── en │ │ └── fathom-analytics.php │ └── nl │ │ └── fathom-analytics.php ├── variables │ └── FathomAnalyticsVariable.php └── widgets │ ├── Report.php │ ├── Statistics.php │ └── TopPages.php └── tailwind.config.js /.craftplugin: -------------------------------------------------------------------------------- 1 | {"pluginName":"Fathom Analytics","pluginDescription":"Statistics and chart widgets for Fathom Lite analytics.","pluginVersion":"1.1.4","pluginAuthorName":"Sten Van den Bergh","pluginVendorName":"stenvdb","pluginAuthorUrl":"https://stenvdb.be","pluginAuthorGithub":"stenvdb","codeComments":"yes","pluginComponents":["services","settings","widgets"],"consolecommandName":"","controllerName":"","cpsectionName":"","elementName":"","fieldName":"","modelName":"","purchasableName":"","recordName":"","serviceName":"Reports","taskName":"","utilityName":"","widgetName":"Fathom Statistics,Fathom Report,Fathom Top Pages","apiVersion":"api_version_3_0"} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # CRAFT ENVIRONMENT 2 | .env.php 3 | .env.sh 4 | .env 5 | 6 | # COMPOSER 7 | /vendor 8 | 9 | # BUILD FILES 10 | /bower_components/* 11 | /node_modules/* 12 | /build/* 13 | /yarn-error.log 14 | 15 | # MISC FILES 16 | .cache 17 | .DS_Store 18 | .idea 19 | .project 20 | .settings 21 | *.esproj 22 | *.sublime-workspace 23 | *.sublime-project 24 | *.tmproj 25 | *.tmproject 26 | .vscode/* 27 | !.vscode/settings.json 28 | !.vscode/tasks.json 29 | !.vscode/launch.json 30 | !.vscode/extensions.json 31 | config.codekit3 32 | prepros-6.config 33 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Fathom Analytics Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | ## 1.1.4 - 2020-10-19 8 | ### Fixed 9 | - Put back the environment files parsing in the settings model 10 | 11 | ## 1.1.3 - 2020-10-08 12 | ### Fixed 13 | - Add support for localized settings 14 | - Replace deprecated ucword filter 15 | 16 | 17 | ## 1.1.2 - 2020-05-28 18 | ### Fixed 19 | - Catch different kind of baseUri strings properly 20 | 21 | ## 1.1.1 - 2020-05-18 22 | ### Fixed 23 | - Cleanup baseUri in tracking script 24 | 25 | ## 1.1.0 - 2020-05-18 26 | ### Added 27 | - Editable tracking code snippet in plugin settings 28 | - This plugin can nog automatically inject the tracking snippet into your site (disabled by default) 29 | 30 | ## 1.0.1 - 2020-04-10 31 | ### Fixed 32 | - Case sensitive namespace fixed 33 | 34 | ## 1.0.0 - 2020-01-30 35 | ### Added 36 | - Initial release 37 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Sten Van den Bergh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fathom Analytics for Craft CMS 2 | 3 | Statistics and chart widgets for Fathom Lite. 4 | 5 |  6 | 7 | ## A word about privacy & Fathom 8 | 9 | If you care about privacy and website-analytics, I encourage you to [read some of my toughts about it](https://stenvdb.be/articles/problem-with-website-analytics), explaining why I made the move from Google Analytics to Fathom. 10 | 11 | **TL;DR** 12 | 13 | [Fathom](https://usefathom.com/) is a Google Analytics alternative. They offer [Fathom Lite](https://github.com/usefathom/fathom), which is free and you can self-host. Thereby no data is being shared with third party services. 14 | 15 | Although Fathom Lite **does not collect any personally identifiable information** (and is GDPR compliant), it currently still uses a cookie (and is not PECR compliant without a cookie notice). If you need a cookie-free solution, I suggest switching to Fathom Pro. Fathom Lite might become cookie-free in a future update though 🤞. 16 | 17 | ## Requirements 18 | 19 | This plugin requires Craft CMS 3.0.0-beta.23 or later and [Fathom Lite](https://github.com/usefathom/fathom) 20 | 21 | ## Installation 22 | 23 | To install the plugin, follow these instructions. 24 | 25 | 1. Open your terminal and go to your Craft project: 26 | 27 | cd /path/to/project 28 | 29 | 2. Then tell Composer to load the plugin: 30 | 31 | composer require stenvdb/craft-fathom-analytics 32 | 33 | 3. In the Control Panel, go to Settings → Plugins and click the “Install” button for Fathom Analytics. 34 | 35 | ## Fathom Analytics Overview 36 | 37 | This plugin **only works with Fathom Lite**, the self-hosted open source version. Reasons being Fathom does not have an official documented API. Once Fathom releases a documented API ([which might be in the works](https://trello.com/c/wu4WMy4U/16-api)), I'll consider providing support for Fathom Pro. In the meantime, this plugin uses Fathom's already great internal API. 38 | 39 | ## Configuring Fathom Analytics 40 | 41 | This plugin obviously requires Fathom running on one of your servers. Once it is up and running (which is [super easy](https://github.com/usefathom/fathom/blob/master/docs/Installation%20instructions.md)), configure Fathom Analytic's settings in a `config/fathom-anaytics.php` file (multi-site config is supported). See an example below: 42 | 43 | ``` 44 | array( 48 | // The domain name where Fathom is hosted. This is also the URL where the tracker code is pointed to. 49 | 'baseUri' => '$FATHOM_BASE_URI', 50 | 51 | // The tracking ID of this site. You can find the ID in your tracking code snippet, e.g.: ABCDE 52 | 'trackingId' => '$FATHOM_TRACKING_ID', 53 | 54 | // You can also use multi-site config values, e.g. 55 | // 'trackingId' => [ 56 | // 'default' => '$FATHOM_TRACKING_ID', 57 | // '<...>' => '<...>' 58 | // ] 59 | 60 | 'username' => '$FATHOM_USERNAME', 61 | 62 | 'password' => '$FATHOM_PASSWORD', 63 | 64 | // Automatically inject a tracking script in your site 65 | 'injectTracking' => false, 66 | ), 67 | 'production' => [ 68 | 'injectTracking' => true, 69 | ], 70 | ); 71 | ``` 72 | 73 | ## Tracking Code Snippet 74 | 75 | When enabling `injectTracking` setting, the tracking snippet (editable in the plugin settings) will automatically be injected into the head section of your site. 76 | This is disabled by default. 77 | 78 | Alternatively you can use the following code snippet to force the code injection in your template: 79 | 80 | ``` 81 | {% do craft.fathomAnalytics.inject() %} 82 | ``` 83 | 84 | ## Fathom Analytics Roadmap 85 | 86 | * Support Fathom Pro 87 | * Entry tracking report field (on a per entry basis) 88 | * Have an idea? [Let me know](https://stenvdb.be/contact) 89 | -------------------------------------------------------------------------------- /boilerplate/_core.scss: -------------------------------------------------------------------------------- 1 | @import "core/scss/settings"; 2 | @import "core/scss/components/general"; 3 | @import "core/scss/components/typography"; 4 | @import "core/scss/components/buttons"; 5 | @import "core/scss/components/forms"; 6 | -------------------------------------------------------------------------------- /boilerplate/bin/compile.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | import webpack from 'webpack'; 3 | import Debug from 'debug'; 4 | /* eslint-enable import/no-extraneous-dependencies */ 5 | import webpackConfig from '../config/webpack.config'; 6 | import project from '../config/project.config'; 7 | 8 | const debug = Debug('webpack'); 9 | 10 | // Wrapper around webpack to promisify its compiler and supply friendly logging 11 | const webpackCompiler = () => new Promise((resolve, reject) => { // eslint-disable-line compat/compat 12 | const compiler = webpack(webpackConfig); 13 | 14 | compiler.run((err, stats) => { 15 | if (err) { 16 | debug('Webpack compiler encountered a fatal error.', err); 17 | return reject(err); 18 | } 19 | 20 | const jsonStats = stats.toJson(); 21 | debug('Webpack compile completed.'); 22 | debug(stats.toString(project.compilerStats)); 23 | 24 | if (jsonStats.errors.length > 0) { 25 | debug('Webpack compiler encountered errors.'); 26 | debug(jsonStats.errors.join('\n')); 27 | return reject(new Error('Webpack compiler encountered errors')); 28 | } 29 | if (jsonStats.warnings.length > 0) { 30 | debug('Webpack compiler encountered warnings.'); 31 | debug(jsonStats.warnings.join('\n')); 32 | } else { 33 | debug('No errors or warnings encountered.'); 34 | } 35 | return resolve(jsonStats); 36 | }); 37 | }); 38 | 39 | const compile = () => { 40 | debug('Starting compiler.'); 41 | return Promise.resolve() // eslint-disable-line compat/compat 42 | .then(() => webpackCompiler(webpackConfig)) 43 | .then((stats) => { 44 | if (stats.warnings.length && project.compilerFailOnWarning) { 45 | throw new Error('Config set to fail on warning, exiting with status code "1".'); 46 | } 47 | // debug('Copying static assets to dist folder.') 48 | // fs.copySync(project.paths.public(), project.paths.dist()) 49 | }) 50 | .then(() => { 51 | debug('Compilation completed successfully.'); 52 | }) 53 | .catch((err) => { 54 | debug('Compiler encountered an error.', err); 55 | process.exit(1); 56 | }); 57 | }; 58 | 59 | compile(); 60 | -------------------------------------------------------------------------------- /boilerplate/bin/dev-server.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | import Debug from 'debug'; 3 | /* eslint-enable import/no-extraneous-dependencies */ 4 | import project from '../config/project.config'; 5 | import server from '../server/main'; 6 | 7 | const debug = Debug('webpack'); 8 | 9 | debug('Just before listening to server'); 10 | server.listen(project.serverPort); 11 | debug(`🚎 Server is now running at http://localhost:${project.serverPort}.`); 12 | -------------------------------------------------------------------------------- /boilerplate/bin/watch.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | import webpack from 'webpack'; 3 | import Debug from 'debug'; 4 | /* eslint-enable import/no-extraneous-dependencies */ 5 | import webpackConfig from '../config/webpack.config'; 6 | import project from '../config/project.config'; 7 | 8 | const debug = Debug('webpack'); 9 | 10 | debug('Starting watch mode 👀'); 11 | 12 | webpack(webpackConfig).watch(100, (err, stats) => { 13 | if (err) { 14 | debug('Webpack compiler encountered a fatal error.', err); 15 | } else { 16 | debug(stats.toString(project.compilerStats)); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /boilerplate/config/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ], 5 | "plugins": [ 6 | "@babel/plugin-transform-runtime", 7 | "@babel/plugin-syntax-dynamic-import" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /boilerplate/config/eslint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: 'babel-eslint', 3 | parserOptions: { 4 | allowImportExportEverywhere: true, 5 | }, 6 | env: { 7 | es6: true, 8 | browser: true 9 | }, 10 | extends: 'airbnb', 11 | plugins: ['compat'], 12 | rules: { 13 | 'comma-dangle': 0, 14 | 'no-unused-vars': 1, 15 | 'no-unused-expressions': ['error', { 16 | allowShortCircuit: true, 17 | allowTernary: true 18 | }], 19 | 'no-new': 0, 20 | 'no-param-reassign': [2, { props: false }], 21 | 'no-restricted-syntax': 0, 22 | 'guard-for-in': 0, 23 | 'class-methods-use-this': 0, 24 | 'no-console': [0], 25 | 'compat/compat': 2, 26 | 'max-len': ['error', 100, 2, { 27 | ignoreUrls: true, 28 | ignoreComments: true, 29 | ignoreRegExpLiterals: true, 30 | ignoreStrings: true, 31 | ignoreTemplateLiterals: true, 32 | }], 33 | }, 34 | globals: { 35 | _: false, 36 | Power1: false, 37 | Power2: false, 38 | Power3: false, 39 | Power4: false, 40 | Expo: false, 41 | Circ: false, 42 | CraftEntry: false, 43 | grecaptcha: false, 44 | google: false, 45 | Garnish: false, 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /boilerplate/config/project.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import settings from '../../package.json'; 3 | 4 | const { 5 | dirClient, 6 | dirPublic 7 | } = settings.boilerplate || {}; 8 | 9 | const config = { 10 | pathBase: path.resolve(__dirname, '../..'), 11 | dirClient: dirClient, 12 | dirPublic: dirPublic, 13 | 14 | serverPort: 3000, 15 | 16 | compilerFailOnWarning: false, 17 | compilerQuiet: false, 18 | compilerStats: { 19 | assets: true, 20 | colors: true, 21 | children: false, 22 | chunks: false, 23 | chunkModules: false, 24 | chunkOrigins: false, 25 | hash: false, 26 | modules: false, 27 | timings: false, 28 | versian: false, 29 | excludeAssets: [/fonts/, /img/] 30 | } 31 | }; 32 | 33 | config.globals = { 34 | DEV: process.env.NODE_ENV === 'development', 35 | PROD: process.env.NODE_ENV === 'production', 36 | NODE_ENV: process.env.NODE_ENV, 37 | DEV_SERVER: process.env.DEV_SERVER === '1' 38 | }; 39 | 40 | function base(...args) { 41 | return path.resolve(...[config.pathBase].concat([].slice.call(args))); 42 | } 43 | 44 | config.paths = { 45 | base, 46 | client: base.bind(null, config.dirClient), 47 | public: base.bind(null, config.dirPublic), 48 | }; 49 | 50 | export default config; 51 | -------------------------------------------------------------------------------- /boilerplate/config/stylelint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'stylelint-config-standard', 3 | rules: { 4 | 'selector-list-comma-newline-after': 'always-multi-line', 5 | 'at-rule-no-unknown': null, 6 | 'at-rule-empty-line-before': null, 7 | 'declaration-empty-line-before': 'never', 8 | 'no-descending-specificity': null 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /boilerplate/config/webpack.config.js: -------------------------------------------------------------------------------- 1 | import Debug from 'debug'; 2 | import webpack from 'webpack'; 3 | import MiniCssExtractPlugin from 'mini-css-extract-plugin'; 4 | import purgecss from '@fullhuman/postcss-purgecss'; 5 | import ManifestPlugin from 'webpack-manifest-plugin'; 6 | import CleanWebpackPlugin from 'clean-webpack-plugin'; 7 | import SpriteLoaderPlugin from 'svg-sprite-loader/plugin'; 8 | import StyleLintPlugin from 'stylelint-webpack-plugin'; 9 | import autoprefixer from 'autoprefixer'; 10 | import cssnano from 'cssnano'; 11 | import tailwindcss from 'tailwindcss'; 12 | import normalize from 'postcss-normalize'; 13 | import project from './project.config'; 14 | 15 | const { DEV, PROD, DEV_SERVER } = project.globals; 16 | const debug = Debug('webpack'); 17 | 18 | // Hide irritating deprecation warnings from Webpack loaders, see https://github.com/webpack/loader-utils/issues/56#issuecomment-281967053 19 | process.noDeprecation = true; 20 | 21 | debug('Creating Webpack configuration.'); 22 | 23 | class TailwindExtractor { 24 | static extract(content) { 25 | return content.match(/[A-Za-z0-9-_:\/]+/g) || []; // eslint-disable-line 26 | } 27 | } 28 | 29 | /* 30 | * Create a custom little plugin to add the craft templates folder 31 | * when saving any template, it will retrigger a compile 32 | */ 33 | const WatchExternalFilesPlugin = () => { 34 | WatchExternalFilesPlugin.prototype.apply = (compiler) => { 35 | compiler.plugin('after-compile', (compilation, callback) => { 36 | compilation.contextDependencies.add(project.paths.base('./templates')); 37 | callback(); 38 | }); 39 | }; 40 | }; 41 | 42 | const svgoLoader = { 43 | loader: 'svgo-loader', 44 | options: { 45 | plugins: [ 46 | { 47 | cleanupIDs: { 48 | remove: false, 49 | prefix: Math.random().toString(36).substring(7), 50 | minify: true, 51 | } 52 | }, 53 | { removeTitle: true }, 54 | { removeDimensions: true } 55 | ] 56 | } 57 | }; 58 | 59 | const postcssPlugins = [ 60 | normalize(), 61 | tailwindcss(), 62 | autoprefixer({ 63 | grid: true 64 | }) 65 | ]; 66 | 67 | if (PROD) { 68 | postcssPlugins.push(purgecss({ 69 | content: ['src/templates/_components/widgets/**/*.{html,twig}'], 70 | extractors: [ 71 | { 72 | extractor: TailwindExtractor, 73 | extensions: ['twig', 'html'] 74 | } 75 | ] 76 | })); 77 | postcssPlugins.push(cssnano({ 78 | preset: 'default' 79 | })); 80 | } 81 | 82 | const webpackConfig = { 83 | mode: DEV ? 'development' : 'production', 84 | devtool: 'eval-source-map', 85 | context: project.paths.client(), 86 | entry: { 87 | fathomanalytics: ['./fathomanalytics/src/js/fathomanalytics.js'], 88 | statisticswidget: ['./statisticswidget/src/js/statisticswidget.js'], 89 | reportwidget: ['./reportwidget/src/js/reportwidget.js'], 90 | toppageswidget: ['./toppageswidget/src/js/toppageswidget.js'], 91 | fathomsettings: ['./fathomsettings/src/js/fathomsettings.js'] 92 | }, 93 | output: { 94 | path: project.paths.public(''), 95 | publicPath: '/', 96 | filename: (DEV) ? '[name]/dist/js/[name].js' : '[name]/dist/js/[name].min.js' 97 | }, 98 | resolve: { 99 | modules: [ 100 | project.paths.client(), 101 | 'node_modules' 102 | ], 103 | extensions: ['*', '.js', '.jsx', '.json'], 104 | alias: { 105 | 'pixi.js': 'pixi.js/dist/pixi.min.js', 106 | } 107 | }, 108 | module: { 109 | rules: [ 110 | { 111 | enforce: 'pre', 112 | test: /\.js$/, 113 | exclude: /node_modules/, 114 | loader: 'eslint-loader' 115 | }, 116 | { 117 | test: /\.js$/, 118 | include: project.paths.client('js'), 119 | use: [{ 120 | loader: 'babel-loader' 121 | }] 122 | }, 123 | { 124 | test: /\.scss$/, 125 | use: [ 126 | { 127 | loader: MiniCssExtractPlugin.loader 128 | }, 129 | { 130 | loader: 'css-loader', 131 | options: { 132 | sourceMap: true 133 | } 134 | }, 135 | { 136 | loader: 'postcss-loader', 137 | options: { 138 | sourceMap: true, 139 | plugins: postcssPlugins 140 | } 141 | }, 142 | { 143 | loader: 'sass-loader', 144 | options: { 145 | sourceMap: true, 146 | includePaths: [ 147 | project.paths.client('scss') 148 | ], 149 | }, 150 | }, 151 | ] 152 | }, 153 | { 154 | /* 155 | * This is for image files 156 | * We don't want to load these files or inline them, just leave the relative link as is 157 | * Simply run all images through an optimizer and copy them 158 | */ 159 | test: /\.(png|jpg)/, 160 | use: [{ 161 | loader: 'file-loader', 162 | options: { 163 | emitFile: true, 164 | name: 'img/[name].[ext]', 165 | } 166 | }] 167 | }, 168 | { 169 | test: /\.svg$/, 170 | include: /inline-svg/, 171 | exclude: /sprite-svg/, 172 | use: [ 173 | { 174 | loader: 'svg-url-loader', 175 | }, 176 | svgoLoader 177 | ] 178 | }, 179 | { 180 | test: /\.svg$/, 181 | exclude: /inline-svg/, 182 | use: [ 183 | { 184 | loader: 'svg-sprite-loader', 185 | options: { 186 | extract: true, 187 | spriteFilename: 'img/sprite-svg/sprite.svg' 188 | } 189 | }, 190 | svgoLoader 191 | ] 192 | }, 193 | { 194 | // Fonts 195 | test: /\.(woff|woff2|eot|ttf)$/, 196 | loader: 'url-loader', 197 | options: { 198 | name: 'fonts/[name].[ext]', 199 | limit: 10000 200 | }, 201 | }, 202 | { 203 | test: /\.(html|twig)$/, 204 | loader: 'raw-loader' 205 | } 206 | ] 207 | }, 208 | plugins: [ 209 | new webpack.DefinePlugin({ 210 | 'process.env': JSON.stringify(project.globals) 211 | }), 212 | new webpack.optimize.ModuleConcatenationPlugin(), 213 | new SpriteLoaderPlugin(), 214 | new ManifestPlugin({ 215 | writeToFileEmit: true, 216 | filter: ({ isAsset }) => !isAsset 217 | }), 218 | new CleanWebpackPlugin([ 219 | 'js', 220 | 'css', 221 | 'fonts' 222 | ], { 223 | root: project.paths.public('assets'), 224 | watch: false, 225 | verbose: false, 226 | }), 227 | new webpack.HashedModuleIdsPlugin(), 228 | new webpack.NamedModulesPlugin(), 229 | new MiniCssExtractPlugin({ 230 | filename: (DEV) ? '[name]/dist/css/[name].css' : '[name]/dist/css/[name].min.css' 231 | }), 232 | new StyleLintPlugin(), 233 | ] 234 | }; 235 | 236 | if (DEV_SERVER) { 237 | webpackConfig.plugins.push(new WatchExternalFilesPlugin()); 238 | } 239 | 240 | module.exports = webpackConfig; 241 | -------------------------------------------------------------------------------- /boilerplate/core/scss/_settings.scss: -------------------------------------------------------------------------------- 1 | $buttonHeights: ( 2 | small: 26px, 3 | base: 60px 4 | ) !default; 5 | 6 | // In pixels -> will convert to rem 7 | // (strip-units($sizeValue) / 16) + rem 8 | $headingSizes: ( 9 | h1: 40px, 10 | h2: 32px, 11 | h3: 28px, 12 | h4: 24px, 13 | h5: 20px, 14 | h6: 16px, 15 | ) !default; 16 | -------------------------------------------------------------------------------- /boilerplate/core/scss/components/_buttons.scss: -------------------------------------------------------------------------------- 1 | .button { 2 | $self: &; 3 | @apply relative inline-flex justify-center items-center cursor-pointer px-4; 4 | transition: all 0.15s ease-in-out; 5 | 6 | // IE Bug -> https://github.com/philipwalton/flexbugs/issues/231#issuecomment-362790042 7 | &::after { 8 | content: ''; 9 | min-height: inherit; 10 | font-size: 0; 11 | } 12 | 13 | @each $class, $height in $buttonHeights { 14 | &--#{$class} { 15 | min-height: $height; 16 | min-width: $height; 17 | } 18 | } 19 | 20 | &--has-dropdown { 21 | @apply relative; 22 | 23 | &:hover div.hidden { 24 | display: block; 25 | @apply absolute w-full inset-x-0; 26 | top: 100%; 27 | } 28 | } 29 | } 30 | 31 | .dropdown { 32 | &:hover { 33 | div.hidden { 34 | display: block; 35 | } 36 | } 37 | 38 | div.hidden { 39 | top: 100%; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /boilerplate/core/scss/components/_forms.scss: -------------------------------------------------------------------------------- 1 | // Some small input improvements 2 | input::-webkit-input-placeholder, textarea::-webkit-input-placeholder { @apply text-black opacity-100; } 3 | input:-ms-input-placeholder, textarea:-ms-input-placeholder { @apply text-black opacity-100; } 4 | input::-moz-placeholder, textarea::-moz-placeholder { @apply text-black opacity-100; } 5 | 6 | input, textarea { 7 | .error & { 8 | @apply border-red-500 text-red-500; 9 | &::-webkit-input-placeholder { @apply text-red-500; } 10 | } 11 | } 12 | 13 | // Custom radio buttons and chekcboxes 14 | input { 15 | &[type="radio"] { 16 | & + label::after { 17 | content: ''; 18 | @apply w-2 h-2 absolute rounded-full; 19 | top: 50%; 20 | left: 50%; 21 | transform: translateX(-50%) translateY(-50%); 22 | } 23 | &:checked + label { &::after { background: theme('colors.black'); } } 24 | } 25 | 26 | &[type="checkbox"] { 27 | &:checked + label::after { 28 | content: ''; 29 | @apply absolute; 30 | top: 3px; 31 | left: 3px; 32 | width: 15px; 33 | height: 15px; 34 | background: transparent url('../img/inline-svg/checkbox-mark.svg') no-repeat center center / 15px; 35 | } 36 | } 37 | 38 | &[type="radio"], &[type="checkbox"] { 39 | + label { 40 | .error & { 41 | @apply border-red-500 text-red-500; 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /boilerplate/core/scss/components/_general.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | @apply mx-auto px-4; 3 | max-width: theme('screens.xl'); 4 | } 5 | 6 | // Don't show google reCaptcha badge 7 | .grecaptcha-badge { 8 | position: absolute; 9 | visibility: hidden; 10 | pointer-events: none; 11 | } 12 | -------------------------------------------------------------------------------- /boilerplate/core/scss/components/_typography.scss: -------------------------------------------------------------------------------- 1 | @function strip-units($number) { 2 | @return $number / ($number * 0 + 1); 3 | } 4 | 5 | @each $selector, $size in $headingSizes { 6 | #{$selector} { 7 | font-size: (strip-units($size) / 16) + rem; 8 | } 9 | } 10 | 11 | // A good long-words fix 12 | .dont-break-out { 13 | overflow-wrap: break-word; 14 | word-wrap: break-word; 15 | word-break: break-word; 16 | hyphens: auto; 17 | } 18 | 19 | // A good starter for wysiwyg fields 20 | .body { 21 | p:empty { @apply hidden; } 22 | 23 | a { 24 | @apply underline; 25 | color: inherit; 26 | } 27 | 28 | p, h1, h2, h3, h4, h5, h6, ul, ol, dl, blockquote, img { 29 | &:not(:first-child):not([class]) { @apply my-6; } 30 | &:last-child:not([class]) { @apply mb-0; } 31 | } 32 | 33 | li { @apply my-6; } 34 | 35 | ul { 36 | @apply pl-6; 37 | list-style-type: disc; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /boilerplate/server/main.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | import WebpackDevServer from 'webpack-dev-server'; 3 | import webpack from 'webpack'; 4 | import Debug from 'debug'; 5 | /* eslint-enable import/no-extraneous-dependencies */ 6 | import webpackConfig from '../config/webpack.config'; 7 | import project from '../config/project.config'; 8 | 9 | const debug = Debug('webpack'); 10 | 11 | // Add the webpack-dev-server client entry point to all entry points 12 | webpackConfig.entry.app.unshift(`webpack-dev-server/client?http://localhost:${project.serverPort}`); 13 | 14 | debug('Starting webpack compiler'); 15 | // Start Webpack 16 | const compiler = webpack(webpackConfig); 17 | 18 | // Pass compiler along to the webpack-dev-server 19 | const server = new WebpackDevServer(compiler, { 20 | headers: { 21 | 'Access-Control-Allow-Origin': '*' 22 | }, 23 | publicPath: '//localhost:3000/assets/', 24 | disableHostCheck: true, 25 | contentBase: './www', 26 | https: false, 27 | sockHost: 'localhost', 28 | sockPort: 3000, 29 | quiet: project.compilerQuiet, 30 | noInfo: project.compilerQuiet, 31 | stats: project.compilerStats 32 | }); 33 | 34 | export default server; 35 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stenvdb/craft-fathom-analytics", 3 | "description": "Statistics and chart widgets for Fathom analytics.", 4 | "type": "craft-plugin", 5 | "version": "1.1.4", 6 | "keywords": [ 7 | "craft", 8 | "cms", 9 | "craftcms", 10 | "craft-plugin", 11 | "fathom analytics" 12 | ], 13 | "support": { 14 | "docs": "https://github.com/stenvdb/craft-fathom-analytics/blob/master/README.md", 15 | "issues": "https://github.com/stenvdb/craft-fathom-analytics/issues" 16 | }, 17 | "license": "MIT", 18 | "authors": [ 19 | { 20 | "name": "Sten Van den Bergh", 21 | "homepage": "https://stenvdb.be" 22 | } 23 | ], 24 | "require": { 25 | "craftcms/cms": "^3.0.0-RC1" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "stenvdb\\fathomanalytics\\": "src/" 30 | } 31 | }, 32 | "extra": { 33 | "name": "Fathom Analytics", 34 | "handle": "fathom-analytics", 35 | "hasCpSettings": true, 36 | "hasCpSection": false, 37 | "changelogUrl": "https://raw.githubusercontent.com/stenvdb/craft-fathom-analytics/master/CHANGELOG.md", 38 | "components": { 39 | "reports": "stenvdb\\fathomanalytics\\services\\Reports" 40 | }, 41 | "class": "stenvdb\\fathomanalytics\\FathomAnalytics" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "name": "Sten Van den Bergh", 4 | "email": "hello@stenvdb.be", 5 | "url": "https://github.com/stenvdb" 6 | }, 7 | "license": "UNLICENSED", 8 | "private": true, 9 | "browserslist": [ 10 | "last 2 versions", 11 | "ie 11", 12 | "not ie 10", 13 | "last 3 iOS versions" 14 | ], 15 | "boilerplate": { 16 | "dirClient": "src/assetbundles", 17 | "dirPublic": "src/assetbundles" 18 | }, 19 | "babel": { 20 | "extends": "./boilerplate/config/.babelrc" 21 | }, 22 | "eslintConfig": { 23 | "extends": "./boilerplate/config/eslint.js" 24 | }, 25 | "stylelint": { 26 | "extends": "./boilerplate/config/stylelint.js" 27 | }, 28 | "scripts": { 29 | "compile": "DEBUG=webpack NODE_ENV=development DEV_SERVER=0 babel-node boilerplate/bin/compile", 30 | "compile:dev": "DEBUG=webpack NODE_ENV=development DEV_SERVER=0 babel-node boilerplate/bin/compile", 31 | "compile:prod": "DEBUG=webpack NODE_ENV=production DEV_SERVER=0 babel-node boilerplate/bin/compile", 32 | "server": "DEBUG=webpack NODE_ENV=development DEV_SERVER=1 babel-node boilerplate/bin/dev-server", 33 | "watch": "DEBUG=webpack NODE_ENV=development DEV_SERVER=0 babel-node boilerplate/bin/watch" 34 | }, 35 | "devDependencies": { 36 | "@babel/cli": "^7.1.2", 37 | "@babel/core": "^7.1.2", 38 | "@babel/node": "^7.0.0", 39 | "@babel/plugin-syntax-dynamic-import": "^7.0.0", 40 | "@babel/plugin-transform-runtime": "^7.1.0", 41 | "@babel/preset-env": "^7.1.0", 42 | "@babel/preset-stage-0": "^7.0.0", 43 | "@babel/register": "^7.0.0", 44 | "@fullhuman/postcss-purgecss": "^1.1.0", 45 | "autoprefixer": "^9.1.5", 46 | "babel-eslint": "^10.0.1", 47 | "babel-loader": "^8.0.0-beta.4", 48 | "clean-webpack-plugin": "^1.0.0", 49 | "cli-real-favicon": "0.0.7", 50 | "css-loader": "^1.0.0", 51 | "cssnano": "^4.1.8", 52 | "debug": "^4.0.1", 53 | "dotenv": "^6.2.0", 54 | "eslint": "^5.0.1", 55 | "eslint-config-airbnb": "^17.0.0", 56 | "eslint-loader": "^2.0.0", 57 | "eslint-plugin-compat": "^2.4.0", 58 | "eslint-plugin-import": "^2.13.0", 59 | "eslint-plugin-jsx-a11y": "^6.0.3", 60 | "eslint-plugin-react": "^7.10.0", 61 | "file-loader": "^2.0.0", 62 | "mini-css-extract-plugin": "^0.4.3", 63 | "node-sass": "^4.9.3", 64 | "postcss-loader": "^3.0.0", 65 | "postcss-normalize": "^7.0.1", 66 | "raw-loader": "^0.5.1", 67 | "resolve-url-loader": "^3.0.0", 68 | "sass-loader": "^7.0.3", 69 | "shipit-assets": "^1.3.6", 70 | "shipit-cli": "^4.1.2", 71 | "shipit-db": "^1.8.5", 72 | "style-loader": "^0.23.0", 73 | "stylelint": "^9.3.0", 74 | "stylelint-config-standard": "^18.2.0", 75 | "stylelint-webpack-plugin": "^0.10.5", 76 | "svg-sprite-loader": "^4.1.2", 77 | "svg-url-loader": "^2.3.2", 78 | "svgo": "^1.0.5", 79 | "svgo-loader": "^2.1.0", 80 | "tailwindcss": "^1.0.5", 81 | "url-loader": "^1.0.1", 82 | "webpack": "^4.14.0", 83 | "webpack-dev-server": "^3.1.4", 84 | "webpack-manifest-plugin": "^2.0.3" 85 | }, 86 | "dependencies": { 87 | "@babel/runtime": "^7.1.2", 88 | "axios": "^0.19.0", 89 | "chart.js": "^2.9.3", 90 | "codemirror": "^5.53.2", 91 | "core-js": "3.1.2", 92 | "dayjs": "^1.8.20", 93 | "promise-polyfill": "^8.1.0", 94 | "validator": "^11.1.0" 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /resources/img/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stenvdb/craft-fathom-analytics/eb403d8e4bb38571ead579a0e1779c621fbe3a21/resources/img/screenshot.png -------------------------------------------------------------------------------- /src/FathomAnalytics.php: -------------------------------------------------------------------------------- 1 | Reports::class, 90 | 'tags' => Tags::class 91 | ]); 92 | 93 | // Register our variables 94 | Event::on( 95 | CraftVariable::class, 96 | CraftVariable::EVENT_INIT, 97 | function (Event $event) { 98 | /** @var CraftVariable $variable */ 99 | $variable = $event->sender; 100 | $variable->set('fathomAnalytics', FathomAnalyticsVariable::class); 101 | } 102 | ); 103 | 104 | // Register our widgets 105 | Event::on( 106 | Dashboard::class, 107 | Dashboard::EVENT_REGISTER_WIDGET_TYPES, 108 | function (RegisterComponentTypesEvent $event) { 109 | $event->types[] = StatisticsWidget::class; 110 | $event->types[] = ReportWidget::class; 111 | $event->types[] = TopPagesWidget::class; 112 | } 113 | ); 114 | 115 | // Do something after we're installed 116 | Event::on( 117 | Plugins::class, 118 | Plugins::EVENT_AFTER_INSTALL_PLUGIN, 119 | function (PluginEvent $event) { 120 | if ($event->plugin === $this) { 121 | // We were just installed 122 | } 123 | } 124 | ); 125 | 126 | Craft::info( 127 | Craft::t( 128 | 'fathom-analytics', 129 | '{name} plugin loaded', 130 | ['name' => $this->name] 131 | ), 132 | __METHOD__ 133 | ); 134 | 135 | Event::on(Plugins::class, Plugins::EVENT_AFTER_LOAD_PLUGINS, function() { 136 | $request = Craft::$app->getRequest(); 137 | 138 | if ($request->getIsSiteRequest() && !$request->getIsCpRequest()) { 139 | $this->handleTrackingScript(); 140 | } 141 | }); 142 | } 143 | 144 | public function getSettingsResponse() 145 | { 146 | return Craft::$app->controller->renderTemplate('fathom-analytics/settings', [ 147 | 'settings' => $this->getSettings(), 148 | 'fullPageForm' => true, 149 | 'crumbs' => [ 150 | ['label' => 'Settings', 'url' => UrlHelper::cpUrl('settings')], 151 | ['label' => 'Plugins', 'url' => UrlHelper::cpUrl('settings/plugins')] 152 | ] 153 | ]); 154 | } 155 | 156 | // Protected Methods 157 | // ========================================================================= 158 | 159 | protected function createSettingsModel() 160 | { 161 | return new Settings(); 162 | } 163 | 164 | protected function handleTrackingScript() 165 | { 166 | Event::on(View::class, View::EVENT_BEGIN_BODY, function() { 167 | if (self::getSettings()->injectTracking) { 168 | self::$plugin->tags->inject(); 169 | } 170 | }); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/assetbundles/fathomanalytics/FathomAnalyticsAsset.php: -------------------------------------------------------------------------------- 1 | sourcePath = "@stenvdb/fathomanalytics/assetbundles/fathomanalytics/dist"; 44 | 45 | // define the dependencies 46 | $this->depends = [ 47 | CpAsset::class, 48 | ]; 49 | 50 | // define the relative path to CSS/JS files that should be registered with the page 51 | // when this asset bundle is registered 52 | $this->js = [ 53 | 'js/fathomanalytics.min.js', 54 | ]; 55 | 56 | $this->css = [ 57 | 'css/fathomanalytics.min.css', 58 | ]; 59 | 60 | parent::init(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/assetbundles/fathomanalytics/dist/css/fathomanalytics.min.css: -------------------------------------------------------------------------------- 1 | .fa-border-gray-200{border-color:#edf2f7}.fa-border-solid{border-style:solid}.fa-border-t{border-top-width:1px}.fa-border-b{border-bottom-width:1px}.fa-flex{display:-webkit-box;display:-ms-flexbox;display:flex}.fa-items-end{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.fa-items-center{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.fa-justify-end{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.fa-font-bold{font-weight:700}.fa-leading-none{line-height:1}.fa--m-widget-spacing{margin:-24px}.fa-mx-1{margin-left:.25rem;margin-right:.25rem}.fa-my-6{margin-top:1.5rem;margin-bottom:1.5rem}.fa-mb-0{margin-bottom:0}.fa-mb-4{margin-bottom:1rem}.fa-p-widget-spacing{padding:24px}.fa-py-4{padding-top:1rem;padding-bottom:1rem}.fa-px-widget-spacing{padding-left:24px;padding-right:24px}.fa-text-gray-500{color:#a0aec0}.fa-text-craft-blue{color:#4299e1}.fa-text-xs{font-size:.75rem}.fa-text-2xl{font-size:1.5rem}.fa-text-3xl{font-size:1.875rem}.fa-text-5xl{font-size:3rem}.fa-underline{text-decoration:underline} 2 | -------------------------------------------------------------------------------- /src/assetbundles/fathomanalytics/dist/img/FathomAnalytics-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 53 | -------------------------------------------------------------------------------- /src/assetbundles/fathomanalytics/dist/img/Widget.svg: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/assetbundles/fathomanalytics/src/js/fathomanalytics.js: -------------------------------------------------------------------------------- 1 | import Chart from 'chart.js'; 2 | import '../scss/fathomanalytics.scss'; 3 | 4 | window.FathomAnalytics = {}; 5 | window.FathomAnalytics.Global = class FathomAnalytics { 6 | // Expose Chart.js once 7 | static renderChart(ctx, options) { 8 | new Chart(ctx, options); 9 | } 10 | }; 11 | 12 | window.addEventListener('DOMContentLoaded', () => { 13 | new window.FathomAnalytics.Global(); 14 | }); 15 | -------------------------------------------------------------------------------- /src/assetbundles/fathomanalytics/src/scss/fathomanalytics.scss: -------------------------------------------------------------------------------- 1 | @tailwind utilities; 2 | -------------------------------------------------------------------------------- /src/assetbundles/fathomsettings/SettingsAsset.php: -------------------------------------------------------------------------------- 1 | sourcePath = "@stenvdb/fathomanalytics/assetbundles/fathomsettings/dist"; 44 | 45 | // define the dependencies 46 | $this->depends = [ 47 | CpAsset::class, 48 | ]; 49 | 50 | // define the relative path to CSS/JS files that should be registered with the page 51 | // when this asset bundle is registered 52 | $this->js = [ 53 | 'js/fathomsettings.min.js', 54 | ]; 55 | 56 | $this->css = [ 57 | 'css/fathomsettings.min.css', 58 | ]; 59 | 60 | parent::init(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/assetbundles/fathomsettings/dist/css/fathomsettings.min.css: -------------------------------------------------------------------------------- 1 | .fa-border-gray-200{border-color:#edf2f7}.fa-border-solid{border-style:solid}.fa-border-t{border-top-width:1px}.fa-border-b{border-bottom-width:1px}.fa-flex{display:-webkit-box;display:-ms-flexbox;display:flex}.fa-items-end{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.fa-items-center{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.fa-justify-end{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.fa-font-bold{font-weight:700}.fa-leading-none{line-height:1}.fa--m-widget-spacing{margin:-24px}.fa-mx-1{margin-left:.25rem;margin-right:.25rem}.fa-my-6{margin-top:1.5rem;margin-bottom:1.5rem}.fa-mb-0{margin-bottom:0}.fa-mb-4{margin-bottom:1rem}.fa-p-widget-spacing{padding:24px}.fa-py-4{padding-top:1rem;padding-bottom:1rem}.fa-px-widget-spacing{padding-left:24px;padding-right:24px}.fa-text-gray-500{color:#a0aec0}.fa-text-craft-blue{color:#4299e1}.fa-text-xs{font-size:.75rem}.fa-text-2xl{font-size:1.5rem}.fa-text-3xl{font-size:1.875rem}.fa-text-5xl{font-size:3rem}.fa-underline{text-decoration:underline}.CodeMirror{font-family:monospace;height:300px;color:#000;direction:ltr}.CodeMirror-lines{padding:4px 0}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{padding:0 4px}.CodeMirror-gutter-filler,.CodeMirror-scrollbar-filler{background-color:#fff}.CodeMirror-gutters{border-right:1px solid #ddd;background-color:#f7f7f7;white-space:nowrap}.CodeMirror-linenumber{padding:0 3px 0 5px;min-width:20px;text-align:right;color:#999;white-space:nowrap}.CodeMirror-guttermarker{color:#000}.CodeMirror-guttermarker-subtle{color:#999}.CodeMirror-cursor{border-left:1px solid #000;border-right:none;width:0}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.cm-fat-cursor .CodeMirror-cursor{width:auto;border:0!important;background:#7e7}.cm-fat-cursor div.CodeMirror-cursors{z-index:1}.cm-fat-cursor-mark{background-color:rgba(20,255,20,.5)}.cm-animate-fat-cursor,.cm-fat-cursor-mark{-webkit-animation:blink 1.06s steps(1) infinite;animation:blink 1.06s steps(1) infinite}.cm-animate-fat-cursor{width:auto;border:0;background-color:#7e7}@-webkit-keyframes blink{50%{background-color:transparent}}@keyframes blink{50%{background-color:transparent}}.cm-tab{display:inline-block;text-decoration:inherit}.CodeMirror-rulers{position:absolute;left:0;right:0;top:-50px;bottom:0;overflow:hidden}.CodeMirror-ruler{border-left:1px solid #ccc;top:0;bottom:0;position:absolute}.cm-s-default .cm-header{color:#00f}.cm-s-default .cm-quote{color:#090}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:700}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-strikethrough{text-decoration:line-through}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-type,.cm-s-default .cm-variable-3{color:#085}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta,.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-invalidchar,.cm-s-default .cm-error{color:red}.CodeMirror-composing{border-bottom:2px solid}div.CodeMirror span.CodeMirror-matchingbracket{color:#0b0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#a22}.CodeMirror-matchingtag{background:rgba(255,150,0,.3)}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{position:relative;overflow:hidden;background:#fff}.CodeMirror-scroll{overflow:scroll!important;margin-bottom:-30px;margin-right:-30px;padding-bottom:30px;height:100%;outline:none;position:relative}.CodeMirror-sizer{position:relative;border-right:30px solid transparent}.CodeMirror-gutter-filler,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-vscrollbar{position:absolute;z-index:6;display:none}.CodeMirror-vscrollbar{right:0;top:0;overflow-x:hidden;overflow-y:scroll}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-y:hidden;overflow-x:scroll}.CodeMirror-scrollbar-filler{right:0;bottom:0}.CodeMirror-gutter-filler{left:0;bottom:0}.CodeMirror-gutters{position:absolute;left:0;top:0;min-height:100%;z-index:3}.CodeMirror-gutter{white-space:normal;height:100%;display:inline-block;vertical-align:top;margin-bottom:-30px}.CodeMirror-gutter-wrapper{position:absolute;z-index:4;background:none!important;border:none!important}.CodeMirror-gutter-background{position:absolute;top:0;bottom:0;z-index:4}.CodeMirror-gutter-elt{position:absolute;cursor:default;z-index:4}.CodeMirror-gutter-wrapper ::selection{background-color:transparent}.CodeMirror-gutter-wrapper ::-moz-selection{background-color:transparent}.CodeMirror-lines{cursor:text;min-height:1px}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{border-radius:0;border-width:0;background:transparent;font-family:inherit;font-size:inherit;margin:0;white-space:pre;word-wrap:normal;line-height:inherit;color:inherit;z-index:2;position:relative;overflow:visible;-webkit-tap-highlight-color:transparent;-webkit-font-variant-ligatures:contextual;font-variant-ligatures:contextual}.CodeMirror-wrap pre.CodeMirror-line,.CodeMirror-wrap pre.CodeMirror-line-like{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-linebackground{position:absolute;left:0;right:0;top:0;bottom:0;z-index:0}.CodeMirror-linewidget{position:relative;z-index:2;padding:.1px}.CodeMirror-rtl pre{direction:rtl}.CodeMirror-code{outline:none}.CodeMirror-gutter,.CodeMirror-gutters,.CodeMirror-linenumber,.CodeMirror-scroll,.CodeMirror-sizer{-webkit-box-sizing:content-box;box-sizing:content-box}.CodeMirror-measure{position:absolute;width:100%;height:0;overflow:hidden;visibility:hidden}.CodeMirror-cursor{position:absolute;pointer-events:none}.CodeMirror-measure pre{position:static}div.CodeMirror-cursors{visibility:hidden;position:relative;z-index:3}.CodeMirror-focused div.CodeMirror-cursors,div.CodeMirror-dragcursors{visibility:visible}.CodeMirror-selected{background:#d9d9d9}.CodeMirror-focused .CodeMirror-selected{background:#d7d4f0}.CodeMirror-crosshair{cursor:crosshair}.CodeMirror-line::selection,.CodeMirror-line>span::selection,.CodeMirror-line>span>span::selection{background:#d7d4f0}.CodeMirror-line::-moz-selection,.CodeMirror-line>span::-moz-selection,.CodeMirror-line>span>span::-moz-selection{background:#d7d4f0}.cm-searching{background-color:#ffa;background-color:rgba(255,255,0,.4)}.cm-force-border{padding-right:.1px}@media print{.CodeMirror div.CodeMirror-cursors{visibility:hidden}}.cm-tab-wrap-hack:after{content:""}span.CodeMirror-selectedtext{background:none}.solarized.base03{color:#002b36}.solarized.base02{color:#073642}.solarized.base01{color:#586e75}.solarized.base00{color:#657b83}.solarized.base0{color:#839496}.solarized.base1{color:#93a1a1}.solarized.base2{color:#eee8d5}.solarized.base3{color:#fdf6e3}.solarized.solar-yellow{color:#b58900}.solarized.solar-orange{color:#cb4b16}.solarized.solar-red{color:#dc322f}.solarized.solar-magenta{color:#d33682}.solarized.solar-violet{color:#6c71c4}.solarized.solar-blue{color:#268bd2}.solarized.solar-cyan{color:#2aa198}.solarized.solar-green{color:#859900}.cm-s-solarized{line-height:1.45em;color-profile:sRGB;rendering-intent:auto}.cm-s-solarized.cm-s-dark{color:#839496;background-color:#002b36;text-shadow:#002b36 0 1px}.cm-s-solarized.cm-s-light{background-color:#fdf6e3;color:#657b83;text-shadow:#eee8d5 0 1px}.cm-s-solarized .CodeMirror-widget{text-shadow:none}.cm-s-solarized .cm-header{color:#586e75}.cm-s-solarized .cm-quote{color:#93a1a1}.cm-s-solarized .cm-keyword{color:#cb4b16}.cm-s-solarized .cm-atom,.cm-s-solarized .cm-number{color:#d33682}.cm-s-solarized .cm-def{color:#2aa198}.cm-s-solarized .cm-variable{color:#839496}.cm-s-solarized .cm-variable-2{color:#b58900}.cm-s-solarized .cm-type,.cm-s-solarized .cm-variable-3{color:#6c71c4}.cm-s-solarized .cm-property{color:#2aa198}.cm-s-solarized .cm-operator{color:#6c71c4}.cm-s-solarized .cm-comment{color:#586e75;font-style:italic}.cm-s-solarized .cm-string{color:#859900}.cm-s-solarized .cm-string-2{color:#b58900}.cm-s-solarized .cm-meta{color:#859900}.cm-s-solarized .cm-qualifier{color:#b58900}.cm-s-solarized .cm-builtin{color:#d33682}.cm-s-solarized .cm-bracket{color:#cb4b16}.cm-s-solarized .CodeMirror-matchingbracket{color:#859900}.cm-s-solarized .CodeMirror-nonmatchingbracket{color:#dc322f}.cm-s-solarized .cm-tag{color:#93a1a1}.cm-s-solarized .cm-attribute{color:#2aa198}.cm-s-solarized .cm-hr{color:transparent;border-top:1px solid #586e75;display:block}.cm-s-solarized .cm-link{color:#93a1a1;cursor:pointer}.cm-s-solarized .cm-special{color:#6c71c4}.cm-s-solarized .cm-em{color:#999;text-decoration:underline;-webkit-text-decoration-style:dotted;text-decoration-style:dotted}.cm-s-solarized .cm-error,.cm-s-solarized .cm-invalidchar{color:#586e75;border-bottom:1px dotted #dc322f}.cm-s-solarized.cm-s-dark div.CodeMirror-selected{background:#073642}.cm-s-solarized.cm-s-dark.CodeMirror ::-moz-selection{background:rgba(7,54,66,.99)}.cm-s-solarized.cm-s-dark.CodeMirror ::selection{background:rgba(7,54,66,.99)}.cm-s-dark .CodeMirror-line>span::-moz-selection,.cm-s-dark .CodeMirror-line>span>span::-moz-selection,.cm-s-solarized.cm-s-dark .CodeMirror-line::-moz-selection{background:rgba(7,54,66,.99)}.cm-s-solarized.cm-s-light div.CodeMirror-selected{background:#eee8d5}.cm-s-light .CodeMirror-line>span::-moz-selection,.cm-s-light .CodeMirror-line>span>span::-moz-selection,.cm-s-solarized.cm-s-light .CodeMirror-line::-moz-selection{background:#eee8d5}.cm-s-light .CodeMirror-line>span::selection,.cm-s-light .CodeMirror-line>span>span::selection,.cm-s-solarized.cm-s-light .CodeMirror-line::selection{background:#eee8d5}.cm-s-ligh .CodeMirror-line>span::-moz-selection,.cm-s-ligh .CodeMirror-line>span>span::-moz-selection,.cm-s-solarized.cm-s-light .CodeMirror-line::-moz-selection{background:#eee8d5}.cm-s-solarized.CodeMirror{-webkit-box-shadow:inset 7px 0 12px -6px #000;box-shadow:inset 7px 0 12px -6px #000}.cm-s-solarized .CodeMirror-gutters{border-right:0}.cm-s-solarized.cm-s-dark .CodeMirror-gutters{background-color:#073642}.cm-s-solarized.cm-s-dark .CodeMirror-linenumber{color:#586e75;text-shadow:#021014 0 -1px}.cm-s-solarized.cm-s-light .CodeMirror-gutters{background-color:#eee8d5}.cm-s-solarized.cm-s-light .CodeMirror-linenumber{color:#839496}.cm-s-solarized .CodeMirror-linenumber{padding:0 5px}.cm-s-solarized .CodeMirror-guttermarker-subtle{color:#586e75}.cm-s-solarized.cm-s-dark .CodeMirror-guttermarker{color:#ddd}.cm-s-solarized.cm-s-light .CodeMirror-guttermarker{color:#cb4b16}.cm-s-solarized .CodeMirror-gutter .CodeMirror-gutter-text{color:#586e75}.cm-s-solarized .CodeMirror-cursor{border-left:1px solid #819090}.cm-s-solarized.cm-s-light.cm-fat-cursor .CodeMirror-cursor{background:#7e7}.cm-s-solarized.cm-s-light .cm-animate-fat-cursor{background-color:#7e7}.cm-s-solarized.cm-s-dark.cm-fat-cursor .CodeMirror-cursor{background:#586e75}.cm-s-solarized.cm-s-dark .cm-animate-fat-cursor{background-color:#586e75}.cm-s-solarized.cm-s-dark .CodeMirror-activeline-background{background:hsla(0,0%,100%,.06)}.cm-s-solarized.cm-s-light .CodeMirror-activeline-background{background:rgba(0,0,0,.06)} 2 | -------------------------------------------------------------------------------- /src/assetbundles/fathomsettings/src/js/fathomsettings.js: -------------------------------------------------------------------------------- 1 | import CodeMirror from 'codemirror/lib/codemirror'; 2 | import 'codemirror/mode/javascript/javascript'; 3 | import '../scss/fathomsettings.scss'; 4 | 5 | window.FathomAnalytics = {}; 6 | window.FathomAnalytics.Settings = class FathomSettings { 7 | constructor() { 8 | this.trackingTab = document.querySelector('.tab[id="tab-tracking"]'); 9 | 10 | this.onTabClick = this.tabClick.bind(this); 11 | 12 | this.init(); 13 | } 14 | 15 | init() { 16 | const { hash } = window.location; 17 | if (hash === '' || hash !== '#tracking') { 18 | this.addEvents(); 19 | } else { 20 | this.inject(); 21 | } 22 | } 23 | 24 | addEvents() { 25 | if (this.trackingTab) this.trackingTab.addEventListener('click', this.onTabClick); 26 | } 27 | 28 | tabClick() { 29 | this.trackingTab.removeEventListener('click', this.onTabClick); 30 | this.inject(); 31 | } 32 | 33 | inject() { 34 | CodeMirror.fromTextArea(document.getElementById('trackingCode'), { 35 | indentUnit: 8, 36 | styleActiveLine: true, 37 | lineNumbers: true, 38 | lineWrapping: true, 39 | mode: 'javascript', 40 | theme: 'default' 41 | }); 42 | } 43 | }; 44 | 45 | window.addEventListener('DOMContentLoaded', () => { 46 | new window.FathomAnalytics.Settings(); 47 | }); 48 | -------------------------------------------------------------------------------- /src/assetbundles/fathomsettings/src/scss/fathomsettings.scss: -------------------------------------------------------------------------------- 1 | @tailwind utilities; 2 | 3 | /*! purgecss start ignore */ 4 | @import "~codemirror/lib/codemirror"; 5 | @import "~codemirror/theme/solarized"; 6 | /*! purgecss end ignore */ /* stylelint-disable-line comment-empty-line-before */ 7 | -------------------------------------------------------------------------------- /src/assetbundles/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "fathomanalytics.css": "/fathomanalytics/dist/css/fathomanalytics.min.css", 3 | "fathomanalytics.js": "/fathomanalytics/dist/js/fathomanalytics.min.js", 4 | "fathomsettings.css": "/fathomsettings/dist/css/fathomsettings.min.css", 5 | "fathomsettings.js": "/fathomsettings/dist/js/fathomsettings.min.js", 6 | "reportwidget.css": "/reportwidget/dist/css/reportwidget.min.css", 7 | "reportwidget.js": "/reportwidget/dist/js/reportwidget.min.js", 8 | "statisticswidget.css": "/statisticswidget/dist/css/statisticswidget.min.css", 9 | "statisticswidget.js": "/statisticswidget/dist/js/statisticswidget.min.js", 10 | "toppageswidget.css": "/toppageswidget/dist/css/toppageswidget.min.css", 11 | "toppageswidget.js": "/toppageswidget/dist/js/toppageswidget.min.js" 12 | } -------------------------------------------------------------------------------- /src/assetbundles/reportwidget/ReportWidgetAsset.php: -------------------------------------------------------------------------------- 1 | sourcePath = "@stenvdb/fathomanalytics/assetbundles/reportwidget/dist"; 44 | 45 | // define the dependencies 46 | $this->depends = [ 47 | CpAsset::class, 48 | ]; 49 | 50 | // define the relative path to CSS/JS files that should be registered with the page 51 | // when this asset bundle is registered 52 | $this->js = [ 53 | 'js/reportwidget.min.js', 54 | ]; 55 | 56 | $this->css = [ 57 | 'css/reportwidget.min.css', 58 | ]; 59 | 60 | parent::init(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/assetbundles/reportwidget/dist/css/reportwidget.min.css: -------------------------------------------------------------------------------- 1 | .fa-border-gray-200{border-color:#edf2f7}.fa-border-solid{border-style:solid}.fa-border-t{border-top-width:1px}.fa-border-b{border-bottom-width:1px}.fa-flex{display:-webkit-box;display:-ms-flexbox;display:flex}.fa-items-end{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.fa-items-center{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.fa-justify-end{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.fa-font-bold{font-weight:700}.fa-leading-none{line-height:1}.fa--m-widget-spacing{margin:-24px}.fa-mx-1{margin-left:.25rem;margin-right:.25rem}.fa-my-6{margin-top:1.5rem;margin-bottom:1.5rem}.fa-mb-0{margin-bottom:0}.fa-mb-4{margin-bottom:1rem}.fa-p-widget-spacing{padding:24px}.fa-py-4{padding-top:1rem;padding-bottom:1rem}.fa-px-widget-spacing{padding-left:24px;padding-right:24px}.fa-text-gray-500{color:#a0aec0}.fa-text-craft-blue{color:#4299e1}.fa-text-xs{font-size:.75rem}.fa-text-2xl{font-size:1.5rem}.fa-text-3xl{font-size:1.875rem}.fa-text-5xl{font-size:3rem}.fa-underline{text-decoration:underline} 2 | -------------------------------------------------------------------------------- /src/assetbundles/reportwidget/src/js/reportwidget.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import dayjs from 'dayjs'; 3 | import '../scss/reportwidget.scss'; 4 | 5 | window.FathomAnalytics.ReportWidget = class FathomReportWidget { 6 | constructor(config = {}) { 7 | this.config = Object.assign({ 8 | before: dayjs().subtract(1, 'day').endOf('day') 9 | }, config); 10 | 11 | this.labels = []; 12 | this.range = []; 13 | this.data = []; 14 | 15 | this.init(); 16 | } 17 | 18 | init() { 19 | const range = this.getRange(); 20 | 21 | // Set chart.js labels and initial data (= 0) 22 | range.forEach((step) => { 23 | this.labels.push(step.label); 24 | this.data.push(0); 25 | }); 26 | 27 | // Get Fathom data 28 | axios.get('actions/fathom-analytics/reports/report-widget', { 29 | params: { 30 | before: this.config.before.unix(), 31 | after: this.config.after.unix(), 32 | report: this.config.report, 33 | }, 34 | }).then((response) => { 35 | // Update chart.js data array 36 | response.data.Data.forEach((row) => { 37 | const s = dayjs(row.Date).unix(); 38 | const i = range.findIndex(r => s > r.start.unix() && s < r.end.unix()); 39 | this.data[i] += parseInt(row[this.config.report], 10); 40 | }); 41 | 42 | const options = { 43 | type: this.config.chart, 44 | data: { 45 | labels: this.labels, 46 | datasets: [ 47 | { 48 | label: this.config.report, 49 | aspectRatio: 2.5, 50 | backgroundColor: this.config.chart === 'line' ? 'rgba(66,153,225, 0.1)' : '#4299E1', 51 | borderColor: '#4299E1', 52 | pointBackgroundColor: '#4299E1', 53 | borderWidth: 3, 54 | pointRadius: 2, 55 | pointHitRadius: 4, 56 | lineTension: 0, 57 | data: this.data 58 | } 59 | ] 60 | }, 61 | options: { 62 | responsive: true, 63 | maintainAspectRatio: false, 64 | legend: { 65 | display: false 66 | }, 67 | tooltips: { 68 | bodyFontColor: 'hsl(209, 18%, 30%)', 69 | backgroundColor: '#fff', 70 | borderColor: 'rgba(155, 155, 155, 0.1)', 71 | borderWidth: 1, 72 | caretPadding: 6, 73 | caretSize: 0, 74 | mode: 'index', 75 | titleFontColor: 'hsl(209, 18%, 30%)', 76 | }, 77 | layout: { 78 | padding: { 79 | left: 0, 80 | right: 0, 81 | top: 0, 82 | bottom: 0 83 | } 84 | }, 85 | scales: { 86 | yAxes: [{ 87 | ticks: { 88 | autoSkip: true, 89 | autoSkipPadding: 5, 90 | beginAtZero: true, 91 | fontColor: '#4299E1', 92 | mirror: true 93 | }, 94 | gridLines: { 95 | drawBorder: false, 96 | } 97 | }], 98 | xAxes: [{ 99 | ticks: { 100 | autoSkip: true, 101 | autoSkipPadding: 10, 102 | fontColor: '#4299E1', 103 | }, 104 | // labels: { 105 | // display: false 106 | // }, 107 | gridLines: { 108 | display: false, 109 | drawBorder: false, 110 | } 111 | }] 112 | } 113 | } 114 | }; 115 | 116 | const ctx = document.querySelector(`#widget${this.config.id} #myChart`).getContext('2d'); 117 | window.FathomAnalytics.Global.renderChart(ctx, options); 118 | 119 | // Update total placeholder 120 | const sumEl = document.querySelector(`#widget${this.config.id} .js-fa-total`); 121 | const sum = this.data.reduce((a, b) => a + b); 122 | sumEl.innerHTML = sum; 123 | }); 124 | } 125 | 126 | setDefaults(range) { 127 | range.forEach((step) => { 128 | this.labels.push(step.label); 129 | this.data.push(0); 130 | }); 131 | } 132 | 133 | getRange() { 134 | switch (this.config.period) { 135 | case 'week': { 136 | this.config.after = dayjs().subtract(7, 'days').startOf('day'); 137 | return this.getLastDays(7); 138 | } 139 | case 'month': { 140 | this.config.after = dayjs().subtract(30, 'days').startOf('day'); 141 | return this.getLastDays(30); 142 | } 143 | case 'year': { 144 | this.config.after = dayjs().subtract(365, 'days').startOf('day'); 145 | return this.getLastYear(); 146 | } 147 | default: return []; 148 | } 149 | } 150 | 151 | getLastDays(days) { 152 | const a = []; 153 | for (let i = days; i > 0; i -= 1) { 154 | a.push({ 155 | label: dayjs().subtract(i, 'days').format('D/M'), 156 | start: dayjs().subtract(i, 'days').startOf('day'), 157 | end: dayjs().subtract(i, 'days').endOf('day') 158 | }); 159 | } 160 | return a; 161 | } 162 | 163 | getLastYear() { 164 | const a = []; 165 | 166 | // Get the first month 167 | a.push({ 168 | label: dayjs().subtract(365, 'days').format('MMM'), 169 | start: dayjs().subtract(365, 'days').startOf('day'), 170 | end: dayjs().subtract(365, 'days').endOf('month').endOf('day'), 171 | }); 172 | 173 | // Get the next 11 months 174 | for (let i = 1; i <= 11; i += 1) { 175 | a.push({ 176 | label: a[i - 1].end.add(1, 'day').format('MMM'), 177 | start: a[i - 1].end.add(1, 'day'), 178 | end: a[i - 1].end.add(1, 'day').endOf('month') 179 | }); 180 | } 181 | 182 | // Get the last month 183 | a.push({ 184 | label: this.config.before.format('MMM'), 185 | start: a[a.length - 1].end.add(1, 'day'), 186 | end: this.config.before, 187 | }); 188 | 189 | return a; 190 | } 191 | }; 192 | -------------------------------------------------------------------------------- /src/assetbundles/reportwidget/src/scss/reportwidget.scss: -------------------------------------------------------------------------------- 1 | @tailwind utilities; 2 | 3 | .fa-transition { 4 | transition: all 0.3s ease-in-out; 5 | } 6 | -------------------------------------------------------------------------------- /src/assetbundles/statisticswidget/StatisticsWidgetAsset.php: -------------------------------------------------------------------------------- 1 | sourcePath = "@stenvdb/fathomanalytics/assetbundles/statisticswidget/dist"; 46 | 47 | // define the dependencies 48 | $this->depends = [ 49 | CpAsset::class, 50 | ]; 51 | 52 | // define the relative path to CSS/JS files that should be registered with the page 53 | // when this asset bundle is registered 54 | $this->js = [ 55 | 'js/statisticswidget.min.js', 56 | ]; 57 | 58 | $this->css = [ 59 | 'css/statisticswidget.min.css', 60 | ]; 61 | 62 | parent::init(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/assetbundles/statisticswidget/dist/css/statisticswidget.min.css: -------------------------------------------------------------------------------- 1 | .fa-border-gray-200{border-color:#edf2f7}.fa-border-solid{border-style:solid}.fa-border-t{border-top-width:1px}.fa-border-b{border-bottom-width:1px}.fa-flex{display:-webkit-box;display:-ms-flexbox;display:flex}.fa-items-end{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.fa-items-center{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.fa-justify-end{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.fa-font-bold{font-weight:700}.fa-leading-none{line-height:1}.fa--m-widget-spacing{margin:-24px}.fa-mx-1{margin-left:.25rem;margin-right:.25rem}.fa-my-6{margin-top:1.5rem;margin-bottom:1.5rem}.fa-mb-0{margin-bottom:0}.fa-mb-4{margin-bottom:1rem}.fa-p-widget-spacing{padding:24px}.fa-py-4{padding-top:1rem;padding-bottom:1rem}.fa-px-widget-spacing{padding-left:24px;padding-right:24px}.fa-text-gray-500{color:#a0aec0}.fa-text-craft-blue{color:#4299e1}.fa-text-xs{font-size:.75rem}.fa-text-2xl{font-size:1.5rem}.fa-text-3xl{font-size:1.875rem}.fa-text-5xl{font-size:3rem}.fa-underline{text-decoration:underline} 2 | -------------------------------------------------------------------------------- /src/assetbundles/statisticswidget/src/js/statisticswidget.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import dayjs from 'dayjs'; 3 | import '../scss/statisticswidget.scss'; 4 | 5 | window.FathomAnalytics.StatisticsWidget = class FathomStatisticsWidget { 6 | constructor(config = {}) { 7 | this.config = Object.assign({ 8 | before: dayjs().subtract(1, 'day').endOf('day') 9 | }, config); 10 | 11 | this.wrapper = document.querySelector(`#widget${this.config.id} .js-fa-realtime`); 12 | this.uniqueVisEl = document.querySelector(`#widget${this.config.id} .js-fa-unique-visitors`); 13 | this.pageviewsEl = document.querySelector(`#widget${this.config.id} .js-fa-pageviews`); 14 | this.avgTimeEl = document.querySelector(`#widget${this.config.id} .js-fa-avg-time`); 15 | this.bounceRateEl = document.querySelector(`#widget${this.config.id} .js-fa-bounce-rate`); 16 | 17 | this.onGet = this.get.bind(this); 18 | 19 | this.init(); 20 | } 21 | 22 | init() { 23 | this.get(); 24 | setInterval(this.onGet, 5000); 25 | 26 | this.getStats(); 27 | } 28 | 29 | get() { 30 | axios.get('actions/fathom-analytics/reports/realtime-widget').then((response) => { 31 | this.wrapper.innerHTML = response.data.Data; 32 | }); 33 | } 34 | 35 | getStats() { 36 | // Set the after date 37 | this.setAfter(); 38 | 39 | axios.get('actions/fathom-analytics/reports/site-stats', { 40 | params: { 41 | before: this.config.before.unix(), 42 | after: this.config.after.unix() 43 | }, 44 | }).then((response) => { 45 | this.uniqueVisEl.innerHTML = response.data.Data.Visitors; 46 | this.pageviewsEl.innerHTML = response.data.Data.Pageviews; 47 | this.avgTimeEl.innerHTML = dayjs() 48 | .hour(0).minute(0).second(response.data.Data.AvgDuration) 49 | .millisecond(0) 50 | .format('mm:ss'); 51 | this.bounceRateEl.innerHTML = `${parseInt(response.data.Data.BounceRate * 10, 10)}%`; 52 | }); 53 | } 54 | 55 | setAfter() { 56 | switch (this.config.period) { 57 | case 'week': { 58 | this.config.after = dayjs().subtract(7, 'days').startOf('day'); 59 | break; 60 | // return this.getLastDays(7); 61 | } 62 | case 'month': { 63 | this.config.after = dayjs().subtract(30, 'days').startOf('day'); 64 | break; 65 | // return this.getLastDays(30); 66 | } 67 | case 'year': { 68 | this.config.after = dayjs().subtract(365, 'days').startOf('day'); 69 | break; 70 | // return this.getLastYear(); 71 | } 72 | default: break; 73 | } 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /src/assetbundles/statisticswidget/src/scss/statisticswidget.scss: -------------------------------------------------------------------------------- 1 | @tailwind utilities; 2 | -------------------------------------------------------------------------------- /src/assetbundles/toppageswidget/TopPagesWidgetAsset.php: -------------------------------------------------------------------------------- 1 | sourcePath = "@stenvdb/fathomanalytics/assetbundles/toppageswidget/dist"; 44 | 45 | // define the dependencies 46 | $this->depends = [ 47 | CpAsset::class, 48 | ]; 49 | 50 | // define the relative path to CSS/JS files that should be registered with the page 51 | // when this asset bundle is registered 52 | $this->js = [ 53 | 'js/toppageswidget.min.js', 54 | ]; 55 | 56 | $this->css = [ 57 | 'css/toppageswidget.min.css', 58 | ]; 59 | 60 | parent::init(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/assetbundles/toppageswidget/dist/css/toppageswidget.min.css: -------------------------------------------------------------------------------- 1 | .fa-border-gray-200{border-color:#edf2f7}.fa-border-solid{border-style:solid}.fa-border-t{border-top-width:1px}.fa-border-b{border-bottom-width:1px}.fa-flex{display:-webkit-box;display:-ms-flexbox;display:flex}.fa-items-end{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.fa-items-center{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.fa-justify-end{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.fa-font-bold{font-weight:700}.fa-leading-none{line-height:1}.fa--m-widget-spacing{margin:-24px}.fa-mx-1{margin-left:.25rem;margin-right:.25rem}.fa-my-6{margin-top:1.5rem;margin-bottom:1.5rem}.fa-mb-0{margin-bottom:0}.fa-mb-4{margin-bottom:1rem}.fa-p-widget-spacing{padding:24px}.fa-py-4{padding-top:1rem;padding-bottom:1rem}.fa-px-widget-spacing{padding-left:24px;padding-right:24px}.fa-text-gray-500{color:#a0aec0}.fa-text-craft-blue{color:#4299e1}.fa-text-xs{font-size:.75rem}.fa-text-2xl{font-size:1.5rem}.fa-text-3xl{font-size:1.875rem}.fa-text-5xl{font-size:3rem}.fa-underline{text-decoration:underline} 2 | -------------------------------------------------------------------------------- /src/assetbundles/toppageswidget/src/js/toppageswidget.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import dayjs from 'dayjs'; 3 | import '../scss/toppageswidget.scss'; 4 | 5 | window.FathomAnalytics.TopPagesWidget = class FathomTopPagesWidget { 6 | constructor(config = {}) { 7 | this.config = Object.assign({ 8 | before: dayjs().subtract(1, 'day').endOf('day') 9 | }, config); 10 | 11 | this.labels = []; 12 | this.range = []; 13 | this.data = []; 14 | 15 | this.init(); 16 | } 17 | 18 | init() { 19 | // Set the after date 20 | this.setAfter(); 21 | 22 | // Get Fathom data 23 | axios.get('actions/fathom-analytics/reports/top-pages-widget', { 24 | params: { 25 | before: this.config.before.unix(), 26 | after: this.config.after.unix() 27 | }, 28 | }).then((response) => { 29 | const labels = []; 30 | const data = []; 31 | 32 | // Update chart.js data & labels array 33 | response.data.Data.forEach((row) => { 34 | labels.push(row.Pathname); 35 | data.push(row.Pageviews); 36 | }); 37 | 38 | const options = { 39 | type: 'horizontalBar', 40 | data: { 41 | labels, 42 | datasets: [ 43 | { 44 | label: 'Pageviews', 45 | aspectRatio: 2.5, 46 | backgroundColor: 'rgba(66,153,225, 0.1)', 47 | borderColor: 'rgba(66,153,225, 0.1)', 48 | pointBackgroundColor: '#4299E1', 49 | barThickness: 26, 50 | borderWidth: 0, 51 | pointRadius: 2, 52 | pointHitRadius: 4, 53 | lineTension: 0, 54 | data 55 | } 56 | ] 57 | }, 58 | options: { 59 | responsive: true, 60 | maintainAspectRatio: false, 61 | legend: { 62 | display: false 63 | }, 64 | tooltips: { 65 | bodyFontColor: 'hsl(209, 18%, 30%)', 66 | backgroundColor: '#fff', 67 | borderColor: 'rgba(155, 155, 155, 0.1)', 68 | borderWidth: 1, 69 | caretPadding: 6, 70 | caretSize: 0, 71 | mode: 'index', 72 | titleFontColor: 'hsl(209, 18%, 30%)', 73 | }, 74 | layout: { 75 | padding: { 76 | left: 0, 77 | right: 0, 78 | top: 0, 79 | bottom: 0 80 | } 81 | }, 82 | scales: { 83 | yAxes: [{ 84 | ticks: { 85 | autoSkip: true, 86 | autoSkipPadding: 5, 87 | beginAtZero: true, 88 | fontColor: '#4299E1', 89 | mirror: true 90 | }, 91 | gridLines: { 92 | display: false, 93 | drawBorder: false, 94 | } 95 | }], 96 | xAxes: [{ 97 | ticks: { 98 | autoSkip: true, 99 | autoSkipPadding: 10, 100 | fontColor: '#4299E1', 101 | }, 102 | // labels: { 103 | // display: false 104 | // }, 105 | gridLines: { 106 | display: false, 107 | drawBorder: false, 108 | } 109 | }] 110 | } 111 | } 112 | }; 113 | 114 | const ctx = document.querySelector(`#widget${this.config.id} #myChart`).getContext('2d'); 115 | window.FathomAnalytics.Global.renderChart(ctx, options); 116 | 117 | document.querySelector(`#widget${this.config.id} #myChart`).style.height = `${(data.length * 30) + 40}px`; 118 | }); 119 | } 120 | 121 | setDefaults(range) { 122 | range.forEach((step) => { 123 | this.labels.push(step.label); 124 | this.data.push(0); 125 | }); 126 | } 127 | 128 | setAfter() { 129 | switch (this.config.period) { 130 | case 'week': { 131 | this.config.after = dayjs().subtract(7, 'days').startOf('day'); 132 | break; 133 | // return this.getLastDays(7); 134 | } 135 | case 'month': { 136 | this.config.after = dayjs().subtract(30, 'days').startOf('day'); 137 | break; 138 | // return this.getLastDays(30); 139 | } 140 | case 'year': { 141 | this.config.after = dayjs().subtract(365, 'days').startOf('day'); 142 | break; 143 | // return this.getLastYear(); 144 | } 145 | default: break; 146 | } 147 | } 148 | 149 | // getLastDays(days) { 150 | // const a = []; 151 | // for (let i = days; i > 0; i -= 1) { 152 | // a.push({ 153 | // label: dayjs().subtract(i, 'days').format('D/M'), 154 | // start: dayjs().subtract(i, 'days').startOf('day'), 155 | // end: dayjs().subtract(i, 'days').endOf('day') 156 | // }); 157 | // } 158 | // return a; 159 | // } 160 | 161 | // getLastYear() { 162 | // const a = []; 163 | 164 | // // Get the first month 165 | // a.push({ 166 | // label: dayjs().subtract(365, 'days').format('MMM'), 167 | // start: dayjs().subtract(365, 'days').startOf('day'), 168 | // end: dayjs().subtract(365, 'days').endOf('month').endOf('day'), 169 | // }); 170 | 171 | // // Get the next 11 months 172 | // for (let i = 1; i <= 11; i += 1) { 173 | // a.push({ 174 | // label: a[i - 1].end.add(1, 'day').format('MMM'), 175 | // start: a[i - 1].end.add(1, 'day'), 176 | // end: a[i - 1].end.add(1, 'day').endOf('month') 177 | // }); 178 | // } 179 | 180 | // // Get the last month 181 | // a.push({ 182 | // label: this.config.before.format('MMM'), 183 | // start: a[a.length - 1].end.add(1, 'day'), 184 | // end: this.config.before, 185 | // }); 186 | 187 | // return a; 188 | // } 189 | }; 190 | -------------------------------------------------------------------------------- /src/assetbundles/toppageswidget/src/scss/toppageswidget.scss: -------------------------------------------------------------------------------- 1 | @tailwind utilities; 2 | 3 | .fa-transition { 4 | transition: all 0.3s ease-in-out; 5 | } 6 | -------------------------------------------------------------------------------- /src/config.php: -------------------------------------------------------------------------------- 1 | true, 29 | 30 | ]; 31 | -------------------------------------------------------------------------------- /src/controllers/ReportsController.php: -------------------------------------------------------------------------------- 1 | 35 | * @package <%= pluginHandle %> 36 | * @since <%= pluginVersion %> 37 | */ 38 | 39 | class ReportsController extends Controller 40 | { 41 | 42 | // Protected Properties 43 | // ========================================================================= 44 | 45 | protected $allowAnonymous = []; 46 | 47 | // Public Methods 48 | // ========================================================================= 49 | 50 | public function actionRealtimeWidget() 51 | { 52 | $this->requireCpRequest(); 53 | 54 | $jsonData = FathomAnalytics::$plugin->reports->getRealtimeVisitors(); 55 | 56 | return $jsonData; 57 | } 58 | 59 | public function actionReportWidget() 60 | { 61 | $this->requireCpRequest(); 62 | 63 | $before = Craft::$app->getRequest()->getParam('before'); 64 | $after = Craft::$app->getRequest()->getParam('after'); 65 | 66 | $jsonData = FathomAnalytics::$plugin->reports->getReport($before, $after); 67 | 68 | return $jsonData; 69 | } 70 | 71 | public function actionSiteStats() 72 | { 73 | $this->requireCpRequest(); 74 | 75 | $before = Craft::$app->getRequest()->getParam('before'); 76 | $after = Craft::$app->getRequest()->getParam('after'); 77 | 78 | $jsonData = FathomAnalytics::$plugin->reports->getSiteStats($before, $after); 79 | 80 | return $jsonData; 81 | } 82 | 83 | public function actionTopPagesWidget() 84 | { 85 | $this->requireCpRequest(); 86 | 87 | $before = Craft::$app->getRequest()->getParam('before'); 88 | $after = Craft::$app->getRequest()->getParam('after'); 89 | 90 | $jsonData = FathomAnalytics::$plugin->reports->getTopPages($before, $after); 91 | 92 | return $jsonData; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/controllers/SettingsController.php: -------------------------------------------------------------------------------- 1 | 35 | * @package <%= pluginHandle %> 36 | * @since <%= pluginVersion %> 37 | */ 38 | 39 | class SettingsController extends Controller 40 | { 41 | 42 | // Protected Properties 43 | // ========================================================================= 44 | 45 | protected $allowAnonymous = []; 46 | 47 | // Public Methods 48 | // ========================================================================= 49 | 50 | public function actionSave() 51 | { 52 | $this->requireCpRequest(); 53 | 54 | $settings = Craft::$app->getRequest()->getBodyParam('settings', []); 55 | $plugin = Craft::$app->getPlugins()->getPlugin('fathom-analytics'); 56 | 57 | if (!Craft::$app->getPlugins()->savePluginSettings($plugin, $settings)) { 58 | Craft::$app->getSession()->setError(Craft::t('app', "Couldn't save plugin settings.")); 59 | 60 | // Send the plugin back to the template 61 | Craft::$app->getUrlManager()->setRouteParams([ 62 | 'plugin' => 'fathom-analytics', 63 | ]); 64 | 65 | return null; 66 | } 67 | 68 | Craft::$app->getSession()->setNotice(Craft::t('app', 'Plugin settings saved.')); 69 | 70 | return $this->redirectToPostedUrl(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/icon-mask.svg: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/icon.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/models/Settings.php: -------------------------------------------------------------------------------- 1 | baseUri, $siteHandle))), '/'); 73 | return $baseUri; 74 | } 75 | 76 | public function getUsername($siteHandle = null): string 77 | { 78 | return Craft::parseEnv(ConfigHelper::localizedValue($this->username, $siteHandle)); 79 | } 80 | 81 | public function getPassword($siteHandle = null): string 82 | { 83 | return Craft::parseEnv(ConfigHelper::localizedValue($this->password, $siteHandle)); 84 | } 85 | 86 | public function getTrackingId($siteHandle = null): string 87 | { 88 | return Craft::parseEnv(ConfigHelper::localizedValue($this->trackingId, $siteHandle)); 89 | } 90 | 91 | public function behaviors() 92 | { 93 | return [ 94 | 'parser' => [ 95 | 'class' => EnvAttributeParserBehavior::class, 96 | 'attributes' => ['baseUri', 'username'], 97 | ], 98 | ]; 99 | } 100 | 101 | public function rules() 102 | { 103 | return [ 104 | ['username', 'email'], 105 | ['baseUri', 'url'] 106 | ]; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/services/Reports.php: -------------------------------------------------------------------------------- 1 | hitFathom('stats/site/realtime', null, null); 42 | 43 | return $this->parseResponse($response); 44 | } 45 | 46 | public function getReport($before, $after) 47 | { 48 | $response = $this->hitFathom('stats/site', $before, $after); 49 | 50 | return $this->parseResponse($response); 51 | } 52 | 53 | public function getSiteStats($before, $after) { 54 | $response = $this->hitFathom('stats/site/agg', $before, $after); 55 | 56 | return $this->parseResponse($response); 57 | } 58 | 59 | public function getTopPages($before, $after) 60 | { 61 | $response = $this->hitFathom('stats/pages/agg', $before, $after); 62 | 63 | return $this->parseResponse($response); 64 | } 65 | 66 | // Private Methods 67 | // ========================================================================= 68 | 69 | private function authenticate() 70 | { 71 | $client = new Client([ 72 | 'base_uri' => 'https://' . FathomAnalytics::$plugin->getSettings()->getBaseUri() . '/' 73 | ]); 74 | 75 | $jar = new CookieJar; 76 | $response = $client->post('api/session', [ 77 | 'json' => [ 78 | 'email' => FathomAnalytics::$plugin->getSettings()->getUsername(), 79 | 'password' => FathomAnalytics::$plugin->getSettings()->getPassword(), 80 | ], 81 | 'cookies' => $jar, 82 | 'headers' => [ 83 | 'Content-Type' => 'application/json; charset=utf-8' 84 | ] 85 | ]); 86 | 87 | if (!$response->getStatusCode() === 200) 88 | { 89 | return; 90 | } 91 | 92 | // Save our auth cookie for reuse 93 | Craft::$app->getSession()->set('fa-auth', $jar->getCookieByName('auth')->getValue()); 94 | } 95 | 96 | private function getCookieJar() 97 | { 98 | if (is_null(Craft::$app->getSession()->get('fa-auth'))) { 99 | $this->authenticate(); 100 | } 101 | 102 | $jar = CookieJar::fromArray([ 103 | 'auth' => Craft::$app->getSession()->get('fa-auth') 104 | ], parse_url('https://' . FathomAnalytics::$plugin->getSettings()->getBaseUri() . '/')['host']); 105 | 106 | return $jar; 107 | } 108 | 109 | private function hitFathom($endpoint, $before, $after) 110 | { 111 | $jar = $this->getCookieJar(); 112 | 113 | $baseUri = 'https://' . FathomAnalytics::$plugin->getSettings()->getBaseUri() . '/'; 114 | 115 | if (substr($baseUri, -1) !== '/') 116 | { 117 | $baseUri .= '/'; 118 | } 119 | 120 | $siteId = $this->getSiteId(FathomAnalytics::$plugin->getSettings()->getTrackingId()); 121 | 122 | $baseUri .= 'api/sites/'.$siteId.'/'; 123 | 124 | $client = new Client([ 125 | 'base_uri' => $baseUri 126 | ]); 127 | 128 | $query = []; 129 | 130 | if (isset($before) && isset($after)) 131 | { 132 | $query['before'] = $before; 133 | $query['after'] = $after; 134 | } 135 | 136 | return $client->get($endpoint, [ 137 | 'cookies' => $jar, 138 | 'query' => $query 139 | ]); 140 | } 141 | 142 | private function getSiteId($trackingId) 143 | { 144 | $jar = $this->getCookieJar(); 145 | 146 | $baseUri = 'https://' . FathomAnalytics::$plugin->getSettings()->getBaseUri() . '/'; 147 | 148 | $endpoint = $baseUri . 'api/sites'; 149 | 150 | $client = new Client([ 151 | 'base_uri' => $baseUri 152 | ]); 153 | 154 | $response = $client->get($endpoint, [ 155 | 'cookies' => $jar 156 | ]); 157 | 158 | if (!$response->getStatusCode() === 200) 159 | { 160 | return; 161 | } 162 | 163 | $data = json_decode($response->getBody()->getContents()); 164 | 165 | $siteId = null; 166 | foreach($data->Data as $site) { 167 | if (FathomAnalytics::$plugin->getSettings()->getTrackingId() == $site->trackingId) { 168 | $siteId = $site->id; 169 | break; 170 | } 171 | } 172 | 173 | return $siteId; 174 | } 175 | 176 | private function parseResponse($response) 177 | { 178 | if (!$response->getStatusCode() === 200) 179 | { 180 | return; 181 | } 182 | 183 | $jsonData = $response->getBody()->getContents(); 184 | 185 | return $jsonData; 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/services/Tags.php: -------------------------------------------------------------------------------- 1 | getSettings(); 40 | $trackingCode = $settings->trackingCode; 41 | 42 | $baseUri = $settings->getBaseUri(); 43 | $trackingId = $settings->getTrackingId(); 44 | 45 | $data = [ 46 | 'baseUri' => $baseUri, 47 | 'trackingId' => $trackingId 48 | ]; 49 | 50 | $js = Craft::$app->getView()->renderString($trackingCode, $data); 51 | 52 | $js = stripslashes(trim($js)); 53 | 54 | Craft::$app->getView()->registerJs($js, View::POS_HEAD); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/templates/_components/widgets/report/body.twig: -------------------------------------------------------------------------------- 1 | {# @var craft \craft\web\twig\variables\CraftVariable #} 2 | {# 3 | /** 4 | * Fathom Analytics plugin for Craft CMS 5 | * 6 | * Report Widget Body 7 | * 8 | * @author Sten Van den Bergh 9 | * @copyright Copyright (c) 2020 Sten Van den Bergh 10 | * @link https://stenvdb.be 11 | * @package FathomAnalytics 12 | * @since 1.0.0 13 | */ 14 | #} 15 | 16 | {% set iconUrl = view.getAssetManager().getPublishedUrl('@stenvdb/fathomanalytics/assetbundles/fathomanalytics/dist', true) ~ '/img/Widget.svg' %} 17 | 18 |
41 | -------------------------------------------------------------------------------- /src/templates/_components/widgets/report/settings.twig: -------------------------------------------------------------------------------- 1 | {# @var craft \craft\web\twig\variables\CraftVariable #} 2 | {# 3 | /** 4 | * Fathom Analytics plugin for Craft CMS 5 | * 6 | * Report Widget Settings 7 | * 8 | * @author Sten Van den Bergh 9 | * @copyright Copyright (c) 2020 Sten Van den Bergh 10 | * @link https://stenvdb.be 11 | * @package FathomAnalytics 12 | * @since 1.0.0 13 | */ 14 | #} 15 | 16 | {% import "_includes/forms" as forms %} 17 | 18 | {% do view.registerAssetBundle("stenvdb\\fathomanalytics\\assetbundles\\fathomanalytics\\FathomAnalyticsAsset") %} 19 | 20 | {% set reportOptions = [ 21 | { label: 'visitors'|t('fathom-analytics')|title, value: 'Visitors' }, 22 | { label: 'pageviews'|t('fathom-analytics')|title, value: 'Pageviews' }, 23 | { label: 'sessions'|t('fathom-analytics')|title, value: 'Sessions' }, 24 | { label: 'bounce rate'|t('fathom-analytics')|title, value: 'BounceRate' }, 25 | ] %} 26 | 27 | {{ forms.selectField({ 28 | label: 'report'|t('fathom-analytics')|title, 29 | name: 'report', 30 | options: reportOptions, 31 | value: settings.report, 32 | }) }} 33 | 34 | {% set periodOptions = [ 35 | { label: 'week'|t('fathom-analytics')|title, value: 'week' }, 36 | { label: 'month'|t('fathom-analytics')|title, value: 'month' }, 37 | { label: 'year'|t('fathom-analytics')|title, value: 'year' } 38 | ] %} 39 | 40 | {{ forms.selectField({ 41 | label: 'period'|t('fathom-analytics')|title, 42 | name: 'period', 43 | options: periodOptions, 44 | value: settings.period, 45 | }) }} 46 | 47 | {% set chartOptions = [ 48 | { label: 'line chart'|t('fathom-analytics')|title, value: 'line' }, 49 | { label: 'bar chart'|t('fathom-analytics')|title, value: 'bar' }, 50 | ] %} 51 | 52 | {{ forms.selectField({ 53 | label: 'chart type'|t('fathom-analytics')|title, 54 | name: 'chart', 55 | options: chartOptions, 56 | value: settings.chart, 57 | }) }} 58 | -------------------------------------------------------------------------------- /src/templates/_components/widgets/statistics/body.twig: -------------------------------------------------------------------------------- 1 | {# @var craft \craft\web\twig\variables\CraftVariable #} 2 | {# 3 | /** 4 | * Fathom Analytics plugin for Craft CMS 5 | * 6 | * Statistics Widget Body 7 | * 8 | * @author Sten Van den Bergh 9 | * @copyright Copyright (c) 2020 Sten Van den Bergh 10 | * @link https://stenvdb.be 11 | * @package FathomAnalytics 12 | * @since 1.0.0 13 | */ 14 | #} 15 | {% set iconUrl = view.getAssetManager().getPublishedUrl('@stenvdb/fathomanalytics/assetbundles/fathomanalytics/dist', true) ~ '/img/Widget.svg' %} 16 | 17 | 66 | -------------------------------------------------------------------------------- /src/templates/_components/widgets/statistics/settings.twig: -------------------------------------------------------------------------------- 1 | {# @var craft \craft\web\twig\variables\CraftVariable #} 2 | {# 3 | /** 4 | * Fathom Analytics plugin for Craft CMS 5 | * 6 | * Statistics Widget Settings 7 | * 8 | * @author Sten Van den Bergh 9 | * @copyright Copyright (c) 2020 Sten Van den Bergh 10 | * @link https://stenvdb.be 11 | * @package FathomAnalytics 12 | * @since 1.0.0 13 | */ 14 | #} 15 | 16 | {% import "_includes/forms" as forms %} 17 | 18 | {% do view.registerAssetBundle("stenvdb\\fathomanalytics\\assetbundles\\fathomanalytics\\FathomAnalyticsAsset") %} 19 | 20 | {% set periodOptions = [ 21 | { label: 'week'|t('fathom-analytics')|title, value: 'week' }, 22 | { label: 'month'|t('fathom-analytics')|title, value: 'month' }, 23 | { label: 'year'|t('fathom-analytics')|title, value: 'year' } 24 | ] %} 25 | 26 | {{ forms.selectField({ 27 | label: 'period'|t('fathom-analytics')|title, 28 | name: 'period', 29 | options: periodOptions, 30 | value: settings.period, 31 | }) }} 32 | -------------------------------------------------------------------------------- /src/templates/_components/widgets/top-pages/body.twig: -------------------------------------------------------------------------------- 1 | {# @var craft \craft\web\twig\variables\CraftVariable #} 2 | {# 3 | /** 4 | * Fathom Analytics plugin for Craft CMS 5 | * 6 | * Report Widget Body 7 | * 8 | * @author Sten Van den Bergh 9 | * @copyright Copyright (c) 2020 Sten Van den Bergh 10 | * @link https://stenvdb.be 11 | * @package FathomAnalytics 12 | * @since 1.0.0 13 | */ 14 | #} 15 | 16 | {% set iconUrl = view.getAssetManager().getPublishedUrl('@stenvdb/fathomanalytics/assetbundles/fathomanalytics/dist', true) ~ '/img/Widget.svg' %} 17 | 18 | 41 | -------------------------------------------------------------------------------- /src/templates/_components/widgets/top-pages/settings.twig: -------------------------------------------------------------------------------- 1 | {# @var craft \craft\web\twig\variables\CraftVariable #} 2 | {# 3 | /** 4 | * Fathom Analytics plugin for Craft CMS 5 | * 6 | * Report Widget Settings 7 | * 8 | * @author Sten Van den Bergh 9 | * @copyright Copyright (c) 2020 Sten Van den Bergh 10 | * @link https://stenvdb.be 11 | * @package FathomAnalytics 12 | * @since 1.0.0 13 | */ 14 | #} 15 | 16 | {% import "_includes/forms" as forms %} 17 | 18 | {% do view.registerAssetBundle("stenvdb\\fathomanalytics\\assetbundles\\fathomanalytics\\FathomAnalyticsAsset") %} 19 | 20 | {% set periodOptions = [ 21 | { label: 'week'|t('fathom-analytics')|title, value: 'week' }, 22 | { label: 'month'|t('fathom-analytics')|title, value: 'month' }, 23 | { label: 'year'|t('fathom-analytics')|title, value: 'year' } 24 | ] %} 25 | 26 | {{ forms.selectField({ 27 | label: 'period'|t('fathom-analytics')|title, 28 | name: 'period', 29 | options: periodOptions, 30 | value: settings.period, 31 | }) }} 32 | -------------------------------------------------------------------------------- /src/templates/settings.twig: -------------------------------------------------------------------------------- 1 | {# @var craft \craft\web\twig\variables\CraftVariable #} 2 | {# 3 | /** 4 | * Fathom Analytics plugin for Craft CMS 3.x 5 | * 6 | * Fathom Analytics Settings.twig 7 | * 8 | * @author Sten Van den Bergh 9 | * @copyright Copyright (c) 2020 Sten Van den Bergh 10 | * @link https://stenvdb.be 11 | * @package FathomAnalytics 12 | * @since 1.0.0 13 | */ 14 | #} 15 | {% extends '_layouts/cp' %} 16 | {% import "_includes/forms" as forms %} 17 | 18 | {% do view.registerAssetBundle("stenvdb\\fathomanalytics\\assetbundles\\fathomsettings\\SettingsAsset") %} 19 | 20 | {% set title = 'Fathom analytics' | t('fathom-analytics') %} 21 | 22 | {% set tabs = { 23 | "settings": {label: "Settings"|t('fathom-analytics'), url: "#settings"}, 24 | "tracking": {label: "Tracking Code"|t('fathom-analytics'), url: "#tracking"}, 25 | } %} 26 | 27 | {% set selectedTab = 'settings' %} 28 | 29 | {% set content %} 30 | 31 | 32 | {{ redirectInput('settings/plugins/fathom-analytics') }} 33 | 34 |