| 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 | |
|---|
| 7 | package MT::ConfigMgr; |
|---|
| 8 | |
|---|
| 9 | use strict; |
|---|
| 10 | use base qw( MT::ErrorHandler ); |
|---|
| 11 | |
|---|
| 12 | use vars qw( $cfg ); |
|---|
| 13 | sub instance { |
|---|
| 14 | return $cfg if $cfg; |
|---|
| 15 | $cfg = __PACKAGE__->new; |
|---|
| 16 | } |
|---|
| 17 | |
|---|
| 18 | sub new { |
|---|
| 19 | my $mgr = bless { __var => { }, __dbvar => { }, __paths => [], __dirty => 0 }, $_[0]; |
|---|
| 20 | $mgr->init; |
|---|
| 21 | $mgr; |
|---|
| 22 | } |
|---|
| 23 | |
|---|
| 24 | sub init { |
|---|
| 25 | } |
|---|
| 26 | |
|---|
| 27 | sub 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 | |
|---|
| 65 | sub paths { |
|---|
| 66 | my $mgr = shift; |
|---|
| 67 | wantarray ? @{$mgr->{__paths}} : $mgr->{__paths}; |
|---|
| 68 | } |
|---|
| 69 | |
|---|
| 70 | our $depth = 0; |
|---|
| 71 | my $max_depth = 5; |
|---|
| 72 | sub 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 | |
|---|
| 94 | sub 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 | |
|---|
| 104 | sub type { |
|---|
| 105 | my $mgr = shift; |
|---|
| 106 | my $var = lc shift; |
|---|
| 107 | $mgr->{__settings}{$var}{type} || 'SCALAR'; |
|---|
| 108 | } |
|---|
| 109 | |
|---|
| 110 | sub 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 | |
|---|
| 128 | sub 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 | |
|---|
| 168 | sub 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 | |
|---|
| 179 | sub is_readonly { |
|---|
| 180 | my $class = shift; |
|---|
| 181 | my ($var) = @_; |
|---|
| 182 | defined $class->instance->{__var}{lc $var} ? 1 : 0; |
|---|
| 183 | } |
|---|
| 184 | |
|---|
| 185 | sub read_config { |
|---|
| 186 | my $class = shift; |
|---|
| 187 | $class->read_config_file(@_); |
|---|
| 188 | } |
|---|
| 189 | |
|---|
| 190 | sub set_dirty { |
|---|
| 191 | my $mgr = shift; |
|---|
| 192 | $mgr = $mgr->instance unless ref($mgr); |
|---|
| 193 | $mgr->{__dirty} = 1; |
|---|
| 194 | } |
|---|
| 195 | |
|---|
| 196 | sub clear_dirty { |
|---|
| 197 | my $mgr = shift; |
|---|
| 198 | $mgr = $mgr->instance unless ref($mgr); |
|---|
| 199 | $mgr->{__dirty} = 0; |
|---|
| 200 | } |
|---|
| 201 | |
|---|
| 202 | sub is_dirty { |
|---|
| 203 | my $mgr = shift; |
|---|
| 204 | $mgr = $mgr->instance unless ref($mgr); |
|---|
| 205 | $mgr->{__dirty}; |
|---|
| 206 | } |
|---|
| 207 | |
|---|
| 208 | sub 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 | |
|---|
| 247 | sub 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 | |
|---|
| 277 | sub 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 | |
|---|
| 305 | sub DESTROY { } |
|---|
| 306 | |
|---|
| 307 | use vars qw( $AUTOLOAD ); |
|---|
| 308 | sub 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 | |
|---|
| 322 | 1; |
|---|
| 323 | __END__ |
|---|
| 324 | |
|---|
| 325 | =head1 NAME |
|---|
| 326 | |
|---|
| 327 | MT::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 | |
|---|
| 339 | I<MT::ConfigMgr> is a singleton class that manages the Movable Type |
|---|
| 340 | configuration file (F<mt.cfg>), allowing access to the config directives |
|---|
| 341 | contained therin. |
|---|
| 342 | |
|---|
| 343 | =head1 USAGE |
|---|
| 344 | |
|---|
| 345 | =head2 MT::ConfigMgr->instance |
|---|
| 346 | |
|---|
| 347 | Returns the singleton I<MT::ConfigMgr> object. Note that when you want the |
|---|
| 348 | object, you should always call I<instance>, never I<new>; I<new> will construct |
|---|
| 349 | a B<new> I<MT::ConfigMgr> object, and that isn't what you want. You want the |
|---|
| 350 | object that has already been initialized with the contents of F<mt.cfg>. This |
|---|
| 351 | initialization is done by I<MT::new>. |
|---|
| 352 | |
|---|
| 353 | =head2 $cfg->read_config($file) |
|---|
| 354 | |
|---|
| 355 | Reads the config file at the path I<$file> and initializes the I<$cfg> object |
|---|
| 356 | with the directives in that file. Returns true on success, C<undef> otherwise; |
|---|
| 357 | if an error occurs you can obtain the error message with C<$cfg-E<gt>errstr>. |
|---|
| 358 | |
|---|
| 359 | =head2 $cfg->define($directive [, %arg ]) |
|---|
| 360 | |
|---|
| 361 | Defines the directive I<$directive> as a valid configuration directive; you |
|---|
| 362 | must define new configuration directives B<before> you read the configuration |
|---|
| 363 | file, or else the read will fail. |
|---|
| 364 | |
|---|
| 365 | =head2 $cfg->ExternalUserManagement() |
|---|
| 366 | |
|---|
| 367 | Returns boolean value indicating whether the configuration is set so that |
|---|
| 368 | external users management feature in Movable Type Enterprise is turned on. |
|---|
| 369 | |
|---|
| 370 | =head1 CONFIGURATION DIRECTIVES |
|---|
| 371 | |
|---|
| 372 | The following configuration directives are allowed in F<mt.cfg>. To get the |
|---|
| 373 | value of a directive, treat it as a method that you are calling on the |
|---|
| 374 | I<$cfg> object. For example: |
|---|
| 375 | |
|---|
| 376 | $cfg->CGIPath |
|---|
| 377 | |
|---|
| 378 | To set the value of a directive, do the same as the above, but pass in a value |
|---|
| 379 | to the method: |
|---|
| 380 | |
|---|
| 381 | $cfg->CGIPath('http://www.foo.com/mt/'); |
|---|
| 382 | |
|---|
| 383 | A list of valid configuration directives can be found in the |
|---|
| 384 | I<CONFIGURATION SETTINGS> section of the manual. |
|---|
| 385 | |
|---|
| 386 | =head1 AUTHOR & COPYRIGHT |
|---|
| 387 | |
|---|
| 388 | Please see the I<MT> manpage for author, copyright, and license information. |
|---|
| 389 | |
|---|
| 390 | =cut |
|---|