blob: 2bdb74e2df9868d8d4e25a693cedddccc7bba7ae [file] [log] [blame]
Marc Kupietza93a2722021-04-28 12:22:45 +02001#' Get reports from a matomo API server
2#'
3#' See matomo Reporting API Reference (\url{https://developer.matomo.org/api-reference/reporting-api}) for details.
4#'
5#' @references \url{https://developer.matomo.org/api-reference/reporting-api}
6#'
7#' @param matomoUrl base URL of your matomo instance
8#' @param siteId matomo site id or vector of site ids
9#' @param period \code{day}, \code{week}, \code{month} or \code{year}
10#' @param date date range (see \url{https://developer.matomo.org/api-reference/reporting-api})
11#' @param filter_limit defines the maximum number of rows to be returned
12#' @param removeFirst logical that determines whether the first row of each site should be removed (to account for incomplete periods)
13#' @param removeLast logical that determines whether the last row of each site should be removed (to account for incomplete periods)
14#' @param accessToken API Authentication Token - you can get this in your
15#' matomo interface under Settings -> Personal -> Settings -> API Authentication Token
16#' and pass it here, or you can make it persistent with \code{\link{persistAccessToken}}.
17#' @param getMethod API method to call – default: VisitsSummary.get
Marc Kupietz71c89752023-07-28 15:58:12 +020018#' @param ignoreEmptyResult if FALSE stop with error message on empty list results, otherwise return empty list
Marc Kupietza93a2722021-04-28 12:22:45 +020019#' @return Data frame with visits summary as returned by matomo. Note that the \code{date} column in the returned data frame refers to the first day of the respective period.
20#'
Marc Kupietz39dd9332023-05-06 18:07:18 +020021#' @importFrom httr2 request req_url_query req_perform resp_body_json resp_body_raw req_error
Marc Kupietza93a2722021-04-28 12:22:45 +020022#' @importFrom jsonlite fromJSON
23#' @importFrom dplyr mutate rowwise bind_rows select summarise n
24#' @import tibble
25#' @importFrom magrittr %>%
Marc Kupietzd07b8eb2023-04-22 23:23:18 +020026#' @importFrom stringr str_replace str_replace_all
Marc Kupietza93a2722021-04-28 12:22:45 +020027#' @importFrom utils head tail
28#'
29#' @examples
30#' \dontrun{
31#' df <- matomoQuery("https://demo.matomo.org/", getMethod = "UserCountry.getCountry")
32#' }
33#'
34#' @export
35matomoQuery <- function(matomoUrl,
36 siteId,
37 period = "month",
38 date = "last16",
Marc Kupietz0ec50522022-04-09 15:53:24 +020039 filter_limit = 500,
Marc Kupietza93a2722021-04-28 12:22:45 +020040 removeFirst = FALSE,
41 removeLast = FALSE,
42 accessToken = getAccessToken(matomoUrl),
Marc Kupietz71c89752023-07-28 15:58:12 +020043 getMethod = "VisitsSummary.get",
44 ignoreEmptyResult = FALSE
Marc Kupietza93a2722021-04-28 12:22:45 +020045) {
Marc Kupietzf17fb822023-04-21 20:54:56 +020046 if ((is.null(accessToken) || accessToken == '' ) && matomoUrl != "https://demo.matomo.org/") {
Marc Kupietza93a2722021-04-28 12:22:45 +020047 stop(
48 paste0(
49 "You must first set an access token with:\n\npersistAccessToken(\"",
50 matomoUrl,
51 "\", <token>)\n\nYou can get the token in your matomo interface under Settings -> Personal -> Security -> Auth token\nprobably at:\n\n", matomoUrl, "index.php?module=UsersManager&action=userSecurity\n\n"
52 ),
53 call. = FALSE
54 )
55 }
Marc Kupietz39dd9332023-05-06 18:07:18 +020056 res <- httr2::request(matomoUrl) %>%
57 httr2::req_url_query(
Marc Kupietza93a2722021-04-28 12:22:45 +020058 module = "API",
Marc Kupietz0ec50522022-04-09 15:53:24 +020059 method = getMethod,
Marc Kupietza93a2722021-04-28 12:22:45 +020060 format = "json",
Marc Kupietza93a2722021-04-28 12:22:45 +020061 idSite = paste0(siteId, collapse = ","),
Marc Kupietzd07b8eb2023-04-22 23:23:18 +020062 date = str_replace_all(date, " *UTC", ""),
Marc Kupietza93a2722021-04-28 12:22:45 +020063 period = period,
64 filter_limit = filter_limit,
Marc Kupietzea986d02021-04-28 15:54:23 +020065 language = "de",
Marc Kupietza93a2722021-04-28 12:22:45 +020066 token_auth = accessToken
Marc Kupietz39dd9332023-05-06 18:07:18 +020067 ) %>%
68 httr2::req_error(body = error_body) %>%
69 httr2::req_perform()
Marc Kupietza93a2722021-04-28 12:22:45 +020070
Marc Kupietz39dd9332023-05-06 18:07:18 +020071 l <-res %>% httr2::resp_body_json()
Marc Kupietza93a2722021-04-28 12:22:45 +020072
Marc Kupietz39dd9332023-05-06 18:07:18 +020073 if("result" %in% names(l) && l[["result"]] == 'error') {
74 stop(paste("In api call", res$url, ":", l[["message"]], "\n"), call. = FALSE)
Marc Kupietzf17fb822023-04-21 20:54:56 +020075 }
76
Marc Kupietz0ec50522022-04-09 15:53:24 +020077 if (period=="range") {
Marc Kupietz71c89752023-07-28 15:58:12 +020078 df <- if (!ignoreEmptyResult && is.list(l) && length(l) == 0) {
Marc Kupietz39dd9332023-05-06 18:07:18 +020079 stop(paste0("API call ", res$url, " returned the empty list [].\n"), call. = FALSE)
80 } else if (is.list(l)) {
81 df <- bind_rows(l)
Marc Kupietzd07b8eb2023-04-22 23:23:18 +020082 } else if (length(siteId) == 1) {
83 l %>% mutate(site_id=siteId)
Marc Kupietz0ec50522022-04-09 15:53:24 +020084 } else {
Marc Kupietz39dd9332023-05-06 18:07:18 +020085 bind_rows(l, .id = "site_id")
Marc Kupietza93a2722021-04-28 12:22:45 +020086 }
Marc Kupietz0ec50522022-04-09 15:53:24 +020087 } else {
Marc Kupietz0ec50522022-04-09 15:53:24 +020088 df <- (if (length(siteId) == 1) {
89 bind_rows(l, .id=period) %>%
90 head(if(removeLast) -1 else filter_limit) %>%
91 tail(if(removeFirst) -1 else filter_limit) %>%
92 mutate(site_id=siteId)
93 } else {
94 df <- bind_rows(l[[1]], .id=period) %>%
95 head(if(removeLast) -1 else filter_limit) %>%
96 tail(if(removeFirst) -1 else filter_limit) %>%
97 mutate(site_id=siteId[1])
98 for (i in 2:length(l)) {
99 df <- bind_rows(df,
100 bind_rows(l[[i]], .id=period) %>%
101 head(if(removeLast) -1 else filter_limit) %>%
102 tail(if(removeFirst) -1 else filter_limit) %>%
103 mutate(site_id=siteId[i]))
104 }
105 df
106 })
107 }
Marc Kupietza93a2722021-04-28 12:22:45 +0200108
109 if("day" %in% colnames(df) | "month" %in% colnames(df) | "ye" %in% colnames(df)) {
110 df <- df %>%
111 mutate(date = as.Date(
112 if (period == "month")
113 paste0(month, "-01")
114 else if (period == "year")
115 paste0(year, "-01-01")
116 else if (period == "week")
117 sub(",.*", "", week)
118 else if (period == "day")
119 day
120 else
121 stop(paste0("unsupported period parameter: '", period, "'"), call. = FALSE)
Marc Kupietz0ec50522022-04-09 15:53:24 +0200122 , optional = TRUE))
Marc Kupietza93a2722021-04-28 12:22:45 +0200123 }
124 return(df)
125}
126
Marc Kupietz39dd9332023-05-06 18:07:18 +0200127
128error_body <- function(resp) {
129 return(paste0("getting ", resp$url))
130}
131
Marc Kupietza93a2722021-04-28 12:22:45 +0200132utils::globalVariables(c("year", "month", "day", "week"))
133
134#' Save access token persistently to your keyring
135#'
136#' @param matomoUrl base URL of your matomo instance
137#' @param accessToken your oauth token
138#' @param id supply if you have multiple IDs, i.e. logins to your matomo instance
139#'
140#' @import keyring
141#'
142#' @export
143#'
144#' @examples
145#' persistAccessToken("https://demo.matomo.org/", "ad7609a669179c4ebca7c995342f7e09")
146#'
147persistAccessToken <- function(matomoUrl, accessToken, id="default") {
148 if (is.null(accessToken))
149 stop("It seems that you have not supplied any access token that could be persisted.", call. = FALSE)
150
151 keyring::key_set_with_value(matomoUrl, username=id, password=accessToken, keyring = NULL)
152}
153
154clearAccessToken <- function(matomoUrl, id="default") {
155 key_delete(matomoUrl, id)
156}
157
158
Marc Kupietza93a2722021-04-28 12:22:45 +0200159#' get access token for matomo from keyring
160#'
161#' @param matomoUrl base URL of your matomo instance
162#' @param id supply if you have multiple IDs, i.e. logins to your matomo instance
163#'
164#' @return access token
165#' @export
166#'
167#' @import keyring
168#'
169getAccessToken <- function(matomoUrl, id="default") {
170 keyList <- tryCatch(withCallingHandlers(key_list(service = matomoUrl),
171 warning = function(w) invokeRestart("muffleWarning"),
172 error = function(e) return(NULL)),
173 error = function(e) { })
174 if (id %in% keyList)
175 key_get(matomoUrl, id)
176 else
177 NULL
178}
179
180