root/branches/release-33/lib/MT/CMS/Asset.pm @ 1787

Revision 1787, 42.4 kB (checked in by auno, 20 months ago)

Fixed showing appears in link for thumbnail on asset edit page. BugzID:65118

  • Property svn:keywords set to Id Revision
Line 
1package MT::CMS::Asset;
2
3use strict;
4use Symbol;
5use MT::Util qw( epoch2ts encode_url format_ts relative_date );
6
7sub edit {
8    my $cb = shift;
9    my ($app, $id, $obj, $param) = @_;
10
11    if ($id) {
12        my $asset_class = $app->model('asset');
13        $param->{asset} = $obj;
14        $param->{search_label} = $app->translate('Assets');
15
16        my $hasher = build_asset_hasher($app);
17        $hasher->($obj, $param, ThumbWidth => 240, ThumbHeight => 240);
18
19        my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} );
20        require MT::Tag;
21        my $tags = MT::Tag->join( $tag_delim, $obj->tags );
22        $param->{tags} = $tags;
23
24        my @related;
25        if ($obj->parent) {
26            my $parent = $asset_class->load($obj->parent);
27            push @related, $hasher->($parent, { asset => $parent, is_parent => 1 });
28
29            push @related, map { $hasher->($_, { asset => $_, is_sibling => 1 }) }
30                $asset_class->search({
31                    id     => { op => '!=', value => $obj->id },
32                    class  => '*',
33                    parent => $obj->parent
34                });
35        }
36        push @related, map { $hasher->($_, { asset => $_, is_child => 1 }) }
37            $asset_class->search({
38                class  => '*',
39                parent => $obj->id,
40            });
41        $param->{related} = \@related if @related;
42
43        my @appears_in;
44        my $place_class = $app->model('objectasset');
45        my $place_iter = $place_class->load_iter(
46                                                 {
47                blog_id => $obj->blog_id || 0,
48                asset_id => $obj->parent ? $obj->parent : $obj->id
49            }
50        );
51        while (my $place = $place_iter->()) {
52            my $entry_class = $app->model($place->object_ds);
53            my $entry = $entry_class->load($place->object_id);
54            my %entry_data = (
55                id    => $place->object_id,
56                class => $entry->class_type,
57                entry => $entry,
58                title => $entry->title,
59            );
60            if (my $ts = $entry->authored_on) {
61                $entry_data{authored_on_ts} = $ts;
62                $entry_data{authored_on_formatted} =
63                  format_ts( MT::App::CMS::LISTING_DATETIME_FORMAT(), $ts, undef,
64                    $app->user ? $app->user->preferred_language : undef );
65            }
66            if (my $ts = $entry->created_on) {
67                $entry_data{created_on_ts} = $ts;
68                $entry_data{created_on_formatted} =
69                  format_ts( MT::App::CMS::LISTING_DATETIME_FORMAT(), $ts, undef,
70                    $app->user ? $app->user->preferred_language : undef );
71            }
72            push @appears_in, \%entry_data;
73        }
74        if (11 == @appears_in) {   
75            pop @appears_in;
76            $param->{appears_in_more} = 1;
77        }
78        $param->{appears_in} = \@appears_in if @appears_in;
79
80        my $prev_asset = $obj->nextprev(
81            direction => 'previous',
82            terms     => { class => '*', blog_id => $obj->blog_id },
83        );
84        my $next_asset = $obj->nextprev(
85            direction => 'next',
86            terms     => { class => '*', blog_id => $obj->blog_id },
87        );
88        $param->{previous_entry_id} = $prev_asset->id if $prev_asset;
89        $param->{next_entry_id}     = $next_asset->id if $next_asset;
90    }
91    1;
92}
93
94sub list {
95    my $app = shift;
96
97    my $blog_id = $app->param('blog_id');
98    my $blog;
99    if ($blog_id) {
100        my $blog_class = $app->model('blog');
101        $blog = $blog_class->load($blog_id)
102          or return $app->errtrans("Invalid request.");
103        my $perms = $app->permissions;
104        return $app->errtrans("Permission denied.")
105          unless $app->user->is_superuser
106          || (
107            $perms
108            && (   $perms->can_edit_assets
109                || $perms->can_edit_all_posts
110                || $perms->can_create_post )
111          );
112    }
113
114    my $asset_class = $app->model('asset') or return;
115    my %terms;
116    my %args = ( sort => 'created_on', direction => 'descend' );
117
118    my $class_filter;
119    my $filter = ( $app->param('filter') || '' );
120    if ( $filter eq 'class' ) {
121        $class_filter = $app->param('filter_val');
122    }
123    elsif ($filter eq 'userpic') {
124        $class_filter = 'image';
125        $terms{created_by} = $app->param('filter_val');
126
127        my $tag = MT::Tag->load( { name => '@userpic' },
128            { binary => { name => 1 } } );
129        if ($tag) {
130            require MT::ObjectTag;
131            $args{'join'} = MT::ObjectTag->join_on(
132                'object_id',
133                {
134                    tag_id            => $tag->id,
135                    object_datasource => MT::Asset->datasource
136                },
137                { unique => 1 }
138            );
139        }
140    }
141
142    $app->add_breadcrumb( $app->translate("Files") );
143    if ($blog_id) {
144        $terms{blog_id} = $blog_id;
145    }
146    else {
147        unless ( $app->user->is_superuser ) {
148            my @perms = MT::Permission->load( { author_id => $app->user->id } );
149            my @blog_ids;
150            push @blog_ids, $_->blog_id
151              foreach grep { $_->can_edit_assets } @perms;
152            $terms{blog_id} = \@blog_ids;
153        }
154    }
155
156    my $hasher = build_asset_hasher( $app,
157        PreviewWidth => 240, PreviewHeight => 240 );
158
159    if ($class_filter) {
160        my $asset_pkg = MT::Asset->class_handler($class_filter);
161        $terms{class} = $asset_pkg->type_list;
162    }
163    else {
164        $terms{class} = '*';    # all classes
165    }
166
167    # identifier => name
168    my $classes = MT::Asset->class_labels;
169    my @class_loop;
170    foreach my $class ( keys %$classes ) {
171        next if $class eq 'asset';
172        push @class_loop,
173          {
174            class_id    => $class,
175            class_label => $classes->{$class},
176          };
177    }
178
179    # Now, sort it
180    @class_loop = sort { $a->{class_label} cmp $b->{class_label} } @class_loop;
181
182    my $dialog_view = $app->param('dialog_view') ? 1 : 0;
183    my $perms = $app->permissions;
184    my %carry_params = map { $_ => $app->param($_) || '' }
185        (qw( edit_field upload_mode require_type next_mode asset_select ));
186    $carry_params{'user_id'} = $app->param('filter_val')
187        if $filter eq 'userpic';
188    _set_start_upload_params($app, \%carry_params);
189    $app->listing(
190        {
191            terms    => \%terms,
192            args     => \%args,
193            type     => 'asset',
194            code     => $hasher,
195            template => $dialog_view
196            ? 'dialog/asset_list.tmpl'
197            : '',
198            params => {
199                (
200                    $blog
201                    ? (
202                        blog_id   => $blog_id,
203                        blog_name => $blog->name
204                          || '',
205                        edit_blog_id => $blog_id,
206                      )
207                    : (),
208                ),
209                is_image         => defined $class_filter
210                  && $class_filter eq 'image' ? 1 : 0,
211                dialog_view      => $dialog_view,
212                search_label     => MT::Asset->class_label_plural,
213                search_type      => 'asset',
214                class_loop       => \@class_loop,
215                can_delete_files => (
216                    $perms ? $perms->can_edit_assets : $app->user->is_superuser
217                ),
218                nav_assets       => 1,
219                panel_searchable => 1,
220                object_type      => 'asset',
221                %carry_params,
222            },
223        }
224    );
225}
226
227sub insert {
228    my $app  = shift;
229    my $text = _process_post_upload( $app );
230    return unless defined $text;
231    $app->load_tmpl(
232        'dialog/asset_insert.tmpl',
233        {
234            upload_html => $text || '',
235            edit_field => scalar $app->param('edit_field') || '',
236        },
237    );
238}
239
240sub asset_userpic {
241    my $app = shift;
242    my ($param) = @_;
243
244    my ($id, $asset);
245    if ($asset = $param->{asset}) {
246        $id = $asset->id;
247    }
248    else {
249        $id = $param->{asset_id} || scalar $app->param('id');
250        $asset = $app->model('asset')->lookup($id);
251    }
252
253    my $thumb_html = $app->model('author')->userpic_html( Asset => $asset );
254
255    my $user_id = $param->{user_id} || $app->param('user_id');
256    if ($user_id) {
257        my $user = $app->model('author')->load( {id => $user_id} );
258        if ($user) {
259            # Delete the author's userpic thumb (if any); it'll be regenerated.
260            if ($user->userpic_asset_id != $asset->id) {
261                my $old_file = $user->userpic_file();
262                my $fmgr = MT::FileMgr->new('Local');
263                if ($fmgr->exists($old_file)) {
264                    $fmgr->delete($old_file);
265                }
266                $user->userpic_asset_id($asset->id);
267                $user->save;
268            }
269        }
270    }
271
272    $app->load_tmpl(
273        'dialog/asset_userpic.tmpl',
274        {
275            asset_id       => $id,
276            edit_field     => $app->param('edit_field') || '',
277            author_userpic => $thumb_html,
278        },
279    );
280}
281
282sub start_upload {
283    my $app = shift;
284
285    $app->add_breadcrumb( $app->translate('Upload File') );
286    my %param;
287    %param = @_ if @_;
288
289    _set_start_upload_params($app, \%param);
290
291    for my $field (qw( entry_insert edit_field upload_mode require_type
292      asset_select )) {
293        $param{$field} ||= $app->param($field);
294    }
295
296    $app->load_tmpl( 'dialog/asset_upload.tmpl', \%param );
297}
298
299sub upload_file {
300    my $app = shift;
301
302    my ($asset, $bytes) = _upload_file( $app,
303        require_type => ($app->param('require_type') || ''),
304        @_,
305    );
306    return if !defined $asset;
307    return $asset if !defined $bytes;  # whatever it is
308
309    complete_insert( $app,
310        asset => $asset,
311        bytes => $bytes,
312    );
313}
314
315sub complete_insert {
316    my $app = shift;
317    my (%args) = @_;
318
319    my $asset = $args{asset};
320    if ( !$asset && $app->param('id') ) {
321        require MT::Asset;
322        $asset = MT::Asset->load( $app->param('id') )
323          || return $app->errtrans( "Can't load file #[_1].",
324            $app->param('id') );
325    }
326    return $app->errtrans('Invalid request.') unless $asset;
327
328    $args{is_image} = $asset->isa('MT::Asset::Image') ? 1 : 0
329      unless defined $args{is_image};
330
331    require MT::Blog;
332    my $blog = $asset->blog
333      or
334      return $app->errtrans( "Can't load blog #[_1].", $app->param('blog_id') );
335    my $perms = $app->permissions
336      or return $app->errtrans('No permissions');
337
338    my $param = {
339        asset_id            => $asset->id,
340        bytes               => $args{bytes},
341        fname               => $asset->file_name,
342        is_image            => $args{is_image} || 0,
343        url                 => $asset->url,
344        middle_path         => $app->param('middle_path') || '',
345        extra_path          => $app->param('extra_path') || '',
346    };
347    for my $field (qw( direct_asset_insert edit_field entry_insert site_path
348      asset_select )) {
349        $param->{$field} = scalar $app->param($field) || '';
350    }
351    if ( $args{is_image} ) {
352        $param->{width}  = $asset->image_width;
353        $param->{height} = $asset->image_height;
354    }
355    if ( !$app->param('asset_select')
356      && ($perms->can_create_post || $app->user->is_superuser) ) {
357        my $html = $asset->insert_options($param);
358        if ( $param->{direct_asset_insert} && !$html ) {
359            $app->param( 'id', $asset->id );
360            return insert($app);
361        }
362        $param->{options_snippet} = $html;
363    }
364
365    if ($perms) {
366        my $pref_param = $app->load_entry_prefs( $perms->entry_prefs );
367        %$param = ( %$param, %$pref_param );
368
369        # Completion for tags
370        my $author     = $app->user;
371        my $auth_prefs = $author->entry_prefs;
372        if ( my $delim = chr( $auth_prefs->{tag_delim} ) ) {
373            if ( $delim eq ',' ) {
374                $param->{'auth_pref_tag_delim_comma'} = 1;
375            }
376            elsif ( $delim eq ' ' ) {
377                $param->{'auth_pref_tag_delim_space'} = 1;
378            }
379            else {
380                $param->{'auth_pref_tag_delim_other'} = 1;
381            }
382            $param->{'auth_pref_tag_delim'} = $delim;
383        }
384
385        require MT::ObjectTag;
386        my $q       = $app->param;
387        my $blog_id = $q->param('blog_id');
388        require JSON;
389        my $json = JSON->new( autoconv => 0 ); # stringifies numbers this way
390        $param->{tags_js} =
391          $json->objToJson(
392            MT::Tag->cache( blog_id => $blog_id, class => 'MT::Asset', private => 1 ) );
393    }
394
395    $app->load_tmpl( 'dialog/asset_options.tmpl', $param );
396}
397
398sub complete_upload {
399    my $app   = shift;
400    my %param = $app->param_hash;
401    my $asset;
402    require MT::Asset;
403    $param{id} && ( $asset = MT::Asset->load( $param{id} ) )
404      or return $app->errtrans("Invalid request.");
405    $asset->label( $param{label} )             if $param{label};
406    $asset->description( $param{description} ) if $param{description};
407    if ( $param{tags} ) {
408        require MT::Tag;
409        my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} );
410        my @tags = MT::Tag->split( $tag_delim, $param{tags} );
411        $asset->set_tags(@tags);
412    }
413    $asset->save();
414    $asset->on_upload( \%param );
415
416    my $perms = $app->permissions;
417    return $app->return_to_dashboard( permission => 1 )
418        unless $app->user->is_superuser
419        || (
420            $perms
421            && (   $perms->can_edit_assets
422                || $perms->can_edit_all_posts
423                || $perms->can_create_post )
424        );
425
426    return $app->redirect(
427        $app->uri(
428            'mode' => 'list_assets',
429            args   => { 'blog_id' => $app->param('blog_id') }
430        )
431    );
432}
433
434sub start_upload_entry {
435    my $app = shift;
436    my $q   = $app->param;
437    $q->param( '_type', 'entry' );
438    defined( my $text = _process_post_upload($app) ) or return;
439    $q->param( 'text', $text );
440
441    # strip any asset id
442    $q->param( 'id', 0 );
443
444    # clear tags value
445    $app->param( 'tags', '' );
446    $app->forward("view");
447}
448
449sub can_view {
450    my ($eh, $app, $id) = @_;
451    my $perms = $app->permissions;
452    return $perms->can_edit_assets();
453}
454
455sub can_delete {
456    my ( $eh, $app, $obj ) = @_;
457    return 1 if $app->user->is_superuser();
458    my $perms = $app->permissions;
459    return $perms && $perms->can_edit_assets();
460}
461
462sub pre_save {
463    my $eh = shift;
464    my ( $app, $obj ) = @_;
465
466    # save tags
467    my $tags = $app->param('tags');
468    if ( defined $tags ) {
469        my $blog = $app->blog;
470        my $fields = $blog ? $blog->smart_replace_fields
471            : MT->config->NwcReplaceField;
472        if ( $fields && $fields =~ m/tags/ig ) {
473            $tags = MT::App::CMS::_convert_word_chars( $app, $tags );
474        }
475
476        require MT::Tag;
477        my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} );
478        my @tags = MT::Tag->split( $tag_delim, $tags );
479        if (@tags) {
480            $obj->set_tags(@tags);
481        }
482        else {
483            $obj->remove_tags();
484        }
485    }
486    1;
487}
488
489sub post_save {
490    my $eh = shift;
491    my ( $app, $obj, $original ) = @_;
492
493    if ( !$original->id ) {
494        $app->log(
495            {
496                message => $app->translate(
497                    "File '[_1]' uploaded by '[_2]'", $obj->file_name,
498                    $app->user->name
499                ),
500                level    => MT::Log::INFO(),
501                class    => 'asset',
502                category => 'new',
503            }
504        );
505    }
506    1;
507}
508
509sub post_delete {
510    my ( $eh, $app, $obj ) = @_;
511
512    $app->log(
513        {
514            message => $app->translate(
515                "File '[_1]' (ID:[_2]) deleted by '[_3]'",
516                $obj->file_name, $obj->id, $app->user->name
517            ),
518            level    => MT::Log::INFO(),
519            class    => 'asset',
520            category => 'delete'
521        }
522    );
523}
524
525sub template_param_edit {
526    my ($cb, $app, $param, $tmpl) = @_;
527    my $asset = $param->{asset} or return;
528    $asset->edit_template_param(@_);
529}
530
531sub asset_list_filters {
532    my $app = shift;
533
534    my %filters;
535    my $types = MT::Asset->class_labels;
536    foreach my $type ( keys %$types ) {
537        my $asset_type = $type;
538        $asset_type =~ s/^asset\.//;
539        $filters{$asset_type} = {
540            label   => sub { MT::Asset->class_handler($type)->class_label_plural },
541            handler => sub {
542                my ( $terms, $args ) = @_;
543                $terms->{class} = $asset_type eq 'asset' ? '*' : $asset_type;
544            },
545        };
546    }
547    my @types =
548      sort { $filters{$a}{label} cmp $filters{$b}{label} } keys %filters;
549    my $order = 100;
550    foreach (@types) {
551        $filters{$_}{order} = $order;
552        $order += 100;
553    }
554    $filters{'asset'}{order} = 0;
555    $filters{'asset'}{label} = "All Assets"; # labels are translated later
556                                             # translate("All Assets");
557    return \%filters;
558}
559
560sub build_asset_hasher {
561    my $app = shift;
562    my (%param) = @_;
563    my ($default_thumb_width, $default_thumb_height, $default_preview_width,
564        $default_preview_height) =
565        @param{qw( ThumbWidth ThumbHeight PreviewWidth PreviewHeight )};
566
567    require File::Basename;
568    require JSON;
569    my %blogs;
570    return sub {
571        my ( $obj, $row, %param ) = @_;
572        my ($thumb_width, $thumb_height) = @param{qw( ThumbWidth ThumbHeight )};
573        $row->{id} = $obj->id;
574        my $blog = $blogs{ $obj->blog_id } ||= $obj->blog;
575        $row->{blog_name} = $blog ? $blog->name : '-';
576        $row->{url} = $obj->url; # this has to be called to calculate
577        $row->{asset_type} = $obj->class_type;
578        $row->{asset_class_label} = $obj->class_label;
579        my $file_path = $obj->file_path; # has to be called to calculate
580        my $meta = $obj->metadata;
581        if ( $file_path && ( -f $file_path ) ) {
582            $row->{file_path} = $file_path;
583            $row->{file_name} = File::Basename::basename( $file_path );
584            my @stat = stat( $file_path );
585            my $size = $stat[7];
586            $row->{file_size} = $size;
587            if ( $size < 1024 ) {
588                $row->{file_size_formatted} = sprintf( "%d Bytes", $size );
589            }
590            elsif ( $size < 1024000 ) {
591                $row->{file_size_formatted} =
592                  sprintf( "%.1f KB", $size / 1024 );
593            }
594            else {
595                $row->{file_size_formatted} =
596                  sprintf( "%.1f MB", $size / 1024000 );
597            }
598            $meta->{'file_size'} = $row->{file_size_formatted};
599        }
600        else {
601            $row->{file_is_missing} = 1 if $file_path;
602        }
603        $row->{file_label} = $row->{label} = $obj->label || $row->{file_name} || $app->translate('Untitled');
604
605        if ($obj->has_thumbnail) { 
606            $row->{has_thumbnail} = 1;
607            my $height = $thumb_height || $default_thumb_height || 75;
608            my $width  = $thumb_width  || $default_thumb_width  || 75;
609            @$meta{qw( thumbnail_url thumbnail_width thumbnail_height )}
610              = $obj->thumbnail_url( Height => $height, Width => $width );
611
612            $meta->{thumbnail_width_offset}  = int(($width  - $meta->{thumbnail_width})  / 2);
613            $meta->{thumbnail_height_offset} = int(($height - $meta->{thumbnail_height}) / 2);
614
615            if ($default_preview_width && $default_preview_height) {
616                @$meta{qw( preview_url preview_width preview_height )}
617                  = $obj->thumbnail_url(
618                    Height => $default_preview_height,
619                    Width  => $default_preview_width,
620                );
621                $meta->{preview_width_offset}  = int(($default_preview_width  - $meta->{preview_width})  / 2);
622                $meta->{preview_height_offset} = int(($default_preview_height - $meta->{preview_height}) / 2);
623            }
624        }
625        else {
626            $row->{has_thumbnail} = 0;
627        }
628
629        my $ts = $obj->created_on;
630        if ( my $by = $obj->created_by ) {
631            my $user = MT::Author->load($by);
632            $row->{created_by} = $user ? $user->name : '';
633        }
634        if ($ts) {
635            $row->{created_on_formatted} =
636              format_ts( MT::App::CMS::LISTING_DATE_FORMAT(), $ts, $blog, $app->user ? $app->user->preferred_language : undef );
637            $row->{created_on_time_formatted} =
638              format_ts( MT::App::CMS::LISTING_TIMESTAMP_FORMAT(), $ts, $blog, $app->user ? $app->user->preferred_language : undef );
639            $row->{created_on_relative} = relative_date( $ts, time, $blog );
640        }
641
642        @$row{keys %$meta} = values %$meta;
643        $row->{metadata_json} = JSON::objToJson($meta);
644        $row;
645    };
646}
647
648sub build_asset_table {
649    my $app = shift;
650    my (%args) = @_;
651
652    my $asset_class = $app->model('asset') or return;
653    my $perms     = $app->permissions;
654    my $list_pref = $app->list_pref('asset');
655    my $limit     = $args{limit};
656    my $param     = $args{param} || {};
657    my $iter;
658    if ( $args{load_args} ) {
659        my $class = $app->model('asset');
660        $iter = $class->load_iter( @{ $args{load_args} } );
661    }
662    elsif ( $args{iter} ) {
663        $iter = $args{iter};
664    }
665    elsif ( $args{items} ) {
666        $iter = sub { pop @{ $args{items} } };
667        $limit = scalar @{ $args{items} };
668    }
669    return [] unless $iter;
670
671    my @data;
672    my $hasher = build_asset_hasher($app);
673    while ( my $obj = $iter->() ) {
674        my $row = $obj->column_values;
675        $hasher->($obj, $row);
676        $row->{object} = $obj;
677        push @data, $row;
678        last if $limit and @data > $limit;
679    }
680    return [] unless @data;
681
682    $param->{template_table}[0]              = {%$list_pref};
683    $param->{template_table}[0]{object_loop} = \@data;
684    $param->{template_table}[0]{object_type} = 'asset';
685    $app->load_list_actions( 'asset', $param );
686    $param->{object_loop} = \@data;
687    $param->{can_delete_files} = 1
688        if (($perms && $perms->can_edit_assets) || $app->user->is_superuser);
689    \@data;
690}
691
692sub asset_insert_text {
693    my $app     = shift;
694    my ($param) = @_;
695    my $q       = $app->param;
696    my $id      = $app->param('id')
697      or return $app->errtrans("Invalid request.");
698    require MT::Asset;
699    my $asset = MT::Asset->load($id)
700      or return $app->errtrans( "Can't load file #[_1].", $id );
701    return $asset->as_html($param);
702}
703
704sub _process_post_upload {
705    my $app   = shift;
706    my %param = $app->param_hash;
707    my $asset;
708    require MT::Asset;
709    $param{id} && ( $asset = MT::Asset->load( $param{id} ) )
710      or return $app->errtrans("Invalid request.");
711    $asset->label( $param{label} )             if $param{label};
712    $asset->description( $param{description} ) if $param{description};
713    if ( $param{tags} ) {
714        require MT::Tag;
715        my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} );
716        my @tags = MT::Tag->split( $tag_delim, $param{tags} );
717        $asset->set_tags(@tags);
718    }
719    $asset->save();
720
721    $asset->on_upload( \%param );
722    return asset_insert_text( $app, \%param );
723}
724
725# FIXME: need to make this work
726sub save {
727    my $app   = shift;
728    my $q     = $app->param;
729    my $perms = $app->permissions;
730    my $type  = $q->param('_type');
731    my $class = $app->model($type)
732      or return $app->errtrans("Invalid request.");
733
734    $app->validate_magic() or return;
735
736    return $app->errtrans("Permission denied.")
737      unless $perms && $perms->can_edit_assets;
738
739    my $blog_id = $q->param('blog_id');
740    my $id = $q->param('id');
741    my $obj = $id ? $class->load($id) : $class->new;
742    my $original = $obj->clone();
743
744    $obj->set_values_from_query($q);
745
746    $app->run_callbacks( 'cms_pre_save.' . $type, $app, $obj, $original )
747      || return $app->errtrans( "Saving [_1] failed: [_2]", $type,
748        $app->errstr );
749
750    $obj->save
751      or return $app->error(
752        $app->translate(
753            "Saving [_1] failed: [_2]",
754            $type, $obj->errstr
755        )
756      );
757
758    $app->run_callbacks( 'cms_post_save.' . $type, $app, $obj, $original );
759
760    $app->redirect(
761        $app->uri(
762            'mode' => 'view',
763            args   => {
764                _type   => $type,
765                blog_id => $blog_id,
766                id      => $obj->id,
767                saved   => 1,
768            }
769        )
770    );
771}
772
773sub _set_start_upload_params {
774    my $app = shift;
775    my ($param) = @_;
776
777    if (my $perms = $app->permissions) {
778        return $app->error( $app->translate("Permission denied.") )
779          unless $perms->can_upload;
780        my $blog_id = $app->param('blog_id');
781        require MT::Blog;
782        my $blog = MT::Blog->load($blog_id);
783
784        $param->{enable_archive_paths} = $blog->column('archive_path');
785        $param->{local_site_path}      = $blog->site_path;
786        $param->{local_archive_path}   = $blog->archive_path;
787        my $label_path;
788        if ( $param->{enable_archive_paths} ) {
789            $label_path = $app->translate('Archive Root');
790        }
791        else {
792            $label_path = $app->translate('Site Root');
793        }
794        my @extra_paths;
795        my $date_stamp = epoch2ts( $blog, time );
796        $date_stamp =~ s!^(\d\d\d\d)(\d\d)(\d\d).*!$1/$2/$3!;
797        my $path_hash = {
798            path  => $date_stamp,
799            label => '<' . $app->translate($label_path) . '>' . '/' . $date_stamp,
800        };
801
802        if ( exists( $param->{middle_path} )
803            && ( $date_stamp eq $param->{middle_path} ) )
804        {
805            $path_hash->{selected} = 1;
806            delete $param->{archive_path};
807        }
808        push @extra_paths, $path_hash;
809        $param->{extra_paths} = \@extra_paths;
810        $param->{refocus}     = 1;
811        $param->{missing_paths} =
812          (      ( defined $blog->site_path || defined $blog->archive_path )
813              && ( -d $blog->site_path || -d $blog->archive_path ) ) ? 0 : 1;
814
815        if ( $param->{missing_paths} ) {
816            if (
817                $app->user->is_superuser
818                || $app->run_callbacks(
819                    'cms_view_permission_filter.blog',
820                    $app, $blog_id, $blog
821                )
822              )
823            {
824                $param->{have_permissions} = 1;
825            }
826        }
827
828        $param->{enable_destination} = 1;
829       
830        my $data = $app->_build_category_list(
831            blog_id => $blog_id,
832            markers => 1,
833            type    => 'folder',
834        );
835        my $top_cat = -1;
836        my $cat_tree = [{
837            id    => -1,
838            label => '/',
839            basename => '/',
840            path  => [],
841        }];
842        foreach (@$data) {
843            next unless exists $_->{category_id};
844            $_->{category_path_ids} ||= [];
845            unshift @{ $_->{category_path_ids} }, -1;
846            push @$cat_tree,
847              {
848                id => $_->{category_id},
849                label => $_->{category_label} . '/',
850                basename => $_->{category_basename} . '/',
851                path => $_->{category_path_ids} || [],
852              };
853        }
854        $param->{category_tree} = $cat_tree;
855    }
856    else {
857        $param->{local_site_path}      = '';
858        $param->{local_archive_path}   = '';
859    }
860
861    $param;
862}
863
864sub _upload_file {
865    my $app = shift;
866    my (%upload_param) = @_;
867
868    if (my $perms = $app->permissions) {
869        return $app->error( $app->translate("Permission denied.") )
870          unless $perms->can_upload;
871    }
872
873    $app->validate_magic() or return;
874
875    my $q = $app->param;
876    my ($fh, $info) = $app->upload_info('file');
877    my $mimetype;
878    if ($info) {
879        $mimetype = $info->{'Content-Type'};
880    }
881    my $has_overwrite = $q->param('overwrite_yes') || $q->param('overwrite_no');
882    my %param = (
883        entry_insert => $q->param('entry_insert'),
884        middle_path  => $q->param('middle_path'),
885        edit_field   => $q->param('edit_field'),
886        site_path    => $q->param('site_path'),
887        extra_path   => $q->param('extra_path'),
888        upload_mode  => $app->mode,
889    );
890    return start_upload( $app, %param,
891        error => $app->translate("Please select a file to upload.") )
892      if !$fh && !$has_overwrite;
893    my $basename = $q->param('file') || $q->param('fname');
894    $basename =~ s!\\!/!g;    ## Change backslashes to forward slashes
895    $basename =~ s!^.*/!!;    ## Get rid of full directory paths
896    if ( $basename =~ m!\.\.|\0|\|! ) {
897        return start_upload( $app, %param,
898            error => $app->translate( "Invalid filename '[_1]'", $basename ) );
899    }
900
901    if (my $asset_type = $upload_param{require_type}) {
902        require MT::Asset;
903        my $asset_pkg = MT::Asset->handler_for_file($basename);
904
905        my %settings_for = (
906            audio => {
907                class => 'MT::Asset::Audio',
908                error => $app->translate( "Please select an audio file to upload." ),
909            },
910            image => {
911                class => 'MT::Asset::Image',
912                error => $app->translate( "Please select an image to upload." ),
913            },
914            video => {
915                class => 'MT::Asset::Video',
916                error => $app->translate( "Please select a video to upload." ),
917            },
918        );
919
920        if (my $settings = $settings_for{$asset_type}) {
921            return start_upload( $app, %param, error => $settings->{error} )
922                if !$asset_pkg->isa($settings->{class});
923        }
924    }
925
926    my ($blog_id, $blog, $fmgr, $local_file, $asset_file, $base_url,
927      $asset_base_url, $relative_url, $relative_path);
928    if ($blog_id = $q->param('blog_id')) {
929        $param{blog_id} = $blog_id;
930        require MT::Blog;
931        $blog = MT::Blog->load($blog_id);
932        $fmgr = $blog->file_mgr;
933
934        ## Set up the full path to the local file; this path could start
935        ## at either the Local Site Path or Local Archive Path, and could
936        ## include an extra directory or two in the middle.
937        my ( $root_path, $middle_path );
938        if ( $q->param('site_path') ) {
939            $root_path = $blog->site_path;
940        }
941        else {
942            $root_path = $blog->archive_path;
943        }
944        return $app->error(
945            $app->translate(
946                "Before you can upload a file, you need to publish your blog."
947            )
948        ) unless -d $root_path;
949        $relative_path = $q->param('extra_path');
950        $middle_path = $q->param('middle_path') || '';
951        my $relative_path_save = $relative_path;
952        if ( $middle_path ne '' ) {
953            $relative_path =
954              $middle_path . ( $relative_path ? '/' . $relative_path : '' );
955        }
956        my $path = $root_path;
957        if ($relative_path) {
958            if ( $relative_path =~ m!\.\.|\0|\|! ) {
959                return start_upload( $app,
960                    %param,
961                    error => $app->translate(
962                        "Invalid extra path '[_1]'", $relative_path
963                    )
964                );
965            }
966            $path = File::Spec->catdir( $path, $relative_path );
967            ## Untaint. We already checked for security holes in $relative_path.
968            ($path) = $path =~ /(.+)/s;
969            ## Build out the directory structure if it doesn't exist. DirUmask
970            ## determines the permissions of the new directories.
971            unless ( $fmgr->exists($path) ) {
972                $fmgr->mkpath($path)
973                  or return start_upload( $app,
974                    %param,
975                    error => $app->translate(
976                        "Can't make path '[_1]': [_2]",
977                        $path, $fmgr->errstr
978                    )
979                  );
980            }
981        }
982        $relative_url =
983          File::Spec->catfile( $relative_path, encode_url($basename) );
984        $relative_path = $relative_path
985          ? File::Spec->catfile( $relative_path, $basename )
986          : $basename;
987        $asset_file = $q->param('site_path') ? '%r' : '%a';
988        $asset_file = File::Spec->catfile( $asset_file, $relative_path );
989        $local_file = File::Spec->catfile( $path, $basename );
990        $base_url = $app->param('site_path') ? $blog->site_url
991          : $blog->archive_url;
992        $asset_base_url = $app->param('site_path') ? '%r' : '%a';
993
994        ## Untaint. We have already tested $basename and $relative_path for security
995        ## issues above, and we have to assume that we can trust the user's
996        ## Local Archive Path setting. So we should be safe.
997        ($local_file) = $local_file =~ /(.+)/s;
998
999        ## If $local_file already exists, we try to write the upload to a
1000        ## tempfile, then ask for confirmation of the upload.
1001        if ( $fmgr->exists($local_file) ) {
1002            if ($has_overwrite) {
1003                my $tmp = $q->param('temp');
1004                if ( $tmp =~ m!([^/]+)$! ) {
1005                    $tmp = $1;
1006                }
1007                else {
1008                    return $app->error(
1009                        $app->translate( "Invalid temp file name '[_1]'", $tmp ) );
1010                }
1011                my $tmp_dir = $app->config('TempDir');
1012                my $tmp_file = File::Spec->catfile( $tmp_dir, $tmp );
1013                if ( $q->param('overwrite_yes') ) {
1014                    $fh = gensym();
1015                    open $fh, $tmp_file
1016                      or return $app->error(
1017                        $app->translate(
1018                            "Error opening '[_1]': [_2]",
1019                            $tmp_file, "$!"
1020                        )
1021                      );
1022                }
1023                else {
1024                    if ( -e $tmp_file ) {
1025                        unlink($tmp_file)
1026                          or return $app->error(
1027                            $app->translate(
1028                                "Error deleting '[_1]': [_2]",
1029                                $tmp_file, "$!"
1030                            )
1031                          );
1032                    }
1033                    return start_upload($app);
1034                }
1035            }
1036            else {
1037                eval { require File::Temp };
1038                if ($@) {
1039                    return $app->error(
1040                        $app->translate(
1041                            "File with name '[_1]' already exists. (Install "
1042                              . "File::Temp if you'd like to be able to overwrite "
1043                              . "existing uploaded files.)",
1044                            $basename
1045                        )
1046                    );
1047                }
1048                my $tmp_dir = $app->config('TempDir');
1049                my ( $tmp_fh, $tmp_file );
1050                eval {
1051                    ( $tmp_fh, $tmp_file ) =
1052                      File::Temp::tempfile( DIR => $tmp_dir );
1053                };
1054                if ($@) {    #!$tmp_fh
1055                    return $app->errtrans(
1056                        "Error creating temporary file; please check your TempDir "
1057                          . "setting in your coniguration file (currently '[_1]') "
1058                          . "this location should be writable.",
1059                        (
1060                              $tmp_dir
1061                            ? $tmp_dir
1062                            : '[' . $app->translate('unassigned') . ']'
1063                        )
1064                    );
1065                }
1066                defined( _write_upload( $fh, $tmp_fh ) )
1067                  or return $app->error(
1068                    $app->translate(
1069                        "File with name '[_1]' already exists; Tried to write "
1070                          . "to tempfile, but open failed: [_2]",
1071                        $basename,
1072                        "$!"
1073                    )
1074                  );
1075                close $tmp_fh;
1076                my ( $vol, $path, $tmp ) = File::Spec->splitpath($tmp_file);
1077                return $app->load_tmpl(
1078                    'dialog/asset_replace.tmpl',
1079                    {
1080                        temp         => $tmp,
1081                        extra_path   => $relative_path_save,
1082                        site_path    => scalar $q->param('site_path'),
1083                        asset_select => $q->param('asset_select'),
1084                        entry_insert => $q->param('entry_insert'),
1085                        edit_field   => $app->param('edit_field'),
1086                        middle_path  => $middle_path,
1087                        fname        => $basename
1088                    }
1089                );
1090            }
1091        }
1092    }
1093    else {
1094        $blog_id        = 0;
1095        $asset_base_url = '%s/support/uploads';
1096        $base_url       = $app->static_path . 'support/uploads';
1097        $param{support_path} =
1098          File::Spec->catdir( $app->static_file_path, 'support', 'uploads' );
1099
1100        require MT::FileMgr;
1101        $fmgr = MT::FileMgr->new('Local');
1102        unless ( $fmgr->exists( $param{support_path} ) ) {
1103            $fmgr->mkpath( $param{support_path} );
1104            unless ( $fmgr->exists( $param{support_path} ) ) {
1105                return $app->error( $app->translate(
1106                    "Could not create upload path '[_1]': [_2]",
1107                        $param{support_path}, $fmgr->errstr
1108                ) );
1109            }
1110        }
1111
1112        require File::Basename;
1113        my ($stem, undef, $type) = File::Basename::fileparse( $basename,
1114            qr/\.[A-Za-z0-9]+$/ );
1115        my $unique_stem = $stem;
1116        $local_file = File::Spec->catfile( $param{support_path},
1117            $unique_stem . $type );
1118        my $i = 1;
1119        while ($fmgr->exists($local_file)) {
1120            $unique_stem = join q{-}, $stem, $i++;
1121            $local_file = File::Spec->catfile( $param{support_path},
1122                $unique_stem . $type );
1123        }
1124
1125        my $unique_basename = $unique_stem . $type;
1126        $relative_path  = $unique_basename;
1127        $relative_url   = encode_url($unique_basename);
1128        $asset_file     = File::Spec->catfile( '%s', 'support', 'uploads',
1129          $unique_basename );
1130    }
1131
1132    require MT::Image;
1133    my ($w, $h, $id, $write_file) = MT::Image->check_upload(
1134        Fh => $fh, Fmgr => $fmgr, Local => $local_file,
1135        Max => $upload_param{max_size},
1136        MaxDim => $upload_param{max_image_dimension}
1137    );
1138
1139    return $app->error(MT::Image->errstr)
1140        unless $write_file;
1141
1142    ## File does not exist, or else we have confirmed that we can overwrite.
1143    my $umask = oct $app->config('UploadUmask');
1144    my $old   = umask($umask);
1145    defined( my $bytes = $write_file->() )
1146      or return $app->error(
1147        $app->translate(
1148            "Error writing upload to '[_1]': [_2]", $local_file,
1149            $fmgr->errstr
1150        )
1151      );
1152    umask($old);
1153
1154    ## Close up the filehandle.
1155    close $fh;
1156
1157    ## If we are overwriting the file, that means we still have a temp file
1158    ## lying around. Delete it.
1159    if ( $q->param('overwrite_yes') ) {
1160        my $tmp = $q->param('temp');
1161        if ( $tmp =~ m!([^/]+)$! ) {
1162            $tmp = $1;
1163        }
1164        else {
1165            return $app->error(
1166                $app->translate( "Invalid temp file name '[_1]'", $tmp ) );
1167        }
1168        my $tmp_file = File::Spec->catfile( $app->config('TempDir'), $tmp );
1169        unlink($tmp_file)
1170          or return $app->error(
1171            $app->translate( "Error deleting '[_1]': [_2]", $tmp_file, "$!" ) );
1172    }
1173
1174    ## We are going to use $relative_path as the filename and as the url passed
1175    ## in to the templates. So, we want to replace all of the '\' characters
1176    ## with '/' characters so that it won't look like backslashed characters.
1177    ## Also, get rid of a slash at the front, if present.
1178    $relative_path =~ s!\\!/!g;
1179    $relative_path =~ s!^/!!;
1180    $relative_url  =~ s!\\!/!g;
1181    $relative_url  =~ s!^/!!;
1182    my $url = $base_url;
1183    $url .= '/' unless $url =~ m!/$!;
1184    $url .= $relative_url;
1185    my $asset_url = $asset_base_url . '/' . $relative_url;
1186
1187    require File::Basename;
1188    my $local_basename = File::Basename::basename($local_file);
1189    my $ext =
1190      ( File::Basename::fileparse( $local_file, qr/[A-Za-z0-9]+$/ ) )[2];
1191
1192    require MT::Asset;
1193    my $asset_pkg = MT::Asset->handler_for_file($local_basename);
1194    my $is_image  = defined($w)
1195      && defined($h)
1196      && $asset_pkg->isa('MT::Asset::Image');
1197    my $asset;
1198    if (
1199        !(
1200            $asset = $asset_pkg->load(
1201                { file_path => $asset_file, blog_id => $blog_id }
1202            )
1203        )
1204      )
1205    {
1206        $asset = $asset_pkg->new();
1207        $asset->file_path($asset_file);
1208        $asset->file_name($local_basename);
1209        $asset->file_ext($ext);
1210        $asset->blog_id($blog_id);
1211        $asset->created_by( $app->user->id );
1212    }
1213    else {
1214        $asset->modified_by( $app->user->id );
1215    }
1216    my $original = $asset->clone;
1217    $asset->url($asset_url);
1218    if ($is_image) {
1219        $asset->image_width($w);
1220        $asset->image_height($h);
1221    }
1222    $asset->mime_type($mimetype) if $mimetype;
1223    $asset->save;
1224    $app->run_callbacks( 'cms_post_save.asset', $app, $asset, $original );
1225
1226    if ($is_image) {
1227        $app->run_callbacks(
1228            'cms_upload_file.' . $asset->class,
1229            File  => $local_file,
1230            file  => $local_file,
1231            Url   => $url,
1232            url   => $url,
1233            Size  => $bytes,
1234            size  => $bytes,
1235            Asset => $asset,
1236            asset => $asset,
1237            Type  => 'image',
1238            type  => 'image',
1239            Blog  => $blog,
1240            blog  => $blog
1241        );
1242        $app->run_callbacks(
1243            'cms_upload_image',
1244            File       => $local_file,
1245            file       => $local_file,
1246            Url        => $url,
1247            url        => $url,
1248            Size       => $bytes,
1249            size       => $bytes,
1250            Asset      => $asset,
1251            asset      => $asset,
1252            Height     => $h,
1253            height     => $h,
1254            Width      => $w,
1255            width      => $w,
1256            Type       => 'image',
1257            type       => 'image',
1258            ImageType  => $id,
1259            image_type => $id,
1260            Blog       => $blog,
1261            blog       => $blog
1262        );
1263    }
1264    else {
1265        $app->run_callbacks(
1266            'cms_upload_file.' . $asset->class,
1267            File  => $local_file,
1268            file  => $local_file,
1269            Url   => $url,
1270            url   => $url,
1271            Size  => $bytes,
1272            size  => $bytes,
1273            Asset => $asset,
1274            asset => $asset,
1275            Type  => 'file',
1276            type  => 'file',
1277            Blog  => $blog,
1278            blog  => $blog
1279        );
1280    }
1281
1282    return ($asset, $bytes);
1283}
1284
1285sub _write_upload {
1286    my ( $upload_fh, $dest_fh ) = @_;
1287    my $fh = gensym();
1288    if ( ref($dest_fh) eq 'GLOB' ) {
1289        $fh = $dest_fh;
1290    }
1291    else {
1292        open $fh, ">$dest_fh" or return;
1293    }
1294    binmode $fh;
1295    binmode $upload_fh;
1296    my ( $bytes, $data ) = (0);
1297    while ( my $len = read $upload_fh, $data, 8192 ) {
1298        print $fh $data;
1299        $bytes += $len;
1300    }
1301    close $fh;
1302    $bytes;
1303}
1304
13051;
Note: See TracBrowser for help on using the browser.