# Copyright 2001-2005 Six Apart. This code cannot be redistributed without # permission from www.sixapart.com. For more information, consult your # Movable Type license. # # $Id: CMS.pm 16631 2005-08-24 17:14:46Z bchoate $ package MT::App::CMS; use strict; use Symbol; use File::Spec; use MT::Util qw( encode_html format_ts offset_time_list offset_time epoch2ts remove_html get_entry mark_odd_rows first_n_words perl_sha1_digest_hex is_valid_email relative_date ts2epoch perl_sha1_digest encode_url dirify encode_js is_valid_date); use MT::App; use MT::Author qw(:constants); use MT::Permission; @MT::App::CMS::ISA = qw( MT::App ); sub init { my $app = shift; $app->SUPER::init(@_) or return; $app->add_methods( 'menu' => \&show_menu, 'admin' => \&show_admin, 'status' => \&show_status, 'save' => \&save_object, 'view' => \&edit_object, 'list' => \&list_objects, 'list_plugins' => \&list_plugins, 'list_pings' => \&list_pings, 'list_entries' => \&list_entries, 'list_comments' => \&list_comments, 'list_authors' => \&list_authors, 'list_commenters' => \&list_commenters, 'save_commenter_perm' => \&save_commenter_perm, 'trust_commenter' => \&trust_commenter, 'ban_commenter' => \&ban_commenter, 'approve_item' => \&approve_item, 'save_entries' => \&save_entries, 'save_entry' => \&save_entry, 'preview_entry' => \&preview_entry, 'cfg_archives' => \&cfg_archives, 'cfg_archives_do_add' => \&cfg_archives_do_add, 'cfg_prefs' => \&cfg_prefs, 'cfg_entries' => \&cfg_entries, 'cfg_plugins' => \&cfg_plugins, 'cfg_feedback' => \&cfg_feedback, 'list_blogs' => \&list_blogs, 'system_list_blogs' => \&system_list_blogs, 'list_cat' => \&list_categories, 'save_cat' => \&save_category, 'edit_placements' => \&edit_placements, 'save_placements' => \&save_placements, 'delete_confirm' => \&delete_confirm, 'delete' => \&delete, 'edit_permissions' => \&edit_permissions, 'save_permissions' => \&save_permissions, 'ping' => \&send_pings, 'rebuild_phase' => \&rebuild_phase, 'rebuild' => \&rebuild_pages, 'rebuild_new_phase' => \&rebuild_new_phase, 'start_rebuild' => \&start_rebuild_pages, 'rebuild_confirm' => \&rebuild_confirm, 'send_notify' => \&send_notify, 'start_upload' => \&start_upload, 'upload_file' => \&upload_file, 'start_upload_entry' => \&start_upload_entry, 'show_upload_html' => \&show_upload_html, 'logout' => \&logout, 'start_recover' => \&start_recover, 'recover' => \&recover_password, 'bookmarklets' => \&bookmarklets, 'make_bm_link' => \&make_bm_link, 'view_log' => \&view_log, 'list_log' => \&view_log, 'reset_log' => \&reset_log, 'export_log' => \&export_log, 'start_import' => \&start_import, 'search_replace' => \&search_replace, 'export' => \&export, 'import' => \&do_import, 'pinged_urls' => \&pinged_urls, 'show_entry_prefs' => \&show_entry_prefs, 'save_entry_prefs' => \&save_entry_prefs, 'reg_file' => \®_file, 'reg_bm_js' => \®_bm_js, 'category_add' => \&category_add, 'category_do_add' => \&category_do_add, 'cc_return' => \&cc_return, 'reset_blog_templates' => \&reset_blog_templates, 'handshake' => \&handshake, 'itemset_action' => \&itemset_action, 'handle_junk' => \&handle_junk, 'not_junk' => \¬_junk, 'cfg_system' => \&cfg_system_feedback, 'cfg_system_feedback' => \&cfg_system_feedback, 'save_plugin_config' => \&save_plugin_config, 'reset_plugin_config' => \&reset_plugin_config, 'save_cfg_system_feedback' => \&save_cfg_system_feedback, 'update_list_prefs' => \&update_list_prefs, 'update_welcome_message' => \&update_welcome_message, 'upgrade' => \&upgrade, 'plugin_control' => \&plugin_control, ); $app->{state_params} = ['_type', 'id', 'tab', 'offset', 'filter', 'filter_val', 'blog_id', 'is_power_edit']; $app->{template_dir} = 'cms'; $app->{plugin_template_path} = ''; $app->{is_admin} = 1; $app->init_core_itemset_actions(); $app; } sub init_request { my $app = shift; $app->SUPER::init_request(@_); $app->{default_mode} = 'list_blogs'; my $mode = $app->mode; if (($mode ne 'logout') && ($mode ne 'start_recover') && ($mode ne 'recover')) { my $schema = $app->{cfg}->SchemaVersion; if (!$schema || ($schema < $app->schema_version)) { $mode = 'upgrade'; $app->mode($mode); } } $app->{requires_login} = $mode && ($mode eq 'start_recover' || $mode eq 'recover' || $mode eq 'reg_bm_js' || $mode eq 'upgrade' || $mode eq 'logout') ? 0 : 1; } sub init_core_itemset_actions { my $app = shift; $app->add_itemset_action({type => 'entry', key => "set_published", label => "Publish Entries", code => \&publish_entries, }, 1); $app->add_itemset_action({type => 'entry', key => "set_draft", label => "Unpublish Entries", code => \&draft_entries, }, 1); $app->add_itemset_action({type => 'ping', key => "unapprove_ping", label => "Unpublish TrackBack(s)", code => \&unapprove_item, condition => sub { $_[0] ne 'junk' }, # param is tab name }, 1); $app->add_itemset_action({type => 'comment', key => "unapprove_comment", label => "Unpublish Comment(s)", code => \&unapprove_item, condition => sub { $_[0] ne 'junk' }, }, 1); $app->add_itemset_action({type => 'comment', key => "trust_commenter", label => "Trust Commenter(s)", code => \&trust_commenter_by_comment, condition => sub { $app->user_can_admin_commenters }, }, 1); $app->add_itemset_action({type => 'comment', key => "untrust_commenter", label => "Untrust Commenter(s)", code => \&untrust_commenter_by_comment, condition => sub { $app->user_can_admin_commenters }, }, 1); $app->add_itemset_action({type => 'comment', key => "ban_commenter", label => "Ban Commenter(s)", code => \&ban_commenter_by_comment, condition => sub { $app->user_can_admin_commenters }, }, 1); $app->add_itemset_action({type => 'comment', key => "unban_commenter", label => "Unban Commenter(s)", code => \&unban_commenter_by_comment, condition => sub { $app->user_can_admin_commenters }, }, 1); $app->add_itemset_action({type => 'commenter', key => "untrust", label => "Untrust Commenter(s)", code => \&untrust_commenter, condition => sub { $app->user_can_admin_commenters }, }, 1); $app->add_itemset_action({type => 'commenter', key => "unban", label => "Unban Commenter(s)", code => \&unban_commenter, condition => sub { $app->user_can_admin_commenters }, }, 1); } sub user_can_admin_commenters { my $app = shift; $app->{author}->is_superuser() || ($app->{perms} && ($app->{perms}->can_administer_blog || $app->{perms}->can_edit_config)); } sub update_welcome_message { my $app = shift; $app->validate_magic or return; # FIXME: permission check my $perms = $app->{perms}; return $app->error("No permissions") unless $perms&& $perms->can_edit_config; my $blog_id = $app->param('blog_id'); my $message = $app->param('welcome-message-text'); my $blog = MT::Blog->load($blog_id, {cached_ok=>1}) or return $app->error("Invalid blog"); $blog->welcome_msg($message); $blog->save; $app->redirect($app->uri( mode => 'menu', args => { blog_id => $blog_id } )); } sub upgrade { my $app = shift; # check for an empty database... no author table would do it... my $driver = MT::Object->driver; my $upgrade_script = $app->config('UpgradeScript'); if (!$driver || !$driver->table_exists('MT::Author')) { return $app->redirect($app->path . $upgrade_script . $app->uri_params( mode => 'install')); } if ($app->{cfg}->SchemaVersion && ($app->{cfg}->SchemaVersion == $app->schema_version)) { return $app->redirect($app->uri); } return $app->redirect($app->path . $upgrade_script); } sub pre_run { my $app = shift; $app->SUPER::pre_run(); ## Localize the label of the default text filter. $MT::Text_filters{__default__}{label} = $app->translate('Convert Line Breaks'); } sub logout { my $app = shift; $app->SUPER::logout(@_); } sub start_recover { my $app = shift; $app->add_breadcrumb($app->translate('Password Recovery')); $app->build_page('recover.tmpl'); } sub recover_password { my $app = shift; my $q = $app->param; my $name = $q->param('name'); my $class; if (MT::Object->driver->isa('MT::ObjectDriver::DBM')) { $class = $app->user_class; } else { $class = 'MT::BasicAuthor'; } eval "use $class;"; my @author = $class->load({ name => $name }); my $author; foreach (@author) { next unless ($_->email && $_->password) && ($_->password ne '(none)'); $author = $_; } $app->log($app->translate("Invalid author name '[_1]' in password recovery attempt", $name)), return $app->error($app->translate( "Author name or birthplace is incorrect.")) unless $author; return $app->error($app->translate( "Author has not set birthplace; cannot recover password")) unless $author->hint; my $hint = $q->param('hint'); $app->log($app->translate("Invalid attempt to recover password (used birthplace '[_1]')", $hint)), return $app->error($app->translate( "Author name or birthplace is incorrect.")) unless $author->hint eq $hint; return $app->error($app->translate("Author does not have email address")) unless $author->email; my @pool = ('a'..'z', 0..9); my $pass; for (1..8) { $pass .= $pool[ rand @pool ] } $author->set_password($pass); $author->save; $app->log($app->translate("Password was reset for author '[_1]' (user #[_2])", $author->name, $author->id)); my %head = ( To => $author->email, From => $app->config('EmailAddressMain') || $author->email, Subject => "Password Recovery" ); my $body = $app->translate('_USAGE_FORGOT_PASSWORD_1') . "\n\n $pass\n\n" . $app->translate('_USAGE_FORGOT_PASSWORD_2') . "\n"; require Text::Wrap; $Text::Wrap::columns = 72; $body = Text::Wrap::wrap('', '', $body); require MT::Mail; MT::Mail->send(\%head, $body) or return $app->error($app->translate( "Error sending mail ([_1]); please fix the problem, then " . "try again to recover your password.", MT::Mail->errstr)); $app->add_breadcrumb($app->translate('Password Recovery')); $app->build_page('recover.tmpl', { recovered => 1, email => $author->email }); } sub is_authorized { my $app = shift; my $blog_id = $app->param('blog_id'); return 1 unless $blog_id; return unless my $author = $app->user; require MT::Permission; my $perms; $perms = $app->{perms} = MT::Permission->load({ author_id => $author->id, blog_id => $blog_id }); if (!$perms && $author->is_superuser) { $perms = $app->{perms} = new MT::Permission; $perms->author_id($author->id); $perms->blog_id($blog_id); $perms->set_full_permissions(); } $perms ? 1 : $app->error($app->translate( "You are not authorized to log in to this blog.")); } sub build_page { my $app = shift; my($page, $param) = @_; $param->{mode} = $app->mode; if (my $perms = $app->{perms}) { $param->{can_post} = $perms->can_post; $param->{can_upload} = $perms->can_upload; $param->{can_edit_entries} = $perms->can_post || $perms->can_edit_all_posts; $param->{can_search_replace} = $perms->can_edit_all_posts; $param->{can_edit_templates} = $perms->can_edit_templates; $param->{can_edit_authors} = $perms->can_administer_blog; $param->{can_edit_config} = $perms->can_edit_config; # FIXME: once we have edit_commenters permission $param->{can_edit_commenters} = $perms->can_edit_config(); $param->{can_rebuild} = $perms->can_rebuild; $param->{can_edit_categories} = $perms->can_edit_categories; $param->{can_edit_notifications} = $perms->can_edit_notifications; $param->{has_manage_label} = $perms->can_edit_templates || $perms->can_administer_blog || $perms->can_edit_categories || $perms->can_edit_config; $param->{has_posting_label} = $perms->can_post || $perms->can_edit_all_posts || $perms->can_upload; $param->{has_community_label} = $perms->can_post || $perms->can_edit_config || $perms->can_edit_notifications || $perms->can_edit_all_posts; $param->{can_view_log} = $perms->can_view_blog_log; } my $blog_id = $app->param('blog_id'); require MT::Blog; if (my $auth = $app->user) { $param->{is_administrator} = $auth->is_superuser; $param->{can_create_blog} = $auth->can_create_blog; $param->{can_view_log} ||= $auth->can_view_log; $param->{author_id} = $auth->id; $param->{author_name} = $auth->name; require MT::Permission; my @perms = MT::Permission->load({ author_id => $auth->id }); my @data; for my $perms (@perms) { next unless $perms->role_mask; my $blog = MT::Blog->load($perms->blog_id, {cached_ok=>1}) or die("Couldn't load blog; perhaps you have not " . "upgraded your MT database? - " . MT::Blog->errstr); push @data, { top_blog_id => $blog->id, top_blog_name => $blog->name }; $data[-1]{top_blog_selected} = 1 if $blog_id && ($blog->id == $blog_id); } @data = sort { $a->{top_blog_name} cmp $b->{top_blog_name} } @data; $param->{top_blog_loop} = \@data; } if ($blog_id && $page ne 'login.tmpl') { my $blog = MT::Blog->load($blog_id, {cached_ok=>1}); if ($blog) { $param->{blog_name} = encode_html($blog->name); $param->{blog_id} = $blog->id; $param->{blog_url} = $blog->site_url; } else { $app->error($app->translate("No such blog [_1]", $blog_id)); } } if ($app->param('is_bm')) { $param->{is_bookmarklet} = 1; } if ($page ne 'login.tmpl') { if (ref $app eq 'MT::App::CMS') { $param->{system_overview_nav} = 1 unless $blog_id || exists $param->{system_overview_nav} || $param->{no_breadcrumbs} || $param->{is_bookmarklet}; $param->{quick_search} = 1 unless defined $param->{quick_search}; } } my $author = $app->user; if ($author && !$blog_id) { # then we're in a system overview area. my @perms = MT::Permission->load({author_id => $author->id}); $param->{has_authors_button} = $author->is_superuser || grep { $_->can_administer_blog } @perms; } my $static_app_url = $app->static_path; $param->{help_url} = $app->config('HelpURL') || $static_app_url . 'docs/'; $param->{show_ip_info} ||= $app->config('ShowIPInformation'); $param->{agent_mozilla} = ($ENV{HTTP_USER_AGENT} || '') =~ /gecko/i; $param->{have_tangent} = eval { require MT::Tangent; 1 } ? 1 : 0; my $type = $app->param('_type') || ''; my $mode = $app->mode; $param->{plugin_action_loop} ||= $MT::PluginActions{$mode} || []; $param->{"mode_$mode" . ($type ? "_$type" : '')} = 1; $param->{return_args} ||= $app->make_return_args; if ($param->{system_overview_nav}) { unshift @{$app->{breadcrumbs}}, { bc_name => $app->translate("System Overview"), bc_uri => $app->uri('mode' => 'admin') }; } elsif ($param->{blog_id}) { if (my $blog = MT::Blog->load($param->{blog_id}, {cached_ok=>1})) { unshift @{$app->{breadcrumbs}}, { bc_name => $blog->name, bc_uri => $app->uri('mode' => 'menu', args => { blog_id => $blog->id})}; } } unshift @{$app->{breadcrumbs}}, { bc_name => $app->translate('Main Menu'), bc_uri => $app->mt_uri }; $app->SUPER::build_page($page, $param); } sub get_newsbox_content { my $app = shift; my $newsbox_url = $app->config('NewsboxURL'); if ($newsbox_url && $newsbox_url ne 'disable') { my $NEWSCACHE_TIMEOUT = 60 * 60 * 24; require MT::Session; my ($news_object) = (""); my $retries = 0; $news_object = MT::Session->load({ id => 'NW' }); if ($news_object && ($news_object->start() < (time - $NEWSCACHE_TIMEOUT))) { $news_object->remove; $news_object = undef; } return $news_object->data() if ($news_object); eval { require LWP::UserAgent; require HTTP::Request; } or return; my $ua = new LWP::UserAgent(agent => 'Movable Type'); my $req = new HTTP::Request(GET => $newsbox_url); my $resp = $ua->request($req); return unless $resp->is_success(); my $result = $resp->content(); if ($result) { $news_object = MT::Session->new(); $news_object->set_values({id => 'NW', kind => 'NW', start => time(), data => $result}); $news_object->save(); } return $result; } } sub make_blog_list { my $app = shift; my ($blogs, $perms) = @_; require MT::TBPing; require MT::Entry; require MT::Comment; my $author = $app->user; my $can_edit_authors = $author->is_superuser; my $data; my $i; for my $blog (@$blogs) { my $blog_id = $blog->id; my $perms = $author->is_superuser ? $author->blog_perm($blog_id) : $perms->{ $blog_id }; $can_edit_authors = 1 if $perms->can_administer_blog; my $row = { id => $blog->id, name => encode_html($blog->name), description => $blog->description, site_url => $blog->site_url }; $row->{num_entries} = MT::Entry->count({ blog_id => $blog_id }); $row->{num_comments} = MT::Comment->count({ blog_id => $blog_id, junk_status => [ 0, 1 ] }, { 'range_incl' => { 'junk_status' => 1 }}); $row->{num_pings} = MT::TBPing->count({ blog_id => $blog_id, junk_status => [ 0, 1 ] },{ 'range_incl' => { 'junk_status' => 1 }}); $row->{num_authors} = 0; my $iter = MT::Permission->load_iter({ blog_id => $blog_id }); while (my $p = $iter->()) { $row->{num_authors}++ if ($p->can_post); } $row->{can_post} = $perms->can_post; $row->{can_edit_entries} = $perms->can_post|| $perms->can_edit_all_posts; $row->{can_edit_templates} = $perms->can_edit_templates; $row->{can_edit_config} = $perms->can_edit_config || $perms->can_administer_blog; $row->{can_administer_blog} = $perms->can_administer_blog; push @$data, $row; } return ($data, $can_edit_authors); } ## Application methods sub list_blogs { my $app = shift; my $q = $app->param; require MT::Blog; require MT::Permission; require MT::Entry; require MT::Comment; my $author = $app->user; my @perms = MT::Permission->load({ author_id => $author->id }); my %perms = map { $_->blog_id => $_ } @perms; my %args; my $list_pref = $app->list_pref('main_menu'); if ($list_pref->{'sort'} eq 'name') { $args{'sort'} = 'name'; } elsif ($list_pref->{'sort'} eq 'created') { $args{'sort'} = 'id'; } elsif ($list_pref->{'sort'} eq 'updated') { $args{'sort'} = 'children_modified_on'; } if ($list_pref->{'order'} eq 'descend') { $args{'direction'} = 'descend'; } $args{join} = ['MT::Permission', 'blog_id', { author_id => $author->id, role_mask => [1, undef] }, # don't count those with mask 0 { range_incl => {role_mask => 1} }]; my @blogs = MT::Blog->load(undef, \%args); my %param = %$list_pref; my $i = 1; ($param{blog_loop}, $param{can_edit_authors}) = $app->make_blog_list(\@blogs, \%perms); delete $param{blog_loop} unless ref $param{blog_loop}; $param{can_create_blog} = $author->can_create_blog; $param{can_view_log} = $author->can_view_log; $param{saved_deleted} = $q->param('saved_deleted'); if ($author->can_create_blog()) { $param{blog_count} = MT::Blog->count(); $param{blog_count_plural} = $param{blog_count} != 1; $param{author_count} = MT::Author->count({type => AUTHOR}); $param{author_count_plural} = $param{author_count} != 1; $param{can_view_blog_count} = 1; } $param{news_html} = $app->get_newsbox_content(); $param{system_overview_nav} = 0; $param{quick_search} = 0; $param{no_breadcrumbs} = 1; $app->build_page('list_blog.tmpl', \%param); } sub list_pref { # FIXME: Can the user manip. the cookie to modify $param arbitrarily? my $app = shift; my ($list) = @_; my $updating = $app->mode eq 'update_list_prefs'; unless ($updating) { my $pref = $app->request("list_pref_$list"); return $pref if defined $pref; } my $cookie = $app->cookie_val('mt_list_pref') || ''; my $mode = $app->mode; # defaults: my $list_pref; if ($list eq 'main_menu') { $list_pref = { 'sort' => 'name', order => 'ascend', view => 'compact', dates => 'relative' }; } else { $list_pref = { rows => 20, view => 'compact', bar => 'above', dates => 'relative' }; } my @list_prefs = split /;/, $cookie; my $new_cookie = ''; foreach my $pref (@list_prefs) { my ($name, $prefs) = $pref =~ m/^(\w+):(.*)$/; next unless $name && $prefs; if ($name eq $list) { my @prefs = split /,/, $prefs; foreach (@prefs) { my ($k, $v) = split /=/; $list_pref->{$k} = $v if exists $list_pref->{$k}; } } else { $new_cookie .= ($new_cookie ne '' ? ';' : '') . $pref; } } if ($updating) { my $updated = 0; if (my $limit = $app->param('limit')) { $list_pref->{rows} = $limit eq 'none' ? $limit : ($limit > 0 ? $limit : 20); $updated = 1; } if (my $view = $app->param('verbosity')) { if ($view =~ m!^compact|expanded$!) { $list_pref->{view} = $view; $updated = 1; } } if (my $bar = $app->param('actions')) { if ($bar =~ m!^above|below|both$!) { $list_pref->{bar} = $bar; $updated = 1; } } if (my $ord = $app->param('order')) { if ($ord =~ m!^ascend|descend$!) { $list_pref->{order} = $ord; $updated = 1; } } if (my $sort = $app->param('sort')) { if ($sort =~ m!^name|created|updated$!) { $list_pref->{'sort'} = $sort; $updated = 1; } } if (my $dates = $app->param('dates')) { if ($dates =~ m!^relative|full$!) { $list_pref->{'dates'} = $dates; $updated = 1; } } if ($updated) { my @list_prefs; foreach (keys %$list_pref) { push @list_prefs, $_ . '=' . $list_pref->{$_}; } my $prefs = join ',', @list_prefs; $new_cookie .= ($new_cookie ne '' ? ';' : '') . $list . ':' . $prefs; $app->bake_cookie(-name => 'mt_list_pref', -value => $new_cookie, -expires => '+10y'); } } if ($list_pref->{rows}) { $list_pref->{"limit_" . $list_pref->{rows}} = $list_pref->{rows}; } if ($list_pref->{view}) { $list_pref->{"view_" . $list_pref->{view}} = 1; } if ($list_pref->{dates}) { $list_pref->{"dates_" . $list_pref->{dates}} = 1; } if ($list_pref->{bar}) { if ($list_pref->{bar} eq 'both') { $list_pref->{"position_actions_both"} = 1; $list_pref->{"position_actions_top"} = 1; $list_pref->{"position_actions_bottom"} = 1; } elsif ($list_pref->{bar} eq 'below') { $list_pref->{"position_actions_bottom"} = 1; } elsif ($list_pref->{bar} eq 'above') { $list_pref->{"position_actions_top"} = 1; } } if ($list_pref->{'sort'}) { $list_pref->{'sort_' . $list_pref->{'sort'}} = 1; } if ($list_pref->{'order'}) { $list_pref->{'order_' . $list_pref->{'order'}} = 1; } $app->request("list_pref_$list", $list_pref); } sub system_list_blogs { my $app = shift; my $author = $app->user; my $list_pref = $app->list_pref('blog'); my $limit = $list_pref->{rows}; my $offset = $limit eq 'none' ? 0 : ($app->param('offset') || 0); my $args = { offset => $offset, sort => 'name' }; $args->{limit} = $limit + 1 unless $limit eq 'none'; unless ($author->is_superuser) { $args->{join} = ['MT::Permission', 'blog_id', { author_id => $author->id }, { unique => 1 } ]; } require MT::Blog; my %param = %$list_pref; my @blogs = MT::Blog->load(undef, $args); my @perms = MT::Permission->load({ author_id => $author->id }); my %perms = map { $_->blog_id => $_ } @perms; my $can_edit_authors; ($param{blog_loop}, $can_edit_authors) = $app->make_blog_list(\@blogs, \%perms); delete $param{blog_loop} unless ref $param{blog_loop}; if ($param{blog_loop} && ($limit ne 'none')) { ## We tried to load $limit + 1 entries above; if we actually got ## $limit + 1 back, we know we have another page of entries. my $have_next = @{$param{blog_loop}} > $limit; pop @{$param{blog_loop}} while @{$param{blog_loop}} > $limit; if ($offset) { $param{prev_offset} = 1; $param{prev_offset_val} = $offset - $limit; $param{prev_offset_val} = 0 if $param{prev_offset_val} < 0; } if ($have_next) { $param{next_offset} = 1; $param{next_offset_val} = $offset + $limit; } } $param{object_type} = 'blog'; $param{object_type_plural} = 'weblogs'; $param{list_start} = $offset + 1; delete $args->{limit}; delete $args->{offset}; $param{list_total} = MT::Blog->count(undef, $args); $param{list_end} = $offset + (scalar @blogs); $param{next_max} = $param{list_total} - ($limit eq 'none' ? 0 : $limit); $param{next_max} = 0 if ($param{next_max} || 0) < $offset + 1; $param{can_create_blog} = $author->can_create_blog; $param{saved_deleted} = $app->param('saved_deleted'); $param{nav_blogs} = 1; my $plugin_actions = $app->plugin_itemset_actions('blog'); $param{plugin_itemset_action_loop} = $plugin_actions if $plugin_actions; my $core_actions = $app->core_itemset_actions('blog'); $param{core_itemset_action_loop} = $core_actions if $core_actions; $param{has_itemset_actions} = ($plugin_actions || $core_actions) ? 1 : 0; $app->add_breadcrumb($app->translate("Weblogs")); $param{nav_weblogs} = 1; $app->build_page('system_list_blog.tmpl', \%param) } sub list_authors { my $app = shift; my $this_author = $app->user; my $this_author_id = $this_author->id; my $list_pref = $app->list_pref('author'); my %param = %$list_pref; my $limit = $list_pref->{rows}; my $offset = $limit eq 'none' ? 0 : ($app->param('offset') || 0); my $args = { offset => $offset, sort => 'name' }; $args->{limit} = $limit + 1 if $limit ne 'none'; my %author_entry_count; require MT::Entry; unless (MT::Object->driver->isa('MT::ObjectDriver::DBM')) { # Berkeley DB users don't get the count of entries per author my $author_entry_count_iter = MT::Entry->count_group_by(undef, {group => ['author_id']}); while (my ($count, $author_id) = $author_entry_count_iter->()) { $author_entry_count{$author_id} = $count; } } $param{can_create_user} = $this_author->is_superuser; # really, "can_edit_authors" when it exists my $author_iter = MT::Author->load_iter({ type => MT::Author::AUTHOR() }, $args); my (@data, %authors); while (my $au = $author_iter->()) { my $has_edit_access = $this_author->can_administer($au); #next unless $au->id == $this_author->id # || $has_edit_access # || $this_author->is_superuser(); # in spirit, "can_edit_users" my $row = $au->column_values; $row->{name} = '(unnamed)' if !$row->{name}; $authors{$au->id} ||= $au; $row->{id} = $au->id; $row->{email} = '' unless $au->email =~ /@/; $row->{entry_count} = $author_entry_count{$au->id}; $row->{is_me} = $au->id == $this_author_id; $row->{has_edit_access} = !$row->{is_me} && ($this_author->is_superuser || ($row->{created_by} && $row->{created_by} == $this_author_id) || $has_edit_access); my $parent_author = $authors{$au->created_by} ||= MT::Author->load($au->created_by) if $au->created_by; $row->{created_by} = $parent_author->name if $parent_author; my ($last) = MT::Entry->load({author_id => $au->id}, {'sort' => 'created_on', direction => 'descend', limit => 1}); if ($last) { if (my $ts = $last->created_on) { $row->{last_entry_formatted} = format_ts("%Y.%m.%d", $ts); $row->{last_entry_time_formatted} = format_ts("%Y-%m-%d %H:%M:%S", $ts); $row->{last_entry_relative} = relative_date($ts, time, $last->blog); } } push @data, $row; last if ($limit ne 'none') && (scalar @data == $limit); } $param{object_loop} = \@data; $param{object_type} = 'author'; $param{object_type_plural} = 'authors'; $param{limit} = $limit; $param{list_start} = $offset + 1; delete $args->{limit}; delete $args->{offset}; $param{list_total} = MT::Author->count({ type => MT::Author::AUTHOR() }, $args); $param{list_end} = $offset + (scalar @data); $param{next_offset_val} = $offset + (scalar @data); $param{next_offset} = $param{next_offset_val} < $param{list_total} ? 1 : 0; $param{next_max} = $param{list_total} - ($limit eq 'none' ? 0 : $limit); $param{next_max} = 0 if ($param{next_max} || 0) < $offset + 1; if ($offset > 0) { $param{prev_offset} = 1; $param{prev_offset_val} = $offset - ($limit eq 'none' ? 0 : $limit); $param{prev_offset_val} = 0 if $param{prev_offset_val} < 0; } $param{saved_deleted} = $app->param('saved_deleted'); $app->add_breadcrumb($app->translate("Authors")); $param{nav_authors} = 1; $app->build_page('list_author.tmpl', \%param) } sub bookmarklets { my $app = shift; $app->add_breadcrumb($app->translate('QuickPost')); $app->build_page('bookmarklets.tmpl'); } sub make_bm_link { my $app = shift; my %param = ( have_link => 1 ); my @show = $app->param('show'); my $height = 440; s/[^\w]//g foreach @show; # non-word chars could be harmful my %show = map { $_ => 1 } @show; $height += 50 if $show{t}; # trackback $height += 40 if $show{ac}; # allow comments $height += 20 if $show{ap}; # allow pings $height += 40 if $show{cb}; # convert breaks $height += 50 if $show{c}; # category $height += 80 if $show{e}; # excerpt $height += 80 if $show{k}; # keywords $height += 80 if $show{'m'}; # more text $param{bm_show} = join ',', @show; $param{bm_height} = $height; $param{bm_js} = $app->_bm_js($param{bm_show}, $height); $app->add_breadcrumb($app->translate('QuickPost')); $app->build_page('bookmarklets.tmpl', \%param); } sub _bm_js { my $app = shift; my($show, $height) = @_; my %args = (is_bm => 1, bm_show => $show, '_type' => 'entry'); my $uri = $app->base . $app->uri('mode' => 'view', args => \%args); qq!javascript:d=document;w=window;t='';if(d.selection)t=d.selection.createRange().text;else{if(d.getSelection)t=d.getSelection();else{if(w.getSelection)t=w.getSelection()}}void(w.open('$uri&link_title='+escape(d.title)+'&link_href='+escape(d.location.href)+'&text='+escape(t),'_blank','scrollbars=yes,width=400,height=$height,status=yes,resizable=yes,scrollbars=yes'))!; } sub view_log { my $app = shift; my $author = $app->user; my $blog_id = $app->param('blog_id'); if ($blog_id) { return $app->error($app->translate("Permission denied.")) unless $app->{perms}->can_view_blog_log; } else { return $app->error($app->translate("Permission denied.")) unless $author->can_view_log; } require MT::Log; my $list_pref = $app->list_pref('log'); my $limit = $list_pref->{rows}; my $offset = $limit eq 'none' ? 0 : ($app->param('offset') || 0); my $arg = { $blog_id ? (blog_id => $blog_id) : () }; my $cfg = $app->{cfg}; my $iter = MT::Log->load_iter($arg, { ($cfg->ObjectDriver ne 'DBM' ? # work around a flaw in DBM driver ('sort' => 'id') : ('sort' => 'created_on')), 'direction' => 'descend', 'offset' => $offset, $limit ne 'none' ? ('limit' => $limit) : () }); my %param = ( %$list_pref ); my $log = $app->build_log_table(iter => $iter, param => \%param); if ($blog_id) { my $so = $app->blog->server_offset; if ($so) { my $partial_hour_offset = 60 * abs($so - int($so)); my $tz = sprintf("%s%02d:%02d", $so < 0 ? '-' : '+', abs($so), $partial_hour_offset); $param{time_offset} = $tz; } } $param{object_type} = 'log'; $param{object_type_plural} = $app->translate('log records'); $param{search_type} = $app->translate('Activity Log'); $param{list_start} = $offset + 1; $param{list_total} = MT::Log->count($arg); $param{list_end} = $offset + (scalar @$log); $param{next_offset_val} = $offset + (scalar @$log); $param{next_offset} = $param{next_offset_val} < $param{list_total} ? 1 : 0; $param{next_max} = $param{list_total} - ($limit eq 'none' ? 0 : $limit); $param{next_max} = 0 if ($param{next_max} || 0) < $offset + 1; if ($offset > 0) { $param{prev_offset} = 1; $param{prev_offset_val} = $offset - ($limit eq 'none' ? 0 : $limit); $param{prev_offset_val} = 0 if $param{prev_offset_val} < 0; } $param{'reset'} = $app->param('reset'); $param{nav_log} = 1; $app->add_breadcrumb($app->translate('Activity Log')); unless ($app->param('blog_id')) { $param{system_overview_nav} = 1; } $app->build_page('view_log.tmpl', \%param); } sub build_log_table { my $app = shift; my (%args) = @_; my $blog = $app->blog; my $blog_view = $blog ? 1 : 0; my $i = 1; my @log; my $iter; if ($args{load_args}) { my $class = $app->_load_driver_for('log'); $iter = $class->load_iter( @{ $args{load_args} } ); } elsif ($args{iter}) { $iter = $args{iter}; } elsif ($args{items}) { $iter = sub { pop @{ $args{items} } }; } my $param = $args{param}; my %blogs; while (my $log = $iter->()) { my $row = { log_message => $log->message, log_ip => $log->ip }; if (my $ts = $log->created_on) { if ($blog_view) { $row->{created_on_formatted} = format_ts("%Y.%m.%d %H:%M:%S", epoch2ts($blog, ts2epoch(undef, $ts))); } else { $row->{created_on_formatted} = format_ts("%Y.%m.%d %H:%M:%S", $ts); } #my $blog = $blogs{$log->blog_id} = MT::Blog->load($log->blog_id) # if $log->blog_id; $row->{created_on_relative} = relative_date($ts, time); #, $blog); } push @log, $row; } return [] unless @log; $param->{log_table}[0]{object_loop} = \@log; \@log; } sub reset_log { my $app = shift; my $author = $app->user; return $app->error($app->translate("Permission denied.")) unless $author->can_view_log; $app->validate_magic() or return; require MT::Log; if (my $blog_id = $app->param('blog_id')) { my @obj = MT::Log->load({ blog_id => $blog_id }); if (@obj) { $_->remove foreach @obj; $app->log($app->translate("Application log for blog '[_1]' reset by '[_2]' (user #[_3])", $blog_id, $author->name, $author->id)); } } else { MT::Log->remove_all; $app->log($app->translate("Application log reset by '[_1]' (user #[_2])", $author->name, $author->id)); } $app->add_return_arg('reset' => 1); $app->call_return; } sub export_log { my $app = shift; my $author = $app->user; my $perms = $app->{perms}; my $blog = $app->blog; my $blog_view = $blog ? 1 : 0; if ($blog_view) { return $app->error($app->translate("Permission denied.")) unless $author->can_view_log || ($perms && $perms->can_view_blog_log); } else { return $app->error($app->translate("Permission denied.")) unless $author->can_view_log; } $app->validate_magic() or return; $| = 1; my $charset = $app->config('PublishCharset'); my (%terms) = @_; if ($blog) { $terms{blog_id} = $blog->id; } require MT::Log; my $iter = MT::Log->load_iter(\%terms, { 'sort' => 'created_on', 'direction' => 'ascend' }); my %blogs; my $file = ''; $file = dirify($blog->name) . '-' if $blog; my @ts = gmtime(time); my $ts = sprintf "%04d-%02d-%02d-%02d-%02d-%02d", $ts[5]+1900, $ts[4]+1, @ts[3,2,1,0]; $file .= "log_$ts.csv"; $app->{no_print_body} = 1; $app->set_header("Content-Disposition" => "attachment; filename=$file"); $app->send_http_header($charset ? "text/csv; charset=$charset" : 'text/csv'); my $csv = "timestamp,ip,weblog,message\n"; while (my $log = $iter->()) { # columns: # date, ip address, weblog, log message my @col; my $ts = $log->created_on; if ($blog_view) { push @col, format_ts("%Y-%m-%d %H:%M:%S", epoch2ts($blog, ts2epoch(undef, $ts))); } else { push @col, format_ts("%Y-%m-%d %H:%M:%S", $log->created_on); } push @col, $log->ip; if ($log->blog_id) { my $blog = $blogs{$log->blog_id} ||= MT::Blog->load($log->blog_id, {cached_ok=>1}); my $name = $blog->name; $name =~ s/"/\\"/gs; $name =~ s/[\r\n]+/ /gs; push @col, '"' . $name . '"'; } else { push @col, ''; } my $msg = $log->message; $msg =~ s/"/\\"/gs; $msg =~ s/[\r\n]+/ /gs; push @col, '"' . $msg . '"'; $csv .= (join ',', @col) . "\n"; $app->print($csv); $csv = ''; } } sub start_import { my $app = shift; my $blog_id = $app->param('blog_id'); my %param; require MT::Category; my $iter = MT::Category->load_iter({ blog_id => $blog_id }); my @data; while (my $cat = $iter->()) { push @data, { category_id => $cat->id, category_label => $cat->label }; } @data = sort { $a->{category_label} cmp $b->{category_label} } @data; $param{category_loop} = \@data; $param{nav_import} = 1; $param{can_edit_authors} = $app->{perms}->can_administer_blog; $app->add_breadcrumb($app->translate('Import/Export')); $app->build_page('import.tmpl', \%param); } sub show_admin { my $app = shift; my %param; $param{nav_admin} = 1; # System Stats require MT::Blog; $param{blog_count} = MT::Blog->count(); # active author count: someone who has posted within 90 days require MT::Author; require MT::Entry; my $to = time + (60*60*24); my $from = time - (60*60*24*90 + 60*60*24); my $to_ts = epoch2ts(undef, $to); my $from_ts = epoch2ts(undef, $from); $param{active_author_count} = MT::Author->count( { type => MT::Author::AUTHOR() }, { join => [ 'MT::Entry', 'author_id', { created_on => [ $from_ts, $to_ts ] }, { unique => 1, range_incl => { created_on => 1 } } ] } ); $param{author_count} = MT::Author->count( { type => MT::Author::AUTHOR() }); require MT::Entry; $param{entry_count} = MT::Entry->count(); require MT::Comment; $param{comment_count} = MT::Comment->count( { junk_status => [0, 1] }, { range_incl => { junk_status => 1 } } ); require MT::TBPing; $param{trackback_count} = MT::TBPing->count( { junk_status => [0, 1] }, { range_incl => { junk_status => 1 } } ); $param{nav_info} = 1; $param{news_html} = $app->get_newsbox_content(); $param{quick_search} = 0; $app->build_page('admin.tmpl', \%param); } sub show_status { my $app = shift; my %param; $param{nav_status} = 1; # System Stats require MT::Blog; $param{blog_count} = MT::Blog->count(); # active author count: someone who has posted within 90 days require MT::Author; my $to = time; my $from = epoch2ts(undef, time - (60*60*24*90 + 1)); $param{active_author_count} = MT::Author->count( { type => MT::Author::AUTHOR() }, { join => [ 'MT::Entry', 'author_id', { created_on => [ $from, $to ] }, { unique => 1, range_incl => { created_on => 1 } } ] } ); require MT::Author; $param{author_count} = MT::Author->count( { type => MT::Author::AUTHOR() }); require MT::Entry; $param{entry_count} = MT::Entry->count(); require MT::Comment; $param{comment_count} = MT::Comment->count( { junk_status => [0, 1] }, { range_incl => { junk_status => 1 } } ); require MT::TBPing; $param{trackback_count} = MT::TBPing->count( { junk_status => [0, 1] }, { range_incl => { junk_status => 1 } } ); $param{nav_info} = 1; $param{quick_search} = 0; $app->build_page('system_info.tmpl', \%param); } sub show_menu { my $app = shift; my $perms = $app->{perms} or return $app->error($app->translate("No permissions")); require MT::Comment; require MT::TBPing; require MT::Trackback; require MT::Permission; require MT::Entry; my $blog_id = $app->param('blog_id'); my $iter = MT::Entry->load_iter({ blog_id => $blog_id }, { 'sort' => 'created_on', direction => 'descend', limit => 5 }); my @e_data; my $i = 1; my $author_id = $app->user->id; while (my $entry = $iter->()) { my $row = { entry_id => $entry->id, entry_blog_id => $entry->blog_id, }; $row->{entry_title} = $entry->title; unless (defined($row->{entry_title})) { my $title = remove_html($entry->text); $row->{entry_title} = substr($title||"", 0, 22) . '...'; } else { $row->{entry_title} = substr($row->{entry_title}, 0, 22) . '...' if $row->{entry_title} && $row->{entry_title} =~ m(\S{22,}); } $row->{entry_title} = encode_html($row->{entry_title}, 1); $row->{entry_created_on} = format_ts("%Y.%m.%d", $entry->created_on); $row->{has_edit_access} = $perms->can_edit_all_posts || $entry->author_id == $author_id; push @e_data, $row; } $iter = MT::Comment->load_iter({ blog_id => $blog_id, junk_status => [ 0, 1 ] }, { 'sort' => 'created_on', direction => 'descend', limit => 5, range_incl => { junk_status => 1 } }); my @c_data; $i = 1; while (my $comment = $iter->()) { my $row = { comment_id => $comment->id, comment_author => $comment->author, comment_blog_id => $comment->blog_id, }; $row->{comment_author} = substr($row->{comment_author}, 0, 22) . '...' if $row->{comment_author} && $row->{comment_author} =~ m(\S{22,}); $row->{comment_created_on} = format_ts("%Y.%m.%d", $comment->created_on); if (my $entry = $comment->entry) { $row->{has_edit_access} = $perms->can_edit_all_posts || $entry->author_id == $author_id; } push @c_data, $row; } $iter = MT::TBPing->load_iter({ blog_id => $blog_id, junk_status => [ 0, 1 ] }, { 'sort' => 'created_on', direction => 'descend', limit => 5, range_incl => { junk_status => 1 } }); my @p_data; $i = 1; while (my $ping = $iter->()) { my $row = { ping_id => $ping->id, ping_title => $ping->title || '[No title]', ping_url => $ping->source_url, ping_blog_id => $ping->blog_id, }; # FIXME: trim this shorter. $row->{ping_title} = substr($row->{ping_title}, 0, 22) . '...' if $row->{ping_title} =~ m(\S{22,}); $row->{ping_created_on} = format_ts("%Y.%m.%d", $ping->created_on); my $tb = MT::Trackback->load($ping->tb_id); if ($tb->entry_id) { my $entry = MT::Entry->load($tb->entry_id); $row->{has_edit_access} = $perms->can_edit_all_posts || $entry->author_id == $author_id; $row->{ping_entry_id} = $entry->id; } push @p_data, $row; } require MT::Blog; my $blog = MT::Blog->load($blog_id, {cached_ok=>1}); my %param = (entry_loop => \@e_data, comment_loop => \@c_data, ping_loop => \@p_data); require MT::Entry; my $future_entry = MT::Entry->load({blog_id => $blog_id, author_id => $author_id, status => MT::Entry::FUTURE()}); if (defined($MT::PluginActions{'blog'})) { $param{plugin_action_loop} = $MT::PluginActions{'blog'}; } $param{blog_description} = $blog->description; $param{welcome} = $blog->welcome_msg; $param{num_entries} = MT::Entry->count({ blog_id => $blog_id }); $param{num_comments} = MT::Comment->count({ blog_id => $blog_id }); $param{num_authors} = 0; $param{has_edit_access} = $perms->can_post || $perms->can_edit_all_posts; $iter = MT::Permission->load_iter({ blog_id => $blog_id }); while (my $p = $iter->()) { $param{num_authors}++ if $p->can_post; } if ($blog->junk_folder_expiry) { $app->expire_junk($blog); } $app->build_page('menu.tmpl', \%param); } my %API = ( author => 'MT::Author', commenter => 'MT::Author', comment => 'MT::Comment', entry => 'MT::Entry', template => 'MT::Template', blog => 'MT::Blog', notification => 'MT::Notification', templatemap => 'MT::TemplateMap', category => 'MT::Category', banlist => 'MT::IPBanList', ping => 'MT::TBPing', ping_cat => 'MT::TBPing', log => 'MT::Log', ); sub edit_object { my $app = shift; my %param = $_[0] ? %{ $_[0] } : (); my $q = $app->param; my $type = $q->param('_type'); return unless $API{$type}; my $blog_id = $q->param('blog_id'); my $id = $q->param('id'); my $perms = $app->{perms}; my $author = $app->user; my $cfg = $app->config; $param{styles} = ''; return $app->error($app->translate("No permissions")) if !$perms && $id && $type ne 'author'; MT->_register_core_callbacks({ CMSViewPermissionFilter_blog => sub { my ($eh, $app, $id) = @_; if ( ($id && !$app->{perms}->can_edit_config) || (!$id && !$app->user->can_create_blog)) { return 0; } 1; }, CMSViewPermissionFilter_template => sub { my ($eh, $app, $id) = @_; return !$id || $app->{perms}->can_edit_templates; }, CMSViewPermissionFilter_entry => sub { my ($eh, $app, $id) = @_; if (!$id && !$app->param('is_bm') && !$app->{perms}->can_post) { return 0; } my $obj = MT::Entry->load($id, {cached_ok=>1}) if $id; if ($id && !$app->{perms}->can_edit_entry($obj, $app->user)) { return 0; } 1; }, CMSViewPermissionFilter_author => sub { my ($eh, $app, $id) = @_; return $id && ($app->user->id == $id); }, CMSViewPermissionFilter_category => sub { my ($eh, $app, $id) = @_; return $app->{perms}->can_edit_categories(); }, CMSViewPermissionFilter_commenter => sub { my $eh = shift; my ($app, $id) = @_; my $auth = MT::Author->load( { id => $id, type => MT::Author::COMMENTER }); $auth ? 1 : 0; }, CMSViewPermissionFilter_comment => sub { my $eh = shift; my ($app, $id, $objp) = @_; return 0 unless ($id); $objp->force() or return 0; require MT::Entry; my $entry = MT::Entry->load($objp->force()->entry_id, {cached_ok=>1}) or return 0; if (!($entry->author_id == $app->user->id || $app->{perms}->can_edit_all_posts)) { return 0; } 1; }, CMSViewPermissionFilter_ping => sub { my $eh = shift; my ($app, $id, $objp) = @_; $objp->force() or return 0; require MT::Trackback; my $tb = MT::Trackback->load($objp->force->tb_id, {cached_ok=>1}); if ($tb) { if ($tb->entry_id) { require MT::Entry; my $entry = MT::Entry->load($tb->entry_id, {cached_ok=>1}); return ($entry->author_id == $app->user->id || $app->{perms}->can_edit_all_posts); } elsif ($tb->category_id) { require MT::Category; my $cat = MT::Category->load($tb->category_id,{cached_ok=>1}); return $cat && $app->{perms}->can_edit_categories; } } else { return 0; # no TrackBack center--no edit } } }); my $class = $app->_load_driver_for($type) or return; my $cols = $class->column_names; require MT::Promise; my $obj_promise = MT::Promise::delay(sub { return $class->load($id) || undef; }); if (!$author->is_superuser) { MT->run_callbacks('CMSViewPermissionFilter_' . $type, $app, $id, $obj_promise) || return $app->error($app->translate("Permission denied. ") . MT->errstr()); } my $obj; my $blog; require MT::Blog; if ($blog_id) { $blog = MT::Blog->load($blog_id, {cached_ok=>1}); } if ($id) { # object exists, we're just editing it. # Stash the object itself so we don't have to keep forcing the promise $obj = $obj_promise->force() or return $app->error($app->translate("Load failed: [_1]", $class->errstr || "(no reason given)")); # Populate the param hash with the object's own values for my $col (@$cols) { $param{$col} = defined $q->param($col) ? $q->param($col) : $obj->$col(); } # Set type-specific display parameters if ($type eq 'entry') { $param{nav_entries} = 1; $param{entry_edit} = 1; $app->add_breadcrumb($app->translate('Entries'), $app->uri( 'mode' => 'list_entries', args => { blog_id => $blog_id })); $app->add_breadcrumb($obj->title || $app->translate('(untitled)')); ## Don't pass in author_id, because it will clash with the ## author_id parameter of the author currently logged in. delete $param{'author_id'}; delete $param{'category_id'}; if (my $cat = $obj->category) { $param{category_id} = $cat->id; } $blog_id = $obj->blog_id; my $status = $q->param('status') || $obj->status; $param{"status_" . MT::Entry::status_text($status)} = 1; $param{"allow_comments_" . ($q->param('allow_comments') || $obj->allow_comments || 0)} = 1; my $df = $q->param('created_on_manual') || format_ts("%Y-%m-%d %H:%M:%S", $obj->created_on); $param{'created_on_formatted'} = $df; my $comments = $obj->comments; my @c_data; my $i = 1; @$comments = grep { $_->junk_status > -1 } @$comments; @$comments = sort { $a->created_on cmp $b->created_on } @$comments; my $c_data = $app->build_comment_table( items => $comments, param => \%param ); $param{num_comment_rows} = @$c_data + 3; $param{num_comments} = @$c_data; $param{can_send_notifications} = $perms->can_send_notifications; ## Load list of trackback pings sent for this entry. require MT::Trackback; require MT::TBPing; my $tb = MT::Trackback->load({ entry_id => $obj->id }); my $tb_data; if ($tb) { my $iter = MT::TBPing->load_iter({ tb_id => $tb->id, 'junk_status' => [ 0, 1 ] }, { 'sort' => 'created_on', direction => 'descend', 'range_incl' => { 'junk_status' => 1 } }); $tb_data = $app->build_ping_table( iter => $iter, param => \%param ); } else { $tb_data = []; } $param{num_ping_rows} = @$tb_data + 3; $param{num_pings} = @$tb_data; $param{show_pings_tab} = @$tb_data || $obj->allow_pings; $param{show_comments_tab} = @$c_data || $obj->allow_comments; ## Load next and previous entries for next/previous links if (my $next = $obj->next) { $param{next_entry_id} = $next->id; } if (my $prev = $obj->previous) { $param{previous_entry_id} = $prev->id; } $param{ping_errors} = $q->param('ping_errors'); $param{can_view_log} = $app->user->can_view_log; $param{"tab_" . ($app->param('tab') || 'entry')} = 1; $param{entry_permalink} = $obj->permalink; $param{'mode_view_entry'} = 1; $param{'basename_old'} = $obj->basename; my $plugin_actions = $app->plugin_itemset_actions($type); $param{plugin_itemset_action_loop} = $plugin_actions if $plugin_actions; $param{plugin_itemset_action_loop} = [] if !$plugin_actions; # disabling for now since the existing core actions aren't terribly # useful on for edit entry screen. #my $core_actions = $app->core_itemset_actions($type); #$param{core_itemset_action_loop} = $core_actions # if $core_actions; my $core_actions; $param{has_itemset_actions} = ($plugin_actions || $core_actions) ? 1 : 0; } elsif ($type eq 'category') { $param{nav_categories} = 1; $app->add_breadcrumb($app->translate('Categories'), $app->uri( 'mode' => 'list_cat', args => { blog_id => $obj->blog_id })); $app->add_breadcrumb($obj->label); require MT::Trackback; my $tb = MT::Trackback->load({ category_id => $obj->id }); if ($tb) { my $path = $app->config('CGIPath'); $path .= '/' unless $path =~ m!/$!; my $script = $app->config('TrackbackScript'); $param{tb_url} = $path . $script . '/' . $tb->id; if ($param{tb_passphrase} = $tb->passphrase) { $param{tb_url} .= '/' . encode_url($param{tb_passphrase}); } } } elsif ($type eq 'template') { $param{nav_templates} = 1; my $tab; if ($obj->type eq 'index') { $tab = 'index'; } elsif ($obj->type eq 'archive' || $obj->type eq 'individual' || $obj->type eq 'category') { $tab = 'archive'; } elsif ($obj->type eq 'custom') { $tab = 'module'; } else { $tab = 'system'; } $app->add_breadcrumb($app->translate('Templates'), $app->uri( 'mode' => 'list', args => { '_type' => 'template', 'blog_id' => $obj->blog_id, 'tab' => $tab })); $app->add_breadcrumb($obj->name); $blog_id = $obj->blog_id; $param{has_name} = $obj->type eq 'index' || $obj->type eq 'custom' || $obj->type eq 'archive' || $obj->type eq 'category' || $obj->type eq 'individual'; $param{has_outfile} = $obj->type eq 'index'; $param{has_rebuild} = $obj->type eq 'index'; $param{custom_dynamic} = ($blog->custom_dynamic_templates||"") eq 'custom'; $param{has_build_options} = ($param{custom_dynamic} || $param{has_rebuild}); $param{is_special} = $param{type} ne 'index' && $param{type} ne 'archive' && $param{type} ne 'category' && $param{type} ne 'individual'; $param{has_build_options} = $param{has_build_options} && $param{type} ne 'custom' && ! $param{is_special}; $param{rebuild_me} = defined $obj->rebuild_me ? $obj->rebuild_me : 1; $param{search_type} = $app->translate('Templates'); $param{object_type} = 'template'; } elsif ($type eq 'blog') { require MT::IPBanList; my $output = $param{output} || ''; $param{need_full_rebuild} = 1 if $q->param('need_full_rebuild'); $param{need_index_rebuild} = 1 if $q->param('need_index_rebuild'); $param{show_ip_info} = MT::IPBanList->count({'blog_id' => $id}); if ($output eq 'cfg_prefs.tmpl') { $app->add_breadcrumb($app->translate('General Settings')); $param{global_sanitize_spec} = $cfg->GlobalSanitizeSpec; $param{'sanitize_spec_' . ($obj->sanitize_spec ? 1 : 0)} = 1; $param{sanitize_spec_manual} = $obj->sanitize_spec if $obj->sanitize_spec; $param{words_in_excerpt} = 40 unless defined $param{words_in_excerpt} && $param{words_in_excerpt} ne ''; $param{'sort_order_comments_' . ($obj->sort_order_comments || 0)} = 1; $param{'sort_order_posts_' . ($obj->sort_order_posts || 0)} = 1; my $lang = $obj->language || 'en'; $lang = 'en' if lc($lang) eq 'en-us' || lc($lang) eq 'en_us'; $param{'language_' . $lang} = 1; if ($obj->cc_license) { $param{cc_license_name} = MT::Util::cc_name($obj->cc_license); $param{cc_license_image_url} = MT::Util::cc_image($obj->cc_license); $param{cc_license_url} = MT::Util::cc_url($obj->cc_license); } my $entries_on_index = ($obj->entries_on_index || 0); if ($entries_on_index) { $param{'list_on_index'} = $entries_on_index; $param{'posts'} = 1; } else { $param{'list_on_index'} = ($obj->days_on_index || 0); $param{'days'} = 1; } } elsif ($output eq 'cfg_feedback.tmpl') { $app->add_breadcrumb($app->translate('Feedback Settings')); $param{system_allow_comments} = $cfg->AllowComments; $param{system_allow_pings} = $cfg->AllowPings; my $threshold = $obj->junk_score_threshold || 0; $threshold = '+' . $threshold if $threshold > 0; $param{junk_score_threshold} = $threshold; $param{tk_available} = eval { require MIME::Base64; 1; } && eval { require LWP::UserAgent; 1 }; $param{email_new_comments_1} = ($obj->email_new_comments || 0) == 1; $param{email_new_comments_2} = ($obj->email_new_comments || 0) == 2; $param{email_new_pings_1} = ($obj->email_new_pings || 0) == 1; $param{email_new_pings_2} = ($obj->email_new_pings || 0) == 2; $param{junk_folder_expiry} = $obj->junk_folder_expiry || 60; $param{auto_delete_junk} = $obj->junk_folder_expiry; $param{'auto_approve_commenters'} = !$obj->manual_approve_commenters; $param{identity_system} = $app->config('IdentitySystem'); # FIXME: use $app->uri, or whatever, below $param{handshake_return} = ($app->config('CGIPath') . $app->config('AdminScript')); $param{"moderate_comments"} = $obj->moderate_unreg_comments; $param{"moderate_comments_" . ($obj->moderate_unreg_comments || 0)} = 1; $param{"moderate_pings_" . ($obj->moderate_pings || 0 )} = 1; } elsif ($output eq 'cfg_entries.tmpl') { $app->add_breadcrumb($app->translate('New Entry Default Settings')); $param{system_allow_comments} = $cfg->AllowComments && ($blog->allow_reg_comments || $blog->allow_unreg_comments); $param{system_allow_pings} = $cfg->AllowPings && $blog->allow_pings; $param{system_allow_selected_pings} = $cfg->OutboundTrackbackLimit eq 'selected'; $param{system_allow_outbound_pings} = $cfg->OutboundTrackbackLimit eq 'any'; $param{system_allow_local_pings} = ($cfg->OutboundTrackbackLimit eq 'local') || ($cfg->OutboundTrackbackLimit eq 'any'); $param{'status_default_' . $obj->status_default} = 1 if $obj->status_default; $param{'allow_comments_default_' . ($obj->allow_comments_default||0)} = 1; } elsif ($output eq 'cfg_archives.tmpl') { $app->add_breadcrumb($app->translate('Publishing Settings')); if ($obj->column('archive_path') || $obj->column('archive_url')) { $param{enable_archive_paths} = 1; $param{archive_path} = $obj->column('archive_path'); $param{archive_url} = $obj->column('archive_url'); } else { $param{archive_path} = ''; $param{archive_url} = ''; } $param{'archive_type_preferred_' . $blog->archive_type_preferred} = 1 if $blog->archive_type_preferred; my $at = $blog->archive_type; if ($at && $at ne 'None') { my @at = split /,/, $at; for my $at (@at) { $param{'archive_type_' . $at} = 1; } } } elsif ($output eq 'list_plugin.tmpl') { $app->add_breadcrumb($app->translate('Plugin Settings')); my @data; my $next_is_first = 1; my $plugin_id = 1; my $icon = $app->static_path . 'images/plugin.gif'; for my $plugin (@MT::Plugins) { my $plugin_sig = $plugin->{plugin_sig}; my $profile = $MT::Plugins{$plugin_sig}; my ($snip_tmpl, $config_html); my %param; my $settings = $plugin->get_config_obj('blog:'. $blog_id); $plugin->load_config(\%param, 'blog:' . $blog_id); next unless $snip_tmpl = $plugin->config_template(\%param, 'blog'); my $plugin_name = remove_html($plugin->name()); $plugin->{description} = $plugin->{description}; (my $cgi_path = $app->config('AdminCGIPath') || $app->config('CGIPath')) =~ s|/$||; my $plugin_page = ($cgi_path . '/' . $plugin->envelope . '/' . $plugin->config_link()) if $plugin->{config_link}; my $tmpl; if (ref $snip_tmpl ne 'HTML::Template') { require HTML::Template; $tmpl = HTML::Template->new(scalarref => ref $snip_tmpl ? $snip_tmpl : \$snip_tmpl, die_on_bad_params => 0, loop_context_vars => 1); } else { $tmpl = $snip_tmpl; } $config_html = $app->build_page_in_mem($tmpl, \%param); my $row = { first => $plugin_id == 1, plugin_name => $plugin_name, plugin_page => $plugin_page, plugin_major => 1, plugin_icon => $icon, plugin_desc => $plugin->description(), plugin_version => $plugin->version(), plugin_author_name => $plugin->author_name(), plugin_author_link => $plugin->author_link(), plugin_plugin_link => $plugin->plugin_link(), plugin_full_path => $plugin->{full_path}, plugin_doc_link => $plugin->doc_link(), plugin_sig => $plugin->{plugin_sig}, plugin_key => $plugin->key(), plugin_config_link => $plugin->config_link(), plugin_config_html => $config_html, plugin_settings_id => $settings->id, plugin_id => $plugin_id++, }; $row->{plugin_tags} = listify($profile->{tags}) if $profile->{tags}; $row->{plugin_attributes} = listify($profile->{attributes}) if $profile->{attributes}; $row->{plugin_junk_filters} = listify($profile->{junk_filters}) if $profile->{junk_filters}; $row->{plugin_text_filters} = listify($profile->{text_filters}) if $profile->{text_filters}; if ($profile->{text_filters} || $profile->{junk_filters} || $profile->{tags} || $profile->{attributes}) { $row->{plugin_resources} = 1; } push @data, $row; } $param{can_config} = 1; $param{plugin_loop} = \@data; } else { $app->add_breadcrumb($app->translate('General Settings')); } (my $offset = $obj->server_offset) =~ s![-\.]!_!g; $offset =~ s!_00$!!; $param{'server_offset_' . $offset} = 1; if ($output eq 'cfg_feedback.tmpl' || $output eq 'cfg_entries.tmpl') { ## Load text filters. my $filters = MT->all_text_filters; my $default_entries = $obj->convert_paras; my $default_comments = $obj->convert_paras_comments; if ($default_entries eq '1') { $default_entries = '__default__'; } if ($default_comments eq '1') { $default_comments = '__default__'; } $param{text_filters} = []; $param{text_filters_comments} = []; for my $filter (keys %$filters) { my $row = { filter_key => $filter, filter_label => $filters->{$filter}{label}, }; my $rowc = { %$row }; $row->{filter_selected} = $filter eq $default_entries; $rowc->{filter_selected} = $filter eq $default_comments; push @{ $param{text_filters} }, $row; push @{ $param{text_filters_comments} }, $rowc; } $param{text_filters} = [ sort { $a->{filter_key} cmp $b->{filter_key} } @{ $param{text_filters} } ]; unshift @{ $param{text_filters} }, { filter_key => '0', filter_label => $app->translate('None'), filter_selected => !$default_entries, }; unshift @{ $param{text_filters_comments} }, { filter_key => '0', filter_label => $app->translate('None'), filter_selected => !$default_entries, }; } $param{nav_config} = 1; } elsif ($type eq 'ping') { $param{nav_trackbacks} = 1; $app->add_breadcrumb($app->translate('TrackBacks'), $app->uri('mode' => 'list_pings', args => { blog_id => $blog_id })); $app->add_breadcrumb('Edit TrackBack'); $param{approved} = $app->param('approved'); $param{unapproved} = $app->param('unapproved'); require MT::Trackback; if (my $tb = MT::Trackback->load($obj->tb_id, {cached_ok=>1})) { if ($tb->entry_id) { $param{entry_ping} = 1; require MT::Entry; if (my $entry = MT::Entry->load($tb->entry_id, {cached_ok=>1})) { $param{entry_title} = $entry->title; $param{entry_id} = $entry->id; } } elsif ($tb->category_id) { $param{category_ping} = 1; require MT::Category; if (my $cat = MT::Category->load($tb->category_id, {cached_ok=>1})) { $param{category_id} = $cat->id; $param{category_label} = $cat->label; } } } $param{"ping_approved"} = $obj->is_published or $param{"ping_pending"} = $obj->is_moderated or $param{"is_junk"} = $obj->is_junk; ## Load next and previous entries for next/previous links if (my $next = $obj->next) { $param{next_ping_id} = $next->id; } if (my $prev = $obj->previous) { $param{previous_ping_id} = $prev->id; } my $parent = $obj->parent; if (ref $parent eq 'MT::Entry') { if ($parent->status == MT::Entry::RELEASE()) { $param{entry_permalink} = $parent->permalink; } } if ($obj->junk_log) { $app->build_junk_table(param => \%param, object => $obj); } $param{created_on_time_formatted} = format_ts("%Y-%m-%d %H:%M:%S", $obj->created_on()); $param{created_on_day_formatted} = format_ts("%Y.%m.%d", $obj->created_on()); $param{search_type} = $app->translate('TrackBacks'); $param{object_type} = 'ping'; # since MT::App::build_page clobbers it: $param{source_blog_name} = $param{blog_name}; } elsif ($type eq 'comment') { $param{nav_comments} = 1; $app->add_breadcrumb($app->translate('Comments'), $app->uri('mode' => 'list_comments', args => { blog_id => $blog_id })); $app->add_breadcrumb($app->translate('Edit Comment')); if (my $entry = $obj->entry) { $param{entry_title} = $entry->title || $entry->text || ""; $param{entry_title} = substr($param{entry_title}, 0, 25) . '...' if $param{entry_title} && $param{entry_title} =~ m(\S{25,}); $param{entry_permalink} = $entry->permalink; } else { $param{no_entry} = 1; } $param{comment_approved} = $obj->is_published or $param{comment_pending} = $obj->is_moderated or $param{is_junk} = $obj->is_junk; $param{created_on_time_formatted} = format_ts("%Y-%m-%d %H:%M:%S", $obj->created_on()); $param{created_on_day_formatted} = format_ts("%Y.%m.%d", $obj->created_on()); $param{approved} = $app->param('approved'); $param{unapproved} = $app->param('unapproved'); $param{is_junk} = $obj->is_junk; ## Load next and previous entries for next/previous links if (my $next = $obj->next) { $param{next_comment_id} = $next->id; } if (my $prev = $obj->previous) { $param{previous_comment_id} = $prev->id; } if ($obj->junk_log) { $app->build_junk_table(param => \%param, object => $obj); } my $perm = MT::Permission->load({author_id => $obj->commenter_id, blog_id => $obj->blog_id}); if ($perm) { $param{commenter_approved} = ($perm->can_comment() && !$perm->can_not_comment() ? 1 : undef); $param{commenter_banned} = ($perm->can_not_comment() ? 1 : undef); } if ($obj->commenter_id) { if ($obj->email !~ m/@/) { # no email for this commenter $param{email_withheld} = 1; } } $param{invisible_unregistered} = !$obj->visible && !$obj->commenter_id; $param{search_type} = $app->translate('Comments'); $param{object_type} = 'comment'; } elsif ($type eq 'author') { # TBD: Populate permissions / blogs for this user # populate blog_loop, permission_loop # General permissions... $param{can_create_blog} = $obj->can_create_blog; $param{can_view_log} = $obj->can_view_log; require MT::Permission; my $all_perms = MT::Permission->perms; my @perms = MT::Permission->load( { 'author_id' => $id } ); my @blogs; my %perms; if (@perms) { foreach my $perm (@perms) { my $blog = MT::Blog->load( $perm->blog_id, {cached_ok=>1} ); push @blogs, $blog; $perms{$perm->blog_id} = $perm; } } #@blogs = sort { $_->name } @blogs; my @blog_loop; foreach my $blog (@blogs) { my $perm = $perms{$blog->id}; if ($perm && $perm->role_mask) { my @p_data; for my $ref (@$all_perms) { next if $ref->[1] =~ /comment/; my $meth = 'can_' . $ref->[1]; push @p_data, { have_access => $perm->$meth(), prompt => $app->translate($ref->[2]), blog_id => $blog->id, author_id => $id, mask => $ref->[0] }; } push @blog_loop, { blog_id => $blog->id, blog_name => $blog->name, permission_loop => \@p_data, }; } } @blog_loop = sort { $a->{blog_name} cmp $b->{blog_name} } @blog_loop; $param{blog_loop} = \@blog_loop; } elsif ($type eq 'commenter') { $app->add_breadcrumb($app->translate("Authenticated Commenters"), $app->uri(mode => 'list_commenters', args => { blog_id => $blog_id })); $app->add_breadcrumb($app->translate("Commenter Details")); my $tab = $q->param('tab') || 'commenter'; # populate the comments / junk comments for this user $param{'mode_view_commenter'} = 1; if ($tab eq 'commenter') { # we need itemset actions for commenters my $plugin_actions = $app->plugin_itemset_actions('commenter'); $param{plugin_itemset_action_loop} = $plugin_actions if $plugin_actions; #my $core_actions = $app->core_itemset_actions('commenter'); #$param{core_itemset_action_loop} = $core_actions # if $core_actions; # no native actions for this screen. $param{has_itemset_actions} = ($plugin_actions) ? 1 : 0; $param{is_email_hidden} = $obj->is_email_hidden; $param{status} = {PENDING => "pending", APPROVED => "approved", BANNED => "banned"}->{$obj->status($blog_id)}; $param{commenter_approved} = $obj->status($blog_id) == APPROVED; $param{commenter_banned} = $obj->status($blog_id) == BANNED; $param{profile_page} = $app->config('IdentityURL'); $param{profile_page} .= "/" unless $param{profile_page} =~ m|/$|; $param{profile_page} .= $obj->name(); } else { my $list_pref = $app->list_pref('comment'); %param = (%param, %$list_pref); my $limit = $list_pref->{rows}; my $offset = $q->param('offset') || 0; my (%terms, %arg); $terms{commenter_id} = $id; if ($tab eq 'comments') { $terms{junk_status} = [ 0, 1 ]; $arg{range_incl} = { junk_status => 1 }; } elsif ($tab eq 'junk') { $terms{junk_status} = -1; } $arg{offset} = $offset if $offset; require MT::Comment; my $iter = MT::Comment->load_iter(\%terms, \%arg); my $loop = $app->build_comment_table( iter => $iter, param => \%param ); if ($limit ne 'none') { ## We tried to load $limit + 1 entries above; if we actually got ## $limit + 1 back, we know we have another page of entries. my $have_next = @$loop > $limit; pop @$loop while @$loop > $limit; if ($offset) { $param{prev_offset} = 1; $param{prev_offset_val} = $offset - $limit; $param{prev_offset_val} = 0 if $param{prev_offset_val} < 0; } if ($have_next) { $param{next_offset} = 1; $param{next_offset_val} = $offset + $limit; } } $param{limit} = $limit; $param{offset} = $offset; $param{object_type} = 'comment'; $param{object_type_plural} = 'comments'; $param{search_type} = $app->translate('Comments'); $param{list_start} = $offset + 1; $param{list_end} = $offset + scalar @$loop; delete $arg{limit}; delete $arg{offset}; $param{list_total} = MT::Comment->count(\%terms, \%arg); if ($param{list_total}) { $param{next_max} = $param{list_total} - ($limit eq 'none' ? 0 : $limit); $param{next_max} = 0 if ($param{next_max} || 0) < $offset + 1; } # These are stubs for the commenter-scoped itemset references $param{plugin_itemset_action_loop} = []; $param{core_itemset_action_loop} = []; } $param{"tab_$tab"} = 1; } $param{new_object} = 0; } else { # object is new $param{new_object} = 1; for my $col (@$cols) { $param{$col} = $q->param($col); } if ($type eq 'entry') { $param{entry_edit} = 1; if ($blog_id) { $app->add_breadcrumb($app->translate('Entries'), $app->uri('mode' => 'list_entries', args => { blog_id => $blog_id })); $app->add_breadcrumb($app->translate('New Entry')); $param{nav_new_entry} = 1; } # (if there is no blog_id parameter, this is a # bookmarklet post and doesn't need breadcrumbs.) delete $param{'author_id'}; delete $param{'pinged_urls'}; my $blog_timezone = 0; if ($blog_id) { my $blog = MT::Blog->load($blog_id, {cached_ok=>1}); $blog_timezone = $blog->server_offset(); my $def_status = $q->param('status') || $blog->status_default; if ($def_status) { $param{"status_" . MT::Entry::status_text($def_status)} = 1; } $param{'allow_comments_' . (defined $q->param('allow_comments') ? $q->param('allow_comments') : $blog->allow_comments_default)} = 1; $param{allow_comments} = $blog->allow_comments_default unless defined $q->param('allow_comments'); $param{allow_pings} = $blog->allow_pings_default unless defined $q->param('allow_pings'); } require POSIX; $param{created_on_formatted} = $q->param('created_on_manual') || POSIX::strftime("%Y-%m-%d %H:%M:%S", offset_time_list(time, $blog)); if ($q->param('is_bm')) { $param{selected_text} = $param{text}; $param{text} = sprintf qq(%s\n\n%s), scalar $q->param('link_title'), scalar $q->param('link_href'), scalar $q->param('link_title'), $param{text}; my $show = $q->param('bm_show') || ''; if ($show =~ /\b(trackback|t)\b/) { $param{show_trackback} = 1; ## Now fetch original page and scan it for embedded ## TrackBack RDF tags. my $url = $q->param('link_href'); if (my $items = MT::Util::discover_tb($url, 1)) { if (@$items == 1) { $param{to_ping_urls} = $items->[0]->{ping_url}; } else { $param{to_ping_url_loop} = $items; } } } # This is needed for the QuickPost entry screen. require MT::Permission; my $iter = MT::Permission->load_iter({ author_id => $app->user->id }); my @data; while (my $perms = $iter->()) { next unless $perms->can_post; my $blog = MT::Blog->load($perms->blog_id, {cached_ok=>1}); next unless $blog; push @data, { blog_id => $blog->id, blog_name => $blog->name, blog_convert_breaks => $blog->convert_paras, blog_status => $blog->status_default, blog_allow_comments => $blog->allow_comments_default, blog_allow_pings => $blog->allow_pings_default, blog_basename_limit => $blog->basename_limit || 30 }; # populate category $param{avail_blogs}{$blog->id} = 1; } @data = sort { $a->{blog_name} cmp $b->{blog_name} } @data; $param{blog_loop} = \@data; } } elsif ($type eq 'author') { require MT::Permission; my @perms = MT::Permission->load({'author_id'=>$app->user->id }); my @blogs; if (@perms) { foreach my $perm (@perms) { next unless $perm->can_administer_blog; my $blog = MT::Blog->load( $perm->blog_id, {cached_ok=>1} ); my $row = { blog_id => $blog->id, blog_name => $blog->name }; if ($param{checked_blog_ids} && $param{checked_blog_ids}{$blog->id}) { $row->{is_checked} = 1; } push @blogs, $row; } } @blogs = sort { $a->{blog_name} cmp $b->{blog_name} } @blogs; $param{blog_loop} = \@blogs; } elsif ($type eq 'template') { my $template_type = $q->param('type') || return $app->errtrans("Create template requires type"); $param{nav_templates} = 1; my $tab; if ($template_type eq 'index') { $tab = 'index'; } elsif ($template_type eq 'archive' || $template_type eq 'individual' || $template_type eq 'category') { $tab = 'archive'; } elsif ($template_type eq 'custom') { $tab = 'module'; } else { $tab = 'system'; } $app->add_breadcrumb($app->translate('Templates'), $app->uri('mode' => 'list', args => { '_type' => 'template', blog_id => $blog->id, 'tab' => $tab })); $app->add_breadcrumb($app->translate('New Template')); $param{has_name} = $template_type eq 'index' || $template_type eq 'custom' || $template_type eq 'archive' || $template_type eq 'category' || $template_type eq 'individual'; $param{has_outfile} = $template_type eq 'index'; $param{has_rebuild} = $template_type eq 'index'; $param{custom_dynamic} = $blog->custom_dynamic_templates eq 'custom'; $param{has_build_options} = $blog->custom_dynamic_templates eq 'custom' || $param{has_rebuild}; $param{is_special} = $param{type} ne 'index' && $param{type} ne 'archive' && $param{type} ne 'category' && $param{type} ne 'individual'; $param{has_build_options} = $param{has_build_options} && $param{type} ne 'custom' && !$param{is_special};; $param{rebuild_me} = 1; } elsif ($type eq 'blog') { $app->add_breadcrumb($app->translate('New Weblog')); $param{server_offset_0} = 1; } } # Regardless of whether the obj is new, load data into $param if ($type eq 'entry') { ## Load categories and process into loop for category pull-down. require MT::Placement; my $cat_id = $param{category_id}; my $depth = 0; my %places; if ($id) { my @places = MT::Placement->load({ entry_id => $id, is_primary => 0}); %places = map { $_->category_id => 1 } @places; } $param{reedit} = 1 if $q->param('reedit'); if (!$q->param('is_bm')) { my $data = $app->_build_category_list($blog_id, undef, 1); foreach (@$data) { next unless exists $_->{category_id}; $_->{category_is_primary} = $cat_id && $cat_id == $_->{category_id}; $param{selected_category} = $_->{category_id} if $_->{category_is_primary}; if ($param{reedit}) { $_->{category_is_selected} = $q->param('add_category_id_' . $_->{category_id}) || $_->{category_is_primary};; } else { $_->{category_is_selected} = exists $places{$_->{category_id}} || $_->{category_is_primary}; } } my $top = { category_id => '', category_label => $app->translate('Select') }; $top->{category_is_selected} = 1 unless $cat_id; $param{category_loop} = [ $top, @$data ]; $param{have_multiple_categories} = scalar @$data > 1; $param{add_category_loop} = $data; } if ($blog) { $param{basename_limit} = $blog->basename_limit || 30; # FIXME } ## Now load user's preferences and customization for new/edit ## entry page. if ($perms) { my $prefs = $perms->entry_prefs || 'Advanced|Bottom'; ($prefs, my($pos)) = split /\|/, $prefs; if ($prefs eq 'Basic') { $param{'disp_prefs_' . $prefs} = 1; $param{'disp_prefs_show_body'} = 1; } elsif ($prefs eq 'Advanced') { my @all = qw( category body extended excerpt convert_breaks allow_comments authored_on allow_pings ping_urls basename ); for my $p (@all) { $param{'disp_prefs_show_' . $p} = 1; } } else { my @p = split /,/, $prefs; for my $p (@p) { $param{'disp_prefs_show_' . $p} = 1; } } if ($pos eq 'Both') { $param{'position_buttons_top'} = 1; $param{'position_buttons_bottom'} = 1; $param{'position_buttons_both'} = 1; } else { $param{'position_buttons_' . $pos} = 1; } $param{disp_prefs_bar_colspan} = $param{new_object} ? 1 : 2; } ## Load text filters. my %entry_filters; if (defined(my $filter = $q->param('convert_breaks'))) { $entry_filters{$filter} = 1; } elsif ($obj) { %entry_filters = map { $_ => 1 } @{ $obj->text_filters }; } else { my $blog = MT::Blog->load($blog_id, {cached_ok=>1}); my $cb = $blog->convert_paras; $cb = '__default__' if $cb eq '1'; $entry_filters{$cb} = 1; $param{convert_breaks} = $cb; } my $filters = MT->all_text_filters; $param{text_filters} = []; for my $filter (keys %$filters) { push @{ $param{text_filters} }, { filter_key => $filter, filter_label => $filters->{$filter}{label}, filter_selected => $entry_filters{$filter}, filter_docs => $filters->{$filter}{docs}, }; } $param{text_filters} = [ sort { $a->{filter_key} cmp $b->{filter_key} } @{ $param{text_filters} } ]; unshift @{ $param{text_filters} }, { filter_key => '0', filter_label => $app->translate('None'), filter_selected => (!keys %entry_filters), }; if ($blog) { my $ext = ($blog->file_extension || ''); $ext = '.' . $ext if $ext ne ''; $param{blog_file_extension} = $ext; } } elsif ($type eq 'template') { $param{"type_$param{type}"} = 1; } elsif ($type eq 'blog') { my $cwd = ''; if ($ENV{MOD_PERL}) { ## If mod_perl, just use the document root. $cwd = $app->{apache}->document_root; } else { $cwd = $app->mt_dir; } $cwd =~ s!([\\/])cgi-bin([\\/].*)?$!$1!; $cwd =~ s!([\\/])mt[\\/]?$!$1!i; if (!$param{site_path}) { $param{site_path} = $cwd; } #if (!$param{archive_path}) { # $param{archive_path} = File::Spec->catdir($cwd, 'archives'); #} if (!$param{site_url}) { $param{site_url} = $app->base . '/'; $param{site_url} =~ s!/cgi-bin(/.*)?$!/!; $param{site_url} =~ s!/mt/?$!/!i; } #if (!$param{archive_url}) { # $param{archive_url} = $param{site_url} . 'archives/'; #} } elsif ($type eq 'author') { $app->add_breadcrumb($app->translate("Authors"), $app->uri(mode => 'list_authors')); if ($obj) { $app->add_breadcrumb($obj->name); } else { $app->add_breadcrumb($app->translate("Create New Author")); } my $langs = $app->supported_languages; my @data; my $preferred = $obj && $obj->preferred_language ? $obj->preferred_language : 'en-us'; $preferred = 'en-us' if (lc($preferred) eq 'en_us'); for my $tag (keys %$langs) { my $row = { l_tag => $tag, l_name => $app->translate($langs->{$tag}) }; $row->{l_selected} = 1 if $preferred eq $tag; push @data, $row; } $param{languages} = [ sort { $a->{l_name} cmp $b->{l_name} } @data ]; $param{'nav_authors'} = 1; } if (($q->param('msg')||"") eq 'nosuch') { $param{nosuch} = 1; } for my $p ($q->param) { $param{$p} = $q->param($p) if $p =~ /^saved/; } if ($type eq 'comment') { my $cmntr = MT::Author->load({ id => $obj->commenter_id(), type => MT::Author::COMMENTER }); $param{email_hidden} = $cmntr && $cmntr->is_email_hidden(); $param{email} = $cmntr ? $cmntr->email : $obj->email; $param{comments_script_uri} = $app->config('CommentScript'); if ($cmntr) { $param{profile_page} = $app->config('IdentityURL'); $param{profile_page} .= "/" unless $param{profile_page} =~ m|/$|; $param{profile_page} .= $cmntr->name(); } } if ($type ne 'blog' && defined($MT::PluginActions{$type})) { $param{plugin_action_loop} = $MT::PluginActions{$type}; } if ($q->param('is_bm')) { my $show = $q->param('bm_show') || ''; my %opts = ('c' => 'category', 't' => 'trackback', 'ap' => 'allow_pings', 'ac' => 'allow_comments', 'cb' => 'convert_breaks', 'e' => 'excerpt', 'k' => 'keywords', 'm' => 'text_more', 'b' => 'basename'); if ($show) { my @show = map "show_$_", split /,/, $show; @param{ @show } = (1) x @show; # map the shortened show options to the long names used in the # quick post template foreach (@show) { s/^show_//; $param{"show_" . $opts{$_}} = 1 if exists $opts{$_}; } } if ($show =~ /\b(category|c)\b/) { my @c_data; my $blog_loop = $param{blog_loop}; foreach my $blog (@$blog_loop) { my $blog_id = $blog->{blog_id}; my $blog_cats = $app->_build_category_list($blog_id); my $i = 0; for my $row (@$blog_cats) { $row->{category_blog_id} = $blog_id; $row->{category_index} = $i++; my $spacer = $row->{category_label_spacer} || ''; $spacer =~ s/\ /\\u00A0/g; $row->{category_label_js} = $spacer . encode_js($row->{category_label}); } push @c_data, @$blog_cats; $blog->{add_category_loop} = $blog_cats; } $param{category_loop} = \@c_data; } return $app->build_page("bm_entry.tmpl", \%param); } elsif ($param{output}) { return $app->build_page($param{output}, \%param); } else { return $app->build_page("edit_${type}.tmpl", \%param); } } sub build_junk_table { my $app = shift; my (%args) = @_; my $param = $args{param}; my $obj = $args{object}; if (defined $obj->junk_score) { $param->{junk_score} = ($obj->junk_score > 0 ? '+' : '') . $obj->junk_score; } my $log = $obj->junk_log || ''; my @log = split /[\r?\n]/, $log; my @junk; for (my $i = 0; $i < scalar(@log); $i++) { my $line = $log[$i]; $line =~ s/(^\s+|\s+$)//g; next unless $line; last if $line =~ m/^--->/; my ($test, $score, $log); ($test) = $line =~ m/^([^:]+?):/; if (defined $test) { ($score) = $test =~ m/\(([+-]?\d+?(?:\.\d*?)?)\)/; $test =~ s/\(.+\)//; } if (defined $score) { $score =~ s/\+//; $score .= '.0' unless $score =~ m/\./; $score = ($score > 0 ? '+' : '') . $score; } $log = $line; $log =~ s/^[^:]+:\s*//; for (my $j = $i + 1; $j < scalar(@log); $j++) { my $line = $log[$j]; if ($line =~ m/^\t+(.*)$/s) { $i = $j; $log .= "
" . $1; } else { last; } } push @junk, { test => $test, score => $score, log => $log }; } $param->{junk_log_loop} = \@junk; \@junk; } sub CMSSaveFilter_author { my ($eh, $app) = @_; my $name = $app->param('name'); if (defined $name) { $name =~ s/(^\s+|\s+$)//g; $app->param('name', $name); } return $eh->error($app->translate("Author requires username")) if ($name !~ /\w/); if (!$app->param('id')) { # it's a new object return $eh->error($app->translate("Author requires password")) if ($app->param('pass') !~ /\w/); return $eh->error($app->translate("Author requires password hint")) if ($app->param('hint') !~ /\w/); } return $eh->error(MT->translate("Email Address is required for password recovery")) unless $app->param('email'); 1; } sub CMSSaveFilter_template { 1; } sub CMSSaveFilter_notification { my $eh = shift; my ($app) = @_; my $email = lc $app->param('email'); $email =~ s/(^\s+|\s+$)//gs; my $blog_id = $app->param('blog_id'); if (!is_valid_email($email)) { return $eh->error($app->translate("The value you entered was not a valid email address")); } require MT::Notification; # duplicate check my $notification_iter = MT::Notification->load_iter({blog_id => $blog_id}); while (my $obj = $notification_iter->()) { if (lc($obj->email) eq $email) { return $eh->error($app->translate("The e-mail address you entered is already on the Notification List for this weblog.")); } } return 1; } sub CMSSaveFilter_banlist { my $eh = shift; my ($app) = @_; my $ip = $app->param('ip'); $ip =~ s/(^\s+|\s+$)//g; return $eh->error(MT->translate("You did not enter an IP address to ban.")) if ('' eq $ip); my $blog_id = $app->param('blog_id'); require MT::IPBanList; my $existing = MT::IPBanList->load({ 'ip' => $ip, 'blog_id' => $blog_id}); my $id = $app->param('id'); if ($existing && (!$id || $existing->id != $id)) { return $eh->error($app->translate("The IP you entered is already banned for this weblog.")); } return 1; } sub CMSSaveFilter_blog { my $eh = shift; my ($app) = @_; my $name = $app->param('name'); if (defined $name) { $name =~ s/(^\s+|\s+$)//g; $app->param('name', $name); } return $eh->error(MT->translate("You did not specify a weblog name.")) if (!$app->param('cfg_screen') && $app->param('name') eq ''); return $eh->error(MT->translate("Site URL must be an absolute URL.")) if ($app->param('cfg_screen') eq 'cfg_archives' && $app->param('site_url') !~ m.^http://.); require MT::Blog; return $eh->error(MT->translate("There is already a weblog by that name!")) if (grep { $_->id != $app->param('id')} MT::Blog->load({name => $name})); return 1; } sub CMSSaveFilter_category { my $eh = shift; my ($app) = @_; return $app->errtrans("The name '[_1]' is too long!", $app->param('label')) if (length($app->param('label')) > 100); return 1; } sub CMSPreSave_ping { my $eh = shift; my ($app, $obj) = @_; my $status = $app->param('status'); if ($status eq 'publish') { $obj->approve; } elsif ($status eq 'moderate') { $obj->moderate; $obj->junk_status(0); } elsif ($status eq 'junk') { $obj->junk; } return 1; } sub CMSPreSave_comment { my $eh = shift; my ($app, $obj) = @_; my $status = $app->param('status'); if ($status eq 'publish') { $obj->approve; } elsif ($status eq 'moderate') { $obj->moderate; $obj->junk_status(0); } elsif ($status eq 'junk') { $obj->junk; } return 1; } sub CMSPreSave_author { my $eh = shift; my ($app, $obj) = @_; # Authors should only be of type AUTHOR when created from # the CMS app; COMMENTERs are created from the Comments app. $obj->type(MT::Author::AUTHOR); my $pass = $app->param('pass'); if ($pass) { $obj->set_password($pass); } ## If this is an author editing his/her profile, $id will be ## some defined value; if so we should update the author's ## cookie to reflect any changes made to username and password. ## Otherwise, this is a new user, and we shouldn't update the ## cookie. if ($obj->id) { $app->start_session; } else { $obj->created_by($app->user->id); } 1; } sub CMSPreSave_template { my $eh = shift; my ($app, $obj) = @_; $obj->rebuild_me(0) unless $app->param('rebuild_me'); # (this is to hack around browsers' unwillingness to send value # of a disabled checkbox.) require MT::Blog; my $blog = MT::Blog->load($obj->blog_id, {cached_ok=>1}); if ($blog->custom_dynamic_templates eq 'custom') { $obj->build_dynamic(0) unless $app->param('build_dynamic'); } elsif ($blog->custom_dynamic_templates eq 'archives') { $obj->build_dynamic($obj->type eq 'archive' || $obj->type eq 'category' || $obj->type eq 'individual' || 0); } else { $obj->build_dynamic(0) unless $obj->build_dynamic; } ## Strip linefeed characters. (my $text = $obj->text) =~ tr/\r//d; $obj->text($text); 1; } sub init_blog { my ($obj, $preferred_lang) = @_; $obj->set_defaults(); $obj->language($preferred_lang || 'en'); } sub CMSPreSave_blog { my $eh = shift; my ($app, $obj) = @_; if (!$app->param('overlay') && $app->param('cfg_screen') ) { # checkbox options have to be blanked if they aren't # passed. my $screen = $app->param('cfg_screen'); my @fields; if ($screen eq 'cfg_prefs') { @fields = qw( old_style_archive_links ping_weblogs ping_blogs ping_technorati autodiscover_links ); } elsif ($screen eq 'cfg_entries') { @fields = qw( ping_blogs ping_weblogs ping_technorati allow_comments_default allow_pings_default autodiscover_links internal_autodiscovery ); } elsif ($screen eq 'cfg_archives') { @fields = qw(site_url site_path archive_type_preferred file_extension); } elsif ($screen eq 'cfg_templatemaps') { } elsif ($screen eq 'cfg_feedback') { @fields = qw( allow_pings require_comment_emails allow_comment_html autolink_urls moderate_pings ); } elsif ($screen eq 'cfg_plugins') { } for my $cb (@fields) { $obj->$cb(0) if !defined $app->param($cb); } if ($screen eq 'cfg_feedback') { # value for comments: 1 == Accept from anyone # 2 == Accept authenticated only # 0 == No comments my $comments = $app->param('allow_comments'); if ($comments == 1) { $obj->allow_unreg_comments(1); $obj->allow_reg_comments(1); } elsif ($comments == 2) { $obj->allow_unreg_comments(0); $obj->allow_reg_comments(1); } elsif ($comments == 0) { $obj->allow_unreg_comments(0); $obj->allow_reg_comments(0); } $obj->require_comment_emails($app->param('require_email_address')); $obj->moderate_unreg_comments($app->param('moderate_comments')); my $pings = $app->param('allow_pings'); if ($pings) { $obj->moderate_pings($app->param('moderate_pings')); } else { $obj->moderate_pings(1); $obj->email_new_pings(1); } my $threshold = $app->param('junk_score_threshold'); $threshold =~ s/\+//; $threshold ||= 0; $obj->junk_score_threshold($threshold); my $expiry = $app->param('junk_folder_expiry') || 0; $obj->junk_folder_expiry($expiry); my $tok = ''; ($tok = $obj->remote_auth_token) =~ s/\s//g; $obj->remote_auth_token($tok); $obj->junk_folder_expiry(0) unless $app->param('auto_delete_junk'); } elsif ($screen eq 'cfg_entries') { $obj->basename_limit(15) if $obj->basename_limit < 15; # 15 is the *minimum* $obj->basename_limit(250) if $obj->basename_limit > 250; # 15 is the *maximum* } elsif ($screen eq 'cfg_prefs') { if ($app->param('days_or_posts') eq 'days') { $obj->days_on_index($app->param('list_on_index')); $obj->entries_on_index(0); } else { $obj->entries_on_index($app->param('list_on_index')); $obj->days_on_index(0); } } elsif ($screen eq 'cfg_archives') { if (my $dcty = $app->param('dynamicity')) { $obj->custom_dynamic_templates($dcty); } if (!$app->param('enable_archive_paths')) { $obj->archive_url(''); $obj->archive_path(''); } } } else { #$obj->is_dynamic(0) unless defined $app->{query}->param('is_dynamic'); } if (($obj->sanitize_spec || '') eq '1') { $obj->sanitize_spec(scalar $app->param('sanitize_spec_manual')); } ## If this is a new blog, set the preferences and archive ## settings to the defaults. if (!$obj->id) { init_blog($obj, $app->user->preferred_language); } 1; } sub CMSPreSave_category { my $eh = shift; my ($app, $obj) = @_; $obj->category_parent(0) if !defined $app->param('category_parent'); $obj->allow_pings(0) if !defined $app->param('allow_pings'); if (defined(my $pass = $app->param('tb_passphrase'))) { $obj->{__tb_passphrase} = $pass; } my @siblings = MT::Category->load({ parent => $obj->parent, blog_id => $obj->blog_id }); foreach (@siblings) { next if $_->id == $obj->id; return $app->errtrans("No categories with the same name can have the same parent") if $_->label eq $obj->label; } 1; } sub CMSPreSave_entry { my $eh = shift; my ($app, $obj) = @_; $obj->discover_tb_from_entry(); 1; } sub CMSPostSave_blog { my $eh = shift; my ($app, $obj, $original) = @_; my $screen = $app->param('cfg_screen') || ''; if ($screen eq 'cfg_archives') { if (my $dcty = $app->param('dynamicity')) { $app->update_dynamicity($obj); } $app->cfg_archives_save($obj); } if (!$original->id) { # If the object is new, the "orignal" was blank ## If this is a new blog, we need to set up a permissions ## record for the existing user. require MT::Permission; my $perms = MT::Permission->new; $perms->author_id($app->user->id); $perms->blog_id($obj->id); $perms->set_full_permissions; $perms->save; ## Load default templates into new blog database. my $tmpl_list; eval { $tmpl_list = require 'MT/default-templates.pl' }; warn $app->errtrans("Can't find default template list; where is " . "'default-templates.pl'?"), return if $@ || !$tmpl_list || ref($tmpl_list) ne 'ARRAY' ||!@$tmpl_list; require MT::Template; my @arch_tmpl; for my $val (@$tmpl_list) { $val->{name} = $app->translate($val->{name}); $val->{text} = $app->translate_templatized($val->{text}); my $tmpl = MT::Template->new; $tmpl->set_values($val); $tmpl->build_dynamic(0) unless $tmpl->build_dynamic(); $tmpl->blog_id($obj->id); $tmpl->save or return $app->errtrans( "Populating blog with default templates failed: [_1]", $tmpl->errstr); if ($val->{type} eq 'archive' || $val->{type} eq 'category' || $val->{type} eq 'individual') { push @arch_tmpl, $tmpl; } } ## Set up mappings from new templates to archive types. for my $tmpl (@arch_tmpl) { my(@at); if ($tmpl->type eq 'archive') { @at = qw( Daily Weekly Monthly ); } elsif ($tmpl->type eq 'category') { @at = qw( Category ); } elsif ($tmpl->type eq 'individual') { @at = qw( Individual ); } require MT::TemplateMap; for my $at (@at) { my $map = MT::TemplateMap->new; $map->archive_type($at); $map->is_preferred(1); $map->template_id($tmpl->id); $map->blog_id($tmpl->blog_id); $map->save or return $app->errtrans("Setting up mappings failed: [_1]", $map->errstr); } } $app->log($app->translate("Weblog '[_1]' created by '[_2]' (user #[_3])", $obj->name, $app->user->name, $app->user->id)); } else { # if you've changed the comment configuration if ((grep { $original->column($_) ne $obj->column($_) } qw(allow_unreg_comments allow_reg_comments remote_auth_token))) { if (RegistrationAffectsArchives($obj->id,'Individual')) { $app->add_return_arg(need_full_rebuild => 1); } else { $app->add_return_arg(need_index_rebuild => 1); } } # if other settings were changed that would affect published pages: if (grep { $original->column($_) ne $obj->column($_) } qw(allow_pings allow_comment_html)) { $app->add_return_arg(need_full_rebuild => 1); } } 1; } sub RegistrationAffectsArchives { # :-P my ($blog_id, $archive_type) = @_; require MT::TemplateMap; require MT::Template; my @tms = MT::TemplateMap->load({archive_type => $archive_type, blog_id => $blog_id}); grep { $_->text =~ /MTIfRegistrationRequired|MTIfRegistrationNotRequired|MTIfRegistrationAllowed/ } map { MT::Template->load($_->template_id) } @tms; } sub CMSPostSave_author { my $eh = shift; my ($app, $obj, $original) = @_; if (!$original->id) { my $author_id = $obj->id; for my $blog_id ($app->param('add_to_blog')) { # FIXME: check for existing permission just in case my $pe = MT::Permission->new; $pe->blog_id($blog_id); $pe->author_id($author_id); # By default, a new author can post and comment $pe->can_post(1); $pe->can_comment(1); $pe->save; } } else { if ($app->user->id == $obj->id) { # re-save user cookie to avoid appearance of logging out $app->{author} = $obj; $app->start_session(); } } 1; } sub CMSPostSave_comment { my $eh = shift; my ($app, $obj, $original) = @_; if ($obj->visible || (($obj->visible || 0) != ($original->visible||0))) { $app->rebuild_entry(Entry => $obj->entry_id, BuildIndexes => 1); } 1; } sub CMSPostSave_ping { my $eh = shift; my ($app, $obj, $original) = @_; require MT::Trackback; require MT::Entry; require MT::Category; if (my $tb = MT::Trackback->load($obj->tb_id, {cached_ok=>1})) { my ($entry, $cat); if ($tb->entry_id && ($entry = MT::Entry->load($tb->entry_id, {cached_ok=>1}))) { if ($obj->visible || (($obj->visible || 0) != ($original->visible || 0))) { $app->rebuild_entry(Entry => $entry, BuildIndexes => 1); } } elsif ($tb->category_id && ($cat = MT::Category->load($tb->category_id, {cached_ok=>1}))) { # FIXME: rebuild single category } } 1; } sub CMSPostSave_trackback { my $eh = shift; my ($app, $obj) = @_; $app->rebuild_entry(Entry => $obj->entry_id, BuildIndexes => 1); 1; } sub CMSPostSave_template { my $eh = shift; my ($app, $obj, $original) = @_; if ($obj->build_dynamic && !$original->build_dynamic) { if ($obj->type eq 'index') { $app->rebuild_indexes(BlogID => $obj->blog_id, Template => $obj); # XXXX } else { $app->rebuild(BlogID => $obj->blog_id, TemplateID => $obj->id); } } 1; } sub save_object { my $app = shift; my $q = $app->param; my $type = $q->param('_type'); my $id = $q->param('id'); $app->validate_magic() or return; my $author = $app->user; MT->_register_core_callbacks({ CMSSavePermissionFilter_blog => sub { my ($eh, $app, $id) = @_; return ($id && $app->{perms}->can_edit_config) || (!$id && $app->user->can_create_blog); }, CMSSavePermissionFilter_template => sub { my ($eh, $app, $id) = @_; return $app->{perms}->can_edit_templates; }, CMSSavePermissionFilter_category => sub { my ($eh, $app, $id) = @_; return $app->{perms}->can_edit_categories(); }, CMSSavePermissionFilter_notification => sub { my ($eh, $app, $id) = @_; return $app->{perms}->can_edit_notifications; }, CMSSavePermissionFilter_author => sub { my ($eh, $app, $id) = @_; if (!$id) { return $author->is_superuser; } else { return $author->id == $id; } }, CMSSavePermissionFilter_comment => sub { my ($eh, $app, $id) = @_; return 0 unless $id; # Can't create new comments here return 1 if $app->{perms}->can_edit_all_posts; if ($app->{perms}->can_post) { my $c = MT::Comment->load($id, {cached_ok=>1}); return ($c->entry->author_id == $app->user->id); } else { return 0; } }, CMSSavePermissionFilter_ping => sub { my ($eh, $app, $id) = @_; return 0 unless $id; # Can't create new pings here return 1 if $app->{perms}->can_edit_all_posts; my $p = MT::TBPing->load($id, {cached_ok=>1}); my $tbitem = $p->parent; if ($tbitem->isa('MT::Entry')) { return ($app->{perms}->can_post && ($tbitem->author_id == $app->user->id)); } else { return $app->{perms}->can_edit_categories; } }, CMSSavePermissionFilter_banlist => sub { my ($eh, $app, $id) = @_; $app->{perms}->can_edit_config; }, }) || die MT->errstr; # Check permissions my $perms = $app->{perms}; if (!$author->is_superuser) { if ($type ne 'author') { # for authors, blog-ctx $perms is not relevant return $app->errtrans("No permissions") if !$perms && $id; } MT->run_callbacks('CMSSavePermissionFilter_' . $type, $app, $id) || return $app->error($app->translate("Permission denied.") . MT->errstr()); } MT->_register_core_callbacks({"CMSSaveFilter_" . $type => \&{"CMSSaveFilter_" . $type}}) if $app->can("CMSSaveFilter_" . $type); my $filter_result = MT->run_callbacks('CMSSaveFilter_' . $type, $app); if (!$filter_result) { my %param; $param{error} = MT->errstr; $param{return_args} = $app->param('return_args'); if (($type eq 'notification') || ($type eq 'banlist')) { return $app->list_objects(\%param); } elsif ($app->param('cfg_screen') eq 'cfg_archives') { return $app->cfg_archives(\%param); } else { return $app->edit_object(\%param); } } if ($type eq 'author') { ## If we are saving an author profile, we need to do some ## password maintenance. First make sure that the two ## passwords match... my %param; if ($q->param('pass') ne $q->param('pass_verify')) { $param{error} = $app->translate('Passwords do not match.'); } else { if ($q->param('pass') && $id) { my $auth = MT::Author->load($id, {cached_ok=>1}); if (!$auth->is_valid_password($q->param('old_pass'))) { $param{error} = $app->translate('Failed to verify current password.'); } } } my $hint = $q->param('hint') || ''; $hint =~ s!^\s+|\s+$!!gs; unless ($hint) { $param{error} = $app->translate('Password hint is required.'); } if ($param{error}) { my $qual = $id ? '' : 'author_state_'; for my $f (qw( name nickname email url )) { $param{$qual . $f} = $q->param($f); } $param{checked_blog_ids} = { map { $_ => 1 } $q->param('add_to_blog') }; return $app->edit_object(\%param); } ## ... then check to make sure that the author isn't trying ## to change his/her username to one that already exists. my $name = $app->param('name'); my $existing = MT::Author->load({ name => $name, type => MT::Author::AUTHOR}); if ($existing && (!$q->param('id') || $existing->id != $q->param('id'))) { my %param = (error => $app->translate('An author by that name already exists.')); my $qual = $id ? '' : 'author_state_'; for my $f (qw( name email url )) { $param{$qual . $f} = $q->param($f); } $param{checked_blog_ids} = { map { $_ => 1 } $q->param('add_to_blog') }; return $app->edit_object(\%param); } } my $class = $app->_load_driver_for($type) or return; my($obj); if ($id) { $obj = $class->load($id); } else { $obj = $class->new; } my $original = $obj->clone(); my $names = $obj->column_names; my %values = map { $_ => (scalar $q->param($_)) } @$names; if ($type eq 'comment') { require MT::Entry; my $entry = MT::Entry->load($obj->entry_id, {cached_ok=>1}); if (!($entry->author_id == $app->user->id || $perms->can_edit_all_posts)) { return $app->error($app->translate("Permission denied.")); } } $obj->set_values(\%values); MT->_register_core_callbacks({"CMSPreSave_" . $type => \&{"CMSPreSave_" . $type}}) if $app->can("CMSPreSave_" . $type); MT->run_callbacks('CMSPreSave_' . $type, $app, $obj, $original) || return $app->error("Save failed: " . MT->errstr); # Done pre-processing the record-to-be-saved; now save it. $obj->touch() if ($type eq 'blog'); $obj->save or return $app->error($app->translate( "Saving object failed: [_1]", $obj->errstr)); MT->_register_core_callbacks({"CMSPostSave_" . $type => \&{"CMSPostSave_" . $type}}) if $app->can("CMSPostSave_" . $type); # Now post-process it. MT->run_callbacks('CMSPostSave_' . $type, $app, $obj, $original) or return $app->error(MT->errstr()); # Finally, decide where to go next, depending on the object type. my $blog_id = $q->param('blog_id'); if ($type eq 'blog') { $blog_id = $obj->id; } # TODO: convert this to use $app->call_return(); # then templates can determine the page flow. if ($type eq 'author' && !$id) { return $app->redirect($app->uri('mode' => 'edit_permissions', args => { 'author_id' => $obj->id })); } elsif ($type eq 'notification') { return $app->redirect($app->uri('mode' => 'list', args => { '_type' => 'notification', blog_id => $blog_id, saved => $obj->email })); } elsif ($type eq 'ping') { $app->add_return_arg('saved_ping' => 1); return $app->call_return; } elsif ($type eq 'comment') { $app->add_return_arg('saved_comment' => 1); return $app->call_return; } elsif (my $cfg_screen = $q->param('cfg_screen')) { if ($cfg_screen eq 'cfg_templatemaps') { # TBD $cfg_screen = 'cfg_archives'; } my $site_path = $obj->site_path; $app->add_return_arg( no_writedir => 1 ) unless -d $site_path && -w $site_path; $app->add_return_arg( saved => 1 ); return $app->call_return; } elsif ($type eq 'banlist') { return $app->redirect($app->uri('mode' => 'list', args => {'_type' => 'banlist', blog_id => $blog_id, saved => $obj->ip})); } elsif ($type eq 'template' && $q->param('rebuild')) { $q->param('type', 'index-' . $obj->id); $q->param('tmpl_id', $obj->id); $q->param('single_template', 1); return $app->start_rebuild_pages(); } else { return $app->redirect($app->uri('mode' => 'view', args => { '_type' => $type, id => $obj->id, blog_id => $blog_id, saved => 1})); } } sub list_objects { my $app = shift; my %param = $_[0] ? %{ $_[0] } : (); my $q = $app->param; my $type = $q->param('_type'); my $perms = $app->{perms}; return $app->error($app->translate("No permissions")) unless $type eq 'author' || $perms; if ($perms && (($type eq 'blog' && !$perms->can_edit_config) || ($type eq 'template' && !$perms->can_edit_templates) || ($type eq 'notification' && !$perms->can_edit_notifications) || ($type eq 'author' && !$perms->can_administer_blog))) { return $app->error($app->translate("Permission denied.")); } my $id = $q->param('id'); my $class = $app->_load_driver_for($type) or return; my $blog_id = $q->param('blog_id'); my $list_pref = $app->list_pref($type); my (%terms, %args); %param = ( %param, %$list_pref ); my $cols = $class->column_names; my $limit = $list_pref->{rows}; my $offset = $limit eq 'none' ? 0 : ($app->param('offset') || 0); for my $name (@$cols) { $terms{blog_id} = $blog_id, last if $name eq 'blog_id'; } if ($type eq 'author') { $terms{type} = AUTHOR; } if ($type eq 'notification') { $args{direction} = 'descend'; $args{offset} = $offset; $args{limit} = $limit + 1 if $limit ne 'none'; } elsif ($type eq 'banlist') { $limit = 0; } my $iter = $class->load_iter(\%terms, \%args); my(@data, @index_data, @custom_data, @archive_data, @system_data); my(%authors); require MT::Blog; my $blog = MT::Blog->load($blog_id, {cached_ok=>1}); while (my $obj = $iter->()) { my $row = $obj->column_values; if (my $ts = $obj->created_on) { $row->{created_on_formatted} = format_ts("%Y.%m.%d", $ts); $row->{created_on_time_formatted} = format_ts("%Y.%m.%d %H:%M:%S", $ts); $row->{created_on_relative} = relative_date($ts, time, $blog); } if ($type eq 'author') { $authors{ $obj->id } = $obj->name; if ($obj->id == $app->user->id) { $row->{is_me} = 1; } } if ($type eq 'template') { $row->{name} = '' if !defined $row->{name}; $row->{name} =~ s/^\s+|\s+$//g; $row->{name} = "(" . $app->translate("No Name") . ")" if $row->{name} eq ''; if ($obj->type eq 'index') { push @index_data, $row; $row->{rebuild_me} = defined $row->{rebuild_me} ? $row->{rebuild_me} : 1; } elsif ($obj->type eq 'custom') { push @custom_data, $row; } elsif ($obj->type eq 'archive' || $obj->type eq 'category' || $obj->type eq 'individual') { push @archive_data, $row; } else { $row->{name} = $app->translate($row->{name}); $row->{descriptio