File Coverage

blib/lib/Mojolicious/Plugin/CountryDropDown.pm
Criterion Covered Total %
statement 210 232 90.5
branch 82 106 77.3
condition 40 69 57.9
subroutine 26 26 100.0
pod 1 1 100.0
total 359 434 82.7


line stmt bran cond sub pod time code
1              
2             package Mojolicious::Plugin::CountryDropDown;
3              
4             # ABSTRACT: Provide a localizable dropdown where users can select a country
5              
6 11     11   50640 use strict;
  11         22  
  11         442  
7 11     11   73 use warnings;
  11         20  
  11         425  
8              
9 11     11   1798 use Mojo::Base 'Mojolicious::Plugin';
  11         27221  
  11         99  
10 11     11   14702 use Locale::Country::Multilingual { use_io_layer => 1 };
  11         45985  
  11         75  
11 11     11   35392 use Unicode::Collate;
  11         176197  
  11         638  
12 11     11   13548 use List::MoreUtils qw( zip pairwise );
  11         19791  
  11         2940  
13 11     11   91 use Carp qw(confess);
  11         31  
  11         48929  
14              
15             our $VERSION = 0.06;
16             $VERSION = eval $VERSION; ## no critic ProhibitStringyEval
17              
18             sub register {
19 10     10 1 744 my ( $plugin, $app, $opts ) = @_;
20              
21 10 50       97 my $collate = Unicode::Collate->new()
22             or confess 'Unsatisfied dependency: Unicode::Collate';
23 10 50       828678 my $lcm = Locale::Country::Multilingual->new()
24             or confess 'Unsatisfied dependency: Locale::Country::Multilingual';
25              
26 10         431 $plugin->{_collate} = $collate;
27 10         37 $plugin->{_lcm} = $lcm;
28              
29 10         67 $plugin->_build_conf( $app, $opts );
30              
31             $app->helper(
32             'country_select_field' => sub {
33 14     14   491622 my ( $c, $opts ) = @_;
34              
35 14   100     193 my $selected = $opts->{select} || $plugin->{_conf}->{select} || '';
36 14         50 my %countries = %{ $plugin->_build_country_list( $c, $opts ) };
  14         79  
37              
38 14         691 my @preferred = ();
39 14 100 66     86 if ( exists $opts->{prefer} and ( ref $opts->{prefer} eq ref [] ) ) {
40 2         3 @preferred = map { uc($_) } @{ $opts->{prefer} };
  6         16  
  2         7  
41             }
42             else {
43 12 100       22 @preferred = @{ $plugin->{_conf}->{prefer} || [] };
  12         95  
44             }
45              
46 14         582 my @vals = values(%countries);
47 14         853 my @keys = keys(%countries);
48 14         4090 my %countries_by_name = zip @vals, @keys;
49              
50             # simply using "sort" isn't appropriate...
51 14         1163 my @sorted_names = $plugin->{_collate}->sort( keys %countries_by_name );
52 14         1213567 my @sorted_codes = @countries_by_name{@sorted_names};
53              
54 14         59 my @preferred_options = ();
55 14 100       81 if (@preferred) {
56 4         14 foreach my $code (@preferred) {
57 12         43 my @opt = ( $countries{$code}, $code );
58 12 100       34 if ( $code eq $selected ) {
59 3         9 push @opt, selected => 'selected';
60 3         8 $selected = '';
61             }
62 12         43 push @preferred_options, [@opt];
63             }
64 4         17 push @preferred_options, [ '-----', '', disabled => 'disabled' ];
65             }
66              
67             my @options = (
68             @preferred_options,
69             pairwise {
70              
71             # $a = $names->[$n], $b = $codes->[$n]
72 3498         6758 my $option = [ $a, $b ];
73 3498 100       6161 push @$option, selected => "selected" if $b eq $selected;
74 3498         8027 $option;
75             }
76 14         448 @sorted_names,
77             @sorted_codes
78             );
79              
80             # html code gets generated by Mojolicious
81 14 50 66     187 my %attr = %{ $opts->{html_attr} || $plugin->{_conf}->{html_attr} || {} };
  14         264  
82 14   100     94 my $name = delete( $attr{name} ) || 'country';
83              
84 14 100 66     98 if ( !exists $attr{id} ) {
    100          
85 11         37 $attr{id} = $name;
86             }
87             elsif ( exists $attr{id} and not defined $attr{id} ) {
88 2         8 delete $attr{id};
89             }
90              
91 14         374 return $c->select_field( $name, \@options, %attr );
92             }
93 10         250 );
94              
95 10     1   1589 $app->helper( 'country_drop_down' => sub { return shift->country_select_field(@_) } );
  1         44843  
96              
97             $app->helper(
98             'csf_country_list' => sub {
99 7     7   109376 my $c = shift;
100 7   100     49 my $opts = shift // {};
101 7         94 return $plugin->_build_country_list( $c, $opts );
102             }
103 10         906 );
104              
105             $app->helper(
106             'csf_conf' => sub {
107 20     20   330034 my ( $c, $new ) = @_;
108              
109 20 100       99 unless ( defined $new ) {
110 1         9 return $plugin->{_conf};
111             }
112              
113 19 100 66     189 if ( ref $new eq ref {} and !%{$new} ) {
  19         117  
114 2         12 delete $plugin->{_conf};
115             }
116              
117 19         104 $plugin->_build_conf( $c, $new );
118 19         86 return $plugin->{_conf};
119             }
120 10         973 );
121              
122             $app->helper(
123             'code2country' => sub {
124 12     12   259025 my ( $c, $code ) = ( shift, shift );
125              
126 12 100 66     127 unless ( defined $code and $code ) {
127 1         25 $c->app->log->warn('code2country() called without a code');
128 1         64 return;
129             }
130              
131 11         61 my $lang = $plugin->_get_language( $c, shift );
132              
133 11         83 return $plugin->{_lcm}->code2country( $code, $lang );
134             }
135 10         956 );
136              
137             $app->helper(
138             'country2code' => sub {
139 12     12   217122 my ( $c, $country, $lang, $codeset ) = @_;
140              
141 12 50 33     108 unless ( defined $country and $country ) {
142 0         0 $c->app->log->warn('country2code() called without a country name');
143 0         0 return;
144             }
145              
146 12         116 $lang = $plugin->_get_language( $app, $lang );
147 12   66     68 $codeset = $plugin->_check_codeset( $app, $codeset ) // $plugin->{_conf}->{codeset};
148              
149 12         86 return $plugin->{_lcm}->country2code( $country, $codeset, $lang );
150             }
151 10         992 );
152 10         902 return;
153             } ## end sub register
154              
155             # Determine the most appropriate language to use using a fallback hierarchy
156              
157             sub _get_language {
158 44     44   114 my ( $self, $c, $param ) = @_;
159              
160             # param considered first
161 44 100       148 if ( defined $param ) {
162 16 50       75 if ( $self->_check_lng_avail($param) ) {
163 16         68 return $param;
164             }
165             }
166              
167             # explicitly configured languages considered next
168 28 100       143 if ( exists $self->{_conf}->{language} ) {
169 11         72 return $self->{_conf}->{language};
170             }
171              
172             # fallback: language determined by I18N plugin
173 17 50 33     97 if ( defined $c->stash->{i18n}
174             && ( my $l = $c->stash->{i18n}->languages() ) )
175             {
176 0 0       0 if ( $self->_check_lng_avail($l) ) {
177 0         0 return $l;
178             }
179              
180 0         0 $c->app->log->debug( 'The language determined by the I18N plugin is not available; using fallback "en"' );
181             }
182              
183             # fallback: default = en
184 17 50       535 if ( $self->_check_lng_avail('en') ) {
185 17         130 return 'en';
186             }
187              
188             # 'en' not available? somethingĀ“s borken...
189 0         0 $c->app->log->error( "Loading language 'en' failed! Broken installation of Locale::Country::Multilingual?" );
190 0         0 confess "Default language 'en' not available!";
191             } ## end sub _get_language
192              
193             sub _check_lng_avail {
194 44     44   136 my ( $self, $l ) = @_;
195              
196 44 100       90 my %lng_avail = %{ $self->{_lng_avail} || {} };
  44         374  
197 44 100 66     332 return 1 if defined $lng_avail{$l} and $lng_avail{$l};
198 22 50       111 return 0 if defined $lng_avail{$l};
199              
200             # check if L::C::M supports this language
201             # results are cached
202 22         294 my $loaded = $self->{_lcm}->assert_lang($l);
203              
204 22 100 66     496498 if ( defined $loaded and lc($loaded) eq lc($l) ) {
205 21         127 $self->{_lng_avail}->{$l} = 1;
206 21         153 return 1;
207             }
208             else {
209 1         5 $self->{_lng_avail}->{$l} = 0;
210 1         6 return 0;
211             }
212             }
213              
214             sub _build_conf {
215 29     29   74 my ( $self, $c, $opts ) = @_;
216              
217 29 100       187 unless ( defined $self->{_conf} ) {
218             # populate basic defaults
219 12         99 $self->{_conf} = { html_attr => { name => 'country' }, codeset => 'LOCALE_CODE_ALPHA_2', };
220             }
221              
222 29 50       58 my %conf = %{ $opts || {} };
  29         194  
223 29         96 foreach my $p (qw/ select language names prefer exclude html_attr codeset /) {
224 203 100       517 if ( exists $conf{$p} ) {
225 32         78 my $method = '_' . $p;
226 32         188 $self->$method( $c, $conf{$p} );
227             }
228             }
229              
230 29         108 return;
231             }
232              
233             sub _build_country_list {
234 21     21   53 my ( $self, $c ) = ( shift, shift );
235 21 50       46 my %opts = %{ shift || {} };
  21         126  
236              
237 21         165 $self->{_lcm}->set_lang( $self->_get_language( $c, $opts{language} ) );
238              
239             # get the codes from the right codeset and the country names in the language set above
240 21   33     370 my $codeset = $self->_check_codeset( $c, $opts{codeset} )
241             || $self->{_conf}->{codeset};
242 21         57 my $list = {};
243              
244 21 100       78 if ( $codeset eq 'LOCALE_CODE_ALPHA_2' ) {
245 19         104 my @codes = $self->{_lcm}->all_country_codes($codeset);
246 19         3058 my @names = $self->{_lcm}->all_country_names();
247 19         4755 %{$list} = zip @codes, @names;
  19         2285  
248             }
249             else {
250             # performance killer, but I donĀ“t see another solution
251 2         13 foreach my $code ( $self->{_lcm}->all_country_codes($codeset) ) {
252 498         10440 $list->{$code} = $self->{_lcm}->code2country($code);
253             }
254             }
255              
256             # remove excluded countries
257 21         1052 my @excl = ();
258 21 100       148 if ( defined $opts{exclude} ) {
    50          
259 1         3 @excl = @{ $opts{exclude} };
  1         4  
260             }
261             elsif ( defined $self->{_conf}->{exclude} ) {
262 0         0 @excl = @{ $self->{_conf}->{exclude} };
  0         0  
263             }
264 21         74 for my $code (@excl) {
265 1         3 $code = uc($code);
266 1         4 delete $list->{$code};
267             }
268              
269             # replace official country names with app-specific names
270 21 50 33     221 if ( defined $opts{names} and ( ref $opts{names} eq ref {} ) ) {
    100          
271 0         0 while ( my ( $k, $v ) = each %{ $opts{names} } ) {
  0         0  
272 0         0 $k = uc($k);
273 0         0 $list->{$k} = $v;
274             }
275             }
276             elsif ( exists $self->{_conf}->{names} ) {
277 4         6 while ( my ( $k, $v ) = each %{ $self->{_conf}->{names} } ) {
  16         72  
278 12         19 $k = uc($k);
279 12         30 $list->{$k} = $v;
280             }
281             }
282              
283             # return a hash of country names indexed by code
284 21         2126 return $list;
285             } ## end sub _build_country_list
286              
287             sub _check_codeset {
288 39     39   109 my ( $self, $c, $candidate ) = @_;
289              
290 39 100 66     5308 return unless defined $candidate and $candidate;
291 12         41 $candidate = uc($candidate);
292              
293 12 100       57 unless ( index( $candidate, "LOCALE_CODE_" ) == 0 ) {
294 8         28 $candidate = "LOCALE_CODE_" . $candidate;
295             }
296              
297 12 100       84 if ( $candidate =~ /\ALOCALE_CODE_(?:ALPHA_[23]|NUMERIC)\Z/ ) {
298 11         47 return $candidate;
299             }
300              
301 1         27 $c->app->log->warn('Invalid value specified for codeset');
302              
303 1         79 return;
304             }
305              
306             # Following here are helper functions for (re-)setting various config elements
307              
308             sub _select {
309 2     2   5 my ( $self, $c, $s ) = @_;
310              
311 2 50       13 if ( defined $s ) {
312 2 50       6 if ( ref $s ) {
313 0         0 $c->app->log->warn('Please provide a scalar value');
314             }
315             else {
316 2         10 $self->{_conf}->{select} = uc($s);
317             }
318             }
319             else {
320 0         0 delete $self->{_conf}->{select};
321             }
322              
323 2         5 return;
324             }
325              
326             sub _codeset {
327 7     7   18 my ( $self, $c, $code ) = @_;
328              
329 7 100       28 if ( defined $code ) {
330 6 100       32 if ( my $val = $self->_check_codeset( $c, $code ) ) {
331 5         20 $self->{_conf}->{codeset} = $val;
332             }
333             }
334             else {
335 1         3 $self->{_conf}->{codeset} = 'LOCALE_CODE_ALPHA_2';
336             }
337              
338 7         26 return;
339             }
340              
341             sub _language {
342 13     13   38 my ( $self, $c, $lang ) = @_;
343              
344 13 100 66     186 if ( defined $lang and $lang ) {
345 11 100       57 if ( $self->_check_lng_avail($lang) ) {
346 10         47 $self->{_conf}->{language} = $lang;
347             }
348             else {
349 1         31 $c->app->log->warn("Language '$lang' is not supported!");
350 1         71 delete $self->{_conf}->{language};
351             }
352             }
353             else {
354 2         9 delete $self->{_conf}->{language};
355             }
356              
357 13         51 return;
358             }
359              
360             sub _prefer {
361 3     3   8 my ( $self, $c, $codes ) = @_;
362              
363 3 100 66     25 if ( defined $codes and $codes ) {
364 2 50 33     28 if ( ref $codes and not( ref $codes eq ref [] ) ) {
365 0         0 $c->app->log->warn( 'Either provide scalar value or array ref with name(s) of preferred countries' );
366             }
367             else {
368 2 50       11 $codes = [$codes] unless ref $codes;
369 2         4 $_ = uc($_) foreach ( @{$codes} );
  2         16  
370 2         8 $self->{_conf}->{prefer} = $codes;
371             }
372             }
373             else {
374 1         5 delete $self->{_conf}->{prefer};
375             }
376              
377 3         10 return;
378             }
379              
380             sub _exclude {
381 2     2   4 my ( $self, $c, $codes ) = @_;
382              
383 2 100 66     13 if ( defined $codes and $codes ) {
384 1 50 33     11 if ( ref $codes and not( ref $codes eq ref [] ) ) {
385 0         0 $c->app->log->warn( 'Either provide scalar value or array ref with name(s) of excluded countries' );
386             }
387             else {
388 1 50       5 $codes = [$codes] unless ref $codes;
389 1         2 $_ = uc($_) foreach ( @{$codes} );
  1         7  
390 1         4 $self->{_conf}->{exclude} = $codes;
391             }
392             }
393             else {
394 1         4 delete $self->{_conf}->{exclude};
395             }
396              
397 2         6 return;
398             }
399              
400             sub _names {
401 2     2   5 my ( $self, $c, $names ) = @_;
402              
403 2 50 33     19 if ( defined $names and $names ) {
404 2 50       16 if ( not ref $names eq ref {} ) {
405 0         0 $c->app->log->warn( 'Please provide a hash ref with a mapping of country ids to names!' );
406             }
407             else {
408 2         3 foreach ( keys %{$names} ) {
  2         9  
409 5         18 $names->{ uc($_) } = delete $names->{$_};
410             }
411 2         9 $self->{_conf}->{names} = $names;
412             }
413             }
414             else {
415 0         0 delete $self->{_conf}->{names};
416             }
417              
418 2         6 return;
419             }
420              
421             sub _html_attr {
422 3     3   10 my ( $self, $c, $attr ) = @_;
423              
424 3 50 33     26 if ( defined $attr and $attr ) {
425 3 50       17 if ( not ref $attr eq ref {} ) {
426 0         0 $c->app->log->warn('Please provide a hash ref with attribute names and values!');
427             }
428             else {
429 3         10 $self->{_conf}->{html_attr} = $attr;
430 3 100 66     46 unless ( defined $self->{_conf}->{html_attr}->{name}
431             and $self->{_conf}->{html_attr}->{name} =~ /\A\S+\Z/ )
432             {
433 1         5 $self->{_conf}->{html_attr}->{name} = 'country';
434             }
435             }
436             }
437             else {
438 0         0 delete $self->{_conf}->{html_attr};
439              
440             # form field with no name makes no sense
441             # set default name "country"
442 0         0 $self->{_conf}->{html_attr} = { name => 'country' };
443             }
444              
445 3         10 return;
446             } ## end sub _html_attr
447              
448             1;
449              
450              
451              
452              
453              
454             =pod
455              
456             =head1 NAME
457              
458             Mojolicious::Plugin::CountryDropDown - Provide a localizable dropdown where users can select a country
459              
460             =head1 VERSION
461              
462             version 0.06
463              
464             =head1 SYNOPSIS
465              
466             use Mojolicious::Plugin::CountryDropDown;
467              
468             sub startup {
469             my $self = shift;
470              
471             $self->plugin('CountryDropDown');
472             ...
473              
474             In your controller:
475              
476             get '/' => sub {
477             my $self = shift;
478              
479             $self->csf_conf({ language => 'de' }); # this sets the default language
480             ...
481              
482             In your template (this time with TemplateToolkit syntax):
483              
484             [% h.country_select_field({ html_attr => { class => 'shiny', } }) %]
485              
486             =encoding utf-8
487              
488             =head1 NAME
489              
490             Mojolicious::Plugin::CountryDrowDown - use a localized dropdown ("select" field) to let
491             your users select countries in your HTML forms.
492              
493             =head1 VERSION
494              
495             Version 0.06
496              
497             =head1 WARNINGS
498              
499             Version 0.04 was the first public release and considered a beta release!
500              
501             Version 0.05_0x and later include some extensive API changes and are
502             generally incompatible with 0.04 - so please watch out when updating!
503              
504             =head1 CONFIGURATION
505              
506             You may pass a hash ref on plugin registration. The following keys are currently
507             recognized:
508              
509             =over 4
510              
511             =item language
512              
513             Language code (string). The language used for the country names.
514              
515             Valid values are those known to L.
516             Invalid values will be silently ignored. See section "LANGUAGE FALLBACKS" below.
517              
518             =item select
519              
520             Country code (string). Sets the country to be selected by default.
521              
522             Valid values are those known to L.
523             Invalid values (i.e. unknown codes) will be accepted but of course have no effect when generating
524             the form (unless you add artificial country names for these codes using the C config key).
525             See section "CODESETS" below.
526              
527             =item prefer
528              
529             Array reference with a list of country codes which are put to the
530             top of the select field in the order in which they appear in this list.
531             A spacer option is added after them and before the following list of countries.
532             The countries specified here arenĀ“t currently removed from the following list
533             so that they will appear twice within the select form field.
534             If one of these preferred countries is also "select"ed, the pre-selection will
535             happen in the prepended section of preferred countries.
536              
537             Valid values are country codes known to L.
538             Invalid values will be silently ignored. See section "CODESETS" below.
539              
540             =item exclude
541              
542             Array reference with a list of country codes which are removed from the
543             list.
544              
545             Valid values are country codes known to L.
546             Invalid values will be silently ignored. See section "CODESETS" below.
547              
548             =item html_attr
549              
550             Hash reference whose pairs will be used as HTML attributes within the opening
551             "select" tag. No validation is performed with regard to any HTML doctype!
552              
553             The attribute "name" will always be set for the "select" element. By default it's
554             value will be "country".
555              
556             The attribute "id" will also be set for the "select" element by default. The
557             default value is the currently configured value for the "name" attribute.
558             Unlike with the "name" attribute you can remove the "id" attribute by setting
559             the configured value to "undef".
560              
561             =item names
562              
563             Hash reference with pairs of country codes and associated names.
564             The names specified here will be used instead of the "official" names as provided by
565             L. The replacement happens
566             without taking the currently used language into account!
567              
568             The hash keys have to be country codes from the appropriate codeset!
569             See section "CODESETS" below.
570              
571             On the other hand you can use this to inject extra "countries" but it's up to you to
572             provide unique codes and translate the names appropriately to the currently used
573             language.
574              
575             =item codeset
576              
577             Codeset id as used by L.
578             Either C, C or C.
579             By default, 2-letter country codes (C) are used.
580              
581             =back
582              
583             =head1 LANGUAGE FALLBACKS
584              
585             This module will use a fallback hierarchy to determine which language it has to use
586             for the country names.
587              
588             =over 4
589              
590             =item *
591              
592             Highest precedence lies with a parameter to the current function call.
593              
594             =item *
595              
596             Next the value from the configuration is used, if set.
597              
598             =item *
599              
600             If in use in the application the Mojolicious plugin C is used next and asked for
601             the language it chose.
602              
603             =item *
604              
605             Finally, if none of the previous methods provided us with a value (or if none of them
606             represented a language that is supported by
607             L) the last fallback will
608             be the english language ("en").
609              
610             =back
611              
612             Especially if you're using the C plugin it's ususally your best option to not
613             explicitly set a language.
614              
615             =head1 CODESETS
616              
617             Any of the three codesets used by L
618             (i.e. C, C and C) are accepted - but you have to make sure that you
619             consistenly use the same codeset across function calls and config values.
620              
621             =head1 METHODS/HELPERS
622              
623             =head2 country_select_field ( [ $hashref ] )
624              
625             Helper method that creates the html fragment with the select field.
626              
627             Optionally takes a hash reference with configuration values.
628             See section "CONFIGURATION" above for values which are currently allowed / recognized.
629             These values override the current configuration but are used only for the current method
630             call.
631              
632             =head2 code2country ( $code [, $language ] )
633              
634             Returns the name for the given country code.
635             The code can be from any of the three supported codesets.
636              
637             my $code = 'DE'; # ALPHA_2 code
638             my $name = $app->code2country($code); # returns "Germany" unless a
639             # different language was set
640              
641             From which language the name is taken is determined by following the fallback
642             hierarchy described above.
643              
644             Optionally you may specifiy the language to use as second param.
645              
646             my $lang = 'fr';
647             my $name = $app->code2country( $code, $lang ); # returns "Allemange"
648              
649             The value of the language parameter is used only for the current method call.
650              
651             =head2 country2code ( $name [, $language [, $codeset ] ] )
652              
653             Returns the code for the given country name:
654              
655             my $code = $self->country2code( 'Allemange', 'fr' ); # returns "DE"
656              
657             my $code = $self->country2code( 'Deutschland', 'de', 'NUMERIC' ); # returns "276"
658              
659             Make sure that the name is given in the currently configured default language
660             or specifiy the language as second param!
661              
662             If you want to get a code from a different codeset than the default ALPHA_2 you
663             can specifiy the codeset as third param.
664             If you want to specifiy a codeset but no language you need to pass an undefined
665             value as second param.
666              
667             The values given for the language and codeset parameters are used only for the
668             current method call.
669              
670             =head2 csf_country_list ( [ $hashref ] )
671              
672             Returns a hash ref indexed by the codes with the country names as values.
673              
674             Optionally takes a hash reference with configuration values.
675             See section "CONFIGURATION" above for values which are currently allowed / recognized.
676             These values override the current configuration but are used only for the current method
677             call.
678              
679             Please remember that hashes are unsorted. Therefore, the configuration entries for
680             preferred countries are also ignored.
681              
682             =head2 csf_conf ( [ $hashref ] )
683              
684             Returns a hash ref with the current configuration if no param is given.
685             Otherwise resets or updates configuration values, depending on the contents of the hash ref.
686             See section "CONFIGURATION" above for the hash keys which are currently allowed / recognized.
687              
688             $c->csf_conf( {
689             language => 'en',
690             select => 'DE',
691             prefer => [ 'DE', 'AT', 'CH', ],
692             attr => { class => 'shinycss', id => 'myId', }
693             })
694              
695             The actions taken depend on the content of the hash:
696              
697             =over 4
698              
699             =item *
700             If there's no key for a configuration entry the currently configured value is retained.
701              
702             =item *
703             If there is a key for a configuration entry but the value is undef the respective
704             configuration entry is removed (or reset to it's default value).
705              
706             =item *
707             If there is a key for a configuration entry and it carries a non-undef value, then that
708             value replaces the configured value.
709              
710             There is no way to "modify" a configuration entry (e.g. append additional html attributes)!
711              
712             =item *
713             If the hash is empty (i.e. if you call C<$c-Ecsf_conf({})>), the whole configuration is
714             reset.
715              
716             =back
717              
718             =head1 AUTHORS
719              
720             =over 4
721              
722             =item *
723              
724             Renee Baecker
725              
726             =item *
727              
728             Heiko Jansen
729              
730             =item *
731              
732             Skye Shaw
733              
734             =back
735              
736             =head1 COPYRIGHT AND LICENSE
737              
738             This software is Copyright (c) 2012 by Hochschulbibliothekszentrum NRW (hbz).
739              
740             This is free software, licensed under:
741              
742             The GNU General Public License, Version 3, June 2007
743              
744             =cut
745              
746              
747             __END__