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. =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 = "
 
'; 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;