Technical Documentation: matomo-org/matomo
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
Overview (`README.md`)
Matomo (formerly Piwik) - matomo.org
Code Status
Description
Matomo is the leading Free/Libre open analytics platform.
Matomo is a full-featured PHP MySQL software program that you download and install on your own webserver.
At the end of the five-minute installation process, you will be given a JavaScript code.
Simply copy and paste this tag on websites you wish to track and access your analytics reports in real-time.
Matomo aims to be a Free software alternative to Google Analytics and is already used on more than 1,400,000 websites. Privacy is built-in!
Mission Statement
« To create, as a community, the leading international open source digital analytics platform, that gives every user full control of their data, to empower better decisions. »
Our purpose is to:
« Empower People Ethically »
License
Matomo is released under the GPL v3 (or later) license, see LICENSE.
Requirements
- PHP 8.1.0 or greater
- MySQL version 8.0 or greater, or MariaDB version 10.6 or greater
- PHP extension pdo and pdo_mysql, or the MySQLi extension
- Matomo is OS / server independent
See https://matomo.org/docs/requirements/.
Install Matomo
- Download Matomo
- Upload Matomo to your webserver
- Point your browser to the directory
- Follow the steps
- Add the given JavaScript code to your pages
- (You may also generate fake data to experiment, by enabling the plugin VisitorGenerator)
See https://matomo.org/docs/installation/.
Develop for Matomo
When using Matomo for development you need to install Matomo from the Git repository.
This will also give you access to a DDEV environment you can use. More details can be found in the DDEV README.
Free trial
If you do not have a server or don't want to host yourself, you can use our Matomo Cloud service (21 day free trial): https://matomo.org/start-free-analytics-trial/
Online Demo
Check out the online demo for Matomo at demo.matomo.cloud.
Changelog
For the list of all tickets closed in the current and past releases, see matomo.org/changelog/. For the list of technical changes in the Matomo platform, see developer.matomo.org/changelog.
Get involved!
We believe in liberating Web Analytics, providing a free platform for simple and advanced analytics. Matomo was built by dozens of people like you,
and we need your help to make Matomo better… Why not participate in a useful project today? Learn how you can contribute to Matomo.
Careers at Matomo
We're hiring! Learn more on our Careers page.
Translations
Our translations are managed on Weblate.
Quality Assurance
The Matomo project uses an ever-expanding comprehensive set of thousands of unit tests and hundreds of automated integration tests, system tests, JavaScript tests, and screenshot UI tests, running on a continuous integration server as part of its software quality assurance. Learn more.
We use BrowserStack.com testing tool to help check the Matomo user interface is compatible with many browsers.
Security
Security is a top priority at Matomo. As potential issues are discovered, we validate, patch and release fixes as quickly as we can. We have a security bug bounty program in place that rewards researchers for finding security issues and disclosing them to us.
Learn more or check out our HackerOne program.
Support for Matomo
For Free support, post a message in our community forums: forum.matomo.org
For Professional paid support, purchase a Matomo On-Premise Support Plan: matomo.org/support-plans
Contact
Website: matomo.org
About us: matomo.org/team/
Contact us: matomo.org/contact/
More information
What makes Matomo unique from the competition:
You own your web analytics data: since Matomo is installed on your server, the data is stored in your own database and you can get all the statistics using the powerful Matomo Analytics API.
Matomo is a Free Software which can easily be configured to respect your visitors' privacy.
Modern, easy to use User Interface: you can fully customize your dashboard, drag and drop widgets and more.
Matomo features are built inside plugins: you can add new features and remove the ones you don’t need.
You can build your own web analytics plugins or hire a consultant to have your custom feature built-in Matomo.A vibrant international Open community of more than 200,000 active users (tracking even more websites!)
Advanced Web Analytics capabilities such as E-commerce Tracking, Goal tracking, Campaign tracking,
Custom Variables, Email Reports, Custom Segment Editor, Geo Location, Real-time visits and maps, and a lot more!
Documentation and more info on https://matomo.org.
We are together creating the best open analytics platform in the world!
Overview (`.ddev/README.md`)
Matomo DDEV environment
WARNING: Matomo DDEV environment can be helpful when developing for Matomo, to be able to create or test new plugins locally, to run unit, integration, system or UI tests, but it is highly discouraged to use DDEV environments to run or host publicly accessible production installations of Matomo.
Prerequisites
Before you begin, make sure you have DDEV installed. If you don't have it yet, follow the installation instructions in the official DDEV documentation.
If you don't have a Docker provider already available, you will need to start by choosing one. We recommend installing Rancher Desktop, as it is free and open source.
By default, Rancher Desktop will use ports 80 and 443 for Traefik router for Kubernetes. To make these ports available to DDEV, we recommend disabling Traefik by unchecking the checkbox: Preferences > Kubernetes > Enable Traefik.
Setup steps
1. Start the environment
Once DDEV is installed, navigate to your project directory and run:
ddev startThis command will start the DDEV environment for local development. By default, your local environment will be accessible at https://matomo.ddev.site.
You can also open this URL directly in your browser by running:
ddev launchThis will automatically open the default browser and take you to the Matomo setup page.
2. Set up Matomo using the UI installer
Follow the on-screen instructions in the UI installer to complete the Matomo setup. This will configure the necessary database and settings for your local instance.
3. Set up the development environment
After Matomo is set up, you can initialize the development environment by running:
ddev matomo:init:devThis command will set up the environment for development, installing the additional dependencies required.
We have also added a parameter to the ddev matomo:init:dev command to enable source maps for Vue components.
To enable them, you can run:
ddev matomo:init:dev --with-sourcemapsYou can also disable sourcemaps at any time if you want to, just run the default command again:
ddev matomo:init:devNOTE: You should tick 'Disable cache' on your browser developer tools to see the changes.
To see help:
ddev help matomo:init:dev4. Set up the testing environment
After Matomo is set up, you can initialize the testing environment by running:
ddev matomo:init:testsThis command will set up the environment for running tests, ensuring everything is in place for the UI and other automated tests.
Usage
The command ddev matomo:console provides access to all Matomo console commands. Some useful commands include:
core:archive– Run local archivinggenerate:plugin- Generate a new plugin/theme including all needed filestests:run– Run unit, integration, and system teststests:run-ui– Run UI teststests:run-js– Run tracker JavaScript teststests:run-vue– Run Vue component unit testscache:clear- Remove all caches, including CSS and JavaScriptvue:build- Builds vue modules for one or more plugins
To mount local plugin repositories into Matomo without symlinks, use the host commands:
ddev matomo:plugins:mount
ddev matomo:plugins:mount ../plugin-FormAnalytics ../plugin-HeatmapSessionRecording
ddev matomo:plugins:mount ../ '^.*/plugin-(FormAnalytics|Funnels)$'
ddev matomo:plugins:unmount FormAnalytics
ddev matomo:plugins:unmountddev matomo:plugins:mount creates a local-only .ddev/docker-compose.local-plugins.yaml, adds bind mounts for discovered plugin-* directories, and restarts DDEV automatically. If you pass a plugin directory path directly, the command reads the plugin name from plugin.json when present and otherwise infers it from directory names like plugin-FormAnalytics.
When a plugin declares require.php in plugin.json, the mount command compares it with the PHP version currently used by DDEV. Simple constraints such as >=7.2.0 are enforced and incompatible plugins are skipped. More complex Composer-style expressions are only warned about and the plugin is mounted anyway.
ddev matomo:plugins:unmount removes one or more managed plugin mounts by plugin name. If all managed mounts are removed, the generated compose override is deleted automatically.
For more information about Matomo development, check out the official Matomo Developer Documentation.
Syncing test files from CI
When a build produces different UI screenshots or system test files than your checkout expects, you can copy the files CI generated back into your working copy:
ddev matomo:artifacts:sync-screenshots 12345 'Marketplace_.*'
ddev matomo:artifacts:sync-system-tests 12345 -eThe first argument is the build number, and both commands accept -r to sync a plugin repository, eg -r innocraft/plugin-FormAnalytics.
Syncing from matomo-org/matomo needs no setup, because core's own artifacts are public. Every other repository is protected, plugin repositories under matomo-org included, so the commands look up credentials whenever you point them somewhere else with -r. The credentials come from git, so they are stored wherever your credential helper keeps them and never appear in a file in the repository, in the process list or in your shell history — the password is handed to the console over STDIN.
Store them once per machine:
ddev matomo:artifacts:loginWhich credential helper git uses is your own configuration, exactly as it is for git push. Pick one that keeps the password in your operating system's credential store rather than in a file:
git config --global credential.helper osxkeychain # macOS
git config --global credential.helper manager # Windows
git config --global credential.helper libsecret # LinuxGit ships the helpers for macOS and Windows, so nothing needs installing there. On Debian and Ubuntu the libsecret helper ships as source in /usr/share/doc/git/contrib/credential/libsecret and has to be built once. Inside WSL2, point git at the Windows credential manager instead. Note that git config --global credential.helper store writes ~/.git-credentials in plain text, so it is a poor choice for this.
Without a helper configured, git accepts the credentials and silently stores nothing, so ddev matomo:artifacts:login reads them back afterwards and tells you if that happened rather than reporting a success you did not get.
These host commands are covered by tests/shell/ddev-artifacts-commands.sh, which needs neither DDEV nor credentials to run and is run by CI on Linux, macOS and Windows.
The console commands behind these wrappers, tests:sync-ui-screenshots and development:sync-system-test-processed, can also be run through ddev matomo:console directly. They take the password as --http-password, or read it from STDIN with --http-password-stdin.
Update PHP or MySQL version
You can create .ddev/config.local.yaml to adjust the environment configuration. This file will be automatically ignored by git.
For example, you can adjust php_version to 8.4. All configuration from .ddev/config.yaml can be overridden. Check DDEV documentation for more details.
From your host, run ddev restart for the changes to take effect.
Generating testing data locally
To see some visits within your local Matomo instance, you don't need a website with the JavaScript tracking code installed. Instead, you can generate sample visits using the VisitorGenerator plugin.
Start by creating a site in the Matomo Dashboard. Then, run the following commands on your host machine:
ddev matomo:console development:enable
ddev matomo:console plugin:activate VisitorGenerator
ddev matomo:console visitorgenerator:generate-visitsKnown issues
Currently, the screenshots generated by UI tests do not match those generated in the CI environment. However, this does not affect the functionality of the tests themselves. The screenshots should still be good enough to verify that your changes or plugin are working correctly.
Overview (`js/README.md`)
Introduction
The js/ folder contains:
- index.php - a servlet described below
- piwik.js - the uncompressed piwik.js source for you to study or reference
- README.md - this documentation file
Why Use "js/index.php"?
js/index.php (or implicitly as "js/") can be used to serve up the minified
piwik.js- it supports conditional-GET and Last-Modified, so piwik.js can be cached
by the browser - it supports deflate/gzip compression if your web server (e.g., Apache
without mod_deflate or mod_gzip), shrinking the data transfer to 8K
- it supports conditional-GET and Last-Modified, so piwik.js can be cached
js/index.php (or implicitly as "js/") can also act as a proxy to matomo.php
If you are concerned about the impact of browser-based privacy filters which
attempt to block tracking, you can change your tracking code to use "js/"
instead of "piwik.js" and "matomo.php", respectively.Note that in order for Page Overlay to work, the Piwik tracker method
setAPIUrl()needs to be called with its parameter pointing to the root directory of Piwik. E.g.:js_paq.push(['setAPIUrl', u]);
Deployment
To minify core JavaScript you may run the following command (with Development Mode enabled):
$ ./console development:build-tracker-js(Note: this command can also be used to minify plugin tracker JavaScript.)
To manually minify the file:
piwik.js is minified using YUICompressor 2.4.8.
To install YUICompressor run:bash$ cd /path/to/piwik/js/ $ wget https://github.com/yui/yuicompressor/releases/download/v2.4.8/yuicompressor-2.4.8.zip $ unzip yuicompressor-2.4.8.zipTo compress the code containing the evil "eval", run:
bash$ cd /path/to/piwik/js/ $ sed '/<DEBUG>/,/<\/DEBUG>/d' < piwik.js | sed 's/eval/replacedEvilString/' | java -jar yuicompressor-2.4.8.jar --type js --line-break 1000 | sed 's/replacedEvilString/eval/' | sed 's/^[/][*]/\/*!/' > piwik.min.js && cp piwik.min.js ../piwik.js && cp piwik.min.js ../matomo.jsThis will generate the minify /path/to/piwik/js/piwik.min.js and copy it to
/path/to/piwik/piwik.js. Both "js/piwik.min.js" and "piwik.js" need to be committed.We recommend to execute this command under Linux. It has not been tested with Windows and
MacOS might add a trailing newline which fails tests.
VisitorGeolocatorTestIn a production environment, the tests/javascript folder is not used and can
be removed (if present).We use /*! to include Piwik's license header in the minified source. Read
Stallman's "The JavaScript Trap" for more information.Information about the current version number you have installed can be found under What version of Piwik do I have?.
Overview (`lang/README.md`)
Contribute
If you want to improve an existing Matomo translation or contribute a new translation, please have a look at our Weblate project.
We likely won't accept pull requests for translations on GitHub since we manage all translations in this separate system. Translations will automatically be transferred to GitHub from time to time!
If you have any questions feel free to contact the team at [email protected].
Overview (`libs/README.md`)
Legal notice
See the LEGALNOTICE file.
Matomo modifications to libs/
In general, bug fixes and improvements are reported upstream. Until these are
included upstream, we maintain a list of bug fixes and local mods made to
third-party libraries:
- HTML/Quickform2/
- in r2626, php 5.1.6 incompatibility
- in r3040, exception classes don't follow PEAR naming convention
- Zend/
- strip require_once (to support autoloading)
- fix #6980 ("Array to string conversion") in
Zend/Session/Exception.php - various adjustments to remove unused parts of Zend Framework
- Make sure sessions work when storing notifications
- materialize/
- in materialize.min.css removed the loading of Roboto fonts as the paths do not match and couldn't be loaded. Also
we do not want to load as many different font styles of Roboto font.
- in materialize.min.css removed the loading of Roboto fonts as the paths do not match and couldn't be loaded. Also
Overview (`libs/Authenticator/README.md`)
Google Authenticator PHP class
- Copyright (c) 2012-2016, http://www.phpgangsta.de
- Author: Michael Kliewe, @PHPGangsta and contributors
- Licensed under the BSD License.
This PHP class can be used to interact with the Google Authenticator mobile app for 2-factor-authentication. This class
can generate secrets, generate codes, validate codes and present a QR-Code for scanning the secret. It implements TOTP
according to RFC6238
For a secure installation you have to make sure that used codes cannot be reused (replay-attack). You also need to
limit the number of verifications, to fight against brute-force attacks. For example you could limit the amount of
verifications to 10 tries within 10 minutes for one IP address (or IPv6 block). It depends on your environment.
Usage:
See following example:
<?php
require_once 'PHPGangsta/GoogleAuthenticator.php';
$ga = new PHPGangsta_GoogleAuthenticator();
$secret = $ga->createSecret();
echo "Secret is: ".$secret."\n\n";
$qrCodeUrl = $ga->getQRCodeGoogleUrl('Blog', $secret);
echo "Google Charts URL for the QR-Code: ".$qrCodeUrl."\n\n";
$oneCode = $ga->getCode($secret);
echo "Checking Code '$oneCode' and Secret '$secret':\n";
$checkResult = $ga->verifyCode($secret, $oneCode, 2); // 2 = 2*30sec clock tolerance
if ($checkResult) {
echo 'OK';
} else {
echo 'FAILED';
}Running the script provides the following output:
Secret is: OQB6ZZGYHCPSX4AK
Google Charts URL for the QR-Code: https://www.google.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth://totp/infoATphpgangsta.de%3Fsecret%3DOQB6ZZGYHCPSX4AK
Checking Code '848634' and Secret 'OQB6ZZGYHCPSX4AK':
OKInstallation:
Use Composer to
install the packageFrom project root directory execute following
composer install
Composer will take care of autoloading
the library. Just include the following at the top of your filerequire_once __DIR__ . '/../vendor/autoload.php';
Run Tests:
- All tests are inside
testsfolder. - Execute
composer installand then run the tests from project root
directory - Run as
phpunit testsfrom the project root directory
ToDo:
- ??? What do you need?
Notes:
If you like this script or have some features to add: contact me, visit my blog, fork this project, send pull requests, you know how it works.
Overview (`misc/package/README.md`)
Matomo Packages
We build the main Matomo packages and Debian package using https://github.com/matomo-org/matomo-package
Overview (`misc/proxy-hide-piwik-url/README.md`)
Matomo Proxy Hide URL
The proxy script has been moved to matomo/tracker-proxy.
Overview (`node_modules/chroma-js/readme.md`)
Chroma.js
Chroma.js is a tiny JavaScript library (8.5kB) for all kinds of color conversions and color scales.
Usage
Initiate and manipulate colors:
chroma('#D4F880').darken().hex(); // #9BC04BWorking with color scales is easy, too:
scale = chroma.scale(['white', 'red']);
scale(0.5).hex(); // #FF7F7FLab/Lch interpolation looks better than than RGB
chroma.scale(['white', 'red']).mode('lab');Custom domains! Quantiles! Color Brewer!!
chroma.scale('RdYlBu').domain(myValues, 7, 'quantiles');And why not use logarithmic color scales once in your life?
chroma.scale(['lightyellow', 'navy']).domain([1, 100000], 7, 'log');Like it?
Why not dive into the API docs (quite short actually), and download chroma.min.js right away.
You can use it in node.js, too!
npm install chroma-jsBuild instructions
To compile the coffee-script source files you have to run
make clean
makeTo run the tests simply run
vows test/*.coffeeSimilar Libraries / Prior Art
- Chromatist
- GrapeFruit (Python)
- colors.py (Python)
- d3.js
Author
Chroma.js is written by Gregor Aisch.
License
Released under BSD license.
Versions prior to 0.4 were released under GPL.
Known issues
- HSI color conversion is experimental and produces weird results sometimes
Further reading
Overview (`node_modules/iframe-resizer/README.md`)
This library enables the automatic resizing of the height and width of both same and cross domain iFrames to fit their contained content. It provides a range of features to address the most common issues with using iFrames, these include:
- Height and width resizing of the iFrame to content size.
- Works with multiple and nested iFrames.
- Domain authentication for cross domain iFrames.
- Provides a range of page size calculation methods to support complex CSS layouts.
- Detects changes to the DOM that can cause the page to resize using MutationObserver.
- Detects events that can cause the page to resize (Window Resize, CSS Animation and Transition, Orientation Change and Mouse events).
- Simplified messaging between iFrame and host page via postMessage.
- Fixes in page links in iFrame and supports links between the iFrame and parent page.
- Provides custom sizing and scrolling methods.
- Exposes parent position and viewport size to the iFrame.
- Provides
onMouseEnterandonMouseLeaveevents for the iFrame. - Works with ViewerJS to support PDF and ODF documents.
Donate
Iframe-resizer is the result of many 100s of hours of work, if you would like to join others in showing support for the continued development of this project, then please feel free to buy me a coffee.
Getting Started
Install
This package can be installed via NPM (npm install iframe-resizer --save).
Usage
The package contains two minified JavaScript files in the js folder. The first (iframeResizer.min.js) is for the page hosting the iFrames. It can be called with via JavaScript:
const iframes = iFrameResize( [{options}], [css selector] || [iframe] );The second file (iframeResizer.contentWindow.min.js) needs placing in the page(s) contained within your iFrame. This file is designed to be a guest on someone else's system, so has no dependencies and won't do anything until it's activated by a message from the containing page.
Typical setup
The normal configuration is to have the iFrame resize when the browser window changes size or the content of the iFrame changes. To set this up you need to configure one of the dimensions of the iFrame to a percentage and tell the library to only update the other dimension. Normally you would set the width to 100% and have the height scale to fit the content.
<style>
iframe {
width: 1px;
min-width: 100%;
}
</style>
<script src="/js/iframeResizer.min.js"></script>
<iframe id="myIframe" src="http://anotherdomain.com/iframe.html"></iframe>
<script>
iFrameResize({ log: true }, '#myIframe')
</script>Note: Using min-width to set the width of the iFrame, works around an issue in iOS that can prevent the iFrame from sizing correctly.
If you have problems, check the troubleshooting section.
Example
To see this working take a look at this example and watch the console.
API Documentation
IFrame-Resizer provides an extensive range of options and APIs for both the parent page and the iframed page.
- Parent Page API
- IFramed Page API
- Use with Libraries and Frameworks
- Troubleshooting
- Upgrade from version 3
- Version history
License
Copyright © 2013-23 David J. Bradshaw -
Licensed under the MIT License
Overview (`node_modules/jquery-mousewheel/README.md`)
jQuery Mouse Wheel Plugin
A jQuery plugin that adds cross-browser mouse wheel support with delta normalization.
In order to use the plugin, simply bind the mousewheel event to an element.
It also provides two deprecated helper methods called mousewheel and unmousewheel
that act just like other event helper methods in jQuery.
The event object is updated with the normalized deltaX and deltaY properties.
In addition, there is a new property on the event object called deltaFactor. Multiply
the deltaFactor by deltaX or deltaY to get the scroll distance that the browser
has reported.
Here is an example of using both the bind and helper method syntax:
// using on
$( "#my_elem" ).on( "mousewheel", function( event ) {
console.log( event.deltaX, event.deltaY, event.deltaFactor );
} );
// using the event helper - not recommended!
$( "#my_elem" ).mousewheel( function( event ) {
console.log( event.deltaX, event.deltaY, event.deltaFactor );
} );The old behavior of adding three arguments (delta, deltaX, and deltaY) to the
event handler is now deprecated and will be removed in later releases.
The Deltas...
The combination of browsers, operating systems, and devices offer a huge range of possible delta values. In fact if the user
uses a trackpad and then a physical mouse wheel the delta values can differ wildly. This plugin normalizes those
values so you get a whole number starting at +-1 and going up in increments of +-1 according to the force or
acceleration that is used. This number has the potential to be in the thousands depending on the device.
Getting the scroll distance
In some use-cases we prefer to have the normalized delta but in others we want to know how far the browser should
scroll based on the users input. This can be done by multiplying the deltaFactor by the deltaX or deltaY
event property to find the scroll distance the browser reported.
The deltaFactor property was added to the event object in 3.1.5 so that the actual reported delta value can be
extracted. This is a non-standard property.
Building the code in the repo & running tests
git clone [email protected]:jquery/jquery-mousewheel.git
cd jquery-mousewheel/
npm install
npm testThe unit tests are very basic sanity checks; improvements welcome.
To fully test the plugin, load test/index.html in each supported
browser and follow the instructions at the top of the file after the unit tests finish.
Overview (`node_modules/jquery.scrollto/README.md`)
jQuery.scrollTo
Lightweight, cross-browser and highly customizable animated scrolling with jQuery
Installation
The plugin requires jQuery 1.8 or higher.
Via bower:
bower install jquery.scrollToVia npm:
npm install jquery.scrolltoVia packagist:
php composer.phar require --prefer-dist flesler/jquery.scrollto "*"Using a public CDN
CDN provided by jsdelivr
<script src="//cdn.jsdelivr.net/npm/[email protected]/jquery.scrollTo.min.js"></script>CDN provided by cdnjs
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery-scrollTo/2.1.3/jquery.scrollTo.min.js"></script>Downloading Manually
If you want the latest stable version, get the latest release from the releases page.
2.0
Version 2.0 has been recently released. It is mostly backwards compatible, if you have any issue first check this link.
If your problem is not solved then go ahead and report the issue.
Usage
jQuery.scrollTo's signature is designed to resemble $().animate().
$(element).scrollTo(target[,duration][,settings]);_element_
This must be a scrollable element, to scroll the whole window use $(window).
_target_
This defines the position to where element must be scrolled. The plugin supports all these formats:
- A number with a fixed position:
250 - A string with a fixed position with px:
"250px" - A string with a percentage (of container's size):
"50%" - A string with a relative step:
"+=50px" - An object with
leftandtopcontainining any of the aforementioned:{left:250, top:"50px"} - The string
"max"to scroll to the end. - A string selector that will be relative to the element to scroll:
".section:eq(2)" - A DOM element, probably a child of the element to scroll:
document.getElementById("top") - A jQuery object with a DOM element:
$("#top")
_settings_
The duration parameter is a shortcut to the setting with the same name.
These are the supported settings:
- axis: The axes to animate:
xy(default),x,y,yx - interrupt: If
truewill cancel the animation if the user scrolls. Default isfalse - limit: If
truethe plugin will not scroll beyond the container's size. Default istrue - margin: If
true, subtracts the margin and border of thetargetelement. Default isfalse - offset: Added to the final position, can be a number or an object with
leftandtop - over: Adds a % of the
targetdimensions:{left:0.5, top:0.5} - queue: If
truewill scroll oneaxisand then the other. Default isfalse - onAfter(target, settings): A callback triggered when the animation ends (jQuery's
complete()) - onAfterFirst(target, settings): A callback triggered after the first axis scrolls when queueing
You can add any setting supported by $().animate() as well:
- duration: Duration of the animation, default is
0which makes it instantaneous - easing: Name of an easing equation, you must register the easing function:
swing - fail(): A callback triggered when the animation is stopped (f.e via
interrupt) - step(): A callback triggered for every animated property on every frame
- progress(): A callback triggered on every frame
- And more, check jQuery's documentation
window shorthand
You can use $.scrollTo(...) as a shorthand for $(window).scrollTo(...).
Changing the default settings
As with most plugins, the default settings are exposed so they can be changed.
$.extend($.scrollTo.defaults, {
axis: 'y',
duration: 800
});Stopping the animation
jQuery.scrollTo ends up creating ordinary animations which can be stopped by calling $().stop() or $().finish() on the same element you called $().scrollTo(), including the window.
Remember you can pass a fail() callback to be called when the animation is stopped.
onAfter and requestAnimationFrame
jQuery.scrollTo has a onAfter callback for work that runs after the animation finishes. It will be called before the scroll event fires. To combat this you can use requestAnimationFrame to do work on the next tick. It is available in many browsers, but you may want to polyfill for the few it does not support.
$.scrollTo(100, {
onAfter: function() {
requestAnimationFrame(function() {
$(".result").addClass("selected");
});
}
});Demo
Check the demo to see every option in action.
Complementary plugins
There are two plugins, also created by me that depend on jQuery.scrollTo and aim to simplify certain use cases.
[jQuery.localScroll](https://github.com/flesler/jquery.localScroll)
This plugin makes it very easy to implement anchor navigation.
If you don't want to include another plugin, you can try using something like this minimalistic gist.
[jQuery.serialScroll](https://github.com/flesler/jquery.serialScroll)
This plugin simplifies the creation of scrolling slideshows.
License
(The MIT License)
Copyright (c) 2007 Ariel Flesler [email protected]
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Overview (`node_modules/jquery/README.md`)
jQuery
jQuery is a fast, small, and feature-rich JavaScript library.
For information on how to get started and how to use jQuery, please see jQuery's documentation.
For source files and issues, please visit the jQuery repo.
If upgrading, please see the blog post for 3.7.1. This includes notable differences from the previous version and a more readable changelog.
Including jQuery
Below are some of the most common ways to include jQuery.
Browser
Script tag
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>Webpack / Browserify / Babel
There are several ways to use Webpack, Browserify or Babel. For more information on using these tools, please refer to the corresponding project's documentation. In the script, including jQuery will usually look like this:
import $ from "jquery";If you need to use jQuery in a file that's not an ECMAScript module, you can use the CommonJS syntax:
var $ = require( "jquery" );AMD (Asynchronous Module Definition)
AMD is a module format built for the browser. For more information, we recommend require.js' documentation.
define( [ "jquery" ], function( $ ) {
} );Node
To include jQuery in Node, first install with npm.
npm install jqueryFor jQuery to work in Node, a window with a document is required. Since no such window exists natively in Node, one can be mocked by tools such as jsdom. This can be useful for testing purposes.
const { JSDOM } = require( "jsdom" );
const { window } = new JSDOM( "" );
const $ = require( "jquery" )( window );Overview (`node_modules/mousetrap/README.md`)
Mousetrap
Mousetrap is a simple library for handling keyboard shortcuts in Javascript.
It is licensed under the Apache 2.0 license.
It is around 2kb minified and gzipped and 4.5kb minified, has no external dependencies, and has been tested in the following browsers:
- Internet Explorer 6+
- Safari
- Firefox
- Chrome
It has support for keypress, keydown, and keyup events on specific keys, keyboard combinations, or key sequences.
Getting started
Include mousetrap on your page before the closing `` tag
html<script src="/path/to/mousetrap.min.js"></script>or install
mousetrapfromnpmand require itjsvar Mousetrap = require('mousetrap');Add some keyboard events to listen for
html<script> // single keys Mousetrap.bind('4', function() { console.log('4'); }); Mousetrap.bind("?", function() { console.log('show shortcuts!'); }); Mousetrap.bind('esc', function() { console.log('escape'); }, 'keyup'); // combinations Mousetrap.bind('command+shift+k', function() { console.log('command shift k'); }); // map multiple combinations to the same callback Mousetrap.bind(['command+k', 'ctrl+k'], function() { console.log('command k or control k'); // return false to prevent default browser behavior // and stop event from bubbling return false; }); // gmail style sequences Mousetrap.bind('g i', function() { console.log('go to inbox'); }); Mousetrap.bind('* a', function() { console.log('select all'); }); // konami code! Mousetrap.bind('up up down down left right left right b a enter', function() { console.log('konami code'); }); </script>
Why Mousetrap?
There are a number of other similar libraries out there so what makes this one different?
- There are no external dependencies, no framework is required
- You are not limited to
keydownevents (You can specifykeypress,keydown, orkeyupor let Mousetrap choose for you). - You can bind key events directly to special keys such as
?or*without having to specifyshift+/orshift+8which are not consistent across all keyboards - It works with international keyboard layouts
- You can bind Gmail like key sequences in addition to regular keys and key combinations
- You can programatically trigger key events with the
trigger()method - It works with the numeric keypad on your keyboard
- The code is well documented/commented
Tests
Unit tests are run with mocha.
Running in browser
View it online to check your browser compatibility. You may also download the repo and open tests/mousetrap.html in your browser.
Running with Node.js
Install development dependencies
shcd /path/to/repo npm installRun tests
shnpm test
Documentation
Full documentation can be found at https://craig.is/killing/mice
Overview (`node_modules/qrcodejs2/README.md`)
QRCode.js
QRCode.js is javascript library for making QRCode. QRCode.js supports Cross-browser with HTML5 Canvas and table tag in DOM.
QRCode.js has no dependencies.
Basic Usages
<div id="qrcode"></div>
<script type="text/javascript">
new QRCode(document.getElementById("qrcode"), "http://jindo.dev.naver.com/collie");
</script>or with some options
var qrcode = new QRCode("test", {
text: "http://jindo.dev.naver.com/collie",
width: 128,
height: 128,
colorDark : "#000000",
colorLight : "#ffffff",
correctLevel : QRCode.CorrectLevel.H
});and you can use some methods
qrcode.clear(); // clear the code.
qrcode.makeCode("http://naver.com"); // make another code.Browser Compatibility
IE6~10, Chrome, Firefox, Safari, Opera, Mobile Safari, Android, Windows Mobile, ETC.
License
MIT License
Contact
twitter @davidshimjs
Overview (`node_modules/sprintf-js/README.md`)
sprintf-js
sprintf-js is a complete open source JavaScript sprintf implementation for the browser and Node.js.
Note: as of v1.1.1 you might need some polyfills for older environments. See Support section below.
Usage
var sprintf = require('sprintf-js').sprintf,
vsprintf = require('sprintf-js').vsprintf
sprintf('%2$s %3$s a %1$s', 'cracker', 'Polly', 'wants')
vsprintf('The first 4 letters of the english alphabet are: %s, %s, %s and %s', ['a', 'b', 'c', 'd'])Installation
NPM
npm install sprintf-jsBower
bower install sprintfAPI
`sprintf`
Returns a formatted string:
string sprintf(string format, mixed arg1?, mixed arg2?, ...)`vsprintf`
Same as sprintf except it takes an array of arguments, rather than a variable number of arguments:
string vsprintf(string format, array arguments?)Format specification
The placeholders in the format string are marked by % and are followed by one or more of these elements, in this order:
- An optional number followed by a
$sign that selects which argument index to use for the value. If not specified, arguments will be placed in the same order as the placeholders in the input string. - An optional
+sign that forces to precede the result with a plus or minus sign on numeric values. By default, only the-sign is used on negative numbers. - An optional padding specifier that says what character to use for padding (if specified). Possible values are
0or any other character preceded by a'(single quote). The default is to pad with spaces. - An optional
-sign, that causessprintfto left-align the result of this placeholder. The default is to right-align the result. - An optional number, that says how many characters the result should have. If the value to be returned is shorter than this number, the result will be padded. When used with the
j(JSON) type specifier, the padding length specifies the tab size used for indentation. - An optional precision modifier, consisting of a
.(dot) followed by a number, that says how many digits should be displayed for floating point numbers. When used with thegtype specifier, it specifies the number of significant digits. When used on a string, it causes the result to be truncated. - A type specifier that can be any of:
%— yields a literal%characterb— yields an integer as a binary numberc— yields an integer as the character with that ASCII valuedori— yields an integer as a signed decimal numbere— yields a float using scientific notationu— yields an integer as an unsigned decimal numberf— yields a float as is; see notes on precision aboveg— yields a float as is; see notes on precision aboveo— yields an integer as an octal numbers— yields a string as ist— yieldstrueorfalseT— yields the type of the argument1v— yields the primitive value of the specified argumentx— yields an integer as a hexadecimal number (lower-case)X— yields an integer as a hexadecimal number (upper-case)j— yields a JavaScript object or array as a JSON encoded string
Features
Argument swapping
You can also swap the arguments. That is, the order of the placeholders doesn't have to match the order of the arguments. You can do that by simply indicating in the format string which arguments the placeholders refer to:
sprintf('%2$s %3$s a %1$s', 'cracker', 'Polly', 'wants')And, of course, you can repeat the placeholders without having to increase the number of arguments.
Named arguments
Format strings may contain replacement fields rather than positional placeholders. Instead of referring to a certain argument, you can now refer to a certain key within an object. Replacement fields are surrounded by rounded parentheses - ( and ) - and begin with a keyword that refers to a key:
var user = {
name: 'Dolly',
}
sprintf('Hello %(name)s', user) // Hello DollyKeywords in replacement fields can be optionally followed by any number of keywords or indexes:
var users = [
{name: 'Dolly'},
{name: 'Molly'},
{name: 'Polly'},
]
sprintf('Hello %(users[0].name)s, %(users[1].name)s and %(users[2].name)s', {users: users}) // Hello Dolly, Molly and PollyNote: mixing positional and named placeholders is not (yet) supported
Computed values
You can pass in a function as a dynamic value and it will be invoked (with no arguments) in order to compute the value on the fly.
sprintf('Current date and time: %s', function() { return new Date().toString() })AngularJS
You can use sprintf and vsprintf (also aliased as fmt and vfmt respectively) in your AngularJS projects. See demo/.
Support
Node.js
sprintf-js runs in all active Node versions (4.x+).
Browser
sprintf-js should work in all modern browsers. As of v1.1.1, you might need polyfills for the following:
String.prototype.repeat()(any IE)Array.isArray()(IE < 9)Object.create()(IE < 9)
YMMV
License
sprintf-js is licensed under the terms of the BSD 3-Clause License.
Notes
1 sprintf doesn't use the typeof operator. As such, the value null is a null, an array is an array (not an object), a date value is a date etc.
Overview (`node_modules/visibilityjs/README.md`)
Visibility.js [](https://travis-ci.org/ai/visibilityjs)
Visibility.js is a wrapper for the Page Visibility API. It hides vendor prefixes and adds high level functions.
Page Visibility API allows you to determine whether your web page is either visible to
a user or hidden in background tab or prerendering. It allows you to use
the page visibility state in JavaScript logic and improve browser performance
by disabling unnecessary timers and AJAX requests, or improve user interface
experience (for example, by stopping video playback or slideshow when user
switches to another browser tab).
Moreover, you can detect if the browser is just prerendering the page while
the user has still not opened the link, and don’t count this as a visit in your
analytics module, or do not run heavy calculations or other actions which will
disable the prerendering.
Page Visibility API is natively supported by all browsers. For old browsers
you can use lib/visibility.fallback.js with focus/blur hack (note that this
hack has an issue: when browser just lose focus but still visible for user, its state will change to [hidden]).
Translations
Документация на русском:
habrahabr.ru/blogs/javascript/125833/
States
Currently the Page Visibility API supports three visibility states:
visible: user has opened the page and works within it.hidden: user has switched to another tab or minimized browser window.prerender: browser is just prerendering a page which may possibly be opened
by the user to make the apparent loading time smaller.
Timers
The main use case for this library is to enable some of the times only when
content is visible to the user, i.e. the ones animating a countdown animation.
Visibility.every(interval, callback) is similar tosetInterval(callback, interval), but calls callback every interval ms only
if the page is visible. For example, let’s create a countdown timer:
Visibility.every(1000, function () {
updateCountdownAnimation();
});You can provide an additional interval which will be used when the page
is hidden. In next example, a check for inbox updates will be run every 1 minute
for a visible page and every 5 minutes for a hidden one:
var minute = 60 * 1000;
Visibility.every(minute, 5 * minute, function () {
checkForEmail();
});When the page becomes visible, if the callback has not been called in longer than
the visible interval, it will be called immediately. In the example above, if you
hid the page for 9 minutes, checkForEmail will get called once while the page is hidden,
and immediately when it is made visible.
Visibility.every returns a timer identifier, much like the setInterval
function. However, it cannot be passed to clearInterval, and you should useVisibility.stop(id) to stop the timer.
var slideshow = Visibility.every(5 * 1000, function () {
nextSlide();
});
$('.stopSlideshow').click(function () {
Visibility.stop(slideshow);
});If the browser does not support the Page Visibility API, Visibility.every will
fall back to setInterval, and callback will be run every interval ms for
both the hidden and visible pages.
Initializers
Another common use case is when you need to execute some actions upon a switch to
particular visibility state.
Waiting until the page becomes visible
Visibility.onVisible(callback) checks current state of the page. If it is
visible now, it will run callback, otherwise it will wait until state changes
to visible, and then run callback.
For example, let’s show an animated notification only when the page is visible,
so if some user opens a page in the background, the animation will delay until
the page becomes visible, i.e. until the user has switched
to a tab with the page:
Visibility.onVisible(function () {
startIntroAnimation();
});If a browser doesn’t support Page Visibility API, Visibility.onVisible
will run the callback immediately.
Wait until the page is opened after prerendering
A web developer can hint a browser (using Prerendering API) that an user
is likely to click on some link (i.e. on a “Next” link in a multi-page article),
and the browser then may prefetch and prerender the page, so that the user will
not wait after actually going via the link.
But you may not want to count the browser prerendering a page as a visitor in
your analytics system. Moreover, the browser will disable prerendering if you
will try to do heavy computations or use audio/video tags on the page. So, you
may decide to not run parts of the code while prerendering and wait until the
user actually opens the link.
You can use Visibility.afterPrerendering(callback) in this cases. For example,
this code will only take real visitors (and not page prerenderings) into
account:
Visibility.afterPrerendering(function () {
Statistics.countVisitor();
});If the browser doesn’t support Page Visibility API,Visibility.afterPrerendering will run callback immediately.
Low-level API
In some cases you may need more low-level methods. For example, you may want to
count the time user has viewed the page in foreground and time it has stayed in
background.
Visibility.isSupported() will return true if browser supports the
Page Visibility API:
if( Visibility.isSupported() ) {
Statistics.startTrackingVisibility();
}Visibility.state() will return a string with visibility state. More states
can be added in the future, so for most cases a simpler Visibility.hidden()
method can be used. It will return true if the page is hidden by any reason.
For example, while prerendering, Visibility.state() will return "prerender",
but Visibility.hidden() will return true.
This code will aid in collecting page visibility statistics:
$(document).load(function () {
if ( 'hidden' == Visibility.state() ) {
Statistics.userOpenPageInBackgroundTab();
}
if ( 'prerender' == Visibility.state() ) {
Statistics.pageIsPrerendering();
}
});And this example will only enable auto-playing when the page is opening as a
visible tab (not a background one):
$(document).load(function () {
if ( !Visibility.hidden() ) {
VideoPlayer.play();
}
});Using Visibility.change(callback) you can listen to visibility state changing
events. The callback takes 2 arguments: an event object and a state name.
Let’s collect some statistics with this events approach:
Visibility.change(function (e, state) {
Statistics.visibilityChange(state);
});Method change returns listener ID. You can use it to unbind listener byVisibility.unbind(id):
var listener = Visibility.change(function (e, state) {
if ( !Visibility.hidden() ) {
VideoPlayer.pause();
}
});
VideoPlayer.onFinish(function () {
Visibility.unbind(listener);
});Methods onVisible and afterPrerendering will also return listener ID,
if they wait visibility state changes. If they execute callback immediately,
they return true if Page Visibility API is supported and false
if they can’t detect visibility state.
var listener = Visibility.onVisible(function () {
notification.takeAttention();
});
notification.onOutOfDate(function () {
if ( typeof(listener) == 'number' ) {
Visibility.unbind(listener);
}
});Packages
Visibility.js is shipped with 4 files:
visibility.core– core module.visibility.timers–everyandstopmethods to setsetIntervaldepend
on visibility state.visibility–visibility.coreandvisibility.timerstogether.visibility.fallback– fallback for browser without Page Visibility API.
It use documentfocus/blurevents, so document become to be hidden,
when browser just lose focus, but still visible for user.
Installing
Available by NPM:
npm install --save visibilityjsContributing
To run tests you need node.js and npm. For example, in Ubuntu run:
shsudo apt-get install nodejs npmNext install npm dependencies:
shnpm installRun all tests:
shnpm testAlso you can see real usage example in integration test
test/integration.html.
Overview (`plugins/AIProviders/README.md`)
AIProviders
Configure AI provider connections and default model settings used by Matomo AI features.
Built-in providers: Anthropic, OpenAI, Google, AWS Bedrock, and a generic custom provider for OpenAI-compatible endpoints.
Configuration
Settings are managed from Administration > System > AI Providers.
The plugin stores the default provider, default capability level, and provider connection settings as Matomo system settings (not shown on the generic plugin settings page). API keys are only returned to the administration UI as masked state, not as secret values.
On a managed environment, the default provider is forced and locked via the [AIProviders] defaultProvider config setting. The AI Providers settings page and its admin menu entry are hidden entirely.
Custom provider and local LLM servers
The generic custom provider talks to any OpenAI-compatible Chat Completions API. This includes hosted OpenAI-compatible services as well as local LLM servers such as Ollama, LM Studio, llama.cpp (llama-server), vLLM and LocalAI, which all expose the same /v1/chat/completions wire format.
Two things matter when configuring it:
API base URL. Enter the URL up to and including the OpenAI-compatible API root. Most often, that is the
/v1path. Matomo appends/chat/completionsitself, so do not include it. Examples:- Ollama:
http://localhost:11434/v1 - LM Studio:
http://localhost:1234/v1 - vLLM / llama.cpp:
http://localhost:8000/v1
- Ollama:
API key. Optional for the custom provider. Many local servers run without authentication, so the key may be left blank.
Model. The "test connection" action probes
GET {base}/modelsand populates the model picker from the result. Pick the model to use. The custom provider has no built-in default model.
Managed credentials
Provider connection settings can also be supplied through namespaced DI values, the [AIProviders] config section, or environment variables instead of the administration UI. Per field, managed values win over the database value:
AIProviders.openaiApiKey[AIProviders]
openaiApiKey = "..." ; or env MATOMO_AIPROVIDERS_OPENAI_API_KEY
custom-providerApiKey = "..." ; or env MATOMO_AIPROVIDERS_CUSTOM_PROVIDER_API_KEY
custom-providerEndpointUrl = "..." ; or env MATOMO_AIPROVIDERS_CUSTOM_PROVIDER_ENDPOINT_URLEndpointUrl only applies to the providers that have an endpoint field at all — the custom provider's base URL and AWS Bedrock's region. The fixed hosted providers (OpenAI, Anthropic, Google) always talk to their own API, so a value supplied for them is ignored.
Credentials supplied this way never appear in the UI as secret values and cannot be edited or removed there.
A managed API key is bound to the endpoint it belongs to. For a provider whose endpoint field is a free-form URL (the custom provider), supply EndpointUrl together with ApiKey: such a key is only ever sent to the endpoint supplied alongside it, and an endpoint stored on the instance is ignored. Without a managed endpoint the provider has no destination and reports as not connected, so do not set ApiKey for a provider whose endpoint the instance should choose itself.
Saving or testing a different endpoint is then rejected with an error naming the config key to set instead. Only the endpoint is bound this way, not the key: a connection test may still submit an API key of its own, which is then the one sent to the managed endpoint. Providers that expand the field into a host they control, such as AWS Bedrock with its region, are unaffected by the key binding.
Supplying EndpointUrl on its own — without an API key — locks the endpoint field the same way, for the custom provider and for Bedrock's region alike: the supplied value is the one used, and saving or testing a different one is rejected rather than silently discarded, so a connection test can never report a pairing that a save would refuse to store. An endpoint the instance had configured before stays in the database untouched and takes effect again once the supplied value is removed.
In a multi-tenant setup the config.ini.php is scoped to each tenant, so the [AIProviders] section is the natural place to give each tenant its own provider credentials and forced default. Environment variables are process-wide and shared across tenants, so prefer the config file when the value must differ per tenant.
Restricted providers and the provider selection allowlist
A managed environment can demote providers to restricted (non-selectable) in the AIProviders.filterAIProviders event via AIProvidersList::setSelectable(). Restricted providers are hidden from every admin surface and can never become the default, but stay registered for completions.
Plugins listed in the allowlist may target a specific provider and model per request even though the default provider is forced.
[AIProviders]
defaultProvider = "openai"
providerSelectionAllowlist[] = "ExamplePlugin"Usage from other plugins
To use a provider from another plugin, call the AIProvider service like so:
use Piwik\Container\StaticContainer;
use Piwik\Plugins\AIProviders\AIRequest;
use Piwik\Plugins\AIProviders\AIProviderService;
$service = StaticContainer::get(AIProviderService::class);
$response = $service->complete(new AIRequest('why is the sky blue, answer in 7 words', 'YourPlugin'));
$text = $response->getText();When a managed environment forces a provider from configuration, the service also ignores caller-provided provider and model overrides unless the calling plugin is on the providerSelectionAllowlist (see above), in which case its requested provider and model are honoured.
AIProviderResponse returns the generated text plus request metadata. toArray() returns:
[
'providerId' => 'openai', // provider identifier used for the request
'providerName' => 'OpenAI', // human-readable provider name
'model' => 'gpt-4.1-mini', // model used
'text' => 'Generated response text.',
'inputTokens' => 42, // input/prompt tokens reported by the provider, or null
'outputTokens' => 12, // output/completion tokens reported by the provider, or null
'cacheReadTokens' => null, // input tokens served from the prompt cache, not in inputTokens, or null
'cacheWriteTokens' => null, // input tokens written to the prompt cache, not in inputTokens, or null
'reasoningLevel' => 'none', // reasoning level used
'webSearchUsed' => false, // whether provider-side web search actually ran
'webSearchRequestCount' => null, // searches performed, or null when none ran / not reported
'webSearchQueries' => [], // queries the model issued, when the provider echoes them
'webSearchCitations' => [], // ['url' => …, 'title' => …, 'domain' => …] per source
'executionTimeMs' => 1234, // total request time in milliseconds, including retries, or null
'stopReason' => 'stop', // provider stop reason, if available, or null
'cost' => null, // USD cost billed by the provider, when it reports one, or null
]stopReason is the provider's own vocabulary, not a normalised one. Anthropic, AWS Bedrock and Google
report end_turn / max_tokens (Google's STOP / MAX_TOKENS are mapped onto those, and SAFETY /RECITATION onto guardrail_intervened); OpenAI reports stop / length on both its grounded and
ungrounded paths. Match against the values of the provider you resolved to, and pass unrecognised ones
through rather than treating them as failures.
Web search (grounding)
Ask the provider to search the web before answering, and read the sources it used:
$response = $service->complete(
(new AIRequest($prompt, 'YourPlugin'))->withWebSearchEnabled(true)
);
if ($response->wasWebSearchUsed()) {
foreach ($response->getWebSearchCitations() as $citation) {
// $citation['url'], $citation['title'], $citation['domain']
}
}Supported by Anthropic, Google and OpenAI. Providers without a web search tool (AWS Bedrock, custom
provider) reject the request with an AIProviderClientException rather than silently answering
ungrounded. Call $service->canUseWebSearch('YourPlugin') first if you need to fall back: it answers
for the provider complete() would actually resolve to, which on a managed instance is the forced
provider rather than the one you asked for.
The model decides whether to search. All three providers let the model choose, and AIProviders does
not override that. A grounded request can come back with no search at all, so readwasWebSearchUsed() and getWebSearchRequestCount() on the response for what actually happened.
Grounded answers arrive in fragments, which AIProviders reassembles: adjacent fragments are one
sentence split at a citation boundary and are joined as-is, while a fragment after a tool block or a
separate output message starts a new sentence and gets a single space. Never a newline, so a boundary
falling inside a JSON string value cannot break getJsonData().
Citation titles and queries are untrusted model output. They are length-capped but otherwise
verbatim, so escape them where you render them. URLs are guaranteed to be http(s).
Grounding is not a marginal cost. Every provider charges per search, and the retrieved page content
is billed as input tokens on top, so a grounded request costs a multiple of the same request
ungrounded rather than a little more. The number of searches is capped atAIRequest::DEFAULT_MAX_WEB_SEARCHES (2) on Anthropic (max_uses) and OpenAI (max_tool_calls);
raise or lower it per request with withMaxWebSearches(). OpenAI also caps the retrieved context
(search_context_size: medium). Google exposes no cap at all and ignores the setting.
Grounded requests are slow (30-90s). The provider timeout defaults to 120s for them (30s otherwise).
That outlasts the default read timeout of every common web server and proxy in front of PHP (nginxfastcgi_read_timeout and Apache Timeout are both 60s), which cut the connection whatever PHP is
configured to allow. So grounded completions are meant for CLI commands and scheduled tasks, which
have nothing in front of them. From a web request, either raise those limits yourself or lower the
timeout with withTimeoutSeconds(), or the connection dies before the provider answers. (PHP's ownmax_execution_time is the lesser worry: on non-Windows SAPIs it does not advance while a cURL
transfer is waiting.) A retryable HTTP status also re-runs the searches, so it multiplies both the wall
time and the per-search fees.
Four provider-specific caveats:
- Google's citation URLs are not publisher URLs. It returns a grounding redirect, so
urlis avertexaisearch.cloud.google.comlink and onlydomain, taken from the chunk title which Google
sets to the publisher host, identifies the publisher. Two sources from the same page therefore do
not deduplicate for Google: count distinctdomain, not distincturl. When the title is not a
hostname,domainis an empty string rather than a guess. - On OpenAI, combining web search with JSON mode is degraded, not native. OpenAI rejects
text.formattogether with web search, sowithJsonResponse()on a grounded request relies on the
prompt instruction alone; handle anullfromgetJsonData(). The search itself still runs. - On Google, web search and JSON mode cannot combine at all, so the request is rejected with an
AIProviderClientException. Gemini would accept it and then answer without searching. Verified
againstgemini-3.1-flash-lite: asking for JSON suppresses grounding two independent ways, throughresponseMimeType: application/jsonand through the JSON instructionwithJsonResponse()adds to
the prompt, so dropping either one does not restore it. Drop one of the two calls;canUseWebSearch()
cannot warn you, because it does not see the request. - Anthropic can pause mid-search. With several searches to run it may end the turn early and expect
the message back to resume. AIProviders is deliberately single-round-trip, so the partial answer is
returned as-is withgetStopReason()reportingpause_turn. Check it before treating a grounded
Anthropic answer as complete.
JSON mode
For structured output, call withJsonResponse() and read the decoded object with getJsonData():
$response = $service->complete(
(new AIRequest($prompt, 'Goals'))->withJsonResponse()
);
$data = $response->getJsonData(); // array, or null if the model did not return valid JSONEach provider asks for JSON the best way it can: response_format (OpenAI-compatible), responseMimeType (Google). And AIProviders always adds a system instruction to return a single JSON object, so it works for providers without a native option (for example Anthropic) too. The model can still occasionally return invalid JSON, so always handle a null from getJsonData().
Conversations (multi-turn, tool calling)
complete() is prompt-in/text-out. When your plugin maintains a back-and-forth conversation and dispatches tool calls itself (as AskMatomo does), use AIConversationRequest with AIProviderService::converse() instead. The service resolves the provider exactly like complete() (forced provider, allowlisted caller selection, then the configured default) and returns one assistant turn per call.
use Piwik\Container\StaticContainer;
use Piwik\Plugins\AIProviders\AIConversationRequest;
use Piwik\Plugins\AIProviders\AIConversationResponse;
use Piwik\Plugins\AIProviders\AIProviderService;
$service = StaticContainer::get(AIProviderService::class);
$messages = [
['role' => 'user', 'content' => [['type' => 'text', 'text' => 'How many visits yesterday?']]],
];
$response = $service->converse(
(new AIConversationRequest($messages, 'YourPlugin'))
->withSystemPrompt($systemPrompt)
->withTools($toolCatalog) // optional; MCP-aligned shape, see below
->withMaxTokens(2048)
);
if ($response->getStopReason() === AIConversationResponse::STOP_TOOL_USE) {
// Run the requested tool_use blocks, append this turn and the tool
// results to $messages, and call converse() again.
}Messages, tools, and the assistant's content all use one provider-agnostic canonical shape documented in CanonicalMessage.php. Each provider translates that shape to and from its own wire format inside converse(). Running tools and appending their results to the history for the next call is the caller's responsibility. The service performs a single round-trip per call.
AIConversationResponse exposes the assistant content blocks (getContent()), the stop reason mapped onto the STOP_* constants (getStopReason()), a text convenience (getText()), token usage, and the raw decoded provider response. Treat an unknown stop reason like STOP_END_TURN. Unlike complete(), an empty text turn is valid here: a turn may consist solely of tool_use blocks.
Not every provider supports conversations. Gate conversational features on availability.
if (!$service->canConverse()) {
// Hide or disable the feature.
}Usage limits and metering (events)
AIProviders posts events around every complete() and converse() provider call so a plugin can limit or bill AI usage (Matomo Cloud does) without AIProviders depending on it. With no listener, nothing changes.
| Event | When | Listener gets |
|---|---|---|
AIProviders.beforeRequest |
Before every complete() / converse() provider call, after the provider is resolved |
AIRequestContext $context, AIRequestDecision $decision |
AIProviders.usage |
After every complete() / converse() provider call: success, empty completion or error |
AIUsage $usage |
AIProviders.getRemainingBudget |
When a caller calls getRemainingBudget($featureKey) before a batch |
string $featureKey, ?int &$budget |
AIProviders.checkFeatureAllowed |
When a caller calls checkFeatureAllowed($featureKey, $payload) before an action |
string $featureKey, array $payload, AIRequestDecision $decision |
- A decision starts as allowed, and listeners can only
deny(), so one listener cannot overrule another's denial. A denied call is never sent: the caller gets anAIQuotaExceededExceptioncarrying the decision. beforeRequestandusageshare one context, whose request ID links them and can serve as a dedupe key.- An exception thrown by a
usagelistener is logged and not passed on, because the call has already been made and paid for. It does stop the listeners after it, so catch your own errors. - Meter
usageevents with outcomesuccessonly. Anerroroutcome may mean nothing was sent or billed, for example when no API key is configured.beforeRequestis also posted byassertRequestAllowed()(with$context->isProbe()true), and no usage follows a probe or a denial. - Connection tests and model listings in the admin UI call the provider directly and post no events.
- Neither event carries prompt or response content.
Callers help listeners by describing the call:
$request = (new AIRequest($prompt, 'YourPlugin'))
->withFeatureKey('YourPlugin.summary') // defaults to 'YourPlugin.default'
->withIdSite($idSite)
->withUsageReference((string) $idQuery) // your own ID for this call
->withMeta(['source' => 'scheduled']); // extra data for listeners, no prompt content
try {
$response = $service->complete($request);
} catch (AIQuotaExceededException $e) {
// Show $e->getMessage(); $e->getDecision() has the used / limit figures.
}assertRequestAllowed($request) asks the listeners the same question before a feature starts, without calling the provider. It does not check whether the provider supports the request.
Providers report what a call used on their response: input, output and prompt-cache token counts (inputTokens excludes cached tokens for every provider), web searches, flatFeeCalls for per-call priced APIs, the provider-billed cost, and free-form providerMeta.
Overview (`plugins/ExamplePlugin/README.md`)
Matomo ExamplePlugin Plugin
Description
Add your plugin description here.
Overview (`plugins/ExampleTheme/README.md`)
Matomo ExampleTheme Theme
Description
Add your theme description here.
Overview (`plugins/ExampleVisualization/README.md`)
Matomo SimpleTableVisualizationExample Plugin
Description
Example for generating a simple visualization.
FAQ
My question?
My answer
Changelog
Here goes the changelog text.
Support
Please direct any feedback to ...
Overview (`node_modules/@materializecss/materialize/README.md`)
MaterializeCSS
Table of Contents
- Quickstart
- Documentation
- Build
- Supported Browsers
- Changelog
- Testing
- Contributing
- Copyright and license
Quickstart:
Read the getting started guide for more information on how to use materialize.
- Download the latest release of materialize directly from GitHub. (Beta)
- Clone the repo:
git clone https://github.com/materializecss/materialize.git - Include the files via jsDelivr.
- Install with npm:
npm install @materializecss/materialize(Beta:npm install @materializecss/materialize@next)
Documentation
The documentation can be found at https://materializecss.github.io/materialize. To run the documentation locally on your machine, you need Node.js installed on your computer.
Running documentation locally
Run these commands to set up the documentation:
git clone https://github.com/materializecss/materialize
cd materialize
npm installThen run npm run dev to compile the documentation. When it finishes, open a new browser window and navigate to localhost:8000/docs. We use BrowserSync to display the documentation.
Documentation for previous releases
Previous releases and their documentation are available for download.
Build
If you want to build materialize.css or materialize.js from the latest commit, you can build the files with the following command after npm install. See package.json to check the current version like 1.0.0.
npm run release -- --oldver=<current_version> --newver=<new_version>Supported Browsers:
Materialize is compatible with:
- Chrome 35+
- Firefox 31+
- Safari 9+
- Opera
- Edge
- IE 11+
Changelog
For changelogs, check out the Releases section of materialize or the CHANGELOG.md.
Testing
We use Jasmine as our testing framework and we're trying to write a robust test suite for our components. If you want to help, here's a starting guide on how to write tests in Jasmine.
Contributing
Check out the CONTRIBUTING document in the root of the repository to learn how you can contribute. You can also browse the help-wanted tag in our issue tracker to find things to do.
Copyright and license
Code Copyright 2023 Materialize. Code released under the MIT license.
Overview (`node_modules/mousetrap/plugins/README.md`)
Plugins
Plugins extend the functionality of Mousetrap. To use a plugin just include the plugin after mousetrap.
<script src="mousetrap.js"></script>
<script src="mousetrap-record.js"></script>Bind dictionary
Allows you to make multiple bindings in a single Mousetrap.bind call.
Global bind
Allows you to set global bindings that work even inside of input fields.
Pause/unpause
Allows you to temporarily prevent Mousetrap events from firing.
Record
Allows you to capture a keyboard shortcut or sequence defined by a user.
Overview (`plugins/Morpheus/fonts/README.md`)
To change / add / remove icons please read the instructions on https://developer.matomo.org/guides/matomo-font.
Overview (`node_modules/mousetrap/plugins/bind-dictionary/README.md`)
Bind Dictionary
This extension overwrites the default bind behavior and allows you to bind multiple combinations in a single bind call.
Usage looks like:
Mousetrap.bind({
'a': function() { console.log('a'); },
'b': function() { console.log('b'); }
});You can optionally pass in keypress, keydown or keyup as a second argument.
Other bind calls work the same way as they do by default.
Overview (`node_modules/mousetrap/plugins/global-bind/README.md`)
Global Bind
This extension allows you to specify keyboard events that will work anywhere including inside textarea/input fields.
Usage looks like:
Mousetrap.bindGlobal('ctrl+s', function() {
_save();
});This means that a keyboard event bound using Mousetrap.bind will only work outside of form input fields, but using Moustrap.bindGlobal will work in both places.
If you wanted to create keyboard shortcuts that only work when you are inside a specific textarea you can do that too by creating your own extension.
Overview (`node_modules/mousetrap/plugins/pause/README.md`)
Pause/unpause
This extension allows Mousetrap to be paused and unpaused without having to reset keyboard shortcuts and rebind them.
Usage looks like:
// stop Mousetrap events from firing
Mousetrap.pause();
// allow Mousetrap events to fire again
Mousetrap.unpause();Overview (`node_modules/mousetrap/plugins/record/README.md`)
Record
This extension lets you use Mousetrap to record keyboard sequences and play them back:
<button onclick="recordSequence()">Record</button>
<script>
function recordSequence() {
Mousetrap.record(function(sequence) {
// sequence is an array like ['ctrl+k', 'c']
alert('You pressed: ' + sequence.join(' '));
});
}
</script>Node Modules Chroma Js Doc Api (`node_modules/chroma-js/doc/api.md`)
Initializing colors
chroma(a, b, c, [a], [mode])
Generic color factory. Returns an instance of chroma.Color. mode defaults to "rgb".
The following calls all return the same color (red #ff0000):
chroma("red");
chroma("#ff0000");
chroma("#f00");
chroma("FF0000");
chroma(255, 0, 0);
chroma([255, 0, 0]);
chroma(0, 1, 0.5, 'hsl');
chroma([0, 1, 0.5], 'hsl');
chroma(0, 1, 1, 'hsv');
chroma("rgb(255,0,0)");
chroma("rgb(100%,0%,0%)");
chroma("hsl(0,100%,50%)");
chroma(53.24, 80.09, 67.20, 'lab');
chroma(53.24, 104.55, 40, 'lch');
chroma(1, 0, 0, 'gl');chroma.hex() / chroma.css()
Returns a color from a hex code or css color. Alias: chroma.css()
chroma.hex("#ff0000");
chroma.hex("red");
chroma.hex("rgb(255, 0, 0)");chroma.*xyz*()
Creates a chroma.Color instance from a specific color space. Shortcut to chroma(…, mode).
chroma.rgb(255, 0, 0);
chroma.hsl(0, 1, 0.5);
chroma.hsv(120, 0.5, 0.5);
chroma.lab(53.24, 80.09, 67.20);
chroma.lch(53.24, 104.55, 40);
chroma.gl(1, 0, 0);chroma.interpolate(color1, color2, f, mode)
Colors can be also be interpolates between two other colors in a given mode.
chroma.interpolate('white', 'black', 0) // #ffffff
chroma.interpolate('white', 'black', 1) // #000000
chroma.interpolate('white', 'black', 0.5) // #7f7f7f
chroma.interpolate('white', 'black', 0.5, 'hsv') // #808080
chroma.interpolate('white', 'black', 0.5, 'lab') // #777777This also works with colors with alpha channel:
chroma.interpolate('rgba(0,0,0,0)', 'rgba(255,0,0,1)', 0.5).css() //"rgba(127.5,0,0,0.5)"chroma.interpolate.bezier(colors)
Colors can be also be interpolates between two other colors in a given mode.
bezInterpolator = chroma.interpolate.bezier(['white', 'yellow', 'red', 'black']);
bezInterpolator(0).hex() // #ffffff
bezInterpolator(0.33).hex() // #ffcc67
bezInterpolator(0.66).hex() // #b65f1a
bezInterpolator(1).hex() // #000000Working with chroma.colors
Here's what you can do with it:
- color.hex|css|rgb|hsv|hsl|lab|lch()
- color.alpha()
- color.darker()
- color.brighter()
- color.saturate()
- color.desaturate()
- color.luminance()
color.*xxx*()
Returns the color components for a specific color space:
chroma('red').hex() // "#FF0000""
chroma('red').rgb() // [255, 0, 0]
chroma('red').hsv() // [0, 1, 1]
chroma('red').hsl() // [0, 1, 0.5]
chroma('red').lab() // [53.2407, 80.0924, 67.2031]
chroma('red').lch() // [53.2407, 104.5517, 39.9990]
chroma('red').rgba() // [255, 0, 0, 1]
chroma('red').css() // "rgb(255,0,0)"
chroma('red').alpha(0.7).css() // "rgba(255,0,0,0.7)"
chroma('red').css('hsl') // "hsl(0,100%,50%)"
chroma('red').alpha(0.7).css('hsl') // "hsla(0,100%,50%,0.7)"
chroma('blue').css('hsla') // "hsla(240,100%,50%,1)"color.alpha()
Returns or sets the colors alpha value.
var red = chroma('red');
red.alpha(0.5);
red.css(); // rgba(255,0,0,0.5);color.darker(*amount*)
Decreases the lightness of the color in Lab color space.
chroma('red').darken().hex() // #BC0000color.brighter(*amount*)
chroma('red').brighten().hex() // #FF603Bcolor.saturate(*amount*)
Returns a more saturated variation of the color.
chroma('#eecc99').saturate().hex() // #fcc973color.desaturate(*amount*)
Returns a less saturated variation of the color.
chroma('red').desaturate().hex() // #ec3d23color.luminance()
Returns the relative luminance of the color, which is a value between 0 (black) and 1 (white).
chroma('black').luminance() // 0
chroma('white').luminance() // 1
chroma('red').luminance() // 0.2126As of version 0.6.2 you can also set the luminance directly:
chroma('#ff0000').luminance(0.4).hex() // #ff8585"Working with color scales
chroma.scale()
Creates a color scale function from the given set of colors.
var scale = chroma.scale(['lightyellow', 'navy']);
scale(0.5); // #7F7FB0Need some advice for good colors? How about using a pre-defined ColorBrewer scale:
chroma.scale('RdYlBu');scale.out()
By default the color scale functions return instances of chroma.Color.
var col = scale(0.5);
col.hex(); // #7F7FB0
col.rgb(); // [127.5, 127.5, 176]Using scale.out() you can configure the color scale to automatically return colors in the desired format.
scale = chroma.scale(['lightyellow', 'navy']).out('hex');
scale(0.5); // "#7F7FB0"scale.mode()
Specify in which color space the colors should be interpolated. Defaults to "rgb". You can use any of the following spaces:
var scale = chroma.scale(['lightyellow', 'navy']);
scale.mode('hsv')(0.5); // #54C08A
scale.mode('hsl')(0.5); // #31FF98
scale.mode('lab')(0.5); // #967CB2
scale.mode('lch')(0.5); // #D26662scale.domain()
You can specify the input range of your data (defaults to [0..1]).
var scale = chroma.scale(['lightyellow', 'navy']).domain([0, 400]);
scale(200); // #7F7FB0Instead of just passing the minimum and maximum values you can specify custom "stops". chroma.js would now return a distinct set of four different colors:
var scale = chroma.scale(['lightyellow', 'navy'])
.domain([0, 100, 200, 300, 400]);
scale(98); // #7F7FB0
scale(99); // #7F7FB0
scale(100); // #AAAAC0
scale(101); // #AAAAC0If you don't want to pick the stops by hand, you can auto-generate a set of N equidistant input classes:
chroma.scale(['#eee', '#900']).domain([0, 400], 7);Don't like linear scales? How about logarithmic stops?
chroma.scale(['#eee', '#900']).domain([1, 1000000], 7, 'log');For more advanced techniques you need the actual dataset
chroma.scale(['#eee', '#900']).domain(values, 5, 'quantiles');
chroma.scale(['#eee', '#900']).domain(values, 5, 'k-means');Calling .domain() with no arguments will return the current domain.
chroma.scale(['white', 'red']).domain([0, 100], 4).domain() // [0, 25, 50, 75, 100]scale.range()
If you need to change the color range after initializing the color scale.
chroma.scale().range(['lightyellow', 'navy']);scale.correctLightness()
As of version 0.5.2 chroma.scale supports automatic lightness correction of color scales.
Important note: The lightness correction only works for sequential color scales, where the input colors are ordered by lightness. So this won’t work for diverging color scales, yet.
chroma.scale(['lightyellow', 'navy']).correctLightness(true);scale.colors(mode='hex')
If your color scale has set a distinct number of classes, scale.colors() can be used to retreive all possible colors generated by this scale.
chroma.scale('RdYlGn').domain([0,1], 5).colors()
// returns ['#a50026', '#f88d52', '#ffffbf', '#86cb66', '#006837']Useful methods
chroma.luminance
Shortcut for the color.luminance()
chroma.luminance('black') // 0
chroma.luminance('white') // 1
chroma.luminance('#ff0000') // 0.2126chroma.contrast(a, b)
Returns the contrast ratio between two given colors. According to the Web Content Accessibility Guidelines the contrast between background and small text should be at least 4.5 : 1.
chroma.contrast('white', 'navy') // 16.00 – ok
chroma.contrast('white', 'yellow') // 1.07 – not ok!Plugins CustomDimensions Docs Faq (`plugins/CustomDimensions/docs/faq.md`)
FAQ
I have a large database, can I install the plugin on the command line?
Yes, this is not only possible but even recommended as the installation may take hours. To do this:
- Execute the command
./console plugin:activate CustomDimensionswithin your Matomo directory
Where can I manage Custom Dimensions?
Custom Dimensions can be managed by clicking on your username or user icon in the top right. There will be a menu
item "Custom Dimensions" within the "Manage" section of the left menu. By clicking on it you can manage Custom Dimensions.
Please note that the permission Admin is required in order to be able to manage them.
Where can I find the Id for a Custom Dimension?
You can find them by going to the "Manage Custom Dimensions" page in your personal area. For each dimension you will
find the Id in the table that lists all available Custom Dimensions.
How do I set a value for a dimension in the JavaScript Tracker?
Please have a look at the JavaScript Tracker guide for Custom Dimensions.
How do I set a value for a dimension in the PHP Tracker?
$tracker->setCustomDimension($customDimensionId, $value);
Please note custom tracking parameters are cleared after each tracking request. If you want to keep the same
Custom Dimensions over all request make sure to call this method before each tracking call.
I have configured all available Custom Dimension slots, can I add more?
Yes, this is possible. To make a new Custom Dimension slot available execute the following command including the scope option:
./console customdimensions:add-custom-dimension --scope=action
./console customdimensions:add-custom-dimension --scope=visitBe aware that this can take a long time depending on the size of your database as it requires MySQL schema changes.
You can directly create multiple Custom Dimension slots. To do this add the option --count=X. Usually it doesn't take much
longer to create directly multiple new slots.
Is it possible to delete a Custom Dimension and all of its data?
In the UI it is only possible to deactivate a dimension. However, on the command line you can remove a Custom Dimension
and report it's log data by executing the following console command:
./console customdimensions:remove-custom-dimension --scope=$scope --index=$indexMake sure to replace $scope and $index with the correct values. To get a list of all available indexes execute ./console customdimensions:info.
Removing a Custom Dimension may take a long time as it requires MySQL schema changes. Currently, only log data is removed. Archived reports will be
not deleted currently.
Plugins TrackingSpamPrevention Docs Faq (`plugins/TrackingSpamPrevention/docs/faq.md`)
FAQ
How do I allow specific IPs to not be blocked?
Say you are using AWS to replay your traffic using log analytics. When you have cloud provider IP range blocking enabled, all the requests from your AWS would be blocked. However, you can specifically allow your own IPs to be allowed and not blocked using the "IP allow list" setting in "Administration => General Settings". Enter one IP address or CIDR range per line.
Alternatively, you can force a list of allowed IP ranges by editing your config/config.ini.php file like this (this overrides the setting, and the field is hidden from the UI while the override is in place):
[TrackingSpamPrevention]
ip_allow_list[] = "127.0.0.1/32"
ip_allow_list[] = "192.168.0.0/21"Make sure to enter a valid IP range.
How do I block specific IPs from being tracked?
Use the "IP block list" setting in "Administration => General Settings". Enter one IP address or CIDR range per line. If an address matches both the allow list and the block list, the allow list takes precedence.
Alternatively, you can force a list of blocked IP ranges by editing your config/config.ini.php file like this (this overrides the setting, and the field is hidden from the UI while the override is in place):
[TrackingSpamPrevention]
ip_block_list[] = "203.0.113.88/32"
ip_block_list[] = "198.51.100.0/24"What happens when it fails to synchronise public IPs from cloud providers?
Any error is currently ignored and if it does not synchronise successfully, then the IP for the provider that failed are not synced.
To be aware when such an error happens you can enable the following setting:
[TrackingSpamPrevention]
block_cloud_sync_throw_exception_on_error = 1It is disabled by default as it could stop other scheduled tasks from being executed.
How can I block specific organisations from being tracked?
This can be useful if you are receiving spam requests from a provider that isn't automatically detected yet by this plugin.
For this to work a geolocation provider must be enabled, and "Block tracking requests from Cloud hosting providers" in "Administration => General Settings" must be set to "Use custom organisation list". The "Organisation block list" starts from whatever list is already stored for your installation, which is Matomo's default set of hosting and datacenter providers until something changes it. Extend or trim it as needed. Note that a stored list stops following Matomo's maintained one, so if you find yourself on this option after upgrading and did not choose it, selecting "Use Matomo's default provider list" puts you back on the list Matomo keeps up to date.
The other two options need no list of your own: "Use Matomo's default provider list" always matches against the current default set, and "Do not block Cloud hosting providers" turns organisation matching off. Your custom list is kept either way, so it is still there when you switch back.
Alternatively, you can execute a command to block a new organisation like this:
./console trackingspamprevention:block-geo-ip-organisation --organisation-name="Example"When "Use Matomo's default provider list" is selected the command switches to the custom organisation list, so that the organisation it adds is actually matched. When blocking is turned off it leaves that alone and tells you the entry has no effect until you select the custom list.
You can also force a list of blocked organisations by editing your config/config.ini.php file like this (this overrides the setting, and the field is hidden from the UI while the override is in place):
[TrackingSpamPrevention]
organisation_block_list[] = "ExampleOrg"
organisation_block_list[] = "another example"An overridden list is used whenever organisation blocking is on, including under "Use Matomo's default provider list".
Each organisation will be compared lower case and the organisation only needs to contain the configured value, it does not need to match it exactly.
You can find out the organisation name for an IP address by visiting the website of your geolocation database and using their demo tool.
--- METRICS ---
- Files Extracted: 31
- Estimated Token Budget: ~25593 tokens
- Canonical Reference: https://codewiki.google/github.com/matomo-org/matomo