Skip to content

Commit 767a2b4

Browse files
committed
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
1 parent 27f0daf commit 767a2b4

File tree

5 files changed

+112
-47
lines changed

5 files changed

+112
-47
lines changed

_includes/includes/ui/downloads.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,12 @@ function downloadLargeCTA($product, $platform, $tier, $filepattern) {
7373
$fileData = $versions->$platform->$tier->files->$file;
7474
$fileSize = formatSizeUnits($fileData->size);
7575
$host = KeymanHosts::Instance()->downloads_keyman_com;
76+
$downloadSiteUrl = "$host/$platform/$tier/{$versions->$platform->$tier->version}/$file";
77+
$downloadUrl = htmlentities("/go/app/download/$platform/{$versions->$platform->$tier->version}/$tier?url=".
78+
rawurlencode($downloadSiteUrl));
7679

7780
echo <<<END
78-
<div class="download-cta-big selected" id="cta-big-Windows" data-url='$host/$platform/$tier/{$versions->$platform->$tier->version}/$file' data-version='{$versions->$platform->$tier->version}'>
81+
<div class="download-cta-big selected" id="cta-big-Windows" data-url='$downloadUrl' data-version='{$versions->$platform->$tier->version}'>
7982
<div class="download-stable-email">
8083
<h3>$product {$versions->$platform->$tier->version}</h3>
8184
<p>Released: {$fileData->date}</p>

go/.htaccess

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ RewriteRule "^package/download/(keyboard/)?([^/]+)$" "package/download.php?type=
3131
# keyboard/id/share
3232
RedirectMatch "/go/keyboard/([^/?]+)/share$" "/keyboards/share/$1"
3333

34+
#
35+
# go/app/download
36+
#
37+
38+
# download-app?url=...
39+
RewriteRule "^app/download/([^/?]+)/([^/?]+)/(.*)" "app/download.php?product=$1&version=$2&tier=$3" [END,QSA]
40+
3441
#
3542
# Non-app-specific endpoints
3643
#

go/app/download.php

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Keyman\Site\com\keyman;
5+
6+
require __DIR__ . '/../../_includes/autoload.php';
7+
8+
const DEBUG=0;
9+
10+
use Keyman\Site\com\keyman\KeymanComSentry;
11+
use Keyman\Site\com\keyman\Validation;
12+
use Keyman\Site\Common\KeymanHosts;
13+
use Keyman\Site\Common\JsonApiFailure;
14+
15+
$env = getenv();
16+
KeymanComSentry::init();
17+
18+
AppDownloadPage::redirect_to_file(
19+
isset($_REQUEST['url']) ? $_REQUEST['url'] : null,
20+
isset($_REQUEST['product']) ? $_REQUEST['product'] : null,
21+
isset($_REQUEST['version']) ? $_REQUEST['version'] : null,
22+
isset($_REQUEST['tier']) ? $_REQUEST['tier'] : null,
23+
// TODO: should we? or just leave it encoded in URL for now?
24+
// isset($_REQUEST['bcp47']) ? $_REQUEST['bcp47'] : null,
25+
// isset($_REQUEST['update']) ? $_REQUEST['update'] : null
26+
);
27+
28+
class AppDownloadPage {
29+
30+
public static function redirect_to_file($url, $product, $version, $tier) {
31+
if(empty($url)) {
32+
JsonApiFailure::InvalidParameters("url");
33+
}
34+
35+
if(empty($product)) {
36+
$product = 'unknown';
37+
}
38+
39+
if(empty($version)) {
40+
$version = '0.0';
41+
}
42+
43+
if(empty($tier)) {
44+
$tier = 'stable';
45+
}
46+
47+
$tier = Validation::validate_tier($tier, 'stable');
48+
49+
$url_e = htmlentities($url);
50+
51+
if(DEBUG) {
52+
header('Content-Type: text/plain');
53+
}
54+
55+
if(KeymanHosts::Instance()->Tier() !== KeymanHosts::TIER_TEST) {
56+
self::report_app_download_event($product, $version, $tier);
57+
58+
if(DEBUG) {
59+
echo "\n\nLocation: $url\n";
60+
exit;
61+
}
62+
63+
// We don't do a redirect for Test tier because a test instance of the
64+
// downloads server is not available and so it gives us an error
65+
header("HTTP/1.1 302 Found");
66+
header("Cache-Control: no-store");
67+
header("Location: $url");
68+
}
69+
70+
echo "<a href='$url_e'>Download Link</a>";
71+
}
72+
73+
private static function report_app_download_event($product, $version, $tier) {
74+
global $env;
75+
$url = KeymanHosts::Instance()->SERVER_api_keyman_com . "/app-downloads-increment/".rawurlencode($product).
76+
"/".rawurlencode($version)."/".rawurlencode($tier);
77+
78+
if(KeymanHosts::Instance()->Tier() !== KeymanHosts::TIER_TEST) {
79+
if(KeymanHosts::Instance()->Tier() === KeymanHosts::TIER_DEVELOPMENT)
80+
$key = 'local';
81+
else
82+
$key = $env['API_KEYMAN_COM_INCREMENT_DOWNLOAD_KEY'];
83+
84+
$c = curl_init($url);
85+
curl_setopt($c, CURLOPT_HEADER, 0);
86+
curl_setopt($c, CURLOPT_POSTFIELDS, 'key='.rawurlencode($key));
87+
curl_setopt($c, CURLOPT_RETURNTRANSFER, TRUE);
88+
curl_setopt($c, CURLOPT_USERAGENT , "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1)");
89+
$data = curl_exec($c);
90+
curl_close($c);
91+
92+
if(DEBUG) {
93+
var_dump("app-downloads-increment ($url):",$data);
94+
}
95+
} else
96+
$data = TRUE;
97+
return $data !== FALSE;
98+
}
99+
}

go/package/download.php

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -117,48 +117,3 @@ private static function report_download_event($id, $platform, $tier, $bcp47, $up
117117
return $data !== FALSE;
118118
}
119119
}
120-
121-
/**
122-
* Returns a GUIDv4 string
123-
*
124-
* Uses the best cryptographically secure method
125-
* for all supported pltforms with fallback to an older,
126-
* less secure version.
127-
* https://www.php.net/manual/en/function.com-create-guid.php#119168
128-
*
129-
* @param bool $trim
130-
* @return string
131-
*/
132-
function GUIDv4 ($trim = true)
133-
{
134-
// Windows
135-
if (function_exists('com_create_guid') === true) {
136-
if ($trim === true)
137-
return trim(com_create_guid(), '{}');
138-
else
139-
return com_create_guid();
140-
}
141-
142-
// OSX/Linux
143-
if (function_exists('openssl_random_pseudo_bytes') === true) {
144-
$data = openssl_random_pseudo_bytes(16);
145-
$data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100
146-
$data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10
147-
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
148-
}
149-
150-
// Fallback (PHP 4.2+)
151-
mt_srand((double)microtime() * 10000);
152-
$charid = strtolower(md5(uniqid(rand(), true)));
153-
$hyphen = chr(45); // "-"
154-
$lbrace = $trim ? "" : chr(123); // "{"
155-
$rbrace = $trim ? "" : chr(125); // "}"
156-
$guidv4 = $lbrace.
157-
substr($charid, 0, 8).$hyphen.
158-
substr($charid, 8, 4).$hyphen.
159-
substr($charid, 12, 4).$hyphen.
160-
substr($charid, 16, 4).$hyphen.
161-
substr($charid, 20, 12).
162-
$rbrace;
163-
return $guidv4;
164-
}

keyboards/install.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,12 @@ protected static function WriteWindowsBoxes() {
122122

123123
// This maps to buildStandardWindowsDownloadUrl in install.js (which we don't use here for those
124124
// few users who have JS disabled -- although this is not really a tested/supported scenario)
125-
$downloadLink = KeymanHosts::Instance()->downloads_keyman_com .
125+
$downloadServerLink = KeymanHosts::Instance()->downloads_keyman_com .
126126
"/windows/{$hu['tier']}/{$hu['version']}/keyman-setup" .
127127
self::BOOTSTRAP_SEPARATOR . "{$hu['id']}" .
128128
(empty($hu['bcp47']) ? "" : ".{$hu['bcp47']}") .
129129
".exe";
130+
$downloadLink = "/go/app/download/windows/{$hu['version']}/{$hu['tier']}?url=".rawurlencode($downloadServerLink);
130131

131132
$helpLink = KeymanHosts::Instance()->help_keyman_com . "/products/windows/current-version/start/download-and-install-keyman";
132133

0 commit comments

Comments
 (0)