root/branches/StyleCatcher-2.1-dev/plugins/StyleCatcher/lib/StyleCatcher/CMS.pm @ 1088

Revision 1088, 24.8 kB (checked in by mpaschal, 15 months ago)

Untangle this rat's nest of metadata discovery
BugzID: 81932

Line 
1# Movable Type (r) Open Source (C) 2005-2008 Six Apart, Ltd.
2# This program is distributed under the terms of the
3# GNU General Public License, version 2.
4#
5# $Id: CMS.pm 2576 2008-06-14 00:35:33Z bchoate $
6
7package StyleCatcher::CMS;
8
9use strict;
10use File::Basename qw( basename dirname );
11
12use MT::Util qw( remove_html decode_html );
13
14our $DEFAULT_STYLE_LIBRARY;
15
16sub style_library {
17    return MT->registry("stylecatcher_libraries");
18}
19
20sub file_mgr {
21    my $app = MT->instance;
22    require MT::FileMgr;
23    my $filemgr = MT::FileMgr->new('Local')
24      or return $app->error( MT::FileMgr->errstr );
25    $filemgr;
26}
27
28sub listify {
29    my ($data) = @_;
30    my @list;
31    foreach my $k (keys %$data) {
32        my %entry = %{ $data->{$k} };
33        $entry{key} = $k;
34        delete $entry{plugin};
35        $entry{label} = $entry{label}->() if ref($entry{label});
36        $entry{description_label} = $entry{description_label}->() if ref($entry{description_label});
37        push @list, \%entry;
38    }
39    @list = sort { $a->{order} <=> $b->{order} } @list;
40    \@list;
41}
42
43sub view {
44    my $app     = shift;
45    my $blog_id = $app->param('blog_id');
46    $app->return_to_dashboard( redirect => 1 ) unless $blog_id;
47
48    my $blog = MT::Blog->load($blog_id);
49    return $app->errtrans("Invalid request") unless $blog;
50
51    my $static_path = $app->static_file_path;
52    if (! -d $static_path ) {
53        return $app->errtrans("Your mt-static directory could not be found. Please configure 'StaticFilePath' to continue.");
54    }
55
56    my $themeroot =
57      File::Spec->catdir( $app->static_file_path, 'support', 'themes' );
58    my $webthemeroot = $app->static_path . 'support/themes';
59    my $stylelibrary = listify(style_library());
60    my $theme_data   = make_themes();
61    my $styled_blogs = fetch_blogs();
62
63    my $config = plugin()->get_config_hash();
64
65    my @blog_loop;
66    my %current_themes;
67    my ($blog_theme, $blog_layout);
68    foreach my $blog (@$styled_blogs) {
69        my $curr_theme = $config->{"current_theme_" . $blog->id} || '';
70        my $curr_layout = $config->{"current_layout_" . $blog->id} || 'layout-wtt';
71        push @blog_loop,
72          {
73            blog_id   => $blog->id,
74            blog_name => $blog->name,
75            layout    => $curr_layout,
76            theme_id  => $curr_theme,
77            view_link => $blog->site_url,
78          };
79        if ($blog->id == $blog_id) {
80            $blog_theme = $curr_theme;
81            $blog_layout = $curr_layout;
82        }
83        if ( $theme_data->{themes} && $curr_theme ) {
84            foreach my $theme ( @{ $theme_data->{themes} } ) {
85                if ( ($theme->{prefix} || '') . ':' . $theme->{name} eq $curr_theme ) {
86                    push @{ $theme->{blogs} }, $blog->id;
87                    next if exists $current_themes{ $theme->{name} };
88                    $current_themes{ $theme->{name} } = 1;
89                    push @{ $theme->{tags} }, 'collection:current';
90                }
91            }
92        }
93    }
94
95    push @{ $theme_data->{categories} }, 'current'
96      if %current_themes;
97
98    require JSON;
99    my $url   = $app->param('url');
100    my %param = (
101        version     => plugin()->version,
102        # blog_loop   => \@blog_loop,
103        blog_id => $blog_id,
104        themes_json => JSON::objToJson(
105            $theme_data, { pretty => 1, indent => 2, delimiter => 1 }
106        ),
107        auto_fetch => $url ? 1 : 0,
108        style_library => $stylelibrary,
109        current_theme => $blog_theme || '',
110        current_layout => $blog_layout || 'layout-wtt',
111        dynamic_blog => (($blog->custom_dynamic_templates || '') eq 'all'),
112    );
113
114    if ( $blog_id && @$styled_blogs ) {
115        my $blog = $styled_blogs->[0];
116        $param{blog_name} = $blog->name;
117        $param{blog_url}  = $blog->site_url;
118    }
119
120    my $path = $app->static_path;
121    $path .= '/' unless $path =~ m!/$!;
122    $path .= plugin()->envelope . "/";
123    $path = $app->base . $path if $path =~ m!^/!;
124    $param{plugin_static_uri} = $path;
125
126    $app->build_page( 'view.tmpl', \%param );
127}
128
129# AJAX/JSON modes
130
131# returns a json structure of styles given a particular url
132sub js {
133    # ydnar's remixer uses javascript files for each collection of styles -
134    # we generate these js files from css metadata
135    # StyleCatcher will pick up any metadata in the theme css file in the
136    # format of 'key: value' in comment-space
137    # The remixer only uses name, author, description at the moment.
138    my $app = shift;
139    return $app->json_error( $app->errstr ) unless $app->validate_magic;
140
141    my $data = fetch_themes($app->param('url'))
142        or return $app->json_error( $app->errstr );
143    return $app->json_result( $data );
144}
145
146sub files_from_response {
147    my ($res, %param) = @_;
148
149    my $extensions = $param{css} ? qr{ (?:gif|jpe?g|png|css) }xms
150                   :               qr{ (?:gif|jpe?g|png)     }xms
151                   ;
152
153    my $stylesheet = $res->content;
154    $stylesheet =~ s!/\*.*?\*/!!gs;    # strip all comments first
155    my @images = $stylesheet =~ m{
156        \b url\( \s*                          # opening url() reference
157        ['"]?
158        ( [\w\.\-/]+\.$extensions )  # a filename ending in an image extension
159        ['"]?
160        \s* \)                                # close of url() reference
161    }xmsgi;
162
163    return @images;
164}
165
166sub download_theme {
167    my $app = shift;
168    my ($url) = @_;
169
170    my $static_path = $app->static_file_path;
171    my $themeroot   = File::Spec->catdir($static_path, 'support', 'themes');
172    my $ua          = $app->new_ua();
173    my $filemgr     = file_mgr()
174        or return;
175
176    my @url = split( /\//, $url );
177    my $stylesheet_filename = pop @url;
178    my $theme_url = join(q{/}, @url) . '/';
179
180    my ($basename, $extension) = split /\./, $stylesheet_filename;
181    if ($basename eq 'screen' || $basename eq 'style') {
182        $basename = $url[-1];
183    }
184
185    # Pick up the stylesheet
186    my $stylesheet_res = $ua->get($url);
187
188    my @images = files_from_response($stylesheet_res, css => 1);
189
190    my $theme_path = File::Spec->catdir($themeroot, $basename);
191    if (!$filemgr->mkpath($theme_path)) {
192        my $error = $app->translate("Could not create [_1] folder - Check that your 'themes' folder is webserver-writable.",
193            $basename);
194        return $app->json_error($error);
195    }
196
197    $filemgr->put_data( $stylesheet_res->content,
198        File::Spec->catfile($theme_path, $basename . '.css') );
199
200    # Pick up the images we parsed earlier and write them to the theme folder
201    my %got_files;
202    my @files = ('thumbnail.gif', 'thumbnail-large.gif', @images);
203    FILE: while (my $rel_url = shift @files) {
204        # Is this safe to get?
205        my $full_url = URI->new_abs($rel_url, $theme_url);
206        next FILE if !$full_url;
207        my $url = $full_url->as_string();
208        next FILE if $url !~ m{ \A \Q$theme_url\E }xms;
209
210        next FILE if $got_files{$url};
211        $got_files{$url} = 1;
212        my $res = $ua->get($url);
213
214        # Skip files that don't download; we were accidentally doing so already.
215        next FILE if !$res->is_success();
216
217        my $canon_rel_url = URI->new($rel_url)->rel($theme_url);
218        my @image_path = split /\//, $canon_rel_url->as_string();
219        my $image_filename = pop @image_path;
220
221        my $image_path = File::Spec->catdir($theme_path, @image_path);
222        if (!$filemgr->exists($image_path) && !$filemgr->mkpath($image_path)) {
223            my $error = $app->translate("Could not create [_1] folder - Check that your 'themes' folder is webserver-writable.",
224                $basename);
225            return $app->json_error($error);
226        }
227
228        my $image_full_path = File::Spec->catfile($image_path, $image_filename);
229        $filemgr->put_data($res->content, $image_full_path, 'upload')
230          or return $app->json_error( $filemgr->errstr );
231
232        if ($image_filename =~ m{ \.css \z }xmsi) {
233            my @new_files = files_from_response($res, css => 0);
234            # Schedule these as full URLs so relative references aren't
235            # misabsolved relative to the theme directory.
236            @new_files = map {
237                my $uri = URI->new_abs($_, $url);
238                $uri ? $uri->as_string() : ();
239            } @new_files;
240            push @files, @new_files;
241        }
242    }
243
244    return $basename;
245}
246
247# does the work after user selects a particular theme to apply to a blog
248sub apply {
249    my $app = shift;
250
251    my ($blog_id, $url, $layout, $name, $template_set)
252        = map { $app->param($_) || q{} } (qw( blog_id url layout name template_set ));
253
254    # Load the default stylesheet for this blog
255    my $tmpl = load_style_template($blog_id);
256
257    $app->validate_magic or return $app->json_error($app->translate("Invalid request"));
258    return $app->json_error($app->translate("Invalid request"))
259      unless $blog_id && $url && $tmpl;
260
261    my $static_path = $app->static_file_path;
262    if (! -d $static_path ) {
263        return $app->json_error($app->translate("Your mt-static directory could not be found. Please configure 'StaticFilePath' to continue."));
264    }
265
266    # if this isn't a local url, then we have to grab some files from
267    # yonder...
268    my $static_url = $app->static_path;
269    if ( $url !~ m{ \A \Q$static_url\E (?:support/)? themes/ }xms ) {
270        my $basename = download_theme($app, $url)
271            or return;
272        $url = "${static_url}support/themes/$basename/$basename.css";
273    }
274
275    my $blog = MT->model('blog')->load($blog_id)
276      or return $app->json_error( $app->translate('No such blog [_1]', $blog_id) );
277    my $blog_tset = $blog->template_set;
278    my $base_css_url = MT->registry('template_sets')->{$blog_tset}->{base_css};
279
280    my $base_css = q{};
281    if ($base_css_url) {
282        my $uri = URI->new_abs($base_css_url, $app->static_path);
283        $base_css = '@import url(' . $uri->as_string() . ');'
284            if $uri;
285    }
286
287    # Replacing the theme import or adding a new one at the beginning
288    my $template_text  = $tmpl->text();
289    my $replaced       = 0;
290    my $header = '/* This is the StyleCatcher theme addition. Do not remove this block. */';
291    my $footer = '/* end StyleCatcher imports */';
292    my $styles = <<"EOT";
293$header
294$base_css
295\@import url($url);
296$footer
297EOT
298    if ($template_text =~ s/\Q$header\E.*\Q$footer\E/$styles/s) {
299        $tmpl->text( $template_text );
300        $replaced = 1;
301    }
302    unless ($replaced) {
303
304        # we're dealing with a template that wasn't modified before now
305        # we will need to backup the existing one to make sure the new
306        # style is applied properly.
307        my @ts = MT::Util::offset_time_list( time, $blog_id );
308        my $ts = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $ts[5] + 1900,
309          $ts[4] + 1, @ts[ 3, 2, 1, 0 ];
310        my $backup = $tmpl->clone;
311        delete $backup->{column_values}
312          {id};    # make sure we don't overwrite original
313        delete $backup->{changed_cols}{id};
314        $backup->name( $backup->name . ' (Backup from ' . $ts . ')' );
315        $backup->outfile('');
316        $backup->linked_file( $tmpl->linked_file );
317        $backup->rebuild_me(0);
318        $backup->build_dynamic(0);
319        $backup->identifier(undef);
320        $backup->type('backup');
321        $backup->save;
322        $tmpl->linked_file('');    # make sure this one isn't linked now
323        $tmpl->identifier('styles');
324        $tmpl->text($styles);
325    }
326
327    # Putting the stylesheet back together again
328    $tmpl->save or return $app->json_error( $tmpl->errstr );
329
330    $blog->page_layout($layout);
331    $blog->touch();
332    $blog->save();
333
334    # rebuild only the stylesheet! forcibly. with prejudice.
335    $app->rebuild_indexes(
336        BlogID   => $tmpl->blog_id,
337        Template => $tmpl,
338        Force    => 1
339    );
340
341    my $p = plugin();
342    $name =~ s/^repo_\d+:/local:/;
343    $name =~ s/\.css$//;
344    $p->set_config_value('current_theme_' . $blog_id, $name);
345    if ($layout) {
346        $p->set_config_value('current_layout_' . $blog_id, $layout);
347    } else {
348        $p->set_config_value('current_layout_' . $blog_id, undef);
349    }
350
351    return $app->json_result(
352        {
353            message =>
354              $app->translate("Successfully applied new theme selection.")
355        }
356    );
357}
358
359# Utility methods
360
361sub fetch_blogs {
362    my $app     = MT->app;
363    my $user    = $app->user;
364    my $blog_id = $app->param('blog_id');
365
366    my @blogs;
367    if ($blog_id) {
368        @blogs = MT::Blog->load($blog_id);
369    } else {
370        if ( $user->is_superuser() ) {
371            if ($blog_id) {
372                @blogs = MT::Blog->load($blog_id);
373            }
374        }
375        else {
376            my $args = { author_id => $user->id };
377            $args->{blog_id} = $blog_id if $blog_id;
378            require MT::Permission;
379            my @perms = MT::Permission->load( { author_id => $user->id } );
380            foreach my $perm (@perms) {
381                next unless $perm->can_edit_templates;
382                push @blogs, MT::Blog->load( $perm->blog_id );
383            }
384        }
385    }
386    my @styled_blogs;
387    foreach my $blog (@blogs) {
388        my $tmpl = load_style_template( $blog->id );
389        if ($tmpl) {
390            push @styled_blogs, $blog;
391        }
392    }
393    @styled_blogs = sort { $a->name cmp $b->name } @styled_blogs;
394
395    \@styled_blogs;
396}
397
398sub load_style_template {
399    my ($blog_id) = @_;
400
401    require MT::Template;
402    my $tmpl;
403
404    $tmpl = MT::Template->load(
405        {
406            blog_id    => $blog_id,
407            identifier => 'styles'
408        }
409    );
410
411    $tmpl ||= MT::Template->load(
412        {
413            blog_id => $blog_id,
414            outfile => "styles.css"
415        }
416    );
417
418    # MT 3.x era stylesheet file
419    $tmpl ||= MT::Template->load(
420        {
421            blog_id => $blog_id,
422            outfile => "styles-site.css"
423        }
424    );
425
426    unless ($tmpl) {
427
428        # Create one since we didn't find a candidate
429        $tmpl = new MT::Template;
430        $tmpl->blog_id($blog_id);
431        $tmpl->name('Stylesheet');
432        $tmpl->type('index');
433        $tmpl->identifier('styles');
434        $tmpl->outfile("styles.css");
435        $tmpl->text(<<'EOT');
436@import url(<$MTStaticWebPath$>themes-base/blog.css);
437@import url(<$MTStaticWebPath$>themes/minimalist-red/styles.css);
438EOT
439        $tmpl->save();
440    }
441
442    $tmpl;
443}
444
445# pulls a list of themes available from a particular url
446sub fetch_themes {
447    my $app = MT->app;
448    my ($url) = @_;
449    return undef unless $url;
450
451    my $blog_id = $app->param('blog_id');
452    my $data    = {};
453
454  # If we have a url then we're specifying a specific theme (css) or repo (html)
455    # Pick up the file (html with <link>s or a css file with metadata)
456    my $user_agent = $app->new_ua;
457    my $request    = HTTP::Request->new( GET => $url );
458    my $response   = $user_agent->request($request);
459
460    # Make a repo if you've got a ton of links or an automagic entry if
461    # you're a css file
462    my $type = $response->headers->{'content-type'};
463    $type = shift @$type if ref $type eq 'ARRAY';
464    if ( $type =~ m!^text/css! ) {
465        $data->{auto}{url} = $url;
466        my $theme = metadata_for_theme(
467            url  => $url,
468            tags => ['collection:auto'],
469        );
470        $data->{themes} = [$theme];
471    }
472    elsif ( $type =~ m!^text/html! ) {
473        my @repo_themes;
474        for my $link (
475            ref( $response->headers->{'link'} ) eq 'ARRAY'
476            ? @{ $response->headers->{'link'} }
477            : $response->headers->{'link'}
478          )
479        {
480            my ( $css, @parsed_link ) = split( /;/, $link );
481            $css =~ s/[<>]//g;
482            my %attr;
483            foreach (@parsed_link) {
484                my ( $name, $val ) = split /=/, $_, 2;
485                $name =~ s/^ //;
486                $val  =~ s/^['"]|['"]$//g;
487                next if $name eq '/';
488                $attr{ lc($name) } = $val;
489            }
490            next unless lc $attr{rel}  eq 'theme';
491            next unless lc $attr{type} eq 'text/x-theme';
492
493            # Fix for relative theme locations
494            if ($css !~ m!^https?://!) {
495                my $new_css = $url;
496                $new_css =~ s!/[a-z0-9_-]+\.[a-z]+?$|/$!/!;
497                $new_css .= $css;
498                $css = $new_css;
499            }
500            push @repo_themes, $css;
501        }
502
503        my $themes = [];
504        for my $repo_theme (@repo_themes) {
505            my $theme = metadata_for_theme(
506                url => $repo_theme,
507            );
508            push @$themes, $theme if $theme;
509        }
510        $data->{themes} = $themes;
511        if ( $data->{repo}{display_name} = $response->headers->{'title'} ) {
512            $data->{repo}{name} =
513              MT::Util::dirify( $data->{repo}{display_name} );
514        }
515        else {
516            $data->{repo}{display_name} = $url;
517            $data->{repo}{name}         = MT::Util::dirify($url);
518        }
519        $data->{repo}{url} = $url;
520    }
521    else {
522        return $app->error( $app->translate('Invalid URL: [_1]', $url) );
523    }
524
525    $data;
526}
527
528# sets up the object structure we return through json to populate
529# the mixer.
530sub make_themes {
531    my $app = MT->instance;
532
533    # categories
534    #   current    (for active theme)
535    #   repo       (for themes found at repo link)
536    #   my-designs (for themes that are stored locally)
537    #   mt-designs (for themes that are local and installed by default)
538    #   auto       (for link to a single css file)
539
540    # structure of "data"
541    #   categories => [ one, two, three ]  ie: 'current', 'repo'
542    #   themes => [
543    #       { theme }
544    #   ]
545    #   repo => {
546    #       display_name => 'display name',
547    #       name => 'repo name',
548    #       url => 'url of repo',
549    #   }
550
551# structure of "theme"
552#   theme => {
553#       name => 'theme_dir',
554#       imageSmall => 'link_to/thumbnail.gif',
555#       imageBig => 'link_to/thumbnail-large.gif',
556#       title => 'Theme Title',
557#       description => 'Theme description.',
558#       url_css => 'link_to/theme.css',
559#       url_zip => 'link_to/theme.zip',
560#       author => 'Author Name',
561#       author_url => 'http://author.com/'
562#       author_affiliation => 'Author Co.',
563#       layouts => "comma,delimited,layout,list"
564#       sort => 'theme_sortable_name',
565#       tags => ['association:tag']  ie, 'color:blue', 'designer:author', 'collection:repo'
566#   }
567
568    my ( $categories, $themes );
569    my $sys_root = File::Spec->catdir( $app->static_file_path, 'themes' );
570
571    # Generate our list of themes within the themeroot directory
572    my @sys_list = glob( File::Spec->catfile( $sys_root, "*" ) );
573    $categories->{'mt-designs'} = 1 if @sys_list;
574    for my $theme (@sys_list) {
575        my $theme_dir = $theme;
576        my $theme_url = $app->static_path . 'themes';
577        next unless -d $theme;
578        $theme =~ s/.*[\\\/]//;
579        $themes->{$theme} = metadata_for_theme(
580            path => $theme_dir,
581            url  => "$theme_url/$theme/",
582            tags => ['collection:mt-designs'],
583        );
584        $themes->{$theme}{prefix} = 'default';
585    }
586
587    my $themeroot =
588      File::Spec->catdir( $app->static_file_path, 'support', 'themes' );
589
590    # Generate our list of themes within the themeroot directory
591    my @themeroot_list = glob( File::Spec->catfile( $themeroot, "*" ) );
592    $categories->{'my-designs'} = 1 if @themeroot_list;
593    for my $theme (@themeroot_list) {
594        my $theme_dir = $theme;
595        next unless -d $theme;
596        $theme =~ s/.*[\\\/]//;
597        $themes->{$theme} = metadata_for_theme(
598            path => $theme_dir,
599            url  => $app->static_path . "support/themes/$theme/",
600            tags => ['collection:my-designs'],
601        );
602        $themes->{$theme}{prefix} = 'local';
603    }
604
605    my $data = {
606        categories => [ keys %$categories ],
607        themes     => [ values %$themes ]
608    };
609
610    $data;
611}
612
613sub theme_for_url {
614    my %param = @_;
615    my ($url, $path, $tags, $baseurl, $basepath)
616        = @param{qw( url path tags baseurl basepath )};
617    my $app = MT->instance;
618
619    my %theme;
620    if ($path && -e $path) {
621        $theme{stylesheet} = file_mgr()->get_data($path);
622        $theme{id} = basename(dirname($path));
623    }
624    elsif ($url) {
625        my $user_agent = $app->new_ua;
626        my $response   = $user_agent->get($url);
627        return if !$response->is_success();
628        $theme{stylesheet} = $response->content;
629
630        my $id = $url;
631        $id =~ s{ / (?:screen|style) \.css \z }{}xms;
632        $id =~ s/.*[\\\/]//;
633        $theme{id} = $id;
634    }
635
636    return %theme;
637}
638
639sub metadata_for_stylesheet {
640    my %param = @_;
641    my ($stylesheet) = @param{qw( stylesheet )};
642
643    # Pick up the metadata from the css
644    my @css_lines = split( /\r?\n/, $stylesheet || '' );
645    my $commented = 0;
646    my @comments;
647    for my $line (@css_lines) {
648        my $pos;
649        $pos = index( $line, "/*" );
650        unless ( $pos == -1 ) {
651            $line = substr( $line, $pos + 2 );
652            $commented = 1;
653        }
654        if ($commented) {
655            $pos = index( $line, "*/" );
656            unless ( $pos == -1 ) {
657                $line = substr( $line, 0, $pos );
658                $commented = 0;
659            }
660            push @comments, $line;
661        }
662    }
663
664    my %metadata;
665
666    # Trim me white space, yarr
667    for my $comment (@comments) {
668        # Strip any null bytes
669        $comment =~ tr/\x00//d;
670        $comment =~ s/^\s+|\s+$//g;
671
672        my ( $key, $value ) = split( /:/, $comment, 2 ) or next;
673        next unless defined $value;
674        $value =~ s/^\s+//;
675        $metadata{ lc $key } = $value;
676    }
677
678    my %field_map = (
679        title        => [ 'name',         'theme name' ],
680        author       => [ 'designer',     'author' ],
681        author_url   => [ 'designer_url', 'author_url', 'author uri' ],
682        template_set => [ 'template_set', 'template' ],
683        description  => [ 'description' ],
684    );
685    while (my ($best_name, $possible_names) = each %field_map) {
686        ($metadata{$best_name}) = grep { defined }
687            delete @metadata{ @$possible_names }, q{};
688        # TODO: do html mashing later
689        $metadata{$best_name} = decode_html(remove_html($metadata{$best_name}));
690    }
691
692    return %metadata;
693}
694
695sub thumbnails_for_theme {
696    my %param = @_;
697    my ($url, $path, $metadata)
698        = @param{qw( url path metadata )};
699    my $app = MT->instance;
700
701    my %thumbnails;
702    THUMB: for my $thumb (qw( thumbnail thumbnail_large )) {
703        $thumbnails{$thumb} = $metadata->{$thumb};
704        next THUMB if $thumbnails{$thumb};
705
706        my $thumb_filename = $thumb;
707        $thumb_filename =~ tr/_/-/;
708        $thumb_filename .= '.gif';
709
710        require URI;
711        if ($path) {
712            my ($volume, $dir, $theme_filename) = File::Spec->splitpath($path);
713            my $thumb_path = File::Spec->catpath($volume, $dir, $thumb_filename);
714            if (-e $thumb_path) {
715                my $url_uri = URI->new_abs($thumb_filename, $url);
716                $thumbnails{$thumb} = $url_uri->as_string();
717            }
718        }
719        elsif ($url) {
720            my $url_uri = URI->new_abs($thumb_filename, $url);
721            my $thumb_url = $url_uri->as_string();
722
723            my $user_agent = $app->new_ua;
724            my $response   = $user_agent->head($thumb_url);
725            if ($response->is_success()) {
726                $thumbnails{$thumb} = $thumb_url;
727            }
728        }
729
730        # Use plugin's default thumbnail if necessary.
731        $thumbnails{$thumb} ||= $app->static_path . 'plugins/StyleCatcher/'
732            . 'images/' . $thumb_filename;
733    }
734
735    return %thumbnails;
736}
737
738sub metadata_for_theme {
739    my $app = MT->app;
740    my %param = @_;
741    my ($url, $path, $tags, $default_metadata)
742        = @param{qw( url path tags metadata )};
743
744    # Update a path, if present, from a theme directory to the real full
745    # stylesheet path.
746    if ($path && -d $path) {
747        $path =~ s{ / \z }{}xms;
748        FILESTEM: for my $filestem (basename($path), "screen", "style") {
749            my $full_path = File::Spec->catfile($path, "$filestem.css");
750            if ($full_path && -f $full_path) {
751                $path = $param{path} = $full_path;
752
753                $url =~ s{ / \z }{}xms;
754                $url  = $param{url}  = "$url/$filestem.css";
755
756                last FILESTEM;
757            }
758        }
759    }
760
761    my %theme      = theme_for_url(%param);
762    my %metadata   = metadata_for_stylesheet(%param, %theme);
763    my %thumbnails = thumbnails_for_theme(%param, metadata => \%metadata);
764
765    my $data = {
766        name         => $theme{id},
767        description  => $metadata{description} || q{},
768        title        => $metadata{title} || '(Untitled)',
769        url          => $url,
770        imageSmall   => $thumbnails{thumbnail},
771        imageBig     => $thumbnails{thumbnail_large},
772        layouts      => $metadata{layouts} || q{},
773        sort         => lc($metadata{title} || $theme{id} || q{}),
774        tags         => $tags || [],
775        blogs        => [],
776        author       => $metadata{author},
777        author_url   => $metadata{author_url},
778        template_set => $metadata{template_set},
779        author_affiliation => $metadata{author_affiliation} || q{},
780    };
781    $data;
782}
783
784sub plugin {
785    return MT->component('StyleCatcher');
786}
787
7881;
Note: See TracBrowser for help on using the browser.