Skip to content
Merged
4 changes: 4 additions & 0 deletions .htaccess
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ RewriteRule "^keyboard/(.*)$" "/script/keyboard/keyboard.php?id=$1" [END]

RewriteRule "^increment-download/(.*)$" "/script/increment-download/increment-download.php?id=$1" [END]

#### Rewrites for /script folder: /app-downloads-increment

RewriteRule "^app-downloads-increment/([^/]+)/([^/]+)/(.*)$" "/script/app-downloads-increment/app-downloads-increment.php?product=$1&version=$2&tier=$3" [END]

#### Rewrites for /script folder: /model

RewriteRule "^model(/)?$" "/script/model-search/model-search.php" [END]
Expand Down
23 changes: 23 additions & 0 deletions schemas/app-downloads-increment/1.0/app-downloads-increment.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"$ref": "#/definitions/app-downloads-increment",

"definitions": {
"app-downloads-increment": {
"type": "object",
"required": [
"product",
"version",
"tier",
"count"
],
"additionalProperties": true,
"properties": {
"product": { "type": "string" },
"version": { "type": "string" },
"tier": { "type": "string" },
"count": { "type": "integer" }
}
}
}
}
178 changes: 178 additions & 0 deletions schemas/windows-update/17.0/sample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
{
"msi": {
"name": "Keyman for Windows MSI installer",
"version": "18.0.245",
"date": "2025-12-03",
"platform": "win",
"stability": "stable",
"file": "keymandesktop.msi",
"md5": "98EABC9C8F4803B11C5EF2EF64F0D802",
"type": "msi",
"build": "245",
"size": 110870528,
"url": "https://downloads.keyman.com/windows/stable/18.0.245/keymandesktop.msi"
},
"setup": {
"name": "Keyman for Windows setup bootstrap",
"version": "18.0.245",
"date": "2025-12-03",
"platform": "win",
"stability": "stable",
"file": "setup.exe",
"md5": "C32FD8926909F1E447BBC72AF8B16380",
"type": "exe",
"build": "245",
"size": 4032904,
"url": "https://downloads.keyman.com/windows/stable/18.0.245/setup.exe"
},
"bundle": {
"name": "Keyman for Windows",
"version": "18.0.245",
"date": "2025-12-03",
"platform": "win",
"stability": "stable",
"file": "keyman-18.0.245.exe",
"md5": "2BE0290AF6DF49BA736CEDCE50D1E763",
"type": "exe",
"build": "245",
"size": 111668424,
"url": "https://downloads.keyman.com/windows/stable/18.0.245/keyman-18.0.245.exe"
},
"keyboards": {
"khmer_angkor": {
"id": "khmer_angkor",
"name": "Khmer Angkor",
"license": "mit",
"authorName": "Makara Sok",
"authorEmail": "makara_sok@sil.org",
"description": "<p>Khmer Unicode keyboard layout based on the NiDA keyboard layout.\nAutomatically corrects many common keying errors.</p>",
"languages": {
"km": {
"examples": [
{
"keys": "x j m E r",
"note": "Name of language",
"text": "\u1781\u17d2\u1798\u17c2\u179a"
}
],
"font": {
"family": "Busra",
"source": [
"Busra-Regular.ttf"
]
},
"oskFont": {
"family": "KbdKhmr",
"source": [
"KbdKhmr.ttf"
]
},
"languageName": "Khmer",
"displayName": "Khmer"
}
},
"lastModifiedDate": "2025-12-16T08:51:56.000Z",
"packageFilename": "khmer_angkor.kmp",
"packageFileSize": 2541924,
"jsFilename": "khmer_angkor.js",
"jsFileSize": 74154,
"packageIncludes": [
"visualKeyboard",
"welcome",
"documentation",
"fonts"
],
"version": "2.4",
"encodings": [
"unicode"
],
"platformSupport": {
"windows": "full",
"macos": "full",
"linux": "full",
"desktopWeb": "full",
"ios": "full",
"android": "full",
"mobileWeb": "full"
},
"minKeymanVersion": "10.0",
"sourcePath": "release/k/khmer_angkor",
"helpLink": "https://help.keyman.com/keyboard/khmer_angkor",
"related": {
"khmer10": {
"deprecates": true
},
"basic_kbdkni": {
"deprecates": false
}
},
"url": "https://keyman.com/go/package/download/khmer_angkor?version=2.4&platform=windows&tier=stable&update=0"
},
"sil_ipa": {
"id": "sil_ipa",
"name": "IPA (SIL)",
"license": "mit",
"authorName": "Martin Hosken, Lorna Evans",
"authorEmail": "fonts@sil.org",
"description": "<p>The keyboard layout is described in terms of an IPA chart rather than a\nkeyboard. This is because many base characters are typed as a sequence\nof a letter followed by one of &lt;, &gt; or = which are characters used to\nchange a base character to another base character. Diacritics are typed\nas sequences of an appropriate key.</p>",
"languages": {
"und-latn": {
"examples": [],
"font": {
"family": "Charis",
"source": [
"Charis-Regular.ttf"
]
},
"oskFont": {
"family": "Charis",
"source": [
"Charis-Regular.ttf"
]
},
"languageName": "Undetermined",
"scriptName": "Latin",
"displayName": "Undetermined (Latin)"
}
},
"lastModifiedDate": "2025-06-09T21:19:30.000Z",
"packageFilename": "sil_ipa.kmp",
"packageFileSize": 3626354,
"jsFilename": "sil_ipa.js",
"jsFileSize": 88661,
"packageIncludes": [
"documentation",
"welcome",
"fonts"
],
"version": "2.0.2",
"encodings": [
"unicode"
],
"platformSupport": {
"windows": "full",
"macos": "full",
"linux": "full",
"desktopWeb": "full",
"ios": "full",
"android": "full",
"mobileWeb": "full"
},
"minKeymanVersion": "17.0",
"sourcePath": "release/sil/sil_ipa",
"helpLink": "https://help.keyman.com/keyboard/sil_ipa",
"related": {
"ipauni11": {
"deprecates": true
},
"ipauni111": {
"deprecates": true
},
"ipa93_km5": {
"deprecates": false
}
},
"url": "https://keyman.com/go/package/download/sil_ipa?version=2.0.2&platform=windows&tier=stable&update=0"
}
}
}
28 changes: 28 additions & 0 deletions script/app-downloads-increment/app-downloads-increment.inc.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php
namespace Keyman\Site\com\keyman\api;

require_once(__DIR__ . '/../../tools/util.php');

class AppDownloads {
static function increment($mssql, $product, $version, $tier) {

$stmt = $mssql->prepare('EXEC sp_app_downloads_increment :product, :version, :tier');
$stmt->bindParam(":product", $product);
$stmt->bindParam(":version", $version);
$stmt->bindParam(":tier", $tier);
$stmt->execute();
$data = $stmt->fetchAll();
if(count($data) == 0) {
return NULL;
}

$obj = [
'product' => $data[0]['product'],
'version' => $data[0]['version'],
'tier' => $data[0]['tier'],
'count' => intval($data[0]['count'])
];

return $obj;
}
}
72 changes: 72 additions & 0 deletions script/app-downloads-increment/app-downloads-increment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php
require_once(__DIR__ . '/../../tools/util.php');

allow_cors();
json_response();

require_once(__DIR__ . '/app-downloads-increment.inc.php');
require_once(__DIR__ . '/../../tools/db/db.php');
require_once __DIR__ . '/../../tools/autoload.php';

use Keyman\Site\Common\KeymanHosts;

$mssql = Keyman\Site\com\keyman\api\Tools\DB\DBConnect::Connect();
$env = getenv();

header('Link: <' . KeymanHosts::Instance()->api_keyman_com .'/schemas/app-downloads-increment/1.0/app-downloads-increment.json#>; rel="describedby"');

$AllowGet = isset($_REQUEST['debug']);

if(!$AllowGet && $_SERVER['REQUEST_METHOD'] != 'POST') {
fail('POST required');
}

if(!isset($_REQUEST['key'])) {
fail('key parameter must be set');
}

// Note: we don't currently unit-test this one
if(KeymanHosts::Instance()->Tier() === KeymanHosts::TIER_DEVELOPMENT)
$key = 'local';
else
$key = $env['API_KEYMAN_COM_INCREMENT_DOWNLOAD_KEY'];

if($_REQUEST['key'] !== $key) {
fail('Invalid key');
}

if(!isset($_REQUEST['product']) ||
!isset($_REQUEST['version']) ||
!isset($_REQUEST['tier'])
) {
// We don't constrain what the product / version / tier may be here, because
// we may add other products in the future
fail('product, version, tier parameters must be set');
}

$product = $_REQUEST['product'];
$version = $_REQUEST['version'];
$tier = $_REQUEST['tier'];

/**
* POST https://api.keyman.com/app-downloads-increment/product/version/tier
*
* Increments the download counter for a single product identified by
* `product`, `version`, and `tier`. Returns the new total count for the
* product/version/tier for the day
*
* https://api.keyman.com/schemas/app-downloads-increment.json is JSON schema
*
* @param product the name of the product to increment ( "android", "ios",
* "linux", "macos", "web", "windows", "developer"...)
* @param version the version number ("1.2.3")
* @param tier the tier of the product ("alpha", "beta", "stable")
* @param key internal key to allow endpoint to run
*/

$json = \Keyman\Site\com\keyman\api\AppDownloads::increment($mssql, $product, $version, $tier);
if($json === NULL) {
fail("Failed to increment stat, invalid parameters [$product, $version, $tier]?", 401);
}

echo json_encode($json, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
54 changes: 29 additions & 25 deletions script/statistics/annual-statistics.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,44 +7,48 @@

namespace Keyman\Site\com\keyman\api;

// strip out repeated columns with numeric keys (by default the results returned
// give each column twice, once with a column name, and once with a column index)
function filter_columns_by_name($data) {
$result = [];
foreach($data as $row) {
$r = [];
foreach($row as $id => $val) {
if(!is_numeric($id)) {
$r[$id] = intval($val);
}
}
array_push($result, $r);
}
return $result;
}

class AnnualStatistics {

function execute($mssql, $startDate, $endDate) {
return $this->_execute('sp_annual_statistics', $mssql, $startDate, $endDate);
}

$stmt = $mssql->prepare('EXEC sp_annual_statistics :prmStartDate, :prmEndDate');
function executeDownloadsByMonth($mssql, $startDate, $endDate) {
return $this->_execute('sp_keyboard_downloads_by_month_statistics', $mssql, $startDate, $endDate);
}

$stmt->bindParam(":prmStartDate", $startDate);
$stmt->bindParam(":prmEndDate", $endDate);
function executeKeyboards($mssql, $startDate, $endDate) {
return $this->_execute('sp_statistics_keyboard_downloads_by_id', $mssql, $startDate, $endDate);
}

$stmt->execute();
$data = $stmt->fetchAll();
return filter_columns_by_name($data);
function executeAppDownloadsByMonth($mssql, $startDate, $endDate) {
return $this->_execute('sp_app_downloads_by_month_statistics', $mssql, $startDate, $endDate);
}

function executeDownloadsByMonth($mssql, $startDate, $endDate) {
$stmt = $mssql->prepare('EXEC sp_keyboard_downloads_by_month_statistics :prmStartDate, :prmEndDate');
private function _execute($proc, $mssql, $startDate, $endDate) {
$stmt = $mssql->prepare("EXEC $proc :prmStartDate, :prmEndDate");

$stmt->bindParam(":prmStartDate", $startDate);
$stmt->bindParam(":prmEndDate", $endDate);

$stmt->execute();
$data = $stmt->fetchAll();
return filter_columns_by_name($data);
return $this->filter_columns_by_name($data);
}

// strip out repeated columns with numeric keys (by default the results returned
// give each column twice, once with a column name, and once with a column index)
private function filter_columns_by_name($data) {
$result = [];
foreach($data as $row) {
$r = [];
foreach($row as $id => $val) {
if(!is_numeric($id)) {
$r[$id] = is_numeric($val) ? intval($val) : $val;
}
}
array_push($result, $r);
}
return $result;
}
}
Loading