www_copy and www_pasteList Forkified

This commit is contained in:
Paul Driver 2010-10-04 10:09:23 -07:00
parent ea607eb4c9
commit e238f72278
12 changed files with 605 additions and 216 deletions

View file

@ -1,6 +1,9 @@
7.10.3 7.10.3
7.10.2 7.10.2
- Added WebGUI::Fork api
- Moved html export to Fork
- Moved clipboard functions to Fork
- fixed #11884: Editing Templates impossible / Code editor not loaded - fixed #11884: Editing Templates impossible / Code editor not loaded
- recommitted ukplayer. Removal broke Matrix. Licencing information was available but overlooked. - recommitted ukplayer. Removal broke Matrix. Licencing information was available but overlooked.
- fixed #11883: Wiki "Add page" link does not encode special chars - fixed #11883: Wiki "Add page" link does not encode special chars

View file

@ -19,6 +19,8 @@ save you many hours of grief.
is in WebGUI again. Licencing information was overlooked. An is in WebGUI again. Licencing information was overlooked. An
upgrade to 7.10.1 will break the Matrix. This is fixed now. upgrade to 7.10.1 will break the Matrix. This is fixed now.
* WebGUI now depends on Monkey::Patch for sanely scoped monkeypatching.
7.10.1 7.10.1
-------------------------------------------------------------------- --------------------------------------------------------------------
* WebGUI now depends on PerlIO::eol, for doing line ending translation. * WebGUI now depends on PerlIO::eol, for doing line ending translation.

View file

@ -47,6 +47,7 @@ sub addForkTable {
my $sql = q{ my $sql = q{
CREATE TABLE Fork ( CREATE TABLE Fork (
id CHAR(22), id CHAR(22),
userId CHAR(22),
groupId CHAR(22), groupId CHAR(22),
status LONGTEXT, status LONGTEXT,
error TEXT, error TEXT,

View file

@ -37,6 +37,9 @@ use WebGUI::HTML;
use WebGUI::HTMLForm; use WebGUI::HTMLForm;
use WebGUI::Keyword; use WebGUI::Keyword;
use WebGUI::ProgressBar; use WebGUI::ProgressBar;
use WebGUI::ProgressTree;
use Monkey::Patch;
use WebGUI::Fork;
use WebGUI::Search::Index; use WebGUI::Search::Index;
use WebGUI::TabForm; use WebGUI::TabForm;
use WebGUI::Utility; use WebGUI::Utility;
@ -2555,6 +2558,34 @@ sub setSize {
$self->{_properties}{assetSize} = $size; $self->{_properties}{assetSize} = $size;
} }
#-------------------------------------------------------------------
=head2 setState ( $state )
Updates the asset table with the new state of the asset.
=cut
sub setState {
my ($self, $state) = @_;
my $sql = q{
UPDATE asset
SET state = ?,
stateChangedBy = ?,
stateChanged = ?
WHERE assetId = ?
};
$self->session->db->write(
$sql, [
$state,
$self->session->user->userId,
time,
$self->getId,
]
);
$self->{_properties}->{state} = $state;
$self->purgeCache;
}
#------------------------------------------------------------------- #-------------------------------------------------------------------

View file

@ -46,13 +46,26 @@ Duplicates this asset and the entire subtree below it. Returns the root of the
If true, then only children, and not descendants, will be duplicated. If true, then only children, and not descendants, will be duplicated.
=head3 $state
Set this to "clipboard" if you want the resulting asset to be on the clipboard
(rather than published) when we're done.
=cut =cut
sub duplicateBranch { sub duplicateBranch {
my $self = shift; my ($self, $childrenOnly, $state) = @_;
my $childrenOnly = shift; my $session = $self->session;
my $log = $session->log;
my $clipboard = $state && $state =~ /^clipboard/;
my $newAsset = $self->duplicate(
{ skipAutoCommitWorkflows => 1,
skipNotification => 1,
state => $state,
}
);
my $newAsset = $self->duplicate({skipAutoCommitWorkflows=>1,skipNotification=>1});
# Correctly handle positions for Layout assets # Correctly handle positions for Layout assets
my $contentPositions = $self->get("contentPositions"); my $contentPositions = $self->get("contentPositions");
my $assetsToHide = $self->get("assetsToHide"); my $assetsToHide = $self->get("assetsToHide");
@ -66,7 +79,21 @@ sub duplicateBranch {
next; next;
} }
last unless $child; last unless $child;
my $newChild = $childrenOnly ? $child->duplicate({skipAutoCommitWorkflows=>1, skipNotification=>1}) : $child->duplicateBranch; my $newChild;
if ($childrenOnly) {
$newChild = $child->duplicate(
{ skipAutoCommitWorkflows => 1,
skipNotification => 1,
state => $clipboard && 'clipboard-limbo',
}
);
}
elsif($clipboard) {
$newChild = $child->duplicateBranch(0, 'clipboard-limbo');
}
else {
$newChild = $child->duplicateBranch;
}
$newChild->setParent($newAsset); $newChild->setParent($newAsset);
my ($oldChildId, $newChildId) = ($child->getId, $newChild->getId); my ($oldChildId, $newChildId) = ($child->getId, $newChild->getId);
$contentPositions =~ s/\Q${oldChildId}\E/${newChildId}/g if ($contentPositions); $contentPositions =~ s/\Q${oldChildId}\E/${newChildId}/g if ($contentPositions);

View file

@ -49,6 +49,55 @@ sub canPaste {
return $self->validParent($self->session); ##Lazy call to a class method return $self->validParent($self->session); ##Lazy call to a class method
} }
#-------------------------------------------------------------------
=head2 copyInBackground ( $process, $args )
WebGUI::Fork method called by www_copy
=cut
sub copyInBackground {
my ($process, $args) = @_;
my $session = $process->session;
my $asset = WebGUI::Asset->new($session, $args->{assetId});
my @pedigree = ('self');
my $childrenOnly = 0;
if ($args->{childrenOnly}) {
$childrenOnly = 1;
push @pedigree, 'children';
}
else {
push @pedigree, 'descendants';
}
my $ids = $asset->getLineage(\@pedigree);
my $tree = WebGUI::ProgressTree->new($session, $ids);
my $patch = Monkey::Patch::patch_class 'WebGUI::Asset', 'duplicate', sub {
my $duplicate = shift;
my $self = shift;
my $id = $self->getId;
my $asset = eval { $self->$duplicate(@_) };
my $e = $@;
if ($e) {
$tree->note($id, $e);
$tree->failure($id, 'Died');
}
else {
$tree->success($id);
}
$process->update(sub { $tree->json });
die $e if $e;
return $asset;
};
my $newAsset = $asset->duplicateBranch($childrenOnly, 'clipboard');
$newAsset->update({ title => $newAsset->getTitle . ' (copy)'});
if ($args->{commit}) {
my $tag = WebGUI::VersionTag->getWorking($session);
$tag->requestCommit();
}
}
#------------------------------------------------------------------- #-------------------------------------------------------------------
=head2 cut ( ) =head2 cut ( )
@ -97,6 +146,10 @@ A hash reference of options that can modify how this method works.
Assets that normally autocommit their workflows (like CS Posts, and Wiki Pages) won't if this is true. Assets that normally autocommit their workflows (like CS Posts, and Wiki Pages) won't if this is true.
=head4 state
A state for the duplicated asset (defaults to 'published')
=cut =cut
sub duplicate { sub duplicate {
@ -132,6 +185,10 @@ sub duplicate {
keywords => $keywords, keywords => $keywords,
} ); } );
if (my $state = $options->{state}) {
$newAsset->setState($state);
}
return $newAsset; return $newAsset;
} }
@ -218,11 +275,12 @@ sub paste {
my $i18n=WebGUI::International->new($session, 'Asset'); my $i18n=WebGUI::International->new($session, 'Asset');
$outputSub->(sprintf $i18n->get('pasting %s'), $pastedAsset->getTitle) if defined $outputSub; $outputSub->(sprintf $i18n->get('pasting %s'), $pastedAsset->getTitle) if defined $outputSub;
if ($self->getId eq $pastedAsset->get("parentId") || $pastedAsset->setParent($self)) { if ($self->getId eq $pastedAsset->get("parentId") || $pastedAsset->setParent($self)) {
$pastedAsset->publish(['clipboard','clipboard-limbo']); # Paste only clipboard items
$pastedAsset->updateHistory("pasted to parent ".$self->getId);
# Update lineage in search index. # Update lineage in search index.
my $assetIter = $pastedAsset->getLineageIterator(['self', 'descendants']); my $assetIter = $pastedAsset->getLineageIterator(
['self', 'descendants'], {
statesToInclude => ['clipboard','clipboard-limbo']
}
);
while ( 1 ) { while ( 1 ) {
my $asset; my $asset;
eval { $asset = $assetIter->() }; eval { $asset = $assetIter->() };
@ -233,15 +291,67 @@ sub paste {
last unless $asset; last unless $asset;
$outputSub->(sprintf $i18n->get('indexing %s'), $pastedAsset->getTitle) if defined $outputSub; $outputSub->(sprintf $i18n->get('indexing %s'), $pastedAsset->getTitle) if defined $outputSub;
$asset->setState('published');
$asset->indexContent(); $asset->indexContent();
} }
$pastedAsset->updateHistory("pasted to parent ".$self->getId);
return 1; return 1;
} }
return 0; return 0;
} }
#-------------------------------------------------------------------
=head2 pasteInBackground ( )
WebGUI::Fork method called by www_pasteList
=cut
sub pasteInBackground {
my ($process, $args) = @_;
my $session = $process->session;
my $self = WebGUI::Asset->new($session, $args->{assetId});
my @roots = grep { $_ && $_->canEdit }
map { WebGUI::Asset->newPending($session, $_) }
@{ $args->{list} };
my @ids;
for my $r (@roots) {
my $these = $r->getLineage(
['self', 'descendants'], {
statesToInclude => ['clipboard', 'clipboard-limbo']
}
);
push(@ids, @$these);
}
my $tree = WebGUI::ProgressTree->new($session, \@ids);
my $patch = Monkey::Patch::patch_class(
'WebGUI::Asset', 'indexContent', sub {
my $indexContent = shift;
my $self = shift;
my $id = $self->getId;
$tree->focus($id);
my $ret = eval { $self->$indexContent(@_) };
my $e = $@;
if ($e) {
$tree->note($id, $e);
$tree->failure($id, 'Died');
}
else {
$tree->success($id);
}
$process->update(sub { $tree->json });
die $e if $e;
return $ret;
}
);
$self->paste($_->getId) for @roots;
}
#------------------------------------------------------------------- #-------------------------------------------------------------------
=head2 www_copy ( ) =head2 www_copy ( )
@ -255,89 +365,54 @@ If with children/descendants is selected, a progress bar will be rendered.
sub www_copy { sub www_copy {
my $self = shift; my $self = shift;
my $session = $self->session; my $session = $self->session;
my $http = $session->http;
my $redir = $self->getParent->getUrl;
return $session->privilege->insufficient unless $self->canEdit; return $session->privilege->insufficient unless $self->canEdit;
my $with = $session->form->get('with'); my $with = $session->form->get('with');
my %args;
if ($with eq 'children') { if ($with eq 'children') {
$self->_wwwCopyChildren; $args{childrenOnly} = 1;
} }
elsif ($with eq 'descendants') { elsif ($with ne 'descendants') {
$self->_wwwCopyDescendants; my $newAsset = $self->duplicate({
skipAutoCommitWorkflows => 1,
state => 'clipboard'
}
);
$newAsset->update({ title => $newAsset->getTitle . ' (copy)'});
my $result = WebGUI::VersionTag->autoCommitWorkingIfEnabled(
$session, {
allowComments => 1,
returnUrl => $redir,
}
);
$http->setRedirect($redir) unless $result eq 'redirect';
return 'redirect';
} }
else {
$self->_wwwCopySingle; my $tag = WebGUI::VersionTag->getWorking($session);
if ($tag->canAutoCommit) {
$args{commit} = 1;
unless ($session->setting->get('skipCommitComments')) {
$redir = $tag->autoCommitUrl($redir);
}
} }
}
#------------------------------------------------------------------- $args{assetId} = $self->getId;
sub _wwwCopyChildren { shift->_wwwCopyProgress(1) } my $process = WebGUI::Fork->start(
$session, 'WebGUI::Asset', 'copyInBackground', \%args
#------------------------------------------------------------------- );
sub _wwwCopyDescendants { shift->_wwwCopyProgress(0) } my $i18n = WebGUI::International->new($session, 'Asset');
my $pairs = $process->contentPairs(
#------------------------------------------------------------------- 'ProgressTree', {
sub _wwwCopyFinish { title => $i18n->get('Copy Assets'),
my ($self, $newAsset) = @_; icon => 'assets',
my $session = $self->session; proceed => $redir
my $i18n = WebGUI::International->new($session, 'Asset');
my $title = sprintf("%s (%s)", $self->getTitle, $i18n->get('copy'));
$newAsset->update({ title => $title });
$newAsset->cut;
my $result = WebGUI::VersionTag->autoCommitWorkingIfEnabled(
$session, {
allowComments => 1,
returnUrl => $self->getUrl,
} }
); );
my $redirect = $result eq 'redirect'; $http->setRedirect($self->getUrl($pairs));
$session->asset($self->getContainer) unless $redirect; return 'redirect';
return $redirect;
}
#-------------------------------------------------------------------
sub _wwwCopyProgress {
my ($self, $childrenOnly) = @_;
my $session = $self->session;
my $i18n = WebGUI::International->new($session, 'Asset');
# This could potentially time out, so we'll render a progress bar.
my $pb = WebGUI::ProgressBar->new($session);
my @stack;
return $pb->run(
title => $i18n->get('Copy Assets'),
icon => $session->url->extras('adminConsole/assets.gif'),
code => sub {
my $bar = shift;
my $newAsset = $self->duplicateBranch($childrenOnly);
$bar->update($i18n->get('cut'));
my $redirect = $self->_wwwCopyFinish($newAsset);
return $redirect ? $self->getUrl : $self->getContainer->getUrl;
},
wrap => {
'WebGUI::Asset::duplicateBranch' => sub {
my ($bar, $original, $asset, @args) = @_;
push(@stack, $asset->getTitle);
my $ret = $asset->$original(@args);
pop(@stack);
return $ret;
},
'WebGUI::Asset::duplicate' => sub {
my ($bar, $original, $asset, @args) = @_;
my $name = join '/', @stack, $asset->getTitle;
$bar->update($name);
return $asset->$original(@args);
},
}
);
}
#-------------------------------------------------------------------
sub _wwwCopySingle {
my $self = shift;
my $newAsset = $self->duplicate({skipAutoCommitWorkflows => 1});
my $redirect = $self->_wwwCopyFinish($newAsset);
return $redirect ? undef : $self->getContainer->www_view;
} }
#------------------------------------------------------------------- #-------------------------------------------------------------------
@ -363,9 +438,8 @@ sub www_copyList {
foreach my $assetId ($session->form->param("assetId")) { foreach my $assetId ($session->form->param("assetId")) {
my $asset = WebGUI::Asset->newByDynamicClass($session,$assetId); my $asset = WebGUI::Asset->newByDynamicClass($session,$assetId);
if ($asset->canEdit) { if ($asset->canEdit) {
my $newAsset = $asset->duplicate({skipAutoCommitWorkflows => 1}); my $newAsset = $asset->duplicate({skipAutoCommitWorkflows => 1, state => 'clipboard'});
$newAsset->update({ title=>$newAsset->getTitle.' (copy)'}); $newAsset->update({ title=>$newAsset->getTitle.' (copy)'});
$newAsset->cut;
} }
} }
if ($self->session->form->process("proceed") ne "") { if ($self->session->form->process("proceed") ne "") {
@ -503,7 +577,7 @@ sub www_duplicateList {
foreach my $assetId ($session->form->param("assetId")) { foreach my $assetId ($session->form->param("assetId")) {
my $asset = WebGUI::Asset->newByDynamicClass($session,$assetId); my $asset = WebGUI::Asset->newByDynamicClass($session,$assetId);
if ($asset->canEdit) { if ($asset->canEdit) {
my $newAsset = $asset->duplicate({skipAutoCommitWorkflows => 1, }); my $newAsset = $asset->duplicate({skipAutoCommitWorkflows => 1});
$newAsset->update({ title=>$newAsset->getTitle.' (copy)'}); $newAsset->update({ title=>$newAsset->getTitle.' (copy)'});
} }
} }
@ -657,25 +731,29 @@ sub www_pasteList {
my $self = shift; my $self = shift;
my $session = $self->session; my $session = $self->session;
return $session->privilege->insufficient() unless $self->canEdit && $session->form->validToken; return $session->privilege->insufficient() unless $self->canEdit && $session->form->validToken;
my $form = $session->form;
my $pb = WebGUI::ProgressBar->new($session);
##Need to store the list of assetIds for the status subroutine
my @assetIds = $form->param('assetId');
##Need to set the URL that should be displayed when it is done
my $i18n = WebGUI::International->new($session, 'Asset');
$pb->start($i18n->get('Paste Assets'), $session->url->extras('adminConsole/assets.gif'));
ASSET: foreach my $clipId (@assetIds) {
next ASSET unless $clipId;
my $pasteAsset = WebGUI::Asset->newPending($session, $clipId);
if (! $pasteAsset && $pasteAsset->canEdit) {
$pb->update(sprintf $i18n->get('skipping %s'), $pasteAsset->getTitle);
next ASSET;
}
$self->paste($clipId, sub {$pb->update(@_);});
}
return $pb->finish( ($form->param('proceed') eq 'manageAssets') ? $self->getUrl('op=assetManager') : $self->getUrl );
}
my $form = $session->form;
my $process = WebGUI::Fork->start(
$session, 'WebGUI::Asset', 'pasteInBackground', {
assetId => $self->getId,
list => [ $form->get('assetId') ],
}
);
my $redir = $self->getUrl(
($form->get('proceed') eq 'manageAssets') ? 'op=assetManager' : ()
);
my $i18n = WebGUI::International->new($session, 'Asset');
my $pairs = $process->contentPairs(
'ProgressTree', {
title => $i18n->get('Paste Assets'),
icon => 'assets',
proceed => $redir,
}
);
$session->http->setRedirect($self->getUrl($pairs));
return 'redirect';
}
1; 1;

View file

@ -24,6 +24,7 @@ use WebGUI::Utility ();
use WebGUI::Session; use WebGUI::Session;
use URI::URL; use URI::URL;
use Scope::Guard qw(guard); use Scope::Guard qw(guard);
use WebGUI::ProgressTree;
=head1 NAME =head1 NAME
@ -400,7 +401,7 @@ sub exportBranch {
close $handle; close $handle;
$cs->var->end; $cs->var->end;
$cs->close(); $cs->close();
$asset->$report('collateral notes', $output); $asset->$report('collateral notes', $output) if $output;
}; };
my $path = $asset->exportGetUrlAsPath; my $path = $asset->exportGetUrlAsPath;
eval { $asset->exportAssetCollateral($path, $options, $cs) }; eval { $asset->exportAssetCollateral($path, $options, $cs) };
@ -666,48 +667,33 @@ specified asset and keeps a json structure as the status.
=cut =cut
sub exportInFork { sub exportInFork {
my ($process, $args) = @_; my ( $process, $args ) = @_;
my $self = WebGUI::Asset->new($process->session, delete $args->{assetId}); my $session = $process->session;
my $self = WebGUI::Asset->new( $session, delete $args->{assetId} );
$args->{indexFileName} = delete $args->{index}; $args->{indexFileName} = delete $args->{index};
my %flat; my $assetIds = $self->exportGetDescendants( undef, $args->{depth} );
my $tree = WebGUI::ProgressTree->new( $session, $assetIds );
my $hashify; $hashify = sub {
my ($asset, $depth) = @_;
return if $depth < 1;
my $hash = { url => $asset->getUrl };
my $children = $asset->getLineage(['children'], { returnObjects => 1 });
$hash->{children} = [ map { $hashify->($_, $depth - 1) } @$children ];
$flat{$asset->getId} = $hash;
return $hash;
};
my $tree = $hashify->($self, $args->{depth});
my $last = $tree;
my %reports = ( my %reports = (
'bad user privileges' => sub { shift->{badUserPrivileges} = 1 }, 'done' => sub { $tree->success(shift) },
'not exportable' => sub { shift->{notExportable} = 1 }, 'exporting page' => sub { $tree->focus(shift) },
'done' => sub { shift->{done} = 1 }, 'collateral notes' => sub { $tree->note(@_) },
'exporting page' => sub { 'bad user privileges' => sub {
my $hash = shift; $tree->failure( shift, 'Bad User Privileges' );
$hash->{current} = 1;
delete $last->{current};
$last = $hash;
}, },
'collateral notes' => sub { 'not exportable' => sub {
my ($hash, $text) = @_; $tree->failure( shift, 'Not Exportable' );
$hash->{collateralNotes} = $text if $text;
}, },
); );
$args->{report} = sub { $args->{report} = sub {
my ($asset, $key, @args) = @_; my ( $asset, $key, @args ) = @_;
my $code = $reports{$key}; my $code = $reports{$key};
my $hash = $flat{$asset->getId}; $code->( $asset->getId, @args );
$code->($hash, @args); $process->update( sub { $tree->json } );
$process->update(sub { JSON::encode_json($tree) });
}; };
$self->exportAsHtml($args); $self->exportAsHtml($args);
delete $last->{current}; $tree->focus(undef);
$process->update(JSON::encode_json($tree)); $process->update( $tree->json );
} } ## end sub exportInFork
#------------------------------------------------------------------- #-------------------------------------------------------------------
@ -1036,7 +1022,12 @@ sub www_exportStatus {
} }
); );
$process->setGroup(13); $process->setGroup(13);
my $pairs = $process->contentPairs('AssetExport'); my $i18n = WebGUI::International->new( $session, 'Asset' );
my $pairs = $process->contentPairs('ProgressTree', {
icon => 'assets',
title => $i18n->get('Page Export Status'),
}
);
$session->http->setRedirect($self->getUrl($pairs)); $session->http->setRedirect($self->getUrl($pairs));
return 'redirect'; return 'redirect';
} }

View file

@ -88,41 +88,59 @@ sub canView {
my $user = shift || $session->user; my $user = shift || $session->user;
$user = WebGUI::User->new( $session, $user ) $user = WebGUI::User->new( $session, $user )
unless eval { $user->isa('WebGUI::User') }; unless eval { $user->isa('WebGUI::User') };
return 1 if $user->isAdmin; return
return $user->isInGroup( $self->getGroupId ); $user->isAdmin
|| $user->userId eq $self->getUserId
|| $user->isInGroup( $self->getGroupId );
} }
#------------------------------------------------------------------- #-------------------------------------------------------------------
=head2 contentPairs ($module, $pid) =head2 contentPairs ($module, $pid, $extra)
Returns a bit of query string useful for redirecting to a Returns a bit of query string useful for redirecting to a
WebGUI::Operation::Fork plugin. $module should be the bit that comes after WebGUI::Operation::Fork plugin. $module should be the bit that comes after
WebGUI::Operation::Fork, e.g. $process->contentPairs('Foo') should return WebGUI::Operation::Fork, e.g. $process->contentPairs('Foo') should return
something like "op=fork;module=Foo;pid=adlfjafo87ad9f78a7", which will something like "op=fork;module=Foo;pid=adlfjafo87ad9f78a7", which will
get dispatched to WebGUI::Operation::Fork::Foo::handler($process) get dispatched to WebGUI::Operation::Fork::Foo::handler($process).
$extra is an optional hashref that will add further parameters onto the list
of pairs, e.g. { foo => 'bar' } becomes ';foo=bar'
=cut =cut
sub contentPairs { sub contentPairs {
my ( $self, $module ) = @_; my ( $self, $module, $extra ) = @_;
my $pid = $self->getId; my $url = $self->session->url;
return "op=fork;module=$module;pid=$pid"; my $pid = $self->getId;
} my %params = (
op => 'fork',
module => $module,
pid => $self->getId,
$extra ? %$extra : ()
);
return join(
';',
map {
my $k = $_;
join( '=', map { $url->escape($_) } ( $k, $params{$k} ) );
} keys %params
);
} ## end sub contentPairs
#----------------------------------------------------------------- #-----------------------------------------------------------------
=head2 create ( ) =head2 create ( )
Internal class method. Creates a new Fork object and inserts a Internal class method. Creates a new Fork object inserts it into the db.
blank row of data into the db.
=cut =cut
sub create { sub create {
my ( $class, $session ) = @_; my ( $class, $session ) = @_;
my $id = $session->id->generate; my $id = $session->id->generate;
$session->db->setRow( $class->tableName, 'id', {}, $id ); my %data = ( userId => $session->user->userId );
$session->db->setRow( $class->tableName, 'id', \%data, $id );
bless { session => $session, id => $id }, $class; bless { session => $session, id => $id }, $class;
} }
@ -275,9 +293,9 @@ sub forkAndExec {
Get data from the database record for this process (returned as a simple list, Get data from the database record for this process (returned as a simple list,
not an arrayref). Valid keys are: id, status, error, startTime, endTime, not an arrayref). Valid keys are: id, status, error, startTime, endTime,
finished, groupId. They all have more specific accessors, but you can use finished, groupId, userId. They all have more specific accessors, but you can
this to get several at once if you're very careful. You should probably use use this to get several at once if you're very careful. You should probably
the accessors, though, since some of them have extra logic. use the accessors, though, since some of them have extra logic.
=cut =cut
@ -356,6 +374,16 @@ sub getStatus {
#----------------------------------------------------------------- #-----------------------------------------------------------------
=head2 getUserId
Returns the userId of the user who initiated this Fork.
=cut
sub getUserId { $_[0]->get('userId') }
#-----------------------------------------------------------------
=head2 init ( ) =head2 init ( )
Spawn a master process from which Forks will fork(). The intent Spawn a master process from which Forks will fork(). The intent

View file

@ -1,4 +1,4 @@
package WebGUI::Fork::AssetExport; package WebGUI::Fork::ProgressTree;
=head1 LEGAL =head1 LEGAL
@ -19,12 +19,12 @@ use warnings;
=head1 NAME =head1 NAME
WebGUI::Fork::AssetExport WebGUI::Fork::ProgressTree
=head1 DESCRIPTION =head1 DESCRIPTION
Renders an admin console page that polls ::Status to draw a friendly graphical Renders an admin console page that polls ::Status to draw a friendly graphical
representation of how an export is coming along. representation of how progress on a tree of assets is coming along.
=head1 SUBROUTINES =head1 SUBROUTINES
@ -33,77 +33,99 @@ These subroutines are available from this package:
=cut =cut
use Template; use Template;
use HTML::Entities;
use JSON;
my $template = <<'TEMPLATE'; my $template = <<'TEMPLATE';
<p> <p>
Currently exporting <span id='current'></span> <div id='meter'>
<div id='meter-bar'>
<div id='meter-text'></div>
</div>
</div>
Current asset: <span id='focus'></span>
(<span id='finished'></span>/<span id='total'></span>).<br /> (<span id='finished'></span>/<span id='total'></span>).<br />
<span id='elapsed'></span> seconds elapsed. <span id='elapsed'></span> seconds elapsed.
</p>
<ul id='tree'></ul> <ul id='tree'></ul>
[% MACRO yui(file) BLOCK %] [% MACRO inc(file) BLOCK %]<script src="$extras/$file"></script>[% END %]
<script src="$extras/yui/build/$file"></script> [% MACRO yui(file) BLOCK %][% inc("yui/build/$file") %][% END %]
[% END %]
[% yui("yahoo/yahoo-min.js") %] [% yui("yahoo/yahoo-min.js") %]
[% yui("json/json-min.js") %] [% yui("json/json-min.js") %]
[% yui("event/event-min.js") %] [% yui("event/event-min.js") %]
[% yui("connection/connection_core-min.js") %] [% yui("connection/connection_core-min.js") %]
[% inc("underscore/underscore-min.js") %]
<script> <script>
(function (statusUrl) { (function (params) {
var JSON = YAHOO.lang.JSON; var JSON = YAHOO.lang.JSON, statusUrl = params.statusUrl;
function finish() {
var redir = params.redirect;
if (redir) {
setTimeout(function() {
// The idea here is to only allow local redirects
var loc = window.location;
loc.href = loc.protocol + '//' + loc.host + redir;
}, 1000);
}
}
function error(msg) { function error(msg) {
alert(msg); alert(msg);
} }
function setHtml(id, html) {
document.getElementById(id).innerHTML = html;
}
function draw(data) { function draw(data) {
var ul, old, finished = 0, total = 0, current; var tree, finished = 0, total = 0, focus, pct;
function recurse(asset, node) { function recurse(asset, node) {
var li = document.createElement('li'), txt, notes, ul, i; var li = document.createElement('li'), txt, notes, ul, i;
total += 1; total += 1;
txt = asset.url; txt = asset.url;
if (asset.current) { if (asset.focus) {
li.className += 'current'; li.className += 'focus';
current = asset.url; focus = asset.url;
} }
else if (asset.badUserPrivileges) { else if (asset.failure) {
li.className = 'error'; li.className = 'failure';
txt += ' (bad user privileges)'; txt += ' (' + asset.failure + ')';
finished += 1; finished += 1;
} }
else if (asset.notExportable) { else if (asset.success) {
li.className = 'error'; li.className = 'success';
txt += ' (not exportable)';
finished += 1;
}
else if (asset.done) {
li.className = 'done';
finished += 1; finished += 1;
} }
li.appendChild(document.createTextNode(txt)); li.appendChild(document.createTextNode(txt));
if (asset.collateralNotes) { if (notes = asset.notes) {
notes = document.createElement('p'); _.each(notes, function (note) {
notes.innerHTML = asset.collateralNotes; var p = document.createElement('p');
li.appendChild(notes); p.innerHTML = note;
li.appendChild(p);
});
} }
if (asset.children) { if (asset.children) {
ul = document.createElement('ul'); ul = document.createElement('ul');
for (i = 0; i < asset.children.length; i += 1) { _.each(asset.children, function (child) {
recurse(asset.children[i], ul); recurse(child, ul);
li.appendChild(ul); });
} li.appendChild(ul);
} }
node.appendChild(li); node.appendChild(li);
} }
ul = document.createElement('ul'); tree = document.getElementById('tree');
old = document.getElementById('tree'); tree.innerHTML = '';
ul.id = old.id; _.each(JSON.parse(data.status), function (root) {
recurse(JSON.parse(data.status), ul); recurse(root, tree);
old.parentNode.replaceChild(ul, old); });
document.getElementById('total').innerHTML = total; pct = Math.floor((finished/total)*100) + '%';
document.getElementById('finished').innerHTML = finished;
document.getElementById('current').innerHTML = current || 'nothing'; setHtml('meter-text', pct);
document.getElementById('elapsed').innerHTML = data.elapsed; document.getElementById('meter-bar').style.width = pct;
setHtml('total', total);
setHtml('finished', finished);
setHtml('focus', focus || 'nothing');
setHtml('elapsed', data.elapsed);
} }
function fetch() { function fetch() {
var callback = { var callback = {
@ -119,6 +141,7 @@ Currently exporting <span id='current'></span>
} }
else if (data.finished) { else if (data.finished) {
draw(data); draw(data);
finish();
} }
else { else {
draw(data); draw(data);
@ -132,16 +155,21 @@ Currently exporting <span id='current'></span>
YAHOO.util.Connect.asyncRequest('GET', statusUrl, callback, null); YAHOO.util.Connect.asyncRequest('GET', statusUrl, callback, null);
} }
YAHOO.util.Event.onDOMReady(fetch); YAHOO.util.Event.onDOMReady(fetch);
}("$statusUrl")); }([% params %]));
</script> </script>
TEMPLATE TEMPLATE
my $stylesheet = <<'STYLESHEET'; my $stylesheet = <<'STYLESHEET';
<style> <style>
#meter { border: thin solid black; position: relative }
#meter-bar { background-color: lime; font-size: 18pt;
height: 20pt; line-height: 20pt }
#meter-text { position: absolute; top: 0; left: 0; width: 100%;
text-align: center }
#tree li { color: black } #tree li { color: black }
#tree li.current { color: cyan } #tree li.focus { color: cyan }
#tree li.error { color: red } #tree li.failure { color: red }
#tree li.done { color: green } #tree li.success { color: green }
</style> </style>
STYLESHEET STYLESHEET
@ -157,17 +185,21 @@ sub handler {
my $process = shift; my $process = shift;
my $session = $process->session; my $session = $process->session;
my $url = $session->url; my $url = $session->url;
my $form = $session->form;
my $tt = Template->new( { INTERPOLATE => 1 } ); my $tt = Template->new( { INTERPOLATE => 1 } );
my %vars = ( my %vars = (
statusUrl => $url->page( $process->contentPairs('Status') ), params => JSON::encode_json( {
extras => $session->url->extras, statusUrl => $url->page( $process->contentPairs('Status') ),
redirect => scalar $form->get('proceed'),
}
),
extras => $url->extras,
); );
$tt->process( \$template, \%vars, \my $content ) or die $tt->error; $tt->process( \$template, \%vars, \my $content ) or die $tt->error;
my $console = WebGUI::AdminConsole->new( $process->session, 'assets' ); my $console = WebGUI::AdminConsole->new( $session, $form->get('icon') );
$session->style->setRawHeadTags($stylesheet); $session->style->setRawHeadTags($stylesheet);
my $i18n = WebGUI::International->new( $session, 'Asset' ); return $console->render( $content, encode_entities( $form->get('title') ) );
return $console->render( $content, $i18n->get('Page Export Status') );
} ## end sub handler } ## end sub handler
1; 1;

172
lib/WebGUI/ProgressTree.pm Normal file
View file

@ -0,0 +1,172 @@
package WebGUI::ProgressTree;
use warnings;
use strict;
=head1 NAME
WebGUI::ProgressTree
=head1 DESCRIPTION
Helper functions for maintaining a JSON represtentation of the progress of an
operation that modifies a tree of assets. See WebGUI::Fork::ProgressTree for a
status page that renders this.
=head1 SYNOPSIS
my $tree = WebGUI::ProgressTree->new($session, \@assetIds);
$tree->success($assetId);
$tree->failure($assetId, $reason);
$tree->note($assetId, 'something about this one...');
=head1 LEGAL
-------------------------------------------------------------------
WebGUI is Copyright 2001-2009 Plain Black Corporation.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
http://www.plainblack.com info@plainblack.com
-------------------------------------------------------------------
=head1 METHODS
=cut
#-------------------------------------------------------------------
=head2 new ($session, $assetIds)
Constructs new tree object for tracking the progress of $assetIds.
=cut
sub new {
my ( $class, $session, $assetIds ) = @_;
my $db = $session->db;
my $dbh = $db->dbh;
my $set = join( ',', map { $dbh->quote($_) } @$assetIds );
my $sql = qq{
SELECT a.assetId, a.parentId, d.url
FROM asset a INNER JOIN assetData d ON a.assetId = d.assetId
WHERE a.assetId IN ($set)
ORDER BY a.lineage ASC, d.revisionDate DESC
};
my $sth = $db->read($sql);
my ( %flat, @roots );
while ( my $asset = $sth->hashRef ) {
my ( $id, $parentId ) = delete @{$asset}{ 'assetId', 'parentId' };
# We'll get back multiple rows for each asset, but the first one is
# the latest. Skip the others.
next if $flat{$id};
$flat{$id} = $asset;
if ( my $parent = $flat{$parentId} ) {
push( @{ $parent->{children} }, $asset );
}
else {
push( @roots, $asset );
}
}
my $self = {
session => $session,
tree => \@roots,
flat => \%flat,
};
bless $self, $class;
} ## end sub new
#-------------------------------------------------------------------
=head2 success ($assetId)
Whatever we were doing to $assetId succeeded. Woohoo!
=cut
sub success {
my ( $self, $assetId ) = @_;
$self->{flat}->{$assetId}->{success} = 1;
}
#-------------------------------------------------------------------
=head2 failure ($assetId, $reason)
Whatever we were doing to $assetId didn't work for $reason. Aww.
=cut
sub failure {
my ( $self, $assetId, $reason ) = @_;
$self->{flat}->{$assetId}->{failure} = $reason;
}
#-------------------------------------------------------------------
=head2 note ($assetId, $note)
Add some extra text. WebGUI::Fork::ProgressTree displays these as paragraphs
under the node for this asset.
=cut
sub note {
my ( $self, $assetId, $note ) = @_;
push( @{ $self->{flat}->{$assetId}->{notes} }, $note );
}
#-------------------------------------------------------------------
=head2 focus ($assetId)
Make a note that this is the asset that we are currently doing something with.
=cut
sub focus {
my ( $self, $assetId ) = @_;
if ( my $last = delete $self->{last} ) {
delete $last->{focus};
}
if ($assetId) {
my $focus = $self->{last} = $self->{flat}->{$assetId};
$focus->{focus} = 1;
}
}
#-------------------------------------------------------------------
=head2 tree
A hashy representation of the status of this tree of assets.
=cut
sub tree { $_[0]->{tree} }
#-------------------------------------------------------------------
=head2 json
$self->tree encoded as json.
=cut
sub json { JSON::encode_json( $_[0]->tree ) }
#-------------------------------------------------------------------
=head2 session
The WebGUI::Session this progress tree is associated with.
=cut
sub session { $_[0]->{session} }
1;

View file

@ -37,6 +37,23 @@ These methods are available from this class:
=cut =cut
#-------------------------------------------------------------------
=head2 autoCommitUrl ( $base )
Returns the url autoCommitWorkingIfEnabled would redirect to if it were going
to.
=cut
sub autoCommitUrl {
my $self = shift;
my $session = $self->session;
my $url = $session->url;
my $base = shift || $url->page;
my $id = $self->getId;
return $url->append($base, "op=commitVersionTag;tagId=$id");
}
#------------------------------------------------------------------- #-------------------------------------------------------------------
@ -75,25 +92,13 @@ sub autoCommitWorkingIfEnabled {
return undef return undef
unless $versionTag; unless $versionTag;
#Auto commit is no longer determined from autoRequestCommit if ($options->{override} || $versionTag->canAutoCommit) {
# auto commit assets
# save and commit button and site wide auto commit work the same
# Do not auto commit if tag is system wide tag or tag belongs to someone else
if (
$options->{override}
|| ( $class->getVersionTagMode($session) eq q{autoCommit}
&& ! $versionTag->get(q{isSiteWide})
&& $versionTag->get(q{createdBy}) eq $session->user()->userId()
)
) {
if ($session->setting->get("skipCommitComments") || !$options->{allowComments}) { if ($session->setting->get("skipCommitComments") || !$options->{allowComments}) {
$versionTag->requestCommit; $versionTag->requestCommit;
return 'commit'; return 'commit';
} }
else { else {
my $url = $options->{returnUrl} || $session->url->page; my $url = $versionTag->autoCommitUrl($options->{returnUrl});
$url = $session->url->append($url, "op=commitVersionTag;tagId=" . $versionTag->getId);
$session->http->setRedirect($url); $session->http->setRedirect($url);
return 'redirect'; return 'redirect';
} }
@ -103,6 +108,24 @@ sub autoCommitWorkingIfEnabled {
#------------------------------------------------------------------- #-------------------------------------------------------------------
=head2 canAutoCommit
Returns true if we would autocommit this tag without an override.
=cut
sub canAutoCommit {
my $self = shift;
my $session = $self->session;
my $class = ref $self;
my $mode = $class->getVersionTagMode($session);
return $mode eq 'autoCommit'
&& !$self->get('isSiteWide')
&& $self->get('createdBy') eq $session->user->userId;
}
#-------------------------------------------------------------------
=head2 clearWorking ( ) =head2 clearWorking ( )
Makes it so this tag is no longer the working tag for any user. Makes it so this tag is no longer the working tag for any user.

View file

@ -145,6 +145,7 @@ checkModule("CHI", "0.34" );
checkModule('IO::Socket::SSL', ); checkModule('IO::Socket::SSL', );
checkModule('Net::Twitter', "3.13006" ); checkModule('Net::Twitter', "3.13006" );
checkModule('PerlIO::eol', "0.14" ); checkModule('PerlIO::eol', "0.14" );
checkModule('Monkey::Patch', '0.3' );
failAndExit("Required modules are missing, running no more checks.") if $missingModule; failAndExit("Required modules are missing, running no more checks.") if $missingModule;