root/branches/release-32/lib/MT/CMS/Asset.pm @ 1615

Revision 1615, 42.3 kB (checked in by bchoate, 20 months ago)

Removed 'deferred tag load' mechanism. Fixed jsonification of numeric tags so they are strings too.

  • 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        require JSON;
387        my $json = JSON->new( autoconv => 0 ); # stringifies numbers this way
388        $param->{tags_js} =
389          $json->objToJson(
390            MT::Tag->cache( blog_id => $blog_id, class => 'MT::Asset', private => 1 ) );
391    }
392
393    $app->load_tmpl( 'dialog/asset_options.tmpl', $param );
394}
395
396sub complete_upload {
397    my $app   = shift;
398    my %param = $app->param_hash;
399    my $asset;
400    require MT::Asset;
401    $param{id} && ( $asset = MT::Asset->load( $param{id} ) )
402      or return $app->errtrans("Invalid request.");
403    $asset->label( $param{label} )             if $param{label};
404    $asset->description( $param{description} ) if $param{description};
405    if ( $param{tags} ) {
406        require MT::Tag;
407        my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} );
408        my @tags = MT::Tag->split( $tag_delim, $param{tags} );
409        $asset->set_tags(@tags);
410    }
411    $asset->save();
412    $asset->on_upload( \%param );
413
414    my $perms = $app->permissions;
415    return $app->return_to_dashboard( permission => 1 )
416        unless $app->user->is_superuser
417        || (
418            $perms
419            && (   $perms->can_edit_assets
420                || $perms->can_edit_all_posts
421                || $perms->can_create_post )
422        );
423
424    return $app->redirect(
425        $app->uri(
426            'mode' => 'list_assets',
427            args   => { 'blog_id' => $app->param('blog_id') }
428        )
429    );
430}
431
432sub start_upload_entry {
433    my $app = shift;
434    my $q   = $app->param;
435    $q->param( '_type', 'entry' );
436    defined( my $text = _process_post_upload($app) ) or return;
437    $q->param( 'text', $text );
438
439    # strip any asset id
440    $q->param( 'id', 0 );
441
442    # clear tags value
443    $app->param( 'tags', '' );
444    $app->forward("view");
445}
446
447sub can_view {
448    my ($eh, $app, $id) = @_;
449    my $perms = $app->permissions;
450    return $perms->can_edit_assets();
451}
452
453sub can_delete {
454    my ( $eh, $app, $obj ) = @_;
455    return 1 if $app->user->is_superuser();
456    my $perms = $app->permissions;
457    return $perms && $perms->can_edit_assets();
458}
459
460sub pre_save {
461    my $eh = shift;
462    my ( $app, $obj ) = @_;
463
464    # save tags
465    my $tags = $app->param('tags');
466    if ( defined $tags ) {
467        my $blog = $app->blog;
468        my $fields = $blog ? $blog->smart_replace_fields
469            : MT->config->NwcReplaceField;
470        if ( $fields && $fields =~ m/tags/ig ) {
471            $tags = MT::App::CMS::_convert_word_chars( $app, $tags );
472        }
473
474        require MT::Tag;
475        my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} );
476        my @tags = MT::Tag->split( $tag_delim, $tags );
477        if (@tags) {
478            $obj->set_tags(@tags);
479        }
480        else {
481            $obj->remove_tags();
482        }
483    }
484    1;
485}
486
487sub post_save {
488    my $eh = shift;
489    my ( $app, $obj, $original ) = @_;
490
491    if ( !$original->id ) {
492        $app->log(
493            {
494                message => $app->translate(
495                    "File '[_1]' uploaded by '[_2]'", $obj->file_name,
496                    $app->user->name
497                ),
498                level    => MT::Log::INFO(),
499                class    => 'asset',
500                category => 'new',
501            }
502        );
503    }
504    1;
505}
506
507sub post_delete {
508    my ( $eh, $app, $obj ) = @_;
509
510    $app->log(
511        {
512            message => $app->translate(
513                "File '[_1]' (ID:[_2]) deleted by '[_3]'",
514                $obj->file_name, $obj->id, $app->user->name
515            ),
516            level    => MT::Log::INFO(),
517            class    => 'asset',
518            category => 'delete'
519        }
520    );
521}
522
523sub template_param_edit {
524    my ($cb, $app, $param, $tmpl) = @_;
525    my $asset = $param->{asset} or return;
526    $asset->edit_template_param(@_);
527}
528
529sub asset_list_filters {
530    my $app = shift;
531
532    my %filters;
533    my $types = MT::Asset->class_labels;
534    foreach my $type ( keys %$types ) {
535        my $asset_type = $type;
536        $asset_type =~ s/^asset\.//;
537        $filters{$asset_type} = {
538            label   => sub { MT::Asset->class_handler($type)->class_label_plural },
539            handler => sub {
540                my ( $terms, $args ) = @_;
541                $terms->{class} = $asset_type eq 'asset' ? '*' : $asset_type;
542            },
543        };
544    }
545    my @types =
546      sort { $filters{$a}{label} cmp $filters{$b}{label} } keys %filters;
547    my $order = 100;
548    foreach (@types) {
549        $filters{$_}{order} = $order;
550        $order += 100;
551    }
552    $filters{'asset'}{order} = 0;
553    $filters{'asset'}{label} = "All Assets"; # labels are translated later
554                                             # translate("All Assets");
555    return \%filters;
556}
557
558sub build_asset_hasher {
559    my $app = shift;
560    my (%param) = @_;
561    my ($default_thumb_width, $default_thumb_height, $default_preview_width,
562        $default_preview_height) =
563        @param{qw( ThumbWidth ThumbHeight PreviewWidth PreviewHeight )};
564
565    require File::Basename;
566    require JSON;
567    my %blogs;
568    return sub {
569        my ( $obj, $row, %param ) = @_;
570        my ($thumb_width, $thumb_height) = @param{qw( ThumbWidth ThumbHeight )};
571        $row->{id} = $obj->id;
572        my $blog = $blogs{ $obj->blog_id } ||= $obj->blog;
573        $row->{blog_name} = $blog ? $blog->name : '-';
574        $row->{url} = $obj->url; # this has to be called to calculate
575        $row->{asset_type} = $obj->class_type;
576        $row->{asset_class_label} = $obj->class_label;
577        my $file_path = $obj->file_path; # has to be called to calculate
578        my $meta = $obj->metadata;
579        if ( $file_path && ( -f $file_path ) ) {
580            $row->{file_path} = $file_path;
581            $row->{file_name} = File::Basename::basename( $file_path );
582            my @stat = stat( $file_path );
583            my $size = $stat[7];
584            $row->{file_size} = $size;
585            if ( $size < 1024 ) {
586                $row->{file_size_formatted} = sprintf( "%d Bytes", $size );
587            }
588            elsif ( $size < 1024000 ) {
589                $row->{file_size_formatted} =
590                  sprintf( "%.1f KB", $size / 1024 );
591            }
592            else {
593                $row->{file_size_formatted} =
594                  sprintf( "%.1f MB", $size / 1024000 );
595            }
596            $meta->{'file_size'} = $row->{file_size_formatted};
597        }
598        else {
599            $row->{file_is_missing} = 1 if $file_path;
600        }
601        $row->{file_label} = $row->{label} = $obj->label || $row->{file_name} || $app->translate('Untitled');
602
603        if ($obj->has_thumbnail) { 
604            $row->{has_thumbnail} = 1;
605            my $height = $thumb_height || $default_thumb_height || 75;
606            my $width  = $thumb_width  || $default_thumb_width  || 75;
607            @$meta{qw( thumbnail_url thumbnail_width thumbnail_height )}
608              = $obj->thumbnail_url( Height => $height, Width => $width );
609
610            $meta->{thumbnail_width_offset}  = int(($width  - $meta->{thumbnail_width})  / 2);
611            $meta->{thumbnail_height_offset} = int(($height - $meta->{thumbnail_height}) / 2);
612
613            if ($default_preview_width && $default_preview_height) {
614                @$meta{qw( preview_url preview_width preview_height )}
615                  = $obj->thumbnail_url(
616                    Height => $default_preview_height,
617                    Width  => $default_preview_width,
618                );
619                $meta->{preview_width_offset}  = int(($default_preview_width  - $meta->{preview_width})  / 2);
620                $meta->{preview_height_offset} = int(($default_preview_height - $meta->{preview_height}) / 2);
621            }
622        }
623        else {
624            $row->{has_thumbnail} = 0;
625        }
626
627        my $ts = $obj->created_on;
628        if ( my $by = $obj->created_by ) {
629            my $user = MT::Author->load($by);
630            $row->{created_by} = $user ? $user->name : '';
631        }
632        if ($ts) {
633            $row->{created_on_formatted} =
634              format_ts( MT::App::CMS::LISTING_DATE_FORMAT(), $ts, $blog, $app->user ? $app->user->preferred_language : undef );
635            $row->{created_on_time_formatted} =
636              format_ts( MT::App::CMS::LISTING_TIMESTAMP_FORMAT(), $ts, $blog, $app->user ? $app->user->preferred_language : undef );
637            $row->{created_on_relative} = relative_date( $ts, time, $blog );
638        }
639
640        @$row{keys %$meta} = values %$meta;
641        $row->{metadata_json} = JSON::objToJson($meta);
642        $row;
643    };
644}
645
646sub build_asset_table {
647    my $app = shift;
648    my (%args) = @_;
649
650    my $asset_class = $app->model('asset') or return;
651    my $perms     = $app->permissions;
652    my $list_pref = $app->list_pref('asset');
653    my $limit     = $args{limit};
654    my $param     = $args{param} || {};
655    my $iter;
656    if ( $args{load_args} ) {
657        my $class = $app->model('asset');
658        $iter = $class->load_iter( @{ $args{load_args} } );
659    }
660    elsif ( $args{iter} ) {
661        $iter = $args{iter};
662    }
663    elsif ( $args{items} ) {
664        $iter = sub { pop @{ $args{items} } };
665        $limit = scalar @{ $args{items} };
666    }
667    return [] unless $iter;
668
669    my @data;
670    my $hasher = build_asset_hasher($app);
671    while ( my $obj = $iter->() ) {
672        my $row = $obj->column_values;
673        $hasher->($obj, $row);
674        $row->{object} = $obj;
675        push @data, $row;
676        last if $limit and @data > $limit;
677    }
678    return [] unless @data;
679
680    $param->{template_table}[0]              = {%$list_pref};
681    $param->{template_table}[0]{object_loop} = \@data;
682    $param->{template_table}[0]{object_type} = 'asset';
683    $app->load_list_actions( 'asset', $param );
684    $param->{object_loop} = \@data;
685    $param->{can_delete_files} = 1
686        if (($perms && $perms->can_edit_assets) || $app->user->is_superuser);
687    \@data;
688}
689
690sub asset_insert_text {
691    my $app     = shift;
692    my ($param) = @_;
693    my $q       = $app->param;
694    my $id      = $app->param('id')
695      or return $app->errtrans("Invalid request.");
696    require MT::Asset;
697    my $asset = MT::Asset->load($id)
698      or return $app->errtrans( "Can't load file #[_1].", $id );
699    return $asset->as_html($param);
700}
701
702sub _process_post_upload {
703    my $app   = shift;
704    my %param = $app->param_hash;
705    my $asset;
706    require MT::Asset;
707    $param{id} && ( $asset = MT::Asset->load( $param{id} ) )
708      or return $app->errtrans("Invalid request.");
709    $asset->label( $param{label} )             if $param{label};
710    $asset->description( $param{description} ) if $param{description};
711    if ( $param{tags} ) {
712        require MT::Tag;
713        my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} );
714        my @tags = MT::Tag->split( $tag_delim, $param{tags} );
715        $asset->set_tags(@tags);
716    }
717    $asset->save();
718
719    $asset->on_upload( \%param );
720    return asset_insert_text( $app, \%param );
721}
722
723# FIXME: need to make this work
724sub save {
725    my $app   = shift;
726    my $q     = $app->param;
727    my $perms = $app->permissions;
728    my $type  = $q->param('_type');
729    my $class = $app->model($type)
730      or return $app->errtrans("Invalid request.");
731
732    $app->validate_magic() or return;
733
734    return $app->errtrans("Permission denied.")
735      unless $perms && $perms->can_edit_assets;
736
737    my $blog_id = $q->param('blog_id');
738    my $id = $q->param('id');
739    my $obj = $id ? $class->load($id) : $class->new;
740    my $original = $obj->clone();
741
742    $obj->set_values_from_query($q);
743
744    $app->run_callbacks( 'cms_pre_save.' . $type, $app, $obj, $original )
745      || return $app->errtrans( "Saving [_1] failed: [_2]", $type,
746        $app->errstr );
747
748    $obj->save
749      or return $app->error(
750        $app->translate(
751            "Saving [_1] failed: [_2]",
752            $type, $obj->errstr
753        )
754      );
755
756    $app->run_callbacks( 'cms_post_save.' . $type, $app, $obj, $original );
757
758    $app->redirect(
759        $app->uri(
760            'mode' => 'view',
761            args   => {
762                _type   => $type,
763                blog_id => $blog_id,
764                id      => $obj->id,
765                saved   => 1,
766            }
767        )
768    );
769}
770
771sub _set_start_upload_params {
772    my $app = shift;
773    my ($param) = @_;
774
775    if (my $perms = $app->permissions) {
776        return $app->error( $app->translate("Permission denied.") )
777          unless $perms->can_upload;
778        my $blog_id = $app->param('blog_id');
779        require MT::Blog;
780        my $blog = MT::Blog->load($blog_id);
781
782        $param->{enable_archive_paths} = $blog->column('archive_path');
783        $param->{local_site_path}      = $blog->site_path;
784        $param->{local_archive_path}   = $blog->archive_path;
785        my $label_path;
786        if ( $param->{enable_archive_paths} ) {
787            $label_path = $app->translate('Archive Root');
788        }
789        else {
790            $label_path = $app->translate('Site Root');
791        }
792        my @extra_paths;
793        my $date_stamp = epoch2ts( $blog, time );
794        $date_stamp =~ s!^(\d\d\d\d)(\d\d)(\d\d).*!$1/$2/$3!;
795        my $path_hash = {
796            path  => $date_stamp,
797            label => '<' . $app->translate($label_path) . '>' . '/' . $date_stamp,
798        };
799
800        if ( exists( $param->{middle_path} )
801            && ( $date_stamp eq $param->{middle_path} ) )
802        {
803            $path_hash->{selected} = 1;
804            delete $param->{archive_path};
805        }
806        push @extra_paths, $path_hash;
807        $param->{extra_paths} = \@extra_paths;
808        $param->{refocus}     = 1;
809        $param->{missing_paths} =
810          (      ( defined $blog->site_path || defined $blog->archive_path )
811              && ( -d $blog->site_path || -d $blog->archive_path ) ) ? 0 : 1;
812
813        if ( $param->{missing_paths} ) {
814            if (
815                $app->user->is_superuser
816                || $app->run_callbacks(
817                    'cms_view_permission_filter.blog',
818                    $app, $blog_id, $blog
819                )
820              )
821            {
822                $param->{have_permissions} = 1;
823            }
824        }
825
826        $param->{enable_destination} = 1;
827       
828        my $data = $app->_build_category_list(
829            blog_id => $blog_id,
830            markers => 1,
831            type    => 'folder',
832        );
833        my $top_cat = -1;
834        my $cat_tree = [{
835            id    => -1,
836            label => '/',
837            basename => '/',
838            path  => [],
839        }];
840        foreach (@$data) {
841            next unless exists $_->{category_id};
842            $_->{category_path_ids} ||= [];
843            unshift @{ $_->{category_path_ids} }, -1;
844            push @$cat_tree,
845              {
846                id => $_->{category_id},
847                label => $_->{category_label} . '/',
848                basename => $_->{category_basename} . '/',
849                path => $_->{category_path_ids} || [],
850              };
851        }
852        $param->{category_tree} = $cat_tree;
853    }
854    else {
855        $param->{local_site_path}      = '';
856        $param->{local_archive_path}   = '';
857    }
858
859    $param;
860}
861
862sub _upload_file {
863    my $app = shift;
864    my (%upload_param) = @_;
865
866    if (my $perms = $app->permissions) {
867        return $app->error( $app->translate("Permission denied.") )
868          unless $perms->can_upload;
869    }
870
871    $app->validate_magic() or return;
872
873    my $q = $app->param;
874    my ($fh, $info) = $app->upload_info('file');
875    my $mimetype;
876    if ($info) {
877        $mimetype = $info->{'Content-Type'};
878    }
879    my $has_overwrite = $q->param('overwrite_yes') || $q->param('overwrite_no');
880    my %param = (
881        entry_insert => $q->param('entry_insert'),
882        middle_path  => $q->param('middle_path'),
883        edit_field   => $q->param('edit_field'),
884        site_path    => $q->param('site_path'),
885        extra_path   => $q->param('extra_path'),
886        upload_mode  => $app->mode,
887    );
888    return start_upload( $app, %param,
889        error => $app->translate("Please select a file to upload.") )
890      if !$fh && !$has_overwrite;
891    my $basename = $q->param('file') || $q->param('fname');
892    $basename =~ s!\\!/!g;    ## Change backslashes to forward slashes
893    $basename =~ s!^.*/!!;    ## Get rid of full directory paths
894    if ( $basename =~ m!\.\.|\0|\|! ) {
895        return start_upload( $app, %param,
896            error => $app->translate( "Invalid filename '[_1]'", $basename ) );
897    }
898
899    if (my $asset_type = $upload_param{require_type}) {
900        require MT::Asset;
901        my $asset_pkg = MT::Asset->handler_for_file($basename);
902
903        my %settings_for = (
904            audio => {
905                class => 'MT::Asset::Audio',
906                error => $app->translate( "Please select an audio file to upload." ),
907            },
908            image => {
909                class => 'MT::Asset::Image',
910                error => $app->translate( "Please select an image to upload." ),
911            },
912            video => {
913                class => 'MT::Asset::Video',
914                error => $app->translate( "Please select a video to upload." ),
915            },
916        );
917
918        if (my $settings = $settings_for{$asset_type}) {
919            return start_upload( $app, %param, error => $settings->{error} )
920                if !$asset_pkg->isa($settings->{class});
921        }
922    }
923
924    my ($blog_id, $blog, $fmgr, $local_file, $asset_file, $base_url,
925      $asset_base_url, $relative_url, $relative_path);
926    if ($blog_id = $q->param('blog_id')) {
927        $param{blog_id} = $blog_id;
928        require MT::Blog;
929        $blog = MT::Blog->load($blog_id);
930        $fmgr = $blog->file_mgr;
931
932        ## Set up the full path to the local file; this path could start
933        ## at either the Local Site Path or Local Archive Path, and could
934        ## include an extra directory or two in the middle.
935        my ( $root_path, $middle_path );
936        if ( $q->param('site_path') ) {
937            $root_path = $blog->site_path;
938        }
939        else {
940            $root_path = $blog->archive_path;
941        }
942        return $app->error(
943            $app->translate(
944                "Before you can upload a file, you need to publish your blog."
945            )
946        ) unless -d $root_path;
947        $relative_path = $q->param('extra_path');
948        $middle_path = $q->param('middle_path') || '';
949        my $relative_path_save = $relative_path;
950        if ( $middle_path ne '' ) {
951            $relative_path =
952              $middle_path . ( $relative_path ? '/' . $relative_path : '' );
953        }
954        my $path = $root_path;
955        if ($relative_path) {
956            if ( $relative_path =~ m!\.\.|\0|\|! ) {
957                return start_upload( $app,
958                    %param,
959                    error => $app->translate(
960                        "Invalid extra path '[_1]'", $relative_path
961                    )
962                );
963            }
964            $path = File::Spec->catdir( $path, $relative_path );
965            ## Untaint. We already checked for security holes in $relative_path.
966            ($path) = $path =~ /(.+)/s;
967            ## Build out the directory structure if it doesn't exist. DirUmask
968            ## determines the permissions of the new directories.
969            unless ( $fmgr->exists($path) ) {
970                $fmgr->mkpath($path)
971                  or return start_upload( $app,
972                    %param,
973                    error => $app->translate(
974                        "Can't make path '[_1]': [_2]",
975                        $path, $fmgr->errstr
976                    )
977                  );
978            }
979        }
980        $relative_url =
981          File::Spec->catfile( $relative_path, encode_url($basename) );
982        $relative_path = $relative_path
983          ? File::Spec->catfile( $relative_path, $basename )
984          : $basename;
985        $asset_file = $q->param('site_path') ? '%r' : '%a';
986        $asset_file = File::Spec->catfile( $asset_file, $relative_path );
987        $local_file = File::Spec->catfile( $path, $basename );
988        $base_url = $app->param('site_path') ? $blog->site_url
989          : $blog->archive_url;
990        $asset_base_url = $app->param('site_path') ? '%r' : '%a';
991
992        ## Untaint. We have already tested $basename and $relative_path for security
993        ## issues above, and we have to assume that we can trust the user's
994        ## Local Archive Path setting. So we should be safe.
995        ($local_file) = $local_file =~ /(.+)/s;
996
997        ## If $local_file already exists, we try to write the upload to a
998        ## tempfile, then ask for confirmation of the upload.
999        if ( $fmgr->exists($local_file) ) {
1000            if ($has_overwrite) {
1001                my $tmp = $q->param('temp');
1002                if ( $tmp =~ m!([^/]+)$! ) {
1003                    $tmp = $1;
1004                }
1005                else {
1006                    return $app->error(
1007                        $app->translate( "Invalid temp file name '[_1]'", $tmp ) );
1008                }
1009                my $tmp_dir = $app->config('TempDir');
1010                my $tmp_file = File::Spec->catfile( $tmp_dir, $tmp );
1011                if ( $q->param('overwrite_yes') ) {
1012                    $fh = gensym();
1013                    open $fh, $tmp_file
1014                      or return $app->error(
1015                        $app->translate(
1016                            "Error opening '[_1]': [_2]",
1017                            $tmp_file, "$!"
1018                        )
1019                      );
1020                }
1021                else {
1022                    if ( -e $tmp_file ) {
1023                        unlink($tmp_file)
1024                          or return $app->error(
1025                            $app->translate(
1026                                "Error deleting '[_1]': [_2]",
1027                                $tmp_file, "$!"
1028                            )
1029                          );
1030                    }
1031                    return start_upload($app);
1032                }
1033            }
1034            else {
1035                eval { require File::Temp };
1036                if ($@) {
1037                    return $app->error(
1038                        $app->translate(
1039                            "File with name '[_1]' already exists. (Install "
1040                              . "File::Temp if you'd like to be able to overwrite "
1041                              . "existing uploaded files.)",
1042                            $basename
1043                        )
1044                    );
1045                }
1046                my $tmp_dir = $app->config('TempDir');
1047                my ( $tmp_fh, $tmp_file );
1048                eval {
1049                    ( $tmp_fh, $tmp_file ) =
1050                      File::Temp::tempfile( DIR => $tmp_dir );
1051                };
1052                if ($@) {    #!$tmp_fh
1053                    return $app->errtrans(
1054                        "Error creating temporary file; please check your TempDir "
1055                          . "setting in your coniguration file (currently '[_1]') "
1056                          . "this location should be writable.",
1057                        (
1058                              $tmp_dir
1059                            ? $tmp_dir
1060                            : '[' . $app->translate('unassigned') . ']'
1061                        )
1062                    );
1063                }
1064                defined( _write_upload( $fh, $tmp_fh ) )
1065                  or return $app->error(
1066                    $app->translate(
1067                        "File with name '[_1]' already exists; Tried to write "
1068                          . "to tempfile, but open failed: [_2]",
1069                        $basename,
1070                        "$!"
1071                    )
1072                  );
1073                close $tmp_fh;
1074                my ( $vol, $path, $tmp ) = File::Spec->splitpath($tmp_file);
1075                return $app->load_tmpl(
1076                    'dialog/asset_replace.tmpl',
1077                    {
1078                        temp         => $tmp,
1079                        extra_path   => $relative_path_save,
1080                        site_path    => scalar $q->param('site_path'),
1081                        asset_select => $q->param('asset_select'),
1082                        entry_insert => $q->param('entry_insert'),
1083                        edit_field   => $app->param('edit_field'),
1084                        middle_path  => $middle_path,
1085                        fname        => $basename
1086                    }
1087                );
1088            }
1089        }
1090    }
1091    else {
1092        $blog_id        = 0;
1093        $asset_base_url = '%s/support/uploads';
1094        $base_url       = $app->static_path . 'support/uploads';
1095        $param{support_path} =
1096          File::Spec->catdir( $app->static_file_path, 'support', 'uploads' );
1097
1098        require MT::FileMgr;
1099        $fmgr = MT::FileMgr->new('Local');
1100        unless ( $fmgr->exists( $param{support_path} ) ) {
1101            $fmgr->mkpath( $param{support_path} );
1102            unless ( $fmgr->exists( $param{support_path} ) ) {
1103                return $app->error( $app->translate(
1104                    "Could not create upload path '[_1]': [_2]",
1105                        $param{support_path}, $fmgr->errstr
1106                ) );
1107            }
1108        }
1109
1110        require File::Basename;
1111        my ($stem, undef, $type) = File::Basename::fileparse( $basename,
1112            qr/\.[A-Za-z0-9]+$/ );
1113        my $unique_stem = $stem;
1114        $local_file = File::Spec->catfile( $param{support_path},
1115            $unique_stem . $type );
1116        my $i = 1;
1117        while ($fmgr->exists($local_file)) {
1118            $unique_stem = join q{-}, $stem, $i++;
1119            $local_file = File::Spec->catfile( $param{support_path},
1120                $unique_stem . $type );
1121        }
1122
1123        my $unique_basename = $unique_stem . $type;
1124        $relative_path  = $unique_basename;
1125        $relative_url   = encode_url($unique_basename);
1126        $asset_file     = File::Spec->catfile( '%s', 'support', 'uploads',
1127          $unique_basename );
1128    }
1129
1130    require MT::Image;
1131    my ($w, $h, $id, $write_file) = MT::Image->check_upload(
1132        Fh => $fh, Fmgr => $fmgr, Local => $local_file,
1133        Max => $upload_param{max_size},
1134        MaxDim => $upload_param{max_image_dimension}
1135    );
1136
1137    return $app->error(MT::Image->errstr)
1138        unless $write_file;
1139
1140    ## File does not exist, or else we have confirmed that we can overwrite.
1141    my $umask = oct $app->config('UploadUmask');
1142    my $old   = umask($umask);
1143    defined( my $bytes = $write_file->() )
1144      or return $app->error(
1145        $app->translate(
1146            "Error writing upload to '[_1]': [_2]", $local_file,
1147            $fmgr->errstr
1148        )
1149      );
1150    umask($old);
1151
1152    ## Close up the filehandle.
1153    close $fh;
1154
1155    ## If we are overwriting the file, that means we still have a temp file
1156    ## lying around. Delete it.
1157    if ( $q->param('overwrite_yes') ) {
1158        my $tmp = $q->param('temp');
1159        if ( $tmp =~ m!([^/]+)$! ) {
1160            $tmp = $1;
1161        }
1162        else {
1163            return $app->error(
1164                $app->translate( "Invalid temp file name '[_1]'", $tmp ) );
1165        }
1166        my $tmp_file = File::Spec->catfile( $app->config('TempDir'), $tmp );
1167        unlink($tmp_file)
1168          or return $app->error(
1169            $app->translate( "Error deleting '[_1]': [_2]", $tmp_file, "$!" ) );
1170    }
1171
1172    ## We are going to use $relative_path as the filename and as the url passed
1173    ## in to the templates. So, we want to replace all of the '\' characters
1174    ## with '/' characters so that it won't look like backslashed characters.
1175    ## Also, get rid of a slash at the front, if present.
1176    $relative_path =~ s!\\!/!g;
1177    $relative_path =~ s!^/!!;
1178    $relative_url  =~ s!\\!/!g;
1179    $relative_url  =~ s!^/!!;
1180    my $url = $base_url;
1181    $url .= '/' unless $url =~ m!/$!;
1182    $url .= $relative_url;
1183    my $asset_url = $asset_base_url . '/' . $relative_url;
1184
1185    require File::Basename;
1186    my $local_basename = File::Basename::basename($local_file);
1187    my $ext =
1188      ( File::Basename::fileparse( $local_file, qr/[A-Za-z0-9]+$/ ) )[2];
1189
1190    require MT::Asset;
1191    my $asset_pkg = MT::Asset->handler_for_file($local_basename);
1192    my $is_image  = defined($w)
1193      && defined($h)
1194      && $asset_pkg->isa('MT::Asset::Image');
1195    my $asset;
1196    if (
1197        !(
1198            $asset = $asset_pkg->load(
1199                { file_path => $asset_file, blog_id => $blog_id }
1200            )
1201        )
1202      )
1203    {
1204        $asset = $asset_pkg->new();
1205        $asset->file_path($asset_file);
1206        $asset->file_name($local_basename);
1207        $asset->file_ext($ext);
1208        $asset->blog_id($blog_id);
1209        $asset->created_by( $app->user->id );
1210    }
1211    else {
1212        $asset->modified_by( $app->user->id );
1213    }
1214    my $original = $asset->clone;
1215    $asset->url($asset_url);
1216    if ($is_image) {
1217        $asset->image_width($w);
1218        $asset->image_height($h);
1219    }
1220    $asset->mime_type($mimetype) if $mimetype;
1221    $asset->save;
1222    $app->run_callbacks( 'cms_post_save.asset', $app, $asset, $original );
1223
1224    if ($is_image) {
1225        $app->run_callbacks(
1226            'cms_upload_file.' . $asset->class,
1227            File  => $local_file,
1228            file  => $local_file,
1229            Url   => $url,
1230            url   => $url,
1231            Size  => $bytes,
1232            size  => $bytes,
1233            Asset => $asset,
1234            asset => $asset,
1235            Type  => 'image',
1236            type  => 'image',
1237            Blog  => $blog,
1238            blog  => $blog
1239        );
1240        $app->run_callbacks(
1241            'cms_upload_image',
1242            File       => $local_file,
1243            file       => $local_file,
1244            Url        => $url,
1245            url        => $url,
1246            Size       => $bytes,
1247            size       => $bytes,
1248            Asset      => $asset,
1249            asset      => $asset,
1250            Height     => $h,
1251            height     => $h,
1252            Width      => $w,
1253            width      => $w,
1254            Type       => 'image',
1255            type       => 'image',
1256            ImageType  => $id,
1257            image_type => $id,
1258            Blog       => $blog,
1259            blog       => $blog
1260        );
1261    }
1262    else {
1263        $app->run_callbacks(
1264            'cms_upload_file.' . $asset->class,
1265            File  => $local_file,
1266            file  => $local_file,
1267            Url   => $url,
1268            url   => $url,
1269            Size  => $bytes,
1270            size  => $bytes,
1271            Asset => $asset,
1272            asset => $asset,
1273            Type  => 'file',
1274            type  => 'file',
1275            Blog  => $blog,
1276            blog  => $blog
1277        );
1278    }
1279
1280    return ($asset, $bytes);
1281}
1282
1283sub _write_upload {
1284    my ( $upload_fh, $dest_fh ) = @_;
1285    my $fh = gensym();
1286    if ( ref($dest_fh) eq 'GLOB' ) {
1287        $fh = $dest_fh;
1288    }
1289    else {
1290        open $fh, ">$dest_fh" or return;
1291    }
1292    binmode $fh;
1293    binmode $upload_fh;
1294    my ( $bytes, $data ) = (0);
1295    while ( my $len = read $upload_fh, $data, 8192 ) {
1296        print $fh $data;
1297        $bytes += $len;
1298    }
1299    close $fh;
1300    $bytes;
1301}
1302
13031;
Note: See TracBrowser for help on using the browser.