Skip to content

Commit

Permalink
Item11264:
Browse files Browse the repository at this point in the history
   * now using a local REST callback for all checks for proper caching 
   * plugin versions are now compared using Foswiki::Configre::Dependency::compare_version
   * added {ExcludeExtensions} feature not to disclose private plugins 



git-svn-id: http://svn.foswiki.org/trunk@13257 0b4bb1d4-4e5a-0410-9cc4-b2b747904278
  • Loading branch information
MichaelDaum authored and MichaelDaum committed Nov 30, 2011
1 parent 01372e0 commit 2472a36
Show file tree
Hide file tree
Showing 7 changed files with 370 additions and 256 deletions.
38 changes: 27 additions & 11 deletions UpdatesPlugin/data/System/UpdatesPlugin.txt
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
%META:TOPICINFO{author="micha" comment="reprev" date="1321804319" format="1.1" reprev="2" version="2"}%
---+!! %TOPIC%
<div style="float:right"><img src="%ATTACHURL%/logo.png"></div>
<img class="foswikiRight" src="%ATTACHURL%/logo.png">
%$SHORTDESCRIPTION%

%TOC%

This plugin is used to keep [[%USERSWEB%.AdminGroup][administrators]] informed of updates to their installed extensions via a pop-up message at the top of the page.
This plugin is used to keep [[%USERSWEB%.AdminGroup][administrators]] informed
of updates to their installed extensions via a pop-up message at the top of the
page.

Users can choose to either perform the update immediately using the =configure= tool, or delay it until later.
Users can choose to either perform the update immediately using the =configure=
tool, or delay it until later.

---++ Usage

Click <a href="#" id="forceCheck">here</a> to force checking foswiki.org for extension updates. If your wiki requires updating, a message will be displayed at the top of the page.
Click <a href="#" id="forceCheck">here</a> to force checking foswiki.org for
extension updates. If your wiki requires updating, a message will be displayed
at the top of the page.

<literal>
<script>
Expand All @@ -26,20 +31,31 @@ jQuery(function($) {
</literal>
%JQREQUIRE{"cookie"}%

The upgrade decision is stored locally, so that the plugin will only check for updates again when this cookie expires (in 7 days).
The upgrade decision is stored locally, so that the plugin will only check for
updates again when this cookie expires (in 7 days).

Note that the plugin requires Javascript and Cookies to be enabled in the browser.
Note that the plugin requires Javascript and Cookies to be enabled in the
browser.

---++ Installation Instructions

%$INSTALL_INSTRUCTIONS%

---++ Notes
The plugin sends update status requests to the repository server to retrieve the current release information for extensions. If these update status requests are proxied via the local server (the default) then the current status of extensions retrieved from the repository is cached in the working/work_areas/UpdatesPlugin directory for 24 hours (default) before being retrieved again. You can safely clear the cache at any time by deleting all files in this directory.

The plugin defines a REST handler for handling proxy requests. The 'rest' script must be removed from the ={AuthScripts}= configuration for this to work.

The plugin defines a macro, =EXTENSIONVERSIONJSON=, which gets a JSON structure describing all the currently installed extensions. This is for purely internal use and will be removed in a future release.
The plugin sends update status requests to the repository server to retrieve
the current release information for extensions. If these update status requests
are proxied via the local server (the default) then the current status of
extensions retrieved from the repository is cached in the
working/work_areas/UpdatesPlugin directory for 24 hours (default) before being
retrieved again. You can safely clear the cache at any time by deleting all
files in this directory.

The plugin defines a REST handler for handling proxy requests. The 'rest'
script must be removed from the ={AuthScripts}= configuration for this to work.

%T% If you are using private extensions on your wiki server that are not available
as Open Source on foswiki.org then use the ={ExcludeExtensions}= parameter in =configure=
to prevent disclosing them to the outside world.

---++ Info
<!--
Expand Down
190 changes: 36 additions & 154 deletions UpdatesPlugin/lib/Foswiki/Plugins/UpdatesPlugin.pm
Original file line number Diff line number Diff line change
Expand Up @@ -18,193 +18,75 @@ package Foswiki::Plugins::UpdatesPlugin;
use strict;
use warnings;

use Assert;

use Foswiki::Func ();

our $VERSION = '$Rev$';
our $RELEASE = '0.20';
our $RELEASE = '0.30';
our $SHORTDESCRIPTION = 'Checks Foswiki.org for updates';
our $NO_PREFS_IN_TOPIC = 1;
our $core;

use constant RESPECT_COOKIE => 1; # Set to 0 to ignore the cookie
use constant DEBUG => 0; # Set to 1 to enable debug mode

sub initPlugin {

Foswiki::Func::registerRESTHandler( 'report', \&_REST_report );
Foswiki::Func::registerTagHandler( 'EXTENSIONVERSIONJSON', \&_EXTENSIONVERSIONJSON );

# bail out if not an admin and not in view mode
return 0 unless
Foswiki::Func::isAnAdmin() &&
Foswiki::Func::getContext()->{view};
my $context = Foswiki::Func::getContext();
return 1
unless Foswiki::Func::isAnAdmin()
&& ( $context->{view} || $context->{rest} );

my $request = Foswiki::Func::getRequestObject();
my $cookie = $request->cookie("FOSWIKI_UPDATESPLUGIN");
my $cookie;

return 0 if RESPECT_COOKIE && defined($cookie) && $cookie <= 0; # 0: DoNothing
$cookie = $request->cookie("FOSWIKI_UPDATESPLUGIN") unless DEBUG;

Foswiki::Func::readTemplate("updatesplugin");
return 1 if defined($cookie) && $cookie <= 0; # 0: DoNothing

my $installedPlugins = '';

# we already know that the admin has to do something, so don't search again.
# this happens when the admin continues to click around but did not action
# on the info banner.
$installedPlugins = Foswiki::Func::expandTemplate("installedplugins")
unless defined($cookie);
Foswiki::Func::readTemplate("updatesplugin");

my $css = Foswiki::Func::expandTemplate("css");
# add stuff to page
my $css = Foswiki::Func::expandTemplate("css");
my $messageTmpl = Foswiki::Func::expandTemplate("messagetmpl");

require Foswiki::Plugins::JQueryPlugin;

Foswiki::Plugins::JQueryPlugin::createPlugin("cookie");
Foswiki::Plugins::JQueryPlugin::createPlugin("tmpl");

my $reportUrl;
if ( $Foswiki::cfg{Plugins}{UpdatesPlugin}{ProxyUrl} ) {
$reportUrl = $Foswiki::cfg{Plugins}{UpdatesPlugin}{ProxyUrl};
} else {
# SMELL read Foswiki::cfg{ExtensionsRepositories} and generate the report url on its base
$reportUrl = $Foswiki::cfg{Plugins}{UpdatesPlugin}{ReportUrl};
}
$reportUrl ||= "http://foswiki.org/Extensions/UpdatesPluginReport";
my $jsFile =
(DEBUG) ? 'jquery.updates.uncompressed.js' : 'jquery.updates.js';

my $configureUrl = $Foswiki::cfg{Plugins}{UpdatesPlugin}{ConfigureUrl}
|| Foswiki::Func::getScriptUrl(undef, undef, "configure");
my $configureUrl = $Foswiki::cfg{Plugins}{UpdatesPlugin}{ConfigureUrl}
|| Foswiki::Func::getScriptUrl( undef, undef, "configure" );

my $debug = (DEBUG) ? '.uncompressed' : '';

Foswiki::Func::addToZone("head", "UPDATESPLUGIN::META", <<META);
<meta name="foswiki.UPDATESPLUGIN::REPORTURL" content="$reportUrl" />
<meta name="foswiki.UPDATESPLUGIN::CONFIGUREURL" content="$configureUrl" />
Foswiki::Func::addToZone( "head", "UPDATESPLUGIN::META", <<META);
<meta name="foswiki.UPDATESPLUGIN::CONFIGUREURL" content="configureUrl" />
$css
$messageTmpl
META

Foswiki::Func::addToZone(
"script", "UPDATESPLUGIN::JS",
<<JS, "JQUERYPLUGIN::FOSWIKI, JQUERYPLUGIN::COOKIE, JQUERYPLUGIN::TMPL" );
$installedPlugins
<script src="%PUBURLPATH%/%SYSTEMWEB%/UpdatesPlugin/jquery.updates$debug.js"></script>
Foswiki::Func::addToZone( "script", "UPDATESPLUGIN::JS",
<<JS, "JQUERYPLUGIN::FOSWIKI, JQUERYPLUGIN::COOKIE, JQUERYPLUGIN::TMPL" );
<script src="%PUBURLPATH%/%SYSTEMWEB%/UpdatesPlugin/$jsFile"></script>
JS

return 1;
}
Foswiki::Func::registerRESTHandler(
'check',
sub {
return getCore( shift, debug => DEBUG )->handleRESTCheck(@_);
}
);

# SMELL: hack assumes structure of plugins controller object
sub _EXTENSIONVERSIONJSON {
my ($session, %params, $topic, $web, $topicObject) = @_;

# First get contribs and skins by poking into the System web. The versions returned
# may include %$VERSION% if this is a pseudo-install
my $list = Foswiki::Func::expandCommonVariables( '{%SEARCH{"1" nosearch="on" nototal="on" web="System" topic="*Skin,*Contrib" format="$topic" separator=","}%};' );

my $data = {};
foreach my $thing (split(',', $list)) {
next unless $thing =~ /^([a-zA-Z0-9_]+)$/;
my $mn = $1;
my $mod = "Foswiki::Contrib::$mn";
# SMELL: unconditional loading of contribs (is that so bad?)
eval "require $mod";
unless ($@) {
$data->{$mn} = eval "\$Foswiki::Contrib::${mn}::RELEASE"
|| '%$RELEASE';
}
}

# Get plugins; this should obtain "true" version numbers
my $controller = $session->{plugins};
foreach my $plugin ( @{ $controller->{plugins} } ) {
$data->{$plugin->{name}} = eval "\$$plugin->{module}::RELEASE"
|| '%$RELEASE';
}
require JSON;
return JSON::to_json($data);
return 1;
}

# the plugin can be configured to proxy the request for data. In this case the local server
# publishes a REST method that responds with the data from f.o - either cached locally or
# freshly mined.
sub _REST_report {
my ( $session, $plugin, $verb, $response ) = @_;

my $query = Foswiki::Func::getCgiQuery();
my $list = $query->param( 'list' );
my @exts = split( /,/, $list );
my $works = Foswiki::Func::getWorkArea( 'UpdatesPlugin' );
my $fh;
my @reply; # array of JSON-encoded structures

my @unfound;
foreach my $ext (@exts) {
# if the cache is fresh, use it
if ( -e "$works/$ext"
&& (stat( "$works/$ext" ))[9] >
time() - $Foswiki::cfg{Plugins}{UpdatesPlugin}{ProxyCacheTimeout} ) {
# Use the cache
local $/ = undef;
if ( open( $fh, '<:encoding(UTF-8)', "$works/$ext" ) ) {
push( @reply, <$fh> );
close( $fh );
next;
}
}
# Not found in cache, or cache out of date. Update cache.
push( @unfound, $ext );
}
if (scalar(@unfound)) {
# One or more things in the cache need an update from f.o

# SMELL read Foswiki::cfg{ExtensionsRepositories} and generate the report url on its base
my $reportUrl = $Foswiki::cfg{Plugins}{UpdatesPlugin}{ReportUrl}
|| "http://foswiki.org/Extensions/UpdatesPluginReport";
$reportUrl .= "?list=".join(',', @unfound);
# Pass through a controlled subset of possible params
for my $param (qw(contenttype skin)) {
my $ct = $query->param( $param );
$reportUrl .= ";$param=$ct" if $param;
}
my $resource = Foswiki::Func::getExternalResource( $reportUrl );

if ( !$resource->is_error() && $resource->isa( 'HTTP::Response' ) ) {
my $content = $resource->decoded_content();
# "Verify" the format of the resource and reduce to a perl array
if ( $content =~ /^foswikiUpdates.handleResponse\((\[.*\])\);$/s ) {
require JSON;
my $data = JSON::from_json( $1 );
# Refresh the cache
foreach my $ext ( @$data ) {
my $text = JSON::to_json($ext);
if (open( $fh, '>:encoding(UTF-8)', "$works/$ext->{topic}" )) {
print $fh $text;
}
push( @reply, $text );
}
} else {
_backREST($response, 500, "Response from $reportUrl is unparseable: $content");
return undef;
}
} else {
_backREST($response, 500, "Failed to get $reportUrl: ".$resource->message());
return undef;
}
}
# Rebuild the reply and send to the client
_backREST($response, 200, 'foswikiUpdates.handleResponse([' . join(',', @reply ) . ']);');
return undef;
}
sub getCore {
unless ($core) {
require Foswiki::Plugins::UpdatesPlugin::Core;
$core = new Foswiki::Plugins::UpdatesPlugin::Core(@_);
}

sub _backREST {
my ($response, $status, $content) = @_;
$response->header(
-status => 200,
-type => 'text/plain',
-charset => 'UTF-8'
);
$response->print( Encode::encode_utf8($content) );
#
return $core;
}


1;
15 changes: 6 additions & 9 deletions UpdatesPlugin/lib/Foswiki/Plugins/UpdatesPlugin/Config.spec
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,14 @@
# http://foswiki.org/Extensions/UpdatesPluginReport
$Foswiki::cfg{Plugins}{UpdatesPlugin}{ReportUrl} = "http://foswiki.org/Extensions/UpdatesPluginReport";

# **URL**
# A proxy URL that is normally used to obtain the update report. This is normally the URL of a REST
# handler on the local site. Note: don't forget to remove 'rest' from the {AuthScripts} setting,
# or this won't work. You can set this URL to the empty path, which will force the plugin to refer
# back to Foswiki.org for every update check. Otherwise the response from Foswiki.org is cached
# on this server.
$Foswiki::cfg{Plugins}{UpdatesPlugin}{ProxyUrl} = '$Foswiki::cfg{ScriptUrlPath}/rest$Foswiki::cfg{ScriptSuffix}/UpdatesPlugin/report';

# **NUMBER**
# Number of seconds to cache the update report for extensions. Default is 24 hours.
$Foswiki::cfg{Plugins}{UpdatesPlugin}{ProxyCacheTimeout} = 86400;
$Foswiki::cfg{Plugins}{UpdatesPlugin}{CacheTimeout} = 86400;

# **STRING**
# A list of extensions that shall not be checked for updates on foswiki.org. This setting can be used
# to prevent plugins not hosted on foswiki.org from being reported to the outside world.
$Foswiki::cfg{Plugins}{UpdatesPlugin}{ExcludeExtensions} = '';

# **URL**
# The URL of the 'configure' program used to install extensions on your Foswiki.
Expand Down
Loading

0 comments on commit 2472a36

Please sign in to comment.