root/branches/wheeljack/lib/MT/Asset/Image.pm @ 975

Revision 975, 16.8 kB (checked in by bchoate, 3 years ago)

Removing storage/restoration of thumbnail default height.

  • Property svn:keywords set to Id Revision
Line 
1# Copyright 2001-2006 Six Apart. This code cannot be redistributed without
2# permission from www.sixapart.com.  For more information, consult your
3# Movable Type license.
4#
5# $Id$
6
7package MT::Asset::Image;
8
9use strict;
10use base 'MT::Asset';
11
12# List of supported file extensions (to aid the stock 'can_handle' method.)
13sub extensions { [ qr/gif/i, qr/jpe?g/i, qr/png/i, ] }
14
15sub class_label {
16    MT->translate('Image');
17}
18
19sub metadata {
20    my $obj = shift;
21    my $meta = $obj->SUPER::metadata(@_);
22    $meta->{MT->translate("Actual Dimensions")} = MT->translate(
23        "[_1] wide, [_2] high",
24        $obj->image_width, $obj->image_height
25    ) if defined $obj->image_width && defined $obj->image_height;
26    $meta;
27}
28
29sub thumbnail_file {
30    my $asset = shift;
31    my (%param) = @_;
32    my $file_path = $asset->file_path;
33    my @imginfo = stat($file_path);
34    return undef unless @imginfo;
35
36    my $blog = $param{Blog} || $asset->blog;
37    return undef unless $blog;
38    my $fmgr;
39
40    require MT::Util;
41    my $asset_cache_path = File::Spec->catdir($blog->site_path,
42        MT->config('AssetCacheDir'));
43    if (!-d $asset_cache_path) {
44        $fmgr = $blog->file_mgr;
45        $fmgr->mkpath($asset_cache_path) or return undef;
46    }
47
48    my $file = $asset->thumbnail_filename(@_);
49    my $thumbnail = File::Spec->catfile($asset_cache_path, $file);
50    my @thumbinfo = stat($thumbnail);
51
52    # thumbnail file exists and is dated on or later than source image
53    if (@thumbinfo && ($thumbinfo[9] >= $imginfo[9])) {
54        return $thumbnail;
55    }
56
57    # stale or non-existent thumbnail. let's create one!
58    $fmgr ||= $blog->file_mgr;
59    return undef unless $fmgr;
60
61    return undef unless $fmgr->can_write($asset_cache_path);
62
63    # create a thumbnail for this file
64    require MT::Image;
65    my $img = new MT::Image(Filename => $file_path)
66        or return $asset->error(MT::Image->errstr);
67
68    # 100000px wide image, 10px tall => 164x230
69    #     scale the horizontal to fit
70    # 100000px tall image, 10px wide => 164x230
71    #     scale the vertical to fit
72    # 100000px wide/tall => 164x164
73    #     scale the horizontal to fit
74
75    my $h = $param{Height};
76    my $w = $param{Width};
77
78    # find the longest dimension of the image:
79    my ($i_h, $i_w) = ($img->{height}, $img->{width});
80    my ($n_h, $n_w) = ($i_h, $i_w);
81    my $scale = '';
82    if ($i_h > $i_w) {
83        # scale, if necessary, by height
84        if ($i_h > $h) {
85            $scale = 'h';
86        } elsif ($i_w > $w) {
87            $scale = 'w';
88        }
89    } else {
90        # scale, if necessary, by width
91        if ($i_w > $w) {
92            $scale = 'w';
93        } elsif ($i_h > $h) {
94            $scale = 'h';
95        }
96    }
97    if ($scale eq 'h') {
98        # scale by height
99        $n_h = $h;
100        $n_w = int($i_w * $h / $i_h);
101        if ($n_w > $w) {
102            $n_w = $w;
103            $n_h = int($i_h * $w / $i_w);
104        }
105    } elsif ($scale eq 'w') {
106        # scale by width
107        $n_w = $w;
108        $n_h = int($i_h * $w / $i_w);
109    }
110
111    my ($data) = $img->scale(Height => $n_h, Width => $n_w)
112        or return $asset->error(MT->translate("Error scaling image: [_1]", $img->errstr));
113    $fmgr->put_data($data, $thumbnail, 'upload')
114        or return $asset->error(MT->translate("Error creating thumbnail file: [_1]", $fmgr->errstr));
115    return $thumbnail;
116}
117
118sub thumbnail_filename {
119    my $asset = shift;
120    my (%param) = @_;
121
122    require MT::Util;
123    my $signature = sprintf 'height:%d;width:%d',
124        $param{Height}, $param{Width};
125    my $suffix = MT::Util::perl_sha1_digest_hex($signature);
126    return $asset->id . '.' . $suffix . '.' . $asset->file_ext;
127}
128
129sub as_html {
130    my $asset = shift;
131    my ($param) = @_;
132
133    my $text = '';
134
135    my $fname = $asset->file_name;
136    require MT::Util;
137
138    my $thumb = undef;
139    if ($param->{thumb}) {
140        $thumb = MT::Asset->load($param->{thumb_asset_id}) ||
141            return $asset->error(
142                MT->translate("Can't load asset #[_1]",
143                    $param->{thumb_asset_id})
144            );
145    }
146
147    my $dimensions = sprintf('width="%s" height="%s"', ($thumb
148        ? ($thumb->image_width, $thumb->image_height)
149        : ($asset->image_width, $asset->image_height)));
150
151    if ($param->{popup}) {
152        my $popup = MT::Asset->load($param->{popup_asset_id}) ||
153            return $asset->error(
154                MT->translate("Can't load asset #[_1]",
155                    $param->{popup_asset_id})
156            );
157        my $link = $thumb
158            ? sprintf('<img src="%s" %s alt="%s" />',
159                MT::Util::encode_html($thumb->url),
160                $dimensions,
161                MT::Util::encode_html($fname),
162              )
163            : MT->translate('View image');
164        $text = sprintf(
165            q|<a href="%s" onclick="window.open('%s','popup','width=%d,height=%d,scrollbars=no,resizable=no,toolbar=no,directories=no,location=no,menubar=no,status=no,left=0,top=0'); return false">%s</a>|,
166            MT::Util::encode_html($popup->url),
167            MT::Util::encode_html($popup->url),
168            $asset->image_width, $asset->image_height, $link,
169        );
170    } elsif ($param->{include}) {
171        my $wrap_style = $param->{wrap_text} && $param->{align}
172            ? 'class="display_img_' . $param->{align} . '" ' : '';
173        if ($param->{thumb}) {
174            $text = sprintf(
175                '<a href="%s"><img alt="%s" src="%s" %s %s/></a>',
176                MT::Util::encode_html($asset->url),
177                MT::Util::encode_html($fname),
178                MT::Util::encode_html($thumb->url),
179                $dimensions, $wrap_style,
180            );
181        } else {
182            $text = sprintf(
183                '<img alt="%s" src="%s" %s %s/>',
184                MT::Util::encode_html($fname),
185                MT::Util::encode_html($asset->url),
186                $dimensions, $wrap_style,
187            );
188        }
189    } else {
190        $text = sprintf('<a href="%s">%s</a>',
191            MT::Util::encode_html($asset->url),
192            MT->translate('Download file'),
193        );
194    }
195
196    return $text;
197}
198
199# Return a HTML snippet of form options for inserting this asset
200# into a web page. Default behavior is no options.
201sub insert_options {
202    my $asset = shift;
203    my ($param) = @_;
204
205    my $app = MT->instance;
206    my $perms = $app->{perms};
207    my $blog = $asset->blog or return;
208
209    eval { require MT::Image; MT::Image->new or die; };
210    $param->{do_thumb} = $@ ? 0 : 1;
211
212    $param->{can_save_image_defaults} = $perms->can_save_image_defaults ? 1 : 0;
213    #$param->{constrain} = $blog->image_default_constrain ? 1 : 0;
214    $param->{popup} = $blog->image_default_popup ? 1 : 0;
215    $param->{wrap_text} = $blog->image_default_wrap_text ? 1 : 0;
216    $param->{make_thumb} = $blog->image_default_thumb ? 1 : 0;
217    $param->{'align_'.$_} = $blog->image_default_align eq $_ ? 1 : 0
218        for qw(left center right);
219    $param->{'unit_w'.$_} = $blog->image_default_wunits eq $_ ? 1 : 0
220        for qw(percent pixels);
221    $param->{thumb_width} = $blog->image_default_width || $asset->image_width || 0;
222
223    return $app->build_page('asset_image_options.tmpl', $param);
224}
225
226sub on_upload {
227    my $asset = shift;
228    my ($param) = @_;
229
230    $asset->SUPER::on_upload(@_);
231
232    my $app = MT->instance;
233    require MT::Util;
234
235    my $url = $asset->url;
236    my $width = $asset->image_width;
237    my $height = $asset->image_height;
238
239    my ($base_url, $fname) = $url =~ m|(.*)/([^/]*)|;
240    $url = $base_url . '/' . $fname; # no need to re-encode filename; url is already encoded
241    my $blog = $asset->blog or return;
242    my $blog_id = $blog->id;
243
244    my($thumb, $thumb_width, $thumb_height);
245    $thumb_width = $param->{thumb_width};
246    $thumb = $param->{thumb};
247    if ($thumb) {
248        if ($thumb_width && ($thumb_width !~ m/^\d+$/)) {
249            undef $thumb_width;
250        }
251        # width > 1000 not really a thumbnail, so consider invalid
252        if ($thumb_width > 1000) {
253            undef $thumb_width;
254        }
255    }
256    if ($thumb && !$thumb_width) {
257        undef $thumb;
258    }
259    if($param->{image_defaults}) {
260        return $app->error($app->translate(
261            'Permission denied setting image defaults for blog #[_2]', $blog_id
262        )) unless $app->{perms}->can_save_image_defaults;
263        # Save new defaults if requested.
264        $blog->image_default_wrap_text($param->{wrap_text} ? 1 : 0);
265        $blog->image_default_align($param->{align} || MT::Blog::ALIGN());
266        if ($thumb) {
267            $blog->image_default_thumb(1);
268            $blog->image_default_width($thumb_width);
269            $blog->image_default_wunits($param->{thumb_width_type} || MT::Blog::UNITS());
270        } else {
271            $blog->image_default_thumb(0);
272            $blog->image_default_width(0);
273            $blog->image_default_wunits(MT::Blog::UNITS());
274        }
275        #$blog->image_default_constrain($param->{constrain} ? 1 : 0);
276        $blog->image_default_popup($param->{popup} ? 1 : 0);
277        $blog->save;
278    }
279
280    # Thumbnail creation
281    if ($thumb = $param->{thumb}) {
282        require MT::Image;
283        my $base_path = $param->{site_path} ?
284            $blog->site_path : $blog->archive_path;
285        my $file = $param->{fname};
286        if ($file =~ m!\.\.|\0|\|!) {
287            return $app->error($app->translate("Invalid filename '[_1]'", $file));
288        }
289        my $i_file = File::Spec->catfile($base_path, $file);
290        ## Untaint. We checked $file for security holes above.
291        ($i_file) = $i_file =~ /(.+)/s;
292        my $fmgr = $blog->file_mgr;
293        my $data = $fmgr->get_data($i_file, 'upload')
294            or return $app->error($app->translate(
295                "Reading '[_1]' failed: [_2]", $i_file, $fmgr->errstr));
296        my $image_type = scalar $param->{image_type};
297        my $img = MT::Image->new( Data => $data,
298                                  Type => $image_type )
299            or return $app->error($app->translate(
300                "Thumbnail failed: [_1]", MT::Image->errstr));
301        my($w, $h) = map $param->{$_}, qw( thumb_width thumb_height );
302        (my($blob), $thumb_width, $thumb_height) =
303            $img->scale( Width => $w, Height => $h )
304            or return $app->error($app->translate("Thumbnail failed: [_1]",
305                $img->errstr));
306        require File::Basename;
307        my($base, $path, $ext) = File::Basename::fileparse($i_file, '\.[^.]*');
308        my $t_file = $path . $base . '-thumb' . $ext;
309        my $basename = $base . '-thumb' . $ext;
310        ## If the thumbnail filename already exists, we don't want to overwrite
311        ## it, because it could contain valuable data; so we'll just make
312        ## sure to generate the name uniquely.
313        my $i = 0;
314        while ($fmgr->exists($t_file)) {
315            $basename = $base . '-thumb' . (++$i) . $ext;
316            $t_file = File::Spec->catfile($path . $basename);
317        }
318        $fmgr->put_data($blob, $t_file, 'upload')
319            or return $app->error($app->translate(
320                "Error writing to '[_1]': [_2]", $t_file, $fmgr->errstr));
321
322        $file =~ s/\Q$base$ext\E$//;
323        my $url = $param->{site_path} ? $blog->site_url : $blog->archive_url;
324        $url .= '/' unless $url =~ m!/$!;
325        $url .= $file;
326        $thumb = $url . MT::Util::encode_url($basename);
327
328        my $img_pkg = MT::Asset->handler_for_file($t_file);
329        my $asset_thumb = new $img_pkg;
330        my $original = $asset_thumb->clone;
331        $asset_thumb->blog_id($blog_id);
332        $asset_thumb->url($thumb);
333        $asset_thumb->file_path($t_file);
334        $asset_thumb->file_name($basename);
335        my $ext2 = $ext;
336        $ext2 =~ s/^\.//;
337        $asset_thumb->file_ext($ext2);
338        $asset_thumb->image_width($thumb_width);
339        $asset_thumb->image_height($thumb_height);
340        $asset_thumb->created_by($app->user->id);
341        $asset_thumb->parent($asset->id);
342        $asset_thumb->save;
343        MT->run_callbacks('CMSPostSave.asset', $app, $asset_thumb, $original);
344
345        $param->{thumb_asset_id} = $asset_thumb->id;
346
347        MT->run_callbacks('CMSUploadFile.' . $asset_thumb->class,
348                          File => $t_file, Url => $thumb, Size => length($blob),
349                          Asset => $asset_thumb,
350                          Type => 'thumbnail',
351                          Blog => $blog);
352
353        MT->run_callbacks('CMSUploadImage',
354                          File => $t_file, Url => $thumb,
355                          Asset => $asset_thumb,
356                          Width => $thumb_width, Height => $thumb_height,
357                          ImageType => $image_type,
358                          Size => length($blob),
359                          Type => 'thumbnail',
360                          Blog => $blog);
361    }
362    if ($param->{popup}) {
363        require MT::Template;
364        if (my $tmpl = MT::Template->load({ blog_id => $blog_id,
365                                            type => 'popup_image' })) {
366            (my $rel_path = $param->{fname}) =~ s!\.[^.]*$!!;
367            if ($rel_path =~ m!\.\.|\0|\|!) {
368                return $app->error($app->translate(
369                    "Invalid basename '[_1]'", $rel_path));
370            }
371            my $ext = $blog->file_extension || '';
372            $ext = '.' . $ext if $ext ne '';
373            require MT::Template::Context;
374            my $ctx = MT::Template::Context->new;
375            $ctx->stash('blog', $blog);
376            $ctx->stash('blog_id', $blog->id);
377            $ctx->stash('asset', $asset);
378            $ctx->stash('image_url', $url);
379            $ctx->stash('image_width', $width);
380            $ctx->stash('image_height', $height);
381            my $popup = $tmpl->build($ctx) or die $tmpl->errstr;
382            my $fmgr = $blog->file_mgr;
383            my $root_path = $param->{site_path} ?
384                $blog->site_path : $blog->archive_path;
385            my $abs_file_path = File::Spec->catfile($root_path, $rel_path . $ext);
386
387            ## If the popup filename already exists, we don't want to overwrite
388            ## it, because it could contain valuable data; so we'll just make
389            ## sure to generate the name uniquely.
390            my($i, $rel_path_ext) = (0, $rel_path . $ext);
391            while ($fmgr->exists($abs_file_path)) {
392                $rel_path_ext = $rel_path . ++$i . $ext;
393                $abs_file_path = File::Spec->catfile($root_path, $rel_path_ext);
394            }
395            my ($vol, $dirs, $basename) = File::Spec->splitpath($rel_path_ext);
396            my $rel_url_ext = File::Spec->catpath($vol, $dirs, MT::Util::encode_url($basename));
397 
398            ## Untaint. We have checked for security holes above, so we
399            ## should be safe.
400            ($abs_file_path) = $abs_file_path =~ /(.+)/s;
401            $fmgr->put_data($popup, $abs_file_path, 'upload')
402                or return $app->error($app->translate(
403                   "Error writing to '[_1]': [_2]", $abs_file_path,
404                                                     $fmgr->errstr));
405            $url = $param->{site_path} ?
406                $blog->site_url : $blog->archive_url;
407            $url .= '/' unless $url =~ m!/$!;
408            $rel_url_ext =~ s!^/!!;
409            $url .= $rel_url_ext;
410
411            my $html_pkg = MT::Asset->handler_for_file($abs_file_path);
412            my $asset_html = new $html_pkg;
413            my $original = $asset_html->clone;
414            $asset_html->blog_id($blog_id);
415            $asset_html->url($url);
416            $asset_html->file_path($abs_file_path);
417            $asset_html->file_name($basename);
418            $asset_html->file_ext($blog->file_extension);
419            $asset_html->created_by($app->user->id);
420            $asset_html->parent($asset->id);
421            $asset_html->save;
422
423            $param->{popup_asset_id} = $asset_html->id;
424
425            MT->run_callbacks('CMSPostSave.asset', $app, $asset_html, $original);
426
427            MT->run_callbacks('CMSUploadFile.' . $asset_html->class,
428                          File => $abs_file_path, Url => $url,
429                          Asset => $asset_html,
430                          Size => length($popup),
431                          Type => 'popup',
432                          Blog => $blog);
433        }
434    }
435    1;
436}
437
4381;
439
440__END__
441
442=head1 NAME
443
444MT::Asset::Image
445
446=head1 SYNOPSIS
447
448    use MT::Asset::Image;
449
450    # Example
451
452=head1 DESCRIPTION
453
454=head1 METHODS
455
456=head2 MT::Asset::Image->class
457
458Returns 'image', the identifier for this particular class of asset.
459
460=head2 MT::Asset::Image->class_label
461
462Returns the localized descriptive name for this type of asset.
463
464=head2 MT::Asset::Image->extensions
465
466Returns an arrayref of file extensions that are supported by this
467package.
468
469=head2 $asset->metadata
470
471Returns a hashref of metadata values for this asset.
472
473=head2 $asset->thumbnail_file(%param)
474
475Creates or retrieves the file path to a thumbnail image appropriate for
476the asset. If a thumbnail cannot be created, this routine will return
477undef.
478
479=head2 $asset->as_html
480
481Return the HTML I<IMG> element with the image asset attributes.
482
483=head1 AUTHOR & COPYRIGHT
484
485Please see the L<MT/"AUTHOR & COPYRIGHT"> for author, copyright, and
486license information.
487
488=cut
Note: See TracBrowser for help on using the browser.