# 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