root/branches/release-34/lib/MT/CMS/Asset.pm @ 1823

Revision 1823, 42.6 kB (checked in by takayama, 20 months ago)

Fixed BugId:67959
* Added check for result of object loading

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