app.php 34 KB

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