root/branches/release-39/lib/MT/ConfigMgr.pm @ 2510

Revision 2510, 11.2 kB (checked in by bchoate, 18 months ago)

Don't die on failure to save config settings. BugId:80034

Line 
1# Movable Type (r) Open Source (C) 2001-2008 Six Apart, Ltd.
2# This program is distributed under the terms of the
3# GNU General Public License, version 2.
4#
5# $Id$
6
7package MT::ConfigMgr;
8
9use strict;
10use base qw( MT::ErrorHandler );
11
12use vars qw( $cfg );
13sub instance {
14    return $cfg if $cfg;
15    $cfg = __PACKAGE__->new;
16}
17
18sub new {
19    my $mgr = bless { __var => { }, __dbvar => { }, __paths => [], __dirty => 0 }, $_[0];
20    $mgr->init;
21    $mgr;
22}
23
24sub init {
25}
26
27sub define {
28    my $mgr = shift;
29    my($vars);
30    if (ref $_[0] eq 'ARRAY') {
31        $vars = shift;
32    } elsif (ref $_[0] eq 'HASH') {
33        $vars = shift;
34    } else {
35        my($var, %param) = @_;
36        $vars = [ [ $var, \%param ] ];
37    }
38    if (ref($vars) eq 'ARRAY') {
39        foreach my $def (@$vars) {
40            my($var, $param) = @$def;
41            my $lcvar = lc $var;
42            $mgr->{__var}{$lcvar} = undef;
43            $mgr->{__settings}{$lcvar} = keys %$param ? { %$param } : {};
44            $mgr->{__settings}{$lcvar}{key} = $var;
45            if ($mgr->{__settings}{$lcvar}{path}) {
46                push @{$mgr->{__paths}}, $var;
47            }
48        }
49    } elsif (ref($vars) eq 'HASH') {
50        foreach my $var (keys %$vars) {
51            my $param = $vars->{$var};
52            my $lcvar = lc $var;
53            $mgr->{__settings}{$lcvar} = $param;
54            if (ref $param eq 'ARRAY') {
55                $mgr->{__settings}{$lcvar} = $param->[0];
56            }
57            $mgr->{__settings}{$lcvar}{key} = $var;
58            if ($mgr->{__settings}{$lcvar}{path}) {
59                push @{$mgr->{__paths}}, $var;
60            }
61        }
62    }
63}
64
65sub paths {
66    my $mgr = shift;
67    wantarray ? @{$mgr->{__paths}} : $mgr->{__paths};
68}
69
70our $depth = 0;
71my $max_depth = 5;
72sub get_internal {
73    my $mgr = shift;
74    my $var = lc shift;
75    my $val;
76    if (defined(my $alias = $mgr->{__settings}{$var}{alias})) {
77        if ($max_depth < $depth) {
78            die MT->translate('Alias for [_1] is looping in the configuration.', $alias);
79        }
80        local $depth = $depth + 1;
81        $mgr->get($alias);
82    } elsif (defined($val = $mgr->{__var}{$var})) {
83        $val = $val->() if ref($val) eq 'CODE';
84        wantarray && ($mgr->{__settings}{$var}{type}||'') eq 'ARRAY' ?
85            @$val : ((ref $val) eq 'ARRAY' && @$val ? $val->[0] : $val);
86    } elsif (defined($val = $mgr->{__dbvar}{$var})) {
87        wantarray && ($mgr->{__settings}{$var}{type}||'') eq 'ARRAY' ?
88            @$val : ((ref $val) eq 'ARRAY' && @$val ? $val->[0] : $val);
89    } else {
90        $mgr->default($var);
91    }
92}
93
94sub get {
95    my $mgr = shift;
96    my $var = lc shift;
97    if (my $h = $mgr->{__settings}{$var}{handler}) {
98        $h = MT->handler_to_coderef($h) unless ref $h;
99        return $h->($mgr);
100    }
101    return $mgr->get_internal($var, @_);
102}
103
104sub type {
105    my $mgr = shift;
106    my $var = lc shift;
107    $mgr->{__settings}{$var}{type} || 'SCALAR';
108}
109
110sub default {
111    my $mgr = shift;
112    my $var = lc shift;
113    my $def = $mgr->{__settings}{$var}{default};
114    return wantarray ? () : undef unless defined $def;
115    if (my $type = $mgr->{__settings}{$var}{type}) {
116        if ($type eq 'ARRAY') {
117            return wantarray ? ( $def ) : $def;
118        } elsif ($type eq 'HASH') {
119            if (ref $def ne 'HASH') {
120                (my($key), my($val)) = split /=/, $def;
121                return { $key => $val };
122            }
123        }
124    }
125    $def;
126}
127
128sub set_internal {
129    my $mgr = shift;
130    my($var, $val, $db_flag) = @_;
131    $var = lc $var;
132    $db_flag ||= exists $mgr->{__dbvar}{$var};
133    $mgr->set_dirty() if defined($_[2]) && $_[2];
134    my $set = $db_flag ? '__dbvar' : '__var';
135    if (defined(my $alias = $mgr->{__settings}{$var}{alias})) {
136        if ($max_depth < $depth) {
137            die MT->translate('Alias for [_1] is looping in the configuration.', $alias);
138        }
139        local $depth = $depth + 1;
140        $mgr->set($alias, $val, $db_flag);
141    } elsif (my $type = $mgr->{__settings}{$var}{type}) {
142        if ($type eq 'ARRAY') {
143            if (ref $val eq 'ARRAY') {
144                $mgr->{$set}{$var} = $val;
145            } else {
146                $mgr->{$set}{$var} ||= [];
147                push @{ $mgr->{$set}{$var} }, $val if defined $val;
148            }
149        } elsif ($type eq 'HASH') {
150            my $hash = $mgr->{$set}{$var};
151            $hash = $mgr->default($var) unless defined $hash;
152            if (ref $val eq 'HASH') {
153                $mgr->{$set}{$var} = $val;
154            } else {
155                $hash ||= {};
156                (my($key), $val) = split /=/, $val;
157                $mgr->{$set}{$var}{$key} = $val;
158            }
159        } else {
160            $mgr->{$set}{$var} = $val;
161        }
162    } else {
163        $mgr->{$set}{$var} = $val;
164    }
165    return $val;
166}
167
168sub set {
169    my $mgr = shift;
170    my($var, $val, $db_flag) = @_;
171    $var = lc $var;
172    if (my $h = $mgr->{__settings}{$var}{handler}) {
173        $h = MT->handler_to_coderef($h) unless ref $h;
174        return $h->($mgr, $val, $db_flag);
175    }
176    return $mgr->set_internal(@_);
177}
178
179sub is_readonly {
180    my $class = shift;
181    my ($var) = @_;
182    defined $class->instance->{__var}{lc $var} ? 1 : 0;
183}
184
185sub read_config {
186    my $class = shift;
187    $class->read_config_file(@_);
188}
189
190sub set_dirty {
191    my $mgr = shift;
192    $mgr = $mgr->instance unless ref($mgr);
193    $mgr->{__dirty} = 1;
194}
195
196sub clear_dirty {
197    my $mgr = shift;
198    $mgr = $mgr->instance unless ref($mgr);
199    $mgr->{__dirty} = 0;
200}
201
202sub is_dirty {
203    my $mgr = shift;
204    $mgr = $mgr->instance unless ref($mgr);
205    $mgr->{__dirty};
206}
207
208sub save_config {
209    my $class = shift;
210    my $mgr = $class->instance;
211    return 0 unless $mgr->is_dirty();
212    my $data = '';
213    my $settings = $mgr->{__dbvar};
214    foreach (sort keys %$settings) {
215        my $type = ($mgr->{__settings}{$_}{type}||'');
216        if ($type eq 'HASH') {
217            my $h = $settings->{$_};
218            foreach my $k (keys %$h) {
219                $data .= $mgr->{__settings}{$_}{key} . ' ' . $k . '=' . $h->{$k} . "\n";
220            }
221        } elsif ($type eq 'ARRAY') {
222            my $a = $settings->{$_};
223            foreach my $v (@$a) {
224                $data .= $mgr->{__settings}{$_}{key} . ' ' . $v . "\n";
225            }
226        } else {
227            $data .= $mgr->{__settings}{$_}{key} . ' ' . $settings->{$_} . "\n";
228        }
229    }
230    require MT::Config;
231    my ($config) = MT::Config->load() || new MT::Config;
232
233    if ($data !~ m/schemaversion/i) {
234        if ($config->id && (($config->data || '') =~ m/schemaversion/i)) {
235            require Carp;
236            MT->log(Carp::longmess("Caught attempt to clear SchemaVersion setting. New config settings were:\n$data"));
237            return;
238        }
239    }
240
241    $config->data($data);
242    # Ignore any error returned for the sake of MT-Wizard,
243    # where the mt_config table doesn't actually exist yet.
244    $config->save;
245    $mgr->clear_dirty;
246    1;
247}
248
249sub read_config_file {
250    my $class = shift;
251    my($cfg_file) = @_;
252    my $mgr = $class->instance;
253    $mgr->{__var} = {};
254    local(*FH, $_, $/);
255    $/ = "\n";
256    die "Can't read config without config file name" if !$cfg_file;
257    open FH, $cfg_file or
258        return $class->error(MT->translate(
259            "Error opening file '[_1]': [_2]", $cfg_file, "$!" ));
260    my $line;
261    while (<FH>) {
262        chomp; $line++;
263        next if !/\S/ || /^#/;
264        my($var, $val) = $_ =~ /^\s*(\S+)\s+(.*)$/;
265        return $class->error(MT->translate("Config directive [_1] without value at [_2] line [_3]", $var, $cfg_file, $line))
266            unless defined($val) && $val ne '';
267        $val =~ s/\s*$// if defined($val);
268        next unless $var && defined($val);
269        #return $class->error(MT->translate(
270        #    "[_1]:[_2]: variable '[_3]' not defined", $cfg_file, $., $var
271        #    )) unless exists $mgr->{__settings}->{$var};
272        # next unless exists $mgr->{__settings}->{$var};
273        $mgr->set($var, $val);
274    }
275    close FH;
276    1;
277}
278
279sub read_config_db {
280    my $class = shift;
281    my $mgr = $class->instance;
282    require MT::Config;
283    my ($config) = eval { MT::Config->search };
284    if ($config) {
285        my $data = $config->data;
286        my @data = split /[\r?\n]/, $data;
287        my $line = 0;
288        foreach (@data) {
289            $line++;
290            chomp;
291            next if !/\S/ || /^#/;
292            my($var, $val) = $_ =~ /^\s*(\S+)\s+(.+)$/;
293            $val =~ s/\s*$// if defined($val);
294            next unless $var && defined($val);
295            #return $class->error(MT->translate(
296            #    "[_1]:[_2]: variable '[_3]' not defined", "database", $line, $var
297            #)) unless exists $mgr->{__settings}->{$var};
298
299            # ignore setting if it isn't defined...
300            # next unless exists $mgr->{__settings}->{$var};
301            $mgr->set($var, $val, 1);
302        }
303    }
304    1;
305}
306
307sub DESTROY { }
308
309use vars qw( $AUTOLOAD );
310sub AUTOLOAD {
311    my $mgr = $_[0];
312    (my $var = $AUTOLOAD) =~ s!.+::!!;
313    $var = lc $var;
314    return $mgr->error(MT->translate("No such config variable '[_1]'", $var))
315        unless exists $mgr->{__settings}->{$var};
316    no strict 'refs';
317    *$AUTOLOAD = sub {
318        my $mgr = shift;
319        @_ ? $mgr->set($var, @_) : $mgr->get($var);
320    };
321    goto &$AUTOLOAD;
322}
323
3241;
325__END__
326
327=head1 NAME
328
329MT::ConfigMgr - Movable Type configuration manager
330
331=head1 SYNOPSIS
332
333    use MT::ConfigMgr;
334    my $cfg = MT::ConfigMgr->instance;
335
336    $cfg->read_config('/path/to/mt.cfg')
337        or die $cfg->errstr;
338
339=head1 DESCRIPTION
340
341I<MT::ConfigMgr> is a singleton class that manages the Movable Type
342configuration file (F<mt.cfg>), allowing access to the config directives
343contained therin.
344
345=head1 USAGE
346
347=head2 MT::ConfigMgr->instance
348
349Returns the singleton I<MT::ConfigMgr> object. Note that when you want the
350object, you should always call I<instance>, never I<new>; I<new> will construct
351a B<new> I<MT::ConfigMgr> object, and that isn't what you want. You want the
352object that has already been initialized with the contents of F<mt.cfg>. This
353initialization is done by I<MT::new>.
354
355=head2 $cfg->read_config($file)
356
357Reads the config file at the path I<$file> and initializes the I<$cfg> object
358with the directives in that file. Returns true on success, C<undef> otherwise;
359if an error occurs you can obtain the error message with C<$cfg-E<gt>errstr>.
360
361=head2 $cfg->define($directive [, %arg ])
362
363Defines the directive I<$directive> as a valid configuration directive; you
364must define new configuration directives B<before> you read the configuration
365file, or else the read will fail.
366
367=head2 $cfg->ExternalUserManagement()
368
369Returns boolean value indicating whether the configuration is set so that
370external users management feature in Movable Type Enterprise is turned on.
371
372=head1 CONFIGURATION DIRECTIVES
373
374The following configuration directives are allowed in F<mt.cfg>. To get the
375value of a directive, treat it as a method that you are calling on the
376I<$cfg> object. For example:
377
378    $cfg->CGIPath
379
380To set the value of a directive, do the same as the above, but pass in a value
381to the method:
382
383    $cfg->CGIPath('http://www.foo.com/mt/');
384
385A list of valid configuration directives can be found in the
386I<CONFIGURATION SETTINGS> section of the manual.
387
388=head1 AUTHOR & COPYRIGHT
389
390Please see the I<MT> manpage for author, copyright, and license information.
391
392=cut
Note: See TracBrowser for help on using the browser.