625 lines
21 KiB
Perl
625 lines
21 KiB
Perl
package WebGUI::Asset;
|
|
|
|
=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
|
|
-------------------------------------------------------------------
|
|
|
|
=cut
|
|
|
|
use strict;
|
|
use WebGUI::Asset::Shortcut;
|
|
use JSON;
|
|
|
|
=head1 NAME
|
|
|
|
Package WebGUI::Asset (AssetTrash)
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
This is a mixin package for WebGUI::Asset that contains all trash related functions.
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
use WebGUI::Asset;
|
|
|
|
=head1 METHODS
|
|
|
|
These methods are available from this class:
|
|
|
|
=cut
|
|
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
=head2 getAssetsInTrash ( [limitToUser,userId] )
|
|
|
|
Returns an array reference of title, assetId, and classname to the assets in the Trash.
|
|
|
|
=head3 limitToUser
|
|
|
|
If True, only return assets last updated by userId.
|
|
|
|
=head3 userId
|
|
|
|
If not specified, uses current user.
|
|
|
|
=cut
|
|
|
|
sub getAssetsInTrash {
|
|
my $self = shift;
|
|
my $limitToUser = shift;
|
|
my $userId = shift || $self->session->user->userId;
|
|
my $limit;
|
|
if ($limitToUser) {
|
|
$limit = "asset.stateChangedBy=".$self->session->db->quote($userId);
|
|
}
|
|
my $root = WebGUI::Asset->getRoot($self->session);
|
|
return $root->getLineage(
|
|
["descendants", ],
|
|
{
|
|
statesToInclude => ["trash"],
|
|
statusToInclude => [qw/approved pending archived/],
|
|
returnObjects => 1,
|
|
whereClause => $limit,
|
|
}
|
|
);
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
|
|
=head2 isInTrash ( )
|
|
|
|
Returns true if the asset is in the trash.
|
|
|
|
=cut
|
|
|
|
sub isInTrash {
|
|
my $self = shift;
|
|
return $self->get("state") eq "trash";
|
|
}
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
=head2 purge ( [ options ] )
|
|
|
|
Deletes an asset from tables and removes anything bound to that asset, including descendants. Returns 1 on success
|
|
and 0 on failure.
|
|
|
|
=head3 options
|
|
|
|
A hash refernece containing options that change the behavior of this method.
|
|
|
|
=head4 skipExported
|
|
|
|
A boolean that, if true, will skip dealing with exported files.
|
|
|
|
=head4 outputSub
|
|
|
|
A subroutine used to report the status of the purge, most likely used by WebGUI::ProgressBar->update.
|
|
|
|
=cut
|
|
|
|
sub purge {
|
|
my $self = shift;
|
|
my $options = shift;
|
|
my $session = $self->session;
|
|
my $outputSub = $options->{outputSub} || sub {};
|
|
my $i18n = WebGUI::International->new($session, 'Asset');
|
|
|
|
# can't delete if it's one of these things
|
|
if ($self->getId eq $session->setting->get("defaultPage") || $self->getId eq $session->setting->get("notFoundPage") || $self->get("isSystem")) {
|
|
$outputSub->(sprintf $i18n->get('Trying to delete system page %s. Aborting'), $self->getTitle);
|
|
$session->errorHandler->security("delete a system protected page (".$self->getId.")");
|
|
return 0;
|
|
}
|
|
|
|
# assassinate the offspring
|
|
my $childIter = $self->getLineageIterator(["children"],{
|
|
statesToInclude => [qw(published clipboard clipboard-limbo trash trash-limbo)],
|
|
statusToInclude => [qw(approved archived pending)],
|
|
});
|
|
while ( 1 ) {
|
|
my $child;
|
|
eval { $child = $childIter->() };
|
|
if ( my $x = WebGUI::Error->caught('WebGUI::Error::ObjectNotFound') ) {
|
|
$session->log->error($x->full_message);
|
|
next;
|
|
}
|
|
last unless $child;
|
|
unless ($child->purge) {
|
|
$session->errorHandler->security("delete one of (".$self->getId.")'s children which is a system protected page");
|
|
$outputSub->(sprintf $i18n->get('Trying to delete system page %s. Aborting'), $self->getTitle);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
# Delete shortcuts to this asset
|
|
# Also purge any shortcuts to this asset that are in the trash
|
|
$outputSub->($i18n->get('Purging shortcuts'));
|
|
my $shortcuts
|
|
= WebGUI::Asset::Shortcut->getShortcutsForAssetId($self->session, $self->getId, {
|
|
returnObjects => 1,
|
|
});
|
|
for my $shortcut ( @$shortcuts ) {
|
|
$shortcut->purge({ outputSub => $outputSub, });
|
|
}
|
|
|
|
# gotta delete stuff we've exported
|
|
unless ($options->{skipExported}) {
|
|
$outputSub->($i18n->get('Deleting exported files'));
|
|
$self->_invokeWorkflowOnExportedFiles($session->setting->get('purgeWorkflow'), 1);
|
|
}
|
|
|
|
# gonna need this at the end
|
|
my $tags = $session->db->buildArrayRef('select tagId from assetData where assetId=?',[$self->getId]);
|
|
my $tagId = $self->get("tagId");
|
|
|
|
# clean up keywords
|
|
$outputSub->($i18n->get('Deleting keywords'));
|
|
WebGUI::Keyword->new($session)->deleteKeywordsForAsset($self);
|
|
|
|
# clean up search engine
|
|
$outputSub->($i18n->get('Clearing search index'));
|
|
WebGUI::Search::Index->new($self)->delete;
|
|
|
|
# clean up cache
|
|
$outputSub->($i18n->get('Clearing cache'));
|
|
WebGUI::Cache->new($session)->deleteChunk(["asset",$self->getId]);
|
|
$self->purgeCache;
|
|
|
|
# delete stuff out of the asset tables
|
|
$outputSub->($i18n->get('Clearing asset tables'));
|
|
$session->db->beginTransaction;
|
|
$session->db->write("delete from metaData_values where assetId = ?",[$self->getId]);
|
|
foreach my $definition (@{$self->definition($session)}) {
|
|
$session->db->write("delete from ".$definition->{tableName}." where assetId=?", [$self->getId]);
|
|
}
|
|
$session->db->write("delete from asset where assetId=?", [$self->getId]);
|
|
$session->db->commit;
|
|
|
|
# log that we've purged this asset
|
|
$self->updateHistory("purged");
|
|
$self = undef;
|
|
|
|
# clean up version tag if empty
|
|
foreach my $tagId (@{ $tags }) {
|
|
my $versionTag = WebGUI::VersionTag->new($session, $tagId);
|
|
if ($versionTag && $versionTag->getAssetCount == 0) {
|
|
$versionTag->rollback;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
=head2 purgeInFork
|
|
|
|
WebGUI::Fork method called by www_purgeList
|
|
|
|
=cut
|
|
|
|
sub purgeInFork {
|
|
my ( $process, $list ) = @_;
|
|
my $session = $process->session;
|
|
my @roots = grep { $_ && $_->canEdit }
|
|
map { 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',
|
|
'purge',
|
|
sub {
|
|
my ( $purge, $self, $options ) = @_;
|
|
my $id = $self->getId;
|
|
my $zero = '';
|
|
$tree->focus($id);
|
|
$options ||= {};
|
|
local $options->{outputSub} = sub { $zero .= $_[0] };
|
|
my $ret = eval { $self->$purge($options) };
|
|
my $e = $@;
|
|
$tree->focus($id);
|
|
|
|
if ($e) {
|
|
$tree->failure( $id, 'Died' );
|
|
$tree->note( $id, $e );
|
|
}
|
|
elsif ( !$ret ) {
|
|
$tree->failure( $id, 'Failed' );
|
|
$tree->note( $id, $zero );
|
|
}
|
|
else {
|
|
$tree->success($id);
|
|
}
|
|
$process->update( sub { $tree->json } );
|
|
die $e if $e;
|
|
return $ret;
|
|
}
|
|
);
|
|
$_->purge for @roots;
|
|
} ## end sub purgeInFork
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
=head2 restore
|
|
|
|
Publishes assets from the trash.
|
|
|
|
=cut
|
|
|
|
sub restore {
|
|
my $self = shift;
|
|
$self->publish;
|
|
}
|
|
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
=head2 trash ( $options )
|
|
|
|
Removes asset from lineage, places it in trash state. The "gap" in the
|
|
lineage is changed in state to trash-limbo. Returns 1 if the trash
|
|
was successful, otherwise it return undef.
|
|
|
|
=head3 $options
|
|
|
|
An optional hashref of options
|
|
|
|
=head4 outputSub
|
|
|
|
A subroutine used to report the status of the purge, most likely used by WebGUI::ProgressBar->update.
|
|
|
|
=cut
|
|
|
|
sub trash {
|
|
my $self = shift;
|
|
my $options = shift;
|
|
my $session = $self->session;
|
|
my $outputSub = $options->{outputSub} || sub {};
|
|
my $i18n = WebGUI::International->new($session, 'Asset');
|
|
|
|
if ($self->getId eq $session->setting->get("defaultPage") || $self->getId eq $session->setting->get("notFoundPage") || $self->get('isSystem')) {
|
|
$outputSub->(sprintf $i18n->get('Trying to delete system page %s. Aborting'), $self->getTitle);
|
|
$session->errorHandler->security("delete a system protected page (".$self->getId.")");
|
|
return undef;
|
|
}
|
|
|
|
my $assetIter = $self->getLineageIterator(
|
|
['self','descendants'], {
|
|
statesToInclude => [qw(published clipboard clipboard-limbo trash trash-limbo)],
|
|
statusToInclude => [qw(approved archived pending)],
|
|
}
|
|
);
|
|
my $rootId = $self->getId;
|
|
my $db = $session->db;
|
|
$db->beginTransaction;
|
|
while ( 1 ) {
|
|
my $asset;
|
|
eval { $asset = $assetIter->() };
|
|
if ( my $x = WebGUI::Error->caught('WebGUI::Error::ObjectNotFound') ) {
|
|
$session->log->error($x->full_message);
|
|
next;
|
|
}
|
|
last unless $asset;
|
|
$outputSub->($i18n->get('Clearing search index'));
|
|
my $index = WebGUI::Search::Index->new($asset);
|
|
$index->delete;
|
|
$outputSub->($i18n->get('Deleting exported files'));
|
|
$asset->_invokeWorkflowOnExportedFiles($session->setting->get('trashWorkflow'), 1);
|
|
$outputSub->($i18n->get('Clearing cache'));
|
|
$asset->purgeCache;
|
|
$asset->updateHistory("trashed");
|
|
if ($asset->getId eq $rootId) {
|
|
$asset->setState('trash');
|
|
# setState will take care of _properties in $asset, but not in
|
|
# $self (whooops!), so we need to manually update.
|
|
my @keys = qw(state stateChangedBy stateChanged);
|
|
@{$self->{_properties}}{@keys} = @{$asset->{_properties}}{@keys};
|
|
}
|
|
else {
|
|
$asset->setState('trash-limbo');
|
|
}
|
|
}
|
|
$db->commit;
|
|
|
|
# Trash any shortcuts to this asset
|
|
my $shortcuts
|
|
= WebGUI::Asset::Shortcut->getShortcutsForAssetId($session, $self->getId, { returnObjects => 1});
|
|
$outputSub->($i18n->get('Purging shortcuts'));
|
|
for my $shortcut ( @$shortcuts ) {
|
|
$shortcut->trash({ outputSub => $outputSub, });
|
|
}
|
|
|
|
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;
|
|
my $workflowId = shift;
|
|
my $clearExportedAs = shift;
|
|
|
|
if ($clearExportedAs) {
|
|
$self->session->db->write("UPDATE asset SET lastExportedAs = NULL WHERE assetId = ?", [$self->getId]);
|
|
}
|
|
if ($workflowId) {
|
|
my ($lastExportedAs) = $self->get("lastExportedAs");
|
|
my $wfInstance = WebGUI::Workflow::Instance->create($self->session, { workflowId => $workflowId });
|
|
if ($wfInstance) {
|
|
$wfInstance->setScratch(
|
|
WebGUI::Workflow::Activity::DeleteExportedFiles::DELETE_FILES_SCRATCH() =>
|
|
Storable::freeze([ defined($lastExportedAs) ? ($lastExportedAs) : () ])
|
|
);
|
|
$wfInstance->start(1);
|
|
}
|
|
else {
|
|
$self->session->log->warn('The Purge Workflow from the settings has been deleted and cannot be run.');
|
|
}
|
|
}
|
|
}
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
=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 (isIn($self->getId, $self->session->setting->get("defaultPage"), $self->session->setting->get("notFoundPage")));
|
|
|
|
my $asset = $self->getContainer;
|
|
if ($self->getId eq $asset->getId) {
|
|
$asset = $self->getParent;
|
|
}
|
|
$self->forkWithStatusPage({
|
|
plugin => 'ProgressTree',
|
|
title => 'Delete Assets',
|
|
redirect => $self->session->url->gateway($asset->get('url')),
|
|
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<proceeed>.
|
|
|
|
=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.
|
|
|
|
=cut
|
|
|
|
sub www_manageTrash {
|
|
my $self = shift;
|
|
my $ac = WebGUI::AdminConsole->new($self->session,"trash");
|
|
my $i18n = WebGUI::International->new($self->session,"Asset");
|
|
return $self->session->privilege->insufficient() unless ($self->session->user->isInGroup(12));
|
|
$ac->setHelp("trash manage");
|
|
my $header;
|
|
my $limit = 1;
|
|
my $canAdmin = $self->session->user->isInGroup($self->session->setting->get('groupIdAdminTrash'));
|
|
my $systemTrash = $self->session->form->process("systemTrash");
|
|
if ($systemTrash && $canAdmin) {
|
|
$header = $i18n->get(965);
|
|
$ac->addSubmenuItem($self->getUrl('func=manageTrash'), $i18n->get(10,"WebGUI"));
|
|
$limit = undef;
|
|
}
|
|
elsif ( $canAdmin ) {
|
|
$ac->addSubmenuItem($self->getUrl('func=manageTrash;systemTrash=1'), $i18n->get(964));
|
|
}
|
|
$self->session->style->setLink($self->session->url->extras('assetManager/assetManager.css'), {rel=>"stylesheet",type=>"text/css"});
|
|
$self->session->style->setScript($self->session->url->extras('assetManager/assetManager.js'), {type=>"text/javascript"});
|
|
my $output = "
|
|
<script type=\"text/javascript\">
|
|
//<![CDATA[
|
|
var assetManager = new AssetManager();
|
|
assetManager.AddColumn('".WebGUI::Form::checkbox($self->session,{name=>"checkAllAssetIds", extras=>'onclick="toggleAssetListSelectAll(this.form);"'})."','','center','form');
|
|
assetManager.AddColumn('".$i18n->get("99")."','','left','');
|
|
assetManager.AddColumn('".$i18n->get("type")."','','left','');
|
|
assetManager.AddColumn('".$i18n->get("last updated")."','','center','');
|
|
assetManager.AddColumn('".$i18n->get("size")."','','right','');
|
|
\n";
|
|
|
|
# To avoid string escaping issues
|
|
my $json = JSON->new->utf8(1);
|
|
my $amethod = sub {
|
|
my ($method, @args) = @_;
|
|
my $array = $json->encode(\@args);
|
|
$array =~ s/^\[//;
|
|
$array =~ s/\]$//;
|
|
$output .= "assetManager.$method($array);\n";
|
|
};
|
|
foreach my $child (@{$self->getAssetsInTrash($limit)}) {
|
|
my $title = $child->getTitle;
|
|
my $plus =$child->getChildCount({includeTrash => 1}) ? "+ " : " ";
|
|
$amethod->('AddLine',
|
|
WebGUI::Form::checkbox($self->session, {
|
|
name=>'assetId',
|
|
value=>$child->getId
|
|
}),
|
|
qq($plus<a href=").$child->getUrl("op=assetManager")
|
|
.qq(">$title</a>),
|
|
'<p style="display:inline;vertical-align:middle;"><img src="'
|
|
.$child->getIcon(1)
|
|
.'" style="vertical-align:middle;border-style:none;" alt='
|
|
.$child->getName .'" /></p> ' . $child->getName,
|
|
$self->session->datetime->epochToHuman($child->get("revisionDate")),
|
|
formatBytes($child->get("assetSize"))
|
|
);
|
|
$amethod->('AddLineSortData',
|
|
'', $title, $child->getName,
|
|
$child->get('revisionDate'), $child->get('assetSize')
|
|
);
|
|
}
|
|
$output .= '
|
|
assetManager.AddButton("'.$i18n->get("restore").'","restoreList","manageTrash");
|
|
assetManager.AddButton("'.$i18n->get("purge").'","purgeList","manageTrash");
|
|
assetManager.AddFormHidden({ name:"webguiCsrfToken", value:"'.$self->session->scratch->get('webguiCsrfToken').'"});
|
|
assetManager.AddFormHidden({ name:"systemTrash", value:"'.$systemTrash.'"});
|
|
assetManager.Write();
|
|
var assetListSelectAllToggle = false;
|
|
function toggleAssetListSelectAll(form) {
|
|
assetListSelectAllToggle = assetListSelectAllToggle ? false : true;
|
|
if (typeof form.assetId.length == "undefined") {
|
|
form.assetId.checked = assetListSelectAllToggle;
|
|
}
|
|
else {
|
|
for (var i = 0; i < form.assetId.length; i++)
|
|
form.assetId[i].checked = assetListSelectAllToggle;
|
|
}
|
|
}
|
|
//]]>
|
|
</script> <div class="adminConsoleSpacer"> </div>';
|
|
return $ac->render($output, $header);
|
|
}
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
=head2 www_purgeList ( )
|
|
|
|
Purges a piece of content, including all it's revisions, from the system permanently.
|
|
|
|
Returns insufficient privileges unless the submitted form passes the validToken check.
|
|
|
|
=cut
|
|
|
|
sub www_purgeList {
|
|
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';
|
|
$method .= ';systemTrash=1' if $form->get('systemTrash');
|
|
$self->forkWithStatusPage({
|
|
plugin => 'ProgressTree',
|
|
title => 'purge',
|
|
redirect => $self->getUrl("func=$method"),
|
|
method => 'purgeInFork',
|
|
args => [ $form->get('assetId') ],
|
|
}
|
|
);
|
|
}
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
=head2 www_restoreList ( )
|
|
|
|
Restores a piece of content from the trash back to it's original location.
|
|
|
|
=cut
|
|
|
|
sub www_restoreList {
|
|
my $self = shift;
|
|
foreach my $id ($self->session->form->param("assetId")) {
|
|
my $asset = eval { WebGUI::Asset->newPending($self->session,$id); };
|
|
$asset->restore if $asset->canEdit;
|
|
}
|
|
if ($self->session->form->process("proceed") ne "") {
|
|
my $method = "www_".$self->session->form->process("proceed");
|
|
return $self->$method();
|
|
}
|
|
return $self->www_manageTrash();
|
|
}
|
|
|
|
|
|
1;
|
|
|