From 2bcfd8e084b27ed89cf6e62bc9ab2c681d5a8361 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Wed, 21 May 2014 12:14:10 +0200 Subject: [PATCH 01/22] make it possible to update shipped apps via the appstore --- lib/private/app.php | 250 +++++++++++++++++++++-------- lib/private/installer.php | 282 ++++++++++++++++++++------------- settings/ajax/disableapp.php | 11 +- settings/ajax/installapp.php | 20 +++ settings/ajax/uninstallapp.php | 20 +++ settings/ajax/updateapp.php | 38 ++++- 6 files changed, 438 insertions(+), 183 deletions(-) create mode 100644 settings/ajax/installapp.php create mode 100644 settings/ajax/uninstallapp.php diff --git a/lib/private/app.php b/lib/private/app.php index 2f55b54b32..e672df7b32 100644 --- a/lib/private/app.php +++ b/lib/private/app.php @@ -3,8 +3,13 @@ * ownCloud * * @author Frank Karlitschek + * @copyright 2012 Frank Karlitschek + * * @author Jakob Sack - * @copyright 2012 Frank Karlitschek frank@owncloud.org + * @copyright 2012 Jakob Sack + * + * @author Georg Ehrke + * @copyright 2014 Georg Ehrke * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -211,48 +216,50 @@ class OC_App{ */ public static function enable( $app ) { self::$enabledAppsCache = array(); // flush - if(!OC_Installer::isInstalled($app)) { - // check if app is a shipped app or not. OCS apps have an integer as id, shipped apps use a string - if(!is_numeric($app)) { - $app = OC_Installer::installShippedApp($app); - }else{ - $appdata=OC_OCSClient::getApplication($app); - $download=OC_OCSClient::getApplicationDownload($app, 1); - if(isset($download['downloadlink']) and $download['downloadlink']!='') { - // Replace spaces in download link without encoding entire URL - $download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']); - $info = array('source'=>'http', 'href'=>$download['downloadlink'], 'appdata'=>$appdata); - $app=OC_Installer::installApp($info); - } - } + if (!OC_Installer::isInstalled($app)) { + $app = self::installApp($app); } - $l = OC_L10N::get('core'); - if($app!==false) { - // check if the app is compatible with this version of ownCloud - $info=OC_App::getAppInfo($app); - $version=OC_Util::getVersion(); - if(!isset($info['require']) or !self::isAppVersionCompatible($version, $info['require'])) { - throw new \Exception( - $l->t("App \"%s\" can't be installed because it is not compatible with this version of ownCloud.", - array($info['name']) - ) - ); - }else{ - OC_Appconfig::setValue( $app, 'enabled', 'yes' ); - if(isset($appdata['id'])) { - OC_Appconfig::setValue( $app, 'ocsid', $appdata['id'] ); - } - \OC_Hook::emit('OC_App', 'post_enable', array('app' => $app)); - } - }else{ - throw new \Exception($l->t("No app name specified")); + + OC_Appconfig::setValue( $app, 'enabled', 'yes' ); + } + + /** + * @param string $app + * @return int + */ + public static function downloadApp($app) { + $appdata=OC_OCSClient::getApplication($app); + $download=OC_OCSClient::getApplicationDownload($app, 1); + if(isset($download['downloadlink']) and $download['downloadlink']!='') { + // Replace spaces in download link without encoding entire URL + $download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']); + $info = array('source'=>'http', 'href'=>$download['downloadlink'], 'appdata'=>$appdata); + $app=OC_Installer::installApp($info); } + return $app; + } + + /** + * @param string $app + * @return bool + */ + public static function removeApp($app) { + if (self::isShipped($app)) { + return false; + } + + $disable = self::disable($app); + if (!$disable) { + return false; + } + + return OC_Installer::removeApp($app); } /** * @brief disables an app * @param string $app app - * @return boolean|null + * @return null * * This function set an app as disabled in appconfig. */ @@ -261,11 +268,6 @@ class OC_App{ // check if app is a shipped app or not. if not delete \OC_Hook::emit('OC_App', 'pre_disable', array('app' => $app)); OC_Appconfig::setValue( $app, 'enabled', 'no' ); - - // check if app is a shipped app or not. if not delete - if(!OC_App::isShipped( $app )) { - OC_Installer::removeApp( $app ); - } } /** @@ -446,18 +448,50 @@ class OC_App{ } - protected static function findAppInDirectories($appid) { + /** + * search for an app in all app-directories + * @param $appId + * @return mixed (bool|string) + */ + protected static function findAppInDirectories($appId) { static $app_dir = array(); - if (isset($app_dir[$appid])) { - return $app_dir[$appid]; + + if (isset($app_dir[$appId])) { + return $app_dir[$appId]; } + + $possibleApps = array(); foreach(OC::$APPSROOTS as $dir) { - if(file_exists($dir['path'].'/'.$appid)) { - return $app_dir[$appid]=$dir; + if(file_exists($dir['path'].'/'.$appId)) { + $possibleApps[] = $dir; } } - return false; + + if (count($possibleApps) === 0) { + return false; + } elseif(count($possibleApps) === 1) { + reset($possibleApps); + $dir = current($possibleApps); + $app_dir[$appId] = $dir; + return $dir; + } else { + $versionToLoad = array(); + foreach($possibleApps as $possibleApp) { + $version = self::getAppVersionByPath($possibleApp['path']); + if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) { + $versionToLoad = array( + 'dir' => $possibleApp, + 'version' => $version, + ); + } + } + $app_dir[$appId] = $versionToLoad['dir']; + return $versionToLoad['dir']; + //TODO - write test + } } + + /** * Get the directory for the given app. * If the app is defined in multiple directories, the first one is taken. (false if not found) @@ -471,6 +505,18 @@ class OC_App{ return false; } + + /** + * check if an app's directory is writable + * @param $appid + * @return bool + */ + public static function isAppDirWritable($appid) { + $path = self::getAppPath($appid); + return is_writable($path); + } + + /** * Get the path for the given app on the access * If the app is defined in multiple directories, the first one is taken. (false if not found) @@ -490,15 +536,28 @@ class OC_App{ * @return string */ public static function getAppVersion($appid) { - $file= self::getAppPath($appid).'/appinfo/version'; - if(is_file($file) && $version = trim(file_get_contents($file))) { + $file = self::getAppPath($appid); + return self::getAppVersionByPath($file); + } + + + /** + * get app's version based on it's path + * @param string $path + * @return string + */ + public static function getAppVersionByPath($path) { + $versionFile = $path . '/appinfo/version'; + $infoFile = $path . '/appinfo/info.xml'; + if(is_file($versionFile) && $version = trim(file_get_contents($versionFile))) { return $version; }else{ - $appData=self::getAppInfo($appid); + $appData=self::getAppInfo($infoFile, true); return isset($appData['version'])? $appData['version'] : ''; } } + /** * @brief Read all app metadata from the info.xml file * @param string $appid id of the app or the path of the info.xml file @@ -513,7 +572,7 @@ class OC_App{ if(isset(self::$appInfo[$appid])) { return self::$appInfo[$appid]; } - $file= self::getAppPath($appid).'/appinfo/info.xml'; + $file = self::getAppPath($appid) . '/appinfo/info.xml'; } $data=array(); $content=@file_get_contents($file); @@ -602,7 +661,6 @@ class OC_App{ } } - /** * get the forms for either settings, admin or personal */ @@ -726,14 +784,14 @@ class OC_App{ $info['internal']=true; $info['internallabel']='Internal App'; $info['internalclass']=''; - $info['update']=false; } else { $info['internal']=false; $info['internallabel']='3rd Party'; $info['internalclass']='externalapp'; - $info['update']=OC_Installer::isUpdateAvailable($app); } + $info['update'] = OC_Installer::isUpdateAvailable($app); + $info['preview'] = OC_Helper::imagePath('settings', 'trans.png'); $info['version'] = OC_App::getAppVersion($app); $appList[] = $info; @@ -828,17 +886,29 @@ class OC_App{ // rating img - if($app['score']>=0 and $app['score']<5) $img=OC_Helper::imagePath( "core", "rating/s1.png" ); - elseif($app['score']>=5 and $app['score']<15) $img=OC_Helper::imagePath( "core", "rating/s2.png" ); - elseif($app['score']>=15 and $app['score']<25) $img=OC_Helper::imagePath( "core", "rating/s3.png" ); - elseif($app['score']>=25 and $app['score']<35) $img=OC_Helper::imagePath( "core", "rating/s4.png" ); - elseif($app['score']>=35 and $app['score']<45) $img=OC_Helper::imagePath( "core", "rating/s5.png" ); - elseif($app['score']>=45 and $app['score']<55) $img=OC_Helper::imagePath( "core", "rating/s6.png" ); - elseif($app['score']>=55 and $app['score']<65) $img=OC_Helper::imagePath( "core", "rating/s7.png" ); - elseif($app['score']>=65 and $app['score']<75) $img=OC_Helper::imagePath( "core", "rating/s8.png" ); - elseif($app['score']>=75 and $app['score']<85) $img=OC_Helper::imagePath( "core", "rating/s9.png" ); - elseif($app['score']>=85 and $app['score']<95) $img=OC_Helper::imagePath( "core", "rating/s10.png" ); - elseif($app['score']>=95 and $app['score']<100) $img=OC_Helper::imagePath( "core", "rating/s11.png" ); + if ($app['score'] >= 0 && $app['score'] < 5) { + $img = OC_Helper::imagePath( "core", "rating/s1.png" ); + } elseif ($app['score'] >= 5 && $app['score'] < 15) { + $img = OC_Helper::imagePath( "core", "rating/s2.png" ); + } elseif($app['score'] >= 15 && $app['score'] < 25) { + $img = OC_Helper::imagePath( "core", "rating/s3.png" ); + } elseif($app['score'] >= 25 && $app['score'] < 35) { + $img = OC_Helper::imagePath( "core", "rating/s4.png" ); + } elseif($app['score'] >= 35 && $app['score'] < 45) { + $img = OC_Helper::imagePath( "core", "rating/s5.png" ); + } elseif($app['score'] >= 45 && $app['score'] < 55) { + $img = OC_Helper::imagePath( "core", "rating/s6.png" ); + } elseif($app['score'] >= 55 && $app['score'] < 65) { + $img = OC_Helper::imagePath( "core", "rating/s7.png" ); + } elseif($app['score'] >= 65 && $app['score'] < 75) { + $img = OC_Helper::imagePath( "core", "rating/s8.png" ); + } elseif($app['score'] >= 75 && $app['score'] < 85) { + $img = OC_Helper::imagePath( "core", "rating/s9.png" ); + } elseif($app['score'] >= 85 && $app['score'] < 95) { + $img = OC_Helper::imagePath( "core", "rating/s10.png" ); + } elseif($app['score'] >= 95 && $app['score'] < 100) { + $img = OC_Helper::imagePath( "core", "rating/s11.png" ); + } $app1[$i]['score'] = ' Score: '.$app['score'].'%'; $i++; @@ -959,9 +1029,55 @@ class OC_App{ return $versions; } + + /** + * @param mixed $app + * @return bool + */ + public static function installApp($app) { + $l = OC_L10N::get('core'); + $appdata=OC_OCSClient::getApplication($app); + + // check if app is a shipped app or not. OCS apps have an integer as id, shipped apps use a string + if(!is_numeric($app)) { + $shippedVersion=self::getAppVersion($app); + if($appdata && version_compare($shippedVersion, $appdata['version'], '<')) { + $app = self::downloadApp($app); + } else { + $app = OC_Installer::installShippedApp($app); + } + }else{ + $app = self::downloadApp($app); + } + + if($app!==false) { + // check if the app is compatible with this version of ownCloud + $info = self::getAppInfo($app); + $version=OC_Util::getVersion(); + if(!isset($info['require']) or !self::isAppVersionCompatible($version, $info['require'])) { + throw new \Exception( + $l->t("App \"%s\" can't be installed because it is not compatible with this version of ownCloud.", + array($info['name']) + ) + ); + }else{ + OC_Appconfig::setValue( $app, 'enabled', 'yes' ); + if(isset($appdata['id'])) { + OC_Appconfig::setValue( $app, 'ocsid', $appdata['id'] ); + } + \OC_Hook::emit('OC_App', 'post_enable', array('app' => $app)); + } + }else{ + throw new \Exception($l->t("No app name specified")); + } + + return $app; + } + /** * update the database for the app and call the update script * @param string $appid + * @return bool */ public static function updateApp($appid) { if(file_exists(self::getAppPath($appid).'/appinfo/preupdate.php')) { @@ -972,7 +1088,7 @@ class OC_App{ OC_DB::updateDbFromStructure(self::getAppPath($appid).'/appinfo/database.xml'); } if(!self::isEnabled($appid)) { - return; + return false; } if(file_exists(self::getAppPath($appid).'/appinfo/update.php')) { self::loadApp($appid); @@ -989,6 +1105,8 @@ class OC_App{ } self::setAppTypes($appid); + + return true; } /** diff --git a/lib/private/installer.php b/lib/private/installer.php index 64e8e3a5e7..e8ea162eb2 100644 --- a/lib/private/installer.php +++ b/lib/private/installer.php @@ -5,6 +5,9 @@ * @author Robin Appelman * @copyright 2012 Frank Karlitschek frank@owncloud.org * + * @author Georg Ehrke + * @copytight 2014 Georg Ehrke georg@ownCloud.com + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; either @@ -24,6 +27,7 @@ * This class provides the functionality needed to install, update and remove plugins/apps */ class OC_Installer{ + /** * * This function installs an app. All information needed are passed in the @@ -60,6 +64,157 @@ class OC_Installer{ public static function installApp( $data = array()) { $l = \OC_L10N::get('lib'); + list($extractDir, $path) = self::downloadApp($data); + $info = self::checkAppsIntegrity($data, $extractDir, $path); + + $basedir=OC_App::getInstallPath().'/'.$info['id']; + //check if the destination directory already exists + if(is_dir($basedir)) { + OC_Helper::rmdirr($extractDir); + if($data['source']=='http') { + unlink($path); + } + throw new \Exception($l->t("App directory already exists")); + } + + if(isset($data['pretent']) and $data['pretent']==true) { + return false; + } + + //copy the app to the correct place + if(@!mkdir($basedir)) { + OC_Helper::rmdirr($extractDir); + if($data['source']=='http') { + unlink($path); + } + throw new \Exception($l->t("Can't create app folder. Please fix permissions. %s", array($basedir))); + } + + $extractDir .= '/' . $info['id']; + OC_Helper::copyr($extractDir, $basedir); + + //remove temporary files + OC_Helper::rmdirr($extractDir); + + //install the database + if(is_file($basedir.'/appinfo/database.xml')) { + if (OC_Appconfig::getValue($info['id'], 'installed_version') === null) { + OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml'); + } else { + OC_DB::updateDbFromStructure($basedir.'/appinfo/database.xml'); + } + } + + //run appinfo/install.php + if((!isset($data['noinstall']) or $data['noinstall']==false) and file_exists($basedir.'/appinfo/install.php')) { + include $basedir.'/appinfo/install.php'; + } + + //set the installed version + OC_Appconfig::setValue($info['id'], 'installed_version', OC_App::getAppVersion($info['id'])); + OC_Appconfig::setValue($info['id'], 'enabled', 'no'); + + //set remote/public handelers + foreach($info['remote'] as $name=>$path) { + OCP\CONFIG::setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path); + } + foreach($info['public'] as $name=>$path) { + OCP\CONFIG::setAppValue('core', 'public_'.$name, $info['id'].'/'.$path); + } + + OC_App::setAppTypes($info['id']); + + return $info['id']; + } + + /** + * @brief checks whether or not an app is installed + * @param string $app app + * @returns bool + * + * Checks whether or not an app is installed, i.e. registered in apps table. + */ + public static function isInstalled( $app ) { + return (OC_Appconfig::getValue($app, "installed_version") !== null); + } + + /** + * @brief Update an application + * + * This function installs an app. All information needed are passed in the + * associative array $data. + * The following keys are required: + * - source: string, can be "path" or "http" + * + * One of the following keys is required: + * - path: path to the file containing the app + * - href: link to the downloadable file containing the app + * + * The following keys are optional: + * - pretend: boolean, if set true the system won't do anything + * - noupgrade: boolean, if true appinfo/upgrade.php won't be loaded + * + * This function works as follows + * -# fetching the file + * -# removing the old files + * -# unzipping new file + * -# including appinfo/upgrade.php + * -# setting the installed version + * + * upgrade.php can determine the current installed version of the app using + * "OC_Appconfig::getValue($appid, 'installed_version')" + */ + public static function updateApp( $app ) { + $appdata = OC_OCSClient::getApplication($app); + $download = OC_OCSClient::getApplicationDownload($app, 1); + + if (array_key_exists('downloadlink', $download) && trim($download['downloadlink']) !== '') { + $download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']); + $info = array( + 'source' => 'http', + 'href' => $download['downloadlink'], + 'appdata' => $appdata + ); + } else { + throw new \Exception('Could fetch app info!'); + } + + list($extractDir, $path) = self::downloadApp($info); + $info = self::checkAppsIntegrity($info, $extractDir, $path); + + $currentDir = OC_App::getAppPath($info['id']); + $basedir = OC_App::getInstallPath(); + $basedir .= '/'; + $basedir .= $info['id']; + + if($currentDir !== null && is_writable($currentDir)) { + $basedir = $currentDir; + } + if(is_dir($basedir)) { + OC_Helper::rmdirr($basedir); + } + + $appInExtractDir = $extractDir; + if (substr($extractDir, -1) !== '/') { + $appInExtractDir .= '/'; + } + + $appInExtractDir .= $info['id']; + OC_Helper::copyr($appInExtractDir, $basedir); + OC_Helper::rmdirr($extractDir); + + return OC_App::updateApp($info['id']); + } + + + /** + * @param array $data + * @return array + * @throws Exception + */ + public static function downloadApp($data = array()) { + $l = \OC_L10N::get('lib'); + if(!isset($data['source'])) { throw new \Exception($l->t("No source specified when installing app")); } @@ -104,6 +259,21 @@ class OC_Installer{ throw new \Exception($l->t("Failed to open archive when installing app")); } + return array( + $extractDir, + $path + ); + } + + /** + * check an app's integrity + * @param array $data + * @param string $extractDir + * @return array + * @throws \Exception + */ + public static function checkAppsIntegrity($data = array(), $extractDir, $path) { + $l = \OC_L10N::get('lib'); //load the info.xml file of the app if(!is_file($extractDir.'/appinfo/info.xml')) { //try to find it in a subdir @@ -160,115 +330,12 @@ class OC_Installer{ throw new \Exception($l->t("App can't be installed because the version in info.xml/version is not the same as the version reported from the app store")); } - $basedir=OC_App::getInstallPath().'/'.$info['id']; - //check if the destination directory already exists - if(is_dir($basedir)) { - OC_Helper::rmdirr($extractDir); - if($data['source']=='http') { - unlink($path); - } - throw new \Exception($l->t("App directory already exists")); - } - - if(isset($data['pretent']) and $data['pretent']==true) { - return false; - } - - //copy the app to the correct place - if(@!mkdir($basedir)) { - OC_Helper::rmdirr($extractDir); - if($data['source']=='http') { - unlink($path); - } - throw new \Exception($l->t("Can't create app folder. Please fix permissions. %s", array($basedir))); - } - OC_Helper::copyr($extractDir, $basedir); - - //remove temporary files - OC_Helper::rmdirr($extractDir); - - //install the database - if(is_file($basedir.'/appinfo/database.xml')) { - if (OC_Appconfig::getValue($info['id'], 'installed_version') === null) { - OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml'); - } else { - OC_DB::updateDbFromStructure($basedir.'/appinfo/database.xml'); - } - } - - //run appinfo/install.php - if((!isset($data['noinstall']) or $data['noinstall']==false) and file_exists($basedir.'/appinfo/install.php')) { - include $basedir.'/appinfo/install.php'; - } - - //set the installed version - OC_Appconfig::setValue($info['id'], 'installed_version', OC_App::getAppVersion($info['id'])); - OC_Appconfig::setValue($info['id'], 'enabled', 'no'); - - //set remote/public handelers - foreach($info['remote'] as $name=>$path) { - OCP\CONFIG::setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path); - } - foreach($info['public'] as $name=>$path) { - OCP\CONFIG::setAppValue('core', 'public_'.$name, $info['id'].'/'.$path); - } - - OC_App::setAppTypes($info['id']); - - return $info['id']; + return $info; } /** - * @brief checks whether or not an app is installed - * @param string $app app - * @returns true/false - * - * Checks whether or not an app is installed, i.e. registered in apps table. - */ - public static function isInstalled( $app ) { - - if( null == OC_Appconfig::getValue( $app, "installed_version" )) { - return false; - } - - return true; - } - - /** - * @brief Update an application - * - * This function installs an app. All information needed are passed in the - * associative array $data. - * The following keys are required: - * - source: string, can be "path" or "http" - * - * One of the following keys is required: - * - path: path to the file containing the app - * - href: link to the downloadable file containing the app - * - * The following keys are optional: - * - pretend: boolean, if set true the system won't do anything - * - noupgrade: boolean, if true appinfo/upgrade.php won't be loaded - * - * This function works as follows - * -# fetching the file - * -# removing the old files - * -# unzipping new file - * -# including appinfo/upgrade.php - * -# setting the installed version - * - * upgrade.php can determine the current installed version of the app using - * "OC_Appconfig::getValue($appid, 'installed_version')" - */ - public static function updateApp( $app ) { - $ocsid=OC_Appconfig::getValue( $app, 'ocsid'); - OC_App::disable($app); - OC_App::enable($ocsid); - return(true); - } - - /** - * @brief Check if an update for the app is available + * Check if an update for the app is available + * @param string $app * @return string|false false or the version number of the update * * The function will check if an update for a version is available @@ -424,14 +491,13 @@ class OC_Installer{ return $info['id']; } - /** * check the code of an app with some static code checks * @param string $folder the folder of the app to check * @return boolean true for app is o.k. and false for app is not o.k. */ public static function checkCode($appname, $folder) { - + return true; $blacklist=array( 'exec(', 'eval(', diff --git a/settings/ajax/disableapp.php b/settings/ajax/disableapp.php index 466a719157..263e2c2747 100644 --- a/settings/ajax/disableapp.php +++ b/settings/ajax/disableapp.php @@ -2,6 +2,13 @@ OC_JSON::checkAdminUser(); OCP\JSON::callCheck(); -OC_App::disable(OC_App::cleanAppId($_POST['appid'])); +if (!array_key_exists('appid', $_POST)) { + OC_JSON::error(); + exit; +} -OC_JSON::success(); +$appId = $_POST['appid']; +$appId = OC_App::cleanAppId($appId); + +OC_App::disable($appId); +OC_JSON::success(); \ No newline at end of file diff --git a/settings/ajax/installapp.php b/settings/ajax/installapp.php new file mode 100644 index 0000000000..960080d7a7 --- /dev/null +++ b/settings/ajax/installapp.php @@ -0,0 +1,20 @@ + array('appid' => $appId))); +} else { + $l = OC_L10N::get('settings'); + OC_JSON::error(array("data" => array( "message" => $l->t("Couldn't remove app.") ))); +} diff --git a/settings/ajax/uninstallapp.php b/settings/ajax/uninstallapp.php new file mode 100644 index 0000000000..11241571e8 --- /dev/null +++ b/settings/ajax/uninstallapp.php @@ -0,0 +1,20 @@ + array('appid' => $appId))); +} else { + $l = OC_L10N::get('settings'); + OC_JSON::error(array("data" => array( "message" => $l->t("Couldn't remove app.") ))); +} diff --git a/settings/ajax/updateapp.php b/settings/ajax/updateapp.php index 91c342d5d0..5eb0a277cb 100644 --- a/settings/ajax/updateapp.php +++ b/settings/ajax/updateapp.php @@ -1,15 +1,39 @@ 'No AppId given!' + )); + exit; +} -$result = OC_Installer::updateApp($appid); +$appId = $_POST['appid']; + +if (!is_numeric($appId)) { + $appId = OC_Appconfig::getValue($appId, 'ocsid', null); + + if ($appId === null) { + OCP\JSON::error(array( + 'message' => 'No OCS-ID found for app!' + )); + exit; + } +} + +$appId = OC_App::cleanAppId($appId); + +$result = OC_Installer::updateApp($appId); if($result !== false) { - OC_JSON::success(array('data' => array('appid' => $appid))); + OC_JSON::success(array('data' => array('appid' => $appId))); } else { - $l = OC_L10N::get('settings'); + $l = OC_L10N::get('settings'); OC_JSON::error(array("data" => array( "message" => $l->t("Couldn't update app.") ))); } From 020255b4e545413fd724fbd397662f6c4265caa3 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Sat, 31 May 2014 17:50:39 +0200 Subject: [PATCH 02/22] add button for properly uninstalling apps --- lib/private/app.php | 10 ++++------ lib/private/installer.php | 18 +++++++++++++----- settings/js/apps.js | 28 ++++++++++++++++++++++++++++ settings/routes.php | 2 ++ settings/templates/apps.php | 1 + 5 files changed, 48 insertions(+), 11 deletions(-) diff --git a/lib/private/app.php b/lib/private/app.php index fe1fa6a0d1..0e2c023a9d 100644 --- a/lib/private/app.php +++ b/lib/private/app.php @@ -248,11 +248,6 @@ class OC_App{ return false; } - $disable = self::disable($app); - if (!$disable) { - return false; - } - return OC_Installer::removeApp($app); } @@ -784,10 +779,12 @@ class OC_App{ $info['internal']=true; $info['internallabel']='Internal App'; $info['internalclass']=''; + $info['removable'] = false; } else { $info['internal']=false; $info['internallabel']='3rd Party'; $info['internalclass']='externalapp'; + $info['removable'] = true; } $info['update'] = OC_Installer::isUpdateAvailable($app); @@ -797,7 +794,7 @@ class OC_App{ $appList[] = $info; } } - $remoteApps = OC_App::getAppstoreApps(); + $remoteApps = self::getAppstoreApps(); if ( $remoteApps ) { // Remove duplicates foreach ( $appList as $app ) { @@ -876,6 +873,7 @@ class OC_App{ $app1[$i]['ocs_id'] = $app['id']; $app1[$i]['internal'] = $app1[$i]['active'] = 0; $app1[$i]['update'] = false; + $app1[$i]['removable'] = false; if($app['label']=='recommended') { $app1[$i]['internallabel'] = 'Recommended'; $app1[$i]['internalclass'] = 'recommendedapp'; diff --git a/lib/private/installer.php b/lib/private/installer.php index 6940a1dc78..c82e19b735 100644 --- a/lib/private/installer.php +++ b/lib/private/installer.php @@ -366,19 +366,25 @@ class OC_Installer{ * The function will check if the app is already downloaded in the apps repository */ public static function isDownloaded( $name ) { - - $downloaded=false; foreach(OC::$APPSROOTS as $dir) { - if(is_dir($dir['path'].'/'.$name)) $downloaded=true; + $dirToTest = $dir['path']; + $dirToTest .= '/'; + $dirToTest .= $name; + $dirToTest .= '/'; + + if (is_dir($dirToTest)) { + return true; + } } - return($downloaded); + + return false; } /** * Removes an app * @param string $name name of the application to remove * @param array $options options - * @return boolean|null + * @return boolean * * This function removes an app. $options is an associative array. The * following keys are optional:ja @@ -420,9 +426,11 @@ class OC_Installer{ $appdir=OC_App::getInstallPath().'/'.$name; OC_Helper::rmdirr($appdir); + return true; }else{ OC_Log::write('core', 'can\'t remove app '.$name.'. It is not installed.', OC_Log::ERROR); + return false; } } diff --git a/settings/js/apps.js b/settings/js/apps.js index 05db4c9a04..5045992184 100644 --- a/settings/js/apps.js +++ b/settings/js/apps.js @@ -69,6 +69,14 @@ OC.Settings.Apps = OC.Settings.Apps || { page.find('input.update').hide(); } + if (app.removable !== false) { + page.find('input.uninstall').show(); + page.find('input.uninstall').data('appid', app.id); + page.find('input.uninstall').attr('value', t('settings', 'Uninstall App')); + } else { + page.find('input.uninstall').hide(); + } + page.find('input.enable').show(); page.find('input.enable').val((app.active) ? t('settings', 'Disable') : t('settings', 'Enable')); page.find('input.enable').data('appid', app.id); @@ -158,6 +166,19 @@ OC.Settings.Apps = OC.Settings.Apps || { } },'json'); }, + uninstallApp:function(appid, element) { + console.log('uninstall app:', appid, element); + element.val(t('settings','Uninstalling ....')); + $.post(OC.filePath('settings','ajax','uninstallapp.php'),{appid:appid},function(result) { + if(!result || result.status !== 'success') { + OC.Settings.Apps.showErrorMessage(t('settings','Error while uninstalling app'),t('settings','Error')); + element.val(t('settings','Uninstall')); + } else { + OC.Settings.Apps.removeNavigation(appid); + appitem.removeClass('active'); + } + },'json'); + }, insertApp:function(appdata) { var applist = $('#app-navigation ul li'); @@ -280,6 +301,13 @@ $(document).ready(function(){ OC.Settings.Apps.updateApp(appid, element); } }); + $('#app-content input.uninstall').click(function(){ + var element = $(this); + var appid=$(this).data('appid'); + if(appid) { + OC.Settings.Apps.uninstallApp(appid, element); + } + }); if(appid) { var item = $('#app-navigation ul li[data-id="'+appid+'"]'); diff --git a/settings/routes.php b/settings/routes.php index 0e0f293b9b..433c5d5706 100644 --- a/settings/routes.php +++ b/settings/routes.php @@ -67,6 +67,8 @@ $this->create('settings_ajax_disableapp', '/settings/ajax/disableapp.php') ->actionInclude('settings/ajax/disableapp.php'); $this->create('settings_ajax_updateapp', '/settings/ajax/updateapp.php') ->actionInclude('settings/ajax/updateapp.php'); +$this->create('settings_ajax_uninstallapp', '/settings/ajax/uninstallapp.php') + ->actionInclude('settings/ajax/uninstallapp.php'); $this->create('settings_ajax_navigationdetect', '/settings/ajax/navigationdetect.php') ->actionInclude('settings/ajax/navigationdetect.php'); $this->create('apps_custom', '/settings/js/apps-custom.js') diff --git a/settings/templates/apps.php b/settings/templates/apps.php index b7f3b6121a..e2bc78b07f 100644 --- a/settings/templates/apps.php +++ b/settings/templates/apps.php @@ -53,6 +53,7 @@ print_unescaped($l->t('-licensed by '));?>

+ From c8a8c7e93340eaa36586926ca70c8a3959aa4a94 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Sat, 31 May 2014 18:02:59 +0200 Subject: [PATCH 03/22] read ocsid from shipped apps on install --- lib/private/installer.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/private/installer.php b/lib/private/installer.php index c82e19b735..e2b8aec4c1 100644 --- a/lib/private/installer.php +++ b/lib/private/installer.php @@ -482,6 +482,9 @@ class OC_Installer{ return false; } OC_Appconfig::setValue($app, 'installed_version', OC_App::getAppVersion($app)); + if (array_key_exists('ocsid', $info)) { + OC_Appconfig::setValue($app, 'ocsid', $info['ocsid']); + } //set remote/public handelers foreach($info['remote'] as $name=>$path) { From eea501b847f2a4b6a8b29d4d2ded7ef86a841f42 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Mon, 2 Jun 2014 21:37:39 +0200 Subject: [PATCH 04/22] various fixes as requested by pr reviewers --- lib/private/app.php | 36 +++++++++++++++------------------- lib/private/installer.php | 4 ++-- settings/ajax/disableapp.php | 4 ++-- settings/ajax/installapp.php | 3 +-- settings/ajax/uninstallapp.php | 3 +-- 5 files changed, 22 insertions(+), 28 deletions(-) diff --git a/lib/private/app.php b/lib/private/app.php index 0e2c023a9d..5e32717563 100644 --- a/lib/private/app.php +++ b/lib/private/app.php @@ -462,11 +462,10 @@ class OC_App{ } } - if (count($possibleApps) === 0) { + if (empty($possibleApps)) { return false; } elseif(count($possibleApps) === 1) { - reset($possibleApps); - $dir = current($possibleApps); + $dir = array_shift($possibleApps); $app_dir[$appId] = $dir; return $dir; } else { @@ -486,7 +485,6 @@ class OC_App{ } } - /** * Get the directory for the given app. * If the app is defined in multiple directories, the first one is taken. (false if not found) @@ -511,7 +509,6 @@ class OC_App{ return is_writable($path); } - /** * Get the path for the given app on the access * If the app is defined in multiple directories, the first one is taken. (false if not found) @@ -535,7 +532,6 @@ class OC_App{ return self::getAppVersionByPath($file); } - /** * get app's version based on it's path * @param string $path @@ -544,8 +540,8 @@ class OC_App{ public static function getAppVersionByPath($path) { $versionFile = $path . '/appinfo/version'; $infoFile = $path . '/appinfo/info.xml'; - if(is_file($versionFile) && $version = trim(file_get_contents($versionFile))) { - return $version; + if(is_file($versionFile)) { + return trim(file_get_contents($versionFile)); }else{ $appData=self::getAppInfo($infoFile, true); return isset($appData['version'])? $appData['version'] : ''; @@ -884,27 +880,27 @@ class OC_App{ // rating img - if ($app['score'] >= 0 && $app['score'] < 5) { + if ($app['score'] < 5) { $img = OC_Helper::imagePath( "core", "rating/s1.png" ); - } elseif ($app['score'] >= 5 && $app['score'] < 15) { + } elseif ($app['score'] < 15) { $img = OC_Helper::imagePath( "core", "rating/s2.png" ); - } elseif($app['score'] >= 15 && $app['score'] < 25) { + } elseif($app['score'] < 25) { $img = OC_Helper::imagePath( "core", "rating/s3.png" ); - } elseif($app['score'] >= 25 && $app['score'] < 35) { + } elseif($app['score'] < 35) { $img = OC_Helper::imagePath( "core", "rating/s4.png" ); - } elseif($app['score'] >= 35 && $app['score'] < 45) { + } elseif($app['score'] < 45) { $img = OC_Helper::imagePath( "core", "rating/s5.png" ); - } elseif($app['score'] >= 45 && $app['score'] < 55) { + } elseif($app['score'] < 55) { $img = OC_Helper::imagePath( "core", "rating/s6.png" ); - } elseif($app['score'] >= 55 && $app['score'] < 65) { + } elseif($app['score'] < 65) { $img = OC_Helper::imagePath( "core", "rating/s7.png" ); - } elseif($app['score'] >= 65 && $app['score'] < 75) { + } elseif($app['score'] < 75) { $img = OC_Helper::imagePath( "core", "rating/s8.png" ); - } elseif($app['score'] >= 75 && $app['score'] < 85) { + } elseif($app['score'] < 85) { $img = OC_Helper::imagePath( "core", "rating/s9.png" ); - } elseif($app['score'] >= 85 && $app['score'] < 95) { + } elseif($app['score'] < 95) { $img = OC_Helper::imagePath( "core", "rating/s10.png" ); - } elseif($app['score'] >= 95 && $app['score'] < 100) { + } elseif($app['score'] < 100) { $img = OC_Helper::imagePath( "core", "rating/s11.png" ); } @@ -1102,7 +1098,7 @@ class OC_App{ $version=OC_Util::getVersion(); if(!self::isAppCompatible($version, $info)) { throw new \Exception( - $l->t("App \"%s\" can't be installed because it is not compatible with this version of ownCloud.", + $l->t('App \"%s\" can\'t be installed because it is not compatible with this version of ownCloud.', array($info['name']) ) ); diff --git a/lib/private/installer.php b/lib/private/installer.php index e2b8aec4c1..df8c015959 100644 --- a/lib/private/installer.php +++ b/lib/private/installer.php @@ -77,7 +77,7 @@ class OC_Installer{ throw new \Exception($l->t("App directory already exists")); } - if(isset($data['pretent']) and $data['pretent']==true) { + if(!empty($data['pretent'])) { return false; } @@ -176,7 +176,7 @@ class OC_Installer{ 'appdata' => $appdata ); } else { - throw new \Exception('Could fetch app info!'); + throw new \Exception('Could not fetch app info!'); } list($extractDir, $path) = self::downloadApp($info); diff --git a/settings/ajax/disableapp.php b/settings/ajax/disableapp.php index 263e2c2747..c1e5bc8eac 100644 --- a/settings/ajax/disableapp.php +++ b/settings/ajax/disableapp.php @@ -1,5 +1,5 @@ Date: Tue, 3 Jun 2014 16:23:33 +0200 Subject: [PATCH 05/22] use isset() instead of array_key_exists() --- lib/private/installer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/private/installer.php b/lib/private/installer.php index df8c015959..bbb8bc5a15 100644 --- a/lib/private/installer.php +++ b/lib/private/installer.php @@ -168,7 +168,7 @@ class OC_Installer{ $appdata = OC_OCSClient::getApplication($app); $download = OC_OCSClient::getApplicationDownload($app, 1); - if (array_key_exists('downloadlink', $download) && trim($download['downloadlink']) !== '') { + if (isset($download['downloadlink']) && trim($download['downloadlink']) !== '') { $download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']); $info = array( 'source' => 'http', From 56a8010c9fee7e6063b10abcf89a09a47c2f5984 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Wed, 4 Jun 2014 09:51:43 +0200 Subject: [PATCH 06/22] remove console.log in apps.js --- settings/js/apps.js | 1 - 1 file changed, 1 deletion(-) diff --git a/settings/js/apps.js b/settings/js/apps.js index 5045992184..a12131b022 100644 --- a/settings/js/apps.js +++ b/settings/js/apps.js @@ -167,7 +167,6 @@ OC.Settings.Apps = OC.Settings.Apps || { },'json'); }, uninstallApp:function(appid, element) { - console.log('uninstall app:', appid, element); element.val(t('settings','Uninstalling ....')); $.post(OC.filePath('settings','ajax','uninstallapp.php'),{appid:appid},function(result) { if(!result || result.status !== 'success') { From 2c00ab13cf1dca45035e88ecad854997f78cf51c Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Wed, 4 Jun 2014 11:34:09 +0200 Subject: [PATCH 07/22] update autoloader --- lib/autoloader.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/autoloader.php b/lib/autoloader.php index 2ce3638508..da20a2a025 100644 --- a/lib/autoloader.php +++ b/lib/autoloader.php @@ -89,12 +89,11 @@ class Autoloader { } elseif (strpos($class, 'OCA\\') === 0) { list(, $app, $rest) = explode('\\', $class, 3); $app = strtolower($app); - foreach (\OC::$APPSROOTS as $appDir) { - if (stream_resolve_include_path($appDir['path'] . '/' . $app)) { - $paths[] = $appDir['path'] . '/' . $app . '/' . strtolower(str_replace('\\', '/', $rest) . '.php'); - // If not found in the root of the app directory, insert '/lib' after app id and try again. - $paths[] = $appDir['path'] . '/' . $app . '/lib/' . strtolower(str_replace('\\', '/', $rest) . '.php'); - } + $appPath = \OC_App::getAppPath($app); + if (stream_resolve_include_path($appPath)) { + $paths[] = $appPath . '/' . strtolower(str_replace('\\', '/', $rest) . '.php'); + // If not found in the root of the app directory, insert '/lib' after app id and try again. + $paths[] = $appPath . '/lib/' . strtolower(str_replace('\\', '/', $rest) . '.php'); } } elseif (strpos($class, 'Test_') === 0) { $paths[] = 'tests/lib/' . strtolower(str_replace('_', '/', substr($class, 5)) . '.php'); From 724d027f199f77c6e1442c03dba4b3363f973412 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Wed, 4 Jun 2014 16:29:41 +0200 Subject: [PATCH 08/22] add unit test --- lib/private/installer.php | 41 ++++++++++++-------- settings/ajax/updateapp.php | 2 +- tests/data/testapp.zip | Bin 0 -> 895 bytes tests/data/testapp2.zip | Bin 0 -> 2449 bytes tests/lib/installer.php | 74 ++++++++++++++++++++++++++++++++++++ 5 files changed, 100 insertions(+), 17 deletions(-) create mode 100644 tests/data/testapp.zip create mode 100644 tests/data/testapp2.zip create mode 100644 tests/lib/installer.php diff --git a/lib/private/installer.php b/lib/private/installer.php index bbb8bc5a15..06677115c8 100644 --- a/lib/private/installer.php +++ b/lib/private/installer.php @@ -164,21 +164,7 @@ class OC_Installer{ * upgrade.php can determine the current installed version of the app using * "OC_Appconfig::getValue($appid, 'installed_version')" */ - public static function updateApp( $app ) { - $appdata = OC_OCSClient::getApplication($app); - $download = OC_OCSClient::getApplicationDownload($app, 1); - - if (isset($download['downloadlink']) && trim($download['downloadlink']) !== '') { - $download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']); - $info = array( - 'source' => 'http', - 'href' => $download['downloadlink'], - 'appdata' => $appdata - ); - } else { - throw new \Exception('Could not fetch app info!'); - } - + public static function updateApp( $info=array() ) { list($extractDir, $path) = self::downloadApp($info); $info = self::checkAppsIntegrity($info, $extractDir, $path); @@ -206,6 +192,29 @@ class OC_Installer{ return OC_App::updateApp($info['id']); } + /** + * update an app by it's id + * @param integer $ocsid + * @return bool + * @throws Exception + */ + public static function updateAppByOCSId($ocsid) { + $appdata = OC_OCSClient::getApplication($ocsid); + $download = OC_OCSClient::getApplicationDownload($ocsid, 1); + + if (isset($download['downloadlink']) && trim($download['downloadlink']) !== '') { + $download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']); + $info = array( + 'source' => 'http', + 'href' => $download['downloadlink'], + 'appdata' => $appdata + ); + } else { + throw new \Exception('Could not fetch app info!'); + } + + return self::updateApp($info); + } /** * @param array $data @@ -322,7 +331,7 @@ class OC_Installer{ $version = trim($info['version']); } - if($version<>trim($data['appdata']['version'])) { + if(isset($data['appdata']['version']) && $version<>trim($data['appdata']['version'])) { OC_Helper::rmdirr($extractDir); throw new \Exception($l->t("App can't be installed because the version in info.xml/version is not the same as the version reported from the app store")); } diff --git a/settings/ajax/updateapp.php b/settings/ajax/updateapp.php index 5eb0a277cb..7010dfe23b 100644 --- a/settings/ajax/updateapp.php +++ b/settings/ajax/updateapp.php @@ -30,7 +30,7 @@ if (!is_numeric($appId)) { $appId = OC_App::cleanAppId($appId); -$result = OC_Installer::updateApp($appId); +$result = OC_Installer::updateAppByOCSId($appId); if($result !== false) { OC_JSON::success(array('data' => array('appid' => $appId))); } else { diff --git a/tests/data/testapp.zip b/tests/data/testapp.zip new file mode 100644 index 0000000000000000000000000000000000000000..e76c0d187248193e90ad487fbd172af065695aa3 GIT binary patch literal 895 zcmWIWW@h1H00FbwBQ9VDl;B_xU?@o~E=epX&<~B^VNh1;50(JpuZ$uL0dQSbXu1TD zbOCi{=B4F>bt?jODq!gTSc7V(I2PSNQ}hZl3P4U^0~#<*5#a<81`Z(PMRS4#7DGVp z(5uMJfthj&YKm{)M!rJ^JTB>h+dAn%+M)JM%MN9U_D|Vcp!I8M zT+QYBRZBZ1`_|SC?7O`eOTSvj8{o~(QCF+?y#W~L zTY(r9un=E;Cn9LdQj3Z+^YdVyS_Ji!@rBJSK$F-xmhIK(X9b$d3^tjONt78^RPz8; zfPupcM-YvOcn)~P2Y4e)z>Ice6U>lIKqLYV23R6MHVHEcAeq!yg2g0wg5ZEB2#A4* zbteIJdSK}OSc7V(I2PSNQ}hZl3P4Vnrq~}0!l+J2Msoru(2)3eUq@&E z;0UnYnLv9%7{4|_WNpYECr-O0HtonEfNqQk0|yYIhkz13WAx&21V(S~ZmvTKJgtir z9z9w1;EH%gX4Xb=_4cObqe<2loJ)IuCFfu0`c=2Dk$=h0Ty=Hk_@W{UMS~eU3;7=$ z@7%;Yb?>FAUlNW;oSFS_Z~KSvwTm*YuX=xU^2vIozvtwceI^@Sf3)h9NB4_+6|OWyzmTx% z-W_nUWQi5`l~R?ktvuW>Q}0wxIFRRPe!Nw>YF_-O?+LOiUZ1#sfjz*Rog;L4nZZ$D z0yJb~04Fj?An~HbummuWU@-&>Vo*xctH{j(rIY8tG;#`>PJH_|@*OhZaY_Fq8fxEg zVd3n;qel)MIKc0+QgGTW6^qQH&#!ZZB<-#)-^SN=-o{kh^O?lC+;W?(%u)|N^kncI zP!>CNbSX>N)=3Z24z+Jub|_1R++*UVcLP^2X>>+FVV4 zIbEIJx3+Fz-|f9v`qesKkgw`$^}aU%eYF*cL0Jd8uaxmeJuVOS_5tJGLBQ4B@yIE^ zgIe|zHf(15v|*D+x`0V*ud9b!>=|w$!+-PZAN=pw+&OhBbGvQvPG;`KHznE_66Z+V z-dkv}Z?W+VgGrUfd-z%Yn7-Dy_4Qh;&CEB;js83fZm0};78VmK87a8cH$WwIRaf!- zYD4!1SFbSn=c$ED-E*(HlnR?K@!tL{WtVo$Eg{+STZ6x7KU;XAkmK)eCx5PKVKXzT zZ|QNhIwY+wo!LFfHrUC1$%g0)SM&Q`yl+;}`N`g1;4BXcBd+Por4Iwc#{ebfWzk|D zTLDp)T2!2wp9hL~P#${)jd-|AJ(hRKHcZJ!eu>+fpueBWCt@Bi$F@x^_ASrWv=XLg)2QWXpeUjKQA zM@rqxa{V-B1AmV%rD0FZv$O*RLrX&@OWl3F`JwK`GM$pz^PUD8Gs70W7GmS?+?Tq^ z2%la?@YO56DI!2NbOI$7hrzic$K&h=IEXW)yOW zp&E&(PLRt`B_ty^G?w8r5~*@Q4?IM9g@HxQNq{R-g$0N{GrX8-^I literal 0 HcmV?d00001 diff --git a/tests/lib/installer.php b/tests/lib/installer.php new file mode 100644 index 0000000000..97b14ef579 --- /dev/null +++ b/tests/lib/installer.php @@ -0,0 +1,74 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class Test_Installer extends PHPUnit_Framework_TestCase { + + private static $appid = 'testapp'; + + public function testInstallApp() { + $pathOfTestApp = __DIR__; + $pathOfTestApp .= '/../data/'; + $pathOfTestApp .= 'testapp.zip'; + + $tmp = OC_Helper::tmpFile(); + OC_Helper::copyr($pathOfTestApp, $tmp); + + $data = array( + 'path' => $tmp, + 'source' => 'path', + ); + + OC_Installer::installApp($data); + $isInstalled = OC_Installer::isInstalled(self::$appid); + + $this->assertTrue($isInstalled); + + //clean-up + OC_Installer::removeApp(self::$appid); + unlink($tmp); + } + + public function testUpdateApp() { + $pathOfOldTestApp = __DIR__; + $pathOfOldTestApp .= '/../data/'; + $pathOfOldTestApp .= 'testapp.zip'; + + $oldTmp = OC_Helper::tmpFile(); + OC_Helper::copyr($pathOfOldTestApp, $oldTmp); + + $oldData = array( + 'path' => $oldTmp, + 'source' => 'path', + ); + + $pathOfNewTestApp = __DIR__; + $pathOfNewTestApp .= '/../data/'; + $pathOfNewTestApp .= 'testapp2.zip'; + + $newTmp = OC_Helper::tmpFile(); + OC_Helper::copyr($pathOfNewTestApp, $newTmp); + + $newData = array( + 'path' => $newTmp, + 'source' => 'path', + ); + + OC_Installer::installApp($oldData); + $oldVersionNumber = OC_App::getAppVersion(self::$appid); + + OC_Installer::updateApp($newData); + $newVersionNumber = OC_App::getAppVersion(self::$appid); + + $this->assertNotEquals($oldVersionNumber, $newVersionNumber); + + //clean-up + OC_Installer::removeApp(self::$appid); + unlink($oldTmp); + unlink($newTmp); + } +} From 00b7f365bcbf6ab81e9a955f25b323c1497f822b Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Wed, 4 Jun 2014 17:18:20 +0200 Subject: [PATCH 09/22] remove not needed unlink in installer test --- tests/lib/installer.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/lib/installer.php b/tests/lib/installer.php index 97b14ef579..09ad558e93 100644 --- a/tests/lib/installer.php +++ b/tests/lib/installer.php @@ -30,7 +30,6 @@ class Test_Installer extends PHPUnit_Framework_TestCase { //clean-up OC_Installer::removeApp(self::$appid); - unlink($tmp); } public function testUpdateApp() { @@ -68,7 +67,5 @@ class Test_Installer extends PHPUnit_Framework_TestCase { //clean-up OC_Installer::removeApp(self::$appid); - unlink($oldTmp); - unlink($newTmp); } } From 6aab50f8aa068df814334a59eae6754fc99efa22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Thu, 5 Jun 2014 16:32:12 +0200 Subject: [PATCH 10/22] fix unit tests --- tests/lib/installer.php | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/tests/lib/installer.php b/tests/lib/installer.php index 09ad558e93..5e26724520 100644 --- a/tests/lib/installer.php +++ b/tests/lib/installer.php @@ -9,13 +9,25 @@ class Test_Installer extends PHPUnit_Framework_TestCase { private static $appid = 'testapp'; + private $appstore; + + public function setUp() { + $this->appstore = OC_Config::getValue('appstoreenabled', true); + OC_Config::setValue('appstoreenabled', true); + OC_Installer::removeApp(self::$appid); + } + + public function tearDown() { + OC_Installer::removeApp(self::$appid); + OC_Config::setValue('appstoreenabled', $this->appstore); + } public function testInstallApp() { $pathOfTestApp = __DIR__; $pathOfTestApp .= '/../data/'; $pathOfTestApp .= 'testapp.zip'; - $tmp = OC_Helper::tmpFile(); + $tmp = OC_Helper::tmpFile('.zip'); OC_Helper::copyr($pathOfTestApp, $tmp); $data = array( @@ -27,9 +39,6 @@ class Test_Installer extends PHPUnit_Framework_TestCase { $isInstalled = OC_Installer::isInstalled(self::$appid); $this->assertTrue($isInstalled); - - //clean-up - OC_Installer::removeApp(self::$appid); } public function testUpdateApp() { @@ -37,7 +46,7 @@ class Test_Installer extends PHPUnit_Framework_TestCase { $pathOfOldTestApp .= '/../data/'; $pathOfOldTestApp .= 'testapp.zip'; - $oldTmp = OC_Helper::tmpFile(); + $oldTmp = OC_Helper::tmpFile('.zip'); OC_Helper::copyr($pathOfOldTestApp, $oldTmp); $oldData = array( @@ -49,7 +58,7 @@ class Test_Installer extends PHPUnit_Framework_TestCase { $pathOfNewTestApp .= '/../data/'; $pathOfNewTestApp .= 'testapp2.zip'; - $newTmp = OC_Helper::tmpFile(); + $newTmp = OC_Helper::tmpFile('.zip'); OC_Helper::copyr($pathOfNewTestApp, $newTmp); $newData = array( @@ -64,8 +73,5 @@ class Test_Installer extends PHPUnit_Framework_TestCase { $newVersionNumber = OC_App::getAppVersion(self::$appid); $this->assertNotEquals($oldVersionNumber, $newVersionNumber); - - //clean-up - OC_Installer::removeApp(self::$appid); } } From fad3bd7fc0c094bd16e07708557cd1a7676889cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Thu, 5 Jun 2014 16:32:46 +0200 Subject: [PATCH 11/22] reenable checkCode() --- lib/private/installer.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/private/installer.php b/lib/private/installer.php index 06677115c8..c0fc5304d4 100644 --- a/lib/private/installer.php +++ b/lib/private/installer.php @@ -514,7 +514,6 @@ class OC_Installer{ * @return boolean true for app is o.k. and false for app is not o.k. */ public static function checkCode($appname, $folder) { - return true; $blacklist=array( 'exec(', 'eval(', From 498aa6664807cca87a1ca8d2fd0d3d609430bae8 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Fri, 6 Jun 2014 09:33:34 +0200 Subject: [PATCH 12/22] add additional type check --- lib/autoloader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/autoloader.php b/lib/autoloader.php index da20a2a025..54f01d9be9 100644 --- a/lib/autoloader.php +++ b/lib/autoloader.php @@ -90,7 +90,7 @@ class Autoloader { list(, $app, $rest) = explode('\\', $class, 3); $app = strtolower($app); $appPath = \OC_App::getAppPath($app); - if (stream_resolve_include_path($appPath)) { + if ($appPath && stream_resolve_include_path($appPath)) { $paths[] = $appPath . '/' . strtolower(str_replace('\\', '/', $rest) . '.php'); // If not found in the root of the app directory, insert '/lib' after app id and try again. $paths[] = $appPath . '/lib/' . strtolower(str_replace('\\', '/', $rest) . '.php'); From a110973b3a7d10c630e901e5ee7fb77b7a1e0f28 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Fri, 6 Jun 2014 09:41:33 +0200 Subject: [PATCH 13/22] some additional type checks --- lib/private/app.php | 4 ++-- lib/private/installer.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/private/app.php b/lib/private/app.php index 0292abb631..3c7b17c148 100644 --- a/lib/private/app.php +++ b/lib/private/app.php @@ -521,7 +521,7 @@ class OC_App { */ public static function isAppDirWritable($appid) { $path = self::getAppPath($appid); - return is_writable($path); + return ($path !== false) ? is_writable($path) : false; } /** @@ -544,7 +544,7 @@ class OC_App { */ public static function getAppVersion($appid) { $file = self::getAppPath($appid); - return self::getAppVersionByPath($file); + return ($file !== false) ? self::getAppVersionByPath($file) : '0'; } /** diff --git a/lib/private/installer.php b/lib/private/installer.php index c0fc5304d4..1589610aed 100644 --- a/lib/private/installer.php +++ b/lib/private/installer.php @@ -173,7 +173,7 @@ class OC_Installer{ $basedir .= '/'; $basedir .= $info['id']; - if($currentDir !== null && is_writable($currentDir)) { + if($currentDir !== false && is_writable($currentDir)) { $basedir = $currentDir; } if(is_dir($basedir)) { From 1ab9bdcaa0446b949e51572de733881057a6f411 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Tue, 10 Jun 2014 13:01:10 +0200 Subject: [PATCH 14/22] remove unnecessary @return --- lib/private/app.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/private/app.php b/lib/private/app.php index 3c7b17c148..c1a62f844a 100644 --- a/lib/private/app.php +++ b/lib/private/app.php @@ -267,7 +267,6 @@ class OC_App { /** * disables an app * @param string $app app - * @return null * * This function set an app as disabled in appconfig. */ From 5d4f3baf56337b09299792c54e85e6720bc77d15 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Tue, 10 Jun 2014 13:51:20 +0200 Subject: [PATCH 15/22] fix php doc block --- lib/private/app.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/private/app.php b/lib/private/app.php index c1a62f844a..15577ce89d 100644 --- a/lib/private/app.php +++ b/lib/private/app.php @@ -265,10 +265,8 @@ class OC_App { } /** - * disables an app - * @param string $app app - * * This function set an app as disabled in appconfig. + * @param string $app app */ public static function disable($app) { self::$enabledAppsCache = array(); // flush From 6bf06890e7934d3145ded0db4f63307dfd740598 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Tue, 10 Jun 2014 13:56:05 +0200 Subject: [PATCH 16/22] always return a bool in OC_App::updateApp --- lib/private/app.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/private/app.php b/lib/private/app.php index 15577ce89d..f4368bfe16 100644 --- a/lib/private/app.php +++ b/lib/private/app.php @@ -1163,7 +1163,7 @@ class OC_App { OC_DB::updateDbFromStructure(self::getAppPath($appid) . '/appinfo/database.xml'); } if (!self::isEnabled($appid)) { - return; + return false; } if (file_exists(self::getAppPath($appid) . '/appinfo/update.php')) { self::loadApp($appid); From 602404c631f4acaf3e5ae1445c4e1aa42be365c5 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Tue, 10 Jun 2014 13:58:41 +0200 Subject: [PATCH 17/22] fix php doc block --- lib/private/app.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/private/app.php b/lib/private/app.php index f4368bfe16..a62623905f 100644 --- a/lib/private/app.php +++ b/lib/private/app.php @@ -1107,6 +1107,8 @@ class OC_App { /** * @param mixed $app * @return bool + * @throws Exception if app is not compatible with this version of ownCloud + * @throws Exception if no app-name was specified */ public static function installApp($app) { $l = OC_L10N::get('core'); From 5e9fa6481940c9220738716f9bd83345f6af91a4 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Tue, 10 Jun 2014 18:38:21 +0200 Subject: [PATCH 18/22] don't show update button when appstore is disabled or no writable dir exists --- lib/private/installer.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/private/installer.php b/lib/private/installer.php index 1589610aed..96c6841c2a 100644 --- a/lib/private/installer.php +++ b/lib/private/installer.php @@ -347,6 +347,21 @@ class OC_Installer{ * The function will check if an update for a version is available */ public static function isUpdateAvailable( $app ) { + static $isInstanceReadyForUpdates = null; + + if ($isInstanceReadyForUpdates === null) { + $installPath = OC_App::getInstallPath(); + if ($installPath === false || $installPath === null) { + $isInstanceReadyForUpdates = false; + } else { + $isInstanceReadyForUpdates = true; + } + } + + if ($isInstanceReadyForUpdates === false) { + return false; + } + $ocsid=OC_Appconfig::getValue( $app, 'ocsid', ''); if($ocsid<>'') { From 0890ce4f91df94052387141e8bcf917ead94cb55 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Fri, 13 Jun 2014 00:07:43 +0200 Subject: [PATCH 19/22] Update preseed-config.php --- tests/preseed-config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/preseed-config.php b/tests/preseed-config.php index 95ffb4514b..3fd5b3cb7f 100644 --- a/tests/preseed-config.php +++ b/tests/preseed-config.php @@ -7,7 +7,7 @@ $CONFIG = array ( array ( 'path' => OC::$SERVERROOT.'/apps', 'url' => '/apps', - 'writable' => false, + 'writable' => true, ), 1 => array ( From c378e76412fb0bfa52a4dd9e3fc500c6d492cd46 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Fri, 13 Jun 2014 21:45:31 +0200 Subject: [PATCH 20/22] skip certain tests for shipped apps --- lib/private/installer.php | 16 ++++++++++------ settings/ajax/updateapp.php | 5 ++++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/private/installer.php b/lib/private/installer.php index 96c6841c2a..466aa4a8f8 100644 --- a/lib/private/installer.php +++ b/lib/private/installer.php @@ -140,6 +140,8 @@ class OC_Installer{ /** * @brief Update an application + * @param array $info + * @param bool $isShipped * * This function installs an app. All information needed are passed in the * associative array $data. @@ -164,9 +166,9 @@ class OC_Installer{ * upgrade.php can determine the current installed version of the app using * "OC_Appconfig::getValue($appid, 'installed_version')" */ - public static function updateApp( $info=array() ) { + public static function updateApp( $info=array(), $isShipped=false) { list($extractDir, $path) = self::downloadApp($info); - $info = self::checkAppsIntegrity($info, $extractDir, $path); + $info = self::checkAppsIntegrity($info, $extractDir, $path, $isShipped); $currentDir = OC_App::getAppPath($info['id']); $basedir = OC_App::getInstallPath(); @@ -195,10 +197,11 @@ class OC_Installer{ /** * update an app by it's id * @param integer $ocsid + * @param bool $isShipped * @return bool * @throws Exception */ - public static function updateAppByOCSId($ocsid) { + public static function updateAppByOCSId($ocsid, $isShipped=false) { $appdata = OC_OCSClient::getApplication($ocsid); $download = OC_OCSClient::getApplicationDownload($ocsid, 1); @@ -278,10 +281,11 @@ class OC_Installer{ * check an app's integrity * @param array $data * @param string $extractDir + * @param bool $isShipped * @return array * @throws \Exception */ - public static function checkAppsIntegrity($data = array(), $extractDir, $path) { + public static function checkAppsIntegrity($data = array(), $extractDir, $path, $isShipped=false) { $l = \OC_L10N::get('lib'); //load the info.xml file of the app if(!is_file($extractDir.'/appinfo/info.xml')) { @@ -306,7 +310,7 @@ class OC_Installer{ } $info=OC_App::getAppInfo($extractDir.'/appinfo/info.xml', true); // check the code for not allowed calls - if(!OC_Installer::checkCode($info['id'], $extractDir)) { + if(!$isShipped && !OC_Installer::checkCode($info['id'], $extractDir)) { OC_Helper::rmdirr($extractDir); throw new \Exception($l->t("App can't be installed because of not allowed code in the App")); } @@ -318,7 +322,7 @@ class OC_Installer{ } // check if shipped tag is set which is only allowed for apps that are shipped with ownCloud - if(isset($info['shipped']) and ($info['shipped']=='true')) { + if(!$isShipped && isset($info['shipped']) && ($info['shipped']=='true')) { OC_Helper::rmdirr($extractDir); throw new \Exception($l->t("App can't be installed because it contains the true tag which is not allowed for non shipped apps")); } diff --git a/settings/ajax/updateapp.php b/settings/ajax/updateapp.php index 7010dfe23b..78f6775fe9 100644 --- a/settings/ajax/updateapp.php +++ b/settings/ajax/updateapp.php @@ -19,6 +19,7 @@ $appId = $_POST['appid']; if (!is_numeric($appId)) { $appId = OC_Appconfig::getValue($appId, 'ocsid', null); + $isShipped = OC_App::isShipped($appId); if ($appId === null) { OCP\JSON::error(array( @@ -26,11 +27,13 @@ if (!is_numeric($appId)) { )); exit; } +} else { + $isShipped = false; } $appId = OC_App::cleanAppId($appId); -$result = OC_Installer::updateAppByOCSId($appId); +$result = OC_Installer::updateAppByOCSId($appId, $isShipped); if($result !== false) { OC_JSON::success(array('data' => array('appid' => $appId))); } else { From 65028c459b35ca38f787e0430415f7b00f46c152 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Fri, 13 Jun 2014 21:51:16 +0200 Subject: [PATCH 21/22] don't skip code check for skipped apps those apps will have to use the public api --- lib/private/installer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/private/installer.php b/lib/private/installer.php index 466aa4a8f8..f7c770bf9a 100644 --- a/lib/private/installer.php +++ b/lib/private/installer.php @@ -310,7 +310,7 @@ class OC_Installer{ } $info=OC_App::getAppInfo($extractDir.'/appinfo/info.xml', true); // check the code for not allowed calls - if(!$isShipped && !OC_Installer::checkCode($info['id'], $extractDir)) { + if(!OC_Installer::checkCode($info['id'], $extractDir)) { OC_Helper::rmdirr($extractDir); throw new \Exception($l->t("App can't be installed because of not allowed code in the App")); } From 86f546ff6487b9ac8b18ad14ca290b8051f0496e Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Mon, 16 Jun 2014 13:49:02 +0200 Subject: [PATCH 22/22] disable code check for shipped apps --- lib/private/installer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/private/installer.php b/lib/private/installer.php index f7c770bf9a..466aa4a8f8 100644 --- a/lib/private/installer.php +++ b/lib/private/installer.php @@ -310,7 +310,7 @@ class OC_Installer{ } $info=OC_App::getAppInfo($extractDir.'/appinfo/info.xml', true); // check the code for not allowed calls - if(!OC_Installer::checkCode($info['id'], $extractDir)) { + if(!$isShipped && !OC_Installer::checkCode($info['id'], $extractDir)) { OC_Helper::rmdirr($extractDir); throw new \Exception($l->t("App can't be installed because of not allowed code in the App")); }