| 1 | # See bottom of file for license and copyright information |
|---|
| 2 | |
|---|
| 3 | =begin TML |
|---|
| 4 | |
|---|
| 5 | ---+ package Foswiki::UI::Save |
|---|
| 6 | |
|---|
| 7 | UI delegate for save function |
|---|
| 8 | |
|---|
| 9 | =cut |
|---|
| 10 | |
|---|
| 11 | package Foswiki::UI::Save; |
|---|
| 12 | |
|---|
| 13 | use strict; |
|---|
| 14 | use Error qw( :try ); |
|---|
| 15 | use Assert; |
|---|
| 16 | |
|---|
| 17 | require Foswiki; |
|---|
| 18 | require Foswiki::UI; |
|---|
| 19 | require Foswiki::Meta; |
|---|
| 20 | require Foswiki::OopsException; |
|---|
| 21 | |
|---|
| 22 | # Used by save and preview |
|---|
| 23 | sub buildNewTopic { |
|---|
| 24 | my ( $session, $script ) = @_; |
|---|
| 25 | |
|---|
| 26 | my $query = $session->{request}; |
|---|
| 27 | my $webName = $session->{webName}; |
|---|
| 28 | my $topic = $session->{topicName}; |
|---|
| 29 | my $store = $session->{store}; |
|---|
| 30 | my $revision = $query->param('rev') || undef; |
|---|
| 31 | |
|---|
| 32 | unless ( scalar( $query->param() ) ) { |
|---|
| 33 | |
|---|
| 34 | # insufficient parameters to save |
|---|
| 35 | throw Foswiki::OopsException( |
|---|
| 36 | 'attention', |
|---|
| 37 | def => 'bad_script_parameters', |
|---|
| 38 | web => $session->{webName}, |
|---|
| 39 | topic => $session->{topicName}, |
|---|
| 40 | params => [$script] |
|---|
| 41 | ); |
|---|
| 42 | } |
|---|
| 43 | |
|---|
| 44 | Foswiki::UI::checkMirror( $session, $webName, $topic ); |
|---|
| 45 | Foswiki::UI::checkWebExists( $session, $webName, $topic, 'save' ); |
|---|
| 46 | |
|---|
| 47 | my $topicExists = $store->topicExists( $webName, $topic ); |
|---|
| 48 | |
|---|
| 49 | # Prevent saving existing topic? |
|---|
| 50 | my $onlyNewTopic = Foswiki::isTrue( $query->param('onlynewtopic') ); |
|---|
| 51 | if ( $onlyNewTopic && $topicExists ) { |
|---|
| 52 | |
|---|
| 53 | # Topic exists and user requested oops if it exists |
|---|
| 54 | throw Foswiki::OopsException( |
|---|
| 55 | 'attention', |
|---|
| 56 | def => 'topic_exists', |
|---|
| 57 | web => $webName, |
|---|
| 58 | topic => $topic |
|---|
| 59 | ); |
|---|
| 60 | } |
|---|
| 61 | |
|---|
| 62 | # prevent non-Wiki names? |
|---|
| 63 | my $onlyWikiName = Foswiki::isTrue( $query->param('onlywikiname') ); |
|---|
| 64 | if ( ($onlyWikiName) |
|---|
| 65 | && ( !$topicExists ) |
|---|
| 66 | && ( !Foswiki::isValidTopicName($topic) ) ) |
|---|
| 67 | { |
|---|
| 68 | |
|---|
| 69 | # do not allow non-wikinames |
|---|
| 70 | throw Foswiki::OopsException( |
|---|
| 71 | 'attention', |
|---|
| 72 | def => 'not_wikiword', |
|---|
| 73 | web => $webName, |
|---|
| 74 | topic => $topic, |
|---|
| 75 | params => [$topic] |
|---|
| 76 | ); |
|---|
| 77 | } |
|---|
| 78 | |
|---|
| 79 | my $user = $session->{user}; |
|---|
| 80 | Foswiki::UI::checkAccess( $session, $webName, $topic, 'CHANGE', $user ); |
|---|
| 81 | |
|---|
| 82 | my $saveOpts = {}; |
|---|
| 83 | $saveOpts->{minor} = 1 if $query->param('dontnotify'); |
|---|
| 84 | my $originalrev = $query->param('originalrev'); # rev edit started on |
|---|
| 85 | |
|---|
| 86 | # Populate the new meta data |
|---|
| 87 | my $newMeta = new Foswiki::Meta( $session, $webName, $topic ); |
|---|
| 88 | |
|---|
| 89 | my ( $prevMeta, $prevText ); |
|---|
| 90 | my ( $templateText, $templateMeta ); |
|---|
| 91 | my $templatetopic = $query->param('templatetopic'); |
|---|
| 92 | my $templateweb = $webName; |
|---|
| 93 | |
|---|
| 94 | if ($templatetopic) { |
|---|
| 95 | ( $templateweb, $templatetopic ) = |
|---|
| 96 | $session->normalizeWebTopicName( $templateweb, $templatetopic ); |
|---|
| 97 | |
|---|
| 98 | unless ( $store->topicExists( $templateweb, $templatetopic ) ) { |
|---|
| 99 | throw Foswiki::OopsException( |
|---|
| 100 | 'attention', |
|---|
| 101 | def => 'no_such_topic_template', |
|---|
| 102 | web => $templateweb, |
|---|
| 103 | topic => $templatetopic |
|---|
| 104 | ); |
|---|
| 105 | } |
|---|
| 106 | } |
|---|
| 107 | |
|---|
| 108 | if ($topicExists) { |
|---|
| 109 | ( $prevMeta, $prevText ) = |
|---|
| 110 | $store->readTopic( $user, $webName, $topic, $revision ); |
|---|
| 111 | if ($prevMeta) { |
|---|
| 112 | foreach my $k ( keys %$prevMeta ) { |
|---|
| 113 | unless ( $k =~ /^_/ |
|---|
| 114 | || $k eq 'FORM' |
|---|
| 115 | || $k eq 'TOPICPARENT' |
|---|
| 116 | || $k eq 'FIELD' ) |
|---|
| 117 | { |
|---|
| 118 | $newMeta->copyFrom( $prevMeta, $k ); |
|---|
| 119 | } |
|---|
| 120 | } |
|---|
| 121 | } |
|---|
| 122 | } |
|---|
| 123 | elsif ($templatetopic) { |
|---|
| 124 | ( $templateMeta, $templateText ) = |
|---|
| 125 | $store->readTopic( $user, $templateweb, $templatetopic, $revision ); |
|---|
| 126 | $templateText = '' if $query->param('newtopic'); # created by edit |
|---|
| 127 | $templateText = |
|---|
| 128 | $session->expandVariablesOnTopicCreation( $templateText, $user, |
|---|
| 129 | $webName, $topic ); |
|---|
| 130 | foreach my $k ( keys %$templateMeta ) { |
|---|
| 131 | unless ( $k =~ /^_/ |
|---|
| 132 | || $k eq 'FORM' |
|---|
| 133 | || $k eq 'TOPICPARENT' |
|---|
| 134 | || $k eq 'FIELD' |
|---|
| 135 | || $k eq 'TOPICMOVED' ) |
|---|
| 136 | { |
|---|
| 137 | $newMeta->copyFrom( $templateMeta, $k ); |
|---|
| 138 | } |
|---|
| 139 | } |
|---|
| 140 | |
|---|
| 141 | # topic creation, there is no original rev |
|---|
| 142 | $originalrev = 0; |
|---|
| 143 | } |
|---|
| 144 | |
|---|
| 145 | # Determine the new text |
|---|
| 146 | my $newText = $query->param('text'); |
|---|
| 147 | |
|---|
| 148 | my $forceNewRev = $query->param('forcenewrevision'); |
|---|
| 149 | $saveOpts->{forcenewrevision} = $forceNewRev; |
|---|
| 150 | my $newParent = $query->param('topicparent'); |
|---|
| 151 | |
|---|
| 152 | if ( defined($newText) ) { |
|---|
| 153 | |
|---|
| 154 | # text is defined in the query, save that text |
|---|
| 155 | $newText =~ s/\r//g; |
|---|
| 156 | $newText .= "\n" unless $newText =~ /\n$/s; |
|---|
| 157 | |
|---|
| 158 | } |
|---|
| 159 | elsif ( defined $templateText ) { |
|---|
| 160 | |
|---|
| 161 | # no text in the query, but we have a templatetopic |
|---|
| 162 | $newText = $templateText; |
|---|
| 163 | $originalrev = 0; # disable merge |
|---|
| 164 | |
|---|
| 165 | } |
|---|
| 166 | else { |
|---|
| 167 | $newText = ''; |
|---|
| 168 | if ( defined $prevText ) { |
|---|
| 169 | $newText = $prevText; |
|---|
| 170 | $originalrev = 0; # disable merge |
|---|
| 171 | } |
|---|
| 172 | } |
|---|
| 173 | |
|---|
| 174 | my $mum; |
|---|
| 175 | if ($newParent) { |
|---|
| 176 | if ( $newParent ne 'none' ) { |
|---|
| 177 | $mum = { 'name' => $newParent }; |
|---|
| 178 | } |
|---|
| 179 | } |
|---|
| 180 | elsif ($templateMeta) { |
|---|
| 181 | $mum = $templateMeta->get('TOPICPARENT'); |
|---|
| 182 | } |
|---|
| 183 | elsif ($prevMeta) { |
|---|
| 184 | $mum = $prevMeta->get('TOPICPARENT'); |
|---|
| 185 | } |
|---|
| 186 | $newMeta->put( 'TOPICPARENT', $mum ) if $mum; |
|---|
| 187 | |
|---|
| 188 | my $formName = $query->param('formtemplate'); |
|---|
| 189 | my $formDef; |
|---|
| 190 | my $copyMeta; |
|---|
| 191 | |
|---|
| 192 | if ($formName) { |
|---|
| 193 | |
|---|
| 194 | # new form, default field values will be null |
|---|
| 195 | $formName = '' if ( $formName eq 'none' ); |
|---|
| 196 | } |
|---|
| 197 | elsif ($templateMeta) { |
|---|
| 198 | |
|---|
| 199 | # populate the meta-data with field values from the template |
|---|
| 200 | $formName = $templateMeta->get('FORM'); |
|---|
| 201 | $formName = $formName->{name} if $formName; |
|---|
| 202 | $copyMeta = $templateMeta; |
|---|
| 203 | } |
|---|
| 204 | elsif ($prevMeta) { |
|---|
| 205 | |
|---|
| 206 | # populate the meta-data with field values from the existing topic |
|---|
| 207 | $formName = $prevMeta->get('FORM'); |
|---|
| 208 | $formName = $formName->{name} if $formName; |
|---|
| 209 | $copyMeta = $prevMeta; |
|---|
| 210 | } |
|---|
| 211 | |
|---|
| 212 | if ($formName) { |
|---|
| 213 | require Foswiki::Form; |
|---|
| 214 | $formDef = new Foswiki::Form( $session, $webName, $formName ); |
|---|
| 215 | unless ($formDef) { |
|---|
| 216 | unless ($prevMeta) { |
|---|
| 217 | throw Foswiki::OopsException( |
|---|
| 218 | 'attention', |
|---|
| 219 | def => 'no_form_def', |
|---|
| 220 | web => $session->{webName}, |
|---|
| 221 | topic => $session->{topicName}, |
|---|
| 222 | params => [ $webName, $formName ] |
|---|
| 223 | ); |
|---|
| 224 | } |
|---|
| 225 | |
|---|
| 226 | # Recreate the form fields from the previous rev of the topic. |
|---|
| 227 | $formDef = |
|---|
| 228 | new Foswiki::Form( $session, $webName, $formName, $prevMeta ); |
|---|
| 229 | } |
|---|
| 230 | $newMeta->put( 'FORM', { name => $formName } ); |
|---|
| 231 | } |
|---|
| 232 | if ( $copyMeta && $formDef ) { |
|---|
| 233 | |
|---|
| 234 | # Copy existing fields into new form, filtering on the |
|---|
| 235 | # known field names so we don't copy dead data. Though we |
|---|
| 236 | # really should, of course. That comes later. |
|---|
| 237 | my $filter = join( '|', |
|---|
| 238 | map { $_->{name} } |
|---|
| 239 | grep { $_->{name} } @{ $formDef->getFields() } ); |
|---|
| 240 | $newMeta->copyFrom( $copyMeta, 'FIELD', qr/^($filter)$/ ); |
|---|
| 241 | } |
|---|
| 242 | if ($formDef) { |
|---|
| 243 | |
|---|
| 244 | # override with values from the query |
|---|
| 245 | my ( $seen, $missing ) = |
|---|
| 246 | $formDef->getFieldValuesFromQuery( $query, $newMeta ); |
|---|
| 247 | if ( $seen && @$missing ) { |
|---|
| 248 | |
|---|
| 249 | # chuck up if there is at least one field value defined in the |
|---|
| 250 | # query and a mandatory field was not defined in the |
|---|
| 251 | # query or by an existing value. |
|---|
| 252 | # Item5428: clean up <nop>'s |
|---|
| 253 | @$missing = map { |
|---|
| 254 | s/<nop>//g; |
|---|
| 255 | $_ |
|---|
| 256 | } @$missing; |
|---|
| 257 | throw Foswiki::OopsException( |
|---|
| 258 | 'attention', |
|---|
| 259 | def => 'mandatory_field', |
|---|
| 260 | web => $session->{webName}, |
|---|
| 261 | topic => $session->{topicName}, |
|---|
| 262 | params => [ join( ' ', @$missing ) ] |
|---|
| 263 | ); |
|---|
| 264 | } |
|---|
| 265 | } |
|---|
| 266 | |
|---|
| 267 | my $merged; |
|---|
| 268 | |
|---|
| 269 | # assumes rev numbers start at 1 |
|---|
| 270 | if ($originalrev) { |
|---|
| 271 | my ( $orev, $odate ); |
|---|
| 272 | if ( $originalrev =~ /^(\d+)_(\d+)$/ ) { |
|---|
| 273 | ( $orev, $odate ) = ( $1, $2 ); |
|---|
| 274 | } |
|---|
| 275 | elsif ( $originalrev =~ /^\d+$/ ) { |
|---|
| 276 | $orev = $originalrev; |
|---|
| 277 | } |
|---|
| 278 | else { |
|---|
| 279 | $orev = 0; |
|---|
| 280 | } |
|---|
| 281 | my ( $date, $author, $rev, $comment ) = $newMeta->getRevisionInfo(); |
|---|
| 282 | |
|---|
| 283 | # If the last save was by me, don't merge |
|---|
| 284 | if ( ( $orev ne $rev || $odate && $date && $odate ne $date ) |
|---|
| 285 | && $author ne $user ) |
|---|
| 286 | { |
|---|
| 287 | |
|---|
| 288 | require Foswiki::Merge; |
|---|
| 289 | |
|---|
| 290 | my $pti = $prevMeta->get('TOPICINFO'); |
|---|
| 291 | if ( $pti->{reprev} |
|---|
| 292 | && $pti->{version} |
|---|
| 293 | && $pti->{reprev} == $pti->{version} ) |
|---|
| 294 | { |
|---|
| 295 | |
|---|
| 296 | # If the ancestor revision was generated by a reprev, |
|---|
| 297 | # then the original is lost and we can't 3-way merge |
|---|
| 298 | |
|---|
| 299 | $session->{plugins} |
|---|
| 300 | ->dispatch( 'beforeMergeHandler', $newText, $pti->{version}, |
|---|
| 301 | $prevText, undef, undef, $webName, $topic ); |
|---|
| 302 | |
|---|
| 303 | $newText = |
|---|
| 304 | Foswiki::Merge::merge2( $pti->{version}, $prevText, $rev, |
|---|
| 305 | $newText, '.*?\n', $session ); |
|---|
| 306 | } |
|---|
| 307 | else { |
|---|
| 308 | |
|---|
| 309 | # common ancestor; we can 3-way merge |
|---|
| 310 | my ( $ancestorMeta, $ancestorText ) = |
|---|
| 311 | $store->readTopic( undef, $webName, $topic, $orev ); |
|---|
| 312 | |
|---|
| 313 | $session->{plugins} |
|---|
| 314 | ->dispatch( 'beforeMergeHandler', $newText, $rev, $prevText, |
|---|
| 315 | $orev, $ancestorText, $webName, $topic ); |
|---|
| 316 | |
|---|
| 317 | $newText = |
|---|
| 318 | Foswiki::Merge::merge3( $orev, $ancestorText, $rev, $prevText, |
|---|
| 319 | 'new', $newText, '.*?\n', $session ); |
|---|
| 320 | } |
|---|
| 321 | if ( $formDef && $prevMeta ) { |
|---|
| 322 | $newMeta->merge( $prevMeta, $formDef ); |
|---|
| 323 | } |
|---|
| 324 | $merged = |
|---|
| 325 | [ $orev, $session->{users}->getWikiName($author), $rev || 1 ]; |
|---|
| 326 | } |
|---|
| 327 | } |
|---|
| 328 | |
|---|
| 329 | return ( $newMeta, $newText, $saveOpts, $merged ); |
|---|
| 330 | } |
|---|
| 331 | |
|---|
| 332 | =begin TML |
|---|
| 333 | |
|---|
| 334 | ---++ StaticMethod save($session) |
|---|
| 335 | |
|---|
| 336 | Command handler for =save= command. |
|---|
| 337 | This method is designed to be |
|---|
| 338 | invoked via the =UI::run= method. |
|---|
| 339 | |
|---|
| 340 | See System.CommandAndCGIScripts for details of parameters. |
|---|
| 341 | |
|---|
| 342 | Note: =cmd= has been deprecated in favour of =action=. It will be deleted at |
|---|
| 343 | some point. |
|---|
| 344 | |
|---|
| 345 | =cut |
|---|
| 346 | |
|---|
| 347 | sub save { |
|---|
| 348 | my $session = shift; |
|---|
| 349 | |
|---|
| 350 | my $query = $session->{request}; |
|---|
| 351 | my $web = $session->{webName}; |
|---|
| 352 | my $topic = $session->{topicName}; |
|---|
| 353 | my $store = $session->{store}; |
|---|
| 354 | my $user = $session->{user}; |
|---|
| 355 | |
|---|
| 356 | # Do not remove, keep as undocumented feature for compatibility with |
|---|
| 357 | # TWiki 4.0.x: Allow for dynamic topic creation by replacing strings |
|---|
| 358 | # of at least 10 x's XXXXXX with a next-in-sequence number. |
|---|
| 359 | # See Codev.AllowDynamicTopicNameCreation |
|---|
| 360 | if ( $topic =~ /X{10}/ ) { |
|---|
| 361 | my $n = 0; |
|---|
| 362 | my $baseTopic = $topic; |
|---|
| 363 | $store->clearLease( $web, $baseTopic ); |
|---|
| 364 | do { |
|---|
| 365 | $topic = $baseTopic; |
|---|
| 366 | $topic =~ s/X{10}X*/$n/e; |
|---|
| 367 | $n++; |
|---|
| 368 | } while ( $store->topicExists( $web, $topic ) ); |
|---|
| 369 | $session->{topicName} = $topic; |
|---|
| 370 | } |
|---|
| 371 | |
|---|
| 372 | # Allow for more flexible topic creation with sortable names and |
|---|
| 373 | # better performance. See Codev.AutoIncTopicNameOnSave |
|---|
| 374 | if ( $topic =~ /AUTOINC([0-9]+)/ ) { |
|---|
| 375 | my $start = $1; |
|---|
| 376 | my $baseTopic = $topic; |
|---|
| 377 | $store->clearLease( $web, $baseTopic ); |
|---|
| 378 | my $nameFilter = $topic; |
|---|
| 379 | $nameFilter =~ s/AUTOINC([0-9]+)/([0-9]+)/; |
|---|
| 380 | my @list = |
|---|
| 381 | sort { $a <=> $b } |
|---|
| 382 | map { s/^$nameFilter$/$1/; s/^0*([0-9])/$1/; $_ } |
|---|
| 383 | grep { /^$nameFilter$/ } $store->getTopicNames($web); |
|---|
| 384 | if ( scalar @list ) { |
|---|
| 385 | |
|---|
| 386 | # find last one, and increment by one |
|---|
| 387 | my $next = $list[$#list] + 1; |
|---|
| 388 | my $len = length($start); |
|---|
| 389 | $start =~ s/^0*([0-9])/$1/; # cut leading zeros |
|---|
| 390 | $next = $start if ( $start > $next ); |
|---|
| 391 | my $pad = $len - length($next); |
|---|
| 392 | if ( $pad > 0 ) { |
|---|
| 393 | $next = '0' x $pad . $next; # zero-pad |
|---|
| 394 | } |
|---|
| 395 | $topic =~ s/AUTOINC[0-9]+/$next/; |
|---|
| 396 | } |
|---|
| 397 | else { |
|---|
| 398 | |
|---|
| 399 | # first auto-inc topic |
|---|
| 400 | $topic =~ s/AUTOINC[0-9]+/$start/; |
|---|
| 401 | } |
|---|
| 402 | $session->{topicName} = $topic; |
|---|
| 403 | } |
|---|
| 404 | |
|---|
| 405 | my $saveaction = ''; |
|---|
| 406 | foreach my $action qw( save checkpoint quietsave cancel preview |
|---|
| 407 | addform replaceform delRev repRev ) { |
|---|
| 408 | if ( $query->param( 'action_' . $action ) ) |
|---|
| 409 | { |
|---|
| 410 | $saveaction = $action; |
|---|
| 411 | last; |
|---|
| 412 | } |
|---|
| 413 | } |
|---|
| 414 | |
|---|
| 415 | # the 'action' parameter has been deprecated, though is still available |
|---|
| 416 | # for compatibility with old templates. |
|---|
| 417 | if ( !$saveaction && $query->param('action') ) { |
|---|
| 418 | $saveaction = lc( $query->param('action') ); |
|---|
| 419 | $session->writeWarning(<<WARN); |
|---|
| 420 | Use of deprecated "action" parameter to "save". Correct your templates! |
|---|
| 421 | WARN |
|---|
| 422 | |
|---|
| 423 | # handle old values for form-related actions: |
|---|
| 424 | $saveaction = 'addform' if ( $saveaction eq 'add form' ); |
|---|
| 425 | $saveaction = 'replaceform' if ( $saveaction eq 'replace form...' ); |
|---|
| 426 | } |
|---|
| 427 | |
|---|
| 428 | if ( $saveaction eq 'cancel' ) { |
|---|
| 429 | my $lease = $store->getLease( $web, $topic ); |
|---|
| 430 | if ( $lease && $lease->{user} eq $user ) { |
|---|
| 431 | $store->clearLease( $web, $topic ); |
|---|
| 432 | } |
|---|
| 433 | |
|---|
| 434 | # redirect to a sensible place (a topic that exists) |
|---|
| 435 | my ( $w, $t ) = ( '', '' ); |
|---|
| 436 | foreach my $test ( $topic, $query->param('topicparent'), |
|---|
| 437 | $Foswiki::cfg{HomeTopicName} ) |
|---|
| 438 | { |
|---|
| 439 | ( $w, $t ) = $session->normalizeWebTopicName( $web, $test ); |
|---|
| 440 | last if ( $store->topicExists( $w, $t ) ); |
|---|
| 441 | } |
|---|
| 442 | my $viewURL = $session->getScriptUrl( 1, 'view', $w, $t ); |
|---|
| 443 | $session->redirect( $viewURL, undef, 1 ); |
|---|
| 444 | |
|---|
| 445 | return; |
|---|
| 446 | } |
|---|
| 447 | |
|---|
| 448 | if ( $saveaction eq 'preview' ) { |
|---|
| 449 | require Foswiki::UI::Preview; |
|---|
| 450 | Foswiki::UI::Preview::preview($session); |
|---|
| 451 | return; |
|---|
| 452 | } |
|---|
| 453 | |
|---|
| 454 | my $editaction = lc( $query->param('editaction') ) || ''; |
|---|
| 455 | my $edit = $query->param('edit') || 'edit'; |
|---|
| 456 | my $editparams = $query->param('editparams') || ''; |
|---|
| 457 | |
|---|
| 458 | ## SMELL: The form affecting actions do not preserve edit and editparams |
|---|
| 459 | if ( $saveaction eq 'addform' |
|---|
| 460 | || $saveaction eq 'replaceform' |
|---|
| 461 | || $saveaction eq 'preview' && $query->param('submitChangeForm') ) |
|---|
| 462 | { |
|---|
| 463 | require Foswiki::UI::ChangeForm; |
|---|
| 464 | $session->writeCompletePage( |
|---|
| 465 | Foswiki::UI::ChangeForm::generate( |
|---|
| 466 | $session, $web, $topic, $editaction |
|---|
| 467 | ) |
|---|
| 468 | ); |
|---|
| 469 | return; |
|---|
| 470 | } |
|---|
| 471 | |
|---|
| 472 | my $redirecturl; |
|---|
| 473 | |
|---|
| 474 | if ( $saveaction eq 'checkpoint' ) { |
|---|
| 475 | $query->param( -name => 'dontnotify', -value => 'checked' ); |
|---|
| 476 | my $edittemplate = $query->param( 'template' ); |
|---|
| 477 | my $editURL = $session->getScriptUrl( 1, $edit, $web, $topic ); |
|---|
| 478 | $redirecturl = $editURL . '?t=' . time(); |
|---|
| 479 | $redirecturl .= '&redirectto=' . $query->param('redirectto') |
|---|
| 480 | if $query->param('redirectto'); |
|---|
| 481 | |
|---|
| 482 | # select the appropriate edit template |
|---|
| 483 | $redirecturl .= '&action=' . $editaction if $editaction; |
|---|
| 484 | $redirecturl .= '&template=' . $edittemplate if $edittemplate; |
|---|
| 485 | |
|---|
| 486 | $redirecturl .= '&skin=' . $query->param('skin') |
|---|
| 487 | if $query->param('skin'); |
|---|
| 488 | $redirecturl .= '&cover=' . $query->param('cover') |
|---|
| 489 | if $query->param('cover'); |
|---|
| 490 | $redirecturl .= '&nowysiwyg=' . $query->param('nowysiwyg') |
|---|
| 491 | if $query->param('nowysiwyg'); |
|---|
| 492 | $redirecturl .= $editparams |
|---|
| 493 | if $editparams; # May contain anchor |
|---|
| 494 | my $lease = $store->getLease( $web, $topic ); |
|---|
| 495 | if ( $lease && $lease->{user} eq $user ) { |
|---|
| 496 | $store->setLease( $web, $topic, $user, $Foswiki::cfg{LeaseLength} ); |
|---|
| 497 | } |
|---|
| 498 | |
|---|
| 499 | # drop through |
|---|
| 500 | } |
|---|
| 501 | |
|---|
| 502 | if ( $saveaction eq 'quietsave' ) { |
|---|
| 503 | $query->param( -name => 'dontnotify', -value => 'checked' ); |
|---|
| 504 | $saveaction = 'save'; |
|---|
| 505 | |
|---|
| 506 | # drop through |
|---|
| 507 | } |
|---|
| 508 | |
|---|
| 509 | if ( $saveaction =~ /^(del|rep)Rev$/ ) { |
|---|
| 510 | |
|---|
| 511 | # hidden, largely undocumented functions, used by administrators for |
|---|
| 512 | # reverting spammed topics. These functions support rewriting |
|---|
| 513 | # history, in a Joe Stalin kind of way. They should be replaced with |
|---|
| 514 | # mechanisms for hiding revisions. |
|---|
| 515 | $query->param( -name => 'cmd', -value => $saveaction ); |
|---|
| 516 | |
|---|
| 517 | # drop through |
|---|
| 518 | } |
|---|
| 519 | |
|---|
| 520 | my $saveCmd = $query->param('cmd') || 0; |
|---|
| 521 | if ( $saveCmd && !$session->{users}->isAdmin( $session->{user} ) ) { |
|---|
| 522 | throw Foswiki::OopsException( |
|---|
| 523 | 'accessdenied', |
|---|
| 524 | def => 'only_group', |
|---|
| 525 | web => $web, |
|---|
| 526 | topic => $topic, |
|---|
| 527 | params => [ $Foswiki::cfg{SuperAdminGroup} ] |
|---|
| 528 | ); |
|---|
| 529 | } |
|---|
| 530 | |
|---|
| 531 | #success - redirect to topic view (unless its a checkpoint save) |
|---|
| 532 | $redirecturl ||= $session->getScriptUrl( 1, 'view', $web, $topic ); |
|---|
| 533 | |
|---|
| 534 | if ( $saveCmd eq 'delRev' ) { |
|---|
| 535 | |
|---|
| 536 | # delete top revision |
|---|
| 537 | try { |
|---|
| 538 | $store->delRev( $user, $web, $topic ); |
|---|
| 539 | } |
|---|
| 540 | catch Error::Simple with { |
|---|
| 541 | throw Foswiki::OopsException( |
|---|
| 542 | 'attention', |
|---|
| 543 | def => 'save_error', |
|---|
| 544 | web => $web, |
|---|
| 545 | topic => $topic, |
|---|
| 546 | params => [ shift->{-text} ] |
|---|
| 547 | ); |
|---|
| 548 | }; |
|---|
| 549 | |
|---|
| 550 | $session->redirect( $redirecturl, undef, 1 ); |
|---|
| 551 | return; |
|---|
| 552 | } |
|---|
| 553 | |
|---|
| 554 | if ( $saveCmd eq 'repRev' ) { |
|---|
| 555 | |
|---|
| 556 | # replace top revision with the text from the query, trying to |
|---|
| 557 | # make it look as much like the original as possible. The query |
|---|
| 558 | # text is expected to contain %META as well as text. |
|---|
| 559 | my $meta = |
|---|
| 560 | new Foswiki::Meta( $session, $web, $topic, $query->param('text') ); |
|---|
| 561 | my $saveOpts = { |
|---|
| 562 | timetravel => 1, |
|---|
| 563 | operation => 'cmd', |
|---|
| 564 | }; |
|---|
| 565 | try { |
|---|
| 566 | $store->repRev( $user, $web, $topic, $meta->text(), $meta, |
|---|
| 567 | $saveOpts ); |
|---|
| 568 | } |
|---|
| 569 | catch Error::Simple with { |
|---|
| 570 | throw Foswiki::OopsException( |
|---|
| 571 | 'attention', |
|---|
| 572 | def => 'save_error', |
|---|
| 573 | web => $web, |
|---|
| 574 | topic => $topic, |
|---|
| 575 | params => [ shift->{-text} ] |
|---|
| 576 | ); |
|---|
| 577 | }; |
|---|
| 578 | |
|---|
| 579 | $session->redirect( $redirecturl, undef, |
|---|
| 580 | ( $saveaction ne 'checkpoint' ) ); |
|---|
| 581 | return; |
|---|
| 582 | } |
|---|
| 583 | |
|---|
| 584 | my ( $newMeta, $newText, $saveOpts, $merged ) = |
|---|
| 585 | buildNewTopic( $session, 'save' ); |
|---|
| 586 | |
|---|
| 587 | if ( $saveaction =~ /^(save|checkpoint)$/ ) { |
|---|
| 588 | $session->{plugins} |
|---|
| 589 | ->dispatch( 'afterEditHandler', $newText, $topic, $web, $newMeta ); |
|---|
| 590 | } |
|---|
| 591 | |
|---|
| 592 | try { |
|---|
| 593 | $store->saveTopic( $user, $web, $topic, $newText, $newMeta, $saveOpts ); |
|---|
| 594 | } |
|---|
| 595 | catch Error::Simple with { |
|---|
| 596 | throw Foswiki::OopsException( |
|---|
| 597 | 'attention', |
|---|
| 598 | def => 'save_error', |
|---|
| 599 | web => $web, |
|---|
| 600 | topic => $topic, |
|---|
| 601 | params => [ shift->{-text} ] |
|---|
| 602 | ); |
|---|
| 603 | }; |
|---|
| 604 | |
|---|
| 605 | my $lease = $store->getLease( $web, $topic ); |
|---|
| 606 | |
|---|
| 607 | # clear the lease, if (and only if) we own it |
|---|
| 608 | if ( $lease && $lease->{user} eq $user ) { |
|---|
| 609 | $store->clearLease( $web, $topic ); |
|---|
| 610 | } |
|---|
| 611 | |
|---|
| 612 | if ($merged) { |
|---|
| 613 | throw Foswiki::OopsException( |
|---|
| 614 | 'attention', |
|---|
| 615 | def => 'merge_notice', |
|---|
| 616 | web => $web, |
|---|
| 617 | topic => $topic, |
|---|
| 618 | params => $merged |
|---|
| 619 | ); |
|---|
| 620 | } |
|---|
| 621 | |
|---|
| 622 | $session->redirect( $redirecturl, undef, ( $saveaction ne 'checkpoint' ) ); |
|---|
| 623 | } |
|---|
| 624 | |
|---|
| 625 | 1; |
|---|
| 626 | __DATA__ |
|---|
| 627 | # Module of Foswiki - The Free and Open Source Wiki, http://foswiki.org/ |
|---|
| 628 | # |
|---|
| 629 | # Copyright (C) 2008 Foswiki Contributors. Foswiki Contributors |
|---|
| 630 | # are listed in the AUTHORS file in the root of this distribution. |
|---|
| 631 | # NOTE: Please extend that file, not this notice. |
|---|
| 632 | # |
|---|
| 633 | # Additional copyrights apply to some or all of the code in this |
|---|
| 634 | # file as follows: |
|---|
| 635 | # |
|---|
| 636 | # Copyright (C) 1999-2007 Peter Thoeny, peter@thoeny.org |
|---|
| 637 | # and TWiki Contributors. All Rights Reserved. TWiki Contributors |
|---|
| 638 | # are listed in the AUTHORS file in the root of this distribution. |
|---|
| 639 | # Based on parts of Ward Cunninghams original Wiki and JosWiki. |
|---|
| 640 | # Copyright (C) 1998 Markus Peter - SPiN GmbH (warpi@spin.de) |
|---|
| 641 | # Some changes by Dave Harris (drh@bhresearch.co.uk) incorporated |
|---|
| 642 | # |
|---|
| 643 | # This program is free software; you can redistribute it and/or |
|---|
| 644 | # modify it under the terms of the GNU General Public License |
|---|
| 645 | # as published by the Free Software Foundation; either version 2 |
|---|
| 646 | # of the License, or (at your option) any later version. For |
|---|
| 647 | # more details read LICENSE in the root of this distribution. |
|---|
| 648 | # |
|---|
| 649 | # This program is distributed in the hope that it will be useful, |
|---|
| 650 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 651 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
|---|
| 652 | # |
|---|
| 653 | # As per the GPL, removal of this notice is prohibited. |
|---|