diff --git a/lib/WebGUI/Admin.pm b/lib/WebGUI/Admin.pm index 526eff011..97236bc7c 100644 --- a/lib/WebGUI/Admin.pm +++ b/lib/WebGUI/Admin.pm @@ -24,12 +24,12 @@ Admin Plugins do the administrative tasks. use Moose; use JSON qw( from_json to_json ); -use namespace::autoclean; use Scalar::Util; use Search::QueryParser; use WebGUI::Pluggable; use WebGUI::Macro; use WebGUI::Search; +use WebGUI::Fork; has 'session' => ( is => 'ro', @@ -740,6 +740,55 @@ sub www_searchAssets { return to_json( $assetInfo ); } +#---------------------------------------------------------------------------- + +=head2 www_updateAsset( ) + +Update an asset. The assetId is given in a query parameter. The updated +properties are given as a JSON string in the POST body. + +The actual update will happen in a forked process, due to rank being a long +update. + +=cut + +sub www_updateAsset { + my ( $self ) = @_; + my $session = $self->session; + my $assetId = $session->form->get( 'assetId' ); + my $asset = eval { WebGUI::Asset->newById( $session, $assetId ); }; + if ( $@ || !$asset ) { + return to_json( { error => "Could not find asset" } ); + } + + my $props = eval { JSON->new->decode( $session->request->raw_body ); }; + if ( $@ ) { + return to_json( { error => "Unable to decode JSON body" } ); + } + + my $fork = WebGUI::Fork->start( + $session, __PACKAGE__, 'updateAsset', { assetId => $assetId, properties => $props }, + ); + + return to_json( { forkId => $fork->getId } ); +} + +sub updateAsset { + my ( $process, $args ) = @_; + my $session = $process->session; + my $props = $args->{properties}; + my $assetId = $args->{assetId}; + my $asset = WebGUI::Asset->newById( $session, $assetId ); + + # Update rank specially + if ( my $rank = delete $props->{rank} ) { + $asset->setRank( $rank ); + } + + # Update other properties + # TODO: Do we add a revision? or do they request a revision? +} + #---------------------------------------------------------------------- =head2 www_view ( session ) @@ -783,6 +832,7 @@ sub www_view { $style->setCss( $url->extras('yui/build/container/assets/skins/sam/container.css')); $style->setCss( $url->extras('yui/build/autocomplete/assets/skins/sam/autocomplete.css')); $style->setCss( $url->extras('yui/build/menu/assets/skins/sam/menu.css')); + $style->setCss( $url->extras('yui/build/progressbar/assets/skins/sam/progressbar.css') ); $style->setCss( $url->extras('admin/admin.css')); $style->setScript($url->extras('yui/build/yahoo-dom-event/yahoo-dom-event.js')); $style->setScript($url->extras('yui/build/utilities/utilities.js')); @@ -799,7 +849,9 @@ sub www_view { $style->setScript($url->extras('yui/build/button/button-min.js')); $style->setScript($url->extras('yui/build/autocomplete/autocomplete-min.js')); $style->setScript( $url->extras( 'yui/build/json/json-min.js' ) ); + $style->setScript( $url->extras( 'yui/build/progressbar/progressbar-min.js' ) ); $style->setScript( $url->extras( 'yui-webgui/build/i18n/i18n.js' ) ); + $style->setScript( $url->extras( 'Fork/poll.js' ) ); $style->setScript($url->extras('admin/admin.js')); # Use the template in our __DATA__ block diff --git a/lib/WebGUI/Asset.pm b/lib/WebGUI/Asset.pm index 9bff800f0..e01d147d4 100644 --- a/lib/WebGUI/Asset.pm +++ b/lib/WebGUI/Asset.pm @@ -1208,6 +1208,10 @@ sub getHelpers { className => 'WebGUI::AssetHelper::CreateShortcut', label => 'Create Shortcut', }, + duplicate => { + className => 'WebGUI::AssetHelper::Duplicate', + label => 'Duplicate', + }, cut => { className => 'WebGUI::AssetHelper::Cut', label => 'Cut', @@ -1232,6 +1236,10 @@ sub getHelpers { className => 'WebGUI::AssetHelper::Lock', label => 'Lock', }, + delete => { + className => 'WebGUI::AssetHelper::Delete', + label => 'Delete', + }, }; # Merge additional helpers for this class from config diff --git a/lib/WebGUI/AssetClipboard.pm b/lib/WebGUI/AssetClipboard.pm index 71168381c..4070ac948 100644 --- a/lib/WebGUI/AssetClipboard.pm +++ b/lib/WebGUI/AssetClipboard.pm @@ -51,59 +51,6 @@ sub canPaste { return $class->validParent($self->session); } -#------------------------------------------------------------------- - -=head2 copyInFork ( $process, $args ) - -WebGUI::Fork method called by www_copy - -=cut - -sub copyInFork { - my ($process, $args) = @_; - my $session = $process->session; - my $asset = WebGUI::Asset->newById($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); - $process->update(sub { $tree->json }); - my $patch = Monkey::Patch::patch_class( - 'WebGUI::Asset', 'duplicate', sub { - my $duplicate = shift; - my $self = shift; - my $id = $self->getId; - $tree->focus($id); - 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 ( ) diff --git a/lib/WebGUI/AssetHelper.pm b/lib/WebGUI/AssetHelper.pm index 15a8fe5fd..0d6aba8ad 100644 --- a/lib/WebGUI/AssetHelper.pm +++ b/lib/WebGUI/AssetHelper.pm @@ -94,6 +94,11 @@ A URL. Will open a tab in the Admin Console. Anything returned by the URL will A URL. Puts new content into the View tab from the requested URL. +=head4 forkId + +The ID for a WebGUI::Fork process. The Admin Console can then open a progress +dialog to poll for the progress of the forked process. + =head4 scriptFile Loads the requested JavaScript file, referenced by URL. diff --git a/lib/WebGUI/AssetHelper/Copy.pm b/lib/WebGUI/AssetHelper/Copy.pm index f5f894737..55af61e61 100644 --- a/lib/WebGUI/AssetHelper/Copy.pm +++ b/lib/WebGUI/AssetHelper/Copy.pm @@ -3,6 +3,7 @@ package WebGUI::AssetHelper::Copy; use strict; use Class::C3; use base qw/WebGUI::AssetHelper/; +use Scalar::Util qw( blessed ); =head1 LEGAL @@ -34,69 +35,54 @@ These methods are available from this class: =head2 process ( $asset ) -Open a progress dialog for the copy operation +Fork the copy operation =cut sub process { my ($self, $asset) = @_; + my $session = $self->session; + + # Should we autocommit? + my $commit = $session->setting->get('versionTagMode') eq 'autoCommit'; + + # Fork the copy. Forking makes sure it won't get interrupted + my $fork = WebGUI::Fork->start( + $session, blessed( $self ), 'copy', { assetId => $asset->getId, commit => $commit }, + ); return { - openDialog => '?op=assetHelper;helperId=' . $self->id . ';method=copy;assetId=' . $asset->getId, + forkId => $fork->getId, }; } -#---------------------------------------------------------------------------- +#------------------------------------------------------------------- -=head2 www_copy ( $asset ) +=head2 copy ( $process, $args ) -Perform the copy operation, showing the progress. +Perform the copy stuff in a forked process =cut -sub www_copy { - my ( $self, $asset ) = @_; - my $session = $asset->session; - my $i18n = WebGUI::International->new($session, 'Asset'); +sub copy { + my ($process, $args) = @_; + my $session = $process->session; + my $asset = WebGUI::Asset->newById($session, $args->{assetId}); + my $tree = WebGUI::ProgressTree->new($session, [ $asset->getId ] ); + $process->update(sub { $tree->json }); + my $newAsset = $asset->duplicate({ state => "clipboard" }); - return $session->response->stream( sub { - my ( $session ) = @_; - my $pb = WebGUI::ProgressBar->new($session); - my @stack; + # If we aren't committing, add to a tag + if ( !$args->{commit} ) { + $newAsset->update({ + status => "pending", + tagId => WebGUI::VersionTag->getWorking( $session )->getId, + }); + } + $newAsset->update({ title => $newAsset->getTitle . ' (copy)'}); - return $pb->run( - admin => 1, - total => 2, - title => $i18n->get('Copy Assets'), - icon => $session->url->extras('adminConsole/assets.gif'), - code => sub { - my $bar = shift; - my $newAsset = $asset->duplicate; - $bar->update($i18n->get('cut')); - my $title = sprintf("%s (%s)", $asset->getTitle, $i18n->get('copy')); - $newAsset->update({ title => $title }); - $newAsset->cut; - my $result = WebGUI::VersionTag->autoCommitWorkingIfEnabled( - $session, { - allowComments => 1, - returnUrl => $asset->getUrl, - } - ); - if ( $result eq 'redirect' ) { - return $asset->getUrl; - } - return { message => 'Your asset is now copied!' }; - }, - wrap => { - 'WebGUI::Asset::duplicate' => sub { - my ($bar, $original, $asset, @args) = @_; - my $name = join '/', @stack, $asset->getTitle; - $bar->update($name); - return $asset->$original(@args); - }, - } - ); - } ); + $tree->success($asset->getId); + $process->update(sub { $tree->json }); } 1; diff --git a/lib/WebGUI/AssetHelper/Cut.pm b/lib/WebGUI/AssetHelper/Cut.pm index 10c8e7533..cbbf8ad5a 100644 --- a/lib/WebGUI/AssetHelper/Cut.pm +++ b/lib/WebGUI/AssetHelper/Cut.pm @@ -3,6 +3,8 @@ package WebGUI::AssetHelper::Cut; use strict; use Class::C3; use base qw/WebGUI::AssetHelper/; +use Scalar::Util qw( blessed ); +use Monkey::Patch; =head1 LEGAL @@ -51,58 +53,47 @@ sub process { return { error => $i18n->get('41'), }; } + # Fork the cut. Forking makes sure it won't get interrupted + my $fork = WebGUI::Fork->start( + $session, blessed( $self ), 'cut', { assetId => $asset->getId }, + ); + return { - openDialog => '?op=assetHelper;helperId=' . $self->id . ';method=cut;assetId=' . $asset->getId, + forkId => $fork->getId, }; } #---------------------------------------------------------------------------- -=head2 www_cut ( $asset ) +=head2 cut ( process, args ) -Show the progress bar while cutting the asset. +Handle the actual cutting in the forked process. =cut -sub www_cut { - my ( $self, $asset ) = @_; - my $session = $asset->session; - my $i18n = WebGUI::International->new($session, 'Asset'); +sub cut { + my ( $process, $args ) = @_; + my $asset = WebGUI::Asset->newById( $process->session, $args->{assetId} ); - return $session->response->stream( sub { - my ( $session ) = @_; - my $pb = WebGUI::ProgressBar->new($session); - my @stack; + # All the Assets we need to work on + my $assetIds = $asset->getLineage( ['self','descendants'] ); - return $pb->run( - admin => 1, - title => $i18n->get('Copy Assets'), - icon => $session->url->extras('adminConsole/assets.gif'), - code => sub { - my $bar = shift; - $bar->update( "Preparing... (i18n)" ); - $bar->total( $asset->getDescendantCount + 2 ); - $bar->update( "Cutting... (i18n)" ); - my $success = $asset->cut(); - if (! $success) { - return { error => $i18n->get('41', 'WebGUI'), }; - } - return { message => "Your asset is cut!" }; - }, - wrap => { - 'WebGUI::Asset::getLineageIterator' => sub { - my ($bar, $orig, $asset, @args) = @_; - $bar->update("Updating descendants... (i18n)"); - return $asset->$orig(@args); - }, - 'WebGUI::Asset::updateHistory' => sub { - my ( $bar, $orig, $asset, @args ) = @_; - $bar->update( "Updating " . $asset->getTitle ); - return $asset->$orig(@args); - }, - }, - ); - } ); + # Build a tree and update process status + my $tree = WebGUI::ProgressTree->new( $process->session, $assetIds ); + $process->update( sub { $tree->json } ); + + # Monkeypatch a sub to get a status update + my $patch = Monkey::Patch::patch_class( + 'WebGUI::Asset', 'updateHistory', sub { + my ( $orig, $self, @args ) = @_; + $tree->success( $self->assetId ); + $process->update( sub { $tree->json } ); + $self->$orig( @args ); + } + ); + + # Do the actual work + $asset->cut; } 1; diff --git a/lib/WebGUI/AssetHelper/Delete.pm b/lib/WebGUI/AssetHelper/Delete.pm new file mode 100644 index 000000000..0c75a9ffc --- /dev/null +++ b/lib/WebGUI/AssetHelper/Delete.pm @@ -0,0 +1,107 @@ +package WebGUI::AssetHelper::Delete; + +use strict; +use Class::C3; +use base qw/WebGUI::AssetHelper/; +use Scalar::Util qw( blessed ); + +=head1 LEGAL + + ------------------------------------------------------------------- + WebGUI is Deleteright 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 NAME + +Package WebGUI::AssetHelper::Delete + +=head1 DESCRIPTION + +Delete an Asset, and all descendants + +=head1 METHODS + +These methods are available from this class: + +=cut + +#------------------------------------------------------------------- + +=head2 process ( $asset ) + +Fork the Delete operation + +=cut + +sub process { + my ($self, $asset) = @_; + my $session = $self->session; + + my $i18n = WebGUI::International->new($session, 'WebGUI'); + if (! $asset->canEdit) { + return { error => $i18n->get('38'), }; + } + elsif ( $asset->get('isSystem') ) { + return { error => $i18n->get('41'), }; + } + + # Fork the Delete. Forking makes sure it won't get interrupted + my $fork = WebGUI::Fork->start( + $session, blessed( $self ), 'delete', { assetId => $asset->getId, }, + ); + + return { + forkId => $fork->getId, + }; +} + +#------------------------------------------------------------------- + +=head2 delete ( $process, $args ) + +Perform the delete stuff in a forked process + +=cut + +sub delete { + my ($process, $args) = @_; + my $session = $process->session; + my $asset = WebGUI::Asset->newById($session, $args->{assetId}); + + # Prepare a tree with all the ids + my $ids = + $asset->getLineage( + [ 'self', 'descendants' ], { + statesToInclude => [qw(published clipboard clipboard-limbo trash trash-limbo)], + statusToInclude => [qw(approved archived pending)], + } + ); + my $tree = WebGUI::ProgressTree->new( $session, $ids ); + $process->update(sub { $tree->json }); + + # Patch a sub to get a status update + my $patch = Monkey::Patch::patch_class( + 'WebGUI::Asset', + 'setState', + sub { + my ( $setState, $self, $state ) = @_; + my $id = $self->getId; + $tree->focus($id); + my $ret = $self->$setState($state); + $tree->success($id); + $process->update(sub { $tree->json }); + return $ret; + } + ); + + # Do the dirty deed, cheap + $asset->trash; +} + +1; diff --git a/lib/WebGUI/AssetHelper/Duplicate.pm b/lib/WebGUI/AssetHelper/Duplicate.pm new file mode 100644 index 000000000..a9f7e0ae3 --- /dev/null +++ b/lib/WebGUI/AssetHelper/Duplicate.pm @@ -0,0 +1,88 @@ +package WebGUI::AssetHelper::Duplicate; + +use strict; +use Class::C3; +use base qw/WebGUI::AssetHelper/; +use Scalar::Util qw( blessed ); + +=head1 LEGAL + + ------------------------------------------------------------------- + WebGUI is Duplicateright 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 NAME + +Package WebGUI::AssetHelper::Duplicate + +=head1 DESCRIPTION + +Duplicate an Asset, with no children. + +=head1 METHODS + +These methods are available from this class: + +=cut + +#------------------------------------------------------------------- + +=head2 process ( $asset ) + +Fork the duplicate operation + +=cut + +sub process { + my ($self, $asset) = @_; + my $session = $self->session; + + # Should we autocommit? + my $commit = $session->setting->get('versionTagMode') eq 'autoCommit'; + + # Fork the Duplicate. Forking makes sure it won't get interrupted + my $fork = WebGUI::Fork->start( + $session, blessed( $self ), 'duplicate', { assetId => $asset->getId, commit => $commit }, + ); + + return { + forkId => $fork->getId, + }; +} + +#------------------------------------------------------------------- + +=head2 duplicate ( $process, $args ) + +Perform the duplicate stuff in a forked process + +=cut + +sub duplicate { + my ($process, $args) = @_; + my $session = $process->session; + my $asset = WebGUI::Asset->newById($session, $args->{assetId}); + my $tree = WebGUI::ProgressTree->new($session, [ $asset->getId ] ); + $process->update(sub { $tree->json }); + my $newAsset = $asset->duplicate; + + # If we aren't committing, add to a tag + if ( !$args->{commit} ) { + $newAsset->update({ + status => "pending", + tagId => WebGUI::VersionTag->getWorking( $session )->getId, + }); + } + $newAsset->update({ title => $newAsset->getTitle . ' (Duplicate)'}); + + $tree->success($asset->getId); + $process->update(sub { $tree->json }); +} + +1; diff --git a/lib/WebGUI/AssetTrash.pm b/lib/WebGUI/AssetTrash.pm index e0585e074..21baff257 100644 --- a/lib/WebGUI/AssetTrash.pm +++ b/lib/WebGUI/AssetTrash.pm @@ -363,51 +363,6 @@ sub trash { return 1; } -#------------------------------------------------------------------- - -=head2 trashInFork - -WebGUI::Fork method called by www_deleteList and www_delete to move assets -into the trash. - -=cut - -sub trashInFork { - my ( $process, $list ) = @_; - my $session = $process->session; - my @roots = grep { $_->canEdit && $_->canEditIfLocked } - map { - eval { WebGUI::Asset->newPending( $session, $_ ) } - } @$list; - - my @ids = map { - my $list = $_->getLineage( - [ 'self', 'descendants' ], { - statesToInclude => [qw(published clipboard clipboard-limbo trash trash-limbo)], - statusToInclude => [qw(approved archived pending)], - } - ); - @$list; - } @roots; - - my $tree = WebGUI::ProgressTree->new( $session, \@ids ); - $process->update(sub { $tree->json }); - my $patch = Monkey::Patch::patch_class( - 'WebGUI::Asset', - 'setState', - sub { - my ( $setState, $self, $state ) = @_; - my $id = $self->getId; - $tree->focus($id); - my $ret = $self->$setState($state); - $tree->success($id); - $process->update(sub { $tree->json }); - return $ret; - } - ); - $_->trash() for @roots; -} ## end sub trashInFork - require WebGUI::Workflow::Activity::DeleteExportedFiles; sub _invokeWorkflowOnExportedFiles { my $self = shift; @@ -435,63 +390,6 @@ sub _invokeWorkflowOnExportedFiles { #------------------------------------------------------------------- -=head2 www_delete - -Moves self to trash in fork, redirects to Container or Parent if canEdit. -Otherwise returns AdminConsole rendered insufficient privilege. - -=cut - -sub www_delete { - my $self = shift; - return $self->session->privilege->insufficient() unless ($self->canEdit && $self->canEditIfLocked); - return $self->session->privilege->vitalComponent() if $self->get('isSystem'); - return $self->session->privilege->vitalComponent() if ($self->getId ~~ [$self->session->setting->get("defaultPage"), $self->session->setting->get("notFoundPage")]); - $self->trash; - my $asset = $self->getContainer; - if ($self->getId eq $asset->getId) { - $asset = $self->getParent; - } - $self->forkWithStatusPage({ - plugin => 'ProgressTree', - title => 'Delete Assets', - redirect => $asset->getUrl, - method => 'trashInFork', - args => [ $self->getId ], - } - ); -} - -#------------------------------------------------------------------- - -=head2 www_deleteList - -Checks to see if a valid CSRF token was received. If not, then it returns insufficient privilege. - -Moves list of assets to trash, checking each to see if the user canEdit, -and canEditIfLocked. Returns the user to manageTrash, or to the screen set -by the form variable C. - -=cut - -sub www_deleteList { - my $self = shift; - my $session = $self->session; - my $form = $session->form; - return $session->privilege->insufficient() unless $session->form->validToken; - my $method = $form->get('proceed') || 'manageTrash'; - $self->forkWithStatusPage({ - plugin => 'ProgressTree', - title => 'Delete Assets', - redirect => $self->getUrl("func=$method"), - method => 'trashInFork', - args => [ $form->get('assetId') ], - } - ); -} ## end sub www_deleteList - -#------------------------------------------------------------------- - =head2 www_manageTrash ( ) Returns an AdminConsole to deal with assets in the Trash. If user isn't in the Turn On Admin group, renders an insufficient privilege page. diff --git a/t/AssetHelper/Copy.t b/t/AssetHelper/Copy.t index f6267125a..a3a1852ab 100644 --- a/t/AssetHelper/Copy.t +++ b/t/AssetHelper/Copy.t @@ -30,12 +30,13 @@ my $session = WebGUI::Test->session; #---------------------------------------------------------------------------- # Tests -plan tests => 3; # Increment this number for each test you create +plan tests => 2; # Increment this number for each test you create #---------------------------------------------------------------------------- # put your tests here my $output; +$session->setting->set( "versionTagMode" => "autoCommit" ); my $helper = WebGUI::AssetHelper::Copy->new( id => 'copy', session => $session ); my $home = WebGUI::Asset->getDefault($session); my $root = WebGUI::Asset->getRoot($session); @@ -46,19 +47,14 @@ my $root = WebGUI::Asset->getRoot($session); cmp_deeply( $output, { - openDialog => all( - re('helperId=copy'), - re('method=copy'), - re('assetId=' . $home->getId ), - ), + forkId => re('[a-zA-Z0-9_-]{22}'), }, - 'AssetHelper/Copy opens a dialog for the copy method' + 'AssetHelper/Copy forks a process' ); } -my $mech = WebGUI::Test::Mechanize->new( config => WebGUI::Test->file ); -$mech->get_ok( '/?op=assetHelper;helperId=copy;method=copy;assetId=' . $home->getId ); - +WebGUI::Test->waitForAllForks; +$session->cache->clear; my $clippies = $root->getLineage(["descendants"], {statesToInclude => [qw{clipboard clipboard-limbo}], returnObjects => 1,}); is @{ $clippies }, 1, '... only copied 1 asset to the clipboard, no children'; addToCleanup(@{ $clippies }); diff --git a/t/AssetHelper/Cut.t b/t/AssetHelper/Cut.t index 27cc3c1c0..ea877635d 100644 --- a/t/AssetHelper/Cut.t +++ b/t/AssetHelper/Cut.t @@ -59,14 +59,12 @@ $output = $helper->process($safe_page); cmp_deeply( $output, { - openDialog => all( re('method=cut'), re('assetId=' . $safe_page->getId) ), + forkId => re(qr/[a-zA-Z0-9_-]{22}/), }, - 'AssetHelper/Cut opens a dialog' + 'AssetHelper/Cut forks a process' ); -my $mech = WebGUI::Test::Mechanize->new( config => WebGUI::Test->file ); -$mech->get_ok( $output->{ openDialog } ); -$mech->content_lacks( 'error', "Cut succeeded" ); +WebGUI::Test->waitForAllForks; $session->cache->clear; $safe_page = WebGUI::Asset->newById( $session, $safe_page->assetId ); diff --git a/t/AssetHelper/Delete.t b/t/AssetHelper/Delete.t new file mode 100644 index 000000000..708e5bcde --- /dev/null +++ b/t/AssetHelper/Delete.t @@ -0,0 +1,77 @@ +# vim:syntax=perl +#------------------------------------------------------------------- +# 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 +#------------------------------------------------------------------ + +# Write a little about what this script tests. +# +# + +use strict; +use Test::More; +use Test::Deep; +use WebGUI::Test; # Must use this before any other WebGUI modules +use WebGUI::Session; +use WebGUI::Asset; +use WebGUI::AssetHelper::Delete; +use WebGUI::Test::Mechanize; + +#---------------------------------------------------------------------------- +# Init +my $session = WebGUI::Test->session; + + +#---------------------------------------------------------------------------- +# Tests + +my $output; +my $helper = WebGUI::AssetHelper::Delete->new( id => 'Delete', session => $session ); +my $import = WebGUI::Asset->getImportNode($session); + +$session->user({userId => 1}); +$output = $helper->process($import); +cmp_deeply( + $output, + { + error => re('You do not have sufficient privileges'), + }, + 'AssetHelper/Delete checks for editing privileges' +); + +$session->user({userId => 3}); +$output = $helper->process($import); +cmp_deeply( + $output, + { + error => re('vital component'), + }, + 'AssetHelper/Delete checks for system pages' +); + +my $safe_page = $import->getFirstChild; +$output = $helper->process($safe_page); +cmp_deeply( + $output, + { + forkId => re(qr/[a-zA-Z0-9_-]{22}/), + }, + 'AssetHelper/Delete forks a process' +); + +WebGUI::Test->waitForAllForks; + +$session->cache->clear; +$safe_page = WebGUI::Asset->newById( $session, $safe_page->assetId ); +is $safe_page->state, 'trash', '... and the asset was really Deleted'; + +$safe_page->restore; + +done_testing(); + +#vim:ft=perl diff --git a/t/AssetHelper/Duplicate.t b/t/AssetHelper/Duplicate.t new file mode 100644 index 000000000..8059bc992 --- /dev/null +++ b/t/AssetHelper/Duplicate.t @@ -0,0 +1,61 @@ +# vim:syntax=perl +#------------------------------------------------------------------- +# WebGUI is Duplicateright 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 +#------------------------------------------------------------------ + +# Write a little about what this script tests. +# +# + +use strict; +use Test::More; +use Test::Deep; +use WebGUI::Test; # Must use this before any other WebGUI modules +use WebGUI::Session; +use WebGUI::Asset; +use WebGUI::AssetHelper::Duplicate; +use WebGUI::Test::Mechanize; + +#---------------------------------------------------------------------------- +# Init +my $session = WebGUI::Test->session; + + +#---------------------------------------------------------------------------- +# Tests + +plan tests => 2; # Increment this number for each test you create + +#---------------------------------------------------------------------------- +# put your tests here + +my $output; +$session->setting->set( "versionTagMode" => "autoCommit" ); +my $helper = WebGUI::AssetHelper::Duplicate->new( id => 'duplicate', session => $session ); +my $root = WebGUI::Test->asset; +my $test = $root->addChild( { className => 'WebGUI::Asset::Snippet' } ); + +{ + + $output = $helper->process($test); + cmp_deeply( + $output, + { + forkId => re('[a-zA-Z0-9_-]{22}'), + }, + 'AssetHelper/Duplicate forks a process' + ); +} + +WebGUI::Test->waitForAllForks; +$session->cache->clear; +my $children = $root->getLineage(["children"]); +is @{ $children }, 2, '... created a new asset'; + +#vim:ft=perl diff --git a/www/extras/admin/admin.js b/www/extras/admin/admin.js index 8f609f930..4a21867fb 100644 --- a/www/extras/admin/admin.js +++ b/www/extras/admin/admin.js @@ -533,7 +533,7 @@ WebGUI.Admin.prototype.requestUpdateCurrentVersionTag }; /** - * requestHelper( helperClass, assetId ) + * requestHelper( helperId, assetId ) * Request the Asset Helper for the given assetId */ WebGUI.Admin.prototype.requestHelper @@ -561,6 +561,7 @@ WebGUI.Admin.prototype.requestHelper * openDialog : Open a dialog with the given URL * openTab : Open a tab with the given URL * redirect : Redirect the View pane to the given URL + * forkId : The Helper forked a process, use the ID to get the status * scriptFile : Load a JS file * scriptFunc : Run a JS function. Used with scriptFile * scriptArgs : Arguments to scriptFunc. Used with scriptFile @@ -582,6 +583,9 @@ WebGUI.Admin.prototype.processPlugin else if ( resp.error ) { this.showInfoMessage( resp.error ); } + else if ( resp.forkId ) { + // Do nothing right now + } else { alert( "Unknown plugin response: " + YAHOO.lang.JSON.stringify(resp) ); } @@ -1376,11 +1380,15 @@ WebGUI.Admin.AssetTable.prototype.formatLockedBy WebGUI.Admin.AssetTable.prototype.formatRank = function ( elCell, oRecord, oColumn, orderNumber ) { var rank = oRecord.getData("lineage").match(/[1-9][0-9]{0,5}$/); - elCell.innerHTML = ''; - // TODO: Add onchange handler to select row + var input = document.createElement( 'input' ); + input.type = "text"; + input.id = oRecord.getData("assetId") + '_rank'; + input.name = input.id; + input.size = 3; + input.value = rank; + + YAHOO.util.Event.addListener( input, "change", function(){ this.selectRow(elCell); }, this, true ); + elCell.appendChild( input ); }; /** @@ -1640,7 +1648,7 @@ WebGUI.Admin.Tree this.btnDelete = new YAHOO.widget.Button( "treeDelete", { type : "button", label : window.admin.i18n.get('Asset','delete'), - onclick : { fn: this.trash, scope: this } + onclick : { fn: this.delete, scope: this } } ); this.btnCut = new YAHOO.widget.Button( "treeCut", { @@ -1672,23 +1680,281 @@ WebGUI.Admin.Tree YAHOO.lang.extend( WebGUI.Admin.Tree, WebGUI.Admin.AssetTable ); /** - * update( e ) - * Update the selected assets' rank + * runHelperForSelected( helperId ) + * Run the named asset helper for each selected asset + * Show the status of the task in a dialog box + */ +WebGUI.Admin.Tree.prototype.runHelperForSelected += function ( helperId, title ) { + var self = this; + var assetIds = this.getSelected(); + + // Open the dialog with two progress bars + var dialog = new YAHOO.widget.Panel( 'adminModalDialog', { + "width" : '350px', + fixedcenter : true, + constraintoviewport : true, + underlay : "shadow", + close : true, + visible : true, + draggable : false + } ); + dialog.setHeader( title ); + dialog.setBody( + '
0 / ' + assetIds.length + '
' + + '
' + ); + dialog.render( document.body ); + this.treeDialog = dialog; + + var pbQueueBar = new YAHOO.widget.ProgressBar({ + minValue : 0, + value : 0, + maxValue : assetIds.length, + width: '300px', + height: '30px', + anim: true + }); + pbQueueBar.render( 'pbQueue' ); + pbQueueBar.get('anim').duration = 0.5; + pbQueueBar.get('anim').method = YAHOO.util.Easing.easeOut; + var pbQueueStatus = document.getElementById( 'pbQueueStatus' ); + + var pbTaskBar = new YAHOO.widget.ProgressBar({ + minValue : 0, + value : 0, + maxValue : 1, + width: '300px', + height: '30px', + anim: true + }); + pbTaskBar.render( 'pbTask' ); + pbTaskBar.get('anim').duration = 0.5; + pbTaskBar.get('anim').method = YAHOO.util.Easing.easeOut; + + // Clean up when we're done + var finish = function () { + dialog.destroy(); + dialog = null; + self.admin.requestUpdateClipboard(); + self.admin.requestUpdateCurrentVersionTag(); + self.goto( self.admin.currentAssetDef.url ); + }; + + // Build a function to call the helper for the next asset + var callHelper = function( assetIds ) { + var assetId = assetIds.shift(); + + var callback = { + success : function (o) { + var resp = YAHOO.lang.JSON.parse( o.responseText ); + + if ( resp.error ) { + this.admin.processPlugin( resp ); + finish(); + } + else if ( resp.forkId ) { + // Wait until the helper is done, then call the next + YAHOO.WebGUI.Fork.poll({ + url : '?op=fork;pid=' + resp.forkId, + draw : function(data) { + pbTaskBar.set( 'maxValue', data.total ); + pbTaskBar.set( 'value', data.finished ); + }, + finish : function(){ + pbQueueBar.set( 'value', pbQueueBar.get('value') + 1 ); + pbQueueStatus.innerHTML = pbQueueBar.get('value') + ' / ' + pbQueueBar.get('maxValue'); + if ( assetIds.length > 0 ) { + callHelper( assetIds ); + } + else { + // We're all done now! + finish(); + } + }, + }); + } + else { + // Just go to the next one + if ( assetIds.length > 0 ) { + callHelper( assetIds ); + } + else { + finish(); + } + } + }, + failure : function (o) { + + }, + scope: this + }; + + var url = '?op=admin;method=processAssetHelper;helperId=' + helperId + ';assetId=' + assetId; + var ajax = YAHOO.util.Connect.asyncRequest( 'GET', url, callback ); + }; + + // Start the queue + callHelper( assetIds ); +}; + +/** + * cut( e ) + * Run the cut assethelper for the selected assets + */ +WebGUI.Admin.Tree.prototype.cut += function ( e ) { + this.runHelperForSelected( "cut", "Cut" ); +}; + +/** + * copy( e ) + * Run the Copy assethelper for the selected assets + */ +WebGUI.Admin.Tree.prototype.copy += function ( e ) { + this.runHelperForSelected( "copy", "Copy" ); +}; + +/** + * shortcut( e ) + * Run the shortcut assethelper for the selected assets + */ +WebGUI.Admin.Tree.prototype.shortcut += function ( e ) { + this.runHelperForSelected( "shortcut", "Create Shortcut" ); +}; + +/** + * Run the duplicate assethelper for the selected assets + */ +WebGUI.Admin.Tree.prototype.duplicate += function ( e ) { + this.runHelperForSelected( "duplicate", "Duplicate" ); +}; + +/** + * Run the delete assetHelper for the selected assets + */ +WebGUI.Admin.Tree.prototype.delete += function ( e ) { + this.runHelperForSelected( "delete", "Delete" ); +}; + +/** + * Update the selected assets' ranks */ WebGUI.Admin.Tree.prototype.update = function ( e ) { - // Get the new ranks - var assetIds = this.getSelected(); - var payload = {}; // Request data payload - for ( var i = 0; i < assetIds.length; i++ ) { - var assetId = assetIds[i]; - payload[ assetId ] = { - rank : document.getElementById( assetId + "_rank" ).value - }; - } + var self = this; + var assetIds = this.getSelected(); - // Send the request - + // Open the dialog with two progress bars + var dialog = new YAHOO.widget.Panel( 'adminModalDialog', { + "width" : '350px', + fixedcenter : true, + constraintoviewport : true, + underlay : "shadow", + close : true, + visible : true, + draggable : false + } ); + dialog.setHeader( "Updating" ); + dialog.setBody( + '
0 / ' + assetIds.length + '
' + + '
' + ); + dialog.render( document.body ); + this.treeDialog = dialog; + + var pbQueueBar = new YAHOO.widget.ProgressBar({ + minValue : 0, + value : 0, + maxValue : assetIds.length, + width: '300px', + height: '30px', + anim: true + }); + pbQueueBar.render( 'pbQueue' ); + pbQueueBar.get('anim').duration = 0.5; + pbQueueBar.get('anim').method = YAHOO.util.Easing.easeOut; + var pbQueueStatus = document.getElementById( 'pbQueueStatus' ); + + var pbTaskBar = new YAHOO.widget.ProgressBar({ + minValue : 0, + value : 0, + maxValue : 1, + width: '300px', + height: '30px', + anim: true + }); + pbTaskBar.render( 'pbTask' ); + pbTaskBar.get('anim').duration = 0.5; + pbTaskBar.get('anim').method = YAHOO.util.Easing.easeOut; + + // Clean up when we're done + var finish = function () { + dialog.destroy(); + dialog = null; + self.admin.requestUpdateClipboard(); + self.admin.requestUpdateCurrentVersionTag(); + self.goto( self.admin.currentAssetDef.url ); + }; + + + // Build a function to call the helper for the next asset + var callUpdate = function( assetIds ) { + var assetId = assetIds.shift(); + + var callback = { + success : function (o) { + var resp = YAHOO.lang.JSON.parse( o.responseText ); + + if ( resp.error ) { + this.admin.processPlugin( resp ); + finish(); + } + else if ( resp.forkId ) { + // Wait until the helper is done, then call the next + YAHOO.WebGUI.Fork.poll({ + url : '?op=fork;pid=' + resp.forkId, + draw : function(data) { + pbTaskBar.set( 'maxValue', data.total ); + pbTaskBar.set( 'value', data.finished ); + }, + finish : function(){ + pbQueueBar.set( 'value', pbQueueBar.get('value') + 1 ); + pbQueueStatus.innerHTML = pbQueueBar.get('value') + ' / ' + pbQueueBar.get('maxValue'); + if ( assetIds.length > 0 ) { + callHelper( assetIds ); + } + else { + // We're all done now! + finish(); + } + }, + }); + } + else if ( assetIds.length > 0 ) { + callUpdate( assetIds ); + } + else { + finish(); + } + }, + failure : function (o) { + }, + scope : this + }; + + var payload = YAHOO.lang.JSON.stringify({ + "rank" : document.getElementById( assetId + "_rank" ).value + }); + + YAHOO.util.Connect.asyncRequest( "POST", "?op=admin;method=updateAsset;assetId=" + assetId, callback, payload ); + }; + + callUpdate( assetIds ); }; /**