Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make shipped apps updatable via appstore #8808

Merged
merged 25 commits into from
Jun 16, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2bcfd8e
make it possible to update shipped apps via the appstore
georgehrke May 21, 2014
c8636ca
Merge branch 'master' into update_shipped_apps_from_appstore
georgehrke May 31, 2014
020255b
add button for properly uninstalling apps
georgehrke May 31, 2014
c6dc9ae
Merge branch 'master' into update_shipped_apps_from_appstore
georgehrke May 31, 2014
c8a8c7e
read ocsid from shipped apps on install
georgehrke May 31, 2014
eea501b
various fixes as requested by pr reviewers
georgehrke Jun 2, 2014
19129b3
use isset() instead of array_key_exists()
georgehrke Jun 3, 2014
56a8010
remove console.log in apps.js
georgehrke Jun 4, 2014
2c00ab1
update autoloader
georgehrke Jun 4, 2014
724d027
add unit test
georgehrke Jun 4, 2014
00b7f36
remove not needed unlink in installer test
georgehrke Jun 4, 2014
6aab50f
fix unit tests
DeepDiver1975 Jun 5, 2014
fad3bd7
reenable checkCode()
DeepDiver1975 Jun 5, 2014
0fe8f77
Merge branch 'master' into update_shipped_apps_from_appstore
georgehrke Jun 5, 2014
498aa66
add additional type check
georgehrke Jun 6, 2014
a110973
some additional type checks
georgehrke Jun 6, 2014
1ab9bdc
remove unnecessary @return
georgehrke Jun 10, 2014
5d4f3ba
fix php doc block
georgehrke Jun 10, 2014
6bf0689
always return a bool in OC_App::updateApp
georgehrke Jun 10, 2014
602404c
fix php doc block
georgehrke Jun 10, 2014
5e9fa64
don't show update button when appstore is disabled or no writable dir…
georgehrke Jun 10, 2014
0890ce4
Update preseed-config.php
georgehrke Jun 12, 2014
c378e76
skip certain tests for shipped apps
georgehrke Jun 13, 2014
65028c4
don't skip code check for skipped apps
georgehrke Jun 13, 2014
86f546f
disable code check for shipped apps
georgehrke Jun 16, 2014
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions lib/autoloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,11 @@ public function findClass($class) {
} 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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like $appPath defined by \OC_App::getAppPath($app) on line 92 can also be of type false; however, stream_resolve_include_path() does only seem to accept string, maybe add an additional type check?

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');
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bantu @icewind1991 can you help double-checking these auto-loader changes ? (I'm not familiar with autoloader, so not sure if this could break something else)

} elseif (strpos($class, 'Test_') === 0) {
$paths[] = 'tests/lib/' . strtolower(str_replace('_', '/', substr($class, 5)) . '.php');
Expand Down
262 changes: 186 additions & 76 deletions lib/private/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@
* ownCloud
*
* @author Frank Karlitschek
* @copyright 2012 Frank Karlitschek <frank@owncloud.org>
*
* @author Jakob Sack
* @copyright 2012 Frank Karlitschek frank@owncloud.org
* @copyright 2012 Jakob Sack <mail@jakobsack.de>
*
* @author Georg Ehrke
* @copyright 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
Expand Down Expand Up @@ -221,64 +226,53 @@ public static function isEnabled($app) {
public static function enable($app, $groups = null) {
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);
}
}
$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(!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.",
array($info['name'])
)
);
} else {
if (!is_null($groups)) {
OC_Appconfig::setValue($app, 'enabled', json_encode($groups));
}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"));

if (!is_null($groups)) {
OC_Appconfig::setValue($app, 'enabled', json_encode($groups));
}else{
OC_Appconfig::setValue($app, 'enabled', 'yes');
}
}

/**
* disables an app
* @param string $app app
* @return boolean|null
*
* @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']);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure there aren't any other special characters in the download links?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just from moved this code here from another place (https://github.com/owncloud/core/pull/8808/files#diff-4828f39ae4935d14b1dc1a6ffe323725L232)

works in current versions, so apparently it's just fine to escape spaces

$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;
}

return OC_Installer::removeApp($app);
}

/**
* This function set an app as disabled in appconfig.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge this line with the line above @param ... sorry I missed that in my previous comment

* @param string $app app
*/
public static function disable($app) {
self::$enabledAppsCache = array(); // flush
// 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);
}
OC_Appconfig::setValue($app, 'enabled', 'no' );
}

/**
Expand Down Expand Up @@ -461,17 +455,46 @@ public static function getInstallPath() {
}


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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

static should be in the object instead of the method?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

works both, didn't write this piece of code. This is how it is in master

if (isset($app_dir[$appid])) {
return $app_dir[$appid];

if (isset($app_dir[$appId])) {
return $app_dir[$appId];
}
foreach (OC::$APPSROOTS as $dir) {
if (file_exists($dir['path'] . '/' . $appid)) {
return $app_dir[$appid] = $dir;

$possibleApps = array();
foreach(OC::$APPSROOTS as $dir) {
if(file_exists($dir['path'] . '/' . $appId)) {
$possibleApps[] = $dir;
}
}
return false;

if (empty($possibleApps)) {
return false;
} elseif(count($possibleApps) === 1) {
$dir = array_shift($possibleApps);
$app_dir[$appId] = $dir;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this line, $app_dir is never used

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return $dir;
} else {
$versionToLoad = array();
foreach($possibleApps as $possibleApp) {
$version = self::getAppVersionByPath($possibleApp['path']);
if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
$versionToLoad = array(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just return $possibleApp; here instead of looping though the rest, as you only use one wayway

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but we want to load the newest version, that's why we need to check all

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should load the "running" version, not the newest one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can't run multiple app versions that use the same appid, that's why we search for the app with the highest version num

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah okay, is that the same way the core finds the classes of apps?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'dir' => $possibleApp,
'version' => $version,
);
}
}
$app_dir[$appId] = $versionToLoad['dir'];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useless assignment

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

invalid (it's a static variable of the method)

return $versionToLoad['dir'];
//TODO - write test
}
}

/**
Expand All @@ -487,6 +510,17 @@ public static function getAppPath($appid) {
return false;
}


/**
* check if an app's directory is writable
* @param $appid
* @return bool
*/
public static function isAppDirWritable($appid) {
$path = self::getAppPath($appid);
return ($path !== false) ? is_writable($path) : false;
}

/**
* 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)
Expand All @@ -506,15 +540,27 @@ public static function getAppWebPath($appid) {
* @return string
*/
public static function getAppVersion($appid) {
$file = self::getAppPath($appid) . '/appinfo/version';
if (is_file($file) && $version = trim(file_get_contents($file))) {
return $version;
} else {
$appData = self::getAppInfo($appid);
$file = self::getAppPath($appid);
return ($file !== false) ? self::getAppVersionByPath($file) : '0';
}

/**
* 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)) {
return trim(file_get_contents($versionFile));
}else{
$appData = self::getAppInfo($infoFile, true);
return isset($appData['version']) ? $appData['version'] : '';
}
}


/**
* Read all app metadata from the info.xml file
* @param string $appid id of the app or the path of the info.xml file
Expand Down Expand Up @@ -619,7 +665,6 @@ public static function getCurrentApp() {
}
}


/**
* get the forms for either settings, admin or personal
*/
Expand Down Expand Up @@ -745,18 +790,20 @@ public static function listAllApps() {

$info['active'] = $active;

if (isset($info['shipped']) and ($info['shipped'] == 'true')) {
if(isset($info['shipped']) and ($info['shipped'] == 'true')) {
$info['internal'] = true;
$info['internallabel'] = 'Internal App';
$info['internalclass'] = '';
$info['update'] = false;
$info['removable'] = false;
} else {
$info['internal'] = false;
$info['internallabel'] = '3rd Party';
$info['internalclass'] = 'externalapp';
$info['update'] = OC_Installer::isUpdateAvailable($app);
$info['removable'] = true;
}

$info['update'] = OC_Installer::isUpdateAvailable($app);

$info['preview'] = OC_Helper::imagePath('settings', 'trans.png');
$info['version'] = OC_App::getAppVersion($app);
$appList[] = $info;
Expand Down Expand Up @@ -841,6 +888,7 @@ public static function getAppstoreApps($filter = 'approved') {
$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';
Expand All @@ -851,17 +899,29 @@ public static function getAppstoreApps($filter = 'approved') {


// 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'] < 5) {
$img = OC_Helper::imagePath( "core", "rating/s1.png" );
} elseif ($app['score'] < 15) {
$img = OC_Helper::imagePath( "core", "rating/s2.png" );
} elseif($app['score'] < 25) {
$img = OC_Helper::imagePath( "core", "rating/s3.png" );
} elseif($app['score'] < 35) {
$img = OC_Helper::imagePath( "core", "rating/s4.png" );
} elseif($app['score'] < 45) {
$img = OC_Helper::imagePath( "core", "rating/s5.png" );
} elseif($app['score'] < 55) {
$img = OC_Helper::imagePath( "core", "rating/s6.png" );
} elseif($app['score'] < 65) {
$img = OC_Helper::imagePath( "core", "rating/s7.png" );
} elseif($app['score'] < 75) {
$img = OC_Helper::imagePath( "core", "rating/s8.png" );
} elseif($app['score'] < 85) {
$img = OC_Helper::imagePath( "core", "rating/s9.png" );
} elseif($app['score'] < 95) {
$img = OC_Helper::imagePath( "core", "rating/s10.png" );
} elseif($app['score'] < 100) {
$img = OC_Helper::imagePath( "core", "rating/s11.png" );
}

$app1[$i]['score'] = '<img src="' . $img . '"> Score: ' . $app['score'] . '%';
$i++;
Expand Down Expand Up @@ -1043,10 +1103,58 @@ public static function getAppVersions() {
return $versions;
}


/**
* @param mixed $app
* @return bool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add @throws Exception to the doc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just @throws \Exception or is it supposed to have a description in which certain cases an exception is thrown

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the case would be great

* @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');
$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(!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.',
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't comment on the line bellow: In line 1166 there is just a return; without anything. Please add a true/false there or adjust the PHP doc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed with 6bf0689

*/
public static function updateApp($appid) {
if (file_exists(self::getAppPath($appid) . '/appinfo/preupdate.php')) {
Expand All @@ -1057,7 +1165,7 @@ public static function updateApp($appid) {
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);
Expand All @@ -1074,6 +1182,8 @@ public static function updateApp($appid) {
}

self::setAppTypes($appid);

return true;
}

/**
Expand Down
Loading