438 lines
14 KiB
Text
438 lines
14 KiB
Text
|
#!/usr/bin/perl -wT
|
||
|
#
|
||
|
# TWiki Collaboration Platform, http://TWiki.org/
|
||
|
#
|
||
|
# Copyright (C) 2001-2003 Peter Thoeny, peter@thoeny.com
|
||
|
# Copyright (C) 2001 Sven Dowideit, svenud@ozemail.com.au
|
||
|
#
|
||
|
# For licensing info read license.txt file in the TWiki root.
|
||
|
# This program is free software; you can redistribute it and/or
|
||
|
# modify it under the terms of the GNU General Public License
|
||
|
# as published by the Free Software Foundation; either version 2
|
||
|
# of the License, or (at your option) any later version.
|
||
|
#
|
||
|
# This program is distributed in the hope that it will be useful,
|
||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
# GNU General Public License for more details, published at
|
||
|
# http://www.gnu.org/copyleft/gpl.html
|
||
|
#
|
||
|
|
||
|
# Set library paths in @INC, at compile time
|
||
|
BEGIN { unshift @INC, '.'; require 'setlib.cfg'; }
|
||
|
|
||
|
# 'Use locale' for internationalisation of Perl regexes -
|
||
|
# main locale settings are done in TWiki::setupLocale
|
||
|
BEGIN {
|
||
|
# Do a dynamic 'use locale' for this module
|
||
|
if( $TWiki::useLocale ) {
|
||
|
require locale;
|
||
|
import locale ();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
use CGI::Carp qw( fatalsToBrowser );
|
||
|
use CGI;
|
||
|
use TWiki;
|
||
|
|
||
|
use strict;
|
||
|
|
||
|
use vars qw($query);
|
||
|
|
||
|
&main();
|
||
|
|
||
|
|
||
|
sub main
|
||
|
{
|
||
|
$query= new CGI;
|
||
|
my $thePathInfo = $query->path_info();
|
||
|
my $theRemoteUser = $query->remote_user();
|
||
|
my $theTopic = $query->param( 'topic' );
|
||
|
my $newWeb = $query->param( 'newweb' ) || "";
|
||
|
my $newTopic = $query->param( 'newtopic' ) || "";
|
||
|
my $theUrl = $query->url;
|
||
|
my $lockFailure = "";
|
||
|
my $breakLock = $query->param( 'breaklock' );
|
||
|
my $theAttachment = $query->param( 'attachment' );
|
||
|
my $confirm = $query->param( 'confirm' );
|
||
|
my $currentWebOnly = $query->param( 'currentwebonly' ) || "";
|
||
|
my $doAllowNonWikiWord = $query->param( 'nonwikiword' ) || "";
|
||
|
my ( $oldTopic, $oldWeb, $scriptUrlPath, $userName, $dataDir ) =
|
||
|
&TWiki::initialize( $thePathInfo, $theRemoteUser, $theTopic, $theUrl, $query ); # DRKW difference to core
|
||
|
my $skin = $query->param( "skin" ) || TWiki::Prefs::getPreferencesValue( "SKIN" );
|
||
|
|
||
|
$newTopic =~ s/\s//go;
|
||
|
$newTopic =~ s/$TWiki::securityFilter//go;
|
||
|
|
||
|
if( ! $theAttachment ) {
|
||
|
$theAttachment = "";
|
||
|
}
|
||
|
|
||
|
my $wikiUserName = &TWiki::userToWikiName( $userName );
|
||
|
|
||
|
# justChangeRefs will be true when some topics that had links to $oldTopic
|
||
|
# still need updating, previous update being prevented by a lock.
|
||
|
my $justChangeRefs = $query->param( 'changeRefs' ) || "";
|
||
|
|
||
|
my $fileName = &TWiki::Store::getFileName( $oldWeb, $oldTopic );
|
||
|
my $newName;
|
||
|
$newName = &TWiki::Store::getFileName( $newWeb, $newTopic ) if( $newWeb );
|
||
|
|
||
|
if( ! $justChangeRefs ) {
|
||
|
if( checkExist( $oldWeb, $oldTopic, $newWeb, $newTopic, $theAttachment, $fileName, $newName ) ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( ! checkPermissions( $oldWeb, $oldTopic, $wikiUserName ) ) {
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Has user selected new name yet?
|
||
|
if( ! $newTopic || $confirm ) {
|
||
|
newTopicScreen( $oldWeb, $oldTopic, $newWeb, $newTopic, $theAttachment,
|
||
|
$confirm, $currentWebOnly, $skin );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( ! $justChangeRefs ) {
|
||
|
if( ! getLocks( $oldWeb, $oldTopic, $newWeb, $newTopic, $theAttachment, $breakLock, $skin ) ) {
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( ! $justChangeRefs ) {
|
||
|
if( $theAttachment ) {
|
||
|
my $moveError =
|
||
|
&TWiki::Store::moveAttachment( $oldWeb, $oldTopic, $newWeb, $newTopic, $theAttachment );
|
||
|
if( $moveError ) {
|
||
|
TWiki::redirect( $query, &TWiki::getOopsUrl( $newWeb, $newTopic, "oopsmoveerr", $theAttachment, $moveError ) );
|
||
|
return;
|
||
|
}
|
||
|
} else {
|
||
|
if( ! $doAllowNonWikiWord && ! &TWiki::isWikiName( $newTopic ) ) {
|
||
|
TWiki::redirect( $query, &TWiki::getOopsUrl( $newWeb, $newTopic, "oopsrenamenotwikiword" ) );
|
||
|
return;
|
||
|
}
|
||
|
my $renameError = &TWiki::Store::renameTopic( $oldWeb, $oldTopic, $newWeb, $newTopic, "relink" );
|
||
|
if( $renameError ) {
|
||
|
TWiki::redirect( $query, &TWiki::getOopsUrl( $oldWeb, $oldTopic, "oopsrenameerr", $renameError, $newWeb, $newTopic ) );
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Update references in referring pages - not applicable to attachments.
|
||
|
if( ! $theAttachment ) {
|
||
|
my @refs = findReferingPages( $oldWeb, $oldTopic );
|
||
|
my $problems;
|
||
|
( $lockFailure, $problems ) =
|
||
|
updateReferingPages( $oldWeb, $oldTopic, $wikiUserName, $newWeb, $newTopic, @refs );
|
||
|
}
|
||
|
|
||
|
my $new_url = "";
|
||
|
if( $lockFailure ) {
|
||
|
moreRefsToChange( $oldWeb, $oldTopic, $newWeb, $newTopic, $skin );
|
||
|
return;
|
||
|
} else {
|
||
|
#redirect to new topic
|
||
|
$new_url = &TWiki::getViewUrl( $newWeb, $newTopic );
|
||
|
}
|
||
|
|
||
|
TWiki::redirect( $query, $new_url );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
#=========================
|
||
|
sub findReferingPages
|
||
|
{
|
||
|
my @result = ();
|
||
|
|
||
|
# Go through parameters finding all topics for change
|
||
|
my @types = qw\local global\;
|
||
|
foreach my $type ( @types ) {
|
||
|
my $count = 1;
|
||
|
while( $query->param( "TOPIC$type$count" ) ) {
|
||
|
my $checked = $query->param( "RENAME$type$count" );
|
||
|
if ($checked) {
|
||
|
push @result, $type;
|
||
|
push @result, $query->param( "TOPIC$type$count" );
|
||
|
}
|
||
|
$count++;
|
||
|
}
|
||
|
}
|
||
|
return @result;
|
||
|
}
|
||
|
|
||
|
|
||
|
#==================================
|
||
|
# update pages that refer to the one being renamed/moved
|
||
|
sub updateReferingPages
|
||
|
{
|
||
|
my ( $oldWeb, $oldTopic, $wikiUserName, $newWeb, $newTopic, @refs ) = @_;
|
||
|
|
||
|
my $lockFailure = 0;
|
||
|
|
||
|
my $result = "";
|
||
|
my $alphaNum = $TWiki::mixedAlphaNum;
|
||
|
my $preTopic = '^|[^${alphaNum}_]'; # Start of line or non-alphanumeric
|
||
|
my $postTopic = '$|[^${alphaNum}_]'; # End of line or non-alphanumeric
|
||
|
my $spacedTopic = TWiki::Search::spacedTopic( $oldTopic );
|
||
|
|
||
|
while ( @refs ) {
|
||
|
my $type = shift @refs;
|
||
|
my $item = shift @refs;
|
||
|
my( $itemWeb, $itemTopic ) = TWiki::Store::normalizeWebTopicName( "", $item );
|
||
|
if ( &TWiki::Store::topicIsLockedBy( $itemWeb, $itemTopic ) ) {
|
||
|
$lockFailure = 1;
|
||
|
} else {
|
||
|
my $resultText = "";
|
||
|
$result .= ":$item: , ";
|
||
|
#open each file, replace $topic with $newTopic
|
||
|
if ( &TWiki::Store::topicExists($itemWeb, $itemTopic) ) {
|
||
|
my $scantext = &TWiki::Store::readTopicRaw($itemWeb, $itemTopic);
|
||
|
if( ! &TWiki::Access::checkAccessPermission( "change", $wikiUserName, $scantext,
|
||
|
$itemWeb, $itemTopic ) ) {
|
||
|
# This shouldn't happen, as search will not return, but check to be on the safe side
|
||
|
&TWiki::writeWarning( "rename: attempt to change $itemWeb.$itemTopic without permission" );
|
||
|
next;
|
||
|
}
|
||
|
my $insidePRE = 0;
|
||
|
my $insideVERBATIM = 0;
|
||
|
my $noAutoLink = 0;
|
||
|
foreach( split( /\n/, $scantext ) ) {
|
||
|
if( /^%META:TOPIC(INFO|MOVED)/ ) {
|
||
|
$resultText .= "$_\n";
|
||
|
next;
|
||
|
}
|
||
|
# FIXME This code is in far too many places - also in Search.pm and Store.pm
|
||
|
m|<pre>|i && ( $insidePRE = 1 );
|
||
|
m|</pre>|i && ( $insidePRE = 0 );
|
||
|
if( m|<verbatim>|i ) {
|
||
|
$insideVERBATIM = 1;
|
||
|
}
|
||
|
if( m|</verbatim>|i ) {
|
||
|
$insideVERBATIM = 0;
|
||
|
}
|
||
|
m|<noautolink>|i && ( $noAutoLink = 1 );
|
||
|
m|</noautolink>|i && ( $noAutoLink = 0 );
|
||
|
|
||
|
if( ! ( $insidePRE || $insideVERBATIM || $noAutoLink ) ) {
|
||
|
if( $type eq "global" ) {
|
||
|
my $insertWeb = ($itemWeb eq $newWeb) ? "" : "$newWeb.";
|
||
|
s/($preTopic)\Q$oldWeb.$oldTopic\E(?=$postTopic)/$1$insertWeb$newTopic/g;
|
||
|
} else {
|
||
|
# Only replace bare topic (i.e. not preceeded by web) if web of referring
|
||
|
# topic is in original Web of topic that's being moved
|
||
|
if( $oldWeb eq $itemWeb ) {
|
||
|
my $insertWeb = ($oldWeb eq $newWeb) ? "" : "$newWeb.";
|
||
|
s/($preTopic)\Q$oldTopic\E(?=$postTopic)/$1$insertWeb$newTopic/g;
|
||
|
s/\[\[($spacedTopic)\]\]/[[$newTopic][$1]]/gi;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
$resultText .= "$_\n";
|
||
|
}
|
||
|
my ( $meta, $text ) = &TWiki::Store::_extractMetaData( $itemWeb, $itemTopic, $resultText );
|
||
|
&TWiki::Store::saveTopic( $itemWeb, $itemTopic, $text, $meta, "", "unlock", "dontNotify", "" );
|
||
|
} else {
|
||
|
$result .= ";$item does not exist;";
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return ( $lockFailure, $result );
|
||
|
}
|
||
|
|
||
|
|
||
|
#=============================
|
||
|
# return "" if problem, otherwise return text of oldTopic
|
||
|
sub checkPermissions
|
||
|
{
|
||
|
my( $oldWeb, $oldTopic, $wikiUserName ) = @_;
|
||
|
|
||
|
my $ret = "";
|
||
|
|
||
|
if( &TWiki::Store::topicExists( $oldWeb, $oldTopic ) ) {
|
||
|
$ret = &TWiki::Store::readWebTopic( $oldWeb, $oldTopic );
|
||
|
}
|
||
|
|
||
|
if( ! &TWiki::Access::checkAccessPermission( "change", $wikiUserName, $ret, $oldTopic, $oldWeb ) ) {
|
||
|
# user has not permission to change the topic
|
||
|
my $url = &TWiki::getOopsUrl( $oldWeb, $oldTopic, "oopsaccesschange" );
|
||
|
TWiki::redirect( $query, $url );
|
||
|
$ret = "";
|
||
|
}
|
||
|
|
||
|
|
||
|
if( ! &TWiki::Access::checkAccessPermission( "rename", $wikiUserName, $ret, $oldTopic, $oldWeb ) ) {
|
||
|
my $url = &TWiki::getOopsUrl( $oldWeb, $oldTopic, "oopsaccessrename" );
|
||
|
TWiki::redirect( $query, $url );
|
||
|
$ret = "";
|
||
|
}
|
||
|
|
||
|
return $ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
#==========================================
|
||
|
# Check that various webs and topics exist or don't exist as required
|
||
|
sub checkExist
|
||
|
{
|
||
|
my( $oldWeb, $oldTopic, $newWeb, $newTopic, $theAttachment, $oldFileName, $newFileName ) = @_;
|
||
|
|
||
|
my $ret = "";
|
||
|
my $tmpl = "";
|
||
|
|
||
|
# Does old WEB exist?
|
||
|
if( ! &TWiki::Store::webExists( $oldWeb ) ) {
|
||
|
TWiki::redirect( $query, &TWiki::getOopsUrl( $oldWeb, $oldTopic, "oopsnoweb" ) );
|
||
|
$ret = "problem";
|
||
|
}
|
||
|
|
||
|
# Does new WEB exist?
|
||
|
if( defined( $newFileName ) && ! &TWiki::Store::webExists( $newWeb ) ) {
|
||
|
TWiki::redirect( $query, &TWiki::getOopsUrl( $newWeb, $newTopic, "oopsnoweb" ) );
|
||
|
$ret = "problem";
|
||
|
}
|
||
|
|
||
|
# Does old attachment exist?
|
||
|
if( ! -e $oldFileName) {
|
||
|
TWiki::redirect( $query, &TWiki::getOopsUrl( $oldWeb, $oldTopic, "oopsmissing" ) );
|
||
|
$ret = "problem";
|
||
|
}
|
||
|
|
||
|
# Check new topic doesn't exist (opposite if we've moving an attachment)
|
||
|
if( defined( $newFileName ) && -e $newFileName && ! $theAttachment ) {
|
||
|
# Unless moving an attachment, new topic should not already exist
|
||
|
TWiki::redirect( $query, &TWiki::getOopsUrl( $newWeb, $newTopic, "oopstopicexists" ) );
|
||
|
$ret = "problem";
|
||
|
}
|
||
|
|
||
|
if( defined( $newFileName ) && $theAttachment && ! -e $newFileName ) {
|
||
|
TWiki::redirect( $query, &TWiki::getOopsUrl( $newWeb, $newTopic, "oopsmissing" ) );
|
||
|
$ret = "problem";
|
||
|
}
|
||
|
|
||
|
return $ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
#============================
|
||
|
#Return "" if can't get lock, otherwise "okay"
|
||
|
sub getLocks
|
||
|
{
|
||
|
my( $oldWeb, $oldTopic, $newWeb, $newTopic, $theAttachment, $breakLock, $skin ) = @_;
|
||
|
|
||
|
my( $oldLockUser, $oldLockTime, $newLockUser, $newLockTime );
|
||
|
|
||
|
if( ! $breakLock ) {
|
||
|
# Check for lock - at present the lock can't be broken
|
||
|
( $oldLockUser, $oldLockTime ) = &TWiki::Store::topicIsLockedBy( $oldWeb, $oldTopic );
|
||
|
if( $oldLockUser ) {
|
||
|
$oldLockUser = &TWiki::userToWikiName( $oldLockUser );
|
||
|
use integer;
|
||
|
$oldLockTime = ( $oldLockTime / 60 ) + 1; # convert to minutes
|
||
|
}
|
||
|
|
||
|
if( $theAttachment ) {
|
||
|
( $newLockUser, $newLockTime ) = &TWiki::Store::topicIsLockedBy( $newWeb, $newTopic );
|
||
|
if( $newLockUser ) {
|
||
|
$newLockUser = &TWiki::userToWikiName( $newLockUser );
|
||
|
use integer;
|
||
|
$newLockTime = ( $newLockTime / 60 ) + 1; # convert to minutes
|
||
|
my $editLock = $TWiki::editLockTime / 60;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( $oldLockUser || $newLockUser ) {
|
||
|
my $tmpl = &TWiki::Store::readTemplate( "oopslockedrename", $skin );
|
||
|
my $editLock = $TWiki::editLockTime / 60;
|
||
|
if( $oldLockUser ) {
|
||
|
$tmpl =~ s/%OLD_LOCK%/Source topic $oldWeb.$oldTopic is locked by $oldLockUser, lock expires in $oldLockTime minutes.<br \/>/go;
|
||
|
} else {
|
||
|
$tmpl =~ s/%OLD_LOCK%//go;
|
||
|
}
|
||
|
if( $newLockUser ) {
|
||
|
$tmpl =~ s/%NEW_LOCK%/Destination topic $newWeb.$newTopic is locked by $newLockUser, lock expires in $newLockTime minutes.<br \/>/go;
|
||
|
} else {
|
||
|
$tmpl =~ s/%NEW_LOCK%//go;
|
||
|
}
|
||
|
$tmpl =~ s/%NEW_WEB%/$newWeb/go;
|
||
|
$tmpl =~ s/%NEW_TOPIC%/$newTopic/go;
|
||
|
$tmpl =~ s/%ATTACHMENT%/$theAttachment/go;
|
||
|
$tmpl = &TWiki::handleCommonTags( $tmpl, $oldTopic, $oldWeb );
|
||
|
$tmpl = &TWiki::getRenderedVersion( $tmpl, $oldWeb );
|
||
|
TWiki::writeHeader( $query );
|
||
|
print $tmpl;
|
||
|
return "";
|
||
|
} else {
|
||
|
&TWiki::Store::lockTopicNew( $oldWeb, $oldTopic );
|
||
|
if( $theAttachment ) {
|
||
|
&TWiki::Store::lockTopicNew( $newWeb, $newTopic );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return "okay";
|
||
|
}
|
||
|
|
||
|
|
||
|
#============================
|
||
|
# Display screen so user can decide on new web and topic.
|
||
|
sub newTopicScreen
|
||
|
{
|
||
|
my( $oldWeb, $oldTopic, $newWeb, $newTopic, $theAttachment, $confirm, $currentWebOnly, $skin ) = @_;
|
||
|
|
||
|
my $tmpl = "";
|
||
|
|
||
|
if( ! $newTopic ) {
|
||
|
$newTopic = $oldTopic;
|
||
|
}
|
||
|
|
||
|
TWiki::writeHeader( $query );
|
||
|
if( $theAttachment ) {
|
||
|
$tmpl = TWiki::Store::readTemplate( "moveattachment", $skin );
|
||
|
$tmpl =~ s/%FILENAME%/$theAttachment/go;
|
||
|
} elsif( $confirm ) {
|
||
|
$tmpl = TWiki::Store::readTemplate( "renameconfirm", $skin );
|
||
|
} else {
|
||
|
$tmpl = &TWiki::Store::readTemplate( "rename", $skin );
|
||
|
}
|
||
|
|
||
|
$tmpl = setVars( $tmpl, $oldTopic, $newWeb, $newTopic );
|
||
|
$tmpl = &TWiki::handleCommonTags( $tmpl, $oldTopic, $oldWeb );
|
||
|
$tmpl = &TWiki::getRenderedVersion( $tmpl );
|
||
|
if( $currentWebOnly ) {
|
||
|
$tmpl =~ s/%RESEARCH\{.*?web=\"all\".*\}%/(skipped)/o; # Remove search all web search
|
||
|
}
|
||
|
$tmpl =~ s/%RESEARCH/%SEARCH/go; # Pre search result from being rendered
|
||
|
$tmpl = &TWiki::handleCommonTags( $tmpl, $oldTopic, $oldWeb );
|
||
|
print $tmpl;
|
||
|
}
|
||
|
|
||
|
#=========================
|
||
|
sub setVars
|
||
|
{
|
||
|
my( $tmpl, $oldTopic, $newWeb, $newTopic ) = @_;
|
||
|
$tmpl =~ s/%NEW_WEB%/$newWeb/go;
|
||
|
$tmpl =~ s/%NEW_TOPIC%/$newTopic/go;
|
||
|
|
||
|
return $tmpl;
|
||
|
}
|
||
|
|
||
|
#=========================
|
||
|
sub moreRefsToChange
|
||
|
{
|
||
|
my( $oldWeb, $oldTopic, $newWeb, $newTopic, $skin ) = @_;
|
||
|
|
||
|
TWiki::writeHeader( $query );
|
||
|
my $tmpl = TWiki::Store::readTemplate( "renamerefs", $skin );
|
||
|
$tmpl = setVars( $tmpl, $oldTopic, $newWeb, $newTopic );
|
||
|
$tmpl = TWiki::getRenderedVersion( $tmpl );
|
||
|
$tmpl =~ s/%RESEARCH/%SEARCH/go; # Pre search result from being rendered
|
||
|
$tmpl = TWiki::handleCommonTags( $tmpl, $oldTopic, $oldWeb );
|
||
|
print $tmpl;
|
||
|
}
|
||
|
|