root/branches/release-41/lib/MT/CMS/Asset.pm @ 2802

Revision 2802, 43.0 kB (checked in by bchoate, 17 months ago)

Removed additional translate on label path. BugId:80699

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