PageRenderTime 48ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/server/php/includes/main.php

http://github.com/TheRealKerni/HockeyKit
PHP | 660 lines | 486 code | 109 blank | 65 comment | 87 complexity | 66ed769147b95d73a1f124b0ac4f6f59 MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. ## index.php
  3. ##
  4. ## Created by Andreas Linde on 8/17/10.
  5. ## Stanley Rost on 8/17/10.
  6. ## Copyright 2010 Andreas Linde. All rights reserved.
  7. ##
  8. ## Permission is hereby granted, free of charge, to any person obtaining a copy
  9. ## of this software and associated documentation files (the "Software"), to deal
  10. ## in the Software without restriction, including without limitation the rights
  11. ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  12. ## copies of the Software, and to permit persons to whom the Software is
  13. ## furnished to do so, subject to the following conditions:
  14. ##
  15. ## The above copyright notice and this permission notice shall be included in
  16. ## all copies or substantial portions of the Software.
  17. ##
  18. ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  19. ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  20. ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  21. ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  22. ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  23. ## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  24. ## THE SOFTWARE.
  25. date_default_timezone_set('UTC');
  26. require('plist.inc');
  27. require_once('config.inc');
  28. require_once('helper.php');
  29. require_once('logger.php');
  30. require_once('router.php');
  31. require_once('devicedetector.php');
  32. class AppUpdater
  33. {
  34. // define the API V1 paramater keys (only the parameters that differ from V2, iOS only)
  35. const PARAM_1_TYPE = 'type';
  36. const PARAM_1_IDENTIFIER = 'bundleidentifier';
  37. const PARAM_1_DEVICE = 'platform';
  38. const PARAM_1_APP_VERSION = 'version';
  39. const PARAM_1_OS_VERSION = 'ios';
  40. // define URL type parameter values
  41. const PARAM_1_TYPE_VALUE_PROFILE = 'profile';
  42. const PARAM_1_TYPE_VALUE_APP = 'app';
  43. const PARAM_1_TYPE_VALUE_IPA = 'ipa';
  44. // define the API V2 paramater keys
  45. const PARAM_2_IDENTIFIER = 'bundleidentifier';
  46. const PARAM_2_FORMAT = 'format';
  47. const PARAM_2_UDID = 'udid'; // iOS client only
  48. const PARAM_2_DEVICE = 'device';
  49. const PARAM_2_APP_VERSION = 'app_version';
  50. const PARAM_2_OS = 'os';
  51. const PARAM_2_OS_VERSION = 'os_version';
  52. const PARAM_2_LANGUAGE = 'lang';
  53. const PARAM_2_FIRST_START = 'first_start_at';
  54. const PARAM_2_USAGE_TIME = 'usage_time';
  55. const PARAM_2_AUTHORIZE = 'authorize';
  56. // define the API V2 paramater values
  57. const PARAM_2_FORMAT_VALUE_JSON = 'json';
  58. const PARAM_2_FORMAT_VALUE_MOBILEPROVISION = 'mobileprovision';
  59. const PARAM_2_FORMAT_VALUE_PLIST = 'plist';
  60. const PARAM_2_FORMAT_VALUE_IPA = 'ipa';
  61. const PARAM_2_FORMAT_VALUE_APK = 'apk';
  62. const PARAM_2_AUTHORIZE_VALUE_YES = 'yes';
  63. const PARAM_2_AUTHORIZE_VALUE_NO = 'no';
  64. // define the json response format version
  65. const API_V1 = '1';
  66. const API_V2 = '2';
  67. // define support app platforms
  68. const APP_PLATFORM_IOS = "iOS";
  69. const APP_PLATFORM_ANDROID = "Android";
  70. const PLATFORM_IOS = "ios";
  71. const PLATFORM_ANDROID = "android";
  72. // define keys for the returning json string api version 1
  73. const RETURN_RESULT = 'result';
  74. const RETURN_NOTES = 'notes';
  75. const RETURN_TITLE = 'title';
  76. const RETURN_SUBTITLE = 'subtitle';
  77. // define keys for the returning json string api version 2
  78. const RETURN_V2_VERSION = 'version';
  79. const RETURN_V2_SHORTVERSION = 'shortversion';
  80. const RETURN_V2_NOTES = 'notes';
  81. const RETURN_V2_TITLE = 'title';
  82. const RETURN_V2_TIMESTAMP = 'timestamp';
  83. const RETURN_V2_APPSIZE = 'appsize';
  84. const RETURN_V2_AUTHCODE = 'authcode';
  85. const RETURN_V2_MANDATORY = 'mandatory';
  86. const RETURN_V2_AUTH_FAILED = 'FAILED';
  87. // define keys for the array to keep a list of available beta apps to be displayed in the web interface
  88. const INDEX_APP = 'app';
  89. const INDEX_VERSION = 'version';
  90. const INDEX_SUBTITLE = 'subtitle';
  91. const INDEX_DATE = 'date';
  92. const INDEX_APPSIZE = 'appsize';
  93. const INDEX_NOTES = 'notes';
  94. const INDEX_PROFILE = 'profile';
  95. const INDEX_PROFILE_UPDATE = 'profileupdate';
  96. const INDEX_DIR = 'dir';
  97. const INDEX_IMAGE = 'image';
  98. const INDEX_STATS = 'stats';
  99. const INDEX_PLATFORM = 'platform';
  100. // define filetypes
  101. const FILE_IOS_PLIST = '.plist';
  102. const FILE_IOS_IPA = '.ipa';
  103. const FILE_IOS_PROFILE = '.mobileprovision';
  104. const FILE_ANDROID_JSON = '.json';
  105. const FILE_ANDROID_APK = '.apk';
  106. const FILE_COMMON_NOTES = '.html';
  107. const FILE_COMMON_ICON = '.png';
  108. const FILE_VERSION_MANDATORY = '.mandatory'; // if present in a version subdirectory, defines that version to be mandatory
  109. const FILE_VERSION_RESTRICT = '.team'; // if present in a version subdirectory, defines the teams that do have access, comma separated
  110. const FILE_USERLIST = 'stats/userlist.txt'; // defines UDIDs, real names for stats, and comma separated the associated team names
  111. // define version array structure
  112. const VERSIONS_COMMON_DATA = 'common';
  113. const VERSIONS_SPECIFIC_DATA = 'specific';
  114. // define keys for the array to keep a list of devices installed this app
  115. const DEVICE_USER = 'user';
  116. const DEVICE_PLATFORM = 'platform';
  117. const DEVICE_OSVERSION = 'osversion';
  118. const DEVICE_APPVERSION = 'appversion';
  119. const DEVICE_LANGUAGE = 'language';
  120. const DEVICE_LASTCHECK = 'lastcheck';
  121. const DEVICE_INSTALLDATE = 'installdate';
  122. const DEVICE_USAGETIME = 'usagetime';
  123. const CONTENT_TYPE_APK = 'application/vnd.android.package-archive';
  124. const E_UNKNOWN_PLATFORM = -1;
  125. const E_NO_VERSIONS_FOUND = -1;
  126. const E_FILES_INCOMPLETE = -1;
  127. const E_UNKNOWN_API = -1;
  128. const E_UNKNOWN_BUNDLE_ID = -1;
  129. const STATS_SEPARATOR = ';;';
  130. static public function factory($platform = null, $options = null) {
  131. if ($platform) {
  132. require_once(strtolower("platforms/abstract.php"));
  133. $included = include_once(strtolower("platforms/$platform.php"));
  134. if (!$included) {
  135. Logger::log("unknown platform: $platform");
  136. Helper::sendJSONAndExit(self::E_UNKNOWN_PLATFORM, $platform);
  137. }
  138. }
  139. $klass = "{$platform}AppUpdater";
  140. // Logger::log("Factory: Creating $klass");
  141. return new $klass($options);
  142. }
  143. public $appDirectory;
  144. public $applications = array();
  145. protected function __construct($options) {
  146. $this->appDirectory = $options['appDirectory'];
  147. $this->logic = isset($options['logic']) ? $options['logic'] : null;
  148. }
  149. public function execute($action, $arguments = array()) {
  150. if (!method_exists($this, $action))
  151. {
  152. Router::get()->serve404();
  153. }
  154. call_user_func(array($this, $action), $arguments);
  155. }
  156. protected function index($arguments)
  157. {
  158. return $this->show(null);
  159. }
  160. protected function app($arguments)
  161. {
  162. return $this->show($arguments['bundleidentifier']);
  163. }
  164. protected function validateDir($dir)
  165. {
  166. // do not allow .. or / in the name and check if that path actually exists
  167. if (
  168. $dir &&
  169. !preg_match('#(/|\.\.)#u', $dir) &&
  170. file_exists($this->appDirectory.$dir))
  171. {
  172. return $dir;
  173. }
  174. return null;
  175. }
  176. protected function addStats($bundleidentifier, $format)
  177. {
  178. // did we get any user data?
  179. $osname = Router::arg(self::PARAM_2_OS, 'iOS');
  180. if ($osname == "Android") {
  181. $udid = Router::arg_match(self::PARAM_2_UDID, '/^[0-9a-f]{16}$/i');
  182. }
  183. else {
  184. $udid = Router::arg_match(self::PARAM_2_UDID, '/^[0-9a-f]{40}$/i');
  185. }
  186. if (!$udid || !is_dir($this->appDirectory.'stats/')) {
  187. return;
  188. }
  189. $appversion = Router::arg_variants(array(self::PARAM_2_APP_VERSION, self::PARAM_1_APP_VERSION));
  190. $osversion = Router::arg_variants(array(self::PARAM_2_OS_VERSION, self::PARAM_1_OS_VERSION));
  191. $device = Router::arg_variants(array(self::PARAM_2_DEVICE, self::PARAM_1_DEVICE));
  192. $language = Router::arg(self::PARAM_2_LANGUAGE, '');
  193. $firststartdate = Router::arg(self::PARAM_2_FIRST_START, '');
  194. $usagetime = Router::arg(self::PARAM_2_USAGE_TIME, '');
  195. $thisdevice = array(
  196. $udid,
  197. $device,
  198. $osname.' '.$osversion,
  199. $appversion,
  200. date('m/d/Y H:i:s'),
  201. $language,
  202. $firststartdate,
  203. $usagetime);
  204. $filename = $this->appDirectory."stats/".$bundleidentifier;
  205. $lines = @file($filename, FILE_IGNORE_NEW_LINES);
  206. $found = false;
  207. $lines = $lines ? array_filter(array_map('trim', $lines)) : array();
  208. foreach ($lines as $i => $line) {
  209. $device = explode(self::STATS_SEPARATOR, $line);
  210. if (!count($device)) {
  211. continue;
  212. }
  213. // is this the same device?
  214. if ($device[0] === $udid) {
  215. $found = true;
  216. $lines[$i] = join(self::STATS_SEPARATOR, $thisdevice);
  217. break;
  218. }
  219. }
  220. if (!$found) {
  221. $lines[] = join(self::STATS_SEPARATOR, $thisdevice);
  222. }
  223. // write back the updated stats
  224. if ($lines) {
  225. if (!@file_put_contents($filename, join("\n", $lines)))
  226. {
  227. Logger::log("Stats file not writable: $filename");
  228. }
  229. }
  230. }
  231. protected function checkProtectedVersion($restrict)
  232. {
  233. $allowedTeams = array();
  234. foreach (@file($restrict) as $line) {
  235. if (preg_match('/^\s*#/', $line)) continue;
  236. $items = array_filter(array_map('trim', explode(',', $line)));
  237. $allowedTeams = array_merge($allowedTeams, $items);
  238. }
  239. if (!$allowedTeams) return true;
  240. $udid = Router::arg(self::PARAM_2_UDID);
  241. if (!$udid) return false;
  242. $users = self::parseUserList();
  243. if (isset($users[$udid])) {
  244. return count(array_intersect($users[$udid]['teams'], $allowedTeams)) > 0;
  245. }
  246. return false;
  247. }
  248. protected function getApplicationVersions($bundleidentifier, $platform = null)
  249. {
  250. $files = array();
  251. $language = Router::arg(self::PARAM_2_LANGUAGE);
  252. // iOS
  253. $ipa = @array_shift(glob($this->appDirectory.$bundleidentifier . '/*' . self::FILE_IOS_IPA));
  254. $plist = @array_shift(glob($this->appDirectory.$bundleidentifier . '/*' . self::FILE_IOS_PLIST));
  255. $profile = @array_shift(glob($this->appDirectory.$bundleidentifier . '/*' . self::FILE_IOS_PROFILE));
  256. // Android
  257. $apk = @array_shift(glob($this->appDirectory.$bundleidentifier . '/*' . self::FILE_ANDROID_APK));
  258. $json = @array_shift(glob($this->appDirectory.$bundleidentifier . '/*' . self::FILE_ANDROID_JSON));
  259. $note = '';
  260. // Common
  261. if ($language) {
  262. $note = @array_shift(glob($this->appDirectory.$bundleidentifier . '/*' . self::FILE_COMMON_NOTES . '.' . $language));
  263. }
  264. if (!$note) {
  265. $note = @array_shift(glob($this->appDirectory.$bundleidentifier . '/*' . self::FILE_COMMON_NOTES)); // the default language file should not have a language extension, so if en is default, never creaete a .html.en file!
  266. }
  267. $icon = @array_shift(glob($this->appDirectory.$bundleidentifier . '/*' . self::FILE_COMMON_ICON));
  268. $allVersions = array();
  269. if ((!$ipa || !$plist) &&
  270. (!$apk || !$json)) {
  271. // check if any are available in a subdirectory
  272. $subDirs = array();
  273. if ($handleSub = opendir($this->appDirectory . $bundleidentifier)) {
  274. while (($fileSub = readdir($handleSub)) !== false) {
  275. if (!in_array($fileSub, array('.', '..')) &&
  276. is_dir($this->appDirectory . $bundleidentifier . '/'. $fileSub)) {
  277. array_push($subDirs, $fileSub);
  278. }
  279. }
  280. closedir($handleSub);
  281. }
  282. // Sort the files and display
  283. usort($subDirs, array($this, 'sort_versions'));
  284. if (count($subDirs) > 0) {
  285. foreach ($subDirs as $subDir) {
  286. // iOS
  287. $ipa = @array_shift(glob($this->appDirectory.$bundleidentifier . '/'. $subDir . '/*' . self::FILE_IOS_IPA)); // this file could be in a subdirectory per version
  288. $plist = @array_shift(glob($this->appDirectory.$bundleidentifier . '/'. $subDir . '/*' . self::FILE_IOS_PLIST)); // this file could be in a subdirectory per version
  289. // Android
  290. $apk = @array_shift(glob($this->appDirectory.$bundleidentifier . '/'. $subDir . '/*' . self::FILE_ANDROID_APK)); // this file could be in a subdirectory per version
  291. $json = @array_shift(glob($this->appDirectory.$bundleidentifier . '/'. $subDir . '/*' . self::FILE_ANDROID_JSON)); // this file could be in a subdirectory per version
  292. // Common
  293. $note = ''; // this file could be in a subdirectory per version
  294. if ($language) {
  295. $note = @array_shift(glob($this->appDirectory.$bundleidentifier . '/'. $subDir . '/*' . self::FILE_COMMON_NOTES . '.' . $language));
  296. }
  297. if (!$note) {
  298. $note = @array_shift(glob($this->appDirectory.$bundleidentifier . '/'. $subDir . '/*' . self::FILE_COMMON_NOTES));
  299. }
  300. $mandatory = @array_shift(glob($this->appDirectory.$bundleidentifier . '/'. $subDir . '/*' . self::FILE_VERSION_MANDATORY)); // this file defines if the version is mandatory
  301. $restrict = @array_shift(glob($this->appDirectory.$bundleidentifier . '/'. $subDir . '/*' . self::FILE_VERSION_RESTRICT)); // this file defines the teams allowed to access this version
  302. if ($ipa && $plist && (!$platform || $platform == self::PLATFORM_IOS)) {
  303. $version = array();
  304. $version[self::FILE_IOS_IPA] = $ipa;
  305. $version[self::FILE_IOS_PLIST] = $plist;
  306. $version[self::FILE_COMMON_NOTES] = $note;
  307. $version[self::FILE_VERSION_RESTRICT] = $restrict;
  308. $version[self::FILE_VERSION_MANDATORY] = $mandatory;
  309. // if this is a restricted version, check if the UDID is provided and allowed
  310. if ($restrict && !$this->checkProtectedVersion($restrict)) {
  311. continue;
  312. }
  313. $allVersions[$subDir] = $version;
  314. } else if ($apk && $json && (!$platform || $platform == self::PLATFORM_ANDROID)) {
  315. $version = array();
  316. $version[self::FILE_ANDROID_APK] = $apk;
  317. $version[self::FILE_ANDROID_JSON] = $json;
  318. $version[self::FILE_COMMON_NOTES] = $note;
  319. $allVersions[$subDir] = $version;
  320. }
  321. }
  322. if (count($allVersions) > 0) {
  323. $files[self::VERSIONS_SPECIFIC_DATA] = $allVersions;
  324. $files[self::VERSIONS_COMMON_DATA][self::FILE_IOS_PROFILE] = $profile;
  325. $files[self::VERSIONS_COMMON_DATA][self::FILE_COMMON_ICON] = $icon;
  326. }
  327. }
  328. } else {
  329. $version = array();
  330. if ($ipa && $plist) {
  331. $version[self::FILE_IOS_IPA] = $ipa;
  332. $version[self::FILE_IOS_PLIST] = $plist;
  333. $version[self::FILE_COMMON_NOTES] = $note;
  334. $allVersions[] = $version;
  335. $files[self::VERSIONS_SPECIFIC_DATA] = $allVersions;
  336. $files[self::VERSIONS_COMMON_DATA][self::FILE_IOS_PROFILE] = $profile;
  337. $files[self::VERSIONS_COMMON_DATA][self::FILE_COMMON_ICON] = $icon;
  338. } else if ($apk && $json) {
  339. $version[self::FILE_ANDROID_APK] = $apk;
  340. $version[self::FILE_ANDROID_JSON] = $json;
  341. $version[self::FILE_COMMON_NOTES] = $note;
  342. $allVersions[] = $version;
  343. $files[self::VERSIONS_SPECIFIC_DATA] = $allVersions;
  344. $files[self::VERSIONS_COMMON_DATA][self::FILE_COMMON_ICON] = $icon;
  345. }
  346. }
  347. return $files;
  348. }
  349. protected function deliver($bundleidentifier, $api, $format)
  350. {
  351. $files = $this->getApplicationVersions($bundleidentifier);
  352. if (count($files) == 0) {
  353. Logger::log("no versions found: $bundleidentifier $api $format");
  354. return Helper::sendJSONAndExit(self::E_NO_VERSIONS_FOUND, $bundleidentifier);
  355. }
  356. $current = current($files[self::VERSIONS_SPECIFIC_DATA]);
  357. $ipa = isset($current[self::FILE_IOS_IPA]) ? $current[self::FILE_IOS_IPA] : null;
  358. $plist = isset($current[self::FILE_IOS_PLIST]) ? $current[self::FILE_IOS_PLIST] : null;
  359. $apk = isset($current[self::FILE_ANDROID_APK]) ? $current[self::FILE_ANDROID_APK] : null;
  360. $json = isset($current[self::FILE_ANDROID_JSON]) ? $current[self::FILE_ANDROID_JSON] : null;
  361. // notes file is optional, other files are required
  362. if ((!$ipa || !$plist) &&
  363. (!$apk || !$json)) {
  364. Logger::log("uncomplete files: $bundleidentifier");
  365. return Helper::sendJSONAndExit(self::E_FILES_INCOMPLETE, $bundleidentifier);
  366. }
  367. $profile = isset($files[self::VERSIONS_COMMON_DATA][self::FILE_IOS_PROFILE]) ?
  368. $files[self::VERSIONS_COMMON_DATA][self::FILE_IOS_PROFILE] : null;
  369. $image = isset($files[self::VERSIONS_COMMON_DATA][self::FILE_COMMON_ICON]) ?
  370. $files[self::VERSIONS_COMMON_DATA][self::FILE_COMMON_ICON] : null;
  371. $this->addStats($bundleidentifier, $format);
  372. switch ($format) {
  373. case self::PARAM_2_FORMAT_VALUE_MOBILEPROVISION:
  374. Helper::sendFile($profile);
  375. break;
  376. case self::PARAM_2_FORMAT_VALUE_PLIST:
  377. $pos = strpos($current[self::FILE_IOS_IPA], $bundleidentifier);
  378. $ipa_file = substr($current[self::FILE_IOS_IPA], $pos);
  379. $this->deliverIOSAppPlist($bundleidentifier, $plist, $image, $ipa_file);
  380. break;
  381. case self::PARAM_2_FORMAT_VALUE_IPA:
  382. Helper::sendFile($ipa);
  383. break;
  384. case self::PARAM_2_FORMAT_VALUE_APK:
  385. Helper::sendFile($apk, self::CONTENT_TYPE_APK);
  386. break;
  387. default: break;
  388. }
  389. exit();
  390. }
  391. protected function findPublicVersion($files)
  392. {
  393. $publicVersion = array();
  394. foreach ($files as $version => $fileSet) {
  395. if (isset($fileSet[self::FILE_ANDROID_APK])) {
  396. $publicVersion = $fileSet;
  397. break;
  398. }
  399. $restrict = isset($fileSet[self::FILE_VERSION_RESTRICT]) ? $fileSet[self::FILE_VERSION_RESTRICT] : null;
  400. if (isset($fileSet[self::FILE_IOS_IPA]) && $restrict && filesize($restrict) > 0) {
  401. continue;
  402. }
  403. $publicVersion = $fileSet;
  404. break;
  405. }
  406. return $publicVersion;
  407. }
  408. public function show($appBundleIdentifier)
  409. {
  410. // first get all the subdirectories, which do not have a file named "private" present
  411. if ($handle = opendir($this->appDirectory)) {
  412. while (($file = readdir($handle)) !== false) {
  413. if (
  414. in_array($file, array('.', '..')) ||
  415. !is_dir($this->appDirectory . $file) ||
  416. (
  417. glob($this->appDirectory . $file . '/private') &&
  418. !$appBundleIdentifier &&
  419. $this->logic != 'stats'
  420. )
  421. )
  422. {
  423. // skip if not a directory or has `private` file
  424. // but only if no bundle identifier is provided to this function
  425. continue;
  426. }
  427. // if a bundle identifier is provided and the directory does not match, continue
  428. if ($appBundleIdentifier && $file != $appBundleIdentifier) {
  429. continue;
  430. }
  431. // now check if this directory has the 3 mandatory files
  432. $files = $this->getApplicationVersions($file);
  433. if (count($files) == 0) {
  434. continue;
  435. }
  436. $current = $this->findPublicVersion($files[self::VERSIONS_SPECIFIC_DATA]);
  437. $ipa = isset($current[self::FILE_IOS_IPA]) ? $current[self::FILE_IOS_IPA] : null;
  438. $plist = isset($current[self::FILE_IOS_PLIST]) ? $current[self::FILE_IOS_PLIST] : null;
  439. $apk = isset($current[self::FILE_ANDROID_APK]) ? $current[self::FILE_ANDROID_APK] : null;
  440. $json = isset($current[self::FILE_ANDROID_JSON]) ? $current[self::FILE_ANDROID_JSON] : null;
  441. $note = isset($current[self::FILE_COMMON_NOTES]) ? $current[self::FILE_COMMON_NOTES] : null;
  442. $restrict = isset($current[self::FILE_VERSION_RESTRICT]) ? $current[self::FILE_VERSION_RESTRICT] : null;
  443. $profile = isset($files[self::VERSIONS_COMMON_DATA][self::FILE_IOS_PROFILE]) ?
  444. $files[self::VERSIONS_COMMON_DATA][self::FILE_IOS_PROFILE] : null;
  445. $image = isset($files[self::VERSIONS_COMMON_DATA][self::FILE_COMMON_ICON]) ?
  446. $files[self::VERSIONS_COMMON_DATA][self::FILE_COMMON_ICON] : null;
  447. if (!$ipa && !$apk) {
  448. continue;
  449. }
  450. // if this app version has any restrictions, don't show it on the web interface!
  451. // we make it easy for now and do not check if the data makes sense and has users assigned to the defined team names
  452. if ($restrict && strlen(file_get_contents($restrict)) > 0) {
  453. $current = $this->findPublicVersion($files);
  454. }
  455. $app = $ipa ? $ipa : $apk;
  456. $newApp = array();
  457. $newApp['path'] = substr($app, strpos($app, $file));
  458. $newApp[self::INDEX_DIR] = $file;
  459. $newApp[self::INDEX_IMAGE] = substr($image, strpos($image, $file));
  460. $newApp[self::INDEX_NOTES] = $note ? Helper::nl2br_skip_html(file_get_contents($note)) : '';
  461. $newApp[self::INDEX_STATS] = array();
  462. if ($ipa) {
  463. // iOS application
  464. $plistDocument = new DOMDocument();
  465. $plistDocument->load($plist);
  466. $parsed_plist = parsePlist($plistDocument);
  467. // now get the application name from the plist
  468. $newApp[self::INDEX_APP] = $parsed_plist['items'][0]['metadata']['title'];
  469. if (isset($parsed_plist['items'][0]['metadata']['subtitle']) && $parsed_plist['items'][0]['metadata']['subtitle'])
  470. $newApp[self::INDEX_SUBTITLE] = $parsed_plist['items'][0]['metadata']['subtitle'];
  471. $newApp[self::INDEX_VERSION] = $parsed_plist['items'][0]['metadata']['bundle-version'];
  472. $newApp[self::INDEX_DATE] = filectime($ipa);
  473. $newApp[self::INDEX_APPSIZE] = filesize($ipa);
  474. if ($profile) {
  475. $newApp[self::INDEX_PROFILE] = $profile;
  476. $newApp[self::INDEX_PROFILE_UPDATE] = filectime($profile);
  477. }
  478. $newApp[self::INDEX_PLATFORM] = self::APP_PLATFORM_IOS;
  479. } else if ($apk) {
  480. // Android Application
  481. // parse the json file
  482. $parsed_json = json_decode(file_get_contents($json), true);
  483. // now get the application name from the json file
  484. $newApp[self::INDEX_APP] = $parsed_json['title'];
  485. $newApp[self::INDEX_SUBTITLE] = $parsed_json['versionName'];
  486. $newApp[self::INDEX_VERSION] = $parsed_json['versionCode'];
  487. $newApp[self::INDEX_DATE] = filectime($apk);
  488. $newApp[self::INDEX_APPSIZE] = filesize($apk);
  489. $newApp[self::INDEX_PLATFORM] = self::APP_PLATFORM_ANDROID;
  490. if (!isset($newApp[self::INDEX_NOTES])) {
  491. $newApp[self::INDEX_NOTES] = isset($parsed_json['notes']) ? $parsed_json['notes'] : '';
  492. }
  493. }
  494. // now get the current user statistics
  495. $filename = $this->appDirectory."stats/".$file;
  496. if (file_exists($filename)) {
  497. $users = self::parseUserList();
  498. $lines = @file($filename, FILE_IGNORE_NEW_LINES);
  499. foreach ($lines as $i => $line) {
  500. if (!$line) continue;
  501. $device = explode(self::STATS_SEPARATOR, $line);
  502. $device[0] = strtolower($device[0]); // need case-insensitive match
  503. $newdevice = array();
  504. $newdevice[self::DEVICE_USER] = isset($users[$device[0]]) ? $users[$device[0]]['name'] : '-';
  505. $newdevice[self::DEVICE_PLATFORM] = Helper::mapPlatform(isset($device[1]) ? $device[1] : null);
  506. $newdevice[self::DEVICE_OSVERSION] = isset($device[2]) ? $device[2] : '';
  507. $newdevice[self::DEVICE_APPVERSION] = isset($device[3]) ? $device[3] : '';
  508. $newdevice[self::DEVICE_LASTCHECK] = isset($device[4]) ? $device[4] : '';
  509. $newdevice[self::DEVICE_LANGUAGE] = isset($device[5]) ? $device[5] : '';
  510. $newdevice[self::DEVICE_INSTALLDATE] = isset($device[6]) ? $device[6] : '';
  511. $newdevice[self::DEVICE_USAGETIME] = isset($device[7]) ? $device[7] : '';
  512. $newApp[self::INDEX_STATS][] = $newdevice;
  513. }
  514. // sort by app version
  515. $newApp[self::INDEX_STATS] = Helper::array_orderby($newApp[self::INDEX_STATS], self::DEVICE_APPVERSION, SORT_DESC, self::DEVICE_OSVERSION, SORT_DESC, self::DEVICE_PLATFORM, SORT_ASC, self::DEVICE_INSTALLDATE, SORT_DESC, self::DEVICE_LASTCHECK, SORT_DESC);
  516. }
  517. // add it to the array
  518. $this->applications[] = $newApp;
  519. }
  520. closedir($handle);
  521. }
  522. }
  523. protected function parseUserList()
  524. {
  525. $users = array();
  526. $userlistfilename = $this->appDirectory.self::FILE_USERLIST;
  527. $lines = @file($userlistfilename);
  528. if (!$lines)
  529. {
  530. return $users;
  531. }
  532. foreach ($lines as $line)
  533. {
  534. @list($udid, $name, $teams) = explode(";", $line);
  535. if (!$udid || isset($users[$udid])) continue;
  536. $udid = strtolower($udid); // could be uppercase
  537. $teams = array_filter(array_map('trim', explode(',', $teams)));
  538. $users[$udid] = array(
  539. 'udid' => $udid,
  540. 'name' => $name ? $name : '-',
  541. 'teams' => $teams,
  542. );
  543. }
  544. return $users;
  545. }
  546. protected function sort_versions($a, $b)
  547. {
  548. return version_compare($a, $b, '<');
  549. }
  550. }
  551. ?>