| 1 | package MENTA; |
|---|
| 2 | use strict; |
|---|
| 3 | use warnings; |
|---|
| 4 | use utf8; |
|---|
| 5 | use CGI::ExceptionManager; |
|---|
| 6 | use MENTA::Dispatch (); |
|---|
| 7 | |
|---|
| 8 | require Class::Accessor::Lite; |
|---|
| 9 | require MENTA::Context; |
|---|
| 10 | require Text::MicroTemplate; |
|---|
| 11 | |
|---|
| 12 | our $VERSION = '0.13'; |
|---|
| 13 | our $REQ; |
|---|
| 14 | our $CONFIG; |
|---|
| 15 | our $STASH; |
|---|
| 16 | |
|---|
| 17 | sub import { |
|---|
| 18 | strict->import; |
|---|
| 19 | warnings->import; |
|---|
| 20 | utf8->import; |
|---|
| 21 | } |
|---|
| 22 | |
|---|
| 23 | { |
|---|
| 24 | our $context; |
|---|
| 25 | sub context { $context } |
|---|
| 26 | sub run_context { |
|---|
| 27 | my ($class, $config, $req, $engine, $code) = @_; |
|---|
| 28 | local $context = MENTA::Context->new( |
|---|
| 29 | config => $config, |
|---|
| 30 | request => $req, |
|---|
| 31 | __engine => $engine, |
|---|
| 32 | ); |
|---|
| 33 | $code->(); |
|---|
| 34 | } |
|---|
| 35 | } |
|---|
| 36 | |
|---|
| 37 | { |
|---|
| 38 | # Class::Trigger はロードに時間かかるので自前で実装してる |
|---|
| 39 | my $static_triggers; |
|---|
| 40 | sub call_trigger { |
|---|
| 41 | my ($class, $triggername, @args) = @_; |
|---|
| 42 | my $c = context(); |
|---|
| 43 | for my $code (@{$c->{triggers}->{$triggername}}, @{ $static_triggers->{triggers}->{$triggername} || [] }) { |
|---|
| 44 | $code->($c, @args); |
|---|
| 45 | } |
|---|
| 46 | } |
|---|
| 47 | |
|---|
| 48 | sub add_trigger { |
|---|
| 49 | my ($class, $triggername, $code) = @_; |
|---|
| 50 | if (ref context()) { |
|---|
| 51 | push @{context()->{triggers}->{$triggername}}, $code; |
|---|
| 52 | } else { |
|---|
| 53 | push @{$static_triggers->{triggers}->{$triggername}}, $code; |
|---|
| 54 | } |
|---|
| 55 | } |
|---|
| 56 | } |
|---|
| 57 | |
|---|
| 58 | # run as cgi |
|---|
| 59 | sub run_menta { |
|---|
| 60 | my ($class, $config) = @_; |
|---|
| 61 | $class->create_engine($config, 'MinimalCGI')->run; |
|---|
| 62 | } |
|---|
| 63 | |
|---|
| 64 | sub create_engine { |
|---|
| 65 | my ($class, $config, $interface) = @_; |
|---|
| 66 | |
|---|
| 67 | my $engine; |
|---|
| 68 | $engine = HTTP::Engine->new( |
|---|
| 69 | interface => { |
|---|
| 70 | module => $interface, |
|---|
| 71 | request_handler => sub { |
|---|
| 72 | my $req = shift; |
|---|
| 73 | local $MENTA::STASH; |
|---|
| 74 | CGI::ExceptionManager->run( |
|---|
| 75 | callback => sub { |
|---|
| 76 | MENTA->run_context( |
|---|
| 77 | $config, $req, $engine, sub { |
|---|
| 78 | MENTA->call_trigger('BEFORE_DISPATCH'); |
|---|
| 79 | MENTA::Dispatch->dispatch() |
|---|
| 80 | } |
|---|
| 81 | ); |
|---|
| 82 | }, |
|---|
| 83 | powered_by => '<strong>MENTA</strong>, Web Application Framework.', |
|---|
| 84 | stacktrace_class => 'HTTPEngine', |
|---|
| 85 | ($config->{menta}->{fatals_to_browser} ? () : (renderer => sub { "INTERNAL SERVER ERROR!" x 100 })) |
|---|
| 86 | ); |
|---|
| 87 | } |
|---|
| 88 | } |
|---|
| 89 | ); |
|---|
| 90 | } |
|---|
| 91 | |
|---|
| 92 | sub config () { MENTA->context->config } |
|---|
| 93 | |
|---|
| 94 | sub escape_html { |
|---|
| 95 | local $_ = shift; |
|---|
| 96 | return $_ unless $_; |
|---|
| 97 | s/&/&/g; |
|---|
| 98 | s/>/>/g; |
|---|
| 99 | s/</</g; |
|---|
| 100 | s/"/"/g; |
|---|
| 101 | s/'/'/g; |
|---|
| 102 | return $_; |
|---|
| 103 | } |
|---|
| 104 | |
|---|
| 105 | sub unescape_html { |
|---|
| 106 | local $_ = shift; |
|---|
| 107 | return $_ unless $_; |
|---|
| 108 | s/>/>/g; |
|---|
| 109 | s/</</g; |
|---|
| 110 | s/"/"/g; |
|---|
| 111 | s/�*39;/'/g; |
|---|
| 112 | s/&/&/g; |
|---|
| 113 | return $_; |
|---|
| 114 | } |
|---|
| 115 | |
|---|
| 116 | sub raw_string { |
|---|
| 117 | my $s = shift; |
|---|
| 118 | ref $s eq 'Text::MicroTemplate::EncodedString' |
|---|
| 119 | ? $s |
|---|
| 120 | : bless \$s, 'Text::MicroTemplate::EncodedString'; |
|---|
| 121 | } |
|---|
| 122 | |
|---|
| 123 | sub mt_cache_dir { |
|---|
| 124 | # $> は $EFFECTIVE_USER_ID です。詳しくは perldoc perlvar を参照。 |
|---|
| 125 | my $cachedir = config->{menta}->{cache_dir}; |
|---|
| 126 | return $cachedir if $cachedir; |
|---|
| 127 | |
|---|
| 128 | require File::Spec;; |
|---|
| 129 | return File::Spec->catfile(File::Spec->tmpdir(), "menta.${MENTA::VERSION}.$>.mt_cache"); |
|---|
| 130 | } |
|---|
| 131 | |
|---|
| 132 | sub base_dir { |
|---|
| 133 | config->{menta}->{__processed_base_dir} ||= do { |
|---|
| 134 | my $basedir = config->{menta}->{base_dir}; |
|---|
| 135 | unless ($basedir) { |
|---|
| 136 | require Cwd; |
|---|
| 137 | $basedir = Cwd::cwd(); |
|---|
| 138 | } |
|---|
| 139 | $basedir =~ s!([^/])$!$1/!; |
|---|
| 140 | $basedir; |
|---|
| 141 | }; |
|---|
| 142 | } |
|---|
| 143 | |
|---|
| 144 | sub controller_dir { |
|---|
| 145 | config->{menta}->{controller_dir} ||= base_dir() . 'app/controller/'; |
|---|
| 146 | config->{menta}->{controller_dir}; |
|---|
| 147 | } |
|---|
| 148 | |
|---|
| 149 | sub data_dir { |
|---|
| 150 | config->{menta}->{data_dir} ||= base_dir() . 'app/data/'; |
|---|
| 151 | config->{menta}->{data_dir}; |
|---|
| 152 | } |
|---|
| 153 | |
|---|
| 154 | sub __render_partial { |
|---|
| 155 | my ($tmpl, $tmpldir, @params) = @_; |
|---|
| 156 | MENTA::TemplateLoader::__load($tmpl, @params); |
|---|
| 157 | } |
|---|
| 158 | |
|---|
| 159 | # テンプレートの一部を描画する |
|---|
| 160 | sub render { |
|---|
| 161 | my ($tmpl, @params) = @_; |
|---|
| 162 | my $out = MENTA::TemplateLoader::__load($tmpl, @params); |
|---|
| 163 | bless \$out, 'Text::MicroTemplate::EncodedString'; |
|---|
| 164 | } |
|---|
| 165 | |
|---|
| 166 | sub _finish { |
|---|
| 167 | my $res = shift; |
|---|
| 168 | MENTA->call_trigger('BEFORE_OUTPUT', $res); |
|---|
| 169 | CGI::ExceptionManager::detach($res); |
|---|
| 170 | } |
|---|
| 171 | |
|---|
| 172 | sub render_and_print { |
|---|
| 173 | my ($tmpl, @params) = @_; |
|---|
| 174 | require MENTA::TemplateLoader; |
|---|
| 175 | my $out = MENTA::TemplateLoader::__load($tmpl, @params); |
|---|
| 176 | $out = MENTA::Util::encode_output($out); |
|---|
| 177 | |
|---|
| 178 | my $res = HTTP::Engine::Response->new( |
|---|
| 179 | body => $out, |
|---|
| 180 | ); |
|---|
| 181 | $res->headers->content_type("text/html; charset=" . MENTA::Util::_charset()); |
|---|
| 182 | _finish($res); |
|---|
| 183 | } |
|---|
| 184 | |
|---|
| 185 | sub redirect { |
|---|
| 186 | my ($location, ) = @_; |
|---|
| 187 | |
|---|
| 188 | my $res = HTTP::Engine::Response->new( |
|---|
| 189 | status => 302, |
|---|
| 190 | ); |
|---|
| 191 | $res->header('Location' => $location); |
|---|
| 192 | _finish($res); |
|---|
| 193 | } |
|---|
| 194 | |
|---|
| 195 | sub finalize { |
|---|
| 196 | my $str = shift; |
|---|
| 197 | my $content_type = shift || ('text/html; charset=' . MENTA::Util::_charset()); |
|---|
| 198 | |
|---|
| 199 | my $res = HTTP::Engine::Response->new( |
|---|
| 200 | status => 200, |
|---|
| 201 | body => $str, |
|---|
| 202 | ); |
|---|
| 203 | $res->headers->content_type($content_type); |
|---|
| 204 | _finish($res); |
|---|
| 205 | } |
|---|
| 206 | |
|---|
| 207 | sub param { MENTA::Util::decode_input(MENTA->context->request->param(@_)) } |
|---|
| 208 | sub upload { MENTA->context->request->upload(@_) } |
|---|
| 209 | sub mobile_agent { MENTA->context->mobile_agent() } |
|---|
| 210 | sub current_url { |
|---|
| 211 | my $req = MENTA->context->request; |
|---|
| 212 | my $protocol = 'http'; |
|---|
| 213 | my $port = $ENV{SERVER_PORT} || 80; |
|---|
| 214 | my $url = "http://" . $req->header('Host'); |
|---|
| 215 | $url .= docroot(); |
|---|
| 216 | $url .= "$ENV{PATH_INFO}"; |
|---|
| 217 | $url .= '?' . $ENV{QUERY_STRING}; |
|---|
| 218 | } |
|---|
| 219 | |
|---|
| 220 | { |
|---|
| 221 | # プラグインの自動ロード機構 |
|---|
| 222 | sub AUTOLOAD { |
|---|
| 223 | my $method = our $AUTOLOAD; |
|---|
| 224 | $method =~ s/.*:://o; |
|---|
| 225 | (my $prefix = $method) =~ s/_.+//; |
|---|
| 226 | die "変な関数よびだしてませんか?: $method" unless $prefix; |
|---|
| 227 | MENTA::Util::load_plugin($prefix); |
|---|
| 228 | my $code = MENTA->can($method); |
|---|
| 229 | die "${method} という関数が見つかりません" unless $code; |
|---|
| 230 | return $code->(@_); |
|---|
| 231 | } |
|---|
| 232 | } |
|---|
| 233 | |
|---|
| 234 | sub is_post_request () { |
|---|
| 235 | my $method = $ENV{REQUEST_METHOD}; |
|---|
| 236 | return $method eq 'POST'; |
|---|
| 237 | } |
|---|
| 238 | |
|---|
| 239 | sub docroot () { $ENV{SCRIPT_NAME} || '' } |
|---|
| 240 | |
|---|
| 241 | sub uri_for { |
|---|
| 242 | my ($path, $query) = @_; |
|---|
| 243 | my @q; |
|---|
| 244 | while (my ($key, $val) = each %$query) { |
|---|
| 245 | $val = join '', map { /^[a-zA-Z0-9_.!~*'()-]$/ ? $_ : '%' . uc(unpack('H2', $_)) } split //, $val; |
|---|
| 246 | push @q, "${key}=${val}"; |
|---|
| 247 | } |
|---|
| 248 | docroot . '/' . $path . (scalar @q ? '?' . join('&', @q) : ''); |
|---|
| 249 | } |
|---|
| 250 | |
|---|
| 251 | sub static_file_path { |
|---|
| 252 | my $path = shift; |
|---|
| 253 | docroot . '/static/' . $path; |
|---|
| 254 | } |
|---|
| 255 | |
|---|
| 256 | { |
|---|
| 257 | package MENTA::Util; |
|---|
| 258 | # ユーティリティメソッドたち。 |
|---|
| 259 | # これらのメソッドは一般ユーザーはよぶべきではない。 |
|---|
| 260 | |
|---|
| 261 | # HTTP::MobileAgent::Plugin::Charset よりポート。 |
|---|
| 262 | # cp932 の方が実績があるので優先させる方針。 |
|---|
| 263 | # Shift_JIS とかじゃなくて cp932 にしとかないと、諸問題にひっかかりがちなので注意 |
|---|
| 264 | sub _encoding { |
|---|
| 265 | MENTA->context->{encoding} ||= sub { |
|---|
| 266 | return 'utf-8' if !MENTA->context->config->{menta}->{support_mobile}; # mobileじゃないときはutf-8 |
|---|
| 267 | |
|---|
| 268 | my $ma = MENTA->context->mobile_agent(); |
|---|
| 269 | return 'utf-8' if $ma->is_non_mobile; |
|---|
| 270 | return 'utf-8' if $ma->is_docomo && $ma->xhtml_compliant; # docomo の 3G 端末では UTF-8 の表示が保障されている |
|---|
| 271 | return 'utf-8' if $ma->is_softbank && $ma->is_type_3gc; # SoftBank 3G の一部端末は CP932 だと絵文字を送ってこない不具合がある |
|---|
| 272 | return 'cp932'; # au は HTTPS のときに UTF-8 だと文字化ける場合がある |
|---|
| 273 | }->(); |
|---|
| 274 | } |
|---|
| 275 | |
|---|
| 276 | # HTTP の入り口んとこで decode させる用 |
|---|
| 277 | sub decode_input { |
|---|
| 278 | my ($txt, $fb) = @_; |
|---|
| 279 | |
|---|
| 280 | if (_encoding() eq 'utf-8') { |
|---|
| 281 | utf8::decode($txt); |
|---|
| 282 | $txt; |
|---|
| 283 | } else { |
|---|
| 284 | require Encode; |
|---|
| 285 | Encode::decode(_encoding(), $txt, $fb); |
|---|
| 286 | } |
|---|
| 287 | } |
|---|
| 288 | |
|---|
| 289 | # 出力直前んとこで encode させる用 |
|---|
| 290 | sub encode_output { |
|---|
| 291 | my ($txt, $fb) = @_; |
|---|
| 292 | |
|---|
| 293 | if (_encoding() eq 'utf-8'){ |
|---|
| 294 | utf8::encode($txt); |
|---|
| 295 | $txt; |
|---|
| 296 | } else { |
|---|
| 297 | require Encode; |
|---|
| 298 | Encode::encode(_encoding(), $txt, $fb); |
|---|
| 299 | } |
|---|
| 300 | } |
|---|
| 301 | |
|---|
| 302 | # charset に設定する文字列を生成 |
|---|
| 303 | sub _charset { |
|---|
| 304 | if(_encoding() eq 'utf-8'){ |
|---|
| 305 | return 'UTF-8'; |
|---|
| 306 | } else { |
|---|
| 307 | return 'Shift_JIS'; # cp932 |
|---|
| 308 | } |
|---|
| 309 | } |
|---|
| 310 | |
|---|
| 311 | # 一回ロードしたクラスは二度ロードしないための仕組み。 |
|---|
| 312 | { |
|---|
| 313 | my $required = {}; |
|---|
| 314 | sub require_once { |
|---|
| 315 | my $path = shift; |
|---|
| 316 | return if $required->{$path}; |
|---|
| 317 | require $path; |
|---|
| 318 | $required->{$path} = 1; |
|---|
| 319 | } |
|---|
| 320 | } |
|---|
| 321 | |
|---|
| 322 | { |
|---|
| 323 | my %plugin_loaded; |
|---|
| 324 | my $__menta_extract_package = sub { |
|---|
| 325 | my $modulefile = shift; |
|---|
| 326 | open my $fh, '<', $modulefile or die "$modulefile を開けません: $!"; |
|---|
| 327 | my $in_pod = 0; |
|---|
| 328 | while (<$fh>) { |
|---|
| 329 | $in_pod = 1 if m/^=\w/; |
|---|
| 330 | $in_pod = 0 if /^=cut/; |
|---|
| 331 | next if ( $in_pod || /^=cut/ ); # skip pod text |
|---|
| 332 | next if /^\s*\#/; |
|---|
| 333 | |
|---|
| 334 | /^\s* package \s+ (\S*) \s* ;/xms and return $1; |
|---|
| 335 | } |
|---|
| 336 | return; |
|---|
| 337 | }; |
|---|
| 338 | sub load_plugin { |
|---|
| 339 | my $plugin = shift; |
|---|
| 340 | return if $plugin_loaded{$plugin}; |
|---|
| 341 | |
|---|
| 342 | my $path = MENTA::base_dir() . "plugins/${plugin}.pl"; |
|---|
| 343 | require $path; |
|---|
| 344 | $plugin_loaded{$plugin}++; |
|---|
| 345 | |
|---|
| 346 | my $package = $__menta_extract_package->($path) || ''; |
|---|
| 347 | die "${plugin} プラグインの中にパッケージ宣言がみつかりません" unless $package; |
|---|
| 348 | |
|---|
| 349 | |
|---|
| 350 | my $stash = do{ no strict 'refs'; \%{$package.'::'} }; |
|---|
| 351 | |
|---|
| 352 | while(my $name = each %{$stash}){ |
|---|
| 353 | if($name =~ /$plugin/){ |
|---|
| 354 | no strict 'refs'; |
|---|
| 355 | my $code = \&{$package.'::'.$name}; |
|---|
| 356 | *{"MENTA::$name"} = $code if defined &{$code}; |
|---|
| 357 | } |
|---|
| 358 | } |
|---|
| 359 | return $package; |
|---|
| 360 | } |
|---|
| 361 | } |
|---|
| 362 | } |
|---|
| 363 | |
|---|
| 364 | 1; |
|---|