| 1 | package MT::CMS::Tools; |
|---|
| 2 | |
|---|
| 3 | use strict; |
|---|
| 4 | |
|---|
| 5 | use MT::I18N qw( encode_text ); |
|---|
| 6 | use MT::Util qw( encode_url encode_html decode_html encode_js ); |
|---|
| 7 | |
|---|
| 8 | sub system_check { |
|---|
| 9 | my $app = shift; |
|---|
| 10 | |
|---|
| 11 | if ( my $blog_id = $app->param('blog_id') ) { |
|---|
| 12 | return $app->redirect( |
|---|
| 13 | $app->uri( |
|---|
| 14 | 'mode' => 'view_log', |
|---|
| 15 | args => { blog_id => $blog_id } |
|---|
| 16 | ) |
|---|
| 17 | ); |
|---|
| 18 | } |
|---|
| 19 | |
|---|
| 20 | my %param; |
|---|
| 21 | # licensed user count: someone who has logged in within 90 days |
|---|
| 22 | my $sess_class = $app->model('session'); |
|---|
| 23 | my $from = time - ( 60 * 60 * 24 * 90 + 60 * 60 * 24 ); |
|---|
| 24 | $sess_class->remove( |
|---|
| 25 | { kind => 'UA', start => [ undef, $from ] }, |
|---|
| 26 | { range => { start => 1 } } |
|---|
| 27 | ); |
|---|
| 28 | $param{licensed_user_count} = $sess_class->count( { kind => 'UA' } ); |
|---|
| 29 | |
|---|
| 30 | my $author_class = $app->model('author'); |
|---|
| 31 | $param{user_count} = $author_class->count( |
|---|
| 32 | { type => MT::Author::AUTHOR() } ); |
|---|
| 33 | |
|---|
| 34 | # commeters: users with only comment permission and MT::Author::COMMENTER |
|---|
| 35 | my $cmntrs = $author_class->count( |
|---|
| 36 | { type => MT::Author::COMMENTER() } ); |
|---|
| 37 | |
|---|
| 38 | my @perms = $app->model('permission')->load( |
|---|
| 39 | { |
|---|
| 40 | permissions => "%'comment'%", |
|---|
| 41 | blog_id => '0', |
|---|
| 42 | }, |
|---|
| 43 | { |
|---|
| 44 | 'like' => { 'permissions' => 1 }, |
|---|
| 45 | 'not' => { 'blog_id' => 1 }, |
|---|
| 46 | } |
|---|
| 47 | ); |
|---|
| 48 | @perms = grep { $_->permissions =~ m/'comment'/ } @perms; |
|---|
| 49 | $param{commenter_count} = scalar(@perms) + $cmntrs; |
|---|
| 50 | $param{screen_id} = "system-check"; |
|---|
| 51 | $param{syscheck_html} = get_syscheck_content($app) || ''; |
|---|
| 52 | |
|---|
| 53 | $app->load_tmpl( 'system_check.tmpl', \%param ); |
|---|
| 54 | } |
|---|
| 55 | |
|---|
| 56 | sub get_syscheck_content { |
|---|
| 57 | my $app = shift; |
|---|
| 58 | |
|---|
| 59 | my $syscheck_url = $app->base . $app->mt_path . $app->config('CheckScript') . |
|---|
| 60 | '?view=tools&version=' . MT->version_id; |
|---|
| 61 | if ( $syscheck_url && $syscheck_url ne 'disable' ) { |
|---|
| 62 | my $SYSCHECKCACHE_TIMEOUT = 60 * 60 * 24; |
|---|
| 63 | my $sess_class = $app->model('session'); |
|---|
| 64 | my ($syscheck_object) = (""); |
|---|
| 65 | my $retries = 0; |
|---|
| 66 | $syscheck_object = $sess_class->load( { id => 'SC' } ); |
|---|
| 67 | if ( $syscheck_object |
|---|
| 68 | && ( $syscheck_object->start() < ( time - $SYSCHECKCACHE_TIMEOUT ) ) ) |
|---|
| 69 | { |
|---|
| 70 | $syscheck_object->remove; |
|---|
| 71 | $syscheck_object = undef; |
|---|
| 72 | } |
|---|
| 73 | return encode_text( $syscheck_object->data(), 'utf-8', undef ) |
|---|
| 74 | if ($syscheck_object); |
|---|
| 75 | |
|---|
| 76 | my $ua = $app->new_ua({ timeout => 20 }); |
|---|
| 77 | return unless $ua; |
|---|
| 78 | $ua->max_size(undef) if $ua->can('max_size'); |
|---|
| 79 | |
|---|
| 80 | my $req = new HTTP::Request( GET => $syscheck_url ); |
|---|
| 81 | my $resp = $ua->request($req); |
|---|
| 82 | return unless $resp->is_success(); |
|---|
| 83 | my $result = $resp->content(); |
|---|
| 84 | if ($result) { |
|---|
| 85 | require MT::Sanitize; |
|---|
| 86 | |
|---|
| 87 | # allowed html |
|---|
| 88 | my $spec = '* style class id,ul,li,div,span,br,h2,h3,strong,code,blockquote,p'; |
|---|
| 89 | $result = MT::Sanitize->sanitize( $result, $spec ); |
|---|
| 90 | $syscheck_object = MT::Session->new(); |
|---|
| 91 | $syscheck_object->set_values( |
|---|
| 92 | { |
|---|
| 93 | id => 'SC', |
|---|
| 94 | kind => 'SC', |
|---|
| 95 | start => time(), |
|---|
| 96 | data => $result |
|---|
| 97 | } |
|---|
| 98 | ); |
|---|
| 99 | $syscheck_object->save(); |
|---|
| 100 | $result = encode_text( $result, 'utf-8', undef ); |
|---|
| 101 | } |
|---|
| 102 | return $result; |
|---|
| 103 | } |
|---|
| 104 | } |
|---|
| 105 | |
|---|
| 106 | sub start_recover { |
|---|
| 107 | my $app = shift; |
|---|
| 108 | $app->add_breadcrumb( $app->translate('Password Recovery') ); |
|---|
| 109 | $app->load_tmpl('dialog/recover.tmpl'); |
|---|
| 110 | } |
|---|
| 111 | |
|---|
| 112 | sub recover_password { |
|---|
| 113 | my $app = shift; |
|---|
| 114 | my $q = $app->param; |
|---|
| 115 | my $name = $q->param('name'); |
|---|
| 116 | my $class = ref $app eq 'MT::App::Upgrader' ? 'MT::BasicAuthor' : $app->model('author'); |
|---|
| 117 | eval "use $class;"; |
|---|
| 118 | my @author = $class->load( { name => $name } ); |
|---|
| 119 | my $author; |
|---|
| 120 | foreach (@author) { |
|---|
| 121 | next unless $_->password && ( $_->password ne '(none)' ); |
|---|
| 122 | $author = $_; |
|---|
| 123 | } |
|---|
| 124 | |
|---|
| 125 | my ( $rc, $res ) = |
|---|
| 126 | reset_password( $app, $author, $q->param('hint'), $name ); |
|---|
| 127 | |
|---|
| 128 | if ($rc) { |
|---|
| 129 | $app->add_breadcrumb( $app->translate('Password Recovery') ); |
|---|
| 130 | $app->load_tmpl( |
|---|
| 131 | 'dialog/recover.tmpl', |
|---|
| 132 | { |
|---|
| 133 | recovered => 1, |
|---|
| 134 | email => $author->email |
|---|
| 135 | } |
|---|
| 136 | ); |
|---|
| 137 | } |
|---|
| 138 | else { |
|---|
| 139 | $app->error($res); |
|---|
| 140 | } |
|---|
| 141 | } |
|---|
| 142 | |
|---|
| 143 | sub do_list_action { |
|---|
| 144 | my $app = shift; |
|---|
| 145 | $app->validate_magic or return; |
|---|
| 146 | |
|---|
| 147 | # plugin_action_selector should always (?) be in the query; use it? |
|---|
| 148 | my $action_name = $app->param('action_name'); |
|---|
| 149 | my $type = $app->param('_type'); |
|---|
| 150 | my ($the_action) = |
|---|
| 151 | ( grep { $_->{key} eq $action_name } @{ $app->list_actions($type) } ); |
|---|
| 152 | return $app->errtrans( "That action ([_1]) is apparently not implemented!", |
|---|
| 153 | $action_name ) |
|---|
| 154 | unless $the_action; |
|---|
| 155 | |
|---|
| 156 | unless ( ref( $the_action->{code} ) ) { |
|---|
| 157 | if ( my $plugin = $the_action->{plugin} ) { |
|---|
| 158 | $the_action->{code} = |
|---|
| 159 | $app->handler_to_coderef( $the_action->{handler} |
|---|
| 160 | || $the_action->{code} ); |
|---|
| 161 | } |
|---|
| 162 | } |
|---|
| 163 | $the_action->{code}->($app); |
|---|
| 164 | } |
|---|
| 165 | |
|---|
| 166 | sub do_page_action { |
|---|
| 167 | my $app = shift; |
|---|
| 168 | |
|---|
| 169 | # plugin_action_selector should always (?) be in the query; use it? |
|---|
| 170 | my $action_name = $app->param('action_name'); |
|---|
| 171 | my $type = $app->param('_type'); |
|---|
| 172 | my ($the_action) = |
|---|
| 173 | ( grep { $_->{key} eq $action_name } @{ $app->page_actions($type) } ); |
|---|
| 174 | return $app->errtrans( "That action ([_1]) is apparently not implemented!", |
|---|
| 175 | $action_name ) |
|---|
| 176 | unless $the_action; |
|---|
| 177 | |
|---|
| 178 | unless ( ref( $the_action->{code} ) ) { |
|---|
| 179 | if ( my $plugin = $the_action->{plugin} ) { |
|---|
| 180 | $the_action->{code} = |
|---|
| 181 | $app->handler_to_coderef( $the_action->{handler} |
|---|
| 182 | || $the_action->{code} ); |
|---|
| 183 | } |
|---|
| 184 | } |
|---|
| 185 | $the_action->{code}->($app); |
|---|
| 186 | } |
|---|
| 187 | |
|---|
| 188 | sub cfg_system_general { |
|---|
| 189 | my $app = shift; |
|---|
| 190 | my %param; |
|---|
| 191 | if ( $app->param('blog_id') ) { |
|---|
| 192 | return $app->return_to_dashboard( redirect => 1 ); |
|---|
| 193 | } |
|---|
| 194 | |
|---|
| 195 | return $app->errtrans("Permission denied.") |
|---|
| 196 | unless $app->user->is_superuser(); |
|---|
| 197 | my $cfg = $app->config; |
|---|
| 198 | $app->add_breadcrumb( $app->translate('General Settings') ); |
|---|
| 199 | $param{nav_config} = 1; |
|---|
| 200 | $param{nav_settings} = 1; |
|---|
| 201 | $param{languages} = |
|---|
| 202 | $app->languages_list( $app->config('DefaultUserLanguage') ); |
|---|
| 203 | my $tag_delim = $app->config('DefaultUserTagDelimiter') || 'comma'; |
|---|
| 204 | $param{"tag_delim_$tag_delim"} = 1; |
|---|
| 205 | |
|---|
| 206 | ( my $tz = $app->config('DefaultTimezone') ) =~ s![-\.]!_!g; |
|---|
| 207 | $tz =~ s!_00$!!; |
|---|
| 208 | $param{ 'server_offset_' . $tz } = 1; |
|---|
| 209 | |
|---|
| 210 | $param{default_site_root} = $app->config('DefaultSiteRoot'); |
|---|
| 211 | $param{default_site_url} = $app->config('DefaultSiteURL'); |
|---|
| 212 | $param{personal_weblog_readonly} = |
|---|
| 213 | $app->config->is_readonly('NewUserAutoProvisioning'); |
|---|
| 214 | $param{personal_weblog} = $app->config->NewUserAutoProvisioning ? 1 : 0; |
|---|
| 215 | if ( my $id = $param{new_user_template_blog_id} = |
|---|
| 216 | $app->config('NewUserTemplateBlogId') || '' ) |
|---|
| 217 | { |
|---|
| 218 | my $blog = MT::Blog->load($id); |
|---|
| 219 | if ($blog) { |
|---|
| 220 | $param{new_user_template_blog_name} = $blog->name; |
|---|
| 221 | } |
|---|
| 222 | else { |
|---|
| 223 | $app->config( 'NewUserTemplateBlogId', undef, 1 ); |
|---|
| 224 | $cfg->save_config(); |
|---|
| 225 | delete $param{new_user_template_blog_id}; |
|---|
| 226 | } |
|---|
| 227 | } |
|---|
| 228 | $param{system_email_address} = $cfg->EmailAddressMain; |
|---|
| 229 | $param{saved} = $app->param('saved'); |
|---|
| 230 | $param{error} = $app->param('error'); |
|---|
| 231 | $param{screen_class} = "settings-screen system-general-settings"; |
|---|
| 232 | $app->load_tmpl( 'cfg_system_general.tmpl', \%param ); |
|---|
| 233 | } |
|---|
| 234 | |
|---|
| 235 | sub save_cfg_system_general { |
|---|
| 236 | my $app = shift; |
|---|
| 237 | $app->validate_magic or return; |
|---|
| 238 | return $app->errtrans("Permission denied.") |
|---|
| 239 | unless $app->user->is_superuser(); |
|---|
| 240 | |
|---|
| 241 | my $cfg = $app->config; |
|---|
| 242 | $app->config( 'EmailAddressMain', |
|---|
| 243 | $app->param('system_email_address') || undef, 1 ); |
|---|
| 244 | |
|---|
| 245 | $cfg->save_config(); |
|---|
| 246 | |
|---|
| 247 | my $args = (); |
|---|
| 248 | $args->{saved} = 1; |
|---|
| 249 | $app->redirect( |
|---|
| 250 | $app->uri( |
|---|
| 251 | 'mode' => 'cfg_system', |
|---|
| 252 | args => $args |
|---|
| 253 | ) |
|---|
| 254 | ); |
|---|
| 255 | } |
|---|
| 256 | |
|---|
| 257 | sub upgrade { |
|---|
| 258 | my $app = shift; |
|---|
| 259 | |
|---|
| 260 | # check for an empty database... no author table would do it... |
|---|
| 261 | my $driver = MT::Object->driver; |
|---|
| 262 | my $upgrade_script = $app->config('UpgradeScript'); |
|---|
| 263 | my $user_class = MT->model('author'); |
|---|
| 264 | if ( !$driver || !$driver->table_exists($user_class) ) { |
|---|
| 265 | return $app->redirect( $app->path |
|---|
| 266 | . $upgrade_script |
|---|
| 267 | . $app->uri_params( mode => 'install' ) ); |
|---|
| 268 | } |
|---|
| 269 | |
|---|
| 270 | return $app->redirect( $app->path . $upgrade_script ); |
|---|
| 271 | } |
|---|
| 272 | |
|---|
| 273 | sub recover_profile_password { |
|---|
| 274 | my $app = shift; |
|---|
| 275 | $app->validate_magic or return; |
|---|
| 276 | return $app->errtrans("Permission denied.") |
|---|
| 277 | unless $app->user->is_superuser(); |
|---|
| 278 | |
|---|
| 279 | my $q = $app->param; |
|---|
| 280 | |
|---|
| 281 | require MT::Auth; |
|---|
| 282 | require MT::Log; |
|---|
| 283 | if ( !MT::Auth->can_recover_password ) { |
|---|
| 284 | $app->log( |
|---|
| 285 | { |
|---|
| 286 | message => $app->translate( |
|---|
| 287 | "Invalid password recovery attempt; can't recover password in this configuration" |
|---|
| 288 | ), |
|---|
| 289 | level => MT::Log::SECURITY(), |
|---|
| 290 | class => 'system', |
|---|
| 291 | category => 'recover_profile_password', |
|---|
| 292 | } |
|---|
| 293 | ); |
|---|
| 294 | return $app->error("Can't recover password in this configuration"); |
|---|
| 295 | } |
|---|
| 296 | |
|---|
| 297 | my $author_id = $q->param('author_id'); |
|---|
| 298 | my $author = MT::Author->load($author_id); |
|---|
| 299 | |
|---|
| 300 | return $app->error( $app->translate("Invalid author_id") ) |
|---|
| 301 | if !$author || $author->type != MT::Author::AUTHOR() || !$author_id; |
|---|
| 302 | |
|---|
| 303 | my ( $rc, $res ) = |
|---|
| 304 | reset_password( $app, $author, $author->hint, $author->name ); |
|---|
| 305 | |
|---|
| 306 | if ($rc) { |
|---|
| 307 | my $url = $app->uri( |
|---|
| 308 | 'mode' => 'view', |
|---|
| 309 | args => { _type => 'author', recovered => 1, id => $author_id } |
|---|
| 310 | ); |
|---|
| 311 | $app->redirect($url); |
|---|
| 312 | } |
|---|
| 313 | else { |
|---|
| 314 | $app->error($res); |
|---|
| 315 | } |
|---|
| 316 | } |
|---|
| 317 | |
|---|
| 318 | sub start_backup { |
|---|
| 319 | my $app = shift; |
|---|
| 320 | my $user = $app->user; |
|---|
| 321 | my $blog_id = $app->param('blog_id'); |
|---|
| 322 | my $perms = $app->permissions; |
|---|
| 323 | |
|---|
| 324 | unless ( $user->is_superuser ) { |
|---|
| 325 | return $app->errtrans("Permission denied.") |
|---|
| 326 | unless defined($blog_id) && $perms->can_administer_blog; |
|---|
| 327 | } |
|---|
| 328 | |
|---|
| 329 | my %param = (); |
|---|
| 330 | if ( defined($blog_id) ) { |
|---|
| 331 | $param{blog_id} = $blog_id; |
|---|
| 332 | $app->add_breadcrumb( $app->translate('Backup') ); |
|---|
| 333 | } |
|---|
| 334 | else { |
|---|
| 335 | $app->add_breadcrumb( $app->translate('Backup & Restore') ); |
|---|
| 336 | } |
|---|
| 337 | $param{system_overview_nav} = 1 unless $blog_id; |
|---|
| 338 | $param{nav_backup} = 1; |
|---|
| 339 | require MT::Util::Archive; |
|---|
| 340 | my @formats = MT::Util::Archive->available_formats(); |
|---|
| 341 | $param{archive_formats} = \@formats; |
|---|
| 342 | |
|---|
| 343 | my $limit = $app->config('CGIMaxUpload') || 2048; |
|---|
| 344 | $param{over_300} = 1 if $limit >= 300 * 1024; |
|---|
| 345 | $param{over_500} = 1 if $limit >= 500 * 1024; |
|---|
| 346 | $param{over_1024} = 1 if $limit >= 1024 * 1024; |
|---|
| 347 | $param{over_2048} = 1 if $limit >= 2048 * 1024; |
|---|
| 348 | |
|---|
| 349 | my $tmp = $app->config('TempDir'); |
|---|
| 350 | unless ( ( -d $tmp ) && ( -w $tmp ) ) { |
|---|
| 351 | $param{error} = |
|---|
| 352 | $app->translate( |
|---|
| 353 | 'Temporary directory needs to be writable for backup to work correctly. Please check TempDir configuration directive.' |
|---|
| 354 | ); |
|---|
| 355 | } |
|---|
| 356 | $app->load_tmpl( 'backup.tmpl', \%param ); |
|---|
| 357 | } |
|---|
| 358 | |
|---|
| 359 | sub start_restore { |
|---|
| 360 | my $app = shift; |
|---|
| 361 | my $user = $app->user; |
|---|
| 362 | my $blog_id = $app->param('blog_id'); |
|---|
| 363 | my $perms = $app->permissions; |
|---|
| 364 | |
|---|
| 365 | unless ( $user->is_superuser ) { |
|---|
| 366 | return $app->errtrans("Permission denied.") |
|---|
| 367 | unless defined($blog_id) && $perms->can_administer_blog; |
|---|
| 368 | } |
|---|
| 369 | |
|---|
| 370 | my %param = (); |
|---|
| 371 | if ( defined($blog_id) ) { |
|---|
| 372 | $param{blog_id} = $blog_id; |
|---|
| 373 | $app->add_breadcrumb( $app->translate('Backup') ); |
|---|
| 374 | } |
|---|
| 375 | else { |
|---|
| 376 | $app->add_breadcrumb( $app->translate('Backup & Restore') ); |
|---|
| 377 | } |
|---|
| 378 | $param{system_overview_nav} = 1 unless $blog_id; |
|---|
| 379 | $param{nav_backup} = 1; |
|---|
| 380 | |
|---|
| 381 | eval "require XML::SAX"; |
|---|
| 382 | $param{missing_sax} = 1 if $@; |
|---|
| 383 | |
|---|
| 384 | my $tmp = $app->config('TempDir'); |
|---|
| 385 | unless ( ( -d $tmp ) && ( -w $tmp ) ) { |
|---|
| 386 | $param{error} = |
|---|
| 387 | $app->translate( |
|---|
| 388 | 'Temporary directory needs to be writable for restore to work correctly. Please check TempDir configuration directive.' |
|---|
| 389 | ); |
|---|
| 390 | } |
|---|
| 391 | $app->load_tmpl( 'restore.tmpl', \%param ); |
|---|
| 392 | } |
|---|
| 393 | |
|---|
| 394 | sub backup { |
|---|
| 395 | my $app = shift; |
|---|
| 396 | my $user = $app->user; |
|---|
| 397 | my $q = $app->param; |
|---|
| 398 | my $blog_id = $q->param('blog_id'); |
|---|
| 399 | my $perms = $app->permissions; |
|---|
| 400 | unless ( $user->is_superuser ) { |
|---|
| 401 | return $app->errtrans("Permission denied.") |
|---|
| 402 | unless defined($blog_id) && $perms->can_administer_blog; |
|---|
| 403 | } |
|---|
| 404 | $app->validate_magic() or return; |
|---|
| 405 | |
|---|
| 406 | my $blog_ids = $q->param('backup_what'); |
|---|
| 407 | |
|---|
| 408 | my $size = $q->param('size_limit') || 0; |
|---|
| 409 | return $app->errtrans( '[_1] is not a number.', |
|---|
| 410 | encode_html($size) ) |
|---|
| 411 | if $size !~ /^\d+$/; |
|---|
| 412 | |
|---|
| 413 | my @blog_ids = split ',', $blog_ids; |
|---|
| 414 | |
|---|
| 415 | my $archive = $q->param('backup_archive_format'); |
|---|
| 416 | my $enc = $app->charset || 'utf-8'; |
|---|
| 417 | my @ts = gmtime(time); |
|---|
| 418 | my $ts = sprintf "%04d-%02d-%02d-%02d-%02d-%02d", $ts[5] + 1900, $ts[4] + 1, |
|---|
| 419 | @ts[ 3, 2, 1, 0 ]; |
|---|
| 420 | my $file = "Movable_Type-$ts" . '-Backup'; |
|---|
| 421 | |
|---|
| 422 | my $param = { return_args => '__mode=start_backup' }; |
|---|
| 423 | $app->{no_print_body} = 1; |
|---|
| 424 | $app->add_breadcrumb( |
|---|
| 425 | $app->translate('Backup & Restore'), |
|---|
| 426 | $app->uri( mode => 'start_backup' ) |
|---|
| 427 | ); |
|---|
| 428 | $app->add_breadcrumb( $app->translate('Backup') ); |
|---|
| 429 | $param->{system_overview_nav} = 1 if defined($blog_ids) && $blog_ids; |
|---|
| 430 | $param->{blog_id} = $blog_id if $blog_id; |
|---|
| 431 | $param->{blog_ids} = $blog_ids if $blog_ids; |
|---|
| 432 | $param->{nav_backup} = 1; |
|---|
| 433 | |
|---|
| 434 | local $| = 1; |
|---|
| 435 | $app->send_http_header('text/html'); |
|---|
| 436 | $app->print( $app->build_page( 'include/backup_start.tmpl', $param ) ); |
|---|
| 437 | require File::Temp; |
|---|
| 438 | require File::Spec; |
|---|
| 439 | use File::Copy; |
|---|
| 440 | my $temp_dir = $app->config('TempDir'); |
|---|
| 441 | |
|---|
| 442 | require MT::BackupRestore; |
|---|
| 443 | my $count_term = |
|---|
| 444 | $blog_id |
|---|
| 445 | ? { class => '*', blog_id => $blog_id } |
|---|
| 446 | : { class => '*' }; |
|---|
| 447 | my $num_assets = $app->model('asset')->count($count_term); |
|---|
| 448 | my $printer; |
|---|
| 449 | my $splitter; |
|---|
| 450 | my $finisher; |
|---|
| 451 | my $progress = sub { _progress($app, @_); }; |
|---|
| 452 | my $fh; |
|---|
| 453 | my $fname; |
|---|
| 454 | my $arc_buf; |
|---|
| 455 | |
|---|
| 456 | if ( !( $size || $num_assets ) ) { |
|---|
| 457 | $splitter = sub { }; |
|---|
| 458 | |
|---|
| 459 | if ( '0' eq $archive ) { |
|---|
| 460 | ( $fh, my $filepath ) = |
|---|
| 461 | File::Temp::tempfile( 'xml.XXXXXXXX', DIR => $temp_dir ); |
|---|
| 462 | ( my $vol, my $dir, $fname ) = File::Spec->splitpath($filepath); |
|---|
| 463 | $printer = |
|---|
| 464 | sub { my ($data) = @_; print $fh $data; return length($data); }; |
|---|
| 465 | $finisher = sub { |
|---|
| 466 | my ($asset_files) = @_; |
|---|
| 467 | close $fh; |
|---|
| 468 | _backup_finisher( $app, $fname, $param ); |
|---|
| 469 | }; |
|---|
| 470 | } |
|---|
| 471 | else { # archive/compress files |
|---|
| 472 | $printer = |
|---|
| 473 | sub { my ($data) = @_; $arc_buf .= $data; return length($data); }; |
|---|
| 474 | $finisher = sub { |
|---|
| 475 | require MT::Util::Archive; |
|---|
| 476 | my ($asset_files) = @_; |
|---|
| 477 | ( my $fh, my $filepath ) = |
|---|
| 478 | File::Temp::tempfile( $archive . '.XXXXXXXX', DIR => $temp_dir ); |
|---|
| 479 | ( my $vol, my $dir, $fname ) = File::Spec->splitpath($filepath); |
|---|
| 480 | close $fh; |
|---|
| 481 | unlink $filepath; |
|---|
| 482 | my $arc = MT::Util::Archive->new($archive, $filepath); |
|---|
| 483 | $arc->add_string( $arc_buf, "$file.xml" ); |
|---|
| 484 | $arc->add_string( |
|---|
| 485 | "<manifest xmlns='" |
|---|
| 486 | . MT::BackupRestore::NS_MOVABLETYPE() |
|---|
| 487 | . "'><file type='backup' name='$file.xml' /></manifest>", |
|---|
| 488 | "$file.manifest"); |
|---|
| 489 | $arc->close; |
|---|
| 490 | _backup_finisher( $app, $fname, $param ); |
|---|
| 491 | }; |
|---|
| 492 | } |
|---|
| 493 | } |
|---|
| 494 | else { |
|---|
| 495 | my @files; |
|---|
| 496 | my $filename = File::Spec->catfile( $temp_dir, $file . "-1.xml" ); |
|---|
| 497 | $fh = gensym(); |
|---|
| 498 | open $fh, ">$filename"; |
|---|
| 499 | my $url = |
|---|
| 500 | $app->uri |
|---|
| 501 | . "?__mode=backup_download&name=$file-1.xml&magic_token=" |
|---|
| 502 | . $app->current_magic; |
|---|
| 503 | $url .= "&blog_id=$blog_id" if defined($blog_id); |
|---|
| 504 | push @files, |
|---|
| 505 | { |
|---|
| 506 | url => $url, |
|---|
| 507 | filename => $file . "-1.xml" |
|---|
| 508 | }; |
|---|
| 509 | $printer = |
|---|
| 510 | sub { my ($data) = @_; print $fh $data; return length($data); }; |
|---|
| 511 | $splitter = sub { |
|---|
| 512 | my ($findex) = @_; |
|---|
| 513 | print $fh '</movabletype>'; |
|---|
| 514 | close $fh; |
|---|
| 515 | my $filename = |
|---|
| 516 | File::Spec->catfile( $temp_dir, $file . "-$findex.xml" ); |
|---|
| 517 | $fh = gensym(); |
|---|
| 518 | open $fh, ">$filename"; |
|---|
| 519 | my $url = |
|---|
| 520 | $app->uri |
|---|
| 521 | . "?__mode=backup_download&name=$file-$findex.xml&magic_token=" |
|---|
| 522 | . $app->current_magic; |
|---|
| 523 | $url .= "&blog_id=$blog_id" if defined($blog_id); |
|---|
| 524 | push @files, |
|---|
| 525 | { |
|---|
| 526 | url => $url, |
|---|
| 527 | filename => $file . "-$findex.xml" |
|---|
| 528 | }; |
|---|
| 529 | my $header .= |
|---|
| 530 | "<movabletype xmlns='" |
|---|
| 531 | . MT::BackupRestore::NS_MOVABLETYPE() . "'>\n"; |
|---|
| 532 | $header = "<?xml version='1.0' encoding='$enc'?>\n$header" |
|---|
| 533 | if $enc !~ m/utf-?8/i; |
|---|
| 534 | print $fh $header; |
|---|
| 535 | }; |
|---|
| 536 | $finisher = sub { |
|---|
| 537 | my ($asset_files) = @_; |
|---|
| 538 | close $fh; |
|---|
| 539 | my $filename = File::Spec->catfile( $temp_dir, "$file.manifest" ); |
|---|
| 540 | $fh = gensym(); |
|---|
| 541 | open $fh, ">$filename"; |
|---|
| 542 | print $fh "<manifest xmlns='" |
|---|
| 543 | . MT::BackupRestore::NS_MOVABLETYPE() . "'>\n"; |
|---|
| 544 | for my $file (@files) { |
|---|
| 545 | my $name = $file->{filename}; |
|---|
| 546 | print $fh "<file type='backup' name='$name' />\n"; |
|---|
| 547 | } |
|---|
| 548 | for my $id ( keys %$asset_files ) { |
|---|
| 549 | my $name = $id . '-' . $asset_files->{$id}->[2]; |
|---|
| 550 | my $tmp = File::Spec->catfile( $temp_dir, $name ); |
|---|
| 551 | unless ( copy( $asset_files->{$id}->[1], $tmp ) ) { |
|---|
| 552 | $app->log( |
|---|
| 553 | { |
|---|
| 554 | message => $app->translate( |
|---|
| 555 | 'Copying file [_1] to [_2] failed: [_3]', |
|---|
| 556 | $asset_files->{$id}->[1], |
|---|
| 557 | $tmp, $! |
|---|
| 558 | ), |
|---|
| 559 | level => MT::Log::INFO(), |
|---|
| 560 | class => 'system', |
|---|
| 561 | category => 'backup' |
|---|
| 562 | } |
|---|
| 563 | ); |
|---|
| 564 | next; |
|---|
| 565 | } |
|---|
| 566 | print $fh "<file type='asset' name='" |
|---|
| 567 | . $asset_files->{$id}->[2] |
|---|
| 568 | . "' asset_id='" |
|---|
| 569 | . $id |
|---|
| 570 | . "' />\n"; |
|---|
| 571 | my $url = |
|---|
| 572 | $app->uri |
|---|
| 573 | . "?__mode=backup_download&assetname=$name&magic_token=" |
|---|
| 574 | . $app->current_magic; |
|---|
| 575 | $url .= "&blog_id=$blog_id" if defined($blog_id); |
|---|
| 576 | push @files, |
|---|
| 577 | { |
|---|
| 578 | url => $url, |
|---|
| 579 | filename => $name, |
|---|
| 580 | }; |
|---|
| 581 | } |
|---|
| 582 | print $fh "</manifest>\n"; |
|---|
| 583 | close $fh; |
|---|
| 584 | my $url = |
|---|
| 585 | $app->uri |
|---|
| 586 | . "?__mode=backup_download&name=$file.manifest&magic_token=" |
|---|
| 587 | . $app->current_magic; |
|---|
| 588 | $url .= "&blog_id=$blog_id" if defined($blog_id); |
|---|
| 589 | push @files, |
|---|
| 590 | { |
|---|
| 591 | url => $url, |
|---|
| 592 | filename => "$file.manifest" |
|---|
| 593 | }; |
|---|
| 594 | if ( '0' eq $archive ) { |
|---|
| 595 | $param->{files_loop} = \@files; |
|---|
| 596 | $param->{tempdir} = $temp_dir; |
|---|
| 597 | my @fnames = map { $_->{filename} } @files; |
|---|
| 598 | _backup_finisher( $app, \@fnames, $param ); |
|---|
| 599 | } |
|---|
| 600 | else { |
|---|
| 601 | my ( $fh_arc, $filepath ) = |
|---|
| 602 | File::Temp::tempfile( $archive . '.XXXXXXXX', DIR => $temp_dir ); |
|---|
| 603 | ( my $vol, my $dir, $fname ) = File::Spec->splitpath($filepath); |
|---|
| 604 | require MT::Util::Archive; |
|---|
| 605 | close $fh_arc; |
|---|
| 606 | unlink $filepath; |
|---|
| 607 | my $arc = MT::Util::Archive->new($archive, $filepath); |
|---|
| 608 | for my $f (@files) { |
|---|
| 609 | $arc->add_file( $temp_dir, $f->{filename} ); |
|---|
| 610 | } |
|---|
| 611 | $arc->close; |
|---|
| 612 | # for safery, don't unlink before closing $arc here. |
|---|
| 613 | for my $f (@files) { |
|---|
| 614 | unlink File::Spec->catfile( $temp_dir, $f->{filename} ); |
|---|
| 615 | } |
|---|
| 616 | _backup_finisher( $app, $fname, $param ); |
|---|
| 617 | } |
|---|
| 618 | }; |
|---|
| 619 | } |
|---|
| 620 | |
|---|
| 621 | my @tsnow = gmtime(time); |
|---|
| 622 | my $metadata = { |
|---|
| 623 | backup_by => $app->user->name . '(ID: ' . $app->user->id . ')', |
|---|
| 624 | backup_on => sprintf( |
|---|
| 625 | "%04d-%02d-%02dT%02d:%02d:%02d", |
|---|
| 626 | $tsnow[5] + 1900, |
|---|
| 627 | $tsnow[4] + 1, |
|---|
| 628 | @tsnow[ 3, 2, 1, 0 ] |
|---|
| 629 | ), |
|---|
| 630 | backup_what => join( ',', @blog_ids ), |
|---|
| 631 | schema_version => $app->config('SchemaVersion'), |
|---|
| 632 | }; |
|---|
| 633 | MT::BackupRestore->backup( \@blog_ids, $printer, $splitter, $finisher, |
|---|
| 634 | $progress, $size * 1024, |
|---|
| 635 | $enc, $metadata ); |
|---|
| 636 | } |
|---|
| 637 | |
|---|
| 638 | sub backup_download { |
|---|
| 639 | my $app = shift; |
|---|
| 640 | my $user = $app->user; |
|---|
| 641 | my $blog_id = $app->param('blog_id'); |
|---|
| 642 | unless ( $user->is_superuser ) { |
|---|
| 643 | my $perms = $app->permissions; |
|---|
| 644 | return $app->errtrans("Permission denied.") |
|---|
| 645 | unless defined($blog_id) && $perms->can_administer_blog; |
|---|
| 646 | } |
|---|
| 647 | $app->validate_magic() or return; |
|---|
| 648 | my $filename = $app->param('filename'); |
|---|
| 649 | my $assetname = $app->param('assetname'); |
|---|
| 650 | my $temp_dir = $app->config('TempDir'); |
|---|
| 651 | my $newfilename; |
|---|
| 652 | if ( defined($assetname) ) { |
|---|
| 653 | my $sess = MT::Session->load( { kind => 'BU', name => $assetname } ); |
|---|
| 654 | if ( !defined($sess) || !$sess ) { |
|---|
| 655 | return $app->errtrans("Specified file was not found."); |
|---|
| 656 | } |
|---|
| 657 | $newfilename = $filename = $assetname; |
|---|
| 658 | $sess->remove; |
|---|
| 659 | } |
|---|
| 660 | elsif ( defined($filename) ) { |
|---|
| 661 | my $sess = MT::Session->load( { kind => 'BU', name => $filename } ); |
|---|
| 662 | if ( !defined($sess) || !$sess ) { |
|---|
| 663 | return $app->errtrans("Specified file was not found."); |
|---|
| 664 | } |
|---|
| 665 | my @ts = gmtime( $sess->start ); |
|---|
| 666 | my $ts = sprintf "%04d-%02d-%02d-%02d-%02d-%02d", $ts[5] + 1900, |
|---|
| 667 | $ts[4] + 1, @ts[ 3, 2, 1, 0 ]; |
|---|
| 668 | $newfilename = "Movable_Type-$ts" . '-Backup'; |
|---|
| 669 | $sess->remove; |
|---|
| 670 | } |
|---|
| 671 | else { |
|---|
| 672 | $newfilename = $app->param('name'); |
|---|
| 673 | return |
|---|
| 674 | if $newfilename !~ |
|---|
| 675 | /Movable_Type-\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}-Backup(?:-\d+)?\.\w+/; |
|---|
| 676 | $filename = $newfilename; |
|---|
| 677 | } |
|---|
| 678 | |
|---|
| 679 | require File::Spec; |
|---|
| 680 | my $fname = File::Spec->catfile( $temp_dir, $filename ); |
|---|
| 681 | |
|---|
| 682 | my $contenttype; |
|---|
| 683 | if ( !defined($assetname) && ( $filename =~ m/^xml\..+$/i ) ) { |
|---|
| 684 | my $enc = $app->charset || 'utf-8'; |
|---|
| 685 | $contenttype = "text/xml; charset=$enc"; |
|---|
| 686 | $newfilename .= '.xml'; |
|---|
| 687 | } |
|---|
| 688 | elsif ( $filename =~ m/^tgz\..+$/i ) { |
|---|
| 689 | $contenttype = 'application/x-tar-gz'; |
|---|
| 690 | $newfilename .= '.tar.gz'; |
|---|
| 691 | } |
|---|
| 692 | elsif ( $filename =~ m/^zip\..+$/i ) { |
|---|
| 693 | $contenttype = 'application/zip'; |
|---|
| 694 | $newfilename .= '.zip'; |
|---|
| 695 | } |
|---|
| 696 | else { |
|---|
| 697 | $contenttype = 'application/octet-stream'; |
|---|
| 698 | } |
|---|
| 699 | |
|---|
| 700 | if ( open( my $fh, "<", $fname ) ) { |
|---|
| 701 | binmode $fh; |
|---|
| 702 | $app->{no_print_body} = 1; |
|---|
| 703 | $app->set_header( |
|---|
| 704 | "Content-Disposition" => "attachment; filename=$newfilename" ); |
|---|
| 705 | $app->send_http_header($contenttype); |
|---|
| 706 | my $data; |
|---|
| 707 | while ( read $fh, my ($chunk), 8192 ) { |
|---|
| 708 | $data .= $chunk; |
|---|
| 709 | } |
|---|
| 710 | close $fh; |
|---|
| 711 | $app->print($data); |
|---|
| 712 | $app->log( |
|---|
| 713 | { |
|---|
| 714 | message => $app->translate( |
|---|
| 715 | '[_1] successfully downloaded backup file ([_2])', |
|---|
| 716 | $app->user->name, $fname |
|---|
| 717 | ), |
|---|
| 718 | level => MT::Log::INFO(), |
|---|
| 719 | class => 'system', |
|---|
| 720 | category => 'restore' |
|---|
| 721 | } |
|---|
| 722 | ); |
|---|
| 723 | unlink $fname; |
|---|
| 724 | } |
|---|
| 725 | else { |
|---|
| 726 | $app->errtrans('Specified file was not found.'); |
|---|
| 727 | } |
|---|
| 728 | } |
|---|
| 729 | |
|---|
| 730 | sub restore { |
|---|
| 731 | my $app = shift; |
|---|
| 732 | my $user = $app->user; |
|---|
| 733 | return $app->errtrans("Permission denied.") if !$user->is_superuser; |
|---|
| 734 | $app->validate_magic() or return; |
|---|
| 735 | |
|---|
| 736 | my $q = $app->param; |
|---|
| 737 | |
|---|
| 738 | my ($fh) = $app->upload_info('file'); |
|---|
| 739 | my $uploaded = $q->param('file'); |
|---|
| 740 | my ( $volume, $directories, $uploaded_filename ) = |
|---|
| 741 | File::Spec->splitpath($uploaded) |
|---|
| 742 | if defined($uploaded); |
|---|
| 743 | if ( defined($uploaded_filename) |
|---|
| 744 | && ( $uploaded_filename =~ /^.+\.manifest$/i ) ) |
|---|
| 745 | { |
|---|
| 746 | return restore_upload_manifest( $app, $fh ); |
|---|
| 747 | } |
|---|
| 748 | |
|---|
| 749 | my $param = { return_args => '__mode=dashboard' }; |
|---|
| 750 | |
|---|
| 751 | $app->add_breadcrumb( |
|---|
| 752 | $app->translate('Backup & Restore'), |
|---|
| 753 | $app->uri( mode => 'start_restore' ) |
|---|
| 754 | ); |
|---|
| 755 | $app->add_breadcrumb( $app->translate('Restore') ); |
|---|
| 756 | $param->{system_overview_nav} = 1; |
|---|
| 757 | $param->{nav_backup} = 1; |
|---|
| 758 | |
|---|
| 759 | $app->{no_print_body} = 1; |
|---|
| 760 | |
|---|
| 761 | local $| = 1; |
|---|
| 762 | $app->send_http_header('text/html'); |
|---|
| 763 | |
|---|
| 764 | $app->print( $app->build_page( 'restore_start.tmpl', $param ) ); |
|---|
| 765 | |
|---|
| 766 | require File::Path; |
|---|
| 767 | |
|---|
| 768 | my $error = ''; |
|---|
| 769 | my $result; |
|---|
| 770 | if (!$fh) { |
|---|
| 771 | $param->{restore_upload} = 0; |
|---|
| 772 | my $dir = $app->config('ImportPath'); |
|---|
| 773 | my ( $blog_ids, $asset_ids ) = restore_directory( $app, $dir, \$error ); |
|---|
| 774 | if ( defined $blog_ids ) { |
|---|
| 775 | $param->{open_dialog} = 1; |
|---|
| 776 | $param->{blog_ids} = join( ',', @$blog_ids ); |
|---|
| 777 | $param->{asset_ids} = join( ',', @$asset_ids ) |
|---|
| 778 | if defined $asset_ids; |
|---|
| 779 | $param->{tmp_dir} = $dir; |
|---|
| 780 | } |
|---|
| 781 | } |
|---|
| 782 | else { |
|---|
| 783 | $param->{restore_upload} = 1; |
|---|
| 784 | if ( $uploaded_filename =~ /^.+\.xml$/i ) { |
|---|
| 785 | my $blog_ids = restore_file( $app, $fh, \$error ); |
|---|
| 786 | if ( defined $blog_ids ) { |
|---|
| 787 | $param->{open_dialog} = 1; |
|---|
| 788 | $param->{blog_ids} = join( ',', @$blog_ids ); |
|---|
| 789 | } |
|---|
| 790 | } |
|---|
| 791 | else { |
|---|
| 792 | require MT::Util::Archive; |
|---|
| 793 | my $arc; |
|---|
| 794 | my $error; |
|---|
| 795 | if ( $uploaded_filename =~ /^.+\.tar(\.gz)?$/i ) { |
|---|
| 796 | $arc = MT::Util::Archive->new('tgz', $fh); |
|---|
| 797 | } |
|---|
| 798 | elsif ( $uploaded_filename =~ /^.+\.zip$/i ) { |
|---|
| 799 | $arc = MT::Util::Archive->new('zip', $fh); |
|---|
| 800 | } |
|---|
| 801 | else { |
|---|
| 802 | $error = |
|---|
| 803 | $app->translate( |
|---|
| 804 | 'Please use xml, tar.gz, zip, or manifest as a file extension.' |
|---|
| 805 | ); |
|---|
| 806 | } |
|---|
| 807 | unless ($arc) { |
|---|
| 808 | $result = 0; |
|---|
| 809 | $param->{restore_success} = 0; |
|---|
| 810 | if ($error) { |
|---|
| 811 | $param->{error} = $error; |
|---|
| 812 | } |
|---|
| 813 | else { |
|---|
| 814 | $error = MT->translate('Unknown file format'); |
|---|
| 815 | $app->log( |
|---|
| 816 | { |
|---|
| 817 | message => $error . ":$uploaded_filename", |
|---|
| 818 | level => MT::Log::WARNING(), |
|---|
| 819 | class => 'system', |
|---|
| 820 | category => 'restore', |
|---|
| 821 | metadata => MT::Util::Archive->errstr, |
|---|
| 822 | } |
|---|
| 823 | ); |
|---|
| 824 | } |
|---|
| 825 | $app->print( $error ); |
|---|
| 826 | $app->print( |
|---|
| 827 | $app->build_page( "restore_end.tmpl", $param ) ); |
|---|
| 828 | close $fh if $fh; |
|---|
| 829 | return 1; |
|---|
| 830 | } |
|---|
| 831 | my $temp_dir = $app->config('TempDir'); |
|---|
| 832 | require File::Temp; |
|---|
| 833 | my $tmp = File::Temp::tempdir( $uploaded_filename . 'XXXX', |
|---|
| 834 | DIR => $temp_dir ); |
|---|
| 835 | $arc->extract($tmp); |
|---|
| 836 | $arc->close; |
|---|
| 837 | my ( $blog_ids, $asset_ids ) = |
|---|
| 838 | restore_directory( $app, $tmp, \$error ); |
|---|
| 839 | if (defined $blog_ids) { |
|---|
| 840 | $param->{open_dialog} = 1; |
|---|
| 841 | $param->{blog_ids} = join( ',', @$blog_ids ) |
|---|
| 842 | if defined $blog_ids; |
|---|
| 843 | $param->{asset_ids} = join( ',', @$asset_ids ) |
|---|
| 844 | if defined $asset_ids; |
|---|
| 845 | $param->{tmp_dir} = $tmp; |
|---|
| 846 | } |
|---|
| 847 | } |
|---|
| 848 | } |
|---|
| 849 | $param->{restore_success} = !$error; |
|---|
| 850 | $param->{error} = $error if $error; |
|---|
| 851 | if ( ( exists $param->{open_dialog} ) && ( $param->{open_dialog} ) ) { |
|---|
| 852 | $param->{dialog_mode} = 'dialog_adjust_sitepath'; |
|---|
| 853 | $param->{dialog_params} = |
|---|
| 854 | 'magic_token=' |
|---|
| 855 | . $app->current_magic |
|---|
| 856 | . '&blog_ids=' |
|---|
| 857 | . $param->{blog_ids} |
|---|
| 858 | . '&asset_ids=' |
|---|
| 859 | . $param->{asset_ids} |
|---|
| 860 | . '&tmp_dir=' |
|---|
| 861 | . encode_url( $param->{tmp_dir} ); |
|---|
| 862 | if ( ( $param->{restore_upload} ) && ( $param->{restore_upload} ) ) { |
|---|
| 863 | $param->{dialog_params} .= '&restore_upload=1'; |
|---|
| 864 | } |
|---|
| 865 | if ( ( $param->{error} ) && ( $param->{error} ) ) { |
|---|
| 866 | $param->{dialog_params} .= |
|---|
| 867 | '&error=' . encode_url( $param->{error} ); |
|---|
| 868 | } |
|---|
| 869 | } |
|---|
| 870 | |
|---|
| 871 | $app->print( $app->build_page( "restore_end.tmpl", $param ) ); |
|---|
| 872 | close $fh if $fh; |
|---|
| 873 | 1; |
|---|
| 874 | } |
|---|
| 875 | |
|---|
| 876 | sub restore_premature_cancel { |
|---|
| 877 | my $app = shift; |
|---|
| 878 | my $user = $app->user; |
|---|
| 879 | return $app->errtrans("Permission denied.") if !$user->is_superuser; |
|---|
| 880 | $app->validate_magic() or return; |
|---|
| 881 | |
|---|
| 882 | require JSON; |
|---|
| 883 | my $deferred = JSON::jsonToObj( $app->param('deferred_json') ) |
|---|
| 884 | if $app->param('deferred_json'); |
|---|
| 885 | my $param = { restore_success => 1 }; |
|---|
| 886 | if ( defined $deferred && ( scalar( keys %$deferred ) ) ) { |
|---|
| 887 | _log_dirty_restore( $app, $deferred ); |
|---|
| 888 | my $log_url = $app->uri( mode => 'view_log', args => {} ); |
|---|
| 889 | $param->{restore_success} = 0; |
|---|
| 890 | my $message = |
|---|
| 891 | $app->translate( |
|---|
| 892 | 'Some objects were not restored because their parent objects were not restored.' |
|---|
| 893 | ); |
|---|
| 894 | $param->{error} = $message . ' ' |
|---|
| 895 | . $app->translate( |
|---|
| 896 | "Detailed information is in the <a href='javascript:void(0)' onclick='closeDialog(\"[_1]\")'>activity log</a>.", |
|---|
| 897 | $log_url |
|---|
| 898 | ); |
|---|
| 899 | } |
|---|
| 900 | else { |
|---|
| 901 | $app->log( |
|---|
| 902 | { |
|---|
| 903 | message => $app->translate( |
|---|
| 904 | '[_1] has canceled the multiple files restore operation prematurely.', |
|---|
| 905 | $app->user->name |
|---|
| 906 | ), |
|---|
| 907 | level => MT::Log::WARNING(), |
|---|
| 908 | class => 'system', |
|---|
| 909 | category => 'restore', |
|---|
| 910 | } |
|---|
| 911 | ); |
|---|
| 912 | } |
|---|
| 913 | $app->redirect( $app->uri( mode => 'view_log', args => {} ) ); |
|---|
| 914 | } |
|---|
| 915 | |
|---|
| 916 | sub adjust_sitepath { |
|---|
| 917 | my $app = shift; |
|---|
| 918 | my $user = $app->user; |
|---|
| 919 | return $app->errtrans("Permission denied.") if !$user->is_superuser; |
|---|
| 920 | $app->validate_magic() or return; |
|---|
| 921 | |
|---|
| 922 | require MT::BackupRestore; |
|---|
| 923 | |
|---|
| 924 | my $q = $app->param; |
|---|
| 925 | my $tmp_dir = $q->param('tmp_dir'); |
|---|
| 926 | my $error = $q->param('error') || q(); |
|---|
| 927 | my %asset_ids = split ',', $q->param('asset_ids'); |
|---|
| 928 | |
|---|
| 929 | $app->{no_print_body} = 1; |
|---|
| 930 | |
|---|
| 931 | local $| = 1; |
|---|
| 932 | $app->send_http_header('text/html'); |
|---|
| 933 | |
|---|
| 934 | $app->print( $app->build_page( 'dialog/restore_start.tmpl', {} ) ); |
|---|
| 935 | |
|---|
| 936 | my %error_assets; |
|---|
| 937 | my %blogs_meta; |
|---|
| 938 | my @p = $q->param; |
|---|
| 939 | foreach my $p (@p) { |
|---|
| 940 | next unless $p =~ /^site_path_(\d+)/; |
|---|
| 941 | my $id = $1; |
|---|
| 942 | my $blog = $app->model('blog')->load($id) |
|---|
| 943 | or return $app->error($app->translate('Can\'t load blog #[_1].', $id)); |
|---|
| 944 | my $old_site_path = scalar $q->param("old_site_path_$id"); |
|---|
| 945 | my $old_site_url = scalar $q->param("old_site_url_$id"); |
|---|
| 946 | my $site_path = scalar $q->param("site_path_$id") || q(); |
|---|
| 947 | my $site_url = scalar $q->param("site_url_$id") || q(); |
|---|
| 948 | $blog->site_path($site_path); |
|---|
| 949 | $blog->site_url($site_url); |
|---|
| 950 | |
|---|
| 951 | if ( $site_url || $site_path ) { |
|---|
| 952 | $app->print( |
|---|
| 953 | $app->translate( |
|---|
| 954 | "Changing Site Path for the blog '[_1]' (ID:[_2])...", |
|---|
| 955 | $blog->name, $blog->id |
|---|
| 956 | ) |
|---|
| 957 | ); |
|---|
| 958 | } |
|---|
| 959 | else { |
|---|
| 960 | $app->print( |
|---|
| 961 | $app->translate( |
|---|
| 962 | "Removing Site Path for the blog '[_1]' (ID:[_2])...", |
|---|
| 963 | $blog->name, $blog->id |
|---|
| 964 | ) |
|---|
| 965 | ); |
|---|
| 966 | } |
|---|
| 967 | my $old_archive_path = scalar $q->param("old_archive_path_$id"); |
|---|
| 968 | my $old_archive_url = scalar $q->param("old_archive_url_$id"); |
|---|
| 969 | my $archive_path = scalar $q->param("archive_path_$id") || q(); |
|---|
| 970 | my $archive_url = scalar $q->param("archive_url_$id") || q(); |
|---|
| 971 | $blog->archive_path($archive_path); |
|---|
| 972 | $blog->archive_url($archive_url); |
|---|
| 973 | if ( ( $old_archive_url && $archive_url ) |
|---|
| 974 | || ( $old_archive_path && $archive_path ) ) |
|---|
| 975 | { |
|---|
| 976 | $app->print( |
|---|
| 977 | "\n" |
|---|
| 978 | . $app->translate( |
|---|
| 979 | "Changing Archive Path for the blog '[_1]' (ID:[_2])...", |
|---|
| 980 | $blog->name, $blog->id |
|---|
| 981 | ) |
|---|
| 982 | ); |
|---|
| 983 | } |
|---|
| 984 | elsif ( $old_archive_url || $old_archive_path ) { |
|---|
| 985 | $app->print( |
|---|
| 986 | "\n" |
|---|
| 987 | . $app->translate( |
|---|
| 988 | "Removing Archive Path for the blog '[_1]' (ID:[_2])...", |
|---|
| 989 | $blog->name, $blog->id |
|---|
| 990 | ) |
|---|
| 991 | ); |
|---|
| 992 | } |
|---|
| 993 | $blog->save or $app->print( $app->translate("failed") . "\n" ), next; |
|---|
| 994 | $app->print( $app->translate("ok") . "\n" ); |
|---|
| 995 | |
|---|
| 996 | $blogs_meta{$id} = { |
|---|
| 997 | 'old_archive_path' => $old_archive_path, |
|---|
| 998 | 'old_archive_url' => $old_archive_url, |
|---|
| 999 | 'archive_path' => $archive_path, |
|---|
| 1000 | 'archive_url' => $archive_url, |
|---|
| 1001 | 'old_site_path' => $old_site_path, |
|---|
| 1002 | 'old_site_url' => $old_site_url, |
|---|
| 1003 | 'site_path' => $site_path, |
|---|
| 1004 | 'site_url' => $site_url, |
|---|
| 1005 | }; |
|---|
| 1006 | next unless %asset_ids; |
|---|
| 1007 | |
|---|
| 1008 | my $fmgr = ( $site_path || $archive_path ) ? $blog->file_mgr : undef; |
|---|
| 1009 | next unless defined $fmgr; |
|---|
| 1010 | |
|---|
| 1011 | my @assets = |
|---|
| 1012 | $app->model('asset')->load( { blog_id => $id, class => '*' } ); |
|---|
| 1013 | foreach my $asset (@assets) { |
|---|
| 1014 | my $path = $asset->column('file_path'); |
|---|
| 1015 | my $url = $asset->column('url'); |
|---|
| 1016 | if ($archive_path) { |
|---|
| 1017 | $path =~ s/^\Q$old_archive_path\E/$archive_path/i; |
|---|
| 1018 | $asset->file_path($path); |
|---|
| 1019 | } |
|---|
| 1020 | if ($archive_url) { |
|---|
| 1021 | $url =~ s/^\Q$old_archive_url\E/$archive_url/i; |
|---|
| 1022 | $asset->url($url); |
|---|
| 1023 | } |
|---|
| 1024 | if ($site_path) { |
|---|
| 1025 | $path =~ s/^\Q$old_site_path\E/$site_path/i; |
|---|
| 1026 | $asset->file_path($path); |
|---|
| 1027 | } |
|---|
| 1028 | if ($site_url) { |
|---|
| 1029 | $url =~ s/^\Q$old_site_url\E/$site_url/i; |
|---|
| 1030 | $asset->url($url); |
|---|
| 1031 | } |
|---|
| 1032 | $app->print( |
|---|
| 1033 | $app->translate( |
|---|
| 1034 | "Changing file path for the asset '[_1]' (ID:[_2])...", |
|---|
| 1035 | $asset->label, $asset->id |
|---|
| 1036 | ) |
|---|
| 1037 | ); |
|---|
| 1038 | $asset->save |
|---|
| 1039 | or $app->print( $app->translate("failed") . "\n" ), next; |
|---|
| 1040 | $app->print( $app->translate("ok") . "\n" ); |
|---|
| 1041 | unless ( $q->param('redirect') ) { |
|---|
| 1042 | my $old_id = $asset_ids{ $asset->id }; |
|---|
| 1043 | my $filename = "$old_id-" . $asset->file_name; |
|---|
| 1044 | my $file = File::Spec->catfile( $tmp_dir, $filename ); |
|---|
| 1045 | MT::BackupRestore->restore_asset( $file, $asset, $old_id, $fmgr, |
|---|
| 1046 | \%error_assets, sub { $app->print(@_); } ); |
|---|
| 1047 | } |
|---|
| 1048 | } |
|---|
| 1049 | } |
|---|
| 1050 | if (%error_assets) { |
|---|
| 1051 | my $data; |
|---|
| 1052 | while ( my ( $key, $value ) = each %error_assets ) { |
|---|
| 1053 | $data .= |
|---|
| 1054 | $app->translate( 'MT::Asset#[_1]: ', $key ) . $value . "\n"; |
|---|
| 1055 | } |
|---|
| 1056 | my $message = $app->translate( |
|---|
| 1057 | 'Some of the actual files for assets could not be restored.'); |
|---|
| 1058 | $app->log( |
|---|
| 1059 | { |
|---|
| 1060 | message => $message, |
|---|
| 1061 | level => MT::Log::WARNING(), |
|---|
| 1062 | class => 'system', |
|---|
| 1063 | category => 'restore', |
|---|
| 1064 | metadata => $data, |
|---|
| 1065 | } |
|---|
| 1066 | ); |
|---|
| 1067 | $error .= $message; |
|---|
| 1068 | } |
|---|
| 1069 | |
|---|
| 1070 | if ($tmp_dir) { |
|---|
| 1071 | require File::Path; |
|---|
| 1072 | File::Path::rmtree($tmp_dir); |
|---|
| 1073 | } |
|---|
| 1074 | |
|---|
| 1075 | my $param = {}; |
|---|
| 1076 | if ( scalar $q->param('redirect') ) { |
|---|
| 1077 | $param->{restore_end} = 0; # redirect=1 means we are from multi-uploads |
|---|
| 1078 | require JSON; |
|---|
| 1079 | $param->{blogs_meta} = JSON::objToJson( \%blogs_meta ); |
|---|
| 1080 | $param->{next_mode} = 'dialog_restore_upload'; |
|---|
| 1081 | } |
|---|
| 1082 | else { |
|---|
| 1083 | $param->{restore_end} = 1; |
|---|
| 1084 | } |
|---|
| 1085 | if ($error) { |
|---|
| 1086 | $param->{error} = $error; |
|---|
| 1087 | $param->{error_url} = $app->uri( mode => 'view_log', args => {} ); |
|---|
| 1088 | } |
|---|
| 1089 | for my $key ( |
|---|
| 1090 | qw(files last redirect is_dirty is_asset objects_json deferred_json)) |
|---|
| 1091 | { |
|---|
| 1092 | $param->{$key} = scalar $q->param($key); |
|---|
| 1093 | } |
|---|
| 1094 | $param->{name} = $q->param('current_file'); |
|---|
| 1095 | $param->{assets} = encode_html( $q->param('assets') ); |
|---|
| 1096 | $app->print( $app->build_page( 'dialog/restore_end.tmpl', $param ) ); |
|---|
| 1097 | } |
|---|
| 1098 | |
|---|
| 1099 | sub dialog_restore_upload { |
|---|
| 1100 | my $app = shift; |
|---|
| 1101 | my $user = $app->user; |
|---|
| 1102 | return $app->errtrans("Permission denied.") if !$user->is_superuser; |
|---|
| 1103 | $app->validate_magic() or return; |
|---|
| 1104 | |
|---|
| 1105 | my $q = $app->param; |
|---|
| 1106 | |
|---|
| 1107 | my $current = $q->param('current_file'); |
|---|
| 1108 | my $last = $q->param('last'); |
|---|
| 1109 | my $files = $q->param('files'); |
|---|
| 1110 | my $assets_json = $q->param('assets'); |
|---|
| 1111 | my $is_asset = $q->param('is_asset') || 0; |
|---|
| 1112 | my $schema_version = $q->param('schema_version') |
|---|
| 1113 | || $app->config('SchemaVersion'); |
|---|
| 1114 | my $overwrite_template = $q->param('overwrite_templates') ? 1 : 0; |
|---|
| 1115 | |
|---|
| 1116 | my $objects = {}; |
|---|
| 1117 | my $deferred = {}; |
|---|
| 1118 | require JSON; |
|---|
| 1119 | my $objects_json = $q->param('objects_json') if $q->param('objects_json'); |
|---|
| 1120 | $deferred = JSON::jsonToObj( $q->param('deferred_json') ) |
|---|
| 1121 | if $q->param('deferred_json'); |
|---|
| 1122 | |
|---|
| 1123 | my ($fh) = $app->upload_info('file'); |
|---|
| 1124 | |
|---|
| 1125 | my $param = {}; |
|---|
| 1126 | $param->{start} = $q->param('start') || 0; |
|---|
| 1127 | $param->{is_asset} = $is_asset; |
|---|
| 1128 | $param->{name} = $current; |
|---|
| 1129 | $param->{files} = $files; |
|---|
| 1130 | $param->{assets} = $assets_json; |
|---|
| 1131 | $param->{last} = $last; |
|---|
| 1132 | $param->{redirect} = 1; |
|---|
| 1133 | $param->{is_dirty} = $q->param('is_dirty'); |
|---|
| 1134 | $param->{objects_json} = $objects_json if defined($objects_json); |
|---|
| 1135 | $param->{deferred_json} = JSON::objToJson($deferred) if defined($deferred); |
|---|
| 1136 | $param->{blogs_meta} = $q->param('blogs_meta'); |
|---|
| 1137 | $param->{schema_version} = $schema_version; |
|---|
| 1138 | $param->{overwrite_templates} = $overwrite_template; |
|---|
| 1139 | |
|---|
| 1140 | my $uploaded = $q->param('file'); |
|---|
| 1141 | if ( defined($uploaded) ) { |
|---|
| 1142 | $uploaded =~ s!\\!/!g; ## Change backslashes to forward slashes |
|---|
| 1143 | my ( $volume, $directories, $uploaded_filename ) = |
|---|
| 1144 | File::Spec->splitpath($uploaded); |
|---|
| 1145 | if ( $current ne $uploaded_filename ) { |
|---|
| 1146 | close $fh if $uploaded_filename; |
|---|
| 1147 | $param->{error} = |
|---|
| 1148 | $app->translate( 'Please upload [_1] in this page.', $current ); |
|---|
| 1149 | return $app->load_tmpl( 'dialog/restore_upload.tmpl', $param ); |
|---|
| 1150 | } |
|---|
| 1151 | } |
|---|
| 1152 | |
|---|
| 1153 | if (!$fh) { |
|---|
| 1154 | $param->{error} = $app->translate('File was not uploaded.') |
|---|
| 1155 | if !( $q->param('redirect') ); |
|---|
| 1156 | return $app->load_tmpl( 'dialog/restore_upload.tmpl', $param ); |
|---|
| 1157 | } |
|---|
| 1158 | |
|---|
| 1159 | $app->{no_print_body} = 1; |
|---|
| 1160 | |
|---|
| 1161 | local $| = 1; |
|---|
| 1162 | $app->send_http_header('text/html'); |
|---|
| 1163 | |
|---|
| 1164 | $app->print( $app->build_page( 'dialog/restore_start.tmpl', $param ) ); |
|---|
| 1165 | |
|---|
| 1166 | if ( defined $objects_json ) { |
|---|
| 1167 | my $objects_tmp = JSON::jsonToObj($objects_json); |
|---|
| 1168 | my %class2ids; |
|---|
| 1169 | |
|---|
| 1170 | # { MT::CLASS#OLD_ID => NEW_ID } |
|---|
| 1171 | for my $key ( keys %$objects_tmp ) { |
|---|
| 1172 | my ( $class, $old_id ) = split '#', $key; |
|---|
| 1173 | if ( exists $class2ids{$class} ) { |
|---|
| 1174 | my $newids = $class2ids{$class}->{newids}; |
|---|
| 1175 | push @$newids, $objects_tmp->{$key}; |
|---|
| 1176 | my $keymaps = $class2ids{$class}->{keymaps}; |
|---|
| 1177 | push @$keymaps, |
|---|
| 1178 | { newid => $objects_tmp->{$key}, oldid => $old_id }; |
|---|
| 1179 | } |
|---|
| 1180 | else { |
|---|
| 1181 | $class2ids{$class} = { |
|---|
| 1182 | newids => [ $objects_tmp->{$key} ], |
|---|
| 1183 | keymaps => |
|---|
| 1184 | [ { newid => $objects_tmp->{$key}, oldid => $old_id } ] |
|---|
| 1185 | }; |
|---|
| 1186 | } |
|---|
| 1187 | } |
|---|
| 1188 | for my $class ( keys %class2ids ) { |
|---|
| 1189 | eval "require $class;"; |
|---|
| 1190 | next if $@; |
|---|
| 1191 | my $newids = $class2ids{$class}->{newids}; |
|---|
| 1192 | my $keymaps = $class2ids{$class}->{keymaps}; |
|---|
| 1193 | my @objs = $class->load( { id => $newids } ); |
|---|
| 1194 | for my $obj (@objs) { |
|---|
| 1195 | my @old_ids = grep { $_->{newid} eq $obj->id } @$keymaps; |
|---|
| 1196 | my $old_id = $old_ids[0]->{oldid}; |
|---|
| 1197 | $objects->{"$class#$old_id"} = $obj; |
|---|
| 1198 | } |
|---|
| 1199 | } |
|---|
| 1200 | } |
|---|
| 1201 | |
|---|
| 1202 | my $assets = JSON::jsonToObj( decode_html($assets_json) ) |
|---|
| 1203 | if ( defined($assets_json) && $assets_json ); |
|---|
| 1204 | $assets = [] if !defined($assets); |
|---|
| 1205 | my $asset; |
|---|
| 1206 | my @errors; |
|---|
| 1207 | my $error_assets = {}; |
|---|
| 1208 | require MT::BackupRestore; |
|---|
| 1209 | my $blog_ids; |
|---|
| 1210 | my $asset_ids; |
|---|
| 1211 | |
|---|
| 1212 | if ($is_asset) { |
|---|
| 1213 | $asset = shift @$assets; |
|---|
| 1214 | $asset->{fh} = $fh; |
|---|
| 1215 | my $blogs_meta = JSON::jsonToObj( $q->param('blogs_meta') || '{}' ); |
|---|
| 1216 | MT::BackupRestore->_restore_asset_multi( $asset, $objects, |
|---|
| 1217 | $error_assets, sub { $app->print(@_); }, $blogs_meta ); |
|---|
| 1218 | if ( defined( $error_assets->{ $asset->{asset_id} } ) ) { |
|---|
| 1219 | $app->log( |
|---|
| 1220 | { |
|---|
| 1221 | message => $app->translate('Restoring a file failed: ') |
|---|
| 1222 | . $error_assets->{ $asset->{asset_id} }, |
|---|
| 1223 | level => MT::Log::WARNING(), |
|---|
| 1224 | class => 'system', |
|---|
| 1225 | category => 'restore', |
|---|
| 1226 | } |
|---|
| 1227 | ); |
|---|
| 1228 | } |
|---|
| 1229 | } |
|---|
| 1230 | else { |
|---|
| 1231 | ( $blog_ids, $asset_ids ) = eval { |
|---|
| 1232 | MT::BackupRestore->restore_process_single_file( $fh, $objects, |
|---|
| 1233 | $deferred, \@errors, $schema_version, $overwrite_template, |
|---|
| 1234 | sub { _progress($app, @_) } ); |
|---|
| 1235 | }; |
|---|
| 1236 | if ($@) { |
|---|
| 1237 | $last = 1; |
|---|
| 1238 | } |
|---|
| 1239 | } |
|---|
| 1240 | |
|---|
| 1241 | my @files = split( ',', $files ); |
|---|
| 1242 | my $file_next = shift @files if scalar(@files); |
|---|
| 1243 | if ( !defined($file_next) ) { |
|---|
| 1244 | if ( scalar(@$assets) ) { |
|---|
| 1245 | $asset = $assets->[0]; |
|---|
| 1246 | $file_next = $asset->{asset_id} . '-' . $asset->{name}; |
|---|
| 1247 | $param->{is_asset} = 1; |
|---|
| 1248 | } |
|---|
| 1249 | } |
|---|
| 1250 | $param->{files} = join( ',', @files ); |
|---|
| 1251 | $param->{assets} = encode_html( JSON::objToJson($assets) ); |
|---|
| 1252 | $param->{name} = $file_next; |
|---|
| 1253 | if ( 0 < scalar(@files) ) { |
|---|
| 1254 | $param->{last} = 0; |
|---|
| 1255 | } |
|---|
| 1256 | elsif ( 0 >= scalar(@$assets) - 1 ) { |
|---|
| 1257 | $param->{last} = 1; |
|---|
| 1258 | } |
|---|
| 1259 | else { |
|---|
| 1260 | $param->{last} = 0; |
|---|
| 1261 | } |
|---|
| 1262 | $param->{is_dirty} = scalar( keys %$deferred ); |
|---|
| 1263 | if ($last) { |
|---|
| 1264 | $param->{restore_end} = 1; |
|---|
| 1265 | if ( $param->{is_dirty} ) { |
|---|
| 1266 | _log_dirty_restore( $app, $deferred ); |
|---|
| 1267 | my $log_url = $app->uri( mode => 'view_log', args => {} ); |
|---|
| 1268 | $param->{error} = |
|---|
| 1269 | $app->translate( |
|---|
| 1270 | 'Some objects were not restored because their parent objects were not restored.' |
|---|
| 1271 | ); |
|---|
| 1272 | $param->{error_url} = $log_url; |
|---|
| 1273 | } |
|---|
| 1274 | elsif ( scalar( keys %$error_assets ) ) { |
|---|
| 1275 | $param->{error} = |
|---|
| 1276 | $app->translate('Some of the files were not restored correctly.'); |
|---|
| 1277 | my $log_url = $app->uri( mode => 'view_log', args => {} ); |
|---|
| 1278 | $param->{error_url} = $log_url; |
|---|
| 1279 | } |
|---|
| 1280 | elsif ( scalar @errors ) { |
|---|
| 1281 | $param->{error} = join '; ', @errors; |
|---|
| 1282 | my $log_url = $app->uri( mode => 'view_log', args => {} ); |
|---|
| 1283 | $param->{error_url} = $log_url; |
|---|
| 1284 | } |
|---|
| 1285 | else { |
|---|
| 1286 | $app->log( |
|---|
| 1287 | { |
|---|
| 1288 | message => $app->translate( |
|---|
| 1289 | "Successfully restored objects to Movable Type system by user '[_1]'", |
|---|
| 1290 | $app->user->name |
|---|
| 1291 | ), |
|---|
| 1292 | level => MT::Log::INFO(), |
|---|
| 1293 | class => 'system', |
|---|
| 1294 | category => 'restore' |
|---|
| 1295 | } |
|---|
| 1296 | ); |
|---|
| 1297 | $param->{ok_url} = $app->uri( mode => 'start_restore', args => {} ); |
|---|
| 1298 | } |
|---|
| 1299 | } |
|---|
| 1300 | else { |
|---|
| 1301 | my %objects_json; |
|---|
| 1302 | %objects_json = map { $_ => $objects->{$_}->id } keys %$objects; |
|---|
| 1303 | $param->{objects_json} = JSON::objToJson( \%objects_json ); |
|---|
| 1304 | $param->{deferred_json} = JSON::objToJson($deferred); |
|---|
| 1305 | |
|---|
| 1306 | $param->{error} = join( '; ', @errors ); |
|---|
| 1307 | if ( defined($blog_ids) && scalar(@$blog_ids) ) { |
|---|
| 1308 | $param->{next_mode} = 'dialog_adjust_sitepath'; |
|---|
| 1309 | $param->{blog_ids} = join( ',', @$blog_ids ); |
|---|
| 1310 | $param->{asset_ids} = join( ',', @$asset_ids ) |
|---|
| 1311 | if defined($asset_ids); |
|---|
| 1312 | } |
|---|
| 1313 | else { |
|---|
| 1314 | $param->{next_mode} = 'dialog_restore_upload'; |
|---|
| 1315 | } |
|---|
| 1316 | } |
|---|
| 1317 | MT->run_callbacks('restore', $objects, $deferred, \@errors, sub { _progress( $app, @_ ) }); |
|---|
| 1318 | |
|---|
| 1319 | $app->print( $app->build_page( 'dialog/restore_end.tmpl', $param ) ); |
|---|
| 1320 | close $fh if $fh; |
|---|
| 1321 | } |
|---|
| 1322 | |
|---|
| 1323 | sub dialog_adjust_sitepath { |
|---|
| 1324 | my $app = shift; |
|---|
| 1325 | my $user = $app->user; |
|---|
| 1326 | return $app->errtrans("Permission denied.") if !$user->is_superuser; |
|---|
| 1327 | $app->validate_magic() or return; |
|---|
| 1328 | |
|---|
| 1329 | my $q = $app->param; |
|---|
| 1330 | my $tmp_dir = $q->param('tmp_dir'); |
|---|
| 1331 | my $error = $q->param('error') || q(); |
|---|
| 1332 | my $uploaded = $q->param('restore_upload') || 0; |
|---|
| 1333 | my @blog_ids = split ',', $q->param('blog_ids'); |
|---|
| 1334 | my $asset_ids = $q->param('asset_ids'); |
|---|
| 1335 | my @blogs = $app->model('blog')->load( { id => \@blog_ids } ); |
|---|
| 1336 | my @blogs_loop; |
|---|
| 1337 | |
|---|
| 1338 | foreach my $blog (@blogs) { |
|---|
| 1339 | push @blogs_loop, |
|---|
| 1340 | { |
|---|
| 1341 | name => $blog->name, |
|---|
| 1342 | id => $blog->id, |
|---|
| 1343 | old_site_path => $blog->site_path, |
|---|
| 1344 | old_site_url => $blog->site_url, |
|---|
| 1345 | $blog->column('archive_path') |
|---|
| 1346 | ? ( old_archive_path => $blog->archive_path ) |
|---|
| 1347 | : (), |
|---|
| 1348 | $blog->column('archive_url') |
|---|
| 1349 | ? ( old_archive_url => $blog->archive_url ) |
|---|
| 1350 | : (), |
|---|
| 1351 | }; |
|---|
| 1352 | } |
|---|
| 1353 | my $param = { blogs_loop => \@blogs_loop, tmp_dir => $tmp_dir }; |
|---|
| 1354 | $param->{error} = $error if $error; |
|---|
| 1355 | $param->{restore_upload} = $uploaded if $uploaded; |
|---|
| 1356 | $param->{asset_ids} = $asset_ids if $asset_ids; |
|---|
| 1357 | for my $key ( |
|---|
| 1358 | qw(files assets last redirect is_dirty is_asset objects_json deferred_json) |
|---|
| 1359 | ) |
|---|
| 1360 | { |
|---|
| 1361 | $param->{$key} = $q->param($key) if $q->param($key); |
|---|
| 1362 | } |
|---|
| 1363 | $param->{name} = $q->param('current_file'); |
|---|
| 1364 | $app->load_tmpl( 'dialog/adjust_sitepath.tmpl', $param ); |
|---|
| 1365 | } |
|---|
| 1366 | |
|---|
| 1367 | sub convert_to_html { |
|---|
| 1368 | my $app = shift; |
|---|
| 1369 | my $format = $app->param('format'); |
|---|
| 1370 | my $text = $app->param('text'); |
|---|
| 1371 | # XMLHttpRequest always send text in UTF-8... right? |
|---|
| 1372 | if ( defined $text ) { |
|---|
| 1373 | $text = encode_text($text, 'utf-8', $app->config->PublishCharset); |
|---|
| 1374 | } |
|---|
| 1375 | else { |
|---|
| 1376 | $text = '' ; |
|---|
| 1377 | } |
|---|
| 1378 | my $text_more = $app->param('text_more'); |
|---|
| 1379 | if ( defined $text_more ) { |
|---|
| 1380 | $text_more = encode_text($text_more, 'utf-8', $app->config->PublishCharset); |
|---|
| 1381 | } |
|---|
| 1382 | else { |
|---|
| 1383 | $text_more = '' ; |
|---|
| 1384 | } |
|---|
| 1385 | my $result = { |
|---|
| 1386 | text => $app->apply_text_filters( $text, [$format] ), |
|---|
| 1387 | text_more => $app->apply_text_filters( $text_more, [$format] ), |
|---|
| 1388 | format => $format, |
|---|
| 1389 | }; |
|---|
| 1390 | return $app->json_result($result); |
|---|
| 1391 | } |
|---|
| 1392 | |
|---|
| 1393 | sub update_list_prefs { |
|---|
| 1394 | my $app = shift; |
|---|
| 1395 | my $prefs = $app->list_pref( $app->param('_type') ); |
|---|
| 1396 | $app->call_return; |
|---|
| 1397 | } |
|---|
| 1398 | |
|---|
| 1399 | sub recover_passwords { |
|---|
| 1400 | my $app = shift; |
|---|
| 1401 | my @id = $app->param('id'); |
|---|
| 1402 | |
|---|
| 1403 | return $app->errtrans("Permission denied.") |
|---|
| 1404 | unless $app->user->is_superuser(); |
|---|
| 1405 | |
|---|
| 1406 | my $class = ref $app eq 'MT::App::Upgrader' ? 'MT::BasicAuthor' : $app->model('author'); |
|---|
| 1407 | eval "use $class;"; |
|---|
| 1408 | |
|---|
| 1409 | my @msg_loop; |
|---|
| 1410 | foreach (@id) { |
|---|
| 1411 | my $author = $class->load($_) |
|---|
| 1412 | or next; |
|---|
| 1413 | my ( $rc, $res ) = reset_password( $app, $author, $author->hint ); |
|---|
| 1414 | push @msg_loop, { message => $res }; |
|---|
| 1415 | } |
|---|
| 1416 | |
|---|
| 1417 | $app->load_tmpl( 'recover_password_result.tmpl', |
|---|
| 1418 | { message_loop => \@msg_loop, return_url => $app->return_uri } ); |
|---|
| 1419 | } |
|---|
| 1420 | |
|---|
| 1421 | sub reset_password { |
|---|
| 1422 | my $app = shift; |
|---|
| 1423 | my ($author) = $_[0]; |
|---|
| 1424 | my $hint = $_[1]; |
|---|
| 1425 | my $name = $_[2]; |
|---|
| 1426 | |
|---|
| 1427 | require MT::Auth; |
|---|
| 1428 | require MT::Log; |
|---|
| 1429 | if ( !MT::Auth->can_recover_password ) { |
|---|
| 1430 | $app->log( |
|---|
| 1431 | { |
|---|
| 1432 | message => $app->translate( |
|---|
| 1433 | "Invalid password recovery attempt; can't recover password in this configuration" |
|---|
| 1434 | ), |
|---|
| 1435 | level => MT::Log::SECURITY(), |
|---|
| 1436 | class => 'system', |
|---|
| 1437 | category => 'recover_password', |
|---|
| 1438 | } |
|---|
| 1439 | ); |
|---|
| 1440 | return ( 0, |
|---|
| 1441 | $app->translate("Can't recover password in this configuration") ); |
|---|
| 1442 | } |
|---|
| 1443 | |
|---|
| 1444 | $app->log( |
|---|
| 1445 | { |
|---|
| 1446 | message => $app->translate( |
|---|
| 1447 | "Invalid user name '[_1]' in password recovery attempt", $name |
|---|
| 1448 | ), |
|---|
| 1449 | level => MT::Log::SECURITY(), |
|---|
| 1450 | class => 'system', |
|---|
| 1451 | category => 'recover_password', |
|---|
| 1452 | } |
|---|
| 1453 | ), |
|---|
| 1454 | return ( 0, $app->translate("User name or password hint is incorrect.") ) |
|---|
| 1455 | unless $author; |
|---|
| 1456 | return ( 0, |
|---|
| 1457 | $app->translate("User has not set pasword hint; cannot recover password") |
|---|
| 1458 | ) if ( $hint && !$author->hint ); |
|---|
| 1459 | |
|---|
| 1460 | $app->log( |
|---|
| 1461 | { |
|---|
| 1462 | message => $app->translate( |
|---|
| 1463 | "Invalid attempt to recover password (used hint '[_1]')", |
|---|
| 1464 | $hint |
|---|
| 1465 | ), |
|---|
| 1466 | level => MT::Log::SECURITY(), |
|---|
| 1467 | class => 'system', |
|---|
| 1468 | category => 'recover_password' |
|---|
| 1469 | } |
|---|
| 1470 | ), |
|---|
| 1471 | return ( 0, $app->translate("User name or password hint is incorrect.") ) |
|---|
| 1472 | unless $author->hint eq $hint; |
|---|
| 1473 | |
|---|
| 1474 | return ( 0, $app->translate("User does not have email address") ) |
|---|
| 1475 | unless $author->email; |
|---|
| 1476 | |
|---|
| 1477 | my @pool = ( 'a' .. 'z', 0 .. 9 ); |
|---|
| 1478 | my $pass = ''; |
|---|
| 1479 | for ( 1 .. 8 ) { $pass .= $pool[ rand @pool ] } |
|---|
| 1480 | $author->set_password($pass); |
|---|
| 1481 | $author->save; |
|---|
| 1482 | my $message = |
|---|
| 1483 | $app->translate( |
|---|
| 1484 | "Password was reset for user '[_1]' (user #[_2]). Password was sent to the following address: [_3]", |
|---|
| 1485 | $author->name, $author->id, $author->email ); |
|---|
| 1486 | $app->log( |
|---|
| 1487 | { |
|---|
| 1488 | message => $message, |
|---|
| 1489 | level => MT::Log::SECURITY(), |
|---|
| 1490 | class => 'system', |
|---|
| 1491 | category => 'recover_password' |
|---|
| 1492 | } |
|---|
| 1493 | ); |
|---|
| 1494 | |
|---|
| 1495 | my $address = |
|---|
| 1496 | defined $author->nickname |
|---|
| 1497 | ? $author->nickname . ' <' . $author->email . '>' |
|---|
| 1498 | : $author->email; |
|---|
| 1499 | my %head = ( |
|---|
| 1500 | id => 'recover_password', |
|---|
| 1501 | To => $address, |
|---|
| 1502 | From => $app->config('EmailAddressMain') || $address, |
|---|
| 1503 | Subject => $app->translate("Password Recovery") |
|---|
| 1504 | ); |
|---|
| 1505 | my $charset = $app->charset; |
|---|
| 1506 | my $mail_enc = uc( $app->config('MailEncoding') || $charset ); |
|---|
| 1507 | $head{'Content-Type'} = qq(text/plain; charset="$mail_enc"); |
|---|
| 1508 | |
|---|
| 1509 | my $body = $app->build_email( 'recover-password.tmpl', |
|---|
| 1510 | { user_password => $pass, link_to_login => $app->base . $app->mt_uri } |
|---|
| 1511 | ); |
|---|
| 1512 | $body = wrap_text( $body, 72 ); |
|---|
| 1513 | require MT::Mail; |
|---|
| 1514 | MT::Mail->send( \%head, $body ) |
|---|
| 1515 | or return ( |
|---|
| 1516 | 0, |
|---|
| 1517 | $app->translate( |
|---|
| 1518 | "Error sending mail ([_1]); please fix the problem, then " |
|---|
| 1519 | . "try again to recover your password.", |
|---|
| 1520 | MT::Mail->errstr |
|---|
| 1521 | ) |
|---|
| 1522 | ); |
|---|
| 1523 | ( 1, $message ); |
|---|
| 1524 | } |
|---|
| 1525 | |
|---|
| 1526 | sub restore_file { |
|---|
| 1527 | my $app = shift; |
|---|
| 1528 | my ( $fh, $errormsg ) = @_; |
|---|
| 1529 | my $q = $app->param; |
|---|
| 1530 | my $schema_version = |
|---|
| 1531 | $q->param('ignore_schema_conflict') |
|---|
| 1532 | ? 'ignore' |
|---|
| 1533 | : $app->config('SchemaVersion'); |
|---|
| 1534 | my $overwrite_template = $q->param('overwrite_global_templates') ? 1 : 0; |
|---|
| 1535 | |
|---|
| 1536 | require MT::BackupRestore; |
|---|
| 1537 | my ( $deferred, $blogs ) = |
|---|
| 1538 | MT::BackupRestore->restore_file( $fh, $errormsg, $schema_version, $overwrite_template, |
|---|
| 1539 | sub { _progress( $app, @_ ); } ); |
|---|
| 1540 | |
|---|
| 1541 | if ( !defined($deferred) || scalar( keys %$deferred ) ) { |
|---|
| 1542 | _log_dirty_restore( $app, $deferred ); |
|---|
| 1543 | my $log_url = $app->uri( mode => 'view_log', args => {} ); |
|---|
| 1544 | $$errormsg .= '; ' if $$errormsg; |
|---|
| 1545 | $$errormsg .= $app->translate( |
|---|
| 1546 | 'Some objects were not restored because their parent objects were not restored. Detailed information is in the <a href="javascript:void(0);" onclick="closeDialog(\'[_1]\');">activity log</a>.', |
|---|
| 1547 | $log_url |
|---|
| 1548 | ); |
|---|
| 1549 | return $blogs; |
|---|
| 1550 | } |
|---|
| 1551 | if ($$errormsg) { |
|---|
| 1552 | $app->log( |
|---|
| 1553 | { |
|---|
| 1554 | message => $$errormsg, |
|---|
| 1555 | level => MT::Log::ERROR(), |
|---|
| 1556 | class => 'system', |
|---|
| 1557 | category => 'restore', |
|---|
| 1558 | } |
|---|
| 1559 | ); |
|---|
| 1560 | return $blogs; |
|---|
| 1561 | } |
|---|
| 1562 | |
|---|
| 1563 | $app->log( |
|---|
| 1564 | { |
|---|
| 1565 | message => $app->translate( |
|---|
| 1566 | "Successfully restored objects to Movable Type system by user '[_1]'", |
|---|
| 1567 | $app->user->name |
|---|
| 1568 | ), |
|---|
| 1569 | level => MT::Log::INFO(), |
|---|
| 1570 | class => 'system', |
|---|
| 1571 | category => 'restore' |
|---|
| 1572 | } |
|---|
| 1573 | ); |
|---|
| 1574 | |
|---|
| 1575 | $blogs; |
|---|
| 1576 | } |
|---|
| 1577 | |
|---|
| 1578 | sub restore_directory { |
|---|
| 1579 | my $app = shift; |
|---|
| 1580 | my ( $dir, $error ) = @_; |
|---|
| 1581 | |
|---|
| 1582 | if ( !-d $dir ) { |
|---|
| 1583 | $$error = $app->translate( '[_1] is not a directory.', $dir ); |
|---|
| 1584 | return ( undef, undef ); |
|---|
| 1585 | } |
|---|
| 1586 | |
|---|
| 1587 | my $q = $app->param; |
|---|
| 1588 | my $schema_version = |
|---|
| 1589 | $q->param('ignore_schema_conflict') |
|---|
| 1590 | ? 'ignore' |
|---|
| 1591 | : $app->config('SchemaVersion'); |
|---|
| 1592 | |
|---|
| 1593 | my $overwrite_template = $q->param('overwrite_global_templates') ? 1 : 0; |
|---|
| 1594 | |
|---|
| 1595 | my @errors; |
|---|
| 1596 | my %error_assets; |
|---|
| 1597 | require MT::BackupRestore; |
|---|
| 1598 | my ( $deferred, $blogs, $assets ) = |
|---|
| 1599 | MT::BackupRestore->restore_directory( $dir, \@errors, \%error_assets, |
|---|
| 1600 | $schema_version, $overwrite_template, sub { _progress( $app, @_ ); } ); |
|---|
| 1601 | |
|---|
| 1602 | if ( scalar @errors ) { |
|---|
| 1603 | $$error = $app->translate('Error occured during restore process.'); |
|---|
| 1604 | $app->log( |
|---|
| 1605 | { |
|---|
| 1606 | message => $$error, |
|---|
| 1607 | level => MT::Log::WARNING(), |
|---|
| 1608 | class => 'system', |
|---|
| 1609 | category => 'restore', |
|---|
| 1610 | metadata => join( '; ', @errors ), |
|---|
| 1611 | } |
|---|
| 1612 | ); |
|---|
| 1613 | } |
|---|
| 1614 | return ( $blogs, $assets ) unless ( defined($deferred) && %$deferred ); |
|---|
| 1615 | |
|---|
| 1616 | if ( scalar( keys %error_assets ) ) { |
|---|
| 1617 | my $data; |
|---|
| 1618 | while ( my ( $key, $value ) = each %error_assets ) { |
|---|
| 1619 | $data .= |
|---|
| 1620 | $app->translate( 'MT::Asset#[_1]: ', $key ) . $value . "\n"; |
|---|
| 1621 | } |
|---|
| 1622 | my $message = $app->translate('Some of files could not be restored.'); |
|---|
| 1623 | $app->log( |
|---|
| 1624 | { |
|---|
| 1625 | message => $message, |
|---|
| 1626 | level => MT::Log::WARNING(), |
|---|
| 1627 | class => 'system', |
|---|
| 1628 | category => 'restore', |
|---|
| 1629 | metadata => $data, |
|---|
| 1630 | } |
|---|
| 1631 | ); |
|---|
| 1632 | $$error .= $message; |
|---|
| 1633 | } |
|---|
| 1634 | |
|---|
| 1635 | if ( scalar( keys %$deferred ) ) { |
|---|
| 1636 | _log_dirty_restore( $app, $deferred ); |
|---|
| 1637 | my $log_url = $app->uri( mode => 'view_log', args => {} ); |
|---|
| 1638 | $$error = $app->translate( |
|---|
| 1639 | 'Some objects were not restored because their parent objects were not restored. Detailed information is in the <a href="javascript:void(0);" onclick="closeDialog(\'[_1]\');">activity log</a>.', |
|---|
| 1640 | $log_url |
|---|
| 1641 | ); |
|---|
| 1642 | return ( $blogs, $assets ); |
|---|
| 1643 | } |
|---|
| 1644 | |
|---|
| 1645 | return ( $blogs, $assets ) if $$error; |
|---|
| 1646 | |
|---|
| 1647 | $app->log( |
|---|
| 1648 | { |
|---|
| 1649 | message => $app->translate( |
|---|
| 1650 | "Successfully restored objects to Movable Type system by user '[_1]'", |
|---|
| 1651 | $app->user->name |
|---|
| 1652 | ), |
|---|
| 1653 | level => MT::Log::INFO(), |
|---|
| 1654 | class => 'system', |
|---|
| 1655 | category => 'restore' |
|---|
| 1656 | } |
|---|
| 1657 | ); |
|---|
| 1658 | return ( $blogs, $assets ); |
|---|
| 1659 | } |
|---|
| 1660 | |
|---|
| 1661 | sub restore_upload_manifest { |
|---|
| 1662 | my $app = shift; |
|---|
| 1663 | my ($fh) = @_; |
|---|
| 1664 | my $user = $app->user; |
|---|
| 1665 | return $app->errtrans("Permission denied.") if !$user->is_superuser; |
|---|
| 1666 | $app->validate_magic() or return; |
|---|
| 1667 | |
|---|
| 1668 | my $q = $app->param; |
|---|
| 1669 | |
|---|
| 1670 | require MT::BackupRestore; |
|---|
| 1671 | my $backups = MT::BackupRestore->process_manifest($fh); |
|---|
| 1672 | return $app->errtrans( |
|---|
| 1673 | "Uploaded file was not a valid Movable Type backup manifest file.") |
|---|
| 1674 | if !defined($backups); |
|---|
| 1675 | |
|---|
| 1676 | my $files = $backups->{files}; |
|---|
| 1677 | my $assets = $backups->{assets}; |
|---|
| 1678 | my $file_next = shift @$files if defined($files) && scalar(@$files); |
|---|
| 1679 | my $assets_json; |
|---|
| 1680 | my $param = {}; |
|---|
| 1681 | |
|---|
| 1682 | if ( !defined($file_next) ) { |
|---|
| 1683 | if ( scalar(@$assets) > 0 ) { |
|---|
| 1684 | my $asset = shift @$assets; |
|---|
| 1685 | $file_next = $asset->{name}; |
|---|
| 1686 | $param->{is_asset} = 1; |
|---|
| 1687 | } |
|---|
| 1688 | } |
|---|
| 1689 | require JSON; |
|---|
| 1690 | $assets_json = encode_url( JSON::objToJson($assets) ) |
|---|
| 1691 | if scalar(@$assets) > 0; |
|---|
| 1692 | $param->{files} = join( ',', @$files ); |
|---|
| 1693 | $param->{assets} = $assets_json; |
|---|
| 1694 | $param->{filename} = $file_next; |
|---|
| 1695 | $param->{last} = scalar(@$files) ? 0 : ( scalar(@$assets) ? 0 : 1 ); |
|---|
| 1696 | $param->{open_dialog} = 1; |
|---|
| 1697 | $param->{schema_version} = |
|---|
| 1698 | $q->param('ignore_schema_conflict') |
|---|
| 1699 | ? 'ignore' |
|---|
| 1700 | : $app->config('SchemaVersion'); |
|---|
| 1701 | $param->{overwrite_templates} = $q->param('overwrite_global_templates') ? 1 : 0; |
|---|
| 1702 | |
|---|
| 1703 | $param->{dialog_mode} = 'dialog_restore_upload'; |
|---|
| 1704 | $param->{dialog_params} = |
|---|
| 1705 | 'start=1' |
|---|
| 1706 | . '&magic_token=' |
|---|
| 1707 | . $app->current_magic |
|---|
| 1708 | . '&files=' |
|---|
| 1709 | . $param->{files} |
|---|
| 1710 | . '&assets=' |
|---|
| 1711 | . $param->{assets} |
|---|
| 1712 | . '&current_file=' |
|---|
| 1713 | . $param->{filename} |
|---|
| 1714 | . '&last=' |
|---|
| 1715 | . $param->{'last'} |
|---|
| 1716 | . '&schema_version=' |
|---|
| 1717 | . $param->{schema_version} |
|---|
| 1718 | . '&overwrite_templates=' |
|---|
| 1719 | . $param->{overwrite_templates} |
|---|
| 1720 | . '&redirect=1'; |
|---|
| 1721 | $app->load_tmpl( 'restore.tmpl', $param ); |
|---|
| 1722 | |
|---|
| 1723 | #close $fh if $fh; |
|---|
| 1724 | } |
|---|
| 1725 | |
|---|
| 1726 | sub _backup_finisher { |
|---|
| 1727 | my $app = shift; |
|---|
| 1728 | my ( $fnames, $param ) = @_; |
|---|
| 1729 | unless ( ref $fnames ) { |
|---|
| 1730 | $fnames = [$fnames]; |
|---|
| 1731 | } |
|---|
| 1732 | $param->{filename} = $fnames->[0]; |
|---|
| 1733 | $param->{backup_success} = 1; |
|---|
| 1734 | require MT::Session; |
|---|
| 1735 | MT::Session->remove( { kind => 'BU' } ); |
|---|
| 1736 | foreach my $fname (@$fnames) { |
|---|
| 1737 | my $sess = MT::Session->new; |
|---|
| 1738 | $sess->id( $app->make_magic_token() ); |
|---|
| 1739 | $sess->kind('BU'); # BU == Backup |
|---|
| 1740 | $sess->name($fname); |
|---|
| 1741 | $sess->start(time); |
|---|
| 1742 | $sess->save; |
|---|
| 1743 | } |
|---|
| 1744 | my $message; |
|---|
| 1745 | if ( my $blog_id = $param->{blog_id} || $param->{blog_ids} ) { |
|---|
| 1746 | $message = $app->translate( |
|---|
| 1747 | "Blog(s) (ID:[_1]) was/were successfully backed up by user '[_2]'", |
|---|
| 1748 | $blog_id, $app->user->name |
|---|
| 1749 | ); |
|---|
| 1750 | } |
|---|
| 1751 | else { |
|---|
| 1752 | $message = |
|---|
| 1753 | $app->translate( |
|---|
| 1754 | "Movable Type system was successfully backed up by user '[_1]'", |
|---|
| 1755 | $app->user->name ); |
|---|
| 1756 | } |
|---|
| 1757 | $app->log( |
|---|
| 1758 | { |
|---|
| 1759 | message => $message, |
|---|
| 1760 | level => MT::Log::INFO(), |
|---|
| 1761 | class => 'system', |
|---|
| 1762 | category => 'restore' |
|---|
| 1763 | } |
|---|
| 1764 | ); |
|---|
| 1765 | $app->print( $app->build_page( 'include/backup_end.tmpl', $param ) ); |
|---|
| 1766 | } |
|---|
| 1767 | |
|---|
| 1768 | sub _progress { |
|---|
| 1769 | my $app = shift; |
|---|
| 1770 | my $ids = $app->request('progress_ids') || {}; |
|---|
| 1771 | |
|---|
| 1772 | my ( $str, $id ) = @_; |
|---|
| 1773 | if ( $id && $ids->{$id} ) { |
|---|
| 1774 | my $str_js = encode_js($str); |
|---|
| 1775 | $app->print( |
|---|
| 1776 | qq{<script type="text/javascript">progress('$str_js', '$id');</script>} |
|---|
| 1777 | ); |
|---|
| 1778 | } |
|---|
| 1779 | elsif ($id) { |
|---|
| 1780 | $ids->{$id} = 1; |
|---|
| 1781 | $app->print(qq{\n<span id="$id">$str</span>}); |
|---|
| 1782 | } |
|---|
| 1783 | else { |
|---|
| 1784 | $app->print("<span>$str</span>"); |
|---|
| 1785 | } |
|---|
| 1786 | |
|---|
| 1787 | $app->request( 'progress_ids', $ids ); |
|---|
| 1788 | } |
|---|
| 1789 | |
|---|
| 1790 | sub _log_dirty_restore { |
|---|
| 1791 | my $app = shift; |
|---|
| 1792 | my ($deferred) = @_; |
|---|
| 1793 | my %deferred_by_class; |
|---|
| 1794 | for my $key ( keys %$deferred ) { |
|---|
| 1795 | my ( $class, $id ) = split( '#', $key ); |
|---|
| 1796 | if ( exists $deferred_by_class{$class} ) { |
|---|
| 1797 | push @{ $deferred_by_class{$class} }, $id; |
|---|
| 1798 | } |
|---|
| 1799 | else { |
|---|
| 1800 | $deferred_by_class{$class} = [$id]; |
|---|
| 1801 | |
|---|
| 1802 | } |
|---|
| 1803 | } |
|---|
| 1804 | while ( my ( $class_name, $ids ) = each %deferred_by_class ) { |
|---|
| 1805 | my $message = $app->translate( |
|---|
| 1806 | 'Some [_1] were not restored because their parent objects were not restored.', |
|---|
| 1807 | $class_name |
|---|
| 1808 | ); |
|---|
| 1809 | $app->log( |
|---|
| 1810 | { |
|---|
| 1811 | message => $message, |
|---|
| 1812 | level => MT::Log::WARNING(), |
|---|
| 1813 | class => 'system', |
|---|
| 1814 | category => 'restore', |
|---|
| 1815 | metadata => join( ', ', @$ids ), |
|---|
| 1816 | } |
|---|
| 1817 | ); |
|---|
| 1818 | } |
|---|
| 1819 | 1; |
|---|
| 1820 | } |
|---|
| 1821 | |
|---|
| 1822 | 1; |
|---|