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

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

Include blog_id when loading related assets.

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