blob: 28ad2f0d51da66ddba334d3d3cb3629d8a07ba39 [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
18#' @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.
19#'
20#' @import httr
21#' @importFrom jsonlite fromJSON
22#' @importFrom dplyr mutate rowwise bind_rows select summarise n
23#' @import tibble
24#' @importFrom magrittr %>%
Marc Kupietzd07b8eb2023-04-22 23:23:18 +020025#' @importFrom stringr str_replace str_replace_all
Marc Kupietza93a2722021-04-28 12:22:45 +020026#' @importFrom utils head tail
27#'
28#' @examples
29#' \dontrun{
30#' df <- matomoQuery("https://demo.matomo.org/", getMethod = "UserCountry.getCountry")
31#' }
32#'
33#' @export
34matomoQuery <- function(matomoUrl,
35 siteId,
36 period = "month",
37 date = "last16",
Marc Kupietz0ec50522022-04-09 15:53:24 +020038 filter_limit = 500,
Marc Kupietza93a2722021-04-28 12:22:45 +020039 removeFirst = FALSE,
40 removeLast = FALSE,
41 accessToken = getAccessToken(matomoUrl),
42 getMethod = "VisitsSummary.get"
43) {
Marc Kupietzf17fb822023-04-21 20:54:56 +020044 if ((is.null(accessToken) || accessToken == '' ) && matomoUrl != "https://demo.matomo.org/") {
Marc Kupietza93a2722021-04-28 12:22:45 +020045 stop(
46 paste0(
47 "You must first set an access token with:\n\npersistAccessToken(\"",
48 matomoUrl,
49 "\", <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"
50 ),
51 call. = FALSE
52 )
53 }
Marc Kupietza93a2722021-04-28 12:22:45 +020054 httr::GET(
55 url = matomoUrl,
56 query = list(
57 module = "API",
Marc Kupietz0ec50522022-04-09 15:53:24 +020058 method = getMethod,
Marc Kupietza93a2722021-04-28 12:22:45 +020059 format = "json",
Marc Kupietza93a2722021-04-28 12:22:45 +020060 idSite = paste0(siteId, collapse = ","),
Marc Kupietzd07b8eb2023-04-22 23:23:18 +020061 date = str_replace_all(date, " *UTC", ""),
Marc Kupietza93a2722021-04-28 12:22:45 +020062 period = period,
63 filter_limit = filter_limit,
Marc Kupietzea986d02021-04-28 15:54:23 +020064 language = "de",
Marc Kupietza93a2722021-04-28 12:22:45 +020065 token_auth = accessToken
66 )
67 ) -> res
68
69 if (status_code(res) != 200) {
Marc Kupietze4b79542023-04-22 14:38:57 +020070 stop(paste("API call failed:", httr::content(res, "text", encoding = "UTF-8")), call. = FALSE)
Marc Kupietza93a2722021-04-28 12:22:45 +020071 }
72
73 if (!http_type(res) %in% c("application/json", "application/ld+json")) {
Marc Kupietz30305f22023-04-22 12:34:05 +020074 stop(paste("API did not return json:", httr::content(res, "text", encoding = "UTF-8")), call. = FALSE)
Marc Kupietza93a2722021-04-28 12:22:45 +020075 }
76
77 json <- httr::content(res, "text", encoding = "UTF-8")
Marc Kupietzd07b8eb2023-04-22 23:23:18 +020078 warning(json)
Marc Kupietzf17fb822023-04-21 20:54:56 +020079 l <- jsonlite::fromJSON(json)
Marc Kupietzd07b8eb2023-04-22 23:23:18 +020080
Marc Kupietzf17fb822023-04-21 20:54:56 +020081 if("result" %in% colnames(l) && l$result == 'error') {
82 stop(paste("in api call:", l$message), call. = FALSE)
83 }
84
Marc Kupietz0ec50522022-04-09 15:53:24 +020085 if (period=="range") {
Marc Kupietzd07b8eb2023-04-22 23:23:18 +020086 df <- if (is.list(l) && length(l) == 0) {
87 warning("API call returned an empty list.\n", call. = FALSE)
88 } else if (length(siteId) == 1) {
89 l %>% mutate(site_id=siteId)
Marc Kupietz0ec50522022-04-09 15:53:24 +020090 } else {
91 bind_rows(jsonlite::fromJSON(json), .id = "site_id")
Marc Kupietza93a2722021-04-28 12:22:45 +020092 }
Marc Kupietz0ec50522022-04-09 15:53:24 +020093 } else {
Marc Kupietz0ec50522022-04-09 15:53:24 +020094 df <- (if (length(siteId) == 1) {
95 bind_rows(l, .id=period) %>%
96 head(if(removeLast) -1 else filter_limit) %>%
97 tail(if(removeFirst) -1 else filter_limit) %>%
98 mutate(site_id=siteId)
99 } else {
100 df <- bind_rows(l[[1]], .id=period) %>%
101 head(if(removeLast) -1 else filter_limit) %>%
102 tail(if(removeFirst) -1 else filter_limit) %>%
103 mutate(site_id=siteId[1])
104 for (i in 2:length(l)) {
105 df <- bind_rows(df,
106 bind_rows(l[[i]], .id=period) %>%
107 head(if(removeLast) -1 else filter_limit) %>%
108 tail(if(removeFirst) -1 else filter_limit) %>%
109 mutate(site_id=siteId[i]))
110 }
111 df
112 })
113 }
Marc Kupietza93a2722021-04-28 12:22:45 +0200114
115 if("day" %in% colnames(df) | "month" %in% colnames(df) | "ye" %in% colnames(df)) {
116 df <- df %>%
117 mutate(date = as.Date(
118 if (period == "month")
119 paste0(month, "-01")
120 else if (period == "year")
121 paste0(year, "-01-01")
122 else if (period == "week")
123 sub(",.*", "", week)
124 else if (period == "day")
125 day
126 else
127 stop(paste0("unsupported period parameter: '", period, "'"), call. = FALSE)
Marc Kupietz0ec50522022-04-09 15:53:24 +0200128 , optional = TRUE))
Marc Kupietza93a2722021-04-28 12:22:45 +0200129 }
130 return(df)
131}
132
133utils::globalVariables(c("year", "month", "day", "week"))
134
135#' Save access token persistently to your keyring
136#'
137#' @param matomoUrl base URL of your matomo instance
138#' @param accessToken your oauth token
139#' @param id supply if you have multiple IDs, i.e. logins to your matomo instance
140#'
141#' @import keyring
142#'
143#' @export
144#'
145#' @examples
146#' persistAccessToken("https://demo.matomo.org/", "ad7609a669179c4ebca7c995342f7e09")
147#'
148persistAccessToken <- function(matomoUrl, accessToken, id="default") {
149 if (is.null(accessToken))
150 stop("It seems that you have not supplied any access token that could be persisted.", call. = FALSE)
151
152 keyring::key_set_with_value(matomoUrl, username=id, password=accessToken, keyring = NULL)
153}
154
155clearAccessToken <- function(matomoUrl, id="default") {
156 key_delete(matomoUrl, id)
157}
158
159
160
161#' get access token for matomo from keyring
162#'
163#' @param matomoUrl base URL of your matomo instance
164#' @param id supply if you have multiple IDs, i.e. logins to your matomo instance
165#'
166#' @return access token
167#' @export
168#'
169#' @import keyring
170#'
171getAccessToken <- function(matomoUrl, id="default") {
172 keyList <- tryCatch(withCallingHandlers(key_list(service = matomoUrl),
173 warning = function(w) invokeRestart("muffleWarning"),
174 error = function(e) return(NULL)),
175 error = function(e) { })
176 if (id %in% keyList)
177 key_get(matomoUrl, id)
178 else
179 NULL
180}
181
182