app.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  1. <?php
  2. /**
  3. * ownCloud
  4. *
  5. * @author Frank Karlitschek
  6. * @author Jakob Sack
  7. * @copyright 2012 Frank Karlitschek frank@owncloud.org
  8. *
  9. * This library is free software; you can redistribute it and/or
  10. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  11. * License as published by the Free Software Foundation; either
  12. * version 3 of the License, or any later version.
  13. *
  14. * This library is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  18. *
  19. * You should have received a copy of the GNU Affero General Public
  20. * License along with this library. If not, see <http://www.gnu.org/licenses/>.
  21. *
  22. */
  23. /**
  24. * This class manages the apps. It allows them to register and integrate in the
  25. * owncloud ecosystem. Furthermore, this class is responsible for installing,
  26. * upgrading and removing apps.
  27. */
  28. class OC_App{
  29. static private $activeapp = '';
  30. static private $navigation = array();
  31. static private $settingsForms = array();
  32. static private $adminForms = array();
  33. static private $personalForms = array();
  34. static private $appInfo = array();
  35. static private $appTypes = array();
  36. static private $loadedApps = array();
  37. static private $checkedApps = array();
  38. /**
  39. * @brief loads all apps
  40. * @param array $types
  41. * @returns true/false
  42. *
  43. * This function walks through the owncloud directory and loads all apps
  44. * it can find. A directory contains an app if the file /appinfo/app.php
  45. * exists.
  46. *
  47. * if $types is set, only apps of those types will be loaded
  48. */
  49. public static function loadApps($types=null){
  50. // Load the enabled apps here
  51. $apps = self::getEnabledApps();
  52. // prevent app.php from printing output
  53. ob_start();
  54. foreach( $apps as $app ){
  55. if((is_null($types) or self::isType($app, $types)) && !in_array($app, self::$loadedApps)) {
  56. self::loadApp($app);
  57. self::$loadedApps[] = $app;
  58. }
  59. }
  60. ob_end_clean();
  61. if (!defined('DEBUG') || !DEBUG) {
  62. if (is_null($types)) {
  63. OC_Util::$core_scripts = OC_Util::$scripts;
  64. OC_Util::$scripts = array();
  65. OC_Util::$core_styles = OC_Util::$styles;
  66. OC_Util::$styles = array();
  67. if (!OC_AppConfig::getValue('core', 'remote_core.css', false)) {
  68. OC_AppConfig::setValue('core', 'remote_core.css', '/core/minimizer.php');
  69. OC_AppConfig::setValue('core', 'remote_core.js', '/core/minimizer.php');
  70. }
  71. }
  72. }
  73. // return
  74. return true;
  75. }
  76. /**
  77. * load a single app
  78. * @param string app
  79. */
  80. public static function loadApp($app) {
  81. if(is_file(self::getAppPath($app).'/appinfo/app.php')) {
  82. self::checkUpgrade($app);
  83. require_once $app.'/appinfo/app.php';
  84. }
  85. }
  86. /**
  87. * check if an app is of a specific type
  88. * @param string $app
  89. * @param string/array $types
  90. */
  91. public static function isType($app,$types){
  92. if(is_string($types)) {
  93. $types=array($types);
  94. }
  95. $appTypes=self::getAppTypes($app);
  96. foreach($types as $type){
  97. if(array_search($type, $appTypes)!==false) {
  98. return true;
  99. }
  100. }
  101. return false;
  102. }
  103. /**
  104. * get the types of an app
  105. * @param string $app
  106. * @return array
  107. */
  108. private static function getAppTypes($app){
  109. //load the cache
  110. if(count(self::$appTypes)==0) {
  111. self::$appTypes=OC_Appconfig::getValues(false, 'types');
  112. }
  113. if(isset(self::$appTypes[$app])) {
  114. return explode(',', self::$appTypes[$app]);
  115. }else{
  116. return array();
  117. }
  118. }
  119. /**
  120. * read app types from info.xml and cache them in the database
  121. */
  122. public static function setAppTypes($app){
  123. $appData=self::getAppInfo($app);
  124. if(isset($appData['types'])) {
  125. $appTypes=implode(',', $appData['types']);
  126. }else{
  127. $appTypes='';
  128. }
  129. OC_Appconfig::setValue($app, 'types', $appTypes);
  130. }
  131. /**
  132. * get all enabled apps
  133. */
  134. public static function getEnabledApps(){
  135. if(!OC_Config::getValue('installed', false))
  136. return array();
  137. $apps=array('files');
  138. $query = OC_DB::prepare( 'SELECT `appid` FROM `*PREFIX*appconfig` WHERE `configkey` = \'enabled\' AND `configvalue`=\'yes\'' );
  139. $result=$query->execute();
  140. while($row=$result->fetchRow()){
  141. if(array_search($row['appid'], $apps)===false) {
  142. $apps[]=$row['appid'];
  143. }
  144. }
  145. return $apps;
  146. }
  147. /**
  148. * @brief checks whether or not an app is enabled
  149. * @param $app app
  150. * @returns true/false
  151. *
  152. * This function checks whether or not an app is enabled.
  153. */
  154. public static function isEnabled( $app ){
  155. if( 'files'==$app or 'yes' == OC_Appconfig::getValue( $app, 'enabled' )) {
  156. return true;
  157. }
  158. return false;
  159. }
  160. /**
  161. * @brief enables an app
  162. * @param $app app
  163. * @returns true/false
  164. *
  165. * This function set an app as enabled in appconfig.
  166. */
  167. public static function enable( $app ){
  168. if(!OC_Installer::isInstalled($app)) {
  169. // check if app is a shipped app or not. OCS apps have an integer as id, shipped apps use a string
  170. if(!is_numeric($app)) {
  171. $app = OC_Installer::installShippedApp($app);
  172. }else{
  173. $download=OC_OCSClient::getApplicationDownload($app, 1);
  174. if(isset($download['downloadlink']) and $download['downloadlink']!='') {
  175. $app=OC_Installer::installApp(array('source'=>'http','href'=>$download['downloadlink']));
  176. }
  177. }
  178. }
  179. if($app!==false) {
  180. // check if the app is compatible with this version of ownCloud
  181. $info=OC_App::getAppInfo($app);
  182. $version=OC_Util::getVersion();
  183. if(!isset($info['require']) or ($version[0]>$info['require'])) {
  184. OC_Log::write('core', 'App "'.$info['name'].'" can\'t be installed because it is not compatible with this version of ownCloud', OC_Log::ERROR);
  185. return false;
  186. }else{
  187. OC_Appconfig::setValue( $app, 'enabled', 'yes' );
  188. return true;
  189. }
  190. }else{
  191. return false;
  192. }
  193. return $app;
  194. }
  195. /**
  196. * @brief disables an app
  197. * @param $app app
  198. * @returns true/false
  199. *
  200. * This function set an app as disabled in appconfig.
  201. */
  202. public static function disable( $app ){
  203. // check if app is a shiped app or not. if not delete
  204. OC_Appconfig::setValue( $app, 'enabled', 'no' );
  205. }
  206. /**
  207. * @brief adds an entry to the navigation
  208. * @param $data array containing the data
  209. * @returns true/false
  210. *
  211. * This function adds a new entry to the navigation visible to users. $data
  212. * is an associative array.
  213. * The following keys are required:
  214. * - id: unique id for this entry ('addressbook_index')
  215. * - href: link to the page
  216. * - name: Human readable name ('Addressbook')
  217. *
  218. * The following keys are optional:
  219. * - icon: path to the icon of the app
  220. * - order: integer, that influences the position of your application in
  221. * the navigation. Lower values come first.
  222. */
  223. public static function addNavigationEntry( $data ){
  224. $data['active']=false;
  225. if(!isset($data['icon'])) {
  226. $data['icon']='';
  227. }
  228. OC_App::$navigation[] = $data;
  229. return true;
  230. }
  231. /**
  232. * @brief marks a navigation entry as active
  233. * @param $id id of the entry
  234. * @returns true/false
  235. *
  236. * This function sets a navigation entry as active and removes the 'active'
  237. * property from all other entries. The templates can use this for
  238. * highlighting the current position of the user.
  239. */
  240. public static function setActiveNavigationEntry( $id ){
  241. self::$activeapp = $id;
  242. return true;
  243. }
  244. /**
  245. * @brief gets the active Menu entry
  246. * @returns id or empty string
  247. *
  248. * This function returns the id of the active navigation entry (set by
  249. * setActiveNavigationEntry
  250. */
  251. public static function getActiveNavigationEntry(){
  252. return self::$activeapp;
  253. }
  254. /**
  255. * @brief Returns the Settings Navigation
  256. * @returns associative array
  257. *
  258. * This function returns an array containing all settings pages added. The
  259. * entries are sorted by the key 'order' ascending.
  260. */
  261. public static function getSettingsNavigation(){
  262. $l=OC_L10N::get('lib');
  263. $settings = array();
  264. // by default, settings only contain the help menu
  265. if(OC_Config::getValue('knowledgebaseenabled', true)==true) {
  266. $settings = array(
  267. array( "id" => "help", "order" => 1000, "href" => OC_Helper::linkTo( "settings", "help.php" ), "name" => $l->t("Help"), "icon" => OC_Helper::imagePath( "settings", "help.svg" ))
  268. );
  269. }
  270. // if the user is logged-in
  271. if (OC_User::isLoggedIn()) {
  272. // personal menu
  273. $settings[] = array( "id" => "personal", "order" => 1, "href" => OC_Helper::linkTo( "settings", "personal.php" ), "name" => $l->t("Personal"), "icon" => OC_Helper::imagePath( "settings", "personal.svg" ));
  274. // if there're some settings forms
  275. if(!empty(self::$settingsForms))
  276. // settings menu
  277. $settings[]=array( "id" => "settings", "order" => 1000, "href" => OC_Helper::linkTo( "settings", "settings.php" ), "name" => $l->t("Settings"), "icon" => OC_Helper::imagePath( "settings", "settings.svg" ));
  278. //SubAdmins are also allowed to access user management
  279. if(OC_SubAdmin::isSubAdmin($_SESSION["user_id"]) || OC_Group::inGroup( $_SESSION["user_id"], "admin" )) {
  280. // admin users menu
  281. $settings[] = array( "id" => "core_users", "order" => 2, "href" => OC_Helper::linkTo( "settings", "users.php" ), "name" => $l->t("Users"), "icon" => OC_Helper::imagePath( "settings", "users.svg" ));
  282. }
  283. // if the user is an admin
  284. if(OC_Group::inGroup( $_SESSION["user_id"], "admin" )) {
  285. // admin apps menu
  286. $settings[] = array( "id" => "core_apps", "order" => 3, "href" => OC_Helper::linkTo( "settings", "apps.php" ).'?installed', "name" => $l->t("Apps"), "icon" => OC_Helper::imagePath( "settings", "apps.svg" ));
  287. $settings[]=array( "id" => "admin", "order" => 1000, "href" => OC_Helper::linkTo( "settings", "admin.php" ), "name" => $l->t("Admin"), "icon" => OC_Helper::imagePath( "settings", "admin.svg" ));
  288. }
  289. }
  290. $navigation = self::proceedNavigation($settings);
  291. return $navigation;
  292. }
  293. /// This is private as well. It simply works, so don't ask for more details
  294. private static function proceedNavigation( $list ){
  295. foreach( $list as &$naventry ){
  296. $naventry['subnavigation'] = array();
  297. if( $naventry['id'] == self::$activeapp ) {
  298. $naventry['active'] = true;
  299. }
  300. else{
  301. $naventry['active'] = false;
  302. }
  303. } unset( $naventry );
  304. usort( $list, create_function( '$a, $b', 'if( $a["order"] == $b["order"] ){return 0;}elseif( $a["order"] < $b["order"] ){return -1;}else{return 1;}' ));
  305. return $list;
  306. }
  307. /**
  308. * Get the path where to install apps
  309. */
  310. public static function getInstallPath() {
  311. if(OC_Config::getValue('appstoreenabled', true)==false) {
  312. return false;
  313. }
  314. foreach(OC::$APPSROOTS as $dir) {
  315. if(isset($dir['writable']) && $dir['writable']===true)
  316. return $dir['path'];
  317. }
  318. OC_Log::write('core', 'No application directories are marked as writable.', OC_Log::ERROR);
  319. return null;
  320. }
  321. protected static function findAppInDirectories($appid) {
  322. static $app_dir = array();
  323. if (isset($app_dir[$appid])) {
  324. return $app_dir[$appid];
  325. }
  326. foreach(OC::$APPSROOTS as $dir) {
  327. if(file_exists($dir['path'].'/'.$appid)) {
  328. return $app_dir[$appid]=$dir;
  329. }
  330. }
  331. }
  332. /**
  333. * Get the directory for the given app.
  334. * If the app is defined in multiple directory, the first one is taken. (false if not found)
  335. */
  336. public static function getAppPath($appid) {
  337. if( ($dir = self::findAppInDirectories($appid)) != false) {
  338. return $dir['path'].'/'.$appid;
  339. }
  340. }
  341. /**
  342. * Get the path for the given app on the access
  343. * If the app is defined in multiple directory, the first one is taken. (false if not found)
  344. */
  345. public static function getAppWebPath($appid) {
  346. if( ($dir = self::findAppInDirectories($appid)) != false) {
  347. return OC::$WEBROOT.$dir['url'].'/'.$appid;
  348. }
  349. }
  350. /**
  351. * get the last version of the app, either from appinfo/version or from appinfo/info.xml
  352. */
  353. public static function getAppVersion($appid){
  354. $file= self::getAppPath($appid).'/appinfo/version';
  355. $version=@file_get_contents($file);
  356. if($version) {
  357. return trim($version);
  358. }else{
  359. $appData=self::getAppInfo($appid);
  360. return isset($appData['version'])? $appData['version'] : '';
  361. }
  362. }
  363. /**
  364. * @brief Read app metadata from the info.xml file
  365. * @param string $appid id of the app or the path of the info.xml file
  366. * @param boolean path (optional)
  367. * @returns array
  368. */
  369. public static function getAppInfo($appid,$path=false){
  370. if($path) {
  371. $file=$appid;
  372. }else{
  373. if(isset(self::$appInfo[$appid])) {
  374. return self::$appInfo[$appid];
  375. }
  376. $file= self::getAppPath($appid).'/appinfo/info.xml';
  377. }
  378. $data=array();
  379. $content=@file_get_contents($file);
  380. if(!$content) {
  381. return;
  382. }
  383. $xml = new SimpleXMLElement($content);
  384. $data['info']=array();
  385. $data['remote']=array();
  386. $data['public']=array();
  387. foreach($xml->children() as $child){
  388. if($child->getName()=='remote') {
  389. foreach($child->children() as $remote){
  390. $data['remote'][$remote->getName()]=(string)$remote;
  391. }
  392. }elseif($child->getName()=='public') {
  393. foreach($child->children() as $public){
  394. $data['public'][$public->getName()]=(string)$public;
  395. }
  396. }elseif($child->getName()=='types') {
  397. $data['types']=array();
  398. foreach($child->children() as $type){
  399. $data['types'][]=$type->getName();
  400. }
  401. }elseif($child->getName()=='description') {
  402. $xml=(string)$child->asXML();
  403. $data[$child->getName()]=substr($xml, 13, -14);//script <description> tags
  404. }else{
  405. $data[$child->getName()]=(string)$child;
  406. }
  407. }
  408. self::$appInfo[$appid]=$data;
  409. return $data;
  410. }
  411. /**
  412. * @brief Returns the navigation
  413. * @returns associative array
  414. *
  415. * This function returns an array containing all entries added. The
  416. * entries are sorted by the key 'order' ascending. Additional to the keys
  417. * given for each app the following keys exist:
  418. * - active: boolean, signals if the user is on this navigation entry
  419. * - children: array that is empty if the key 'active' is false or
  420. * contains the subentries if the key 'active' is true
  421. */
  422. public static function getNavigation(){
  423. $navigation = self::proceedNavigation( self::$navigation );
  424. return $navigation;
  425. }
  426. /**
  427. * get the id of loaded app
  428. * @return string
  429. */
  430. public static function getCurrentApp(){
  431. $script=substr($_SERVER["SCRIPT_NAME"], strlen(OC::$WEBROOT)+1);
  432. $topFolder=substr($script, 0, strpos($script, '/'));
  433. if($topFolder=='apps') {
  434. $length=strlen($topFolder);
  435. return substr($script, $length+1, strpos($script, '/', $length+1)-$length-1);
  436. }else{
  437. return $topFolder;
  438. }
  439. }
  440. /**
  441. * get the forms for either settings, admin or personal
  442. */
  443. public static function getForms($type){
  444. $forms=array();
  445. switch($type){
  446. case 'settings':
  447. $source=self::$settingsForms;
  448. break;
  449. case 'admin':
  450. $source=self::$adminForms;
  451. break;
  452. case 'personal':
  453. $source=self::$personalForms;
  454. break;
  455. }
  456. foreach($source as $form){
  457. $forms[]=include $form;
  458. }
  459. return $forms;
  460. }
  461. /**
  462. * register a settings form to be shown
  463. */
  464. public static function registerSettings($app,$page){
  465. self::$settingsForms[]= $app.'/'.$page.'.php';
  466. }
  467. /**
  468. * register an admin form to be shown
  469. */
  470. public static function registerAdmin($app,$page){
  471. self::$adminForms[]= $app.'/'.$page.'.php';
  472. }
  473. /**
  474. * register a personal form to be shown
  475. */
  476. public static function registerPersonal($app,$page){
  477. self::$personalForms[]= $app.'/'.$page.'.php';
  478. }
  479. /**
  480. * get a list of all apps in the apps folder
  481. */
  482. public static function getAllApps(){
  483. $apps=array();
  484. foreach(OC::$APPSROOTS as $apps_dir) {
  485. $dh=opendir($apps_dir['path']);
  486. while($file=readdir($dh)){
  487. if($file[0]!='.' and is_file($apps_dir['path'].'/'.$file.'/appinfo/app.php')) {
  488. $apps[]=$file;
  489. }
  490. }
  491. }
  492. return $apps;
  493. }
  494. /**
  495. * check if the app need updating and update when needed
  496. */
  497. public static function checkUpgrade($app) {
  498. if (in_array($app, self::$checkedApps)) {
  499. return;
  500. }
  501. self::$checkedApps[] = $app;
  502. $versions = self::getAppVersions();
  503. $currentVersion=OC_App::getAppVersion($app);
  504. if ($currentVersion) {
  505. $installedVersion = $versions[$app];
  506. if (version_compare($currentVersion, $installedVersion, '>')) {
  507. OC_Log::write($app, 'starting app upgrade from '.$installedVersion.' to '.$currentVersion, OC_Log::DEBUG);
  508. OC_App::updateApp($app);
  509. OC_Appconfig::setValue($app, 'installed_version', OC_App::getAppVersion($app));
  510. }
  511. }
  512. }
  513. /**
  514. * check if the current enabled apps are compatible with the current
  515. * ownCloud version. disable them if not.
  516. * This is important if you upgrade ownCloud and have non ported 3rd
  517. * party apps installed.
  518. */
  519. public static function checkAppsRequirements($apps = array()){
  520. if (empty($apps)) {
  521. $apps = OC_App::getEnabledApps();
  522. }
  523. $version = OC_Util::getVersion();
  524. foreach($apps as $app) {
  525. // check if the app is compatible with this version of ownCloud
  526. $info = OC_App::getAppInfo($app);
  527. if(!isset($info['require']) or ($version[0]>$info['require'])) {
  528. OC_Log::write('core', 'App "'.$info['name'].'" ('.$app.') can\'t be used because it is not compatible with this version of ownCloud', OC_Log::ERROR);
  529. OC_App::disable( $app );
  530. }
  531. }
  532. }
  533. /**
  534. * get the installed version of all apps
  535. */
  536. public static function getAppVersions(){
  537. static $versions;
  538. if (isset($versions)) { // simple cache, needs to be fixed
  539. return $versions; // when function is used besides in checkUpgrade
  540. }
  541. $versions=array();
  542. $query = OC_DB::prepare( 'SELECT `appid`, `configvalue` FROM `*PREFIX*appconfig` WHERE `configkey` = \'installed_version\'' );
  543. $result = $query->execute();
  544. while($row = $result->fetchRow()){
  545. $versions[$row['appid']]=$row['configvalue'];
  546. }
  547. return $versions;
  548. }
  549. /**
  550. * update the database for the app and call the update script
  551. * @param string appid
  552. */
  553. public static function updateApp($appid){
  554. if(file_exists(self::getAppPath($appid).'/appinfo/database.xml')) {
  555. OC_DB::updateDbFromStructure(self::getAppPath($appid).'/appinfo/database.xml');
  556. }
  557. if(!self::isEnabled($appid)) {
  558. return;
  559. }
  560. if(file_exists(self::getAppPath($appid).'/appinfo/update.php')) {
  561. self::loadApp($appid);
  562. include self::getAppPath($appid).'/appinfo/update.php';
  563. }
  564. //set remote/public handelers
  565. $appData=self::getAppInfo($appid);
  566. foreach($appData['remote'] as $name=>$path){
  567. OCP\CONFIG::setAppValue('core', 'remote_'.$name, $appid.'/'.$path);
  568. }
  569. foreach($appData['public'] as $name=>$path){
  570. OCP\CONFIG::setAppValue('core', 'public_'.$name, $appid.'/'.$path);
  571. }
  572. self::setAppTypes($appid);
  573. }
  574. /**
  575. * @param string appid
  576. * @return OC_FilesystemView
  577. */
  578. public static function getStorage($appid){
  579. if(OC_App::isEnabled($appid)) {//sanity check
  580. if(OC_User::isLoggedIn()) {
  581. $view = new OC_FilesystemView('/'.OC_User::getUser());
  582. if(!$view->file_exists($appid)) {
  583. $view->mkdir($appid);
  584. }
  585. return new OC_FilesystemView('/'.OC_User::getUser().'/'.$appid);
  586. }else{
  587. OC_Log::write('core', 'Can\'t get app storage, app, user not logged in', OC_Log::ERROR);
  588. return false;
  589. }
  590. }else{
  591. OC_Log::write('core', 'Can\'t get app storage, app '.$appid.' not enabled', OC_Log::ERROR);
  592. false;
  593. }
  594. }
  595. }