app.php 37 KB

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