root/branches/release-30/lib/MT/CMS/Asset.pm @ 1388

Revision 1388, 42.8 kB (checked in by bchoate, 21 months ago)

Fix for asset insert call.

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