# Copyright 2002-2008 Josh Clark and Global Moxie, LLC. This code cannot be
# redistributed without permission from globalmoxie.com.  For more
# information, consult your Big Medium license.
#
# $Id: CSSEdit.pm 3043 2008-03-31 14:00:38Z josh $

package BigMed::App::Web::CSSEdit;
use strict;
use warnings;
use utf8;
use Carp;
$Carp::Verbose = 1;
use base qw(BigMed::App::Web::CP);
use BigMed::CSS;
use BigMed::JSON;
use BigMed::DiskUtil qw(bm_file_path bm_load_file);
use BigMed::Content::Page;
use BigMed::PageUtils;

sub setup {
    my $app = shift;
    $app->start_mode('edit');
    $app->set_cp_selected_nav('Styles');
    $app->run_modes(
        'AUTOLOAD'  => sub { $_[0]->rm_edit() },
        'edit'      => 'rm_edit',
        'ajax-save' => 'rm_ajax_save',
        'preview-page' => 'rm_preview_page',
    );
    return;
}

sub cgiapp_prerun {
    my $app = shift;
    $app->SUPER::cgiapp_prerun;
    $app->require_privilege_level(5);
    return;
}

###########################################################
# RUN MODES
###########################################################

sub rm_edit {
    my $app     = shift;
    my %options = @_;
    my $site    = $app->current_site;
    
    #gather CSS and add to page as JSON
    BigMed::CSS->init_css();
    my $all_css = BigMed::CSS->select( { site => $site->id } )
      or $app->error_stop;
    my %css_rule;
    my $css;
    while ( $css = $all_css->next ) {
        $css_rule{ $css->selector } = { $css->attributes };
    }
    $app->error_stop if !defined $css;
    $app->js_add_code( 'BM.CSSedit.rules=' . objToJson( \%css_rule ) );
    
    #build page-preview selection
    my @preview_pages = $app->_preview_urls($site);
    my (@urls, %label);
    foreach my $page (@preview_pages) {
        my $esc_url = $app->escape( $page->[1] );
        push @urls, $esc_url;
        $label{$esc_url} = $app->language($page->[0]);
    }
    my $preview_field = $app->prompt_field_ref(
        id => 'BM_PAGE_PREVIEW',
        prompt_as => 'value_list',
        options    => \@urls,
        labels     => \%label,
        label      => 'CSSEDIT_Preview Page',
    );
    my $highlight = $app->prompt_field_ref(
        prompt_as    => 'boolean',
        id           => 'highlight_element',
        label        => 'CSSEDIT_Highlight',
        option_label => 'CSSEDIT_Highlight affected elements',
        value        => 1,
    );
    my $rpreview_fieldset = [
        $app->prompt_fieldset_ref(
            id        => 'bmfs_preview',
            fields    => [$preview_field, $highlight],
            query     => $options{query},
            field_msg => $options{field_msg},
        ),
    ];


    #build form
    my @opt_groups;
    foreach my $group_name (BigMed::CSS->selector_groups) {
        my @selectors = BigMed::CSS->selectors($group_name);
        push @opt_groups, {
            name => $app->language("CSS_SELGRP $group_name"),
            options => \@selectors,
            labels   => { map{ $_ => $app->language("CSS_SEL $_") } @selectors },
        }
    }
    my $selector_field = $app->prompt_field_ref(
        data_class => 'BigMed::CSS',
        column     => 'selector',
        optgroups    => \@opt_groups,
    );

    my @tabs;
    my @font_styles = ('font-weight', 'font-style', 'text-decoration');
    my %font_style = map {$_=>1} @font_styles;
    my ($built_font_style, $built_margin, $built_padding);
    foreach my $group ( BigMed::CSS->attribute_groups ) {
        my @fields;
        foreach my $attr ( BigMed::CSS->attribute_names($group) ) {
            if ($font_style{$attr}) {
                next if $built_font_style;
                push @fields, $app->_prompt_font_styles(@font_styles);
                $built_font_style = 1;
            }
            elsif ( $attr =~ /padding/ ) {
                next if $built_padding;
                push @fields, $app->_prompt_css_box_dimensions('padding');
                $built_padding = 1;
            }
            elsif ( $attr =~ /margin/ ) {
                next if $built_margin;
                push @fields, $app->_prompt_css_box_dimensions('margin');
                $built_margin = 1;
            }
            else {
                my %prompt = BigMed::CSS->attribute_prompt($attr);
                if ($prompt{labels}) { #localize
                    foreach my $key (keys %{ $prompt{labels} } ) {
                        $prompt{labels}->{$key} =
                          $app->language( $prompt{labels}->{$key} );
                    }
                }
                if ($prompt{optgroups}) { #localize
                    foreach my $group (@{ $prompt{optgroups} } ) {
                        $group->{name} = $app->language( $group->{name} );
                    }
                }
                push @fields,
                  $app->prompt_field_ref(
                    id        => "BMCSS_$attr",
                    value     => q(),
                    label     => "CSSATTR_$attr",
                    prompt_as => 'simple_text',
                    %prompt,
                  );
            }
        }
        push @tabs,
          { id      => "CSSGROUP_$group",
            label   => "CSSGROUP_$group",
            checked => @tabs < 1,
            field   => \@fields,
          };
    }
    my $radio_set = $app->prompt_field_ref(
        prompt_as => 'radio_toggle',
        id        => 'css_groups',
        value     => \@tabs,
        container_id => 'BM_CSSCONTROLS',
    );
    my $submit = $app->prompt_field_ref(
        id        => 'css_submit',
        prompt_as => 'submit',
        value     => $app->language('BM_SUBMIT_LABEL_Save'),
    );

    my $r_rules_fieldset = [
        $app->prompt_fieldset_ref(
            id        => 'bmfs_cssrules',
            title     => 'CSSEDIT_Edit Styles',
            fields    => [$selector_field, $radio_set],
            help => 'CSSEDIT_HELP_Selector',
            query     => $options{query},
            field_msg => $options{field_msg},
        ),
        $app->prompt_fieldset_ref(
            id     => 'bmfs_save',
            fields => [$submit],
        ),
    ];

    #page title and message
    my $edit_tmpl = $app->build_url(
        script => 'bm-templates.cgi',
        rm     => 'menu',
        site   => $site->id,
    );
    my $edit_theme = $app->build_url(
        script => 'bm-themes.cgi',
        rm     => 'edit',
        site   => $site->id,
    );
    my $new_theme = $app->build_url(
        script => 'bm-themes.cgi',
        rm     => 'menu',
        site   => $site->id,
    );
    my $clear = ' href="#" class="canceler" id="BM_CLEAR_ALL_STYLES"';
    my $orig_message =
      ['CSSEDIT_Intro', $edit_tmpl, $edit_theme, $new_theme, $clear];
    my ( $title, $message ) = $app->title_and_message(
        field_msg => $options{field_msg},
        message   => $options{message} || $orig_message,
        title     => 'CSSEDIT_Edit Styles',
    );
    $app->set_cp_breadcrumbs( { bc_label => 'CSSEDIT_Edit Styles' } );

    #javascript setup
    $app->js_add_script( $app->env('BMADMINURL') . '/js/bm-css.js' );
    my $form_url = $app->build_url(
        script => 'bm-css.cgi',
        rm     => 'ajax-save',
        site   => $site->id,
    );
    return $app->html_template_screen(
        'screen_css_edit.tmpl',
        bmcp_title      => $title,
        form_url        => $form_url,
        message         => $message,
        preview_fieldset => $rpreview_fieldset,
        rules_fieldset  => $r_rules_fieldset,
        iframe_url      => $urls[0],
    );
}

sub rm_ajax_save {
    my $app = shift;
    my $site = $app->current_site;

    my %field = $app->parse_submission(
        {
            id => 'styles',
            parse_as => 'raw_text',
            required => 1,
        },
    );

    return $app->ajax_parse_error($field{_ERROR}) if $field{_ERROR};
    my $rstyles = jsonToObj($field{styles});

    #gather and parse all style values, identifying empties for deletion
    my @delete;
    my %final_style;
    foreach my $selector (BigMed::CSS->selectors) {
        my $rrules = $rstyles->{$selector} || {};
        my %selector_style;
        ATTRIBUTES: foreach my $attr (keys %{$rrules} ) {
            my $v = $rrules->{$attr};
            
            #don't use eq test, can cause trouble with json object, use length
            next ATTRIBUTES if !defined ($v) || length($v) == 0;
            $v = BigMed::CSS->parse_attribute_value($attr, $v);
            $selector_style{$attr} = $v if $v ne q{};
        }
        if (!keys %selector_style) { #no styles for this selector
            push @delete, $selector;
        }
        else {
            $final_style{$selector} = \%selector_style;
        }
    }
    
    #update real items and delete empty or defunct
    my $all_css = BigMed::CSS->select({site=>$site->id});
    foreach my $sel (keys %final_style) {
        defined ( my $this_sel = $all_css->select({site=>$site->id,selector=>$sel}) )
          or return $app->ajax_system_error;
        my $css = $this_sel->next;
        if (!$css) { #none previously saved
            $css = BigMed::CSS->new();
            $css->set_selector($sel);
            $css->set_section(0);
            $css->set_site($site->id);
        }
        my $extra; #make sure no dupes
        while ($extra = $this_sel->next) {
            $extra->trash or return $app->ajax_system_error;
        }
        return $app->ajax_system_error if !defined $extra;
        
        $css->set_attributes($final_style{$sel});
        $css->save or return $app->ajax_system_error;
    }
    if (@delete) {
        my $defunct = BigMed::CSS->select({site=>$site->id,selector=>\@delete})
          or return $app->ajax_system_error;
        $defunct->trash_all or return $app->ajax_system_error;
    }
    
    #rebuild the css
    BigMed::CSS->build_sheet($site) or return $app->ajax_system_error;

    return $app->ajax_html('Changes saved.');
}

sub rm_preview_page {
    my $app = shift;
    my $site = $app->current_site;
    my ($sid, $pid) = $app->path_args;
    my $sec = $site->section_obj_by_id($sid);
    if (defined $sec && !$sec) {
        $app->set_error(
          head => 'SECTIONS_HEAD_Section could not be found',
          text => ['SECTIONS_Section could not be found', $sid, $site->name],
        );    
    }
    $app->error_stop if !$sec;
    my $page = BigMed::Content::Page->fetch({site=>$site->id, id=>$pid});
    if (defined $page && !$page) {
        $app->set_error(
          text => ['PARSE_ERR_Could not locate page',$pid],
        );    
    }
    $app->error_stop if !$page;
    
    my $path = _preview_page_path($site,$sec,$page);
    my @file;
    if (!$path || !( @file = bm_load_file($path) ) ) {
        $app->set_error(
          head => 'CSSEDIT_HEAD_No published page could be found',
          text => 'CSSEDIT_No published page could be found',
        );    
        $app->error_stop;
    }
    my $html = join("\n",@file);
    
    #don't include the base_url parameter; will screw up IE's ability
    #to edit the stylesheet for other domains (cross-domain restrictions)
    $html =
      BigMed::PageUtils::page_to_cgi( $site, $html )
      . '<style type="text/css" title="___BM_CSSEDITOR_SHEET">body{}</style>';
    return $html;
}


sub _prompt_font_styles {
    my ($app, @attributes) = @_;
    my $img = $app->env('BMADMINURL') . '/img/bmcp_css_';
    my @buttons = map{ {
            id => "BMCSS_$_",
            value => q(),
            button_html => qq{<img src="$img$_.gif" alt="" />},
            BigMed::CSS->attribute_prompt($_),
        } } @attributes;
    return $app->prompt_field_ref(
        prompt_as => 'tritoggle',
        buttons => \@buttons,
        label => 'CSSATTR_Font Style',
        description => 'CSSATTR_DESC_Font Style',
    );
}

sub _prompt_css_box_dimensions {
    my ($app, $attr) = @_;
    return $app->prompt_field_ref(
        id => "BMCSS_$attr",
        prompt_as => 'css_box_dimensions',
        label => "CSSATTR_$attr",
        force_right => 1,
    );
}

sub _preview_urls {
    my ( $app, $site ) = @_;
    my $home = $site->homepage_obj or return ();
    my @sections = map { $site->section_obj_by_id($_) } $home->kids;
    return if $site->error;

    #collect homepage
    my @pages;
    my $homepage = $home->section_page_obj();
    
    #always include the homepage even if it doesn't exist on disk;
    #will display a message to rebuild pages if that's the case.
    push @pages,
      ['CSSEDIT_Homepage', $app->_relay_url( $site, $home, $homepage )];

    #collect sections
    foreach my $sec (@sections) {
        next if !$sec->active;
        my $secpage = $sec->section_page_obj;
        my $path    = _preview_page_path( $site, $sec, $secpage ) or next;
        my $url     = $app->_relay_url( $site, $sec, $secpage );
        push @pages, [['CSSEDIT_Main section', $sec->name], $url];
    }

    #collect detail pages
    my @det_secs = $site->all_active_descendants_ids;
    my @detail_pages;
    if (@det_secs) {
        my $pages = BigMed::Content::Page->select(
            {   site       => $site->id,
                sections   => \@det_secs,
                pub_status => 'published'
            },
            { sort => 'mod_time', order => 'descend', limit => 15 },
          )
          or return;
        my $page;
        while ( ( $page = $pages->next ) && ( @detail_pages < 5 ) ) {
            next if $page->subtype && $page->subtype eq 'section';
            my $sid   = $page->effective_active_section($site);
            my $sec   = $site->section_obj_by_id($sid) or next;
            my $path  = _preview_page_path( $site, $sec, $page ) or next;
            my $title =
              length( $page->title ) > 20
              ? substr( $page->title, 0, 20 ) . '...'
              : $page->title;
            my $url = $app->_relay_url( $site, $sec, $page );
            push @detail_pages, [['CSSEDIT_Detail page', $title], $url];
        }
        return if !defined $page;
    }
    return (@pages, @detail_pages);
}

sub _preview_page_path {
    my ($site, $sec, $page) = @_;
    return if !$page;
    my $dir = $site->directory_path($sec) or return;
    my $slug = $page->subtype eq 'section' ? 'index' : $page->slug;
    my $path = bm_file_path($dir, "${slug}.shtml");
    return $path if -e $path;
    return;
}

sub _relay_url {
    my ($app, $site, $sec, $page) = @_;
    return $app->build_url(
        script => 'bm-css.cgi',
        rm     => 'preview-page',
        site   => $app->current_site->id,
        args   => [$sec->id, $page->id],
    );
}
1;

__END__

