You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1246 lines
34 KiB

12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
13 years ago
13 years ago
12 years ago
12 years ago
12 years ago
11 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
11 years ago
13 years ago
13 years ago
13 years ago
13 years ago
12 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
12 years ago
12 years ago
14 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
  1. <?php
  2. /**
  3. * @author Arthur Schiwon <blizzz@owncloud.com>
  4. * @author Bart Visscher <bartv@thisnet.nl>
  5. * @author Bernhard Posselt <dev@bernhard-posselt.com>
  6. * @author Björn Schießle <schiessle@owncloud.com>
  7. * @author Borjan Tchakaloff <borjan@tchakaloff.fr>
  8. * @author Brice Maron <brice@bmaron.net>
  9. * @author Christopher Schäpers <kondou@ts.unde.re>
  10. * @author Felix Moeller <mail@felixmoeller.de>
  11. * @author Frank Karlitschek <frank@owncloud.org>
  12. * @author Georg Ehrke <georg@owncloud.com>
  13. * @author Jakob Sack <mail@jakobsack.de>
  14. * @author Jan-Christoph Borchardt <hey@jancborchardt.net>
  15. * @author Joas Schilling <nickvergessen@owncloud.com>
  16. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  17. * @author Kamil Domanski <kdomanski@kdemail.net>
  18. * @author Lukas Reschke <lukas@owncloud.com>
  19. * @author Markus Goetz <markus@woboq.com>
  20. * @author Morris Jobke <hey@morrisjobke.de>
  21. * @author Robin Appelman <icewind@owncloud.com>
  22. * @author Robin McCorkell <rmccorkell@karoshi.org.uk>
  23. * @author Sam Tuke <mail@samtuke.com>
  24. * @author Scrutinizer Auto-Fixer <auto-fixer@scrutinizer-ci.com>
  25. * @author Thomas Müller <thomas.mueller@tmit.eu>
  26. * @author Thomas Tanghus <thomas@tanghus.net>
  27. * @author Tom Needham <tom@owncloud.com>
  28. * @author Vincent Petry <pvince81@owncloud.com>
  29. *
  30. * @copyright Copyright (c) 2015, ownCloud, Inc.
  31. * @license AGPL-3.0
  32. *
  33. * This code is free software: you can redistribute it and/or modify
  34. * it under the terms of the GNU Affero General Public License, version 3,
  35. * as published by the Free Software Foundation.
  36. *
  37. * This program is distributed in the hope that it will be useful,
  38. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  39. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  40. * GNU Affero General Public License for more details.
  41. *
  42. * You should have received a copy of the GNU Affero General Public License, version 3,
  43. * along with this program. If not, see <http://www.gnu.org/licenses/>
  44. *
  45. */
  46. use OC\App\DependencyAnalyzer;
  47. use OC\App\Platform;
  48. use OC\OCSClient;
  49. /**
  50. * This class manages the apps. It allows them to register and integrate in the
  51. * ownCloud ecosystem. Furthermore, this class is responsible for installing,
  52. * upgrading and removing apps.
  53. */
  54. class OC_App {
  55. static private $appVersion = [];
  56. static private $adminForms = array();
  57. static private $personalForms = array();
  58. static private $appInfo = array();
  59. static private $appTypes = array();
  60. static private $loadedApps = array();
  61. static private $altLogin = array();
  62. private static $shippedApps = null;
  63. const officialApp = 200;
  64. /**
  65. * clean the appId
  66. *
  67. * @param string|boolean $app AppId that needs to be cleaned
  68. * @return string
  69. */
  70. public static function cleanAppId($app) {
  71. return str_replace(array('\0', '/', '\\', '..'), '', $app);
  72. }
  73. /**
  74. * loads all apps
  75. *
  76. * @param array $types
  77. * @return bool
  78. *
  79. * This function walks through the ownCloud directory and loads all apps
  80. * it can find. A directory contains an app if the file /appinfo/info.xml
  81. * exists.
  82. *
  83. * if $types is set, only apps of those types will be loaded
  84. */
  85. public static function loadApps($types = null) {
  86. if (OC_Config::getValue('maintenance', false)) {
  87. return false;
  88. }
  89. // Load the enabled apps here
  90. $apps = self::getEnabledApps();
  91. // prevent app.php from printing output
  92. ob_start();
  93. foreach ($apps as $app) {
  94. if ((is_null($types) or self::isType($app, $types)) && !in_array($app, self::$loadedApps)) {
  95. self::$loadedApps[] = $app;
  96. self::loadApp($app);
  97. }
  98. }
  99. ob_end_clean();
  100. return true;
  101. }
  102. /**
  103. * load a single app
  104. *
  105. * @param string $app
  106. * @param bool $checkUpgrade whether an upgrade check should be done
  107. * @throws \OC\NeedsUpdateException
  108. */
  109. public static function loadApp($app, $checkUpgrade = true) {
  110. if (is_file(self::getAppPath($app) . '/appinfo/app.php')) {
  111. \OC::$server->getEventLogger()->start('load_app_' . $app, 'Load app: ' . $app);
  112. if ($checkUpgrade and self::shouldUpgrade($app)) {
  113. throw new \OC\NeedsUpdateException();
  114. }
  115. self::requireAppFile($app);
  116. if (self::isType($app, array('authentication'))) {
  117. // since authentication apps affect the "is app enabled for group" check,
  118. // the enabled apps cache needs to be cleared to make sure that the
  119. // next time getEnableApps() is called it will also include apps that were
  120. // enabled for groups
  121. self::$enabledAppsCache = array();
  122. }
  123. \OC::$server->getEventLogger()->end('load_app_' . $app);
  124. }
  125. }
  126. /**
  127. * Load app.php from the given app
  128. *
  129. * @param string $app app name
  130. */
  131. private static function requireAppFile($app) {
  132. // encapsulated here to avoid variable scope conflicts
  133. require_once $app . '/appinfo/app.php';
  134. }
  135. /**
  136. * check if an app is of a specific type
  137. *
  138. * @param string $app
  139. * @param string|array $types
  140. * @return bool
  141. */
  142. public static function isType($app, $types) {
  143. if (is_string($types)) {
  144. $types = array($types);
  145. }
  146. $appTypes = self::getAppTypes($app);
  147. foreach ($types as $type) {
  148. if (array_search($type, $appTypes) !== false) {
  149. return true;
  150. }
  151. }
  152. return false;
  153. }
  154. /**
  155. * get the types of an app
  156. *
  157. * @param string $app
  158. * @return array
  159. */
  160. private static function getAppTypes($app) {
  161. //load the cache
  162. if (count(self::$appTypes) == 0) {
  163. self::$appTypes = OC_Appconfig::getValues(false, 'types');
  164. }
  165. if (isset(self::$appTypes[$app])) {
  166. return explode(',', self::$appTypes[$app]);
  167. } else {
  168. return array();
  169. }
  170. }
  171. /**
  172. * read app types from info.xml and cache them in the database
  173. */
  174. public static function setAppTypes($app) {
  175. $appData = self::getAppInfo($app);
  176. if (isset($appData['types'])) {
  177. $appTypes = implode(',', $appData['types']);
  178. } else {
  179. $appTypes = '';
  180. }
  181. OC_Appconfig::setValue($app, 'types', $appTypes);
  182. }
  183. /**
  184. * check if app is shipped
  185. *
  186. * @param string $appId the id of the app to check
  187. * @return bool
  188. *
  189. * Check if an app that is installed is a shipped app or installed from the appstore.
  190. */
  191. public static function isShipped($appId) {
  192. if (is_null(self::$shippedApps)) {
  193. $shippedJson = \OC::$SERVERROOT . '/core/shipped.json';
  194. if (file_exists($shippedJson)) {
  195. self::$shippedApps = json_decode(file_get_contents($shippedJson), true);
  196. self::$shippedApps = self::$shippedApps['shippedApps'];
  197. } else {
  198. self::$shippedApps = ['files', 'encryption', 'files_external',
  199. 'files_sharing', 'files_trashbin', 'files_versions', 'provisioning_api',
  200. 'user_ldap', 'user_webdavauth'];
  201. }
  202. }
  203. return in_array($appId, self::$shippedApps);
  204. }
  205. /**
  206. * get all enabled apps
  207. */
  208. protected static $enabledAppsCache = array();
  209. /**
  210. * Returns apps enabled for the current user.
  211. *
  212. * @param bool $forceRefresh whether to refresh the cache
  213. * @param bool $all whether to return apps for all users, not only the
  214. * currently logged in one
  215. * @return string[]
  216. */
  217. public static function getEnabledApps($forceRefresh = false, $all = false) {
  218. if (!OC_Config::getValue('installed', false)) {
  219. return array();
  220. }
  221. // in incognito mode or when logged out, $user will be false,
  222. // which is also the case during an upgrade
  223. $appManager = \OC::$server->getAppManager();
  224. if ($all) {
  225. $user = null;
  226. } else {
  227. $user = \OC::$server->getUserSession()->getUser();
  228. }
  229. if (is_null($user)) {
  230. $apps = $appManager->getInstalledApps();
  231. } else {
  232. $apps = $appManager->getEnabledAppsForUser($user);
  233. }
  234. $apps = array_filter($apps, function ($app) {
  235. return $app !== 'files';//we add this manually
  236. });
  237. sort($apps);
  238. array_unshift($apps, 'files');
  239. return $apps;
  240. }
  241. /**
  242. * checks whether or not an app is enabled
  243. *
  244. * @param string $app app
  245. * @return bool
  246. *
  247. * This function checks whether or not an app is enabled.
  248. */
  249. public static function isEnabled($app) {
  250. if ('files' == $app) {
  251. return true;
  252. }
  253. return \OC::$server->getAppManager()->isEnabledForUser($app);
  254. }
  255. /**
  256. * enables an app
  257. *
  258. * @param mixed $app app
  259. * @param array $groups (optional) when set, only these groups will have access to the app
  260. * @throws \Exception
  261. * @return void
  262. *
  263. * This function set an app as enabled in appconfig.
  264. */
  265. public static function enable($app, $groups = null) {
  266. self::$enabledAppsCache = array(); // flush
  267. if (!OC_Installer::isInstalled($app)) {
  268. $app = self::installApp($app);
  269. }
  270. $appManager = \OC::$server->getAppManager();
  271. if (!is_null($groups)) {
  272. $groupManager = \OC::$server->getGroupManager();
  273. $groupsList = [];
  274. foreach ($groups as $group) {
  275. $groupItem = $groupManager->get($group);
  276. if ($groupItem instanceof \OCP\IGroup) {
  277. $groupsList[] = $groupManager->get($group);
  278. }
  279. }
  280. $appManager->enableAppForGroups($app, $groupsList);
  281. } else {
  282. $appManager->enableApp($app);
  283. }
  284. }
  285. /**
  286. * @param string $app
  287. * @return int
  288. */
  289. public static function downloadApp($app) {
  290. $ocsClient = new OCSClient(
  291. \OC::$server->getHTTPClientService(),
  292. \OC::$server->getConfig(),
  293. \OC::$server->getLogger()
  294. );
  295. $appData = $ocsClient->getApplication($app);
  296. $download= $ocsClient->getApplicationDownload($app);
  297. if(isset($download['downloadlink']) and $download['downloadlink']!='') {
  298. // Replace spaces in download link without encoding entire URL
  299. $download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']);
  300. $info = array('source' => 'http', 'href' => $download['downloadlink'], 'appdata' => $appData);
  301. $app = OC_Installer::installApp($info);
  302. }
  303. return $app;
  304. }
  305. /**
  306. * @param string $app
  307. * @return bool
  308. */
  309. public static function removeApp($app) {
  310. if (self::isShipped($app)) {
  311. return false;
  312. }
  313. return OC_Installer::removeApp($app);
  314. }
  315. /**
  316. * This function set an app as disabled in appconfig.
  317. *
  318. * @param string $app app
  319. * @throws Exception
  320. */
  321. public static function disable($app) {
  322. // Convert OCS ID to regular application identifier
  323. if(self::getInternalAppIdByOcs($app) !== false) {
  324. $app = self::getInternalAppIdByOcs($app);
  325. }
  326. if($app === 'files') {
  327. throw new \Exception("files can't be disabled.");
  328. }
  329. self::$enabledAppsCache = array(); // flush
  330. // check if app is a shipped app or not. if not delete
  331. \OC_Hook::emit('OC_App', 'pre_disable', array('app' => $app));
  332. $appManager = \OC::$server->getAppManager();
  333. $appManager->disableApp($app);
  334. }
  335. /**
  336. * marks a navigation entry as active
  337. *
  338. * @param string $id id of the entry
  339. * @return bool
  340. *
  341. * This function sets a navigation entry as active and removes the 'active'
  342. * property from all other entries. The templates can use this for
  343. * highlighting the current position of the user.
  344. *
  345. * @deprecated Use \OC::$server->getNavigationManager()->setActiveEntry() instead
  346. */
  347. public static function setActiveNavigationEntry($id) {
  348. OC::$server->getNavigationManager()->setActiveEntry($id);
  349. return true;
  350. }
  351. /**
  352. * Get the navigation entries for the $app
  353. *
  354. * @param string $app app
  355. * @return array an array of the $data added with addNavigationEntry
  356. *
  357. * Warning: destroys the existing entries
  358. */
  359. public static function getAppNavigationEntries($app) {
  360. if (is_file(self::getAppPath($app) . '/appinfo/app.php')) {
  361. OC::$server->getNavigationManager()->clear();
  362. try {
  363. require $app . '/appinfo/app.php';
  364. } catch (\OC\Encryption\Exceptions\ModuleAlreadyExistsException $e) {
  365. // FIXME we should avoid getting this exception in first place,
  366. // For now we just catch it, since we don't care about encryption modules
  367. // when trying to find out, whether the app has a navigation entry.
  368. }
  369. return OC::$server->getNavigationManager()->getAll();
  370. }
  371. return array();
  372. }
  373. /**
  374. * gets the active Menu entry
  375. *
  376. * @return string id or empty string
  377. *
  378. * This function returns the id of the active navigation entry (set by
  379. * setActiveNavigationEntry
  380. *
  381. * @deprecated Use \OC::$server->getNavigationManager()->getActiveEntry() instead
  382. */
  383. public static function getActiveNavigationEntry() {
  384. return OC::$server->getNavigationManager()->getActiveEntry();
  385. }
  386. /**
  387. * Returns the Settings Navigation
  388. *
  389. * @return string
  390. *
  391. * This function returns an array containing all settings pages added. The
  392. * entries are sorted by the key 'order' ascending.
  393. */
  394. public static function getSettingsNavigation() {
  395. $l = \OC::$server->getL10N('lib');
  396. $settings = array();
  397. // by default, settings only contain the help menu
  398. if (OC_Util::getEditionString() === '' &&
  399. OC_Config::getValue('knowledgebaseenabled', true) == true
  400. ) {
  401. $settings = array(
  402. array(
  403. "id" => "help",
  404. "order" => 1000,
  405. "href" => OC_Helper::linkToRoute("settings_help"),
  406. "name" => $l->t("Help"),
  407. "icon" => OC_Helper::imagePath("settings", "help.svg")
  408. )
  409. );
  410. }
  411. // if the user is logged-in
  412. if (OC_User::isLoggedIn()) {
  413. // personal menu
  414. $settings[] = array(
  415. "id" => "personal",
  416. "order" => 1,
  417. "href" => OC_Helper::linkToRoute("settings_personal"),
  418. "name" => $l->t("Personal"),
  419. "icon" => OC_Helper::imagePath("settings", "personal.svg")
  420. );
  421. //SubAdmins are also allowed to access user management
  422. if (OC_SubAdmin::isSubAdmin(OC_User::getUser())) {
  423. // admin users menu
  424. $settings[] = array(
  425. "id" => "core_users",
  426. "order" => 2,
  427. "href" => OC_Helper::linkToRoute("settings_users"),
  428. "name" => $l->t("Users"),
  429. "icon" => OC_Helper::imagePath("settings", "users.svg")
  430. );
  431. }
  432. // if the user is an admin
  433. if (OC_User::isAdminUser(OC_User::getUser())) {
  434. // admin settings
  435. $settings[] = array(
  436. "id" => "admin",
  437. "order" => 1000,
  438. "href" => OC_Helper::linkToRoute("settings_admin"),
  439. "name" => $l->t("Admin"),
  440. "icon" => OC_Helper::imagePath("settings", "admin.svg")
  441. );
  442. }
  443. }
  444. $navigation = self::proceedNavigation($settings);
  445. return $navigation;
  446. }
  447. // This is private as well. It simply works, so don't ask for more details
  448. private static function proceedNavigation($list) {
  449. $activeApp = OC::$server->getNavigationManager()->getActiveEntry();
  450. foreach ($list as &$navEntry) {
  451. if ($navEntry['id'] == $activeApp) {
  452. $navEntry['active'] = true;
  453. } else {
  454. $navEntry['active'] = false;
  455. }
  456. }
  457. unset($navEntry);
  458. usort($list, create_function('$a, $b', 'if( $a["order"] == $b["order"] ) {return 0;}elseif( $a["order"] < $b["order"] ) {return -1;}else{return 1;}'));
  459. return $list;
  460. }
  461. /**
  462. * Get the path where to install apps
  463. *
  464. * @return string|false
  465. */
  466. public static function getInstallPath() {
  467. if (OC_Config::getValue('appstoreenabled', true) == false) {
  468. return false;
  469. }
  470. foreach (OC::$APPSROOTS as $dir) {
  471. if (isset($dir['writable']) && $dir['writable'] === true) {
  472. return $dir['path'];
  473. }
  474. }
  475. OC_Log::write('core', 'No application directories are marked as writable.', OC_Log::ERROR);
  476. return null;
  477. }
  478. /**
  479. * search for an app in all app-directories
  480. *
  481. * @param string $appId
  482. * @return mixed (bool|string)
  483. */
  484. protected static function findAppInDirectories($appId) {
  485. static $app_dir = array();
  486. if (isset($app_dir[$appId])) {
  487. return $app_dir[$appId];
  488. }
  489. $possibleApps = array();
  490. foreach (OC::$APPSROOTS as $dir) {
  491. if (file_exists($dir['path'] . '/' . $appId)) {
  492. $possibleApps[] = $dir;
  493. }
  494. }
  495. if (empty($possibleApps)) {
  496. return false;
  497. } elseif (count($possibleApps) === 1) {
  498. $dir = array_shift($possibleApps);
  499. $app_dir[$appId] = $dir;
  500. return $dir;
  501. } else {
  502. $versionToLoad = array();
  503. foreach ($possibleApps as $possibleApp) {
  504. $version = self::getAppVersionByPath($possibleApp['path']);
  505. if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
  506. $versionToLoad = array(
  507. 'dir' => $possibleApp,
  508. 'version' => $version,
  509. );
  510. }
  511. }
  512. $app_dir[$appId] = $versionToLoad['dir'];
  513. return $versionToLoad['dir'];
  514. //TODO - write test
  515. }
  516. }
  517. /**
  518. * Get the directory for the given app.
  519. * If the app is defined in multiple directories, the first one is taken. (false if not found)
  520. *
  521. * @param string $appId
  522. * @return string|false
  523. */
  524. public static function getAppPath($appId) {
  525. if ($appId === null || trim($appId) === '') {
  526. return false;
  527. }
  528. if (($dir = self::findAppInDirectories($appId)) != false) {
  529. return $dir['path'] . '/' . $appId;
  530. }
  531. return false;
  532. }
  533. /**
  534. * check if an app's directory is writable
  535. *
  536. * @param string $appId
  537. * @return bool
  538. */
  539. public static function isAppDirWritable($appId) {
  540. $path = self::getAppPath($appId);
  541. return ($path !== false) ? is_writable($path) : false;
  542. }
  543. /**
  544. * Get the path for the given app on the access
  545. * If the app is defined in multiple directories, the first one is taken. (false if not found)
  546. *
  547. * @param string $appId
  548. * @return string|false
  549. */
  550. public static function getAppWebPath($appId) {
  551. if (($dir = self::findAppInDirectories($appId)) != false) {
  552. return OC::$WEBROOT . $dir['url'] . '/' . $appId;
  553. }
  554. return false;
  555. }
  556. /**
  557. * get the last version of the app, either from appinfo/version or from appinfo/info.xml
  558. *
  559. * @param string $appId
  560. * @return string
  561. */
  562. public static function getAppVersion($appId) {
  563. if (!isset(self::$appVersion[$appId])) {
  564. $file = self::getAppPath($appId);
  565. self::$appVersion[$appId] = ($file !== false) ? self::getAppVersionByPath($file) : '0';
  566. }
  567. return self::$appVersion[$appId];
  568. }
  569. /**
  570. * get app's version based on it's path
  571. *
  572. * @param string $path
  573. * @return string
  574. */
  575. public static function getAppVersionByPath($path) {
  576. $versionFile = $path . '/appinfo/version';
  577. $infoFile = $path . '/appinfo/info.xml';
  578. if (is_file($versionFile)) {
  579. return trim(file_get_contents($versionFile));
  580. } else {
  581. $appData = self::getAppInfo($infoFile, true);
  582. return isset($appData['version']) ? $appData['version'] : '';
  583. }
  584. }
  585. /**
  586. * Read all app metadata from the info.xml file
  587. *
  588. * @param string $appId id of the app or the path of the info.xml file
  589. * @param boolean $path (optional)
  590. * @return array|null
  591. * @note all data is read from info.xml, not just pre-defined fields
  592. */
  593. public static function getAppInfo($appId, $path = false) {
  594. if ($path) {
  595. $file = $appId;
  596. } else {
  597. if (isset(self::$appInfo[$appId])) {
  598. return self::$appInfo[$appId];
  599. }
  600. $file = self::getAppPath($appId) . '/appinfo/info.xml';
  601. }
  602. $parser = new \OC\App\InfoParser(\OC::$server->getHTTPHelper(), \OC::$server->getURLGenerator());
  603. $data = $parser->parse($file);
  604. if (is_array($data)) {
  605. $data = OC_App::parseAppInfo($data);
  606. }
  607. self::$appInfo[$appId] = $data;
  608. return $data;
  609. }
  610. /**
  611. * Returns the navigation
  612. *
  613. * @return array
  614. *
  615. * This function returns an array containing all entries added. The
  616. * entries are sorted by the key 'order' ascending. Additional to the keys
  617. * given for each app the following keys exist:
  618. * - active: boolean, signals if the user is on this navigation entry
  619. */
  620. public static function getNavigation() {
  621. $entries = OC::$server->getNavigationManager()->getAll();
  622. $navigation = self::proceedNavigation($entries);
  623. return $navigation;
  624. }
  625. /**
  626. * get the id of loaded app
  627. *
  628. * @return string
  629. */
  630. public static function getCurrentApp() {
  631. $request = \OC::$server->getRequest();
  632. $script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
  633. $topFolder = substr($script, 0, strpos($script, '/'));
  634. if (empty($topFolder)) {
  635. $path_info = $request->getPathInfo();
  636. if ($path_info) {
  637. $topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
  638. }
  639. }
  640. if ($topFolder == 'apps') {
  641. $length = strlen($topFolder);
  642. return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1);
  643. } else {
  644. return $topFolder;
  645. }
  646. }
  647. /**
  648. * @param string $type
  649. * @return array
  650. */
  651. public static function getForms($type) {
  652. $forms = array();
  653. switch ($type) {
  654. case 'admin':
  655. $source = self::$adminForms;
  656. break;
  657. case 'personal':
  658. $source = self::$personalForms;
  659. break;
  660. default:
  661. return array();
  662. }
  663. foreach ($source as $form) {
  664. $forms[] = include $form;
  665. }
  666. return $forms;
  667. }
  668. /**
  669. * register an admin form to be shown
  670. *
  671. * @param string $app
  672. * @param string $page
  673. */
  674. public static function registerAdmin($app, $page) {
  675. self::$adminForms[] = $app . '/' . $page . '.php';
  676. }
  677. /**
  678. * register a personal form to be shown
  679. * @param string $app
  680. * @param string $page
  681. */
  682. public static function registerPersonal($app, $page) {
  683. self::$personalForms[] = $app . '/' . $page . '.php';
  684. }
  685. /**
  686. * @param array $entry
  687. */
  688. public static function registerLogIn(array $entry) {
  689. self::$altLogin[] = $entry;
  690. }
  691. /**
  692. * @return array
  693. */
  694. public static function getAlternativeLogIns() {
  695. return self::$altLogin;
  696. }
  697. /**
  698. * get a list of all apps in the apps folder
  699. *
  700. * @return array an array of app names (string IDs)
  701. * @todo: change the name of this method to getInstalledApps, which is more accurate
  702. */
  703. public static function getAllApps() {
  704. $apps = array();
  705. foreach (OC::$APPSROOTS as $apps_dir) {
  706. if (!is_readable($apps_dir['path'])) {
  707. OC_Log::write('core', 'unable to read app folder : ' . $apps_dir['path'], OC_Log::WARN);
  708. continue;
  709. }
  710. $dh = opendir($apps_dir['path']);
  711. if (is_resource($dh)) {
  712. while (($file = readdir($dh)) !== false) {
  713. if ($file[0] != '.' and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
  714. $apps[] = $file;
  715. }
  716. }
  717. }
  718. }
  719. return $apps;
  720. }
  721. /**
  722. * List all apps, this is used in apps.php
  723. *
  724. * @param bool $onlyLocal
  725. * @param bool $includeUpdateInfo Should we check whether there is an update
  726. * in the app store?
  727. * @return array
  728. */
  729. public static function listAllApps($onlyLocal = false, $includeUpdateInfo = true) {
  730. $installedApps = OC_App::getAllApps();
  731. //TODO which apps do we want to blacklist and how do we integrate
  732. // blacklisting with the multi apps folder feature?
  733. $blacklist = array('files'); //we don't want to show configuration for these
  734. $appList = array();
  735. $l = \OC::$server->getL10N('core');
  736. foreach ($installedApps as $app) {
  737. if (array_search($app, $blacklist) === false) {
  738. $info = OC_App::getAppInfo($app);
  739. if (!isset($info['name'])) {
  740. OC_Log::write('core', 'App id "' . $app . '" has no name in appinfo', OC_Log::ERROR);
  741. continue;
  742. }
  743. $enabled = OC_Appconfig::getValue($app, 'enabled', 'no');
  744. $info['groups'] = null;
  745. if ($enabled === 'yes') {
  746. $active = true;
  747. } else if ($enabled === 'no') {
  748. $active = false;
  749. } else {
  750. $active = true;
  751. $info['groups'] = $enabled;
  752. }
  753. $info['active'] = $active;
  754. if (isset($info['shipped']) and ($info['shipped'] == 'true')) {
  755. $info['internal'] = true;
  756. $info['level'] = self::officialApp;
  757. $info['removable'] = false;
  758. } else {
  759. $info['internal'] = false;
  760. $info['removable'] = true;
  761. }
  762. $info['update'] = ($includeUpdateInfo) ? OC_Installer::isUpdateAvailable($app) : null;
  763. $appIcon = self::getAppPath($app) . '/img/' . $app . '.svg';
  764. if (file_exists($appIcon)) {
  765. $info['preview'] = OC_Helper::imagePath($app, $app . '.svg');
  766. $info['previewAsIcon'] = true;
  767. } else {
  768. $appIcon = self::getAppPath($app) . '/img/app.svg';
  769. if (file_exists($appIcon)) {
  770. $info['preview'] = OC_Helper::imagePath($app, 'app.svg');
  771. $info['previewAsIcon'] = true;
  772. }
  773. }
  774. $info['version'] = OC_App::getAppVersion($app);
  775. $appList[] = $info;
  776. }
  777. }
  778. if ($onlyLocal) {
  779. $remoteApps = [];
  780. } else {
  781. $remoteApps = OC_App::getAppstoreApps();
  782. }
  783. if ($remoteApps) {
  784. // Remove duplicates
  785. foreach ($appList as $app) {
  786. foreach ($remoteApps AS $key => $remote) {
  787. if ($app['name'] === $remote['name'] ||
  788. (isset($app['ocsid']) &&
  789. $app['ocsid'] === $remote['id'])
  790. ) {
  791. unset($remoteApps[$key]);
  792. }
  793. }
  794. }
  795. $combinedApps = array_merge($appList, $remoteApps);
  796. } else {
  797. $combinedApps = $appList;
  798. }
  799. return $combinedApps;
  800. }
  801. /**
  802. * Returns the internal app ID or false
  803. * @param string $ocsID
  804. * @return string|false
  805. */
  806. protected static function getInternalAppIdByOcs($ocsID) {
  807. if(is_numeric($ocsID)) {
  808. $idArray = \OC::$server->getAppConfig()->getValues(false, 'ocsid');
  809. if(array_search($ocsID, $idArray)) {
  810. return array_search($ocsID, $idArray);
  811. }
  812. }
  813. return false;
  814. }
  815. /**
  816. * Get a list of all apps on the appstore
  817. * @param string $filter
  818. * @param string $category
  819. * @return array|bool multi-dimensional array of apps.
  820. * Keys: id, name, type, typename, personid, license, detailpage, preview, changed, description
  821. */
  822. public static function getAppstoreApps($filter = 'approved', $category = null) {
  823. $categories = [$category];
  824. $ocsClient = new OCSClient(
  825. \OC::$server->getHTTPClientService(),
  826. \OC::$server->getConfig(),
  827. \OC::$server->getLogger()
  828. );
  829. if (is_null($category)) {
  830. $categoryNames = $ocsClient->getCategories();
  831. if (is_array($categoryNames)) {
  832. // Check that categories of apps were retrieved correctly
  833. if (!$categories = array_keys($categoryNames)) {
  834. return false;
  835. }
  836. } else {
  837. return false;
  838. }
  839. }
  840. $page = 0;
  841. $remoteApps = $ocsClient->getApplications($categories, $page, $filter);
  842. $apps = [];
  843. $i = 0;
  844. $l = \OC::$server->getL10N('core');
  845. foreach ($remoteApps as $app) {
  846. $potentialCleanId = self::getInternalAppIdByOcs($app['id']);
  847. // enhance app info (for example the description)
  848. $apps[$i] = OC_App::parseAppInfo($app);
  849. $apps[$i]['author'] = $app['personid'];
  850. $apps[$i]['ocs_id'] = $app['id'];
  851. $apps[$i]['internal'] = 0;
  852. $apps[$i]['active'] = ($potentialCleanId !== false) ? self::isEnabled($potentialCleanId) : false;
  853. $apps[$i]['update'] = false;
  854. $apps[$i]['groups'] = false;
  855. $apps[$i]['score'] = $app['score'];
  856. $apps[$i]['removable'] = false;
  857. if ($app['label'] == 'recommended') {
  858. $apps[$i]['internallabel'] = (string)$l->t('Recommended');
  859. $apps[$i]['internalclass'] = 'recommendedapp';
  860. }
  861. $i++;
  862. }
  863. if (empty($apps)) {
  864. return false;
  865. } else {
  866. return $apps;
  867. }
  868. }
  869. public static function shouldUpgrade($app) {
  870. $versions = self::getAppVersions();
  871. $currentVersion = OC_App::getAppVersion($app);
  872. if ($currentVersion && isset($versions[$app])) {
  873. $installedVersion = $versions[$app];
  874. if (version_compare($currentVersion, $installedVersion, '>')) {
  875. return true;
  876. }
  877. }
  878. return false;
  879. }
  880. /**
  881. * Adjust the number of version parts of $version1 to match
  882. * the number of version parts of $version2.
  883. *
  884. * @param string $version1 version to adjust
  885. * @param string $version2 version to take the number of parts from
  886. * @return string shortened $version1
  887. */
  888. private static function adjustVersionParts($version1, $version2) {
  889. $version1 = explode('.', $version1);
  890. $version2 = explode('.', $version2);
  891. // reduce $version1 to match the number of parts in $version2
  892. while (count($version1) > count($version2)) {
  893. array_pop($version1);
  894. }
  895. // if $version1 does not have enough parts, add some
  896. while (count($version1) < count($version2)) {
  897. $version1[] = '0';
  898. }
  899. return implode('.', $version1);
  900. }
  901. /**
  902. * Check whether the current ownCloud version matches the given
  903. * application's version requirements.
  904. *
  905. * The comparison is made based on the number of parts that the
  906. * app info version has. For example for ownCloud 6.0.3 if the
  907. * app info version is expecting version 6.0, the comparison is
  908. * made on the first two parts of the ownCloud version.
  909. * This means that it's possible to specify "requiremin" => 6
  910. * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
  911. *
  912. * @param string $ocVersion ownCloud version to check against
  913. * @param array $appInfo app info (from xml)
  914. *
  915. * @return boolean true if compatible, otherwise false
  916. */
  917. public static function isAppCompatible($ocVersion, $appInfo) {
  918. $requireMin = '';
  919. $requireMax = '';
  920. if (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
  921. $requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
  922. } else if (isset($appInfo['requiremin'])) {
  923. $requireMin = $appInfo['requiremin'];
  924. } else if (isset($appInfo['require'])) {
  925. $requireMin = $appInfo['require'];
  926. }
  927. if (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
  928. $requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
  929. } else if (isset($appInfo['requiremax'])) {
  930. $requireMax = $appInfo['requiremax'];
  931. }
  932. if (is_array($ocVersion)) {
  933. $ocVersion = implode('.', $ocVersion);
  934. }
  935. if (!empty($requireMin)
  936. && version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
  937. ) {
  938. return false;
  939. }
  940. if (!empty($requireMax)
  941. && version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
  942. ) {
  943. return false;
  944. }
  945. return true;
  946. }
  947. /**
  948. * get the installed version of all apps
  949. */
  950. public static function getAppVersions() {
  951. static $versions;
  952. if (isset($versions)) { // simple cache, needs to be fixed
  953. return $versions; // when function is used besides in checkUpgrade
  954. }
  955. $versions = array();
  956. try {
  957. $query = OC_DB::prepare('SELECT `appid`, `configvalue` FROM `*PREFIX*appconfig`'
  958. . ' WHERE `configkey` = \'installed_version\'');
  959. $result = $query->execute();
  960. while ($row = $result->fetchRow()) {
  961. $versions[$row['appid']] = $row['configvalue'];
  962. }
  963. return $versions;
  964. } catch (\Exception $e) {
  965. return array();
  966. }
  967. }
  968. /**
  969. * @param mixed $app
  970. * @return bool
  971. * @throws Exception if app is not compatible with this version of ownCloud
  972. * @throws Exception if no app-name was specified
  973. */
  974. public static function installApp($app) {
  975. $l = \OC::$server->getL10N('core');
  976. $config = \OC::$server->getConfig();
  977. $ocsClient = new OCSClient(
  978. \OC::$server->getHTTPClientService(),
  979. $config,
  980. \OC::$server->getLogger()
  981. );
  982. $appData = $ocsClient->getApplication($app);
  983. // check if app is a shipped app or not. OCS apps have an integer as id, shipped apps use a string
  984. if (!is_numeric($app)) {
  985. $shippedVersion = self::getAppVersion($app);
  986. if ($appData && version_compare($shippedVersion, $appData['version'], '<')) {
  987. $app = self::downloadApp($app);
  988. } else {
  989. $app = OC_Installer::installShippedApp($app);
  990. }
  991. } else {
  992. // Maybe the app is already installed - compare the version in this
  993. // case and use the local already installed one.
  994. // FIXME: This is a horrible hack. I feel sad. The god of code cleanness may forgive me.
  995. $internalAppId = self::getInternalAppIdByOcs($app);
  996. if($internalAppId !== false) {
  997. if($appData && version_compare(\OC_App::getAppVersion($internalAppId), $appData['version'], '<')) {
  998. $app = self::downloadApp($app);
  999. } else {
  1000. self::enable($internalAppId);
  1001. $app = $internalAppId;
  1002. }
  1003. } else {
  1004. $app = self::downloadApp($app);
  1005. }
  1006. }
  1007. if ($app !== false) {
  1008. // check if the app is compatible with this version of ownCloud
  1009. $info = self::getAppInfo($app);
  1010. $version = OC_Util::getVersion();
  1011. if (!self::isAppCompatible($version, $info)) {
  1012. throw new \Exception(
  1013. $l->t('App "%s" cannot be installed because it is not compatible with this version of ownCloud.',
  1014. array($info['name'])
  1015. )
  1016. );
  1017. }
  1018. // check for required dependencies
  1019. $dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
  1020. $missing = $dependencyAnalyzer->analyze($app);
  1021. if (!empty($missing)) {
  1022. $missingMsg = join(PHP_EOL, $missing);
  1023. throw new \Exception(
  1024. $l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s',
  1025. array($info['name'], $missingMsg)
  1026. )
  1027. );
  1028. }
  1029. $config->setAppValue($app, 'enabled', 'yes');
  1030. if (isset($appData['id'])) {
  1031. $config->setAppValue($app, 'ocsid', $appData['id']);
  1032. }
  1033. \OC_Hook::emit('OC_App', 'post_enable', array('app' => $app));
  1034. } else {
  1035. throw new \Exception($l->t("No app name specified"));
  1036. }
  1037. return $app;
  1038. }
  1039. /**
  1040. * update the database for the app and call the update script
  1041. *
  1042. * @param string $appId
  1043. * @return bool
  1044. */
  1045. public static function updateApp($appId) {
  1046. if (file_exists(self::getAppPath($appId) . '/appinfo/database.xml')) {
  1047. OC_DB::updateDbFromStructure(self::getAppPath($appId) . '/appinfo/database.xml');
  1048. }
  1049. unset(self::$appVersion[$appId]);
  1050. if (!self::isEnabled($appId)) {
  1051. return false;
  1052. }
  1053. if (file_exists(self::getAppPath($appId) . '/appinfo/update.php')) {
  1054. self::loadApp($appId, false);
  1055. include self::getAppPath($appId) . '/appinfo/update.php';
  1056. }
  1057. //set remote/public handlers
  1058. $appData = self::getAppInfo($appId);
  1059. if (array_key_exists('ocsid', $appData)) {
  1060. OC_Appconfig::setValue($appId, 'ocsid', $appData['ocsid']);
  1061. }
  1062. foreach ($appData['remote'] as $name => $path) {
  1063. OCP\CONFIG::setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
  1064. }
  1065. foreach ($appData['public'] as $name => $path) {
  1066. OCP\CONFIG::setAppValue('core', 'public_' . $name, $appId . '/' . $path);
  1067. }
  1068. self::setAppTypes($appId);
  1069. $version = \OC_App::getAppVersion($appId);
  1070. \OC_Appconfig::setValue($appId, 'installed_version', $version);
  1071. return true;
  1072. }
  1073. /**
  1074. * @param string $appId
  1075. * @return \OC\Files\View|false
  1076. */
  1077. public static function getStorage($appId) {
  1078. if (OC_App::isEnabled($appId)) { //sanity check
  1079. if (OC_User::isLoggedIn()) {
  1080. $view = new \OC\Files\View('/' . OC_User::getUser());
  1081. if (!$view->file_exists($appId)) {
  1082. $view->mkdir($appId);
  1083. }
  1084. return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
  1085. } else {
  1086. OC_Log::write('core', 'Can\'t get app storage, app ' . $appId . ', user not logged in', OC_Log::ERROR);
  1087. return false;
  1088. }
  1089. } else {
  1090. OC_Log::write('core', 'Can\'t get app storage, app ' . $appId . ' not enabled', OC_Log::ERROR);
  1091. return false;
  1092. }
  1093. }
  1094. /**
  1095. * parses the app data array and enhanced the 'description' value
  1096. *
  1097. * @param array $data the app data
  1098. * @return array improved app data
  1099. */
  1100. public static function parseAppInfo(array $data) {
  1101. // just modify the description if it is available
  1102. // otherwise this will create a $data element with an empty 'description'
  1103. if (isset($data['description'])) {
  1104. // sometimes the description contains line breaks and they are then also
  1105. // shown in this way in the app management which isn't wanted as HTML
  1106. // manages line breaks itself
  1107. // first of all we split on empty lines
  1108. $paragraphs = preg_split("!\n[[:space:]]*\n!m", $data['description']);
  1109. $result = [];
  1110. foreach ($paragraphs as $value) {
  1111. // replace multiple whitespace (tabs, space, newlines) inside a paragraph
  1112. // with a single space - also trims whitespace
  1113. $result[] = trim(preg_replace('![[:space:]]+!m', ' ', $value));
  1114. }
  1115. // join the single paragraphs with a empty line in between
  1116. $data['description'] = implode("\n\n", $result);
  1117. }
  1118. return $data;
  1119. }
  1120. }