root/branches/release-37/lib/MT/ConfigMgr.pm @ 2206

Revision 2206, 11.1 kB (checked in by bchoate, 19 months ago)

Support for PerformanceLoggingPath setting, with error logging if path is unwritable. Thanks, Jay! BugId:79631

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    $config->save or die $config->errstr;
243    $mgr->clear_dirty;
244    1;
245}
246
247sub read_config_file {
248    my $class = shift;
249    my($cfg_file) = @_;
250    my $mgr = $class->instance;
251    $mgr->{__var} = {};
252    local(*FH, $_, $/);
253    $/ = "\n";
254    die "Can't read config without config file name" if !$cfg_file;
255    open FH, $cfg_file or
256        return $class->error(MT->translate(
257            "Error opening file '[_1]': [_2]", $cfg_file, "$!" ));
258    my $line;
259    while (<FH>) {
260        chomp; $line++;
261        next if !/\S/ || /^#/;
262        my($var, $val) = $_ =~ /^\s*(\S+)\s+(.*)$/;
263        return $class->error(MT->translate("Config directive [_1] without value at [_2] line [_3]", $var, $cfg_file, $line))
264            unless defined($val) && $val ne '';
265        $val =~ s/\s*$// if defined($val);
266        next unless $var && defined($val);
267        #return $class->error(MT->translate(
268        #    "[_1]:[_2]: variable '[_3]' not defined", $cfg_file, $., $var
269        #    )) unless exists $mgr->{__settings}->{$var};
270        # next unless exists $mgr->{__settings}->{$var};
271        $mgr->set($var, $val);
272    }
273    close FH;
274    1;
275}
276
277sub read_config_db {
278    my $class = shift;
279    my $mgr = $class->instance;
280    require MT::Config;
281    my ($config) = eval { MT::Config->search };
282    if ($config) {
283        my $data = $config->data;
284        my @data = split /[\r?\n]/, $data;
285        my $line = 0;
286        foreach (@data) {
287            $line++;
288            chomp;
289            next if !/\S/ || /^#/;
290            my($var, $val) = $_ =~ /^\s*(\S+)\s+(.+)$/;
291            $val =~ s/\s*$// if defined($val);
292            next unless $var && defined($val);
293            #return $class->error(MT->translate(
294            #    "[_1]:[_2]: variable '[_3]' not defined", "database", $line, $var
295            #)) unless exists $mgr->{__settings}->{$var};
296
297            # ignore setting if it isn't defined...
298            # next unless exists $mgr->{__settings}->{$var};
299            $mgr->set($var, $val, 1);
300        }
301    }
302    1;
303}
304
305sub DESTROY { }
306
307use vars qw( $AUTOLOAD );
308sub AUTOLOAD {
309    my $mgr = $_[0];
310    (my $var = $AUTOLOAD) =~ s!.+::!!;
311    $var = lc $var;
312    return $mgr->error(MT->translate("No such config variable '[_1]'", $var))
313        unless exists $mgr->{__settings}->{$var};
314    no strict 'refs';
315    *$AUTOLOAD = sub {
316        my $mgr = shift;
317        @_ ? $mgr->set($var, @_) : $mgr->get($var);
318    };
319    goto &$AUTOLOAD;
320}
321
3221;
323__END__
324
325=head1 NAME
326
327MT::ConfigMgr - Movable Type configuration manager
328
329=head1 SYNOPSIS
330
331    use MT::ConfigMgr;
332    my $cfg = MT::ConfigMgr->instance;
333
334    $cfg->read_config('/path/to/mt.cfg')
335        or die $cfg->errstr;
336
337=head1 DESCRIPTION
338
339I<MT::ConfigMgr> is a singleton class that manages the Movable Type
340configuration file (F<mt.cfg>), allowing access to the config directives
341contained therin.
342
343=head1 USAGE
344
345=head2 MT::ConfigMgr->instance
346
347Returns the singleton I<MT::ConfigMgr> object. Note that when you want the
348object, you should always call I<instance>, never I<new>; I<new> will construct
349a B<new> I<MT::ConfigMgr> object, and that isn't what you want. You want the
350object that has already been initialized with the contents of F<mt.cfg>. This
351initialization is done by I<MT::new>.
352
353=head2 $cfg->read_config($file)
354
355Reads the config file at the path I<$file> and initializes the I<$cfg> object
356with the directives in that file. Returns true on success, C<undef> otherwise;
357if an error occurs you can obtain the error message with C<$cfg-E<gt>errstr>.
358
359=head2 $cfg->define($directive [, %arg ])
360
361Defines the directive I<$directive> as a valid configuration directive; you
362must define new configuration directives B<before> you read the configuration
363file, or else the read will fail.
364
365=head2 $cfg->ExternalUserManagement()
366
367Returns boolean value indicating whether the configuration is set so that
368external users management feature in Movable Type Enterprise is turned on.
369
370=head1 CONFIGURATION DIRECTIVES
371
372The following configuration directives are allowed in F<mt.cfg>. To get the
373value of a directive, treat it as a method that you are calling on the
374I<$cfg> object. For example:
375
376    $cfg->CGIPath
377
378To set the value of a directive, do the same as the above, but pass in a value
379to the method:
380
381    $cfg->CGIPath('http://www.foo.com/mt/');
382
383A list of valid configuration directives can be found in the
384I<CONFIGURATION SETTINGS> section of the manual.
385
386=head1 AUTHOR & COPYRIGHT
387
388Please see the I<MT> manpage for author, copyright, and license information.
389
390=cut
Note: See TracBrowser for help on using the browser.