root/trunk/lib/MT/Asset.pm

Revision 4156, 13.6 kB (checked in by fumiakiy, 3 months ago)

Mereged hanson to trunk. "svn merge -r4124:4151 http://code.sixapart.com/svn/movabletype/branches/hanson ."

Line 
1# Movable Type (r) Open Source (C) 2001-2009 Six Apart, Ltd.
2# This program is distributed under the terms of the
3# GNU General Public License, version 2.
4#
5# $Id$
6
7package MT::Asset;
8
9use strict;
10use MT::Tag; # Holds MT::Taggable
11use base qw( MT::Object MT::Taggable MT::Scorable );
12
13__PACKAGE__->install_properties({
14    column_defs => {
15        'id' => 'integer not null auto_increment',
16        'blog_id' => 'integer not null',
17        'label' => 'string(255)',
18        'url' => 'string(255)',
19        'description' => 'text',
20        'file_path' => 'string(255)',
21        'file_name' => 'string(255)',
22        'file_ext' => 'string(20)',
23        'mime_type' => 'string(255)',
24        'parent' => 'integer',
25    },
26    indexes => {
27        label => 1,
28        file_ext => 1,
29        parent => 1,
30        created_by => 1,
31        created_on => 1,
32        blog_class_date => {
33            columns => ['blog_id','class','created_on'],
34        },
35    },
36    class_type => 'file',
37    audit => 1,
38    meta => 1,
39    datasource => 'asset',
40    primary_key => 'id',
41});
42
43require MT::Asset::Image;
44require MT::Asset::Audio;
45require MT::Asset::Video;
46
47sub extensions {
48    my $pkg = shift;
49    return undef unless @_;
50
51    my ($this_pkg) = caller;
52    my ($ext)      = @_;
53    return \@$ext unless MT->config('AssetFileTypes');
54
55    my @custom_ext = map {qr/$_/i}
56        split( /\s*,\s*/, MT->config('AssetFileTypes')->{$this_pkg} );
57    my %seen;
58    my ($new_ext) = grep { ++$seen{$_} < 2 }[ @$ext, @custom_ext ];
59
60    return \@$new_ext;
61}
62
63# This property is a meta-property.
64sub file_path {
65    my $asset = shift;
66    my $path = $asset->SUPER::file_path(@_);
67    return $path if defined($path) && ($path !~ m!^\$!) && (-f $path);
68
69    $path = $asset->cache_property(sub {
70        my $path = $asset->SUPER::file_path();
71        if ($path && ($path =~ m!^\%([ras])!)) {
72            my $blog = $asset->blog;
73            my $root = !$blog || $1 eq 's' ? MT->instance->static_file_path
74                     : $1 eq 'r'           ? $blog->site_path
75                     :                       $blog->archive_path
76                     ;
77            $root =~ s!(/|\\)$!!;
78            $path =~ s!^\%[ras]!$root!;
79        }
80        $path;
81    }, @_);
82    return $path;
83}
84
85sub url {
86    my $asset = shift;
87    my $url = $asset->SUPER::url(@_);
88    return $url if defined($url) && ($url !~ m!^\%!) && ($url =~ m!^https://!);
89
90    $url = $asset->cache_property(sub {
91        my $url = $asset->SUPER::url();
92        if ($url =~ m!^\%([ras])!) {
93            my $blog = $asset->blog;
94            my $root = !$blog || $1 eq 's' ? MT->instance->static_path
95                     : $1 eq 'r'           ? $blog->site_url
96                     :                       $blog->archive_url
97                     ;
98            $root =~ s!/$!!;
99            $url =~ s!^\%[ras]!$root!;
100        }
101        return $url;
102    }, @_);
103    return $url;
104}
105
106# Returns a localized name for the asset type. For MT::Asset, this is simply
107# 'File'.
108sub class_label {
109    MT->translate('Asset');
110}
111
112sub class_label_plural {
113    MT->translate("Assets");
114}
115
116# Removes the asset, associated tags and related file.
117# TBD: Should we track and remove any generated thumbnail files here too?
118sub remove {
119    my $asset = shift;
120    if (ref $asset) {
121        my $blog = MT::Blog->load($asset->blog_id);
122        require MT::FileMgr;
123        my $fmgr = $blog ? $blog->file_mgr : MT::FileMgr->new('Local');
124        my $file = $asset->file_path;
125        unless ($fmgr->delete($file)) {
126            my $app = MT->instance;
127            $app->log(
128                {
129                    message => $app->translate(
130                        "Could not remove asset file [_1] from filesystem: [_2]",
131                        $file, $fmgr->errstr
132                    ),
133                    level    => MT::Log::ERROR(),
134                    class    => 'asset',
135                    category => 'delete',
136                }
137            );
138        }
139        $asset->remove_cached_files;
140
141        # remove children.
142        my $class = ref $asset;
143        my $iter = __PACKAGE__->load_iter({ parent => $asset->id, class => '*' });
144        while(my $a = $iter->()) {
145            $a->remove;
146        }
147
148        # Remove MT::ObjectAsset records
149        $class = MT->model('objectasset');
150        $iter = $class->load_iter({ asset_id => $asset->id });
151        while (my $o = $iter->()) {
152            $o->remove;
153        }
154    }
155
156    $asset->SUPER::remove(@_);
157}
158
159sub save {
160    my $asset = shift;
161    if (defined $asset->file_ext) {
162        $asset->file_ext(lc($asset->file_ext));
163    }
164
165    unless ($asset->SUPER::save(@_)) {
166        print STDERR "error during save: " . $asset->errstr . "\n";
167        die $asset->errstr;
168    }
169}
170
171sub remove_cached_files {
172    my $asset = shift;
173 
174    # remove any asset cache files that exist for this asset
175    my $blog = $asset->blog;
176    if ($asset->id && $blog) {
177        my $cache_dir = $asset->_make_cache_path;
178        if ($cache_dir) {
179            require MT::FileMgr;
180            my $fmgr = $blog->file_mgr || MT::FileMgr->new('Local');
181            if ($fmgr) {
182                my $basename = $asset->file_name;
183                my $ext = '.'.$asset->file_ext;
184                $basename =~ s/$ext$//;
185                my $cache_glob = File::Spec->catfile($cache_dir,
186                    $basename . '-thumb-*' . $ext);
187                my @files = glob($cache_glob);
188                foreach my $file (@files) {
189                    unless ($fmgr->delete($file)) {
190                        my $app = MT->instance;
191                        $app->log(
192                            {
193                                message => $app->translate(
194                                    "Could not remove asset file [_1] from filesystem: [_2]",
195                                    $file, $fmgr->errstr
196                                ),
197                                level    => MT::Log::ERROR(),
198                                class    => 'asset',
199                                category => 'delete',
200                            }
201                        );
202                    }
203                }
204            }
205        }
206    }
207    1;
208}
209
210sub blog {
211    my $asset = shift;
212    my $blog_id = $asset->blog_id or return undef;
213    return $asset->{__blog} if $blog_id && $asset->{__blog} && ($asset->{__blog}->id == $blog_id);
214    require MT::Blog;
215    return $asset->{__blog} = MT::Blog->load($blog_id)
216        or return $asset->error("Failed to load blog for file");
217}
218
219# Returns a true/false response based on whether the active package
220# has extensions registered that match the requested filename.
221sub can_handle {
222    my ($pkg, $filename) = @_;
223    # undef is returned from fileparse if the extension is not known.
224    require File::Basename;
225    my $ext = $pkg->extensions || [];
226    return (File::Basename::fileparse($filename, @$ext))[2] ? 1 : 0;
227}
228
229# Given a filename, returns an appropriate MT::Asset class to associate
230# with it. This lookup is based purely on file extension! If none can
231# be found, it returns MT::Asset.
232sub handler_for_file {
233    my $pkg = shift;
234    my ($filename) = @_;
235    my $types;
236    # special case to check for all registered classes, not just
237    # those that are subclasses of this package.
238    if ($pkg eq 'MT::Asset') {
239        $types = [ keys %{ $pkg->properties->{__type_to_class} || {} } ];
240    }
241    $types ||= $pkg->type_list;
242    if ($types) {
243        foreach my $type (@$types) {
244            my $this_pkg = $pkg->class_handler($type);
245            if ($this_pkg->can_handle($filename)) {
246                return $this_pkg;
247            }
248        }
249    }
250    __PACKAGE__;
251}
252
253sub type_list {
254    my $pkg = shift;
255    my $props = $pkg->properties;
256    my $col = $props->{class_column};
257    my $this_type = $props->{class_type};
258    my @classes = values %{ $props->{__class_to_type} };
259    @classes = grep { m/^\Q$this_type\E:/ } @classes;
260    push @classes, $this_type;
261    return \@classes;
262}
263
264sub metadata {
265    my $asset = shift;
266    return {
267        MT->translate("Tags") => MT::Tag->join(',', $asset->tags),
268        MT->translate("Description") => $asset->description,
269        MT->translate("Name") => $asset->label,
270        url => $asset->url,
271        MT->translate("URL") => $asset->url,
272        MT->translate("Location") => $asset->file_path,
273        name => $asset->file_name,
274        'class' => $asset->class,
275        ext => $asset->file_ext,
276        mime_type => $asset->mime_type,
277        # duration => $asset->duration,
278    };
279}
280
281sub has_thumbnail {
282    0;
283}
284
285sub thumbnail_file {
286    undef;
287}
288
289sub thumbnail_filename {
290    undef;
291}
292
293sub stock_icon_url {
294    undef;
295}
296
297sub thumbnail_url {
298    my $asset = shift;
299    my (%param) = @_;
300
301    require File::Basename;
302    if (my ($thumbnail_file, $w, $h) = $asset->thumbnail_file(@_)) {
303        return $asset->stock_icon_url(@_) if !defined $thumbnail_file;
304        my $file = File::Basename::basename($thumbnail_file);
305        my $asset_file_path = $asset->SUPER::file_path();
306        my $site_url;
307        my $blog = $asset->blog;
308        if (!$blog) {
309            $site_url = $param{Pseudo} ? '%s' : MT->instance->static_path;
310            $site_url .= '/' unless $site_url =~ m!/$!;
311            $site_url .= 'support/';
312        }
313        elsif ( $asset_file_path =~ m/^%a/ ) {
314            $site_url = $param{Pseudo} ? '%a' : $blog->archive_url;
315        }
316        else {
317            $site_url = $param{Pseudo} ? '%r' : $blog->site_url;
318        }
319
320        if ($file && $site_url) {
321            require MT::Util;
322            my $path = $param{Path};
323            if (!defined $path) {
324                $path = MT::Util::caturl(MT->config('AssetCacheDir'), unpack('A4A2', $asset->created_on));
325            } else {
326                require File::Spec;
327                my @path = File::Spec->splitdir($path);
328                $path = '';
329                for my $p (@path) {
330                    $path = MT::Util::caturl($path, $p);
331                }
332            }
333            $file =~ s/%([A-F0-9]{2})/chr(hex($1))/gei;
334            $site_url = MT::Util::caturl($site_url, $path, $file);
335            return ($site_url, $w, $h);
336        }
337    }
338
339    # Use a stock icon
340    return $asset->stock_icon_url(@_);
341}
342
343sub as_html {
344    my $asset = shift;
345    my ($param) = @_;
346    my $fname = $asset->file_name;
347    require MT::Util;
348    my $text = sprintf '<a href="%s">%s</a>',
349        MT::Util::encode_html($asset->url),
350        MT::Util::encode_html($fname);
351    my $app = MT->instance;
352    return $app->param('edit_field') =~ /^customfield/ ? $asset->enclose($text) : $text;
353}
354
355sub enclose {
356    my $asset = shift;
357    my ($html) = @_;
358    my $id = $asset->id;
359    my $type = $asset->class;
360    return qq{<form mt:asset-id="$id" class="mt-enclosure mt-enclosure-$type" style="display: inline;">$html</form>};
361}
362
363# Return a HTML snippet of form options for inserting this asset
364# into a web page. Default behavior is no options.
365sub insert_options {
366    my $asset = shift;
367    my ($param) = @_;
368    return undef;
369}
370
371sub on_upload {
372    my $asset = shift;
373    my ($param) = @_;
374    1;
375}
376
377sub edit_template_param {
378    my $asset = shift;
379    my ($cb, $app, $param, $tmpl) = @_;
380    return;
381}
382
383sub set_values_from_query {
384    my $asset = shift;
385    my ($q) = @_;
386
387    # Set the known columns from the form, if they're set. Subclasses can
388    # opt out or decorate this behavior by overriding the method.
389    my $names = $asset->column_names;
390    my %values;
391    for my $field (@$names) {
392        $values{$field} = $q->param($field)
393            if defined $q->param($field);
394    }
395    $asset->set_values(\%values);
396
397    1;
398}
399
400# $pseudo parameter causes function to return '%r' as
401# root instead of blog site path
402sub _make_cache_path {
403    my $asset = shift;
404    my ($path, $pseudo) = @_;
405    my $blog = $asset->blog;
406
407    require File::Spec;
408    my $year_stamp = '';
409    my $month_stamp = '';
410    if  (!defined $path) {
411        $year_stamp = unpack 'A4', $asset->created_on;
412        $month_stamp = unpack 'x4 A2', $asset->created_on;
413        $path = MT->config('AssetCacheDir');
414    } else {
415        my $merge_path = '';
416        my @split = File::Spec->splitdir($path);
417        for my $p (@split) {
418            $merge_path = File::Spec->catfile($merge_path, $p);
419        }
420        $path = $merge_path if $merge_path;
421    }
422
423    my $asset_file_path = $asset->SUPER::file_path();
424    my $format;
425    my $root_path;
426    if ( !$blog ) {
427        $format = '%s';
428        $root_path = File::Spec->catdir(MT->instance->static_file_path, 'support');
429    }
430    elsif ( $asset_file_path =~ m/^%a/ ) {
431        $format = '%a';
432        $root_path = $blog->archive_path;
433    }
434    else {
435        $format = '%r';
436        $root_path = $blog->site_path;
437    }
438
439    my $real_cache_path = File::Spec->catdir($root_path, $path, $year_stamp,
440        $month_stamp);
441    if (!-d $real_cache_path) {
442        require MT::FileMgr;
443        my $fmgr = $blog ? $blog->file_mgr : MT::FileMgr->new('Local');
444        $fmgr->mkpath($real_cache_path) or return undef;
445    }
446
447    my $asset_cache_path = File::Spec->catdir(($pseudo ? $format : $root_path),
448        $path, $year_stamp, $month_stamp);
449    $asset_cache_path;
450}
451
4521;
453
454__END__
455
456=head1 NAME
457
458MT::Asset
459
460=head1 SYNOPSIS
461
462    use MT::Asset;
463
464    # Example
465
466=head1 DESCRIPTION
467
468This module provides an object definition for a file that is placed under
469MT's control for publishing.
470
471=head1 METHODS
472
473=head2 MT::Asset->new
474
475Constructs a new asset object. The base class is the generic asset object,
476which represents a generic file.
477
478=head2 MT::Asset->handler_for_file($filename)
479
480Returns a I<MT::Asset> package suitable for the filename given. This
481determination is typically made based on the file's extension.
482
483=head1 AUTHORS & COPYRIGHT
484
485Please see the I<MT> manpage for author, copyright, and license information.
486
487=cut
Note: See TracBrowser for help on using the browser.