From 767a2b494937dd337bad8bcb268ac11d14dcef38 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 12 Jan 2026 17:32:56 +1100 Subject: [PATCH] feat: collect app download statistics This adds download statistics collection for Keyman apps hosted on downloads.keyman.com - specifically Keyman for Windows, Keyman for Mac, and Keyman Developer. Other platforms are distributed through stores or other mechanisms, so will not generally be visible here. For now we collect app, version, and tier data, by day. This can be expanded as needed in the future. This is the frontend change; see keymanapp/api.keyman.com#321 for the api backend and database changes. This is implemented as a passthrough redirect on keyman.com. Online updates will not currently appear in these statistics, so the stats against downloads.keyman.com hits will differ. Relates-to: keymanapp/api.keyman.com#321 Test-bot: skip --- _includes/includes/ui/downloads.php | 5 +- go/.htaccess | 7 ++ go/app/download.php | 99 +++++++++++++++++++++++++++++ go/package/download.php | 45 ------------- keyboards/install.php | 3 +- 5 files changed, 112 insertions(+), 47 deletions(-) create mode 100644 go/app/download.php diff --git a/_includes/includes/ui/downloads.php b/_includes/includes/ui/downloads.php index 12f7c94a..484f023a 100644 --- a/_includes/includes/ui/downloads.php +++ b/_includes/includes/ui/downloads.php @@ -73,9 +73,12 @@ function downloadLargeCTA($product, $platform, $tier, $filepattern) { $fileData = $versions->$platform->$tier->files->$file; $fileSize = formatSizeUnits($fileData->size); $host = KeymanHosts::Instance()->downloads_keyman_com; + $downloadSiteUrl = "$host/$platform/$tier/{$versions->$platform->$tier->version}/$file"; + $downloadUrl = htmlentities("/go/app/download/$platform/{$versions->$platform->$tier->version}/$tier?url=". + rawurlencode($downloadSiteUrl)); echo << +

$product {$versions->$platform->$tier->version}

Released: {$fileData->date}

diff --git a/go/.htaccess b/go/.htaccess index 9f8e81e2..1c3ed400 100644 --- a/go/.htaccess +++ b/go/.htaccess @@ -31,6 +31,13 @@ RewriteRule "^package/download/(keyboard/)?([^/]+)$" "package/download.php?type= # keyboard/id/share RedirectMatch "/go/keyboard/([^/?]+)/share$" "/keyboards/share/$1" +# +# go/app/download +# + +# download-app?url=... +RewriteRule "^app/download/([^/?]+)/([^/?]+)/(.*)" "app/download.php?product=$1&version=$2&tier=$3" [END,QSA] + # # Non-app-specific endpoints # diff --git a/go/app/download.php b/go/app/download.php new file mode 100644 index 00000000..db17d482 --- /dev/null +++ b/go/app/download.php @@ -0,0 +1,99 @@ +Tier() !== KeymanHosts::TIER_TEST) { + self::report_app_download_event($product, $version, $tier); + + if(DEBUG) { + echo "\n\nLocation: $url\n"; + exit; + } + + // We don't do a redirect for Test tier because a test instance of the + // downloads server is not available and so it gives us an error + header("HTTP/1.1 302 Found"); + header("Cache-Control: no-store"); + header("Location: $url"); + } + + echo "Download Link"; + } + + private static function report_app_download_event($product, $version, $tier) { + global $env; + $url = KeymanHosts::Instance()->SERVER_api_keyman_com . "/app-downloads-increment/".rawurlencode($product). + "/".rawurlencode($version)."/".rawurlencode($tier); + + if(KeymanHosts::Instance()->Tier() !== KeymanHosts::TIER_TEST) { + if(KeymanHosts::Instance()->Tier() === KeymanHosts::TIER_DEVELOPMENT) + $key = 'local'; + else + $key = $env['API_KEYMAN_COM_INCREMENT_DOWNLOAD_KEY']; + + $c = curl_init($url); + curl_setopt($c, CURLOPT_HEADER, 0); + curl_setopt($c, CURLOPT_POSTFIELDS, 'key='.rawurlencode($key)); + curl_setopt($c, CURLOPT_RETURNTRANSFER, TRUE); + curl_setopt($c, CURLOPT_USERAGENT , "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1)"); + $data = curl_exec($c); + curl_close($c); + + if(DEBUG) { + var_dump("app-downloads-increment ($url):",$data); + } + } else + $data = TRUE; + return $data !== FALSE; + } + } diff --git a/go/package/download.php b/go/package/download.php index bf648b34..843732cd 100644 --- a/go/package/download.php +++ b/go/package/download.php @@ -117,48 +117,3 @@ private static function report_download_event($id, $platform, $tier, $bcp47, $up return $data !== FALSE; } } - -/** -* Returns a GUIDv4 string -* -* Uses the best cryptographically secure method -* for all supported pltforms with fallback to an older, -* less secure version. -* https://www.php.net/manual/en/function.com-create-guid.php#119168 -* -* @param bool $trim -* @return string -*/ -function GUIDv4 ($trim = true) -{ - // Windows - if (function_exists('com_create_guid') === true) { - if ($trim === true) - return trim(com_create_guid(), '{}'); - else - return com_create_guid(); - } - - // OSX/Linux - if (function_exists('openssl_random_pseudo_bytes') === true) { - $data = openssl_random_pseudo_bytes(16); - $data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100 - $data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10 - return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)); - } - - // Fallback (PHP 4.2+) - mt_srand((double)microtime() * 10000); - $charid = strtolower(md5(uniqid(rand(), true))); - $hyphen = chr(45); // "-" - $lbrace = $trim ? "" : chr(123); // "{" - $rbrace = $trim ? "" : chr(125); // "}" - $guidv4 = $lbrace. - substr($charid, 0, 8).$hyphen. - substr($charid, 8, 4).$hyphen. - substr($charid, 12, 4).$hyphen. - substr($charid, 16, 4).$hyphen. - substr($charid, 20, 12). - $rbrace; - return $guidv4; -} \ No newline at end of file diff --git a/keyboards/install.php b/keyboards/install.php index 8a69b2ca..d188a85b 100644 --- a/keyboards/install.php +++ b/keyboards/install.php @@ -122,11 +122,12 @@ protected static function WriteWindowsBoxes() { // This maps to buildStandardWindowsDownloadUrl in install.js (which we don't use here for those // few users who have JS disabled -- although this is not really a tested/supported scenario) - $downloadLink = KeymanHosts::Instance()->downloads_keyman_com . + $downloadServerLink = KeymanHosts::Instance()->downloads_keyman_com . "/windows/{$hu['tier']}/{$hu['version']}/keyman-setup" . self::BOOTSTRAP_SEPARATOR . "{$hu['id']}" . (empty($hu['bcp47']) ? "" : ".{$hu['bcp47']}") . ".exe"; + $downloadLink = "/go/app/download/windows/{$hu['version']}/{$hu['tier']}?url=".rawurlencode($downloadServerLink); $helpLink = KeymanHosts::Instance()->help_keyman_com . "/products/windows/current-version/start/download-and-install-keyman";