diff --git a/.github/workflows/buildvue.yml b/.github/workflows/buildvue.yml
index fe783e32f51..fe140fdc6d6 100644
--- a/.github/workflows/buildvue.yml
+++ b/.github/workflows/buildvue.yml
@@ -19,6 +19,7 @@ permissions:
jobs:
build:
runs-on: ubuntu-latest
+ name: build vue files
steps:
- name: Detect branch for PR
id: vars
diff --git a/.github/workflows/buildwoff2.yml b/.github/workflows/buildwoff2.yml
index 58fef86f427..ed26b7c5533 100644
--- a/.github/workflows/buildwoff2.yml
+++ b/.github/workflows/buildwoff2.yml
@@ -19,6 +19,7 @@ permissions:
jobs:
build:
runs-on: ubuntu-latest
+ name: build woff2
steps:
- name: Detect branch for PR
id: vars
diff --git a/.github/workflows/composer-update.yml b/.github/workflows/composer-update.yml
index 8bdcf308dc0..088cbd09756 100644
--- a/.github/workflows/composer-update.yml
+++ b/.github/workflows/composer-update.yml
@@ -84,7 +84,7 @@ jobs:
message=
curl \
--request POST \
- --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \
+ --header 'authorization: Bearer ${{ secrets.CUSTOM_ACCESS_TOKEN }}' \
--header 'content-type: application/json' \
--data '{
"title":"[automatic composer updates]",
diff --git a/.github/workflows/matomo-tests.yml b/.github/workflows/matomo-tests.yml
index 154ce2c2440..1ff74e9486a 100644
--- a/.github/workflows/matomo-tests.yml
+++ b/.github/workflows/matomo-tests.yml
@@ -13,6 +13,12 @@ on:
- '**.x-dev'
- 'next_release'
workflow_dispatch:
+ workflow_call:
+ inputs:
+ is_preview:
+ type: boolean
+ required: false
+ default: false
permissions:
actions: read
@@ -34,7 +40,7 @@ jobs:
PHP:
runs-on: ubuntu-20.04
strategy:
- fail-fast: false
+ fail-fast: ${{ inputs.is_preview == true }}
matrix:
type: [ 'UnitTests', 'SystemTestsPlugins', 'SystemTestsCore', 'IntegrationTestsCore', 'IntegrationTestsPlugins' ]
php: [ '7.2', '8.2', '8.3' ]
@@ -60,6 +66,7 @@ jobs:
persist-credentials: false
submodules: true
path: matomo
+ ref: ${{ inputs.is_preview == true && '5.x-preview' || github.ref }}
- name: running tests
uses: matomo-org/github-action-tests@main
with:
@@ -81,6 +88,7 @@ jobs:
persist-credentials: false
submodules: true
path: matomo
+ ref: ${{ inputs.is_preview == true && '5.x-preview' || github.ref }}
- name: running tests
uses: matomo-org/github-action-tests@main
with:
@@ -97,6 +105,7 @@ jobs:
persist-credentials: false
submodules: true
path: matomo
+ ref: ${{ inputs.is_preview == true && '5.x-preview' || github.ref }}
- name: running tests
uses: matomo-org/github-action-tests@main
with:
@@ -106,7 +115,7 @@ jobs:
UI:
runs-on: ubuntu-20.04
strategy:
- fail-fast: false
+ fail-fast: ${{ inputs.is_preview == true }}
matrix:
parts: [ 0,1,2,3 ]
steps:
@@ -116,6 +125,7 @@ jobs:
persist-credentials: false
submodules: true
path: matomo
+ ref: ${{ inputs.is_preview == true && '5.x-preview' || github.ref }}
- name: running tests
uses: matomo-org/github-action-tests@main
with:
diff --git a/.github/workflows/release-preview.yml b/.github/workflows/release-preview.yml
new file mode 100644
index 00000000000..7b6c1930856
--- /dev/null
+++ b/.github/workflows/release-preview.yml
@@ -0,0 +1,169 @@
+# Matomo release action for automated PREVIEW releases
+#
+# Required GitHub secrets:
+#
+# GPG_CERTIFICATE | ASCII armored or Base64 encoded GPG certificate that is used to create the signatures for the archives
+# GPG_CERTIFICATE_PASS | Passphrase of the GPG key
+
+name: Build preview release
+
+permissions:
+ actions: read # required for the tests job
+ checks: none
+ contents: write # required to create tag and release
+ deployments: none
+ issues: read # required for the tests job
+ packages: none
+ pull-requests: read # required for the tests jobs
+ repository-projects: none
+ security-events: none
+ statuses: none
+
+on:
+ # TODO: remove manual dispatch after testing and enable cron
+ workflow_dispatch:
+ branches:
+ - 5.x-dev
+ inputs:
+ password:
+ description: 'Release password'
+ required: true
+ #schedule:
+ # - cron: '0 1 * * *' # 1am daily
+env:
+ RELEASE_PASSWORD: ${{ secrets.RELEASE_PASSWORD }}
+jobs:
+ prepare_preview_version:
+ runs-on: ubuntu-latest
+ outputs:
+ do_release: ${{ steps.changes.outputs.do_release }}
+ has_new_version: ${{ steps.version.outputs.has_new_version }}
+ steps:
+ - name: "Check release password"
+ if: ${{ github.event.inputs.password != env.RELEASE_PASSWORD }}
+ uses: actions/github-script@v6
+ with:
+ script: |
+ core.setFailed('Release password didn\'t match.')
+ - name: "Check if user is allowed"
+ if: ${{ github.actor != 'mattab' && github.actor != 'tsteur' && github.actor != 'sgiehl' && github.actor != 'mneudert' && github.actor != 'michalkleiner' && github.actor != 'caddoo'}}
+ uses: actions/github-script@v6
+ with:
+ script: |
+ core.setFailed('User is not allowed to release.')
+ - uses: actions/checkout@v4
+ with:
+ lfs: false
+ fetch-tags: true
+ fetch-depth: 0
+
+ - name: Prepare git config
+ run: |
+ cat <<- EOF > $HOME/.netrc
+ machine github.com
+ login $GITHUB_ACTOR
+ password $GITHUB_TOKEN
+ machine api.github.com
+ login $GITHUB_ACTOR
+ password $GITHUB_TOKEN
+ EOF
+ chmod 600 $HOME/.netrc
+ git config --global user.email "$GITHUB_ACTOR@users.noreply.github.com"
+ git config --global user.name "$GITHUB_ACTOR"
+ git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Check if there are any changes to create a preview release for
+ id: changes
+ run: |
+ LATEST_PREVIEW=$(git tag --sort=-creatordate | grep -E '\.[0-9]{14}$' | head -n 1)
+
+ DIFF=""
+ if [ -n "$LATEST_PREVIEW" ]; then
+ # using || true to always exit either with a diff or a success exit code to not fail the whole workflow
+ DIFF=$(git diff $LATEST_PREVIEW..5.x-dev --unified=0 | grep -vE "^\+\+\+|---" | grep "^[+-]" | grep -v "public const VERSION = '.*';" || true)
+ fi
+
+ if [ -z "$DIFF" ]; then
+ echo "No changes in 5.x-dev since last preview version was created."
+ DO_RELEASE=0
+ else
+ DO_RELEASE=1
+ fi
+
+ echo "do_release=$DO_RELEASE" >> $GITHUB_OUTPUT
+
+ - name: Determine new preview version number
+ id: version
+ if: steps.changes.outputs.do_release == '1'
+ run: |
+ OLD_VERSION=$(php -r "include_once 'core/Version.php'; echo \Piwik\Version::VERSION;")
+ NEW_VERSION=$(php -r "include_once 'core/Version.php'; \$v = new \Piwik\Version(); echo \$v->nextPreviewVersion(\Piwik\Version::VERSION);")
+
+ if [ "$NEW_VERSION" == "" ]; then
+ HAS_NEW_VERSION=0
+ else
+ HAS_NEW_VERSION=1
+ fi
+
+ echo "OLD_VERSION=$OLD_VERSION" >> $GITHUB_ENV
+ echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_ENV
+
+ echo "has_new_version=$HAS_NEW_VERSION" >> $GITHUB_OUTPUT
+ echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
+
+ - name: Check if the previous version has been released
+ if: steps.changes.outputs.do_release == '1' && steps.version.outputs.has_new_version == '1'
+ run: |
+ TAG_EXISTS=$( git tag --list "$OLD_VERSION" )
+
+ # x.y.z-alpha would not be released, all other versions should have an existing tag (a release)
+ if [[ ! $OLD_VERSION =~ -alpha$ ]] && [[ -z "$TAG_EXISTS" ]]; then
+ echo "$OLD_VERSION (as indicated in core/Version.php) has not been released yet."
+ exit 1
+ fi
+
+ - name: Update 5.x-preview branch to latest 5.x-dev
+ if: steps.changes.outputs.do_release == '1' && steps.version.outputs.has_new_version == '1'
+ run: |
+ git checkout -B 5.x-preview
+
+ - name: Update version file with new version
+ if: steps.changes.outputs.do_release == '1' && steps.version.outputs.has_new_version == '1'
+ run: |
+ sed -i "s/VERSION = '${OLD_VERSION}';/VERSION = '${NEW_VERSION}';/g" core/Version.php
+
+ - name: Commit version file changes
+ if: steps.changes.outputs.do_release == '1' && steps.version.outputs.has_new_version == '1'
+ run: |
+ git add core/Version.php
+ git commit -m "Update version to ${NEW_VERSION}"
+
+ - name: Push changes to 5.x-preview
+ if: steps.changes.outputs.do_release == '1' && steps.version.outputs.has_new_version == '1'
+ run: |
+ git push -f origin 5.x-preview
+
+ run_matomo_tests:
+ needs: [prepare_preview_version]
+ uses: ./.github/workflows/matomo-tests.yml
+ if: |
+ always() &&
+ needs.prepare_preview_version.result == 'success' &&
+ needs.prepare_preview_version.outputs.do_release == '1' &&
+ needs.prepare_preview_version.outputs.has_new_version == '1'
+ with:
+ is_preview: true
+
+ release_preview_version:
+ needs: [run_matomo_tests]
+ uses: ./.github/workflows/release.yml
+ if: |
+ always() &&
+ needs.prepare_preview_version.result == 'success' &&
+ needs.run_matomo_tests.result == 'success' &&
+ needs.prepare_preview_version.outputs.do_release == '1' &&
+ needs.prepare_preview_version.outputs.has_new_version == '1'
+ with:
+ is_preview: true
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index b6fa380ccf2..85a1574b5e2 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -30,6 +30,12 @@ on:
password:
description: 'Release password'
required: true
+ workflow_call:
+ inputs:
+ is_preview:
+ type: boolean
+ required: false
+ default: false
env:
RELEASE_PASSWORD: ${{ secrets.RELEASE_PASSWORD }}
@@ -39,13 +45,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: "Check release password"
- if: ${{ github.event.inputs.password != env.RELEASE_PASSWORD }}
+ if: ${{ inputs.is_preview != true && github.event.inputs.password != env.RELEASE_PASSWORD }}
uses: actions/github-script@v6
with:
script: |
core.setFailed('Release password didn\'t match.')
- name: "Check if user is allowed"
- if: ${{ github.actor != 'mattab' && github.actor != 'tsteur' && github.actor != 'sgiehl' && github.actor != 'mneudert' && github.actor != 'michalkleiner' && github.actor != 'caddoo'}}
+ if: ${{ inputs.is_preview != true && github.actor != 'mattab' && github.actor != 'tsteur' && github.actor != 'sgiehl' && github.actor != 'mneudert' && github.actor != 'michalkleiner' && github.actor != 'caddoo'}}
uses: actions/github-script@v6
with:
script: |
@@ -97,15 +103,21 @@ jobs:
exit 1
fi
- if ! [[ ${GITHUB_REF#refs/heads/} =~ ^[4-9]\.x-dev$ || ${GITHUB_REF#refs/heads/} == "next_release" ]]
+ if ! [[ ${GITHUB_REF#refs/heads/} =~ ^[4-9]\.x-(dev|preview)$ || ${GITHUB_REF#refs/heads/} == "next_release" ]]
then
- echo "A tag can only be created from branches '5.x-dev' and 'next_release'. Please create the tag manually if a release needs to be built from another branch."
+ echo "A tag can only be created from branches '5.x-dev', '5.x-preview' or 'next_release'. Please create the tag manually if a release needs to be built from another branch."
exit 1
fi
if [[ ${GITHUB_REF#refs/heads/} =~ ^[4-9]\.x-dev$ && $version =~ ^[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?$ ]]
then
- echo "Only beta/preview release tags can be created from ${GITHUB_REF#refs/heads/} branch."
+ echo "Only beta release tags can be created from ${GITHUB_REF#refs/heads/} branch."
+ exit 1
+ fi
+
+ if [[ ${GITHUB_REF#refs/heads/} =~ ^[4-9]\.x-preview$ && $version =~ [0-9]{14}$ ]]
+ then
+ echo "Only preview release tags can be created from ${GITHUB_REF#refs/heads/} branch."
exit 1
fi
diff --git a/.github/workflows/submodules.yml b/.github/workflows/submodules.yml
index 9762bcbc665..287448021e9 100644
--- a/.github/workflows/submodules.yml
+++ b/.github/workflows/submodules.yml
@@ -91,7 +91,7 @@ jobs:
run: |
curl \
--request POST \
- --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \
+ --header 'authorization: Bearer ${{ secrets.CUSTOM_ACCESS_TOKEN }}' \
--header 'content-type: application/json' \
--data '{
"title":"[automatic submodule updates]",
diff --git a/composer.lock b/composer.lock
index 745a35e3cdf..615eb83de93 100644
--- a/composer.lock
+++ b/composer.lock
@@ -713,12 +713,12 @@
"source": {
"type": "git",
"url": "https://github.com/matomo-org/referrer-spam-list.git",
- "reference": "a214dcf1266141623aaed95ab14af4a64da6b7bb"
+ "reference": "d9c2fa740ac2055f82240130518b33213b5beb1d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/matomo-org/referrer-spam-list/zipball/a214dcf1266141623aaed95ab14af4a64da6b7bb",
- "reference": "a214dcf1266141623aaed95ab14af4a64da6b7bb",
+ "url": "https://api.github.com/repos/matomo-org/referrer-spam-list/zipball/d9c2fa740ac2055f82240130518b33213b5beb1d",
+ "reference": "d9c2fa740ac2055f82240130518b33213b5beb1d",
"shasum": ""
},
"replace": {
@@ -736,7 +736,7 @@
"issues": "https://github.com/matomo-org/referrer-spam-list/issues",
"source": "https://github.com/matomo-org/referrer-spam-list/tree/master"
},
- "time": "2024-07-11T22:09:32+00:00"
+ "time": "2024-07-29T20:28:18+00:00"
},
{
"name": "matomo/searchengine-and-social-list",
@@ -1714,16 +1714,16 @@
},
{
"name": "symfony/console",
- "version": "v5.4.41",
+ "version": "v5.4.42",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "6473d441a913cb997123b59ff2dbe3d1cf9e11ba"
+ "reference": "cef62396a0477e94fc52e87a17c6e5c32e226b7f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/6473d441a913cb997123b59ff2dbe3d1cf9e11ba",
- "reference": "6473d441a913cb997123b59ff2dbe3d1cf9e11ba",
+ "url": "https://api.github.com/repos/symfony/console/zipball/cef62396a0477e94fc52e87a17c6e5c32e226b7f",
+ "reference": "cef62396a0477e94fc52e87a17c6e5c32e226b7f",
"shasum": ""
},
"require": {
@@ -1793,7 +1793,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v5.4.41"
+ "source": "https://github.com/symfony/console/tree/v5.4.42"
},
"funding": [
{
@@ -1809,7 +1809,7 @@
"type": "tidelift"
}
],
- "time": "2024-06-28T07:48:55+00:00"
+ "time": "2024-07-26T12:21:55+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -1880,16 +1880,16 @@
},
{
"name": "symfony/error-handler",
- "version": "v5.4.41",
+ "version": "v5.4.42",
"source": {
"type": "git",
"url": "https://github.com/symfony/error-handler.git",
- "reference": "c25da5cc2de4e6f96b3a0a2813050355a20dd0e1"
+ "reference": "db15ba0fd50890156ed40087ccedc7851a1f5b76"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/error-handler/zipball/c25da5cc2de4e6f96b3a0a2813050355a20dd0e1",
- "reference": "c25da5cc2de4e6f96b3a0a2813050355a20dd0e1",
+ "url": "https://api.github.com/repos/symfony/error-handler/zipball/db15ba0fd50890156ed40087ccedc7851a1f5b76",
+ "reference": "db15ba0fd50890156ed40087ccedc7851a1f5b76",
"shasum": ""
},
"require": {
@@ -1931,7 +1931,7 @@
"description": "Provides tools to manage errors and ease debugging PHP code",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/error-handler/tree/v5.4.41"
+ "source": "https://github.com/symfony/error-handler/tree/v5.4.42"
},
"funding": [
{
@@ -1947,7 +1947,7 @@
"type": "tidelift"
}
],
- "time": "2024-06-09T18:59:35+00:00"
+ "time": "2024-07-23T12:34:05+00:00"
},
{
"name": "symfony/event-dispatcher",
@@ -2115,16 +2115,16 @@
},
{
"name": "symfony/http-foundation",
- "version": "v5.4.40",
+ "version": "v5.4.42",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "cf4893ca4eca3fac4ae06da1590afdbbb4217847"
+ "reference": "9c375b2abef0b657aa0b7612b763df5c12a465ab"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/cf4893ca4eca3fac4ae06da1590afdbbb4217847",
- "reference": "cf4893ca4eca3fac4ae06da1590afdbbb4217847",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9c375b2abef0b657aa0b7612b763df5c12a465ab",
+ "reference": "9c375b2abef0b657aa0b7612b763df5c12a465ab",
"shasum": ""
},
"require": {
@@ -2171,7 +2171,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-foundation/tree/v5.4.40"
+ "source": "https://github.com/symfony/http-foundation/tree/v5.4.42"
},
"funding": [
{
@@ -2187,20 +2187,20 @@
"type": "tidelift"
}
],
- "time": "2024-05-31T14:33:22+00:00"
+ "time": "2024-07-26T11:59:59+00:00"
},
{
"name": "symfony/http-kernel",
- "version": "v5.4.41",
+ "version": "v5.4.42",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "aad4078e1210343b7cd5acb803c02f8b02f002b2"
+ "reference": "948db7caf761dacc8abb9a59465f0639c30cc6dd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/aad4078e1210343b7cd5acb803c02f8b02f002b2",
- "reference": "aad4078e1210343b7cd5acb803c02f8b02f002b2",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/948db7caf761dacc8abb9a59465f0639c30cc6dd",
+ "reference": "948db7caf761dacc8abb9a59465f0639c30cc6dd",
"shasum": ""
},
"require": {
@@ -2284,7 +2284,7 @@
"description": "Provides a structured process for converting a Request into a Response",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-kernel/tree/v5.4.41"
+ "source": "https://github.com/symfony/http-kernel/tree/v5.4.42"
},
"funding": [
{
@@ -2300,7 +2300,7 @@
"type": "tidelift"
}
],
- "time": "2024-06-28T11:42:41+00:00"
+ "time": "2024-07-26T14:46:22+00:00"
},
{
"name": "symfony/monolog-bridge",
@@ -3025,16 +3025,16 @@
},
{
"name": "symfony/string",
- "version": "v5.4.41",
+ "version": "v5.4.42",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "065a9611e0b1fd2197a867e1fb7f2238191b7096"
+ "reference": "909cec913edea162a3b2836788228ad45fcab337"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/065a9611e0b1fd2197a867e1fb7f2238191b7096",
- "reference": "065a9611e0b1fd2197a867e1fb7f2238191b7096",
+ "url": "https://api.github.com/repos/symfony/string/zipball/909cec913edea162a3b2836788228ad45fcab337",
+ "reference": "909cec913edea162a3b2836788228ad45fcab337",
"shasum": ""
},
"require": {
@@ -3091,7 +3091,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v5.4.41"
+ "source": "https://github.com/symfony/string/tree/v5.4.42"
},
"funding": [
{
@@ -3107,20 +3107,20 @@
"type": "tidelift"
}
],
- "time": "2024-06-28T09:20:55+00:00"
+ "time": "2024-07-20T18:38:32+00:00"
},
{
"name": "symfony/var-dumper",
- "version": "v5.4.40",
+ "version": "v5.4.42",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "af8868a6e9d6082dfca11f1a1f205ae93a8b6d93"
+ "reference": "0c17c56d8ea052fc33942251c75d0e28936e043d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/af8868a6e9d6082dfca11f1a1f205ae93a8b6d93",
- "reference": "af8868a6e9d6082dfca11f1a1f205ae93a8b6d93",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0c17c56d8ea052fc33942251c75d0e28936e043d",
+ "reference": "0c17c56d8ea052fc33942251c75d0e28936e043d",
"shasum": ""
},
"require": {
@@ -3180,7 +3180,7 @@
"dump"
],
"support": {
- "source": "https://github.com/symfony/var-dumper/tree/v5.4.40"
+ "source": "https://github.com/symfony/var-dumper/tree/v5.4.42"
},
"funding": [
{
@@ -3196,7 +3196,7 @@
"type": "tidelift"
}
],
- "time": "2024-05-31T14:33:22+00:00"
+ "time": "2024-07-26T12:23:09+00:00"
},
{
"name": "szymach/c-pchart",
diff --git a/core/Config/DatabaseConfig.php b/core/Config/DatabaseConfig.php
index c14a135fc17..24cc2610b9a 100644
--- a/core/Config/DatabaseConfig.php
+++ b/core/Config/DatabaseConfig.php
@@ -15,4 +15,9 @@ public static function getSectionName(): string
{
return 'database';
}
+
+ public static function isTiDb(): bool
+ {
+ return self::getConfigValue('schema') === 'Tidb';
+ }
}
diff --git a/core/DataAccess/ArchiveTableDao.php b/core/DataAccess/ArchiveTableDao.php
index 6ec040ef787..41ab3094fac 100644
--- a/core/DataAccess/ArchiveTableDao.php
+++ b/core/DataAccess/ArchiveTableDao.php
@@ -36,14 +36,14 @@ class ArchiveTableDao
*/
public function getArchiveTableAnalysis($tableDate)
{
- $numericQueryEmptyRow = array(
+ $numericQueryEmptyRow = [
'count_archives' => '-',
'count_invalidated_archives' => '-',
'count_temporary_archives' => '-',
'count_error_archives' => '-',
'count_segment_archives' => '-',
'count_numeric_rows' => '-',
- );
+ ];
$tableDate = str_replace("`", "", $tableDate); // for sanity
@@ -60,7 +60,7 @@ public function getArchiveTableAnalysis($tableDate)
SUM(CASE WHEN name NOT LIKE 'done%' THEN 1 ELSE 0 END) AS count_numeric_rows,
0 AS count_blob_rows
FROM `$numericTable`
- GROUP BY idsite, date1, date2, period";
+ GROUP BY idsite, date1, date2, period ORDER BY idsite, period, date1, date2";
$rows = Db::fetchAll($sql, array(ArchiveWriter::DONE_INVALIDATED, ArchiveWriter::DONE_OK_TEMPORARY,
ArchiveWriter::DONE_ERROR, ArchiveWriter::DONE_ERROR_INVALIDATED));
@@ -76,14 +76,21 @@ public function getArchiveTableAnalysis($tableDate)
COUNT(*) AS count_blob_rows,
SUM(OCTET_LENGTH(value)) AS sum_blob_length
FROM `$blobTable`
- GROUP BY idsite, date1, date1, period";
+ GROUP BY idsite, date1, date2, period ORDER BY idsite, period, date1, date2";
foreach (Db::fetchAll($sql) as $blobStatsRow) {
$label = $blobStatsRow['label'];
+
if (isset($result[$label])) {
$result[$label] = array_merge($result[$label], $blobStatsRow);
} else {
- $result[$label] = $blobStatsRow + $numericQueryEmptyRow;
+ // ensure rows without numeric entries have the
+ // same internal result array key order
+ $result[$label] = array_merge(
+ ['label' => $label],
+ $numericQueryEmptyRow,
+ $blobStatsRow
+ );
}
}
diff --git a/core/DataAccess/Model.php b/core/DataAccess/Model.php
index 4f256ea032b..61f8c9f5bec 100644
--- a/core/DataAccess/Model.php
+++ b/core/DataAccess/Model.php
@@ -63,7 +63,7 @@ public function getInvalidatedArchiveIdsSafeToDelete($archiveTable, $setGroupCon
}
$sql = "SELECT idsite, date1, date2, period, name,
- GROUP_CONCAT(idarchive, '.', value ORDER BY ts_archived DESC) as archives
+ GROUP_CONCAT(idarchive, '.', value ORDER BY ts_archived DESC, idarchive DESC) as archives
FROM `$archiveTable`
WHERE name LIKE 'done%'
AND `value` NOT IN (" . ArchiveWriter::DONE_ERROR . ", " . ArchiveWriter::DONE_ERROR_INVALIDATED . ")
diff --git a/core/DataTable/Filter/ReplaceColumnNames.php b/core/DataTable/Filter/ReplaceColumnNames.php
index cd00441a5da..4141049ef69 100644
--- a/core/DataTable/Filter/ReplaceColumnNames.php
+++ b/core/DataTable/Filter/ReplaceColumnNames.php
@@ -162,6 +162,8 @@ protected function getRenamedColumns($columns)
protected function flattenGoalColumns($columnValue)
{
$newSubColumns = array();
+ // sort by key (idgoal) to ensure a static result
+ ksort($columnValue);
foreach ($columnValue as $idGoal => $goalValues) {
$mapping = Metrics::$mappingFromIdToNameGoal;
if ($idGoal == GoalManager::IDGOAL_CART) {
diff --git a/core/Db/BatchInsert.php b/core/Db/BatchInsert.php
index 73d3c18c357..c25d7b198ac 100644
--- a/core/Db/BatchInsert.php
+++ b/core/Db/BatchInsert.php
@@ -12,6 +12,7 @@
use Exception;
use Piwik\Common;
use Piwik\Config;
+use Piwik\Config\DatabaseConfig;
use Piwik\Container\StaticContainer;
use Piwik\Db;
use Piwik\Log;
@@ -97,6 +98,10 @@ public static function tableInsertBatch($tableName, $fields, $values, $throwExce
}
$filePath = $path . $tableName . '-' . $instanceId . Common::generateUniqId() . '.csv';
+ // always use utf8 for TiDb, as TiDb has problems with latin1
+ if (DatabaseConfig::isTiDb()) {
+ $charset = 'utf8';
+ }
try {
$fileSpec = array(
diff --git a/core/Version.php b/core/Version.php
index 3641107d4f7..6b41cf16222 100644
--- a/core/Version.php
+++ b/core/Version.php
@@ -41,12 +41,12 @@ public function isVersionNumber($version): bool
private function isNonStableVersion($version): bool
{
- return (bool) preg_match('/^\d+\.\d+\.\d+((-.{1,4}\d+(\.\d{14})?)|(-alpha\.\d{14}))$/i', $version);
+ return (bool) preg_match('/^\d+\.\d+\.\d+(-((rc|b|beta)\d+|alpha)(\.\d{14})?)$/i', $version);
}
public function isPreviewVersion($version): bool
{
- if (\preg_match('/^\d+\.\d+\.\d+((-(rc|b|beta)\d+(\.\d{14})?)|(-alpha\.\d{14}))?$/i', $version)) {
+ if ($this->isNonStableVersion($version)) {
if (\preg_match('/\.(\d{14})$/', $version, $matches)) {
$dt = DateTime::createFromFormat('YmdHis', $matches[1]);
@@ -56,4 +56,49 @@ public function isPreviewVersion($version): bool
return false;
}
+
+ public function nextPreviewVersion($version): string
+ {
+ if (!$this->isVersionNumber($version)) {
+ return '';
+ }
+
+ $dt = date('YmdHis');
+
+ if ($this->isPreviewVersion($version)) {
+ // already a preview, update dt and check it's newer
+ $newVersion = substr($version, 0, -14) . $dt;
+ if (version_compare($version, $newVersion, '<')) {
+ return $newVersion;
+ }
+ return '';
+ } elseif ($this->isStableVersion($version)) {
+ // no suffix yet, we need to bump the patch first
+ $newVersion = preg_replace_callback(
+ '/^(\d+\.\d+\.)(\d+)$/',
+ function ($matches) {
+ $matches[2] = $matches[2] + 1;
+ return $matches[1] . $matches[2];
+ },
+ $version
+ );
+
+ return sprintf('%s-alpha.%s', $newVersion, $dt);
+ } elseif ('alpha' === substr($version, -5)) {
+ // -alpha
+ return $version . '.' . $dt;
+ } else {
+ // -b1, -rc1
+ $newVersion = preg_replace_callback(
+ '/^(\d+\.\d+\.\d+-(?:rc|b|beta))(\d+)$/i',
+ function ($matches) {
+ $matches[2] = $matches[2] + 1;
+ return $matches[1] . $matches[2];
+ },
+ $version
+ );
+
+ return $newVersion . '.' . $dt;
+ }
+ }
}
diff --git a/plugins/CoreAdminHome/tests/Integration/Model/DuplicateActionRemoverTest.php b/plugins/CoreAdminHome/tests/Integration/Model/DuplicateActionRemoverTest.php
index 17ac048fd5a..ecc4b8d0d12 100644
--- a/plugins/CoreAdminHome/tests/Integration/Model/DuplicateActionRemoverTest.php
+++ b/plugins/CoreAdminHome/tests/Integration/Model/DuplicateActionRemoverTest.php
@@ -41,11 +41,17 @@ public function testGetDuplicateIdActionsReturnsDuplicateIdActionsAndTreatsLowes
{
$expectedResult = array(
array('name' => 'action1', 'idaction' => 1, 'duplicateIdActions' => array(2, 3)),
- array('name' => 'ACTION2', 'idaction' => 5, 'duplicateIdActions' => array(7, 11)),
array('name' => 'action2', 'idaction' => 4, 'duplicateIdActions' => array(9)),
+ array('name' => 'ACTION2', 'idaction' => 5, 'duplicateIdActions' => array(7, 11)),
array('name' => 'action4', 'idaction' => 6, 'duplicateIdActions' => array(10, 12)),
);
$actualResult = $this->duplicateActionRemover->getDuplicateIdActions();
+
+ // order of element is dependent to database engine, so sort before checking results
+ usort($actualResult, function ($a, $b) {
+ return $a['idaction'] <=> $b['idaction'];
+ });
+
$this->assertEquals($expectedResult, $actualResult);
}
diff --git a/plugins/CorePluginsAdmin/Controller.php b/plugins/CorePluginsAdmin/Controller.php
index 83b1ffb6bb1..a57ced98c3e 100644
--- a/plugins/CorePluginsAdmin/Controller.php
+++ b/plugins/CorePluginsAdmin/Controller.php
@@ -474,7 +474,7 @@ public function activate($redirectAfter = true)
if (!empty($redirectTo) && $redirectTo === 'marketplace') {
$this->redirectToIndex('Marketplace', 'overview');
} elseif (!empty($redirectTo) && $redirectTo === 'tagmanager') {
- $this->redirectToIndex('TagManager', 'gettingStarted');
+ $this->redirectToIndex('TagManager', 'manageContainers');
} elseif (!empty($redirectTo) && $redirectTo === 'referrer') {
$this->redirectAfterModification($redirectAfter);
} else {
diff --git a/plugins/CorePluginsAdmin/tests/UI/TagManagerTeaser_spec.js b/plugins/CorePluginsAdmin/tests/UI/TagManagerTeaser_spec.js
index 5e9ad03d528..52f8055ae52 100644
--- a/plugins/CorePluginsAdmin/tests/UI/TagManagerTeaser_spec.js
+++ b/plugins/CorePluginsAdmin/tests/UI/TagManagerTeaser_spec.js
@@ -63,7 +63,7 @@ describe("TagManagerTeaser", function () {
await page.type('#login_form_password', superUserPassword);
await page.click('#login_form_submit');
- await page.waitForSelector('.tagManagerGettingStarted');
+ await page.waitForSelector('.manageContainer');
await page.waitForNetworkIdle();
await page.waitForTimeout(250);
diff --git a/plugins/CorePluginsAdmin/tests/UI/expected-screenshots/TagManagerTeaser_super_user_activate_plugin.png b/plugins/CorePluginsAdmin/tests/UI/expected-screenshots/TagManagerTeaser_super_user_activate_plugin.png
index a693f5f8922..6e4f3e46838 100644
--- a/plugins/CorePluginsAdmin/tests/UI/expected-screenshots/TagManagerTeaser_super_user_activate_plugin.png
+++ b/plugins/CorePluginsAdmin/tests/UI/expected-screenshots/TagManagerTeaser_super_user_activate_plugin.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:ffac267901af2d688520fc0a93a89d40306b331c9db67d750eff29274cb2273f
-size 200573
+oid sha256:314ba7fbf008d7e047aae72f68deeb7fbda4aed79d8e6b8b1e5f0ae29c0544c9
+size 57471
diff --git a/plugins/Diagnostics/Commands/AnalyzeArchiveTable.php b/plugins/Diagnostics/Commands/AnalyzeArchiveTable.php
index de97fce40d1..7283515586c 100644
--- a/plugins/Diagnostics/Commands/AnalyzeArchiveTable.php
+++ b/plugins/Diagnostics/Commands/AnalyzeArchiveTable.php
@@ -62,14 +62,16 @@ protected function doExecute(): int
$totalError = 0;
$totalSegment = 0;
$totalBlobLength = 0;
+
foreach ($rows as $row) {
- $totalArchives += $row['count_archives'];
- $totalInvalidated += $row['count_invalidated_archives'];
- $totalTemporary += $row['count_temporary_archives'];
- $totalError += $row['count_error_archives'];
- $totalSegment += $row['count_segment_archives'];
+ $totalArchives += (int) $row['count_archives'];
+ $totalInvalidated += (int) $row['count_invalidated_archives'];
+ $totalTemporary += (int) $row['count_temporary_archives'];
+ $totalError += (int) $row['count_error_archives'];
+ $totalSegment += (int) $row['count_segment_archives'];
+
if (isset($row['sum_blob_length'])) {
- $totalBlobLength += $row['sum_blob_length'];
+ $totalBlobLength += (int) $row['sum_blob_length'];
}
}
diff --git a/plugins/Diagnostics/lang/de.json b/plugins/Diagnostics/lang/de.json
index 53023776b4a..b656c063e18 100644
--- a/plugins/Diagnostics/lang/de.json
+++ b/plugins/Diagnostics/lang/de.json
@@ -19,6 +19,7 @@
"EnableRequiredDirectoriesDiagnostic": "Diese Prüfung wurde übersprungen, da diese Prüfung in der Konfiguration deaktiviert ist. Um diese Prüfung zu aktivieren, setzen Sie [General] enable_required_directories_diagnostic = 1 in der Datei \"config/config.ini.php\".",
"HideUnchanged": "Falls Sie nur die geänderten Werte einsehen möchten, können Sie %1$salle unveränderten Werte ausblenden%2$s.",
"HtaccessWarningNginx": "Um sicherzustellen, dass auf sensible Dateien nicht direkt zugegriffen werden kann, wird empfohlen, Ihren Webserver so zu konfigurieren, dass der Zugriff auf bestimmte Verzeichnisse eingeschränkt wird. Weitere Informationen finden Sie in der %1$s offiziellen nginx-Serverkonfiguration %2$s",
+ "MariaDbNotConfigured": "Ihre Datenbankversion deutet darauf hin, dass Sie möglicherweise einen MariaDb-Server verwenden. Wenn dies der Fall ist, stellen Sie bitte sicher, dass Sie [database] schema = Mariadb
in der Datei \"config/config.ini.php\" setzen, um sicherzustellen, dass alle Datenbank-Features wie erwartet funktionieren.",
"MysqlMaxPacketSize": "Maximale Packetgröße",
"MysqlMaxPacketSizeWarning": "Es wird empfohlen die '%1$smax_allowed_packet%2$s' Größe in Ihrer MySQL Datenbank auf mindestens %3$s zu erhöhen. Aktuell ist %4$s eingestellt.",
"MysqlTemporaryTablesWarning": "Die MySQL Berechtigung CREATE TEMPORARY TABLES wird benötigt, damit Matomo korrekt funktioniert.",
diff --git a/plugins/Diagnostics/tests/Integration/Commands/AnalyzeArchiveTableTest.php b/plugins/Diagnostics/tests/Integration/Commands/AnalyzeArchiveTableTest.php
index 8c98f5a691e..d5b3c83e859 100644
--- a/plugins/Diagnostics/tests/Integration/Commands/AnalyzeArchiveTableTest.php
+++ b/plugins/Diagnostics/tests/Integration/Commands/AnalyzeArchiveTableTest.php
@@ -42,9 +42,9 @@ public function testCommandOutputIsAsExpected()
+-------------------------------------------+------------+---------------+-------------+---------+-----------+----------------+-------------+-------------+
| Group | # Archives | # Invalidated | # Temporary | # Error | # Segment | # Numeric Rows | # Blob Rows | # Blob Data |
+-------------------------------------------+------------+---------------+-------------+---------+-----------+----------------+-------------+-------------+
+| day[2010-03-06 - 2010-03-06] idSite = 1 | 7 | 0 | 0 | 0 | 6 | 74 | 75 | %d |
| week[2010-03-01 - 2010-03-07] idSite = 1 | 7 | 0 | 0 | 0 | 6 | 74 | 97 | %d |
| month[2010-03-01 - 2010-03-31] idSite = 1 | 7 | 0 | 0 | 0 | 6 | 74 | 97 | %d |
-| day[2010-03-06 - 2010-03-06] idSite = 1 | 7 | 0 | 0 | 0 | 6 | 74 | 75 | %d |
+-------------------------------------------+------------+---------------+-------------+---------+-----------+----------------+-------------+-------------+
Total # Archives: 21
diff --git a/plugins/Ecommerce/tests/System/expected/test_ecommerceOrderWithItems_Metadata_VisitTime.getVisitInformationPerServerTime__API.getProcessedReport_day.xml b/plugins/Ecommerce/tests/System/expected/test_ecommerceOrderWithItems_Metadata_VisitTime.getVisitInformationPerServerTime__API.getProcessedReport_day.xml
index 6d8e2d50cc8..1829fbed93f 100644
--- a/plugins/Ecommerce/tests/System/expected/test_ecommerceOrderWithItems_Metadata_VisitTime.getVisitInformationPerServerTime__API.getProcessedReport_day.xml
+++ b/plugins/Ecommerce/tests/System/expected/test_ecommerceOrderWithItems_Metadata_VisitTime.getVisitInformationPerServerTime__API.getProcessedReport_day.xml
@@ -419,11 +419,6 @@
¡Felicidades! Su instalación de Matomo está completa.
Asegúrese de que su código de seguimiento está introducido en sus páginas, y espere a sus primeros visitantes.
<p", + "CongratulationsHelp": "¡Felicidades! Su instalación de Matomo está completa.
Asegúrese de que su código de seguimiento está introducido en sus páginas, y espere a sus primeros visitantes.
", "CopyBelowInfoForSupport": "Copie o descargue la información a continuación, en caso de que nuestro equipo de soporte le solicite esta información.", "CopySystemCheck": "Copiar la comprobación del sistema", "DatabaseAbilities": "Capacidades de la base de datos", diff --git a/plugins/Live/lang/fr.json b/plugins/Live/lang/fr.json index 0076f3b6ec6..a8fc803249c 100644 --- a/plugins/Live/lang/fr.json +++ b/plugins/Live/lang/fr.json @@ -8,7 +8,7 @@ "ClickToViewMoreAboutVisit": "Cliquez pour plus d'informations sur cette visite", "ConvertedNGoals": "Objectifs convertis %s", "DisableVisitorProfile": "Désactiver les profils des visiteurs", - "DisableVisitorProfileDescription": "Ici vous pouvez désactiver la fonction de profil du visiteur. Toutes les fonctions liées au journal des visites resteront actives.", + "DisableVisitorProfileDescription": "Toutes les fonctions liées au journal des visites resteront actives.", "DisableVisitsLogAndProfile": "Désactiver le journal des visites et le profil du visiteur", "DisableVisitsLogAndProfileDescription": "Ici, vous pouvez désactiver le journal des visites et la fonction de profil du visiteur. Cela désactivera également les fonctions qui en dépendent, telles que le journal du commerce électronique, le journal des visites segmenté, la carte en temps réel ou le widget temps réel. Cela peut être nécessaire pour respecter les lois locales et les bonnes pratiques en matière de protection de la vie privée.", "FirstVisit": "Première visite", @@ -30,7 +30,7 @@ "OnClickPause": "%s est démarré. Cliquer pour mettre en pause.", "OnClickStart": "%s est arrêté. Cliquer pour démarrer.", "PageRefreshed": "Nombre de fois où cette page a été vue / rafraîchie d'affilée.", - "PluginDescription": "Fournit le log en temps réel des visiteurs et vous permet de visualiser vos visiteurs en temps réel au sein d'un gadget du tableau de bord. Ce composant vous permet aussi de voir le profil d'un visiteur pour n'importe quel utilisateur.", + "PluginDescription": "Fournit le log en temps réel des visiteurs et vous permet de visualiser vos visiteurs en temps réel au sein d'un gadget du tableau de bord. Le profil d'un visiteur peut être consulté pour n'importe quel utilisateur.", "PreviousVisitor": "Visiteur précédent", "QueryMaxExecutionTimeExceeded": "Impossible d'exécuter la requête à temps.", "QueryMaxExecutionTimeExceededReasonDateRange": "Cela peut se produire si la plage de dates sélectionnée est trop large. Veuillez essayer avec une plage de dates plus petite.", diff --git a/plugins/Marketplace/Controller.php b/plugins/Marketplace/Controller.php index 03c5c8153e0..4f3d281b417 100644 --- a/plugins/Marketplace/Controller.php +++ b/plugins/Marketplace/Controller.php @@ -280,6 +280,7 @@ public function overview() $view->isPluginUploadEnabled = CorePluginsAdmin::isPluginUploadEnabled(); $view->uploadLimit = SettingsServer::getPostMaxUploadSize(); $view->inReportingMenu = (bool) Common::getRequestVar('embed', 0, 'int'); + $view->numUsers = $this->environment->getNumUsers(); return $view->render(); } diff --git a/plugins/Marketplace/Marketplace.php b/plugins/Marketplace/Marketplace.php index f859f9e93c4..1d3254b9041 100644 --- a/plugins/Marketplace/Marketplace.php +++ b/plugins/Marketplace/Marketplace.php @@ -167,7 +167,6 @@ public function getClientSideTranslationKeys(&$translationKeys) $translationKeys[] = 'Marketplace_AutoUpdateDisabledWarning'; $translationKeys[] = 'Marketplace_ByXDevelopers'; $translationKeys[] = 'Marketplace_ClickToCompletePurchase'; - $translationKeys[] = 'Marketplace_CurrentNumPiwikUsers'; $translationKeys[] = 'Marketplace_Developer'; $translationKeys[] = 'Marketplace_FeaturedPlugin'; $translationKeys[] = 'Marketplace_LastCommitTime'; diff --git a/plugins/Marketplace/stylesheets/plugin-details.less b/plugins/Marketplace/stylesheets/plugin-details.less index 262bf5e2781..d023982b3e0 100644 --- a/plugins/Marketplace/stylesheets/plugin-details.less +++ b/plugins/Marketplace/stylesheets/plugin-details.less @@ -308,6 +308,16 @@ max-height: calc(~"100vh - 250px"); } + &--with-free-trial { + @media (max-width: 660px) { + max-height: calc(~"90vh - 270px"); + } + + @media (max-width: 480px) { + max-height: calc(~"100vh - 270px"); + } + } + h2, h3, h4, h5, h6 { margin: 20px 0 10px 0; color: #000000; @@ -455,6 +465,8 @@ padding: 24px; height: 90px; border-top: 1px solid #aaa; + margin-top: 1px; // to prevent images overflowing the border + box-sizing: border-box; display: flex; justify-content: space-between; @@ -480,10 +492,117 @@ .matomo-badge-modal { position: initial; + width: 64px; + height: 40px; @media (max-width: 480px) { display: none; } } + + &--with-free-trial { + @media (max-width: 660px) { + padding: 16px 24px; + height: 110px; + } + + .cta-container-modal { + justify-content: flex-end; + } + + .cta-container { + width: 100%; + margin-left: 1rem; + display: flex; + justify-content: flex-end; + box-sizing: border-box; + + .free-trial { + display: flex; + } + + .free-trial-lead-in { + color: #5bb75b; + font-size: 12px; + font-weight: bold; + display: flex; + text-align: right; + flex-grow: 1; + flex-shrink: 1; + flex-wrap: wrap; + align-content: center; + justify-content: flex-end; + padding-right: 1rem; + } + + .free-trial-dropdown { + width: 240px; + height: 36px; + vertical-align: top; + flex-shrink: 0; + } + + .addToCartLink { + width: auto; + max-width: 240px; + vertical-align: top; + padding: 0 1rem; + margin-left: 1rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex-shrink: 0; + } + + @media (max-width: 660px) { + margin-left: 0; + flex-direction: column; /* Stack items vertically */ + justify-content: flex-start; /* Align items to the start */ + + .free-trial-lead-in { + width: 50%; + } + + .free-trial-dropdown { + width: 50%; + } + + .addToCartLink { + width: 50%; + min-width: 50%; + margin-top: 10px; /* space between rows */ + margin-left: 0; + align-self: flex-end; + } + } + + @media (max-width: 400px) { + .addToCartLink { + width: 100%; + min-width: 100%; + } + } + } + + .matomo-badge-modal { + position: initial; + + @media (max-width: 767px) { + width: 48px; + height: 32px; + } + + @media (max-width: 660px) { + display: initial; + position: absolute; + bottom: 16px; + } + + @media (max-width: 400px) { + display: none; + } + } + + } } } diff --git a/plugins/Marketplace/templates/overview.twig b/plugins/Marketplace/templates/overview.twig index 4cf0168b4e1..c971830b1b2 100644 --- a/plugins/Marketplace/templates/overview.twig +++ b/plugins/Marketplace/templates/overview.twig @@ -26,6 +26,7 @@ default-sort="{{ defaultSort|json_encode }}" plugin-sort-options="{{ pluginSortOptions|json_encode }}" num-available-plugins-by-type="{{ numAvailablePluginsByType|json_encode }}" + num-users="{{ numUsers|json_encode }}" > -