diff --git a/_content/10/index.php b/_content/10/index.php
index 5d4a2eca..ef5321a8 100644
--- a/_content/10/index.php
+++ b/_content/10/index.php
@@ -1,12 +1,12 @@
What's New in Keyman Desktop 10 for Windows?
@@ -58,7 +58,7 @@
supports Unicode 11.0 and BCP 47 language identifiers. Desktop 10 also includes additional user interface for Turkish.
What's New in Keyman 10 for macOS?
@@ -68,7 +68,7 @@
and easily install keyboard packages by double-clicking the kmp file.
Keyman for Android is also available on the Play Store.
= $playstoreTable ?>
@@ -92,7 +92,7 @@
Swift 4.0 app.
What's New in KeymanWeb 10?
@@ -104,7 +104,7 @@
Developer Software
Keyman Developer 10 allows you to create
diff --git a/_content/11/index.php b/_content/11/index.php
index 1482d7ac..7c88ab19 100644
--- a/_content/11/index.php
+++ b/_content/11/index.php
@@ -1,11 +1,11 @@
What's New in Keyman 11 for macOS?
@@ -92,7 +92,7 @@
Keyman for Android is available on the Play Store.
= $playstoreTable ?>
@@ -122,7 +122,7 @@
@@ -131,7 +131,7 @@
Developer Software
What's new in Keyman Developer 11?
diff --git a/_content/12/index.php b/_content/12/index.php
index 91db9d88..a784b67d 100644
--- a/_content/12/index.php
+++ b/_content/12/index.php
@@ -1,8 +1,8 @@
User Software
What's New in Keyman 14 for Windows?
@@ -98,7 +99,7 @@
What's New in Keyman 14 for macOS?
@@ -144,7 +145,7 @@
= $playstoreTable ?>
@@ -188,7 +189,7 @@
What's New in KeymanWeb 14?
@@ -208,7 +209,7 @@
Developer Software
What's new in Keyman Developer 14?
diff --git a/_content/15/index.php b/_content/15/index.php
index 79fc5237..89bc2e9c 100644
--- a/_content/15/index.php
+++ b/_content/15/index.php
@@ -1,11 +1,12 @@
User Software
What's New in Keyman 15 for Windows?
@@ -111,7 +112,7 @@
What's New in Keyman 15 for macOS?
@@ -146,7 +147,7 @@
= $playstoreTable ?>
@@ -184,7 +185,7 @@
What's New in KeymanWeb 15?
@@ -201,7 +202,7 @@
Developer Software
What's new in Keyman Developer 15?
diff --git a/_content/developer/download.php b/_content/developer/download.php
index 55853861..2b391220 100644
--- a/_content/developer/download.php
+++ b/_content/developer/download.php
@@ -1,8 +1,8 @@
Keyman Developer Command Line Tools
diff --git a/_content/downloads/_downloads.php b/_content/downloads/_downloads.php
index 2bd26a40..4a7d7b89 100644
--- a/_content/downloads/_downloads.php
+++ b/_content/downloads/_downloads.php
@@ -1,7 +1,9 @@
= _m_Downloads('available_on_play_store') ?>
@@ -20,10 +22,10 @@
= _m_Downloads('products_for_software_developers') ?>
diff --git a/_content/downloads/all-versions/index.php b/_content/downloads/all-versions/index.php
index d286c7b4..1d3063ad 100644
--- a/_content/downloads/all-versions/index.php
+++ b/_content/downloads/all-versions/index.php
@@ -1,6 +1,5 @@
Keyman version history (all products)
Keyman for Linux
@@ -64,8 +65,8 @@
sudo apt install keyman onboard-keyman
You can also sign up to access pre-release versions through Google Play.
@@ -74,10 +75,10 @@
Please register for the pre-release versions through the links below for Keyman beta or
alpha pre-releases on your iOS device. This will grant access to the respective app version
through Apple's TestFlight app, which facilitates direct installation on iOS devices.
-= iosTestFlightTable(); ?>
+= DownloadUI::iosTestFlightTable(); ?>
diff --git a/_content/downloads/releases/_version_downloads.php b/_content/downloads/releases/_version_downloads.php
index 75c43bae..a32a5508 100644
--- a/_content/downloads/releases/_version_downloads.php
+++ b/_content/downloads/releases/_version_downloads.php
@@ -1,9 +1,10 @@
Keyman for Android is also available on the Play Store.
@@ -144,10 +145,10 @@ function array_key_first(array $arr) {
Products for Software Developers
diff --git a/_content/linux/download.php b/_content/linux/download.php
index fb5facfa..d66ccfc2 100644
--- a/_content/linux/download.php
+++ b/_content/linux/download.php
@@ -1,7 +1,5 @@
Install directly from keyman.com
Install via Homebrew
diff --git a/_content/windows/download.php b/_content/windows/download.php
index 8a408255..f77e301d 100644
--- a/_content/windows/download.php
+++ b/_content/windows/download.php
@@ -1,6 +1,7 @@
diff --git a/_content/windows/keyboards.php b/_content/windows/keyboards.php
index ccf5f082..eb5c8e13 100644
--- a/_content/windows/keyboards.php
+++ b/_content/windows/keyboards.php
@@ -1,6 +1,5 @@
= 1073741824) {
+ $bytes = number_format($bytes / 1073741824, 2) . ' GB';
+ } elseif ($bytes >= 1048576) {
+ $bytes = number_format($bytes / 1048576, 2) . ' MB';
+ } elseif ($bytes >= 1024) {
+ $bytes = number_format($bytes / 1024, 2) . ' KB';
+ } elseif ($bytes > 1) {
+ $bytes = $bytes . ' bytes';
+ } elseif ($bytes == 1) {
+ $bytes = $bytes . ' byte';
+ } else {
+ $bytes = '0 bytes';
+ }
+
+ return $bytes;
+ }
+
+ private static function filenamePatterns($target, $tier) {
+ global $versions;
+ switch($target) {
+ case 'android': return 'keyman-$version.apk';
+ case 'android-engine': return 'keyman-engine-android-$version.zip';
+ case 'developer': return ['keymandeveloper-$version.exe', 'kmcomp-$version.zip'];
+ case 'ios': return 'keyman-ios-$version.ipa'; // not currently used
+ case 'ios-engine': return 'keyman-engine-ios-$version.zip';
+ case 'linux': return ''; // not currently used
+ case 'mac': return ['keyman-$version.pkg','keyman-$version.dmg'];
+ case 'web': return 'keymanweb-$version.zip';
+ case 'windows': return ['keyman-$version.exe', 'keymandesktop-$version.exe'];
+ default: throw new Exception("Invalid target $target");
+ }
+ }
+
+ private static function productName($target) {
+ switch($target) {
+ case 'android': return 'product_android';
+ case 'android-engine': return 'product_engine_android';
+ case 'developer': return 'product_developer';
+ case 'ios': return 'product_ios'; // not currently used
+ case 'ios-engine': return 'product_engine_ios';
+ case 'linux': return 'product_linux'; // not currently used
+ case 'mac': return 'product_macos'; // note mismatched platform name
+ case 'web': return 'product_keymanweb'; // note mismatched platform name
+ case 'windows': return 'product_windows';
+ default: throw new Exception("Invalid target $target");
+ }
+ }
+
+ private static function title($target) {
+ switch($target) {
+ case 'android': return 'Keyman for Android';
+ case 'android-engine': return 'Keyman Engine for Android';
+ case 'developer': return 'Keyman Developer';
+ case 'ios': return 'Keyman for iPhone and iPad'; // not currently used
+ case 'ios-engine': return 'Keyman Engine for iPhone and iPad';
+ case 'linux': return 'Keyman for Linux'; // not currently used
+ case 'mac': return 'Keyman for mac';
+ case 'web': return 'KeymanWeb';
+ case 'windows': return 'Keyman for Windows';
+ default: throw new Exception("Invalid target $target");
+ }
+ }
+
+
+ public static function downloadSection($platform, $tiers = '', $target = '') {
+
+ if($target == '') $target = $platform;
+
+ echo sprintf("%s
\n\n",
+ $target, _m_Downloads(DownloadUI::productName($target)));
+ $tiers = explode(' ',$tiers);
+ foreach($tiers as $tier) {
+ $filepatterns = DownloadUI::filenamePatterns($target, $tier);
+ echo DownloadUI::downloadLinks($platform, $tier, $filepatterns);
+ }
+ }
+
+ private static function downloadLinks($platform, $tier, $filepatterns) {
+ global $versions;
+ $product = DownloadUI::productName($platform);
+ echo sprintf("%s
\n\n", ucFirst(_m_Downloads($tier)));
+ if(!empty($versions->$platform->$tier)) {
+ if(!is_array($filepatterns)) $filepatterns = array($filepatterns);
+ foreach($filepatterns as $filepattern) {
+ $file = str_replace('$version', $versions->$platform->$tier->version, $filepattern);
+ $file = str_replace('$tier', $tier, $file);
+
+ if(!empty($versions->$platform->$tier->files->$file)) {
+ $fileData = $versions->$platform->$tier->files->$file;
+ $fileSize = DownloadUI::formatSizeUnits($fileData->size);
+ echo sprintf("- %s %s %s
\n",
+ KeymanHosts::Instance()->downloads_keyman_com ,
+ $platform, $tier,
+ $versions->$platform->$tier->version,
+ $file, $file, $tier,
+ _m_Downloads('released_date_size', $fileData->date, $fileSize));
+ }
+ }
+ }
+ echo sprintf("- %s
\n
\n",
+ KeymanHosts::Instance()->downloads_keyman_com, $platform, $tier,
+ _m_Downloads('all_product_releases', _m_Downloads($product), _m_Downloads($tier)));
+ }
+
+ public static function downloadLargeCTA($platform, $tier) {
+ global $versions;
+
+ if(empty($versions)) return false;
+
+ $found = false;
+
+ $filepatterns = DownloadUI::filenamePatterns($platform, $tier);
+ $title = DownloadUI::title($platform);
+ // CTA only supports the first download (which will typically be the right one?)
+ if(!is_array($filepatterns)) $filepatterns = [$filepatterns];
+ foreach($filepatterns as $filepattern) {
+ $file = str_replace('$version', $versions->$platform->$tier->version, $filepattern);
+ $file = str_replace('$tier', $tier, $file);
+
+ if(empty($versions->$platform->$tier->files->$file)) {
+ continue;
+ }
+
+ $found = true;
+
+ $fileData = $versions->$platform->$tier->files->$file;
+ $fileSize = DownloadUI::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 <<
+
+
$title {$versions->$platform->$tier->version}
+
Released: {$fileData->date}
+
Size: $fileSize
+
+
+
Download Now
+
+
+
+END;
+ }
+
+ if(!$found) {
+ echo "No downloads found for $title.
";
+ return false;
+ }
+ }
+
+ public static function iosTestflightTable() {
+ $testflight = 'https://itunes.apple.com/us/app/testflight/id899247664?mt=8';
+ $testflight_beta_signup = 'https://testflight.apple.com/join/9W4XIoxQ';
+ $testflight_alpha_signup = 'https://testflight.apple.com/join/vnCV2EiH';
+ $testflightTable_cdn = cdn('img/testflight-64x64.png');
+ $testflight_about = 'https://developer.apple.com/testflight/testers/';
+ $testflightTable = <<
+ Available through TestFlight
+
+ Sign up here for beta-only Keyman access.
+ Sign up for here alpha-only Keyman access.
+ |
+END;
+ return $testflightTable;
+ }
+
+ }
+?>
\ No newline at end of file
diff --git a/_includes/2020/KeymanDownloadVersions.php b/_includes/2020/KeymanDownloadVersions.php
deleted file mode 100644
index 550c5616..00000000
--- a/_includes/2020/KeymanDownloadVersions.php
+++ /dev/null
@@ -1,41 +0,0 @@
- 'keyman-(\d+\.\d+\.\d+)\.apk', // Generally, use Play Store
- 'ios' => 'keyman-ios-(\d+\.\d+\.\d+)\.ipa', // Always use App Store; included for completeness but not generally used
- 'mac' => 'keyman-(\d+\.\d+\.\d+)\.dmg',
- 'linux' => '-', // Linux distribution is multiple archives, use a package manager
- 'windows' => 'keyman(desktop)?-(\d+\.\d+\.\d+(\.\d+)?).exe'
- ];
-
- static function getDownloadUrls() {
- if(empty(self::$versions))
- self::$versions = @json_decode(Util::call_downloads_keyman_com('/api/version/2.0', 'downloads.keyman.com-api_version_2.0.json'));
- return self::$versions;
- }
-
- static function getDownloadUrl($platform, $tier = 'stable') {
- $versions = self::getDownloadUrls();
- if(empty($versions->$platform->$tier)) {
- return null;
- }
- $files = $versions->$platform->$tier;
- $pattern = self::filenamePatterns[$platform];
- foreach($files->files as $name => $details) {
- //echo $name;
- if(preg_match("/^$pattern\$/", $name)) {
- return KeymanHosts::Instance()->downloads_keyman_com . "/$platform/$tier/{$files->version}/{$name}";
- }
- }
- return null;
- }
- }
diff --git a/_includes/includes/ui/downloads.php b/_includes/includes/ui/downloads.php
deleted file mode 100644
index 29974e56..00000000
--- a/_includes/includes/ui/downloads.php
+++ /dev/null
@@ -1,116 +0,0 @@
-= 1073741824) {
- $bytes = number_format($bytes / 1073741824, 2) . ' GB';
- } elseif ($bytes >= 1048576) {
- $bytes = number_format($bytes / 1048576, 2) . ' MB';
- } elseif ($bytes >= 1024) {
- $bytes = number_format($bytes / 1024, 2) . ' KB';
- } elseif ($bytes > 1) {
- $bytes = $bytes . ' bytes';
- } elseif ($bytes == 1) {
- $bytes = $bytes . ' byte';
- } else {
- $bytes = '0 bytes';
- }
-
- return $bytes;
- }
-
- function downloadSection($product, $platform, $filepattern, $tiers = '', $target = '') {
- if($target == '') $target = $platform;
-
- echo sprintf("%s
\n\n",
- $target, _m_Downloads($product));
- $tiers = explode(' ',$tiers);
- foreach($tiers as $tier) {
- echo downloadLinks($product, $platform, $tier, $filepattern);
- }
- }
-
- function downloadLinks($product, $platform, $tier, $filepatterns) {
- global $versions;
- echo sprintf("%s
\n\n", ucFirst(_m_Downloads($tier)));
- if(!empty($versions->$platform->$tier)) {
- if(!is_array($filepatterns)) $filepatterns = array($filepatterns);
- foreach($filepatterns as $filepattern) {
- $file = str_replace('$version', $versions->$platform->$tier->version, $filepattern);
- $file = str_replace('$tier', $tier, $file);
-
- if(!empty($versions->$platform->$tier->files->$file)) {
- $fileData = $versions->$platform->$tier->files->$file;
- $fileSize = formatSizeUnits($fileData->size);
- echo sprintf("- %s %s %s
\n",
- KeymanHosts::Instance()->downloads_keyman_com ,
- $platform, $tier,
- $versions->$platform->$tier->version,
- $file, $file, $tier,
- _m_Downloads('released_date_size', $fileData->date, $fileSize));
- }
- }
- }
- echo sprintf("- %s
\n
\n",
- KeymanHosts::Instance()->downloads_keyman_com, $platform, $tier,
- _m_Downloads('all_product_releases', _m_Downloads($product), _m_Downloads($tier)));
- }
-
- function downloadLargeCTA($product, $platform, $tier, $filepattern) {
- global $versions;
-
- if(empty($versions)) return;
- $file = str_replace('$version', $versions->$platform->$tier->version, $filepattern);
- $file = str_replace('$tier', $tier, $file);
-
- if(empty($versions->$platform->$tier->files->$file)) {
- echo "No downloads found for $product.
";
- return false;
- }
-
- $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}
-
Size: $fileSize
-
-
-
Download Now
-
-
-
-END;
- }
-
- function iosTestflightTable() {
- $testflight = 'https://itunes.apple.com/us/app/testflight/id899247664?mt=8';
- $testflight_beta_signup = 'https://testflight.apple.com/join/9W4XIoxQ';
- $testflight_alpha_signup = 'https://testflight.apple.com/join/vnCV2EiH';
- $testflightTable_cdn = cdn('img/testflight-64x64.png');
- $testflight_about = 'https://developer.apple.com/testflight/testers/';
- $testflightTable = <<
- Available through TestFlight
-
- Sign up here for beta-only Keyman access.
- Sign up for here alpha-only Keyman access.
- |
-END;
- return $testflightTable;
- }
-?>
\ No newline at end of file
diff --git a/_scripts/contributors/package-lock.json b/_scripts/contributors/package-lock.json
index 0dc78de4..d64cc2f5 100644
--- a/_scripts/contributors/package-lock.json
+++ b/_scripts/contributors/package-lock.json
@@ -146,27 +146,40 @@
}
},
"node_modules/@humanfs/core": {
- "version": "0.19.1",
- "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
- "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
+ "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==",
"license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/types": "^0.15.0"
+ },
"engines": {
"node": ">=18.18.0"
}
},
"node_modules/@humanfs/node": {
- "version": "0.16.7",
- "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
- "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+ "version": "0.16.8",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz",
+ "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==",
"license": "Apache-2.0",
"dependencies": {
- "@humanfs/core": "^0.19.1",
+ "@humanfs/core": "^0.19.2",
+ "@humanfs/types": "^0.15.0",
"@humanwhocodes/retry": "^0.4.0"
},
"engines": {
"node": ">=18.18.0"
}
},
+ "node_modules/@humanfs/types": {
+ "version": "0.15.0",
+ "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz",
+ "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
"node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
diff --git a/go/app/download.php b/go/app/download.php
index 91554bcb..5704ea9e 100644
--- a/go/app/download.php
+++ b/go/app/download.php
@@ -30,6 +30,12 @@ class AppDownloadPage {
public static function redirect_to_file($url, $product, $version, $tier) {
if(empty($url)) {
JsonApiFailure::InvalidParameters("url");
+ return;
+ }
+
+ if(!preg_match("/^https?:\/\/downloads\\.keyman(-staging)?\\.com(\\.localhost)?\//", $url)) {
+ JsonApiFailure::Failure(400, JsonApiFailure::ERROR_InvalidParameters, "url parameter must start with downloads.keyman.com");
+ return;
}
if(empty($product)) {
diff --git a/go/download/_download.php b/go/download/_download.php
index 5517fef5..8fc6d356 100644
--- a/go/download/_download.php
+++ b/go/download/_download.php
@@ -1,4 +1,24 @@
[?tier=][&version=]
+ *
+ * PRODUCT: kmcomp|keyman-developer|keyman-windows|keyman-mac
+ * TIER: alpha|beta|stable (default 'stable')
+ * VERSION: any valid version number (default to latest for tier)
+ *
+ * Redirects to the referenced executable, e.g. keyman-18.0.252.dmg for Mac.
+ *
+ * This page is referenced by Keyman Developer Server, e.g.
+ * /go/download/keyman-mac and by Keyman Developer kmc documentation
+ * /go/download/kmcomp. These paths are rewritten by .htaccess to call into
+ * _download.php.
+ */
+
require_once _KEYMANCOM_INCLUDES . '/autoload.php';
use Keyman\Site\com\keyman\KeymanComSentry;
use Keyman\Site\Common\KeymanHosts;
@@ -34,11 +54,14 @@
$MAC_VERSION = $versions->mac->$TIER->version;
}
+ // epic/mac-config changes the mac installer from a .dmg to .pkg, in version 19.0.291-alpha
+ $MAC_FILENAME = version_compare($MAC_VERSION, "19.0.291") >= 0 ? "keyman-$MAC_VERSION.pkg" : "keyman-$MAC_VERSION.dmg";
+
$packages = [
"kmcomp" => ["developer", $DEVELOPER_VERSION, "kmcomp-$DEVELOPER_VERSION.zip"],
"keyman-developer" => ["developer", $DEVELOPER_VERSION, "keymandeveloper-$DEVELOPER_VERSION.exe"],
"keyman-windows" => ["windows", $WINDOWS_VERSION, "keyman-$WINDOWS_VERSION.exe"],
- "keyman-mac" => ["mac", $MAC_VERSION, "keyman-$MAC_VERSION.dmg"]
+ "keyman-mac" => ["mac", $MAC_VERSION, $MAC_FILENAME],
];
if(!isset($packages[$object])) {
diff --git a/go/package/download.php b/go/package/download.php
index c76f09d1..eca631cb 100644
--- a/go/package/download.php
+++ b/go/package/download.php
@@ -35,10 +35,12 @@ public static function redirect_to_file($type, $id, $version, $platform, $tier,
if($type !== 'keyboard' && $type !== 'model') {
JsonApiFailure::InvalidParameters("type");
+ return;
}
if(empty($id)) {
JsonApiFailure::InvalidParameters("id, version");
+ return;
}
if(empty($version)) {
@@ -50,6 +52,7 @@ public static function redirect_to_file($type, $id, $version, $platform, $tier,
if(empty($json)) {
JsonApiFailure::Failure(404, JsonApiFailure::ERROR_NotFound, "$type package with id $id was not found");
+ return;
}
$version = $json->version;