@Ghazascanner
_2019runbot
Ghazascanner File Manager
server :Linux phosweb008 3.10.0-1160.el7.x86_64 #1 SMP Mon Oct 19 16:18:59 UTC 2020 x86_64
Current Path :
/
homepage
/
v-mt
/
lib
/
MT
/
App
/
Path :
Upload File :
New :
File
Dir
//homepage/v-mt/lib/MT/App/CMS.pm
# Movable Type (r) (C) 2001-2008 Six Apart, Ltd. All Rights Reserved. # This code cannot be redistributed without permission from www.sixapart.com. # For more information, consult your Movable Type license. # # $Id: CMS.pm 1302 2008-01-21 16:20:11Z bchoate $ package MT::App::CMS; use strict; use base qw( MT::App ); 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 archive_file_for is_url ); use MT::I18N qw( substr_text const length_text wrap_text encode_text break_up_text first_n_text guess_encoding ); use CGI; use constant LISTING_DATE_FORMAT => '%b %e, %Y'; use constant LISTING_DATETIME_FORMAT => '%b %e, %Y'; use constant LISTING_TIMESTAMP_FORMAT => "%Y-%m-%d %I:%M:%S%p"; sub id { 'cms' } sub init { my $app = shift; $app->SUPER::init(@_) or return; $app->{state_params} = [ '_type', 'id', 'tab', 'offset', 'filter', 'filter_val', 'blog_id', 'is_power_edit', 'filter_key' ]; $app->{template_dir} = 'cms'; $app->{plugin_template_path} = ''; $app->{is_admin} = 1; $app->{default_mode} = 'dashboard'; $app; } sub core_methods { my $app = shift; return { 'tools' => \&tools, 'dashboard' => \&dashboard, 'menu' => \&dashboard, 'admin' => \&dashboard, ## Generic handlers 'save' => \&save_object, 'edit' => \&edit_object, 'view' => \&edit_object, 'list' => \&list_objects, 'delete' => \&delete, 'search_replace' => \&search_replace, ## Edit methods 'edit_role' => \&edit_role, ## Listing methods 'list_ping' => \&list_pings, 'list_entry' => \&list_entries, 'list_template' => \&list_template, 'list_page' => \&list_pages, 'list_comment' => { handler => \&list_comments, permission => 'view_feedback', }, 'list_member' => \&list_member, 'list_user' => \&list_authors, 'list_author' => \&list_authors, 'list_commenter' => \&list_commenter, 'list_asset' => \&list_assets, 'list_blog' => \&list_blogs, 'list_category' => \&list_category, 'list_tag' => \&list_tag, 'list_association' => \&list_associations, 'list_role' => \&list_roles, 'asset_insert' => \&asset_insert, 'asset_userpic' => \&asset_userpic, 'save_commenter_perm' => \&save_commenter_perm, 'trust_commenter' => \&trust_commenter, 'ban_commenter' => \&ban_commenter, 'approve_item' => \&approve_item, 'unapprove_item' => \&unapprove_item, 'preview_entry' => \&preview_entry, ## Blog configuration screens 'cfg_archives' => \&cfg_archives, 'cfg_prefs' => \&cfg_prefs, 'cfg_plugins' => \&cfg_plugins, 'cfg_comments' => \&cfg_comments, 'cfg_trackbacks' => \&cfg_trackbacks, 'cfg_registration' => \&cfg_registration, 'cfg_spam' => \&cfg_spam, 'cfg_entry' => \&cfg_entry, 'cfg_web_services' => \&cfg_web_services, ## Save 'save_cat' => \&save_category, 'save_entries' => \&save_entries, 'save_pages' => \&save_pages, 'save_entry' => \&save_entry, 'save_role' => \&save_role, ## List actions 'enable_object' => \&enable_object, 'disable_object' => \&disable_object, 'list_action' => \&do_list_action, 'empty_junk' => \&empty_junk, 'handle_junk' => \&handle_junk, 'not_junk' => \¬_junk, '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, 'entry_notify' => \&entry_notify, 'send_notify' => \&send_notify, 'start_upload' => \&start_upload, 'upload_file' => \&upload_file, 'upload_userpic' => \&upload_userpic, 'complete_insert' => \&complete_insert, 'complete_upload' => \&complete_upload, 'start_upload_entry' => \&start_upload_entry, 'logout' => { code => \&logout, requires_login => 0, }, 'start_recover' => { code => \&start_recover, requires_login => 0, }, 'recover' => { code => \&recover_password, requires_login => 0, }, 'view_log' => \&view_log, 'list_log' => \&view_log, 'reset_log' => \&reset_log, 'export_log' => \&export_log, 'export_notification' => \&export_notification, 'start_import' => \&start_import, 'start_export' => \&start_export, 'export' => \&export, 'import' => \&do_import, 'pinged_urls' => \&pinged_urls, 'save_entry_prefs' => \&save_entry_prefs, 'save_favorite_blogs' => \&save_favorite_blogs, 'reg_file' => \®_file, 'reg_bm_js' => { code => \®_bm_js, requires_login => 0, }, 'folder_add' => \&category_add, 'category_add' => \&category_add, 'category_do_add' => \&category_do_add, 'cc_return' => \&cc_return, 'reset_blog_templates' => \&reset_blog_templates, 'handshake' => \&handshake, 'itemset_action' => \&do_list_action, 'page_action' => \&do_page_action, 'cfg_system' => \&cfg_system_general, 'cfg_system_users' => \&cfg_system_users, '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, 'save_cfg_system_general' => \&save_cfg_system_general, 'save_cfg_system_users' => \&save_cfg_system_users, 'update_welcome_message' => \&update_welcome_message, 'upgrade' => { code => \&upgrade, requires_login => 0, }, 'plugin_control' => \&plugin_control, 'recover_profile_password' => \&recover_profile_password, 'rename_tag' => \&rename_tag, 'remove_user_assoc' => \&remove_user_assoc, 'revoke_role' => \&revoke_role, 'grant_role' => \&grant_role, 'start_backup' => \&start_backup, 'start_restore' => \&start_restore, 'backup' => \&backup, 'backup_download' => \&backup_download, 'restore' => \&restore, 'restore_premature_cancel' => \&restore_premature_cancel, 'adjust_sitepath' => \&adjust_sitepath, 'system_check' => \&system_check, 'dialog_refresh_templates' => \&dialog_refresh_templates, 'refresh_all_templates' => \&refresh_all_templates, ## Comment Replies reply => \&reply, do_reply => \&do_reply, reply_preview => \&reply_preview, ## Dialogs 'dialog_restore_upload' => \&dialog_restore_upload, 'dialog_adjust_sitepath' => \&dialog_adjust_sitepath, 'dialog_post_comment' => \&dialog_post_comment, 'dialog_select_weblog' => \&dialog_select_weblog, 'dialog_select_sysadmin' => \&dialog_select_sysadmin, 'dialog_grant_role' => \&dialog_grant_role, ## AJAX handlers 'delete_map' => \&delete_map, 'add_map' => \&add_map, 'js_tag_check' => \&js_tag_check, 'js_tag_list' => \&js_tag_list, 'convert_to_html' => \&convert_to_html, 'update_list_prefs' => \&update_list_prefs, 'js_add_category' => \&js_add_category, 'remove_userpic' => \&remove_userpic, # declared in MT::App 'update_widget_prefs' => sub { return shift->update_widget_prefs(@_) }, 'js_recent_entries_for_tag' => \&js_recent_entries_for_tag, ## DEPRECATED ## 'list_pings' => \&list_pings, 'list_entries' => \&list_entries, 'list_pages' => \&list_pages, 'list_comments' => { handler => \&list_comments, permission => 'view_feedback', }, 'list_authors' => \&list_authors, 'list_assets' => \&list_assets, 'list_cat' => \&list_category, 'list_blogs' => \&list_blogs, 'list_associations' => \&list_associations, 'list_roles' => \&list_roles, }; } sub core_widgets { my $app = shift; return { new_install => { template => 'widget/new_install.tmpl', set => 'main', # forces this widget to the main group singular => 1, }, new_user => { template => 'widget/new_user.tmpl', set => 'main', # forces this widget to the main group singular => 1, }, new_version => { template => 'widget/new_version.tmpl', set => 'main', singular => 1, handler => \&new_version_widget, }, this_is_you => { label => 'This is You', template => 'widget/this_is_you.tmpl', handler => \&this_is_you_widget, set => 'sidebar', singular => 1, }, mt_shortcuts => { label => 'Handy Shortcuts', template => 'widget/mt_shortcuts.tmpl', singular => 1, set => 'sidebar', }, mt_news => { label => 'Movable Type News', template => 'widget/mt_news.tmpl', handler => \&mt_news_widget, singular => 1, set => 'sidebar', }, blog_stats => { label => 'Blog Stats', template => 'widget/blog_stats.tmpl', handler => \&mt_blog_stats_widget, singular => 1, set => 'main', }, }; } sub core_blog_stats_tabs { my $app = shift; return { entry => { label => 'Entries', template => 'widget/blog_stats_entry.tmpl', handler => \&mt_blog_stats_widget_entry_tab, stats => \&generate_dashboard_stats_entry_tab, }, comment => { label => 'Comments', template => 'widget/blog_stats_comment.tmpl', handler => \&mt_blog_stats_widget_comment_tab, stats => \&generate_dashboard_stats_comment_tab, }, tag_cloud => { label => 'Tag Cloud', template => 'widget/blog_stats_tag_cloud.tmpl', }, }; } sub core_page_actions { return { list_templates => { refresh_all_blog_templates => { label => "Refresh Blog Templates", dialog => 'dialog_refresh_templates', condition => sub { MT->app->blog, }, order => 1000, }, refresh_global_templates => { label => "Refresh Global Templates", code => sub { MT->app->refresh_all_templates(@_); }, condition => sub { ! MT->app->blog, }, order => 1000, continue_prompt => MT->translate('This action will restore your global templates to factory settings without creating a backup. Click OK to continue or Cancel to abort.'), }, }, }; } sub js_recent_entries_for_tag { my $app = shift; my $user = $app->user or return; my $tag_class = $app->model('tag') or return; my $objtag_class = $app->model('objecttag') or return; my $limit = $app->param('limit') || 10; my $obj_ds = $app->param('_type') || 'entry'; my $blog_id = $app->param('blog_id'); my $obj_class = $app->model($obj_ds) or return; my $tag_name = $app->param('tag') or return; if ( 'utf-8' ne lc( $app->config->PublishCharset) ) { $tag_name = MT::I18N::encode_text( $tag_name, 'utf-8', $app->config->PublishCharset ); } my $tag_obj = $tag_class->load( { name => $tag_name }, { binary => { name => 1 } } ); if ( !$tag_obj ) { return $app->json_error( $app->translate("Invalid request.") ); } my $tag_id = $tag_obj->id; my @entries = $obj_class->load( { ( $blog_id ? ( blog_id => $blog_id ) : () ), status => MT::Entry::RELEASE(), }, { sort => 'authored_on', direction => 'descend', limit => $limit, join => $objtag_class->join_on( 'object_id', { ( $blog_id ? ( blog_id => $blog_id ) : () ), tag_id => $tag_id, object_datasource => $obj_ds, } ), } ); my $count = $obj_class->tagged_count( $tag_id, { ( $blog_id ? ( blog_id => $blog_id ) : () ) } ); require MT::Template; require MT::Blog; my $tmpl = $app->load_tmpl('widget/blog_stats_recent_entries.tmpl'); my $ctx = $tmpl->context; $ctx->stash( 'blog', MT::Blog->load($blog_id) ) if $blog_id; $ctx->stash( 'entries', \@entries ); $tmpl->param( 'entry_count', scalar @entries ); $tmpl->param( 'script_url', $app->uri ); $tmpl->param( 'tag', $tag_name ); $tmpl->param( 'blog_id', $blog_id ) if $blog_id; my $editable = $app->user->is_superuser; if ( $blog_id && !$editable ) { $editable = $user->permissions($blog_id)->can_edit_all_posts; } $tmpl->param('editable', $editable); my $html = $app->build_page( $tmpl ); return $app->json_result( { html => $html } ); } sub js_add_category { my $app = shift; unless ( $app->validate_magic ) { return $app->json_error( $app->translate("Invalid request.") ); } my $user = $app->user; my $blog_id = $app->param('blog_id'); my $perms = $app->permissions; my $type = $app->param('_type') || 'category'; my $class = $app->model($type); if ( !$class ) { return $app->json_error( $app->translate("Invalid request.") ); } my $label = $app->param('label'); my $enc = $app->config->PublishCharset; # XMLHttpRequest always send text in UTF-8... right? if ( 'utf-8' ne lc($enc) ) { $label = MT::I18N::encode_text( $label, 'utf-8', $enc ); } my $basename = $app->param('basename'); if ( !defined($label) || ( $label =~ m/^\s*$/ ) ) { return $app->json_error( $app->translate("Invalid request.") ); } my $blog = $app->blog; if ( !$blog ) { return $app->json_error( $app->translate("Invalid request.") ); } my $parent; if ( my $parent_id = $app->param('parent') ) { if ( $parent_id != -1 ) { # special case for 'root' folder $parent = $class->load( { id => $parent_id, blog_id => $blog_id } ); if ( !$parent ) { return $app->json_error( $app->translate("Invalid request.") ); } } } my $obj = $class->new; my $original = $obj->clone; if ( !$app->run_callbacks( 'cms_save_permission.' . $type, $app, $obj, $original ) ) { return $app->json_error( $app->translate("Permission denied.") ); } $obj->label($label); $obj->basename($basename) if $basename; $obj->parent( $parent->id ) if $parent; $obj->blog_id($blog_id); $obj->author_id( $user->id ); $obj->created_by( $user->id ); if ( !$app->run_callbacks( 'cms_pre_save.' . $type, $app, $obj, $original ) ) { return $app->json_error( $app->errstr ); } $obj->save; $app->run_callbacks( 'cms_post_save.' . $type, $app, $obj, $original ); return $app->json_result( { id => $obj->id, basename => $obj->basename } ); } sub convert_to_html { my $app = shift; my $format = $app->param('format'); my $text = $app->param('text'); # XMLHttpRequest always send text in UTF-8... right? if ( defined $text ) { $text = encode_text($text, 'utf-8', $app->config->PublishCharset); } else { $text = '' ; } my $text_more = $app->param('text_more'); if ( defined $text_more ) { $text_more = encode_text($text_more, 'utf-8', $app->config->PublishCharset); } else { $text_more = '' ; } my $result = { text => $app->apply_text_filters( $text, [$format] ), text_more => $app->apply_text_filters( $text_more, [$format] ), format => $format, }; return $app->json_result($result); } sub tools { my $app = shift; $app->system_check; } sub system_check { my $app = shift; if ( my $blog_id = $app->param('blog_id') ) { return $app->redirect( $app->uri( 'mode' => 'view_log', args => { blog_id => $blog_id } ) ); } my %param; # licensed user count: someone who has logged in within 90 days my $sess_class = $app->model('session'); my $from = time - ( 60 * 60 * 24 * 90 + 60 * 60 * 24 ); $sess_class->remove( { kind => 'UA', start => [ undef, $from ] }, { range => { start => 1 } } ); $param{licensed_user_count} = $sess_class->count( { kind => 'UA' } ); my $author_class = $app->model('author'); $param{user_count} = $author_class->count( { type => MT::Author::AUTHOR() } ); # commeters: users with only comment permission and MT::Author::COMMENTER my $cmntrs = $author_class->count( { type => MT::Author::COMMENTER() } ); my @perms = $app->model('permission')->load( { permissions => "%'comment'%", blog_id => '0', }, { 'like' => { 'permissions' => 1 }, 'not' => { 'blog_id' => 1 }, } ); @perms = grep { $_->permissions =~ m/'comment'/ } @perms; $param{commenter_count} = scalar(@perms) + $cmntrs; $param{screen_id} = "system-check"; $param{syscheck_html} = $app->get_syscheck_content() || ''; $app->load_tmpl( 'system_check.tmpl', \%param ); } sub get_syscheck_content { my $app = shift; my $syscheck_url = $app->base . $app->mt_path . $app->config('CheckScript') . '?view=tools&version=' . MT->version_id; if ( $syscheck_url && $syscheck_url ne 'disable' ) { my $SYSCHECKCACHE_TIMEOUT = 60 * 60 * 24; my $sess_class = $app->model('session'); my ($syscheck_object) = (""); my $retries = 0; $syscheck_object = $sess_class->load( { id => 'SC' } ); if ( $syscheck_object && ( $syscheck_object->start() < ( time - $SYSCHECKCACHE_TIMEOUT ) ) ) { $syscheck_object->remove; $syscheck_object = undef; } return encode_text( $syscheck_object->data(), 'utf-8', undef ) if ($syscheck_object); my $ua = $app->new_ua({ timeout => 20 }); return unless $ua; $ua->max_size(undef) if $ua->can('max_size'); my $req = new HTTP::Request( GET => $syscheck_url ); my $resp = $ua->request($req); return unless $resp->is_success(); my $result = $resp->content(); if ($result) { require MT::Sanitize; # allowed html my $spec = '* style class id,ul,li,div,span,br,h2,h3,strong,code,blockquote,p'; $result = MT::Sanitize->sanitize( $result, $spec ); $syscheck_object = MT::Session->new(); $syscheck_object->set_values( { id => 'SC', kind => 'SC', start => time(), data => $result } ); $syscheck_object->save(); $result = encode_text( $result, 'utf-8', undef ); } return $result; } } sub init_plugins { my $app = shift; # This has to be done prior to plugin initialization since we # may have plugins that register themselves using some of the # older callback names. The callback aliases are declared # in init_core_callbacks. $app->init_core_callbacks(); $app->SUPER::init_plugins(@_); } sub init_request { my $app = shift; $app->SUPER::init_request(@_); $app->set_no_cache; $app->{requires_login} = 1 unless exists $app->{requires_login}; # by default, we require login my $mode = $app->mode; # Global 'blog_id' parameter check; if we get something # other than an integer, die if (my $blog_id = $app->param('blog_id')) { if ($blog_id ne int($blog_id)) { die $app->translate("Invalid request"); } } unless (defined $app->{upgrade_required}) { $app->{upgrade_required} = 0; if ( ( $mode ne 'logout' ) && ( $mode ne 'start_recover' ) && ( $mode ne 'recover' ) && ( $mode ne 'upgrade' ) ) { my $schema = $app->config('SchemaVersion'); my $version = $app->config('MTVersion'); if ( !$schema || ( $schema < $app->schema_version ) || ( ( !$version || ( $version < $app->version_number ) ) && $app->config->NotifyUpgrade ) ) { $app->{upgrade_required} = 1; } else { foreach my $plugin (@MT::Components) { if ( $plugin->needs_upgrade ) { $app->{upgrade_required} = 1; last; } } } } } if ($app->{upgrade_required}) { $app->{requires_login} = 0; $app->mode('upgrade'); } } sub core_list_actions { my $app = shift; return { 'entry' => { 'set_published' => { label => "Publish Entries", order => 100, code => \&publish_entries, permission => 'edit_all_posts,publish_post', condition => sub { return 0 if $app->mode eq 'view'; return $app->blog && $app->blog->site_path ? 1 : 0; } }, 'set_draft' => { label => "Unpublish Entries", order => 200, code => \&draft_entries, permission => 'edit_all_posts,publish_post', condition => sub { return 0 if $app->mode eq 'view'; return $app->blog && $app->blog->site_path ? 1 : 0; } }, 'add_tags' => { label => "Add Tags...", order => 300, code => \&add_tags_to_entries, input => 1, input_label => 'Tags to add to selected entries', permission => 'edit_all_posts', condition => sub { return $app->mode ne 'view'; } }, 'remove_tags' => { label => "Remove Tags...", order => 400, code => \&remove_tags_from_entries, input => 1, input_label => 'Tags to remove from selected entries', permission => 'edit_all_posts', condition => sub { return $app->mode ne 'view'; } }, 'open_batch_editor' => { label => "Batch Edit Entries", code => \&open_batch_editor, order => 500, condition => sub { return 0 if $app->mode eq 'view'; $app->param('blog_id') && ( $app->user->is_superuser() || $app->permissions->can_edit_all_posts ); }, }, }, 'page' => { 'set_published' => { label => "Publish Pages", order => 100, code => \&publish_entries, permission => 'manage_pages', condition => sub { return 0 if $app->mode eq 'view'; return $app->blog && $app->blog->site_path ? 1 : 0; }, }, 'set_draft' => { label => "Unpublish Pages", order => 200, code => \&draft_entries, permission => 'manage_pages', condition => sub { return 0 if $app->mode eq 'view'; return $app->blog && $app->blog->site_path ? 1 : 0; }, }, 'add_tags' => { label => "Add Tags...", order => 300, code => \&add_tags_to_entries, input => 1, input_label => 'Tags to add to selected pages', permission => 'manage_pages', condition => sub { return $app->mode ne 'view'; }, }, 'remove_tags' => { label => "Remove Tags...", order => 400, code => \&remove_tags_from_entries, input => 1, input_label => 'Tags to remove from selected pages', permission => 'manage_pages', condition => sub { return $app->mode ne 'view'; }, }, 'open_batch_editor' => { label => "Batch Edit Pages", code => \&open_batch_editor, order => 500, condition => sub { return 0 if $app->mode eq 'view'; $app->param('blog_id') && ( $app->user->is_superuser() || $app->permissions->can_manage_pages ); }, }, }, 'asset' => { 'add_tags' => { label => "Add Tags...", order => 100, code => \&add_tags_to_assets, input => 1, input_label => 'Tags to add to selected assets', permission => 'edit_assets', }, 'remove_tags' => { label => "Remove Tags...", order => 200, code => \&remove_tags_from_assets, input => 1, input_label => 'Tags to remove from selected assets', permission => 'edit_assets', }, }, 'ping' => { 'unapprove_ping' => { label => "Unpublish TrackBack(s)", order => 100, code => \&unapprove_item, permission => 'manage_feedback,publish_post', }, }, 'comment' => { 'unapprove_comment' => { label => "Unpublish Comment(s)", order => 100, code => \&unapprove_item, permission => 'manage_feedback,publish_post', condition => sub { return 1; }, }, 'trust_commenter' => { label => "Trust Commenter(s)", order => 200, code => \&trust_commenter_by_comment, permission => 'manage_feedback', }, 'untrust_commenter' => { label => "Untrust Commenter(s)", order => 300, code => \&untrust_commenter_by_comment, permission => 'manage_feedback', }, 'ban_commenter' => { label => "Ban Commenter(s)", order => 400, code => \&ban_commenter_by_comment, permission => 'manage_feedback', }, 'unban_commenter' => { label => "Unban Commenter(s)", order => 500, code => \&unban_commenter_by_comment, permission => 'manage_feedback', }, }, 'commenter' => { 'untrust' => { label => "Untrust Commenter(s)", order => 100, code => \&untrust_commenter, permission => 'manage_feedback', }, 'unban' => { label => "Unban Commenter(s)", order => 200, code => \&unban_commenter, permission => 'manage_feedback', }, }, 'author' => { 'recover_passwords' => { label => "Recover Password(s)", order => 100, continue_prompt => $app->translate("_WARNING_PASSWORD_RESET_MULTI"), code => \&recover_passwords, condition => sub { ( $app->user->is_superuser() && MT::Auth->can_recover_password ); }, }, 'delete_user' => { label => "Delete", order => 200, continue_prompt => $app->config->ExternalUserManagement ? $app->translate("_WARNING_DELETE_USER_EUM") : $app->translate("_WARNING_DELETE_USER"), code => \&delete, condition => sub { $app->user->is_superuser(); }, }, }, 'blog' => { refresh_blog_templates => { label => "Refresh Template(s)", code => sub { my $app = MT->app; $app->param('backup', 1); $app->refresh_all_templates(@_) }, }, }, 'template' => { refresh_tmpl_templates => { label => "Refresh Template(s)", code => sub { MT->app->refresh_individual_templates(@_) }, permission => 'edit_templates', }, publish_index_templates => { label => "Publish Template(s)", code => sub { MT->app->publish_index_templates(@_) }, permission => 'rebuild', condition => sub { my $app = MT->app; my $tmpl_type = $app->param('filter_key'); return $app->mode eq 'itemset_action' ? 1 : !$app->blog ? 0 : !$tmpl_type ? 0 : $tmpl_type eq 'index_templates' ? 1 : 0 ; }, }, }, }; } sub _entry_label { my $app = MT->instance; my $type = $app->param('type') || 'entry'; $app->model($type)->class_label_plural; } sub asset_list_filters { my $app = shift; my %filters; my $types = MT::Asset->class_labels; foreach my $type ( keys %$types ) { my $asset_type = $type; $asset_type =~ s/^asset\.//; $filters{$asset_type} = { label => sub { MT::Asset->class_handler($type)->class_label_plural }, handler => sub { my ( $terms, $args ) = @_; $terms->{class} = $asset_type eq 'asset' ? '*' : $asset_type; }, }; } my @types = sort { $filters{$a}{label} cmp $filters{$b}{label} } keys %filters; my $order = 100; foreach (@types) { $filters{$_}{order} = $order; $order += 100; } $filters{'asset'}{order} = 0; $filters{'asset'}{label} = "All Assets"; # labels are translated later # translate("All Assets"); return \%filters; } sub core_list_filters { my $app = shift; return { asset => sub { $app->asset_list_filters(@_) }, entry => { published => { label => sub { $app->translate( 'Published [_1]', _entry_label ); }, order => 100, handler => sub { my ( $terms, $args ) = @_; $terms->{status} = 2; }, }, unpublished => { label => sub { $app->translate( 'Unpublished [_1]', _entry_label ); }, order => 200, handler => sub { my ( $terms, $args ) = @_; $terms->{status} = 1; }, }, scheduled => { label => sub { $app->translate( 'Scheduled [_1]', _entry_label ); }, order => 300, handler => sub { my ( $terms, $args ) = @_; $terms->{status} = 4; }, }, my_posts => { label => sub { $app->translate( 'My [_1]', _entry_label ); }, order => 400, handler => sub { my ( $terms, $args ) = @_; $terms->{author_id} = $app->user->id; }, }, received_comments_in_last_7_days => { label => sub { $app->translate( '[_1] with comments in the last 7 days', _entry_label ); }, order => 500, handler => sub { my ( $terms, $args ) = @_; my $ts = time - 10 * 24 * 60 * 60; $ts = MT::Util::epoch2ts( MT->app->blog, $ts ); $args->{join} = MT::Comment->join_on( 'entry_id', { created_on => [ $ts, undef ], junk_status => [ 0, 1 ] }, { range_incl => { created_on => 1 }, unique => 1 } ); # Since we're selecting content from the mt_entry # table, but we want to sort by the joined # 'comment_created_on' column, we have to specify the # sort column as a reference and a full field name, # to prevent MT from adding a 'entry_' prefix to # the column name. $args->{sort} = \'comment_created_on'; $args->{direction} = 'descend'; }, }, _by_date => { label => sub { my $app = MT->instance; my $val = $app->param('filter_val'); my ( $from, $to ) = split /-/, $val; $from = undef unless $from =~ m/^\d{8}$/; $to = undef unless $to =~ m/^\d{8}$/; my $format = '%x'; $from = MT::Util::format_ts( $format, $from . '000000', undef, MT->current_language ) if $from; $to = MT::Util::format_ts( $format, $to . '000000', undef, MT->current_language ) if $to; my $label = _entry_label; if ( $from && $to ) { return $app->translate( '[_1] posted between [_2] and [_3]', $label, $from, $to ); } elsif ($from) { return $app->translate( "[_1] posted since [_2]", $label, $from ); } elsif ($to) { return $app->translate( "[_1] posted on or before [_2]", $label, $to ); } }, handler => sub { my ( $terms, $args ) = @_; my $val = $app->param('filter_val'); my ( $from, $to ) = split /-/, $val; $from = undef unless $from =~ m/^\d{8}$/; $from .= '000000'; $to = undef unless $to =~ m/^\d{8}$/; $to .= '235959'; $terms->{authored_on} = [ MT::Object::ts2db($from), MT::Object::ts2db($to) ]; $args->{range_incl}{authored_on} = 1; }, }, }, ping => { default => { label => 'Non-spam TrackBacks', order => 100, handler => sub { my ( $terms, $args ) = @_; $terms->{junk_status} = [ 0, 1 ]; }, }, my_posts => { label => 'TrackBacks on my entries', order => 200, handler => sub { my ( $terms, $args ) = @_; require MT::Entry; my $app = MT->instance; $terms->{junk_status} = [ 0, 1 ]; require MT::Trackback; $args->{join} = MT::Trackback->join_on( undef, { id => \'= tbping_tb_id', }, { join => MT::Entry->join_on( undef, { id => \'= trackback_entry_id', author_id => $app->user->id } ) }, ); }, }, published => { label => 'Published TrackBacks', order => 200, handler => sub { my ( $terms, $args ) = @_; $terms->{visible} = 1; }, }, unpublished => { label => 'Unpublished TrackBacks', order => 300, handler => sub { my ( $terms, $args ) = @_; $terms->{junk_status} = [ 0, 1 ]; $terms->{visible} = 0; }, }, spam => { label => 'TrackBacks marked as Spam', order => 400, handler => sub { my ( $terms, $args ) = @_; $terms->{junk_status} = -1; }, }, last_7_days => { label => 'All TrackBacks in the last 7 days', order => 700, handler => sub { my ( $terms, $args ) = @_; my $ts = time - 7 * 24 * 60 * 60; $ts = MT::Util::epoch2ts( MT->app->blog, $ts ); $terms->{created_on} = [ $ts, undef ]; $args->{range_incl}{created_on} = 1; $terms->{junk_status} = [ 0, 1 ]; }, }, }, comment => { default => { label => 'Non-spam Comments', order => 100, handler => sub { my ( $terms, $args ) = @_; $terms->{junk_status} = [ 0, 1 ]; }, }, my_posts => { label => 'Comments on my entries', order => 200, handler => sub { my ( $terms, $args ) = @_; require MT::Entry; my $app = MT->instance; $terms->{junk_status} = [ 0, 1 ]; # This join syntax employs a hack that allows us # to do joins on abitrary columns. Typically, # objectdriver joins are applied with the primary # key column of the driving table (here, # mt_comment.comment_id), but we actually want to # join to the mt_entry table where the entry_id # matches to mt_comment.comment_entry_id (not the # primary key). So by specifying NO 'join' column # (which is always compared with the primary key), # we specify the actual join conditions in the # terms. And using a reference for the # 'comment_entry_id' column name, to pass that # on directly to the SQL statement that is generated. $args->{join} = MT::Entry->join_on( undef, { id => \'= comment_entry_id', author_id => $app->user->id } ); }, }, unpublished => { label => 'Pending comments', order => 300, handler => sub { my ( $terms, $args ) = @_; $terms->{junk_status} = [ 0, 1 ]; $terms->{visible} = 0; }, }, spam => { label => 'Spam Comments', order => 400, handler => sub { my ( $terms, $args ) = @_; $terms->{junk_status} = -1; }, }, published => { label => 'Published comments', order => 500, handler => sub { my ( $terms, $args ) = @_; $terms->{visible} = 1; }, }, # my_comments => { # label => 'My comments', # order => 600, # handler => sub { # my ( $terms, $args ) = @_; # $terms->{commenter_id} = $app->user->id; # }, # }, last_7_days => { label => 'Comments in the last 7 days', order => 700, handler => sub { my ( $terms, $args ) = @_; my $ts = time - 7 * 24 * 60 * 60; $ts = MT::Util::epoch2ts( MT->app->blog, $ts ); $terms->{created_on} = [ $ts, undef ]; $args->{range_incl}{created_on} = 1; $terms->{junk_status} = [ 0, 1 ]; }, }, # last_24_hours => { # label => 'All comments in the last 24 hours', # order => 800, # handler => sub { # my ( $terms, $args ) = @_; # my $ts = time - 24 * 60 * 60; # $ts = MT::Util::epoch2ts( MT->app->blog, $ts ); # $terms->{created_on} = [ $ts, undef ]; # $args->{range_incl}{created_on} = 1; # $terms->{junk_status} = [ 0, 1 ]; # }, # }, _comments_by_user => { label => sub { my $app = MT->app; my $user_id = $app->param('filter_val'); my $user = MT::Author->load($user_id); require MT::Author; return $app->translate( "All comments by [_1] '[_2]'", ( $user->type == MT::Author::COMMENTER() ? $app->translate("Commenter") : $app->translate("Author") ), ( $user->nickname ? $user->nickname . ' (' . $user->name . ')' : $user->name ) ); }, handler => sub { my ( $terms, $args ) = @_; my $cmtr_id = int( MT->app->param('filter_val') ); $terms->{commenter_id} = $cmtr_id; }, }, _comments_by_entry => { label => sub { my $entry_id = MT->app->param('filter_val'); my $entry = MT::Entry->load($entry_id); return MT->translate( "All comments for [_1] '[_2]'", $entry->class_label, $entry->title ); }, handler => sub { my ( $terms, $args ) = @_; my $entry_id = int( MT->app->param('filter_val') ); $terms->{entry_id} = $entry_id; $terms->{junk_status} = [ 0, 1 ]; }, }, _by_date => { label => sub { my $app = MT->instance; my $val = $app->param('filter_val'); my ( $from, $to ) = split /-/, $val; $from = undef unless $from =~ m/^\d{8}$/; $to = undef unless $to =~ m/^\d{8}$/; my $format = '%x'; $from = MT::Util::format_ts( $format, $from . '000000', undef, MT->current_language ) if $from; $to = MT::Util::format_ts( $format, $to . '000000', undef, MT->current_language ) if $to; if ( $from && $to ) { return $app->translate( 'Comments posted between [_1] and [_2]', $from, $to ); } elsif ($from) { return $app->translate( "Comments posted since [_1]", $from ); } elsif ($to) { return $app->translate( "Comments posted on or before [_1]", $to ); } }, handler => sub { my ( $terms, $args ) = @_; my $val = $app->param('filter_val'); my ( $from, $to ) = split /-/, $val; $from = undef unless $from =~ m/^\d{8}$/; $from .= '000000'; $to = undef unless $to =~ m/^\d{8}$/; $to .= '235959'; $terms->{junk_status} = [ 0, 1 ]; $terms->{created_on} = [ MT::Object::ts2db($from), MT::Object::ts2db($to) ]; $args->{range_incl}{created_on} = 1; }, }, }, template => { index_templates => { label => "Index Templates", order => 100, handler => sub { my ( $terms, $args ) = @_; $terms->{type} = 'index'; }, condition => sub { $app->param('blog_id'); }, }, archive_templates => { label => "Archive Templates", order => 200, handler => sub { my ( $terms, $args ) = @_; $terms->{type} = [ 'individual', 'page', 'archive', 'category' ]; }, condition => sub { $app->param('blog_id'); }, }, module_templates => { label => "Template Modules", order => 300, handler => sub { my ($terms) = @_; $terms->{type} = 'custom'; }, }, email_templates => { label => "E-mail Templates", order => 400, handler => sub { my ($terms) = @_; $terms->{type} = 'email'; }, condition => sub { !$app->param('blog_id'); }, }, backup_templates => { label => "Backup Templates", order => 10000, handler => sub { my ($terms) = @_; $terms->{type} = 'backup'; }, }, system_templates => { label => "System Templates", order => 500, handler => sub { my ($terms) = @_; my $scope; my $set; if ( my $blog_id = $app->param('blog_id') ) { my $blog = $app->model('blog')->load($blog_id); $set = $blog->template_set; $scope .= 'system'; } else { $terms->{blog_id} = 0; $scope = 'global:system'; } my @tmpl_path = ( $set && ($set ne 'mt_blog')) ? ("template_sets", $set, 'templates', $scope) : ("default_templates", $scope); my $sys_tmpl = MT->registry(@tmpl_path) || {}; $terms->{type} = [ keys %$sys_tmpl ]; }, condition => sub { $app->param('blog_id'); }, }, }, tag => { entry => { label => 'Tags with entries', order => 100, }, page => { label => 'Tags with pages', order => 200, }, asset => { label => 'Tags with assets', order => 300, }, }, sys_user => { enabled => { label => 'Enabled Users', order => 100, handler => sub { my ($terms) = @_; $terms->{status} = 1; }, }, disabled => { label => "Disabled Users", order => 200, handler => sub { my ($terms) = @_; $terms->{status} = 2; }, }, pending => { label => "Pending Users", order => 300, handler => sub { my ($terms) = @_; $terms->{status} = 3; }, }, }, user => { author => { label => 'Authors', order => 100, handler => sub { my ($terms) = @_; require MT::Author; $terms->{type} = MT::Author::AUTHOR(); }, }, commenter => { label => 'Commenters', order => 200, handler => sub { my ($terms) = @_; require MT::Author; $terms->{type} = MT::Author::COMMENTER(); }, }, }, }; } sub core_menus { my $app = shift; return { 'create' => { label => "Create", order => 100, }, 'manage' => { label => "Manage", order => 200, }, 'design' => { label => "Design", order => 300, }, 'prefs' => { label => "Preferences", order => 400, }, 'tools' => { label => "Tools", order => 500, }, 'create:blog' => { label => "Blog", order => 100, view => "system", }, 'create:blog' => { label => "Blog", order => 200, view => "system", mode => "view", args => { _type => "blog" }, permission => "create_blog", }, 'create:user' => { label => "User", order => 200, view => "system", mode => "view", args => { _type => "author" }, permission => "administer", condition => sub { return !MT->config->ExternalUserManagement; }, }, 'create:entry' => { label => "Entry", order => 100, mode => 'view', args => { _type => 'entry' }, permission => 'create_post', view => "blog", }, 'create:page' => { label => "Page", order => 200, mode => 'view', args => { _type => 'page' }, permission => 'manage_pages', view => "blog", }, 'create:file' => { label => "Upload File", order => 300, dialog => 'start_upload', permission => 'upload,edit_assets', view => "blog", }, 'manage:blog' => { label => "Blogs", mode => "list_blog", order => 100, view => "system", }, 'manage:user' => { label => "Users", mode => "list_user", order => 200, permission => "administer", view => "system", }, 'manage:entry' => { label => "Entries", mode => 'list_entry', order => 1000, condition => sub { return 1 if $app->user->is_superuser; if ( $app->param('blog_id') ) { my $perms = $app->user->permissions($app->param('blog_id')); return 1 if $perms->can_create_post || $perms->can_publish_post || $perms->can_edit_all_posts; } else { require MT::Permission; my @blogs = map { $_->blog_id } grep { $_->can_create_post || $_->can_publish_post || $_->can_edit_all_posts } MT::Permission->load( { author_id => $app->user->id } ); return 1 if @blogs; } return 0; }, #permission => 'create_post,publish_post,edit_all_posts', }, 'manage:comment' => { label => "Comments", mode => 'list_comment', order => 2000, condition => sub { return 1 if $app->user->is_superuser; if ( $app->param('blog_id') ) { my $perms = $app->user->permissions($app->param('blog_id')); return 1 if $perms->can_create_post || $perms->can_manage_feedback || $perms->can_edit_all_posts || $perms->can_comment; } else { require MT::Permission; my @blogs = map { $_->blog_id } grep { $_->can_create_post || $_->can_manage_feedback || $_->can_edit_all_posts || $_->can_comment } MT::Permission->load( { author_id => $app->user->id } ); return 1 if @blogs; } return 0; }, #permission => 'create_post,edit_all_posts,manage_feedback,comment', }, 'manage:asset' => { label => "Assets", mode => 'list_asset', order => 3000, permission => 'edit_assets', }, 'manage:page' => { label => "Pages", mode => 'list_pages', order => 4000, permission => 'manage_pages', }, 'manage:ping' => { label => "TrackBacks", mode => 'list_pings', order => 5000, permission => 'create_post,edit_all_posts,manage_feedback', }, 'manage:category' => { label => "Categories", mode => 'list_cat', order => 6000, permission => 'edit_categories', view => "blog", }, 'manage:folder' => { label => "Folders", mode => 'list_cat', args => { _type => 'folder' }, order => 7000, permission => 'manage_pages', view => "blog", }, 'manage:tag' => { label => "Tags", mode => 'list_tag', order => 8000, permission => 'edit_tags', system_permission => 'administer', }, 'manage:blog_user' => { label => "Users", mode => 'list_member', order => 9000, view => "blog", permission => 'administer_blog', system_permission => 'administer', }, 'design:template' => { label => "Templates", mode => 'list', args => { _type => 'template' }, order => 100, permission => 'edit_templates', system_permission => 'edit_templates', }, 'prefs:general' => { label => "General", order => 100, mode => "cfg_system", view => "system", permission => "administer", }, 'prefs:user' => { label => "User", order => 200, mode => "cfg_system_users", view => "system", permission => "administer", }, 'prefs:feedback' => { label => "Feedback", order => 300, mode => "cfg_system_feedback", view => "system", permission => "administer", }, 'prefs:plugins' => { label => "Plugins", order => 600, mode => "cfg_plugins", permission => "administer_blog", system_permission => "manage_plugins", }, 'prefs:settings' => { label => "Blog Settings", mode => 'cfg_prefs', order => 100, permission => 'administer_blog,edit_config,set_publish_paths', system_permission => 'administer', view => "blog", }, 'prefs:notification' => { label => "Address Book", mode => 'list', args => { _type => 'notification' }, order => 400, permission => 'edit_notifications', view => "blog", }, 'tools:main' => { label => "System Information", order => 100, mode => "tools", view => "system", }, 'tools:activity_log' => { label => "Activity Log", order => 200, mode => "view_log", permission => "view_blog_log", system_permission => "view_log", view => "system", }, 'tools:import' => { label => "Import", order => 300, mode => "start_import", view => "blog", permission => "administer_blog", view => "system", }, 'tools:export' => { label => "Export", order => 400, mode => "start_export", view => "blog", permission => "administer_blog", view => "system", }, 'tools:backup' => { label => "Backup", order => 500, mode => "start_backup", permission => "administer_blog", view => "system", }, 'tools:restore' => { label => "Restore", order => 600, mode => "start_restore", permission => "administer_blog", view => "system", }, # System menu which is actually separate # in the CMS navigation 'system' => { label => "System Overview", mode => 'dashboard', order => 10000, }, 'system:user' => { label => "Users", mode => 'list_authors', order => 100, permission => 'administer_blog', system_permission => 'administer', }, 'system:blog' => { label => "Blogs", mode => 'list_blogs', order => 200, }, 'system:template' => { label => "Global Templates", mode => 'list_template', order => 250, system_permission => 'edit_templates', }, 'system:settings' => { label => "Settings", mode => 'cfg_system', order => 300, system_permission => 'administer', }, 'system:plugins' => { label => "Plugins", mode => 'cfg_plugins', order => 400, system_permission => 'manage_plugins', }, 'system:log' => { label => "Activity Log", mode => 'view_log', order => 500, system_permission => 'view_log', }, 'system:tools' => { label => "Tools", mode => 'tools', order => 600, system_permission => 'administer', }, }; } sub init_core_callbacks { my $app = shift; my $pkg = 'cms_'; $app->_register_core_callbacks( { # notification callbacks $pkg . 'save_permission_filter.notification' => sub { my ( $eh, $app, $id ) = @_; my $perms = $app->permissions; return $perms && $perms->can_edit_notifications; }, $pkg . 'save_filter.notification' => \&CMSSaveFilter_notification, $pkg . 'post_delete.notification' => \&CMSPostDelete_notification, # banlist callbacks $pkg . 'save_permission_filter.banlist' => sub { my ( $eh, $app, $id ) = @_; my $perms = $app->permissions; return $perms && ( $perms->can_edit_config || $perms->can_manage_feedback ); }, $pkg . 'save_filter.banlist' => \&CMSSaveFilter_banlist, # associations $pkg . 'delete_permission_filter.association' => \&CMSDeletePermissionFilter_association, # user callbacks $pkg . 'view_permission_filter.author' => \&CMSViewPermissionFilter_author, $pkg . 'save_permission_filter.author' => \&CMSSavePermissionFilter_author, $pkg . 'delete_permission_filter.author' => \&CMSDeletePermissionFilter_author, $pkg . 'save_filter.author' => \&CMSSaveFilter_author, $pkg . 'pre_save.author' => \&CMSPreSave_author, $pkg . 'post_save.author' => \&CMSPostSave_author, $pkg . 'post_delete.author' => \&CMSPostDelete_author, # blog callbacks $pkg . 'view_permission_filter.blog' => \&CMSViewPermissionFilter_blog, $pkg . 'save_permission_filter.blog' => \&CMSSavePermissionFilter_blog, $pkg . 'deletePermissionFilter.blog' => \&CMSDeletePermissionFilter_blog, $pkg . 'pre_save.blog' => \&CMSPreSave_blog, $pkg . 'post_save.blog' => \&CMSPostSave_blog, $pkg . 'save_filter.blog' => \&CMSSaveFilter_blog, $pkg . 'post_delete.blog' => \&CMSPostDelete_blog, # folder callbacks $pkg . 'view_permission_filter.folder' => \&CMSViewPermissionFilter_folder, $pkg . 'save_permission_filter.folder' => \&CMSSavePermissionFilter_folder, $pkg . 'delete_permission_filter.category' => \&CMSDeletePermissionFilter_folder, $pkg . 'pre_save.category' => \&CMSPreSave_folder, $pkg . 'post_save.category' => \&CMSPostSave_folder, $pkg . 'save_filter.category' => \&CMSSaveFilter_folder, $pkg . 'post_delete.category' => \&CMSPostDelete_folder, # category callbacks $pkg . 'view_permission_filter.category' => \&CMSViewPermissionFilter_category, $pkg . 'save_permission_filter.category' => \&CMSSavePermissionFilter_category, $pkg . 'delete_permission_filter.category' => \&CMSDeletePermissionFilter_category, $pkg . 'pre_save.category' => \&CMSPreSave_category, $pkg . 'post_save.category' => \&CMSPostSave_category, $pkg . 'save_filter.category' => \&CMSSaveFilter_category, $pkg . 'post_delete.category' => \&CMSPostDelete_category, # comment callbacks $pkg . 'view_permission_filter.comment' => \&CMSViewPermissionFilter_comment, $pkg . 'save_permission_filter.comment' => \&CMSSavePermissionFilter_comment, $pkg . 'delete_permission_filter.comment' => \&CMSDeletePermissionFilter_comment, $pkg . 'pre_save.comment' => \&CMSPreSave_comment, $pkg . 'post_save.comment' => \&CMSPostSave_comment, $pkg . 'post_delete.comment' => \&CMSPostDelete_comment, # commenter callbacks $pkg . 'view_permission_filter.commenter' => \&CMSViewPermissionFilter_commenter, $pkg . 'delete_permission_filter.commenter' => \&CMSDeletePermissionFilter_commenter, # entry callbacks $pkg . 'view_permission_filter.entry' => \&CMSViewPermissionFilter_entry, $pkg . 'delete_permission_filter.entry' => \&CMSDeletePermissionFilter_entry, $pkg . 'pre_save.entry' => \&CMSPreSave_entry, $pkg . 'post_save.entry' => \&CMSPostSave_entry, $pkg . 'post_delete.entry' => \&CMSPostDelete_entry, # page callbacks $pkg . 'view_permission_filter.page' => \&CMSViewPermissionFilter_page, $pkg . 'delete_permission_filter.page' => \&CMSDeletePermissionFilter_page, $pkg . 'pre_save.page' => \&CMSPreSave_page, $pkg . 'post_save.page' => \&CMSPostSave_page, $pkg . 'post_delete.page' => \&CMSPostDelete_page, # ping callbacks $pkg . 'view_permission_filter.ping' => \&CMSViewPermissionFilter_ping, $pkg . 'save_permission_filter.ping' => \&CMSSavePermissionFilter_ping, $pkg . 'delete_permission_filter.ping' => \&CMSDeletePermissionFilter_ping, $pkg . 'pre_save.ping' => \&CMSPreSave_ping, $pkg . 'post_save.ping' => \&CMSPostSave_ping, $pkg . 'post_delete.ping' => \&CMSPostDelete_ping, # template callbacks $pkg . 'view_permission_filter.template' => \&CMSViewPermissionFilter_template, $pkg . 'save_permission_filter.template' => \&CMSSavePermissionFilter_template, $pkg . 'delete_permission_filter.template' => \&CMSDeletePermissionFilter_template, $pkg . 'pre_save.template' => \&CMSPreSave_template, $pkg . 'post_save.template' => \&CMSPostSave_template, $pkg . 'post_delete.template' => \&CMSPostDelete_template, # tags $pkg . 'delete_permission_filter.tag' => \&CMSDeletePermissionFilter_tag, $pkg . 'post_delete.tag' => \&CMSPostDelete_tag, # junk-related callbacks #'HandleJunk' => \&_builtin_spam_handler, #'HandleNotJunk' => \&_builtin_spam_unhandler, $pkg . 'not_junk_test' => \&_cb_notjunktest_filter, # assets $pkg . 'view_permission_filter.asset' => \&CMSViewPermissionFilter_asset, $pkg . 'delete_permission_filter.asset' => \&CMSDeletePermissionFilter_asset, $pkg . 'pre_save.asset' => \&CMSPreSave_asset, $pkg . 'post_save.asset' => \&CMSPostSave_asset, $pkg . 'post_delete.asset' => \&CMSPostDelete_asset, 'template_param.edit_asset' => \&CMSTemplateParam_edit_asset, } ); } sub user_can_admin_commenters { my $app = shift; my $perms = $app->permissions; $app->user->is_superuser() || ( $perms && ( $perms->can_administer_blog || $perms->can_manage_feedback ) ); } sub validate_magic { my $app = shift; if ( my $feed_token = $app->param('feed_token') ) { return unless $app->user; my $pw = $app->user->api_password; return undef if ( $pw || '' ) eq ''; my $auth_token = MT::Util::perl_sha1_digest_hex( 'feed:' . $pw ); return $feed_token eq $auth_token; } else { return $app->SUPER::validate_magic(@_); } } sub update_welcome_message { my $app = shift; $app->validate_magic or return; my $perms = $app->permissions; return $app->errtrans("Permission denied.") unless $perms && $perms->can_edit_config; my $blog_id = $app->param('blog_id'); my $message = $app->param('welcome-message-text'); my $blog_class = $app->model('blog'); my $blog = $blog_class->load($blog_id) or return $app->error( $app->translate("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'); my $user_class = MT->model('author'); if ( !$driver || !$driver->table_exists($user_class) ) { return $app->redirect( $app->path . $upgrade_script . $app->uri_params( mode => 'install' ) ); } return $app->redirect( $app->path . $upgrade_script ); } sub logout { my $app = shift; $app->SUPER::logout(@_); } sub start_recover { my $app = shift; $app->add_breadcrumb( $app->translate('Password Recovery') ); $app->load_tmpl('dialog/recover.tmpl'); } sub recover_password { my $app = shift; my $q = $app->param; my $name = $q->param('name'); my $class = ref $app eq 'MT::App::Upgrader' ? 'MT::BasicAuthor' : $app->model('author'); eval "use $class;"; my @author = $class->load( { name => $name } ); my $author; foreach (@author) { next unless $_->password && ( $_->password ne '(none)' ); $author = $_; } my ( $rc, $res ) = $app->reset_password( $author, $q->param('hint'), $name ); if ($rc) { $app->add_breadcrumb( $app->translate('Password Recovery') ); $app->load_tmpl( 'dialog/recover.tmpl', { recovered => 1, email => $author->email } ); } else { $app->error($res); } } sub recover_profile_password { my $app = shift; $app->validate_magic or return; return $app->errtrans("Permission denied.") unless $app->user->is_superuser(); my $q = $app->param; require MT::Auth; require MT::Log; if ( !MT::Auth->can_recover_password ) { $app->log( { message => $app->translate( "Invalid password recovery attempt; can't recover password in this configuration" ), level => MT::Log::SECURITY(), class => 'system', category => 'recover_profile_password', } ); return $app->error("Can't recover password in this configuration"); } my $author_id = $q->param('author_id'); my $author = MT::Author->load($author_id); return $app->error( $app->translate("Invalid author_id") ) if !$author || $author->type != MT::Author::AUTHOR() || !$author_id; my ( $rc, $res ) = $app->reset_password( $author, $author->hint, $author->name ); if ($rc) { my $url = $app->uri( 'mode' => 'view', args => { _type => 'author', recovered => 1, id => $author_id } ); $app->redirect($url); } else { $app->error($res); } } sub reset_password { my $app = shift; my ($author) = $_[0]; my $hint = $_[1]; my $name = $_[2]; require MT::Auth; require MT::Log; if ( !MT::Auth->can_recover_password ) { $app->log( { message => $app->translate( "Invalid password recovery attempt; can't recover password in this configuration" ), level => MT::Log::SECURITY(), class => 'system', category => 'recover_password', } ); return ( 0, $app->translate("Can't recover password in this configuration") ); } $app->log( { message => $app->translate( "Invalid user name '[_1]' in password recovery attempt", $name ), level => MT::Log::SECURITY(), class => 'system', category => 'recover_password', } ), return ( 0, $app->translate("User name or password hint is incorrect.") ) unless $author; return ( 0, $app->translate("User has not set pasword hint; cannot recover password") ) if ( $hint && !$author->hint ); $app->log( { message => $app->translate( "Invalid attempt to recover password (used hint '[_1]')", $hint ), level => MT::Log::SECURITY(), class => 'system', category => 'recover_password' } ), return ( 0, $app->translate("User name or password hint is incorrect.") ) unless $author->hint eq $hint; return ( 0, $app->translate("User 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; my $message = $app->translate( "Password was reset for user '[_1]' (user #[_2]). Password was sent to the following address: [_3]", $author->name, $author->id, $author->email ); $app->log( { message => $message, level => MT::Log::SECURITY(), class => 'system', category => 'recover_password' } ); my $address = defined $author->nickname ? $author->nickname . ' <' . $author->email . '>' : $author->email; my %head = ( id => 'recover_password', To => $address, From => $app->config('EmailAddressMain') || $address, Subject => $app->translate("Password Recovery") ); my $charset = $app->charset; my $mail_enc = uc( $app->config('MailEncoding') || $charset ); $head{'Content-Type'} = qq(text/plain; charset="$mail_enc"); my $body = $app->build_email( 'recover-password.tmpl', { user_password => $pass, link_to_login => $app->base . $app->mt_uri } ); $body = wrap_text( $body, 72 ); require MT::Mail; MT::Mail->send( \%head, $body ) or return ( 0, $app->translate( "Error sending mail ([_1]); please fix the problem, then " . "try again to recover your password.", MT::Mail->errstr ) ); ( 1, $message ); } sub js_tag_list { my $app = shift; my $blog_id = $app->param('blog_id'); my $type = $app->param('_type') || 'entry'; my $class = $app->model($type) or return $app->json_error( $app->translate("Invalid request.") ); my $result; if ( my $tag_list = MT::Tag->cache( blog_id => $blog_id, class => $class, private => 1, ) ) { $result = { tags => $tag_list }; } else { $result = { tags => {} }; } $app->json_result($result); } sub js_tag_check { my $app = shift; my $name = $app->param('tag_name'); my $blog_id = $app->param('blog_id'); my $type = $app->param('_type') || 'entry'; my $tag_class = $app->model('tag') or return $app->json_error( $app->translate("Invalid request.") ); my $tag = $tag_class->load( { name => $name }, { binary => { name => 1 } } ); my $class = $app->model($type) or $app->json_error( $app->translate("Invalid request.") ); if ( $tag && $blog_id ) { my $ot_class = $app->model('objecttag'); my $count = $ot_class->count( { object_datasource => $class->datasource, blog_id => $blog_id, tag_id => $tag->id } ); undef $tag unless $count; } return $app->json_result( { exists => $tag ? 'true' : 'false' } ); } sub recover_passwords { my $app = shift; my @id = $app->param('id'); return $app->errtrans("Permission denied.") unless $app->user->is_superuser(); my $class = ref $app eq 'MT::App::Upgrader' ? 'MT::BasicAuthor' : $app->model('author'); eval "use $class;"; my @msg_loop; foreach (@id) { my $author = $class->load($_); my ( $rc, $res ) = $app->reset_password( $author, $author->hint ); push @msg_loop, { message => $res }; } $app->load_tmpl( 'recover_password_result.tmpl', { message_loop => \@msg_loop, return_url => $app->return_uri } ); } sub _merge_default_assignments { my $app = shift; my ( $data, $hasher, $type, $id ) = @_; if ( my $def = $app->config->DefaultAssignments ) { my @def = split ',', $def; while ( my $role_id = shift @def ) { my $blog_id = shift @def; next unless $role_id && $blog_id; next if ( $type eq 'role' ) && ( $id != $role_id ); next if ( $type eq 'blog' ) && ( $id != $blog_id ); my $obj = MT::Association->new; $obj->role_id($role_id); $obj->blog_id($blog_id); $obj->id( 'PSEUDO-' . $role_id . '-' . $blog_id ); my $row = $obj->column_values(); $hasher->( $obj, $row ) if $hasher; $row->{user_id} = 'PSEUDO'; $row->{user_name} = $app->translate('(newly created user)'); push @$data, $row; } } } sub build_asset_hasher { my $app = shift; my (%param) = @_; my ($default_thumb_width, $default_thumb_height, $default_preview_width, $default_preview_height) = @param{qw( ThumbWidth ThumbHeight PreviewWidth PreviewHeight )}; require File::Basename; require JSON; my %blogs; return sub { my ( $obj, $row, %param ) = @_; my ($thumb_width, $thumb_height) = @param{qw( ThumbWidth ThumbHeight )}; $row->{id} = $obj->id; my $blog = $blogs{ $obj->blog_id } ||= $obj->blog; $row->{blog_name} = $blog ? $blog->name : '-'; $row->{url} = $obj->url; # this has to be called to calculate $row->{asset_type} = $obj->class_type; $row->{asset_class_label} = $obj->class_label; my $file_path = $obj->file_path; # has to be called to calculate my $meta = $obj->metadata; if ( $file_path && ( -f $file_path ) ) { $row->{file_path} = $file_path; $row->{file_name} = File::Basename::basename( $file_path ); my @stat = stat( $file_path ); my $size = $stat[7]; $row->{file_size} = $size; if ( $size < 1024 ) { $row->{file_size_formatted} = sprintf( "%d Bytes", $size ); } elsif ( $size < 1024000 ) { $row->{file_size_formatted} = sprintf( "%.1f KB", $size / 1024 ); } else { $row->{file_size_formatted} = sprintf( "%.1f MB", $size / 1024000 ); } $meta->{'file_size'} = $row->{file_size_formatted}; } else { $row->{file_is_missing} = 1 if $file_path; } $row->{file_label} = $row->{label} = $obj->label || $row->{file_name} || $app->translate('Untitled'); if ($obj->has_thumbnail) { $row->{has_thumbnail} = 1; my $height = $thumb_height || $default_thumb_height || 75; my $width = $thumb_width || $default_thumb_width || 75; @$meta{qw( thumbnail_url thumbnail_width thumbnail_height )} = $obj->thumbnail_url( Height => $height, Width => $width ); $meta->{thumbnail_width_offset} = int(($width - $meta->{thumbnail_width}) / 2); $meta->{thumbnail_height_offset} = int(($height - $meta->{thumbnail_height}) / 2); if ($default_preview_width && $default_preview_height) { @$meta{qw( preview_url preview_width preview_height )} = $obj->thumbnail_url( Height => $default_preview_height, Width => $default_preview_width, ); $meta->{preview_width_offset} = int(($default_preview_width - $meta->{preview_width}) / 2); $meta->{preview_height_offset} = int(($default_preview_height - $meta->{preview_height}) / 2); } } else { $row->{has_thumbnail} = 0; } my $ts = $obj->created_on; if ( my $by = $obj->created_by ) { my $user = MT::Author->load($by); $row->{created_by} = $user ? $user->name : ''; } if ($ts) { $row->{created_on_formatted} = format_ts( LISTING_DATE_FORMAT, $ts, $blog, $app->user ? $app->user->preferred_language : undef ); $row->{created_on_time_formatted} = format_ts( LISTING_TIMESTAMP_FORMAT, $ts, $blog, $app->user ? $app->user->preferred_language : undef ); $row->{created_on_relative} = relative_date( $ts, time, $blog ); } @$row{keys %$meta} = values %$meta; $row->{metadata_json} = JSON::objToJson($meta); $row; }; } sub list_assets { my $app = shift; my $blog_id = $app->param('blog_id'); my $blog; if ($blog_id) { my $blog_class = $app->model('blog'); $blog = $blog_class->load($blog_id) or return $app->errtrans("Invalid request."); my $perms = $app->permissions; return $app->errtrans("Permission denied.") unless $app->user->is_superuser || ( $perms && ( $perms->can_edit_assets || $perms->can_edit_all_posts || $perms->can_create_post ) ); } my $asset_class = $app->model('asset') or return; my %terms; my %args = ( sort => 'created_on', direction => 'descend' ); my $class_filter; my $filter = ( $app->param('filter') || '' ); if ( $filter eq 'class' ) { $class_filter = $app->param('filter_val'); } elsif ($filter eq 'userpic') { $class_filter = 'image'; $terms{created_by} = $app->param('filter_val'); my $tag = MT::Tag->load( { name => '@userpic' }, { binary => { name => 1 } } ); if ($tag) { require MT::ObjectTag; $args{'join'} = MT::ObjectTag->join_on( 'object_id', { tag_id => $tag->id, object_datasource => MT::Asset->datasource }, { unique => 1 } ); } } $app->add_breadcrumb( $app->translate("Files") ); if ($blog_id) { $terms{blog_id} = $blog_id; } elsif ((defined $blog_id) && ($blog_id ne '')) { $terms{blog_id} = '0'; } else { unless ( $app->user->is_superuser ) { my @perms = MT::Permission->load( { author_id => $app->user->id } ); my @blog_ids; push @blog_ids, $_->blog_id foreach grep { $_->can_edit_assets } @perms; $terms{blog_id} = \@blog_ids; } } my $hasher = $app->build_asset_hasher( PreviewWidth => 240, PreviewHeight => 240 ); if ($class_filter) { my $asset_pkg = MT::Asset->class_handler($class_filter); $terms{class} = $asset_pkg->type_list; } else { $terms{class} = '*'; # all classes } # identifier => name my $classes = MT::Asset->class_labels; my @class_loop; foreach my $class ( keys %$classes ) { next if $class eq 'asset'; push @class_loop, { class_id => $class, class_label => $classes->{$class}, }; } # Now, sort it @class_loop = sort { $a->{class_label} cmp $b->{class_label} } @class_loop; my $dialog_view = $app->param('dialog_view') ? 1 : 0; my $perms = $app->permissions; my %carry_params = map { $_ => $app->param($_) || '' } (qw( edit_field upload_mode require_type next_mode asset_select )); $carry_params{'user_id'} = $app->param('filter_val') if $filter eq 'userpic'; $app->_set_start_upload_params(\%carry_params); $app->listing( { terms => \%terms, args => \%args, type => 'asset', code => $hasher, template => $dialog_view ? 'dialog/asset_list.tmpl' : '', params => { ( $blog ? ( blog_id => $blog_id, blog_name => $blog->name || '', edit_blog_id => $blog_id, ) : (), ), is_image => defined $class_filter && $class_filter eq 'image' ? 1 : 0, dialog_view => $dialog_view, search_label => MT::Asset->class_label_plural, search_type => 'asset', class_loop => \@class_loop, can_delete_files => ( $perms ? $perms->can_edit_assets : $app->user->is_superuser ), nav_assets => 1, panel_searchable => 1, object_type => 'asset', %carry_params, }, } ); } sub list_roles { my $app = shift; $app->return_to_dashboard( redirect => 1 ) if $app->param('blog_id'); my $pref = $app->list_pref('role'); my $all_perms; if ( $pref->{view_expanded} ) { my @all_perms = @{ MT::Permission->perms() }; $all_perms = [@all_perms]; foreach (@$all_perms) { $_->[1] = $app->translate( $_->[1] ); } } my $author_class = $app->model('author'); my $assoc_class = $app->model('association'); my $hasher = sub { my ( $obj, $row ) = @_; my $user_count = $assoc_class->count( { role_id => $obj->id, author_id => [ 1, undef ], }, { unique => 'author_id', range_incl => { author_id => 1 }, } ); $row->{members} = $user_count; $row->{weblogs} = $assoc_class->count( { role_id => $obj->id, blog_id => [ 1, undef ], }, { unique => 'blog_id', range_incl => { blog_id => 1 }, } ); if ( $obj->created_by ) { my $user = $author_class->load( $obj->created_by ); $row->{created_by} = $user ? $user->name : ''; } else { $row->{created_by} = ''; } # populate permissions for the expanded view if ( $pref->{view_expanded} ) { my @perms; foreach (@$all_perms) { next unless length( $_->[1] || '' ); push @perms, { name => $app->translate( $_->[1] ) } if $obj->has( $_->[0] ); } $row->{perm_loop} = \@perms; } }; unless ( $app->user->is_superuser() ) { return $app->errtrans("Permission denied."); } $app->add_breadcrumb( $app->translate("Roles") ); $app->listing( { args => { sort => 'name' }, type => 'role', code => $hasher, params => { nav_privileges => 1, list_noncron => 1, can_create_role => $app->user->is_superuser, has_expanded_mode => 1, search_label => $app->translate('Users'), object_type => 'role', screen_id => 'list-role', }, } ); } sub list_associations { my $app = shift; my $blog_id = $app->param('blog_id'); my $author_id = $app->param('author_id'); my $role_id = $app->param('role_id'); my $this_user = $app->user; if ( !$this_user->is_superuser ) { if ( ( !$blog_id || !$this_user->permissions($blog_id)->can_administer_blog ) && ( !$author_id || ( $author_id != $this_user->id ) ) ) { return $app->errtrans("Permission denied."); } } my ( $user, $role ); $app->error(undef); if ($author_id) { $app->add_breadcrumb( $app->translate('Users'), $app->user->is_superuser ? $app->uri( mode => 'list_authors' ) : undef ); if ( 'PSEUDO' ne $author_id ) { my $author_class = $app->model('author'); $user = $author_class->load($author_id); $app->add_breadcrumb( $user->name, $app->uri( mode => 'view', args => { _type => 'author', id => $author_id } ) ); } else { $app->add_breadcrumb( $app->translate('(newly created user)') ); } $app->add_breadcrumb( $app->translate('User Associations') ); } if ($role_id) { my $role_class = $app->model('role') or return; $role = $role_class->load($role_id); $app->add_breadcrumb( $app->translate("Roles"), $app->uri( mode => "list_roles" ) ); $app->add_breadcrumb( $role->name, $app->uri( mode => 'edit_role', args => { _type => 'role', id => $role_id } ) ); $app->add_breadcrumb( $app->translate("Role Users & Groups") ); } if ( !$role_id && !$author_id ) { if ($blog_id) { $app->add_breadcrumb( $app->translate("Users") ); } else { $app->add_breadcrumb( $app->translate("Associations") ); } } my $pref = $app->list_pref('association'); my $all_perms; if ( $pref->{view_expanded} ) { my @all_perms = @{ MT::Permission->perms() }; $all_perms = [@all_perms]; foreach (@$all_perms) { $_->[1] = $app->translate( $_->[1] ); } } # Supplies additional parameters for the row being listed my %users; $users{ $this_user->id } = $this_user; my $hasher = sub { my ( $obj, $row ) = @_; if ( my $user = $obj->user ) { $row->{user_id} = $user->id; $row->{user_name} = $user->name; } if ( my $role = $obj->role ) { $row->{role_name} = $role->name; # populate permissions for the expanded view if ( $pref->{view_expanded} ) { my @perms; foreach (@$all_perms) { next unless length( $_->[1] || '' ); push @perms, { name => $app->translate( $_->[1] ) } if $role->has( $_->[0] ); } $row->{perm_loop} = \@perms; } } else { $row->{role_name} = $app->translate("(Custom)"); } if ( my $blog = $obj->blog ) { $row->{blog_name} = $blog->name; } if ( my $ts = $obj->created_on ) { $row->{created_on_formatted} = format_ts( LISTING_DATE_FORMAT, $ts, $obj->blog, $app->user ? $app->user->preferred_language : undef ); $row->{created_on_time_formatted} = format_ts( LISTING_TIMESTAMP_FORMAT, $ts, $obj->blog, $app->user ? $app->user->preferred_language : undef ); $row->{created_on_relative} = relative_date( $ts, time, $obj->blog ); } if ( $row->{created_by} ) { my $created_user = $users{ $row->{created_by} } ||= MT::Author->load( $row->{created_by} ); if ($created_user) { $row->{created_by} = $created_user->name; } else { $row->{created_by} = $app->translate('(user deleted)'); } } }; $app->model('association') or return; my $types; if ( !$author_id && !$blog_id ) { $types = [ MT::Association::USER_BLOG_ROLE(), MT::Association::USER_ROLE(), ]; } elsif ( !$author_id ) { $types = [ MT::Association::USER_BLOG_ROLE(), ]; } elsif ($author_id) { $types = [ MT::Association::USER_BLOG_ROLE(), MT::Association::USER_ROLE() ]; } my $pre_build = sub { my ($param) = @_; my $data = $param->{object_loop} || []; #TODO: handle group_view if ( $param->{user_view} && ( 'PSEUDO' ne $param->{edit_author_id} ) ) { # don't merge } elsif ( $param->{role_view} ) { $app->_merge_default_assignments( $data, $hasher, 'role', $param->{role_id} ); } elsif ( $param->{blog_view} ) { $app->_merge_default_assignments( $data, $hasher, 'blog', $param->{blog_id} ); } else { $app->_merge_default_assignments( $data, $hasher, 'all' ); } }; return $app->listing( { args => { sort => 'created_on', direction => 'descend' }, type => 'association', code => $hasher, terms => { type => $types, $author_id ? ( author_id => $author_id ) : (), $blog_id ? ( blog_id => $blog_id ) : (), $role_id ? ( role_id => $role_id ) : (), }, pre_build => $pre_build, params => { can_create_association => $app->user->is_superuser || ( $blog_id && $app->user->permissions($blog_id)->can_administer_blog ), has_expanded_mode => 1, nav_privileges => ( $author_id || $blog_id ? 0 : 1 ) || $role_id, nav_authors => ( $author_id || $blog_id ? 1 : 0 ) && !$role_id, blog_view => $blog_id ? 1 : 0, user_view => $author_id ? 1 : 0, role_view => $role_id ? 1 : 0, $role_id ? ( role_id => $role_id, role_name => $role->name, ) : (), $author_id ? ( edit_author_id => $author_id, edit_name => $user ? ( $user->nickname ? $user->nickname : $user->name ) : $app->translate('(newly created user)'), edit_object => $app->translate('The user'), group_count => $user ? $user->group_count() : 0, status_enabled => $user ? ( $user->is_active ? 1 : 0 ) : 0, status_pending => $user ? ( $user->status == MT::Author::PENDING() ? 1 : 0 ) : 0, ) : (), saved => $app->param('saved') || 0, saved_deleted => $app->param('saved_deleted') || 0, usergroup_view => !$author_id && !$role_id, blog_id => $blog_id, search_label => $app->translate('Users'), object_type => 'association', pt_name => $app->translate('User'), screen_id => "list-associations", }, } ); } sub list_tag { my $app = shift; my %param; my $filter_key = $app->param('filter_key') || 'entry'; my $type = $app->param('_type') || $filter_key; my $plural; $param{TagObjectType} = $type; $param{Package} = $app->model($type); if ( !$app->blog && !$app->user->is_superuser ) { return $app->errtrans("Permission denied."); } my $perms = $app->permissions; if ( $app->blog && ( ( !$perms ) || ( $perms && $perms->is_empty ) ) ) { $app->delete_param('blog_id'); return $app->return_to_dashboard( permission => 1 ); } if ( $param{Package}->can('class_label_plural') ) { $plural = $param{Package}->class_label_plural(); } else { if ( $type =~ m/y$/ ) { $plural = $type; $plural =~ s/y$/ies/; } else { $plural = $type . 's'; } $plural =~ s/(.*)/\u$1/; } $param{TagObjectLabelPlural} = $plural; $app->model($type) or return; unless ( UNIVERSAL::can( $param{Package}, 'tag_count' ) ) { return $app->errtrans("Invalid type"); } $app->list_tag_for(%param); } sub list_tag_for { my $app = shift; my (%params) = @_; my $pkg = $params{Package}; my $q = $app->param; my $blog_id = $app->param('blog_id'); my $list_pref = $app->list_pref('tag'); my %param = %$list_pref; my $limit = $list_pref->{rows}; my $offset = $app->param('offset') || 0; my ( %terms, %arg ); my $tag_class = $app->model('tag'); my $ot_class = $app->model('objecttag'); my $total = $pkg->tag_count( $blog_id ? { blog_id => $blog_id } : undef ) || 0; $arg{'sort'} = 'name'; $arg{limit} = $limit + 1; if ( $total && $offset > $total - 1 ) { $arg{offset} = $offset = $total - $limit; } elsif ( $offset && ( ( $offset < 0 ) || ( $total - $offset < $limit ) ) ) { $arg{offset} = $offset = $total - $limit; } else { $arg{offset} = $offset if $offset; } $arg{join} = $ot_class->join_on( 'tag_id', { object_datasource => $pkg->datasource, ( $blog_id ? ( blog_id => $blog_id ) : () ) }, { unique => 1, 'join' => $pkg->join_on( undef, { id => \'= objecttag_object_id', ( $pkg =~ m/asset/i ? () : ( class => $pkg->class_type ) ) } ) } ); my $data = $app->build_tag_table( load_args => [ \%terms, \%arg ], 'package' => $pkg, param => \%param ); delete $param{tag_table} unless @$data; ## 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_entry = @$data > $limit; pop @$data while @$data > $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_entry) { $param{next_offset} = 1; $param{next_offset_val} = $offset + $limit; } # load tag filters my $filters = $app->registry( "list_filters", "tag" ) || {}; my $filter_key = $app->param('filter_key') || 'entry'; my $filter_label = ''; if ( my $filter = $filters->{$filter_key} ) { $filter_label = $filter->{label}; } $param{limit} = $limit; $param{offset} = $offset; $param{tag_object_type} = $params{TagObjectType}; $param{tag_object_label} = $params{TagObjectLabel} || $pkg->class_label; $param{tag_object_label_plural} = $params{TagObjectLabelPlural} || $pkg->class_label_plural; $param{object_label} = $tag_class->class_label; $param{object_label_plural} = $tag_class->class_label_plural; $param{object_type} = 'tag'; my $search_types = $app->search_apis($app->blog ? 'blog' : 'system'); if (grep { $_->{key} eq $param{tag_object_type} } @$search_types) { $param{search_type} = $param{tag_object_type}; $param{search_label} = $param{tag_object_label_plural}; } else { $param{search_type} = 'entry'; $param{search_label} = $app->translate("Entries"); } $param{list_start} = $offset + 1; $param{list_end} = $offset + scalar @$data; $param{list_total} = $total; $param{next_max} = $param{list_total} - $limit; $param{next_max} = 0 if ( $param{next_max} || 0 ) < $offset + 1; $param{list_noncron} = 1; $param{list_filters} = $app->list_filters('tag'); $param{filter_key} = $filter_key; $param{filter_label} = $filter_label; $param{link_to} = 'list_' . lc $params{TagObjectType}; $param{saved} = $q->param('saved'); $param{saved_deleted} = $q->param('saved_deleted'); $param{nav_tags} = 1; $app->add_breadcrumb( $app->translate('Tags') ); $param{screen_class} = "list-tag"; $param{screen_id} = "list-tag"; $param{listing_screen} = 1; $app->load_tmpl( 'list_tag.tmpl', \%param ); } sub rename_tag { my $app = shift; my $perms = $app->permissions; my $blog_id = $app->blog->id if $app->blog; ( $blog_id && $perms && $perms->can_edit_tags ) || ( $app->user->is_superuser() ) or return $app->errtrans("Permission denied."); my $id = $app->param('__id'); my $name = $app->param('tag_name') or return $app->error( $app->translate("New name of the tag must be specified.") ); my $obj_type = $app->param('__type') || 'entry'; my $obj_class = $app->model($obj_type); my $tag_class = $app->model('tag'); my $ot_class = $app->model('objecttag'); my $tag = $tag_class->load($id) or return $app->error( $app->translate("No such tag") ); my $tag2 = $tag_class->load( { name => $name }, { binary => { name => 1 } } ); if ($tag2) { return $app->call_return if $tag->id == $tag2->id; } my $terms = { tag_id => $tag->id }; $terms->{blog_id} = $blog_id if $blog_id; my $iter = $obj_class->load_iter( { ( $obj_type =~ m/asset/i ? ( class => '*' ) : ( class => $obj_type ) ) }, { join => MT::ObjectTag->join_on( 'object_id', $terms ) } ); my @tagged_objects; while ( my $o = $iter->() ) { $o->remove_tags( $tag->name ); $o->add_tags($name); push @tagged_objects, $o; } $_->save foreach @tagged_objects; if ($tag2) { $app->add_return_arg( merged => 1 ); } else { $app->add_return_arg( renamed => 1 ); } $app->call_return; } sub build_tag_table { my $app = shift; my (%args) = @_; my $iter; if ( $args{load_args} ) { my $class = $app->model('tag'); $iter = $class->load_iter( @{ $args{load_args} } ); } elsif ( $args{iter} ) { $iter = $args{iter}; } elsif ( $args{items} ) { $iter = sub { shift @{ $args{items} } }; } return [] unless $iter; my $param = $args{param} || {}; my $blog_id = $app->param('blog_id'); my $pkg = $args{'package'}; my @data; while ( my $tag = $iter->() ) { my $count = $pkg->tagged_count( $tag->id, { ( $blog_id ? ( blog_id => $blog_id ) : () ), ( $pkg =~ m/asset/i ? ( class => '*' ) : () ) } ); $count ||= 0; my $row = { tag_id => $tag->id, tag_name => $tag->name, tag_count => $count, object => $tag, }; push @data, $row; } return [] unless @data; $param->{tag_table}[0]{object_loop} = \@data; $app->load_list_actions( 'tag', $param->{tag_table}[0] ); $param->{object_loop} = $param->{tag_table}[0]{object_loop}; } sub load_text_filters { my $app = shift; my ( $selected, $type ) = @_; $selected = '0' unless defined $selected; my $filters = MT->all_text_filters; if ( $selected eq '1' ) { $selected = '__default__'; } my @f; for my $filter ( keys %$filters ) { if ( my $c = $filters->{$filter}{condition} ) { $c = $app->handler_to_coderef($c) unless ref $c; next unless $c->($type); } my $label = $filters->{$filter}{label}; $label = $label->() if ref($label) eq 'CODE'; my $row = { key => $filter, label => $label, }; $row->{selected} = $filter eq $selected; push @f, $row; } @f = sort { $a->{label} cmp $b->{label} } @f; unshift @f, { key => '0', label => $app->translate('None'), selected => !$selected, }; return \@f; } sub is_authorized { my $app = shift; my $blog_id = $app->param('blog_id'); $app->permissions(undef); return 1 unless $blog_id; return unless my $user = $app->user; my $perms = $app->permissions( $user->permissions($blog_id) ); $perms ? 1 : $app->error( $app->translate("You are not authorized to log in to this blog.") ); } sub set_default_tmpl_params { my $app = shift; $app->SUPER::set_default_tmpl_params(@_); my ($tmpl) = @_; my $param = {}; my $mode = $app->mode; my $auth_mode = $app->config('AuthenticationModule'); my ($pref) = split /\s+/, $auth_mode; # TODO - remove after testing or after new IA is defined $param->{app_layout_fixed} = 0; $param->{athena_nav} = 1; $param->{"auth_mode_$pref"} = 1; $param->{mt_news} = $app->config('NewsURL'); $param->{mt_support} = $app->config('SupportURL'); my $lang = lc MT->current_language || 'en_us'; $param->{language_id} = ( $lang !~ /en[_-]us/ ) ? $lang : ''; $param->{mode} = $app->mode; my $blog_id = $app->param('blog_id') || 0; my $blog; my $blog_class = $app->model('blog'); $blog ||= $blog_class->load($blog_id) if $blog_id; 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->{can_manage_plugins} = $auth->can_manage_plugins; $param->{can_edit_templates} = $auth->can_edit_templates; $param->{can_publish_feedbacks} = $auth->is_superuser; $param->{can_search_replace} = $auth->is_superuser; $param->{has_authors_button} = $auth->is_superuser; $param->{author_id} = $auth->id; $param->{author_name} = $auth->name; $param->{author_display_name} = $auth->nickname || $auth->name; } if ( my $perms = $app->permissions ) { my $perm_hash = $perms->to_hash; foreach my $perm_name ( keys %$perm_hash ) { my $perm_token = $perm_name; $perm_token =~ s/^permission\.//; $param->{$perm_token} = $perm_hash->{$perm_name} if defined $perm_hash->{$perm_name}; } $param->{can_edit_entries} = $param->{can_create_post} || $param->{can_edit_all_entries} || $param->{can_publish_post}; $param->{can_search_replace} = $param->{can_edit_all_posts}; $param->{can_edit_authors} = $param->{can_administer_blog}; $param->{can_access_assets} = $param->{can_create_post} || $param->{can_edit_all_posts} || $param->{can_edit_assets}; $param->{can_edit_commenters} = $param->{can_manage_feedback}; $param->{has_manage_label} = $param->{can_edit_templates} || $param->{can_administer_blog} || $param->{can_edit_categories} || $param->{can_edit_config} || $param->{can_edit_tags} || $param->{can_set_publish_paths} || $param->{show_ip_info}; $param->{has_posting_label} = $param->{can_create_post} || $param->{can_edit_entries} || $param->{can_access_assets}; $param->{has_community_label} = $param->{can_edit_entries} || $param->{can_edit_notifications}; $param->{can_publish_feedbacks} = $param->{can_manage_feedback} || $param->{can_publish_post} || $param->{can_edit_all_posts}; $param->{can_view_log} = $param->{can_view_blog_log}; $param->{can_publish_post} = $param->{can_publish_post} || $param->{can_edit_all_posts}; $param->{show_ip_info} = $app->config->ShowIPInformation && $param->{can_manage_feedback}; } my $static_app_url = $app->static_path; $param->{help_url} = $app->help_url() || $static_app_url . 'docs/'; $param->{show_ip_info} ||= $app->config('ShowIPInformation'); my $type = $app->param('_type') || ''; $param->{page_actions} ||= $app->page_actions($mode); $param->{ "mode_$mode" . ( $type ? "_$type" : '' ) } = 1; $param->{return_args} ||= $app->make_return_args; $tmpl->param($param); } sub build_page { my $app = shift; my ( $page, $param ) = @_; $param ||= {}; my $blog_id = $app->param('blog_id') || 0; my $blog; my $blog_class = $app->model('blog'); $blog ||= $blog_class->load($blog_id) if $blog_id; if ( $blog_id && $page ne 'login.tmpl' ) { if ($blog) { $param->{blog_name} = $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 ( $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->{quick_search} = 1 unless defined $param->{quick_search}; } } $app->build_blog_selector($param); $app->build_menus($param); $app->SUPER::build_page( $page, $param ); } sub generate_dashboard_stats { my $app = shift; my ($param) = @_; my $cache_time = 60 * 15; # cache for 15 minutes my $blog_id = $app->blog ? $app->blog->id : 0; my $user = $app->user; my $user_id = $user->id; my $static_path = $app->static_path; my $static_file_path = $app->static_file_path; if ( -f File::Spec->catfile( $static_file_path, "mt.js" ) ) { $param->{static_file_path} = $static_file_path; } else { return; } my $low_dir = sprintf("%03d", $user_id % 1000); my $sub_dir = sprintf("%03d", $blog_id % 1000); my $top_dir = $blog_id > $sub_dir ? $blog_id - $sub_dir : 0; $param->{support_path} = File::Spec->catdir( $static_file_path, 'support', 'dashboard', 'stats', $top_dir, $sub_dir, $low_dir); require MT::FileMgr; my $fmgr = MT::FileMgr->new('Local'); unless ( $fmgr->exists( $param->{support_path} ) ) { $fmgr->mkpath( $param->{support_path} ); unless ( $fmgr->exists( $param->{support_path} ) ) { return; } } my $stats_static_path = $static_path . 'support/dashboard/stats/' . $top_dir . '/' . $sub_dir . '/' . $low_dir; my $tabs = $app->registry('blog_stats_tabs') or return; while (my ($tab_id, $tab) = each %$tabs) { my $file = "${tab_id}.xml"; $param->{stat_url}->{$tab_id} = $stats_static_path . '/' . $file; my $path = File::Spec->catfile( $param->{support_path}, $file ); my $time = ( stat($path) )[9] if -f $path; if ( !$time || ( time - $time > $cache_time ) ) { my $gen_stats = $tab->{stats}; next if !$gen_stats; $gen_stats = $app->handler_to_coderef($gen_stats); my %counts = $gen_stats->($app, $tab); unless ( $app->create_dashboard_stats_file( $path, \%counts ) ) { delete $param->{stat_url}->{$tab_id}; } } } 1; } sub generate_dashboard_stats_entry_tab { my $app = shift; my ($tab) = @_; my $blog_id = $app->blog ? $app->blog->id : 0; my $user = $app->user; my $user_id = $user->id; my $entry_class = $app->model('entry'); my $terms = { status => MT::Entry::RELEASE() }; my $args = { group => [ "extract(year from authored_on)", "extract(month from authored_on)", "extract(day from authored_on)" ], }; $terms->{blog_id} = $blog_id if $blog_id; if ( !$user->is_superuser && !$blog_id ) { $args->{join} = MT::Permission->join_on( undef, { blog_id => \'= entry_blog_id', author_id => $user_id }, ); } my $entry_iter = $entry_class->count_group_by( $terms, $args ); my %counts; while ( my ( $count, $y, $m, $d ) = $entry_iter->() ) { my $date = sprintf( "%04d%02d%02dT00:00:00", $y, $m, $d ); $counts{$date} = $count; } %counts; } sub generate_dashboard_stats_comment_tab { my $app = shift; my ($tab) = @_; my $blog_id = $app->blog ? $app->blog->id : 0; my $user = $app->user; my $user_id = $user->id; my $cmt_class = $app->model('comment'); my $terms = { visible => 1 }; $terms->{blog_id} = $blog_id if $blog_id; my $args = { group => [ "extract(year from created_on)", "extract(month from created_on)", "extract(day from created_on)" ], }; if ( !$user->is_superuser && !$blog_id ) { $args->{join} = MT::Permission->join_on( undef, { blog_id => \'= comment_blog_id', author_id => $user_id }, ); } my $cmt_iter = $cmt_class->count_group_by( $terms, $args ); my %counts; while ( my ( $count, $y, $m, $d ) = $cmt_iter->() ) { my $date = sprintf( "%04d%02d%02dT00:00:00", $y, $m, $d ); $counts{$date} = $count; } %counts; } sub create_dashboard_stats_file { my $app = shift; my ( $file, $data ) = @_; my $support_dir = File::Spec->catdir( $app->static_file_path, "support" ); if ( !-d $support_dir ) { mkdir( $support_dir, 0777 ); if ($!) { $app->log("Failed to create 'support' directory."); return; } } local *FOUT; if ( !open( FOUT, ">$file" ) ) { return; } print FOUT <<EOT; <?xml version="1.0"?> <rsp status_code="0" status_message="Success"> <daily_counts> EOT my $now = time; for ( my $i = 120 ; $i >= 1 ; $i-- ) { my $ds = substr( epoch2ts( $app->blog, $now - ( ( $i - 1 ) * 60 * 60 * 24 ) ), 0, 8 ) . 'T00:00:00'; my $count = $data->{$ds} || 0; print FOUT qq{ <count date="$ds">$count</count>\n}; } print FOUT <<EOT; </daily_counts> </rsp> EOT close FOUT; } sub build_blog_selector { my $app = shift; my ($param) = @_; return if exists $param->{top_blog_loop}; my $blog = $app->blog; my $blog_id = $blog->id if $blog; $param->{dynamic_all} = $blog->custom_dynamic_templates eq 'all' if $blog; my $blog_class = $app->model('blog'); my $auth = $app->user or return; # Any access to a blog will put it on the top of your # recently used blogs list (the blog selector) $app->add_to_favorite_blogs($blog_id) if $blog_id; my %args; $args{join} = MT::Permission->join_on( 'blog_id', { author_id => $auth->id, } ); $args{limit} = 11; # don't load more than 11 my @blogs = $blog_class->load( undef, \%args ); my @fav_blogs = @{ $auth->favorite_blogs || [] }; @fav_blogs = grep { $_ != $blog_id } @fav_blogs if $blog_id; # Special case for when a user only has access to a single blog. if ( (!defined($app->param('blog_id'))) && ( @blogs == 1 ) && ( scalar @fav_blogs <= 1 ) ) { # User only has visibility to a single blog. Don't # bother giving them a dashboard link for 'all blogs', or # to 'select a blog'. $param->{single_blog_mode} = 1; my $blog = $blogs[0]; $blog_id = $blog->id; my $perms = MT::Permission->load( { blog_id => $blog_id, author_id => $auth->id } ); if ( !$app->blog ) { if ( $app->mode eq 'dashboard' ) { $app->param( 'blog_id', $blog_id ); $param->{blog_id} = $blog_id; $param->{blog_name} = $blog->name; $app->permissions($perms); $app->blog($blog); } else { @fav_blogs = ($blog_id); $blog_id = undef; } } } elsif ( @blogs && ( @blogs <= 10 ) ) { # This user only has visibility to 10 or fewer blogs; # no need to reference their 'favorite' blogs list. my @ids = map { $_->id } @blogs; if ($blog_id) { @ids = grep { $_ != $blog_id } @ids; } @fav_blogs = @ids; if ( $auth->is_superuser ) { # Better check to see if there are more than # 10 blogs in the system; if so, a superuser # will still want the 'Select a blog...' chooser. # Otherwise, hide it. my $all_blog_count = $blog_class->count(); if ( $all_blog_count < 11 ) { $param->{selector_hide_chooser} = 1; } } else { # This user is not a superuser and only has # 10 blogs, so they don't need a 'select blog' # link... $param->{selector_hide_chooser} = 1; } } $param->{selector_hide_chooser} ||= 0; # Logic for populating the blog selector control # * Pull list of 'favorite blogs' from user record # * Load all of those blogs so we can display them # * Exclude the current blog from the favorite list so it isn't # shown twice. @blogs = $blog_class->load( { id => \@fav_blogs } ) if @fav_blogs; my %blogs = map { $_->id => $_ } @blogs; @blogs = (); foreach my $id (@fav_blogs) { push @blogs, $blogs{$id} if $blogs{$id}; } my @data; if (@blogs) { my @perms = grep { !$_->is_empty } MT::Permission->load( { author_id => $auth->id, blog_id => \@fav_blogs, } ); my %perms = map { $_->blog_id => $_ } @perms; for my $blog (@blogs) { my $perm = $perms{ $blog->id }; next unless $auth->is_superuser || ( $perm && !$perm->is_empty ); 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 ); } } $param->{top_blog_loop} = \@data; if ( !$app->user->can_create_blog && ( $param->{single_blog_mode} || scalar(@data) <= 1 ) ) { $param->{no_submenu} = 1; } } sub build_menus { my $app = shift; my ($param) = @_; return if exists $param->{top_nav_loop}; my $menus = $app->registry('menus'); my $blog = $app->blog; my $blog_id = $blog->id if $blog; my @top_ids = grep { !/:/ } keys %$menus; my @top; my @sys; my $user = $app->user or return; my $perms = $app->permissions || $user->permissions; my $view = $blog_id ? "blog" : "system"; my $hide_disabled_options = $app->config('HideDisabledMenus') || 0; my $admin = $user->is_superuser(); # || ($perms && $perms->has('administer_blog')); foreach my $id (@top_ids) { my $menu = $menus->{$id}; next if $menu->{view} && $menu->{view} ne $view; if (my $cond = $menu->{condition}) { if (!ref($cond)) { $cond = $menu->{condition} = $app->handler_to_coderef($cond); } next unless $cond->(); } $menu->{allowed} = 1; $menu->{'id'} = $id; my @sub_ids = grep { m/^$id:/ } keys %$menus; my @sub; foreach my $sub_id (@sub_ids) { my $sub = $menus->{$sub_id}; next if $sub->{view} && ( $sub->{view} ne $view ); $sub->{'id'} = $sub_id; if (my $cond = $sub->{condition}) { if (!ref($cond)) { $cond = $sub->{condition} = $app->handler_to_coderef($cond); } next unless $cond->(); } push @sub, $sub; } if ( my $p = $blog_id ? $menu->{permission} || $menu->{system_permission} : $menu->{system_permission} || $menu->{permission} ) { my $allowed = 0; my @p = split /,/, $p; foreach my $p (@p) { my $perm = 'can_' . $p; $allowed = 1, last if ( $perms && $perms->$perm() ) || $admin; } $menu->{allowed} = $allowed; } elsif ( !$perms && !$blog_id ) { $menu->{allowed} = 0 if $menu->{system_permission} && !$admin; } next if $hide_disabled_options && (! $menu->{allowed}); if (@sub) { if ( $id eq 'system' ) { push @sys, $menu; } else { push @top, $menu; } my $has_sub = 0; foreach my $sub (@sub) { my $sys_only = $sub->{id} =~ m/^system:/; $sub->{allowed} = 1; if ( $sub->{mode} ) { $sub->{link} = $app->uri( mode => $sub->{mode}, args => { %{ $sub->{args} || {} }, ( $blog_id && !$sys_only ? ( blog_id => $blog_id ) : () ) } ); } $sub->{allowed} = 0 if $sub->{view} && ( $sub->{view} ne $view ); if ( $sub->{allowed} ) { my $sub_perms = ( $sys_only || ( $sub->{view} || '' ) eq 'system' ) ? $app->user->permissions(0) : $perms; if ( $sub_perms && ( my $p = $blog_id ? $sub->{permission} || $sub->{system_permission} : $sub->{system_permission} || $sub->{permission} ) ) { my $allowed = 0; my @p = split /,/, $p; foreach my $p (@p) { my $perm = 'can_' . $p; $allowed = 1, last if ( $sub_perms && $sub_perms->$perm() ) || $admin; } $sub->{allowed} = $allowed; } elsif ( !$sub_perms && !$blog_id ) { $sub->{allowed} = 0 if ( $sub->{system_permission} || $sub->{permission} ) && !$admin; } $has_sub = 1 if $sub->{allowed}; } } if ( $menu->{mode} ) { my $sys_only = 1 if $menu->{id} eq 'system'; $menu->{link} = $app->uri( mode => $menu->{mode}, args => { %{ $menu->{args} || {} }, ( $blog_id && !$sys_only ? ( blog_id => $blog_id ) : () ) } ); } @sub = sort { $a->{order} <=> $b->{order} } @sub; @sub = grep { $_->{allowed} } @sub if $hide_disabled_options; if ( !$menu->{mode} ) { $menu->{link} = $sub[0]->{link}; } $menu->{sub_nav_loop} = \@sub; if ( $menu->{allowed} ) { $menu->{allowed} = 0 unless $has_sub; } } } @top = grep { $_->{allowed} } @top if $hide_disabled_options; @sys = grep { $_->{allowed} } @sys if $hide_disabled_options; @top = sort { $a->{order} <=> $b->{order} } @top; $param->{top_nav_loop} = \@top; $param->{sys_nav_loop} = \@sys; } sub get_newsbox_content { my $app = shift; my $newsbox_url = $app->config('NewsboxURL'); if ( $newsbox_url && $newsbox_url ne 'disable' ) { return MT::Util::get_newsbox_html($newsbox_url, 'NW'); } return q(); } sub get_lmt_content { my $app = shift; my $newsbox_url = $app->config('LearningNewsURL'); if ( $newsbox_url && $newsbox_url ne 'disable' ) { return MT::Util::get_newsbox_html($newsbox_url, 'LW'); } return q(); } sub make_blog_list { my $app = shift; my ($blogs) = @_; my $tbp_class = $app->model('ping'); my $entry_class = $app->model('entry'); my $comment_class = $app->model('comment'); my $author = $app->user; my $data; my $i; my @blog_ids; push @blog_ids, $_->id for @$blogs; my ( $entry_count, $ping_count, $comment_count ); my $can_edit_authors = 1 if $author->is_superuser; for my $blog (@$blogs) { my $blog_id = $blog->id; my $perms = $author->permissions($blog_id); my $row = { id => $blog->id, name => $blog->name, description => $blog->description, site_url => $blog->site_url }; # we should use count by group here... $row->{num_entries} = ( $entry_count ? $entry_count->{$blog_id} : $entry_count->{$blog_id} = MT::Entry->count( { blog_id => $blog_id } ) ) || 0; $row->{num_comments} = ( $comment_count ? $comment_count->{$blog_id} : $comment_count->{$blog_id} = MT::Comment->count( { blog_id => $blog_id, junk_status => [ 0, 1 ] } ) ) || 0; $row->{num_pings} = ( $ping_count ? $ping_count->{$blog_id} : $ping_count->{$blog_id} = MT::TBPing->count( { blog_id => $blog_id, junk_status => [ 0, 1 ] } ) ) || 0; $row->{num_authors} = 0; # FIXME: this isn't efficient my $iter = MT::Permission->load_iter( { blog_id => [ 0, $blog_id ], # role_mask => [ 2, undef ] #}, { # range_incl => { 'role_mask' => 1 } } ); my %a; while ( my $p = $iter->() ) { next if exists $a{ $p->author_id }; $a{ $p->author_id } = 1; $row->{num_authors}++ if $p->can_create_post; } $row->{can_create_post} = $perms->can_create_post; $row->{can_edit_entries} = $perms->can_create_post || $perms->can_edit_all_posts || $perms->can_publish_post; $row->{can_edit_templates} = $perms->can_edit_templates; $row->{can_edit_config} = $perms->can_edit_config || $perms->can_administer_blog; $row->{can_set_publish_paths} = $perms->can_set_publish_paths || $perms->can_administer_blog; $row->{can_manage_feedback} = $perms->can_manage_feedback; $row->{can_edit_assets} = $perms->can_edit_assets; $row->{can_administer_blog} = $perms->can_administer_blog; push @$data, $row; } $data; } sub build_blog_table { my $app = shift; my (%args) = @_; my $blog_class = $app->model('blog'); my $tbp_class = $app->model('ping'); my $entry_class = $app->model('entry'); my $comment_class = $app->model('comment'); my $iter; if ( $args{load_args} ) { my $class = $app->model('blog'); $iter = $class->load_iter( @{ $args{load_args} } ); } elsif ( $args{iter} ) { $iter = $args{iter}; } elsif ( $args{items} ) { $iter = sub { pop @{ $args{items} } }; } return [] unless $iter; my $param = $args{param}; my $author = $app->user; my $can_edit_authors = $author->is_superuser; my @data; my $i; my ( $entry_count, $ping_count, $comment_count ); while ( my $blog = $iter->() ) { my $blog_id = $blog->id; my $row = { id => $blog->id, name => $blog->name, description => $blog->description, site_url => $blog->site_url }; # we should use count by group here... $row->{num_entries} = ( $entry_count ? $entry_count->{$blog_id} : $entry_count->{$blog_id} = MT::Entry->count( { blog_id => $blog_id } ) ) || 0; $row->{num_comments} = ( $comment_count ? $comment_count->{$blog_id} : $comment_count->{$blog_id} = MT::Comment->count( { blog_id => $blog_id, junk_status => [ 0, 1 ] } ) ) || 0; $row->{num_pings} = ( $ping_count ? $ping_count->{$blog_id} : $ping_count->{$blog_id} = MT::TBPing->count( { blog_id => $blog_id, junk_status => [ 0, 1 ] } ) ) || 0; $row->{num_authors} = 0; # FIXME: This isn't efficient my $iter = MT::Permission->load_iter( { blog_id => [ 0, $blog_id ], # role_mask => [ 2, undef ] #}, { # range_incl => { 'role_mask' => 1 } } ); my %a; while ( my $p = $iter->() ) { next if exists $a{ $p->author_id }; $a{ $p->author_id } = 1; $row->{num_authors}++ if $p->can_create_post; } if ( $author->is_superuser ) { $row->{can_create_post} = 1; $row->{can_edit_entries} = 1; $row->{can_edit_templates} = 1; $row->{can_edit_config} = 1; $row->{can_set_publish_paths} = 1; $row->{can_administer_blog} = 1; } else { my $perms = $author->permissions($blog_id); $row->{can_create_post} = $perms->can_create_post; $row->{can_edit_entries} = $perms->can_create_post || $perms->can_edit_all_posts || $perms->can_publish_post; $row->{can_edit_templates} = $perms->can_edit_templates; $row->{can_edit_config} = $perms->can_edit_config || $perms->can_administer_blog; $row->{can_set_publish_paths} = $perms->can_set_publish_paths || $perms->can_administer_blog; $row->{can_administer_blog} = $perms->can_administer_blog; } $row->{object} = $blog; push @data, $row; } if (@data) { $param->{blog_table}[0]{object_loop} = \@data; $app->load_list_actions( 'blog', \%$param ); $param->{object_loop} = $param->{blog_table}[0]{object_loop}; } \@data; } ## Application methods sub return_to_dashboard { my $app = shift; my (%param) = @_; $param{redirect} = 1 unless %param; my $blog_id = $app->param('blog_id'); $param{blog_id} = $blog_id if $blog_id; return $app->redirect( $app->uri( mode => 'dashboard', args => \%param ) ); } sub dashboard { my $app = shift; my (%param) = @_; if ( $app->request('fresh_login') ) { if ( !$app->param('blog_id') ) { # return to the last blog they visted, if any my $fav_blogs = $app->user->favorite_blogs || []; my $blog_id = $fav_blogs->[0] if @$fav_blogs; $app->param( 'blog_id', $blog_id ) if $blog_id; $app->delete_param('blog_id') unless $app->is_authorized; } } my $param = \%param; $param->{redirect} ||= $app->param('redirect'); $param->{permission} ||= $app->param('permission'); $param->{saved} ||= $app->param('saved'); $param->{system_overview_nav} = $app->param('blog_id') ? 0 : defined($app->param('blog_id')) ? 1 : 0; $param->{quick_search} = 0; $param->{no_breadcrumbs} = 1; $param->{screen_class} = "dashboard"; $param->{screen_id} = "dashboard"; my $default_widgets = { 'blog_stats' => { param => { tab => 'entry' }, order => 1, set => 'main' }, 'this_is_you-1' => { order => 1, set => 'sidebar' }, 'mt_shortcuts' => { order => 2, set => 'sidebar' }, 'mt_news' => { order => 3, set => 'sidebar' }, }; require MT::FileMgr; my $fmgr = MT::FileMgr->new('Local'); $param->{support_path} = File::Spec->catdir( $app->static_file_path, 'support', 'uploads' ); if ( $fmgr->exists( $param->{support_path} ) ) { $param->{has_uploads_path} = 1; } else { $fmgr->mkpath( $param->{support_path} ); if ( $fmgr->exists( $param->{support_path} ) && $fmgr->can_write( $param->{support_path} ) ) { $param->{has_uploads_path} = 1; } } # We require that the determination of the 'single blog mode' # state be done PRIOR to the generation of the widgets $app->build_blog_selector($param); $app->load_widget_list( 'dashboard', $param, $default_widgets ); $param = $app->load_widgets( 'dashboard', $param, $default_widgets ); return $app->load_tmpl( "dashboard.tmpl", $param ); } sub mt_blog_stats_widget_comment_tab { my ($app, $tmpl, $param) = @_; my $user = $app->user; my $blog = $app->blog; my $blog_id = $blog->id if $blog; $param->{editable} = $user->is_superuser; if ( $blog && !$param->{editable} ) { $param->{editable} = $user->permissions($blog_id)->can_edit_all_posts; $param->{comment_editable} = $user->permissions($blog_id)->can_manage_feedback; } my $comments = sub { my $args = { limit => 10, sort => 'created_on', direction => 'descend', }; if ( !$user->is_superuser && !$blog_id ) { $args->{join} = MT::Permission->join_on( undef, { blog_id => \'= comment_blog_id', author_id => $user->id }, ); } my @c = MT::Comment->load( { ( $blog_id ? ( blog_id => $blog_id ) : () ), junk_status => [ 0, 1 ], }, $args ); \@c; }; require MT::Promise; my $ctx = $tmpl->context; $ctx->stash( 'comments', MT::Promise::delay($comments) ); } sub mt_blog_stats_widget_entry_tab { my ($app, $tmpl, $param) = @_; my $user = $app->user; my $blog = $app->blog; my $blog_id = $blog->id if $blog; $param->{editable} = $user->is_superuser; if ( $blog && !$param->{editable} ) { $param->{editable} = $user->permissions($blog_id)->can_edit_all_posts; } my $entries = sub { my $args = { limit => 10, sort => 'authored_on', direction => 'descend', }; if ( !$user->is_superuser && !$blog_id ) { $args->{join} = MT::Permission->join_on( undef, { blog_id => \'= entry_blog_id', author_id => $user->id }, ); } my @e = MT::Entry->load( { ( $blog_id ? ( blog_id => $blog_id ) : () ), }, $args ); \@e; }; require MT::Promise; my $ctx = $tmpl->context; $ctx->stash( 'entries', MT::Promise::delay($entries) ); } sub mt_blog_stats_widget { my $app = shift; my ( $tmpl, $param ) = @_; # For stats shown on this page $app->generate_dashboard_stats($param) or return; my $tabs = $app->registry('blog_stats_tabs') or return; $tabs = $app->filter_conditional_list($tabs, 'dashboard', ($param->{widget_scope} || '')); $param->{tab_html_head} = ''; { local $param->{main}; local $param->{html_head}; my %cfgs; my $stat_url = delete $param->{stat_url}; while (my ($tab_id, $url) = each %$stat_url) { $param->{has_stat_urls} = 1; $cfgs{$tab_id} = { param => { stat_url => $url } }; } $app->build_widgets( set => 'blog_stats', param => $param, widgets => $tabs, widget_cfgs => \%cfgs, passthru_param => [qw( html_head js_include tabs active_stats_panel_updates )], ) or return; $param->{blog_stats} = $param->{main}; $param->{tab_html_head} .= $param->{html_head}; } } sub mt_news_widget { my $app = shift; my ( $tmpl, $param ) = @_; $param->{news_html} = $app->get_newsbox_content() || ''; $param->{learning_mt_news_html} = $app->get_lmt_content() || ''; } sub new_version_widget { my $app = shift; my ( $tmpl, $param ) = @_; push @{ $param->{feature_loop} ||= [] }, { feature_label => MT->translate('Shared Template Modules'), feature_url => $app->help_url('designer/shared-templates.html'), feature_description => MT->translate('Reuse elements of your site design or layout across all the blogs and sites managed within Movable Type.') }, { feature_label => MT->translate('Userpics'), feature_url => $app->help_url('author/userpics.html'), feature_description => MT->translate('Allow authors and commenters to upload a photo of themselves to be displayed alongside their comments.') }, { feature_label => MT->translate('Template Sets'), feature_url => $app->help_url('designer/template-sets.html'), feature_description => MT->translate('Template sets provide an easy way to bundle an entire design and install it into Movable Type.') }; } sub this_is_you_widget { my $app = shift; my ( $tmpl, $param ) = @_; my $user = $app->user; # User profile data # Number of posts by this user require MT::Entry; $param->{publish_count} = MT::Entry->count( { author_id => $user->id, } ); $param->{draft_count} = MT::Entry->count( { author_id => $user->id, status => MT::Entry::HOLD(), } ); if ( $param->{publish_count} ) { require MT::Comment; $param->{comment_count} = MT::Comment->count( { junk_status => [ 0, 1 ], }, { join => MT::Entry->join_on( undef, { author_id => $user->id, 'id' => \'= comment_entry_id', }, {}, ), } ); } my $last_post = MT::Entry->load( { author_id => $user->id, status => MT::Entry::RELEASE(), }, { sort => 'authored_on', direction => 'descend', limit => 1, } ); if ($last_post) { $param->{last_post_id} = $last_post->id; $param->{last_post_blog_id} = $last_post->blog_id; $param->{last_post_blog_name} = $last_post->blog->name; $param->{last_post_ts} = $last_post->authored_on; } if (my ($url) = $user->userpic_url()) { $param->{author_userpic_url} = $url; } $param->{author_userpic_width} = 50; $param->{author_userpic_height} = 50; } sub list_pref { 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 $d = $app->config->DefaultListPrefs || {}; my %default = ( Rows => 20, Format => 'Compact', SortOrder => 'Ascend', # Ascend|Descend Button => 'Above', # Above|Below|Both DateFormat => 'Relative', # Relative|Full ); if ( ( $list eq 'comment' ) || ( $list eq 'ping' ) ) { $default{Format} = 'expanded'; } $default{$_} = lc( $d->{$_} ) for keys %$d; my $list_pref; if ( $list eq 'main_menu' ) { $list_pref = { 'sort' => 'name', order => $default{SortOrder} || 'ascend', view => $default{Format} || 'compact', dates => $default{DateFormat} || 'relative', }; } else { $list_pref = { rows => $default{Rows} || 20, view => $default{Format} || 'compact', bar => $default{Button} || 'above', dates => $default{DateFormat} || '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') ) { $limit = 20 if $limit eq 'none'; $list_pref->{rows} = $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 ( lc $list_pref->{bar} eq 'both' ) { $list_pref->{"position_actions_both"} = 1; $list_pref->{"position_actions_top"} = 1; $list_pref->{"position_actions_bottom"} = 1; } elsif ( lc $list_pref->{bar} eq 'below' ) { $list_pref->{"position_actions_bottom"} = 1; } elsif ( lc $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 list_blogs { my $app = shift; $app->return_to_dashboard( redirect => 1 ) if $app->param('blog_id'); my $author = $app->user; my $list_pref = $app->list_pref('blog'); my $limit = $list_pref->{rows}; my $offset = $app->param('offset') || 0; my $args = { offset => $offset, sort => 'name' }; $args->{limit} = $limit + 1; unless ( $author->is_superuser ) { $args->{join} = MT::Permission->join_on( 'blog_id', { author_id => $author->id }, { unique => 1 } ); } my $blog_class = $app->model('blog'); my %param = %$list_pref; my @blogs = $blog_class->load( undef, $args ); my $can_edit_authors = $author->is_superuser; my $blog_loop = $app->make_blog_list( \@blogs ); if ($blog_loop) { ## 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 = @$blog_loop > $limit; pop @$blog_loop while @$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{offset} = $offset; $param{object_type} = 'blog'; $param{list_start} = $offset + 1; delete $args->{limit}; delete $args->{offset}; $param{list_total} = $blog_class->count( undef, $args ); $param{list_end} = $offset + ( $blog_loop ? scalar @$blog_loop : 0 ); $param{next_max} = $param{list_total} - $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{refreshed} = $app->param('refreshed'); $param{nav_blogs} = 1; $param{list_noncron} = 1; $param{search_label} = $app->translate('Blogs'); if ($blog_loop) { $param{object_loop} = $param{blog_table}[0]{object_loop} = $blog_loop; $app->load_list_actions( 'blog', \%param ); } $param{page_actions} = $app->page_actions('list_blog'); $param{feed_name} = $app->translate("Blog Activity Feed"); $param{feed_url} = $app->make_feed_link('blog'); $app->add_breadcrumb( $app->translate("Blogs") ); $param{nav_weblogs} = 1; $param{object_label} = $blog_class->class_label; $param{object_label_plural} = $blog_class->class_label_plural; $param{screen_class} = "list-blog"; $param{screen_id} = "list-blog"; $param{listing_screen} = 1; return $app->load_tmpl( 'list_blog.tmpl', \%param ); } # list of all users, regardless of commenter/author on a blog sub list_member { my $app = shift; my $blog_id = $app->param('blog_id'); $app->return_to_dashboard( redirect => 1 ) unless $blog_id; my $blog = $app->blog; my $user = $app->user; my $perms = $app->permissions; return $app->return_to_dashboard( permission => 1 ) unless $user->is_superuser() || ($perms && $perms->can_administer_blog()); my $super_user = 1 if $user->is_superuser(); my $args = {}; my $terms = {}; my $param = { list_noncron => 1 }; $args->{join} = MT::Permission->join_on( 'author_id', { blog_id => $blog_id, } ); $args->{sort_order} = 'created_on'; $args->{direction} = 'descend'; $param->{saved} = 1 if $app->param('saved'); $param->{search_label} = $app->translate('Users'); $param->{object_type} = 'author'; my $all_perms; my @all_perms = @{ MT::Permission->perms() }; $all_perms = [@all_perms]; foreach (@$all_perms) { $_->[1] = $app->translate( $_->[1] ); } require MT::Association; require MT::Role; my @all_roles = MT::Role->load( undef, { sort => 'name' }); my $sel_role = 0; my $filter = $app->param('filter') || ''; if ($filter eq 'role') { my $val = scalar $app->param('filter_val'); if ($val) { $sel_role = $val; $args->{join} = MT::Association->join_on('author_id', { blog_id => $blog_id, role_id => $val }); } } elsif ($filter eq 'status') { my $val = $app->param('filter_val'); if ($val eq 'disabled') { $terms->{status} = 2; } elsif ($val eq 'pending') { $terms->{status} = 3; } else { $terms->{status} = 1; } } my @role_loop; foreach my $r (@all_roles) { push @role_loop, { role_id => $r->id, role_name => $r->name, selected => $r->id == $sel_role }; } $param->{role_loop} = \@role_loop; my $hasher = sub { my ( $obj, $row ) = @_; if ( ( $row->{email} || '' ) !~ m/@/ ) { $row->{email} = ''; } if ( $row->{created_by} ) { if ( my $created_by = MT::Author->load( $row->{created_by} ) ) { $row->{created_by} = $created_by->name; } else { $row->{created_by} = $app->translate('*User deleted*'); } } $row->{is_me} = $row->{id} == $user->id; $row->{has_edit_access} = 1 if $super_user; $row->{usertype_author} = 1 if $obj->type == MT::Author::AUTHOR(); if ( $obj->type == MT::Author::COMMENTER() ) { $row->{usertype_commenter} = 1; $row->{status_trusted} = 1 if $obj->is_trusted($blog_id); if ($row->{name} =~ m/^[a-f0-9]{32}$/) { $row->{name} = $row->{nickname} || $row->{url}; } } $row->{status_enabled} = 1 if $obj->status == 1; my @roles = MT::Role->load(undef, { join => MT::Association->join_on('role_id', { author_id => $row->{id}, blog_id => $blog_id }, { unique => 1 })}); my @role_loop; foreach my $role (@roles) { my @perms; foreach (@$all_perms) { next unless length( $_->[1] || '' ); push @perms, $app->translate( $_->[1] ) if $role->has( $_->[0] ); } my $role_perms = join(", ", @perms); push @role_loop, { role_name => $role->name, role_id => $role->id, role_perms => $role_perms }; } $row->{role_loop} = \@role_loop; $row->{auth_icon_url} = $obj->auth_icon_url; }; $param->{screen_id} = "list-member"; return $app->listing( { type => 'user', template => 'list_member.tmpl', terms => $terms, params => $param, args => $args, code => $hasher, } ); } sub list_authors { my $app = shift; my (%param) = @_; $app->return_to_dashboard( redirect => 1 ) if $app->param('blog_id'); my $this_author = $app->user; return $app->return_to_dashboard( permission => 1 ) unless $this_author->is_superuser(); my $this_author_id = $this_author->id; my $list_pref = $app->list_pref('author'); %param = (%param, %$list_pref); my $limit = $list_pref->{rows}; my $offset = $app->param('offset') || 0; my $args = { offset => $offset, sort => 'name' }; $args->{limit} = $limit + 1; my %author_entry_count; $param{tab_users} = 1; my ( $filter_col, $val ); $param{filter_args} = ""; my %terms = ( type => MT::Author::AUTHOR() ); my $filter_key = $app->param('filter_key'); if ( ( $filter_col = $app->param('filter') ) && ( $val = $app->param('filter_val') ) ) { if ( !exists( $terms{$filter_col} ) ) { $terms{$filter_col} = $val; $param{filter} = $filter_col; $param{filter_val} = $val; $param{filter_args} = "&filter=" . encode_url($filter_col) . "&filter_val=" . encode_url($val); } } elsif ($filter_key) { my $filters = $app->registry("list_filters", "sys_user") || {}; if ( my $filter = $filters->{$filter_key} ) { if ( my $code = $filter->{code} || $app->handler_to_coderef( $filter->{handler} ) ) { $param{filter} = 1; $param{filter_key} = $filter_key; $param{filter_label} = $filter->{label}; $code->( \%terms, $args ); } } } $param{can_create_user} = $this_author->is_superuser; $param{synchronized} = 1 if $app->param('synchronized'); $param{error} = 1 if $app->param('error'); my $author_iter = MT::Author->load_iter( \%terms, $args ); my ( @data, %authors, %entry_count_refs ); my $entry_class = $app->model('entry'); while ( my $au = $author_iter->() ) { my $row = $au->column_values; $row->{name} = '(unnamed)' if !$row->{name}; $authors{ $au->id } ||= $au; $row->{id} = $au->id; $row->{email} = '' unless ( !defined $au->email ) or ( $au->email =~ /@/ ); $row->{entry_count} = 0; $entry_count_refs{ $au->id } = \$row->{entry_count}; $row->{is_me} = $au->id == $this_author_id; $row->{has_edit_access} = $this_author->is_superuser; $row->{status_enabled} = $au->is_active; $row->{status_pending} = $au->status == MT::Author::PENDING(); if ( $row->{created_by} ) { my $parent_author = $authors{ $au->created_by } ||= MT::Author->load( $au->created_by ) if $au->created_by; if ($parent_author) { $row->{created_by_name} = $parent_author->name; } else { $row->{created_by_name} = $app->translate('(user deleted)'); } } push @data, $row; last if scalar @data == $limit; } if ( keys %entry_count_refs ) { my $author_entry_count_iter = MT::Entry->count_group_by( { author_id => [ keys %entry_count_refs ] }, { group => ['author_id'] } ); while ( my ( $count, $author_id ) = $author_entry_count_iter->() ) { ${ $entry_count_refs{$author_id} } = $count; } } $param{object_loop} = \@data; $param{object_type} = 'author'; $param{object_label} = $app->model('author')->class_label; $param{object_label_plural} = $app->model('author')->class_label_plural; if ( $this_author->is_superuser() ) { $param{search_label} = $app->translate('Users'); $param{is_superuser} = 1; } $param{limit} = $limit; $param{list_start} = $offset + 1; delete $args->{limit}; delete $args->{offset}; $param{list_total} = MT::Author->count( \%terms, $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; $param{next_max} = 0 if ( $param{next_max} || 0 ) < $offset + 1; if ( $offset > 0 ) { $param{prev_offset} = 1; $param{prev_offset_val} = $offset - $limit; $param{prev_offset_val} = 0 if $param{prev_offset_val} < 0; } $param{offset} = $offset; $param{list_filters} = $app->list_filters("sys_user"); $param{list_noncron} = 1; $param{saved_deleted} = $app->param('saved_deleted'); $param{saved_removed} = $app->param('saved_removed'); $param{author_ldap_found} = $app->param('author_ldap_found'); $param{saved} = $app->param('saved'); my $status = $app->param('saved_status'); $param{"saved_status_$status"} = 1 if $status; $param{unchanged} = $app->param('unchanged'); $app->load_list_actions( 'author', \%param ); $param{page_actions} = $app->page_actions('list_authors'); $param{nav_authors} = 1; $param{listing_screen} = 1; $param{screen_id} = "list-author"; my $tmpl_file = $param{output} || 'list_author.tmpl'; $app->load_tmpl( $tmpl_file, \%param ); } sub make_feed_link { my $app = shift; my ( $view, $params ) = @_; my $user = $app->user; return if ( $user->api_password || '' ) eq ''; $params ||= {}; $params->{view} = $view; $params->{username} = $user->name; $params->{token} = perl_sha1_digest_hex( 'feed:' . $user->api_password ); $app->base . $app->mt_path . $app->config('ActivityFeedScript') . $app->uri_params( args => $params ); } sub build_author_table { my $app = shift; my (%args) = @_; my $i = 1; my @author; my $iter; if ( $args{load_args} ) { my $class = $app->model('author'); $iter = $class->load_iter( @{ $args{load_args} } ); } elsif ( $args{iter} ) { $iter = $args{iter}; } elsif ( $args{items} ) { $iter = sub { pop @{ $args{items} } }; } return [] unless $iter; my $blog_id = $app->param('blog_id'); my $param = $args{param}; $param->{has_edit_access} = $app->user->is_superuser(); $param->{is_administrator} = $app->user->is_superuser(); my ( %blogs, %entry_count_refs ); while ( my $author = $iter->() ) { my $row = { name => $author->name, nickname => $author->nickname, email => $author->email, url => $author->url, status_enabled => $author->is_active, status_pending => ( $author->status == MT::Author::PENDING() ) ? 1 : 0, id => $author->id, entry_count => 0, is_me => ( $app->user->id == $author->id ? 1 : 0 ) }; $entry_count_refs{ $author->id } = \$row->{entry_count}; if ( $author->created_by ) { if ( my $parent_author = $app->model('author')->load( $author->created_by ) ) { $row->{created_by} = $parent_author->name; } else { $row->{created_by} = $app->translate('(user deleted)'); } } $row->{object} = $author; $row->{usertype_author} = 1 if $author->type == MT::Author::AUTHOR(); if ( $author->type == MT::Author::COMMENTER() ) { $row->{usertype_commenter} = 1; $row->{status_trusted} = 1 if $blog_id && $author->is_trusted($blog_id); if ($row->{name} =~ m/^[a-f0-9]{32}$/) { $row->{name} = $row->{nickname} || $row->{url}; } } $row->{auth_icon_url} = $author->auth_icon_url; push @author, $row; } return [] unless @author; my $type = $app->param('entry_type') || 'entry'; my $entry_class = $app->model($type); my $author_entry_count_iter = $entry_class->count_group_by( { author_id => [ keys %entry_count_refs ] }, { group => ['author_id'] } ); while ( my ( $count, $author_id ) = $author_entry_count_iter->() ) { ${ $entry_count_refs{$author_id} } = $count; } $param->{author_table}[0]{object_loop} = \@author; $app->load_list_actions( 'author', $param->{author_table}[0] ); $param->{page_actions} = $param->{author_table}[0]{page_actions} = $app->page_actions('list_authors'); $param->{object_loop} = $param->{author_table}[0]{object_loop}; \@author; } sub quickpost_js { my $app = shift; my ($type) = @_; my $blog_id = $app->blog->id; my $blog = $app->model('blog')->load($blog_id); my %args = ( '_type' => $type, blog_id => $blog_id, qp => 1 ); my $uri = $app->base . $app->uri( 'mode' => 'view', args => \%args ); my $script = 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&title='+encodeURIComponent(d.title)+'&text='+encodeURIComponent(d.location.href)+encodeURIComponent('<br/><br/>')+encodeURIComponent(t),'_blank','scrollbars=yes,status=yes,resizable=yes,location=yes'))!; # Translate the phrase here to avoid ActivePerl DLL bug. $app->translate('<a href="[_1]">QuickPost to [_2]</a> - Drag this link to your browser\'s toolbar then click it when you are on a site you want to blog about.', encode_html($script), $blog->name); } sub apply_log_filter { my $app = shift; my ($param) = @_; my %arg; if ($param) { my $filter_col = $param->{filter}; my $val = $param->{filter_val}; if ( $filter_col && $val ) { if ( $filter_col eq 'level' ) { my @types; for ( 1, 2, 4, 8, 16 ) { push @types, $_ if $val & $_; } if (@types) { $arg{'level'} = \@types; } } elsif ( $filter_col eq 'class' ) { if ($val eq 'publish') { $arg{category} = 'publish'; } else { if ($val =~ m/,/) { $arg{class} = [ split /,/, $val ]; } else { $arg{class} = $val; } } } } $arg{blog_id} = [ split /,/, $param->{blog_id} ] if $param->{blog_id}; } \%arg; } sub view_log { my $app = shift; my $user = $app->user; my $blog_id = $app->param('blog_id'); my $perms = $app->permissions; if ($blog_id) { return $app->error( $app->translate("Permission denied.") ) unless ( $perms && $perms->can_view_blog_log ) || $user->can_view_log; } else { return $app->error( $app->translate("Permission denied.") ) unless $user->can_view_log; } my $log_class = $app->model('log'); my $blog_class = $app->model('blog'); my $list_pref = $app->list_pref('log'); my $limit = $list_pref->{rows}; my $offset = $app->param('offset') || 0; my $terms = { $blog_id ? ( blog_id => $blog_id ) : () }; my $cfg = $app->config; my %param = (%$list_pref); my ( $filter_col, $val ); $param{filter_args} = ""; if ( ( $filter_col = $app->param('filter') ) && ( $val = $app->param('filter_val') ) ) { $param{filter} = $filter_col; $param{filter_val} = $val; my %filter_arg = %{ $app->apply_log_filter( \%param ) }; $terms->{$_} = $filter_arg{$_} foreach keys %filter_arg; $param{filter_args} = "&filter=" . encode_url($filter_col) . "&filter_val=" . encode_url($val); } # all classes of log objects unless ( exists $terms->{class} ) { $terms->{class} = '*'; } my $iter = $log_class->load_iter( $terms, { 'sort' => 'id', 'direction' => 'descend', 'offset' => $offset, 'limit' => $limit } ); my @class_loop; my $labels = MT::Log->class_labels; foreach ( keys %$labels ) { next if $_ eq 'log'; my $name = $_; $name =~ s/log\.(\w)/$1/; next unless $name; push @class_loop, { class_name => $name, class_label => $labels->{$_}, }; } push @class_loop, { class_name => 'comment,ping', class_label => $app->translate("All Feedback"), }, { class_name => 'search', class_label => $app->translate("Search"), }, { class_name => 'publish', class_label => $app->translate("Publishing"), }; @class_loop = sort { $a->{class_label} cmp $b->{class_label} } @class_loop; $param{class_loop} = \@class_loop; my $log = $app->build_log_table( iter => $iter, param => \%param ); my $blog = $blog_class->load($blog_id) if $blog_id; my ($so); if ($blog) { $so = $blog->server_offset; } else { $so = $app->config('TimeOffset'); } 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{search_label} = $app->translate('Activity Log'); $param{list_start} = $offset + 1; $param{list_total} = MT::Log->count($terms); $param{list_end} = $offset + ( scalar @$log ); $param{offset} = $offset; $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; $param{next_max} = 0 if ( $param{next_max} || 0 ) < $offset + 1; if ( $offset > 0 ) { $param{prev_offset} = 1; $param{prev_offset_val} = $offset - $limit; $param{prev_offset_val} = 0 if $param{prev_offset_val} < 0; } $param{'reset'} = $app->param('reset'); $param{nav_log} = 1; $param{feed_name} = $app->translate("System Activity Feed"); $param{screen_class} = "list-log"; $param{screen_id} = "list-log"; $param{listing_screen} = 1; $param{feed_url} = $app->make_feed_link( 'system', $blog_id ? { blog_id => $blog_id } : undef ); if ( $param{feed_url} && $param{filter_args} ) { $param{feed_url} .= $param{filter_args}; } $app->add_breadcrumb( $app->translate('Activity Log') ); unless ( $app->param('blog_id') ) { $param{system_overview_nav} = 1; } $app->load_tmpl( 'view_log.tmpl', \%param ); } sub build_log_table { my $app = shift; my (%args) = @_; my $blog = $app->blog; my $blog_view = $blog ? 1 : 0; my $blog_class = $app->model('blog'); my $i = 1; my @log; my $iter; if ( $args{load_args} ) { my $class = $app->model('log'); $iter = $class->load_iter( @{ $args{load_args} } ); } elsif ( $args{iter} ) { $iter = $args{iter}; } elsif ( $args{items} ) { $iter = sub { pop @{ $args{items} } }; } return [] unless $iter; my $param = $args{param}; my %blogs; # reusing comment length constant for log view my $break_len = const('DISPLAY_LENGTH_EDIT_COMMENT_TEXT_SHORT'); while ( my $log = $iter->() ) { my $msg = $log->message; $msg = break_up_text( $msg, $break_len ) ; # break up really long strings my $row = { log_message => $msg, log_ip => $log->ip, id => $log->id, blog_id => $log->blog_id }; if ( my $ts = $log->created_on ) { if ($blog_view) { $row->{created_on_formatted} = format_ts( LISTING_DATETIME_FORMAT, epoch2ts( $blog, ts2epoch( undef, $ts ) ), $blog, $app->user ? $app->user->preferred_language : undef ); } else { $row->{created_on_formatted} = format_ts( LISTING_DATETIME_FORMAT, epoch2ts( undef, offset_time( ts2epoch( undef, $ts ) ) ), undef, $app->user ? $app->user->preferred_language : undef ); if ( $log->blog_id ) { $blog = $blogs{ $log->blog_id } ||= $blog_class->load( $log->blog_id, { cache_ok => 1 } ); $row->{weblog_name} = $blog ? $blog->name : ''; } else { $row->{weblog_name} = ''; } } $row->{created_on_relative} = relative_date( $ts, time ); $row->{log_detail} = $log->description; } if ( my $uid = $log->author_id ) { my $user_class = $app->model('author'); my $user = $user_class->load($uid); $row->{username} = $user->name if defined $user; } $row->{object} = $log; push @log, $row; } return [] unless @log; $param->{object_loop} = $param->{log_table}[0]{object_loop} = \@log; \@log; } sub reset_log { my $app = shift; $app->validate_magic() or return; my $author = $app->user; my $log_class = $app->model('log'); if ( my $blog_id = $app->param('blog_id') ) { my $perms = $app->permissions; return $app->error( $app->translate("Permission denied.") ) unless $perms && $perms->can_view_log; my $blog_class = $app->model('blog'); my $blog = $blog_class->load( $blog_id ) or return $app->errtrans("Invalid request."); if ( $log_class->remove( { blog_id => $blog_id, class => '*' } ) ) { $app->log( { message => $app->translate( "Activity log for blog '[_1]' (ID:[_2]) reset by '[_3]'", $blog->name, $blog_id, $author->name ), level => MT::Log::INFO(), class => 'system', category => 'reset_log' } ); } } else { return $app->error( $app->translate("Permission denied.") ) unless $author->can_view_log; if ( $log_class->remove( { class => '*' } ) ) { $app->log( { message => $app->translate( "Activity log reset by '[_1]'", $author->name ), level => MT::Log::INFO(), class => 'system', category => 'reset_log' } ); } } $app->add_return_arg( 'reset' => 1 ); $app->call_return; } sub export_notification { my $app = shift; my $user = $app->user; my $perms = $app->permissions; my $blog = $app->blog or return $app->error( $app->translate("Please select a blog.") ); return $app->error( $app->translate("Permission denied.") ) unless $user->is_superuser || ( $perms && $perms->can_edit_notifications ); $app->validate_magic() or return; $| = 1; my $enc = $app->config('ExportEncoding'); $enc = $app->config('LogExportEncoding') if ( !$enc ); $enc = ( $app->charset || '' ) if ( !$enc ); my $not_class = $app->model('notification'); my $iter = $not_class->load_iter( { blog_id => $blog->id }, { 'sort' => 'created_on', 'direction' => 'ascend' } ); my $file = ''; $file = dirify( $blog->name ) . '-' if $blog; $file = "Blog-" . $blog->id . '-' if $file eq '-'; $file .= "notifications_list.csv"; $app->{no_print_body} = 1; $app->set_header( "Content-Disposition" => "attachment; filename=$file" ); $app->send_http_header( $enc ? "text/csv; charset=$enc" : 'text/csv' ); while ( my $note = $iter->() ) { $app->print( $note->email ); $app->print(','); } } sub show_error { my $app = shift; my ($param) = @_; # handle legacy scalar error string signature $param = { error => $param } unless ref($param) eq 'HASH'; my $mode = $app->mode; if ( $mode eq 'rebuild' ) { my $r = MT::Request->instance; if (my $tmpl = $r->cache('build_template')) { # this is the template that likely caused the rebuild error push @{ $param->{button_loop} ||= [] }, { link => $app->uri( mode => 'view', args => { blog_id => $tmpl->blog_id, '_type' => 'template', id => $tmpl->id }), label => $app->translate("Edit Template"), }; } my $blog_id = $app->param('blog_id'); my $url = $app->uri( mode => 'rebuild_confirm', args => { blog_id => $blog_id } ); $param->{goback} ||= qq{window.location='$url'}; $param->{value} ||= $app->translate('Go Back'); } return $app->SUPER::show_error($param); } sub export_log { my $app = shift; my $user = $app->user; my $perms = $app->permissions; my $blog = $app->blog; my $blog_view = $blog ? 1 : 0; if ($blog_view) { return $app->error( $app->translate("Permission denied.") ) unless $user->can_view_log || ( $perms && $perms->can_view_blog_log ); } else { return $app->error( $app->translate("Permission denied.") ) unless $user->can_view_log; } $app->validate_magic() or return; $| = 1; my $enc = $app->config('ExportEncoding'); $enc = $app->config('LogExportEncoding') if ( !$enc ); $enc = ( $app->charset || '' ) if ( !$enc ); my $blog_enc = $app->config('PublishCharset'); my $q = $app->param; my $filter_args = $q->param('filter_args'); my %terms; if ($filter_args) { $q->parse_params($filter_args) if $filter_args; %terms = %{ $app->apply_log_filter( { filter => $q->param('filter'), filter_val => $q->param('filter_val') } ) }; } else { %terms = ( class => '*' ); } if ($blog) { $terms{blog_id} = $blog->id; } my $log_class = $app->model('log'); my $blog_class = $app->model('blog'); my $iter = $log_class->load_iter( \%terms, { 'sort' => 'created_on', 'direction' => 'ascend' } ); my %blogs; my $file = ''; $file = dirify( $blog->name ) . '-' if $blog; $file = "Blog-" . $blog->id . '-' if $file eq '-'; 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( $enc ? "text/csv; charset=$enc" : '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 ) ), $blog, $app->user ? $app->user->preferred_language : undef ); } else { push @col, format_ts( "%Y-%m-%d %H:%M:%S", $log->created_on, undef, $app->user ? $app->user->preferred_language : undef ); } push @col, $log->ip; my $blog; if ( $log->blog_id ) { $blog = $blogs{ $log->blog_id } ||= $blog_class->load( $log->blog_id ); } if ( $blog ) { my $name = $blog->name; $name =~ s/"/\\"/gs; $name =~ s/[\r\n]+/ /gs; $name = encode_text( $name, undef, $enc ) if $enc; push @col, '"' . $name . '"'; } else { push @col, ''; } my $msg = $log->message; $msg = encode_text( $msg, $blog_enc, $enc ) if $enc; $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 $perms = $app->permissions; return $app->return_to_dashboard( permission => 1 ) if $perms && ( !( $perms->can_edit_config || $perms->can_administer_blog ) ); my %param; # FIXME: This should build a category hierarchy! my $cat_class = $app->model('category'); my $iter = $cat_class->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->permissions->can_administer_blog; $param{encoding_names} = const('ENCODING_NAMES'); require MT::Auth; $param{password_needed} = MT::Auth->password_exists; my $importer_loop = []; require MT::Import; my $imp = MT::Import->new; my @keys = $imp->importer_keys; my ( $mt, $mt_format ); for my $key (@keys) { my $importer = $imp->importer($key); $importer->{key} = $key; $mt = $importer, next if $key eq 'import_mt'; $mt_format = $importer, next if $key eq 'import_mt_format'; push @$importer_loop, { label => $importer->{label}, key => $importer->{key}, description => $importer->{description}, importer_options_html => $imp->get_options_html( $importer->{key}, $blog_id ), }; } push @$importer_loop, { label => $mt_format->{label}, key => $mt_format->{key}, description => $mt_format->{description}, importer_options_html => $imp->get_options_html( $mt_format->{key}, $blog_id ), }; unshift @$importer_loop, { label => $mt->{label}, key => $mt->{key}, description => $mt->{description}, importer_options_html => $imp->get_options_html( $mt->{key}, $blog_id ), }; $param{importer_loop} = $importer_loop; if ($blog_id) { $param{blog_id} = $blog_id; my $blog = $app->model('blog')->load($blog_id); $param{text_filters} = $app->load_text_filters( $blog->convert_paras ); } $app->add_breadcrumb( $app->translate('Import/Export') ); $app->load_tmpl( 'import.tmpl', \%param ); } sub _populate_archive_loop { my $app = shift; my ( $blog, $obj ) = @_; my $index = $app->config('IndexBasename'); my $ext = $blog->file_extension || ''; $ext = '.' . $ext if $ext ne ''; require MT::TemplateMap; my @tmpl_maps = MT::TemplateMap->load( { template_id => $obj->id } ); my @maps; my %types; foreach my $map_obj (@tmpl_maps) { my $map = {}; $map->{map_id} = $map_obj->id; $map->{map_is_preferred} = $map_obj->is_preferred; my $at = $map->{archive_type} = $map_obj->archive_type; $types{$at}++; $map->{ 'archive_type_preferred_' . $blog->archive_type_preferred } = 1 if $blog->archive_type_preferred; $map->{file_template} = $map_obj->file_template if $map_obj->file_template; my $archiver = $app->publisher->archiver($at); next unless $archiver; $map->{archive_label} = $archiver->archive_label; my $tmpls = $archiver->default_archive_templates; my $tmpl_loop = []; foreach (@$tmpls) { my $name = $_->{label}; $name =~ s/\.html$/$ext/; $name =~ s/index$ext$/$index$ext/; push @$tmpl_loop, { name => $name, value => $_->{template}, default => ( $_->{default} || 0 ) }; } my $custom = 1; foreach (@$tmpl_loop) { if ( ( !$map->{file_template} && $_->{default} ) || ( $map->{file_template} eq $_->{value} ) ) { $_->{selected} = 1; $custom = 0; $map->{file_template} = $_->{value} if !$map->{file_template}; } } if ($custom) { unshift @$tmpl_loop, { name => $map->{file_template}, value => $map->{file_template}, selected => 1, }; } $map->{archive_tmpl_loop} = $tmpl_loop; if ( 1 < MT::TemplateMap->count( { archive_type => $at, blog_id => $obj->blog_id } ) ) { $map->{has_multiple_archives} = 1; } push @maps, $map; } @maps = sort { archive_type_sorter( $a, $b ) } @maps; return \@maps; } sub edit_object { my $app = shift; my $q = $app->param; my $type = $q->param('_type'); return $app->errtrans("Invalid request.") unless $type; # being a general-purpose method, lets look for a mode handler # that is specifically for editing this type. if we find it, # reroute to it. my $edit_mode = $app->mode . '_' . $type; if ( my $hdlrs = $app->handlers_for_mode($edit_mode) ) { return $app->forward($edit_mode, @_); } my %param = eval { $_[0] ? %{ $_[0] } : (); }; die Carp::longmess if $@; my $class = $app->model($type) or return; my $blog_id = $q->param('blog_id'); if ( defined($blog_id) && $blog_id ) { return $app->error( $app->translate("Invalid parameter") ) unless ( $blog_id =~ m/\d+/ ); } $app->remove_preview_file; my $enc = $app->config->PublishCharset; if ( $q->param('_recover') ) { my $sess_obj = $app->autosave_session_obj; if ($sess_obj) { my $data = $sess_obj->thaw_data; if ($data) { # XMLHttpRequest always send text in UTF-8... right? if ( 'utf-8' eq lc($enc) ) { $q->param( $_, $data->{$_} ) for keys %$data; } else { foreach ( keys %$data ) { my $encoded = MT::I18N::encode_text( $data->{$_}, 'utf-8', $enc ); $q->param( $_, $encoded ); } } $param{'recovered_object'} = 1; } else { $param{'recovered_failed'} = 1; } } else { $param{'recovered_failed'} = 1; } } elsif ( $q->param('qp') ) { foreach (qw( title text )) { my $data = $q->param($_); my $encoded = MT::I18N::encode_text( $data, undef, $enc ) if $data; $q->param( $_, $encoded ); } } $param{autosave_frequency} = $app->config->AutoSaveFrequency; my $id = $q->param('id'); my $perms = $app->permissions; my $author = $app->user; my $cfg = $app->config; $param{styles} = ''; if ( $type eq 'author' ) { if ( $perms || $blog_id ) { return $app->return_to_dashboard( redirect => 1 ); } } else { if ( ( !$perms || !$blog_id ) && ( $type eq 'entry' || $type eq 'page' || $type eq 'category' || $type eq 'folder' || $type eq 'comment' || $type eq 'commenter' || $type eq 'ping' ) ) { return $app->return_to_dashboard( redirect => 1 ); } } my $cols = $class->column_names; require MT::Promise; my $obj_promise = MT::Promise::delay( sub { return $class->load($id) || undef; } ); if ( !$author->is_superuser ) { $app->run_callbacks( 'cms_view_permission_filter.' . $type, $app, $id, $obj_promise ) || return $app->error( $app->translate( "Permission denied: [_1]", $app->errstr() ) ); } my $obj; my $blog; my $blog_class = $app->model('blog'); if ($blog_id) { $blog = $blog_class->load($blog_id); } else { $blog_id = 0; } 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 || $app->translate("(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(); } # Make certain any blog-specific element matches the blog we're # dealing with. If not, call shenanigans. if ( ( exists $param{blog_id} ) && ( $blog_id != ($obj->blog_id || 0) ) ) { return $app->return_to_dashboard( redirect => 1 ); } # Set type-specific display parameters if ( ( $type eq 'entry' ) || ( $type eq 'page' ) ) { return $app->error( $app->translate("Invalid parameter") ) if $obj->class ne $type; $param{nav_entries} = 1; $param{entry_edit} = 1; if ( $type eq 'entry' ) { $app->add_breadcrumb( $app->translate('Entries'), $app->uri( 'mode' => 'list_entries', args => { blog_id => $blog_id } ) ); } elsif ( $type eq 'page' ) { $app->add_breadcrumb( $app->translate('Pages'), $app->uri( 'mode' => 'list_pages', 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'}; unless ( defined $q->param('category_id') ) { delete $param{'category_id'}; if ( my $cat = $obj->category ) { $param{category_id} = $cat->id; } } $blog_id = $obj->blog_id; my $blog = $app->model('blog')->load($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; $param{'authored_on_date'} = $q->param('authored_on_date') || format_ts( "%Y-%m-%d", $obj->authored_on, $blog, $app->user ? $app->user->preferred_language : undef ); $param{'authored_on_time'} = $q->param('authored_on_time') || format_ts( "%H:%M:%S", $obj->authored_on, $blog, $app->user ? $app->user->preferred_language : undef ); 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; # Check permission to send notifications and if the # blog has notification list subscribers if ( $perms->can_send_notifications && $obj->status == MT::Entry::RELEASE() ) { my $not_class = $app->model('notification'); $param{can_send_notifications} = 1; $param{has_subscribers} = $not_class->count( { blog_id => $blog_id } ); } ## 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' } ); $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{has_any_pinged_urls} = ( $obj->pinged_urls || '' ) =~ m/\S/; $param{ping_errors} = $q->param('ping_errors'); $param{can_view_log} = $app->user->can_view_log; $param{entry_permalink} = $obj->permalink; $param{'mode_view_entry'} = 1; $param{'basename_old'} = $obj->basename; if ( my $ts = $obj->authored_on ) { $param{authored_on_ts} = $ts; $param{authored_on_formatted} = format_ts( LISTING_DATETIME_FORMAT, $ts, $blog, $app->user ? $app->user->preferred_language : undef ); } $app->load_list_actions( $type, \%param ); } elsif ( ( $type eq 'category' ) || ( $type eq 'folder' ) ) { $param{nav_categories} = 1; #$param{ "tab_" . ( $app->param('tab') || 'details' ) } = 1; # $app->add_breadcrumb($app->translate('Categories'), # $app->uri( 'mode' => 'list_cat', # args => { blog_id => $obj->blog_id })); # $app->add_breadcrumb($obj->label); my $parent = $obj->parent_category; my $site_url = $blog->site_url; $site_url .= '/' unless $site_url =~ m!/$!; $param{path_prefix} = $site_url . ( $parent ? $parent->publish_path : '' ); $param{path_prefix} .= '/' unless $param{path_prefix} =~ m!/$!; require MT::Trackback; my $tb = MT::Trackback->load( { category_id => $obj->id } ); if ($tb) { my $list_pref = $app->list_pref('ping'); %param = ( %param, %$list_pref ); my $path = $app->config('CGIPath'); $path .= '/' unless $path =~ m!/$!; if ($path =~ m!^/!) { my ($blog_domain) = $blog->archive_url =~ m|(.+://[^/]+)|; $path = $blog_domain . $path; } my $script = $app->config('TrackbackScript'); $param{tb} = 1; $param{tb_url} = $path . $script . '/' . $tb->id; if ( $param{tb_passphrase} = $tb->passphrase ) { $param{tb_url} .= '/' . encode_url( $param{tb_passphrase} ); } $app->load_list_actions( 'ping', $param{ping_table}[0], 'pings' ); } } elsif ( $type eq 'template' ) { # FIXME: Template types should not be enumerated here $param{nav_templates} = 1; my $tab; if ( $obj->type eq 'index' ) { $tab = 'index'; $param{template_group_trans} = $app->translate('index'); } elsif ($obj->type eq 'archive' || $obj->type eq 'individual' || $obj->type eq 'category' || $obj->type eq 'page' ) { # FIXME: enumeration of types $tab = 'archive'; $param{template_group_trans} = $app->translate('archive'); } elsif ( $obj->type eq 'custom' ) { $tab = 'module'; $param{template_group_trans} = $app->translate('module'); } elsif ( $obj->type eq 'widget' ) { $tab = 'widget'; $param{template_group_trans} = $app->translate('widget'); } elsif ( $obj->type eq 'email' ) { $tab = 'email'; $param{template_group_trans} = $app->translate('email'); } else { $tab = 'system'; $param{template_group_trans} = $app->translate('system'); } $param{template_group} = $tab; $blog_id = $obj->blog_id; # FIXME: enumeration of types $param{has_name} = $obj->type eq 'index' || $obj->type eq 'custom' || $obj->type eq 'widget' || $obj->type eq 'archive' || $obj->type eq 'category' || $obj->type eq 'page' || $obj->type eq 'individual'; if ( !$param{has_name} ) { $param{ 'type_' . $obj->type } = 1; $param{name} = $obj->name; } $app->add_breadcrumb( $param{name} ); $param{has_outfile} = $obj->type eq 'index'; $param{has_rebuild} = ( ( $obj->type eq 'index' ) && ( ( $blog->custom_dynamic_templates || "" ) ne 'all' ) ); $param{custom_dynamic} = $blog && ( $blog->custom_dynamic_templates || "" ) eq 'custom'; $param{has_build_options} = ( $param{custom_dynamic} || $param{has_rebuild} ); # FIXME: enumeration of types $param{is_special} = $param{type} ne 'index' && $param{type} ne 'archive' && $param{type} ne 'category' && $param{type} ne 'page' && $param{type} ne 'individual'; $param{has_build_options} = $param{has_build_options} && $param{type} ne 'custom' && $param{type} ne 'widget' && !$param{is_special}; $param{rebuild_me} = defined $obj->rebuild_me ? $obj->rebuild_me : 1; $param{search_label} = $app->translate('Templates'); $param{object_type} = 'template'; my $published_url = $obj->published_url; $param{published_url} = $published_url if $published_url; $param{saved_rebuild} = 1 if $q->param('saved_rebuild'); $app->load_list_actions( 'template', \%param ); $obj->compile; if ( $obj->{errors} && @{ $obj->{errors} } ) { $param{error} = $app->translate( "One or more errors were found in this template."); $param{error} .= "<ul>\n"; foreach my $err ( @{ $obj->{errors} } ) { $param{error} .= "<li>" . MT::Util::encode_html( $err->{message} ) . "</li>\n"; } $param{error} .= "</ul>\n"; } # Populate list of included templates if ( my $includes = $obj->getElementsByTagName('Include') ) { my @includes; my @widgets; my %seen; foreach my $tag (@$includes) { my $include = {}; my $mod = $include->{include_module} = $tag->[1]->{module} || $tag->[1]->{widget}; next unless $mod; my $type = $tag->[1]->{widget} ? 'widget' : 'custom'; next if exists $seen{$type}{$mod}; $seen{$type}{$mod} = 1; my $other = MT::Template->load( { blog_id => [ $obj->blog_id, 0 ], name => $mod, type => $type, }, { sort => 'blog_id', direction => 'descend', } ); if ($other) { $include->{include_link} = $app->mt_uri( mode => 'view', args => { blog_id => $other->blog_id || 0, '_type' => 'template', id => $other->id } ); } else { $include->{create_link} = $app->mt_uri( mode => 'view', args => { blog_id => $obj->blog_id, '_type' => 'template', type => $type, name => $mod, } ); } if ($type eq 'widget') { push @widgets, $include; } else { push @includes, $include; } } $param{include_loop} = \@includes if @includes; $param{widget_loop} = \@widgets if @widgets; } my @sets = ( @{ $obj->getElementsByTagName('WidgetSet') || [] }, @{ $obj->getElementsByTagName('WidgetManager') || [] } ); if ( @sets ) { my @widget_sets; my %seen; foreach my $set (@sets) { my $name = $set->[1]->{name}; next unless $name; next if $seen{$name}; $seen{$name} = 1; push @widget_sets, { include_link => $app->mt_uri( mode => 'edit_widget', args => { blog_id => $obj->blog_id, widgetmanager => $name, }, ), include_module => $name, }; } $param{widget_set_loop} = \@widget_sets if @widget_sets; } $param{have_includes} = 1 if $param{widget_set_loop} || $param{include_loop} || $param{widget_loop}; # Populate archive types for creating new map my $obj_type = $obj->type; if ( $obj_type eq 'individual' || $obj_type eq 'page' || $obj_type eq 'author' || $obj_type eq 'category' || $obj_type eq 'archive' ) { my @at = $app->publisher->archive_types; my @archive_types; for my $at (@at) { my $archiver = $app->publisher->archiver($at); my $archive_label = $archiver->archive_label; $archive_label = $at unless $archive_label; $archive_label = $archive_label->() if ( ref $archive_label ) eq 'CODE'; if ( ( $obj_type eq 'archive' ) || ( $obj_type eq 'author' ) || ( $obj_type eq 'category' ) ) { # only include if it is NOT an entry-based archive type next if $archiver->entry_based; } elsif ( $obj_type eq 'page' ) { # only include if it is a entry-based archive type and page next unless $archiver->entry_based; next if $archiver->entry_class ne 'page'; } elsif ( $obj_type eq 'individual' ) { # only include if it is a entry-based archive type and entry next unless $archiver->entry_based; next if $archiver->entry_class eq 'page'; } push @archive_types, { archive_type_translated => $archive_label, archive_type => $at, }; @archive_types = sort { archive_type_sorter( $a, $b ) } @archive_types; } $param{archive_types} = \@archive_types; # Populate template maps for this template my $maps = $app->_populate_archive_loop( $blog, $obj ); $param{object_loop} = $param{template_map_loop} = $maps if @$maps; } } 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} = $cfg->ShowIPInformation; $param{use_plugins} = $cfg->UsePlugins; 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; } my $lang = $obj->language || 'en'; $lang = 'en' if lc($lang) eq 'en-us' || lc($lang) eq 'en_us'; $lang = 'ja' if lc($lang) eq 'jp'; $param{ 'language_' . $lang } = 1; $param{system_allow_comments} = $cfg->AllowComments; $param{system_allow_pings} = $cfg->AllowPings; $param{tk_available} = eval { require MIME::Base64; 1; } && eval { require LWP::UserAgent; 1 }; $param{'auto_approve_commenters'} = !$obj->manual_approve_commenters; $param{identity_system} = $app->config('IdentitySystem'); $param{handshake_return} = $app->base . $app->mt_uri; $param{"moderate_comments"} = $obj->moderate_unreg_comments; $param{ "moderate_comments_" . ( $obj->moderate_unreg_comments || 0 ) } = 1; $param{ "moderate_pings_" . ( $obj->moderate_pings || 0 ) } = 1; my $cmtauth_reg = $app->registry('commenter_authenticators'); foreach my $auth ( keys %$cmtauth_reg ) { $cmtauth_reg->{$auth}->{disabled} = 1 if exists( $cmtauth_reg->{$auth}->{condition} ) && !( $cmtauth_reg->{$auth}->{condition}->() ); } if ( my $auths = $blog->commenter_authenticators ) { foreach ( split ',', $auths ) { if ( 'MovableType' eq $_ ) { $param{enabled_MovableType} = 1; } else { $cmtauth_reg->{$_}->{enabled} = 1; } } } my @cmtauth_loop; foreach ( keys %$cmtauth_reg ) { $cmtauth_reg->{$_}->{key} = $_; if ( UNIVERSAL::isa( $cmtauth_reg->{$_}->{plugin}, 'MT::Plugin' ) ) { push @cmtauth_loop, $cmtauth_reg->{$_}; } } unshift @cmtauth_loop, $cmtauth_reg->{'TypeKey'} if exists( $cmtauth_reg->{'TypeKey'} ) && $blog->remote_auth_token; unshift @cmtauth_loop, $cmtauth_reg->{'Vox'} if exists $cmtauth_reg->{'Vox'}; unshift @cmtauth_loop, $cmtauth_reg->{'LiveJournal'} if exists $cmtauth_reg->{'LiveJournal'}; unshift @cmtauth_loop, $cmtauth_reg->{'OpenID'} if exists $cmtauth_reg->{'OpenID'}; $param{cmtauth_loop} = \@cmtauth_loop; if ( $output eq 'cfg_prefs.tmpl' ) { $app->add_breadcrumb( $app->translate('General Settings') ); my $lang = $obj->language || 'en'; $lang = 'en' if lc($lang) eq 'en-us' || lc($lang) eq 'en_us'; $lang = 'ja' if lc($lang) eq 'jp'; $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 ); } } elsif ( $output eq 'cfg_entry.tmpl' ) { ## load entry preferences for new/edit entry page of the blog my $pref_param = $app->load_entry_prefs; %param = ( %param, %$pref_param ); $param{ 'sort_order_posts_' . ( $obj->sort_order_posts || 0 ) } = 1; $param{ 'status_default_' . $obj->status_default } = 1 if $obj->status_default; $param{ 'allow_comments_default_' . ( $obj->allow_comments_default || 0 ) } = 1; $param{system_allow_pings} = $cfg->AllowPings && $blog->allow_pings; $param{system_allow_comments} = $cfg->AllowComments && ( $blog->allow_reg_comments || $blog->allow_unreg_comments ); my $replace_fields = $blog->smart_replace_fields || ''; my @replace_fields = split( /,/, $replace_fields ); foreach my $fld (@replace_fields) { $param{ 'nwc_' . $fld } = 1; } $param{ 'nwc_smart_replace_' . ( $blog->smart_replace || 0 ) } = 1; $param{ 'nwc_replace_none' } = ( $blog->smart_replace || 0 ) == 2; } elsif ( $output eq 'cfg_web_services.tmpl' ) { $param{system_disabled_notify_pings} = $cfg->DisableNotificationPings; $param{system_allow_outbound_pings} = $cfg->OutboundTrackbackLimit eq 'any'; my %selected_pings = map { $_ => 1 } split ',', ($obj->update_pings || ''); my $pings = $app->registry('ping_servers'); my @pings; push @pings, { key => $_, label => $pings->{$_}->{label}, exists( $selected_pings{$_} ) ? ( selected => 1 ) : (), } foreach keys %$pings; $param{pings_loop} = \@pings; } elsif ( $output eq 'cfg_comments.tmpl' ) { $param{email_new_comments_1} = ( $obj->email_new_comments || 0 ) == 1; $param{email_new_comments_2} = ( $obj->email_new_comments || 0 ) == 2; $param{nofollow_urls} = $obj->nofollow_urls; $param{follow_auth_links} = $obj->follow_auth_links; $param{ 'sort_order_comments_' . ( $obj->sort_order_comments || 0 ) } = 1; $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{allow_comments} = $blog->allow_reg_comments || $blog->allow_unreg_comments; $param{use_comment_confirmation} = defined $blog->use_comment_confirmation ? $blog->use_comment_confirmation : 0; $param{system_allow_comments} = $cfg->AllowComments && ( $blog->allow_reg_comments || $blog->allow_unreg_comments ); my @cps = MT->captcha_providers; foreach my $cp (@cps) { if ( ( $blog->captcha_provider || '' ) eq $cp->{key} ) { $cp->{selected} = 1; } } $param{captcha_loop} = \@cps; } elsif ( $output eq 'cfg_trackbacks.tmpl' ) { $param{email_new_pings_1} = ( $obj->email_new_pings || 0 ) == 1; $param{email_new_pings_2} = ( $obj->email_new_pings || 0 ) == 2; $param{nofollow_urls} = $obj->nofollow_urls; $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' ); } elsif ( $output eq 'cfg_registration.tmpl' ) { $param{commenter_authenticators} = $obj->commenter_authenticators; my $registration = $cfg->CommenterRegistration; if ( $registration->{Allow} ) { $param{registration} = $blog->allow_commenter_regist ? 1 : 0; } else { $param{system_disallow_registration} = 1; } $param{allow_reg_comments} = $blog->allow_reg_comments; $param{allow_unreg_comments} = $blog->allow_unreg_comments; $param{require_typekey_emails} = $obj->require_typekey_emails; } elsif ( $output eq 'cfg_spam.tmpl' ) { my $threshold = $obj->junk_score_threshold || 0; $threshold = '+' . $threshold if $threshold > 0; $param{junk_score_threshold} = $threshold; $param{junk_folder_expiry} = $obj->junk_folder_expiry || 60; $param{auto_delete_junk} = $obj->junk_folder_expiry; } 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; } } if ( $blog->publish_queue ) { $param{publish_queue} = 1; } } elsif ( $output eq 'cfg_plugin.tmpl' ) { $app->add_breadcrumb( $app->translate('Plugin Settings') ); $param{blog_view} = 1; $app->build_plugin_table( param => \%param, scope => 'blog:' . $blog_id ); $param{can_config} = 1; } else { $app->add_breadcrumb( $app->translate('Settings') ); } ( my $offset = $obj->server_offset ) =~ s![-\.]!_!g; $offset =~ s!_0+$!!; # fix syntax highlight ->! $param{ 'server_offset_' . $offset } = 1; if ( $output eq 'cfg_comments.tmpl' ) { ## Load text filters. $param{text_filters_comments} = $app->load_text_filters( $obj->convert_paras_comments, 'comment' ); } elsif ( $output eq 'cfg_entry.tmpl' ) { ## Load text filters. $param{text_filters} = $app->load_text_filters( $obj->convert_paras, 'entry' ); } $param{nav_config} = 1; $param{error} = $app->errstr if $app->errstr; } 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( $app->translate('Edit TrackBack') ); $param{approved} = $app->param('approved'); $param{unapproved} = $app->param('unapproved'); $param{has_publish_access} = 1 if $app->user->is_superuser; $param{has_publish_access} = ( ( $perms->can_manage_feedback || $perms->can_edit_all_posts ) ? 1 : 0 ) unless $app->user->is_superuser; require MT::Trackback; if ( my $tb = MT::Trackback->load( $obj->tb_id ) ) { if ( $tb->entry_id ) { $param{entry_ping} = 1; require MT::Entry; if ( my $entry = MT::Entry->load( $tb->entry_id ) ) { $param{entry_title} = $entry->title; $param{entry_id} = $entry->id; unless ( $param{has_publish_access} ) { $param{has_publish_access} = ( $perms->can_publish_post && ( $app->user->id == $entry->author_id ) ) ? 1 : 0; } } } elsif ( $tb->category_id ) { $param{category_ping} = 1; require MT::Category; if ( my $cat = MT::Category->load( $tb->category_id ) ) { $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 ( $parent && ( $parent->isa('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( LISTING_DATETIME_FORMAT, $obj->created_on(), $blog, $app->user ? $app->user->preferred_language : undef ); $param{created_on_day_formatted} = format_ts( LISTING_DATE_FORMAT, $obj->created_on(), $blog, $app->user ? $app->user->preferred_language : undef ); $param{search_label} = $app->translate('TrackBacks'); $param{object_type} = 'ping'; $app->load_list_actions( $type, \%param ); # 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_comment', args => { blog_id => $blog_id } ) ); $app->add_breadcrumb( $app->translate('Edit Comment') ); $param{has_publish_access} = 1 if $app->user->is_superuser; $param{has_publish_access} = ( ( $perms->can_manage_feedback || $perms->can_edit_all_posts ) ? 1 : 0 ) unless $app->user->is_superuser; if ( my $entry = $obj->entry ) { my $title_max_len = const('DISPLAY_LENGTH_EDIT_COMMENT_TITLE'); $param{entry_title} = ( !defined( $entry->title ) || $entry->title eq '' ) ? $app->translate('(untitled)') : $entry->title; $param{entry_title} = substr_text( $param{entry_title}, 0, $title_max_len ) . '...' if $param{entry_title} && length_text( $param{entry_title} ) > $title_max_len; $param{entry_permalink} = $entry->permalink; unless ( $param{has_publish_access} ) { $param{has_publish_access} = ( $perms->can_publish_post && ( $app->user->id == $entry->author_id ) ) ? 1 : 0; } } 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( LISTING_DATETIME_FORMAT, $obj->created_on(), $blog, $app->user ? $app->user->preferred_language : undef ); $param{created_on_day_formatted} = format_ts( LISTING_DATE_FORMAT, $obj->created_on(), $blog, $app->user ? $app->user->preferred_language : undef ); $param{approved} = $app->param('approved'); $param{unapproved} = $app->param('unapproved'); $param{is_junk} = $obj->is_junk; $param{entry_class_label} = $obj->entry->class_label; $param{entry_class} = $obj->entry->class; ## 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 ); } if ( my $cmtr_id = $obj->commenter_id ) { my $cmtr = $app->model('author')->load($cmtr_id); if ($cmtr) { $param{is_mine} = 1 if $cmtr_id == $app->user->id; $param{commenter_approved} = $cmtr->commenter_status( $obj->blog_id ) == MT::Author::APPROVED(); $param{commenter_banned} = $cmtr->commenter_status( $obj->blog_id ) == MT::Author::BANNED(); $param{type_author} = 1 if MT::Author::AUTHOR() == $cmtr->type; $param{commenter_url} = $app->uri( mode => 'view', args => { '_type' => 'author', 'id' => $cmtr->id, } ) if ( MT::Author::AUTHOR() == $cmtr->type ) && $app->user->is_superuser; } if ( $obj->email !~ m/@/ ) { # no email for this commenter $param{email_withheld} = 1; } } $param{invisible_unregistered} = !$obj->visible && !$obj->commenter_id; $param{search_label} = $app->translate('Comments'); $param{object_type} = 'comment'; my $children = $app->build_comment_table( load_args => [ { parent_id => $obj->id }, { direction => 'descend' } ] ); $param{object_loop} = $children if @$children; $app->load_list_actions( $type, \%param ); } elsif ( $type eq 'author' ) { # TODO: Populate permissions / blogs for this user # populate blog_loop, permission_loop $param{is_me} = 1 if $id == $author->id; $param{editing_other_profile} = 1 if !$param{is_me} && $author->is_superuser; $param{userpic} = $obj->userpic_html(); require MT::Permission; # General permissions... my $sys_perms = MT::Permission->perms('system'); foreach (@$sys_perms) { $param{ 'perm_can_' . $_->[0] } = ($obj->is_superuser || $obj->permissions(0)->has( $_->[0] )) ? 1 : 0; } $param{perm_is_superuser} = $obj->is_superuser; require MT::Auth; if ( $app->user->is_superuser ) { $param{search_label} = $app->translate('Users'); $param{object_type} = 'author'; $param{can_edit_username} = 1; } $param{status_enabled} = $obj->is_active ? 1 : 0; $param{status_pending} = $obj->status == MT::Author::PENDING() ? 1 : 0; $param{can_modify_password} = ( $param{editing_other_profile} || $param{is_me} ) && MT::Auth->password_exists; $param{can_recover_password} = MT::Auth->can_recover_password; $param{languages} = $app->languages_list( $obj->preferred_language ) unless exists $param{langauges}; } elsif ( $type eq 'commenter' ) { $app->add_breadcrumb( $app->translate("Authenticated Commenters"), $app->uri( mode => 'list_commenter', 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 $app->load_list_actions( 'commenter', \%param ); # no native actions for this screen. delete $param{list_actions} if exists $param{list_actions}; $param{has_list_actions} = 1 if exists $param{more_list_actions}; $param{is_email_hidden} = $obj->is_email_hidden; $param{status} = { PENDING => "pending", APPROVED => "approved", BANNED => "banned" }->{ $obj->commenter_status($blog_id) }; $param{commenter_approved} = $obj->commenter_status($blog_id) == MT::Author::APPROVED(); $param{commenter_banned} = $obj->commenter_status($blog_id) == MT::Author::BANNED(); $param{commenter_url} = $obj->url if $obj->url; if ( $app->user->is_superuser() || ($perms && $perms->can_administer_blog ) ) { $param{object_type} = 'author'; $param{search_label} = $app->translate('Users'); } else { $param{object_type} = 'entry'; } } 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 ]; } 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 ); ## 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{search_label} = $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; $param{next_max} = 0 if ( $param{next_max} || 0 ) < $offset + 1; } # These are stubs for the commenter-scoped itemset references $param{list_actions} = []; $param{more_list_actions} = []; } $param{"tab_$tab"} = 1; $param{screen_class} = ( $tab ne "commenter" ) ? "list-comment" : 0; $param{screen_class} .= ( $tab eq $type ) ? " edit-${type}" : " edit-${type}-${tab}"; $param{is_me} = 1 if $obj->id == $app->user->id; $param{type_author} = 1 if MT::Author::AUTHOR() == $obj->type; } elsif ( $type eq 'asset' ) { my $asset_class = $app->model('asset'); $param{asset} = $obj; $param{search_label} = $app->translate('Assets'); my $hasher = $app->build_asset_hasher; $hasher->($obj, \%param, ThumbWidth => 240, ThumbHeight => 240); my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} ); require MT::Tag; my $tags = MT::Tag->join( $tag_delim, $obj->tags ); $param{tags} = $tags; my @related; if ($obj->parent) { my $parent = $asset_class->load($obj->parent); push @related, $hasher->($parent, { asset => $parent, is_parent => 1 }); push @related, map { $hasher->($_, { asset => $_, is_sibling => 1 }) } $asset_class->search({ id => { op => '!=', value => $obj->id }, class => '*', parent => $obj->parent }); } push @related, map { $hasher->($_, { asset => $_, is_child => 1 }) } $asset_class->search({ class => '*', parent => $obj->id, }); $param{related} = \@related if @related; my @appears_in; my $place_class = $app->model('objectasset'); my $place_iter = $place_class->load_iter({ asset_id => $obj->id }); while (my $place = $place_iter->()) { my $entry_class = $app->model($place->object_ds); my $entry = $entry_class->load($place->object_id); my %entry_data = ( id => $place->object_id, class => $entry->class_type, entry => $entry, title => $entry->title, ); if (my $ts = $entry->authored_on) { $entry_data{authored_on_ts} = $ts; $entry_data{authored_on_formatted} = format_ts( LISTING_DATETIME_FORMAT, $ts, undef, $app->user ? $app->user->preferred_language : undef ); } if (my $ts = $entry->created_on) { $entry_data{created_on_ts} = $ts; $entry_data{created_on_formatted} = format_ts( LISTING_DATETIME_FORMAT, $ts, undef, $app->user ? $app->user->preferred_language : undef ); } push @appears_in, \%entry_data; } if (11 == @appears_in) { pop @appears_in; $param{appears_in_more} = 1; } $param{appears_in} = \@appears_in if @appears_in; my $prev_asset = $obj->nextprev( direction => 'previous', terms => { class => '*', blog_id => $obj->blog_id }, ); my $next_asset = $obj->nextprev( direction => 'next', terms => { class => '*', blog_id => $obj->blog_id }, ); $param{previous_entry_id} = $prev_asset->id if $prev_asset; $param{next_entry_id} = $next_asset->id if $next_asset; } if ( $class->properties->{audit} ) { my $creator = MT::Author->load( { id => $obj->created_by(), type => MT::Author::AUTHOR() } ); if ($creator) { $param{created_by} = $creator->name; } if ( my $mod_by = $obj->modified_by() ) { my $modified = MT::Author->load( { id => $mod_by, type => MT::Author::AUTHOR() } ); if ($modified) { $param{modified_by} = $modified->name; } else { $param{modified_by} = $app->translate("(user deleted)"); } # Since legacy MT installs will still have a # timestamp type for their modified_on fields, # we cannot reliably disaply a modified on date # by default; we must only show the modification # date IF there is also a modified_by value. if ( my $ts = $obj->modified_on ) { $param{modified_on_ts} = $ts; $param{modified_on_formatted} = format_ts( LISTING_DATETIME_FORMAT, $ts, undef, $app->user ? $app->user->preferred_language : undef ); } } if ( my $ts = $obj->created_on ) { $param{created_on_ts} = $ts; $param{created_on_formatted} = format_ts( LISTING_DATETIME_FORMAT, $ts, undef, $app->user ? $app->user->preferred_language : undef ); } } $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' ) || ( $type eq 'page' ) ) { $param{entry_edit} = 1; if ($blog_id) { if ( $type eq 'entry' ) { $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; } elsif ( $type eq 'page' ) { $app->add_breadcrumb( $app->translate('Pages'), $app->uri( 'mode' => 'list_pages', args => { blog_id => $blog_id } ) ); $app->add_breadcrumb( $app->translate('New Page') ); $param{nav_new_page} = 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 = $blog_class->load($blog_id); $blog_timezone = $blog->server_offset(); if ( $type eq 'entry' ) { # We only use new entry defaults on new entries. 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; my @now = offset_time_list( time, $blog ); $param{authored_on_date} = $q->param('authored_on_date') || POSIX::strftime( "%Y-%m-%d", @now ); $param{authored_on_time} = $q->param('authored_on_time') || POSIX::strftime( "%H:%M:%S", @now ); } elsif ( $type eq 'author' ) { $param{create_personal_weblog} = $app->config->NewUserAutoProvisioning ? 1 : 0 unless exists $param{create_personal_weblog}; $param{can_modify_password} = MT::Auth->password_exists; $param{can_recover_password} = MT::Auth->can_recover_password; # $param{editing_other_profile} = 1; } elsif ( $type eq 'template' ) { my $new_tmpl = $q->param('create_new_template'); my $template_type; if ($new_tmpl) { if ( $new_tmpl =~ m/^blank:(.+)/ ) { $template_type = $1; $param{type} = $1; } elsif ( $new_tmpl =~ m/^default:([^:]+):(.+)/ ) { $template_type = $1; $template_type = 'custom' if $template_type eq 'module'; my $template_id = $2; my $set = $blog ? $blog->template_set : undef; require MT::DefaultTemplates; my $def_tmpl = MT::DefaultTemplates->templates($set) || []; my ($tmpl) = grep { $_->{identifier} eq $template_id } @$def_tmpl; $param{text} = $app->translate_templatized( $tmpl->{text} ) if $tmpl; $param{type} = $template_type; } } else { $template_type = $q->param('type'); $template_type = 'custom' if 'module' eq $template_type; $param{type} = $template_type; } return $app->errtrans("Create template requires type") unless $template_type; $param{nav_templates} = 1; my $tab; # FIXME: enumeration of types if ( $template_type eq 'index' ) { $tab = 'index'; $param{template_group_trans} = $app->translate('index'); } elsif ($template_type eq 'archive' || $template_type eq 'individual' || $template_type eq 'category' || $template_type eq 'page' ) { $tab = 'archive'; $param{template_group_trans} = $app->translate('archive'); $param{type_archive} = 1; my @types = ( { key => 'archive', label => $app->translate('Archive') }, { key => 'individual', label => $app->translate('Entry or Page') }, ); $param{new_archive_types} = \@types; } elsif ( $template_type eq 'custom' ) { $tab = 'module'; $param{template_group_trans} = $app->translate('module'); } elsif ( $template_type eq 'widget' ) { $tab = 'widget'; $param{template_group_trans} = $app->translate('widget'); } else { $tab = 'system'; $param{template_group_trans} = $app->translate('system'); } $param{template_group} = $tab; $app->translate($tab); $app->add_breadcrumb( $app->translate('New Template') ); # FIXME: enumeration of types $param{has_name} = $template_type eq 'index' || $template_type eq 'custom' || $template_type eq 'widget' || $template_type eq 'archive' || $template_type eq 'category' || $template_type eq 'page' || $template_type eq 'individual'; $param{has_outfile} = $template_type eq 'index'; $param{has_rebuild} = ( ( $template_type eq 'index' ) && ( ( $blog->custom_dynamic_templates || "" ) ne 'all' ) ); $param{custom_dynamic} = $blog && $blog->custom_dynamic_templates eq 'custom'; $param{has_build_options} = $blog && ($blog->custom_dynamic_templates eq 'custom' || $param{has_rebuild}); # FIXME: enumeration of types $param{is_special} = $param{type} ne 'index' && $param{type} ne 'archive' && $param{type} ne 'category' && $param{type} ne 'page' && $param{type} ne 'individual'; $param{has_build_options} = $param{has_build_options} && $param{type} ne 'custom' && $param{type} ne 'widget' && !$param{is_special}; $param{rebuild_me} = 1; $param{name} = MT::Util::decode_url( $app->param('name') ) if $app->param('name'); } elsif ( $type eq 'blog' ) { $app->add_breadcrumb( $app->translate('New Blog') ); ( my $tz = $cfg->DefaultTimezone ) =~ s![-\.]!_!g; $tz =~ s!_00$!!; # fix syntax highlight ->! $param{ 'server_offset_' . $tz } = 1; $param{'can_edit_config'} = $app->user->can_create_blog; $param{'can_set_publish_paths'} = $app->user->can_create_blog; my $sets = $app->registry("template_sets"); $sets->{$_}{key} = $_ for keys %$sets; $sets->{'mt_blog'}{selected} = 1; $sets = $app->filter_conditional_list([ values %$sets ]); no warnings; @$sets = sort { $a->{order} <=> $b->{order} } @$sets; $param{'template_set_loop'} = $sets; $param{'template_set_index'} = $#$sets; } } # Regardless of whether the obj is new, load data into $param if ( ( $type eq 'entry' ) || ( $type eq 'page' ) || ( $type eq 'template' ) ) { # autosave support, but don't bother if we're reediting if ( !$app->param('reedit') ) { my $sess_obj = $app->autosave_session_obj; if ($sess_obj) { $param{autosaved_object_exists} = 1; $param{autosaved_object_ts} = MT::Util::epoch2ts( $blog, $sess_obj->start ); } } } # Regardless of whether the obj is new, load data into $param if ( ( $type eq 'entry' ) || ( $type eq 'page' ) ) { ## Load categories and process into loop for category pull-down. require MT::Placement; my $cat_id = $param{category_id}; my $depth = 0; my %places; # set the dirty flag in js? $param{dirty} = $q->param('dirty') ? 1 : 0; if ($id) { my @places = MT::Placement->load( { entry_id => $id, is_primary => 0 } ); %places = map { $_->category_id => 1 } @places; } my $cats = $q->param('category_ids'); if ( defined $cats ) { if ( my @cats = split /,/, $cats ) { $cat_id = $cats[0]; %places = map { $_ => 1 } @cats; } } if ( $q->param('reedit') ) { $param{reedit} = 1; if ( !$q->param('basename_manual') ) { $param{'basename'} = ''; } } if ($blog) { $param{file_extension} = $blog->file_extension || ''; $param{file_extension} = '.' . $param{file_extension} if $param{file_extension} ne ''; } else { $param{file_extension} = 'html'; } ## Now load user's preferences and customization for new/edit ## entry page. if ($perms) { my $pref_param = $app->load_entry_prefs( $perms->entry_prefs ); %param = ( %param, %$pref_param ); $param{disp_prefs_bar_colspan} = $param{new_object} ? 1 : 2; # Completion for tags my $auth_prefs = $author->entry_prefs; if ( my $delim = chr( $auth_prefs->{tag_delim} ) ) { if ( $delim eq ',' ) { $param{'auth_pref_tag_delim_comma'} = 1; } elsif ( $delim eq ' ' ) { $param{'auth_pref_tag_delim_space'} = 1; } else { $param{'auth_pref_tag_delim_other'} = 1; } $param{'auth_pref_tag_delim'} = $delim; } require MT::ObjectTag; my $count = MT::Tag->count( undef, { 'join' => MT::ObjectTag->join_on( 'tag_id', { blog_id => $blog_id, object_datasource => MT::Entry->datasource }, { unique => 1 } ) } ); if ( $count > 1000 ) { # FIXME: Configurable limit? $param{defer_tag_load} = 1; } else { require JSON; $param{tags_js} = JSON::objToJson( MT::Tag->cache( blog_id => $blog_id, class => 'MT::Entry', private => 1 ) ); } $param{can_edit_categories} = $perms->can_edit_categories; } my $data = $app->_build_category_list( blog_id => $blog_id, markers => 1, type => $class->container_type, ); my $top_cat = $cat_id; my @sel_cats; my $cat_tree = []; if ( $type eq 'page' ) { push @$cat_tree, { id => -1, label => '/', basename => '/', path => [], }; $top_cat ||= -1; } foreach (@$data) { next unless exists $_->{category_id}; if ( $type eq 'page' ) { $_->{category_path_ids} ||= []; unshift @{ $_->{category_path_ids} }, -1; } push @$cat_tree, { id => $_->{category_id}, label => $_->{category_label} . ( $type eq 'page' ? '/' : '' ), basename => $_->{category_basename} . ( $type eq 'page' ? '/' : '' ), path => $_->{category_path_ids} || [], }; push @sel_cats, $_->{category_id} if $places{ $_->{category_id} } && $_->{category_id} != $cat_id; } $param{category_tree} = $cat_tree; unshift @sel_cats, $top_cat if defined $top_cat && $top_cat ne ""; $param{selected_category_loop} = \@sel_cats; $param{have_multiple_categories} = scalar @$data > 1; $param{basename_limit} = ( $blog ? $blog->basename_limit : 0 ) || 30; if ( $q->param('tags') ) { $param{tags} = $q->param('tags'); } else { if ($obj) { my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} ); require MT::Tag; my $tags = MT::Tag->join( $tag_delim, $obj->tags ); $param{tags} = $tags; } } ## Load text filters if user displays them my %entry_filters; if ( defined( my $filter = $q->param('convert_breaks') ) ) { $entry_filters{$filter} = 1; } elsif ($obj) { %entry_filters = map { $_ => 1 } @{ $obj->text_filters }; } elsif ($blog) { my $cb = $author->text_format || $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) { if ( !defined $param{convert_breaks} ) { my $cb = $blog->convert_paras; $cb = '__default__' if $cb eq '1'; $param{convert_breaks} = $cb; } my $ext = ( $blog->file_extension || '' ); $ext = '.' . $ext if $ext ne ''; $param{blog_file_extension} = $ext; } my $rte; if ($param{convert_breaks} eq 'richtext') { ## Rich Text editor $rte = lc($app->config('RichTextEditor')); } else { $rte = 'archetype'; } my $editors = $app->registry("richtext_editors"); my $edit_reg = $editors->{$rte} || $editors->{archetype}; my $rich_editor_tmpl; if ($rich_editor_tmpl = $edit_reg->{plugin}->load_tmpl($edit_reg->{template})) { $param{rich_editor} = $rte; $param{rich_editor_tmpl} = $rich_editor_tmpl; } $param{object_type} = $type; $param{quickpost_js} = $app->quickpost_js($type); if ( 'page' eq $type ) { $param{search_label} = $app->translate('pages'); $param{output} = 'edit_entry.tmpl'; } $param{sitepath_configured} = $blog && $blog->site_path ? 1 : 0; } elsif ( $type eq 'template' ) { my $set = $blog ? $blog->template_set : undef; require MT::DefaultTemplates; my $tmpls = MT::DefaultTemplates->templates($set); my @tmpl_ids; foreach my $dtmpl (@$tmpls) { if ( !$param{has_name} ) { if ($obj->type eq 'email') { if ($dtmpl->{identifier} eq $obj->identifier) { $param{template_name_label} = $dtmpl->{label}; $param{template_name} = $dtmpl->{name}; } } else { if ( $dtmpl->{type} eq $obj->type ) { $param{template_name_label} = $dtmpl->{label}; $param{template_name} = $dtmpl->{name}; } } } if ( $dtmpl->{type} eq 'index' ) { push @tmpl_ids, { label => $dtmpl->{label}, key => $dtmpl->{key}, selected => $dtmpl->{key} eq ( ( $obj ? $obj->identifier : undef ) || '' ), }; } } $param{index_identifiers} = \@tmpl_ids; $param{"type_$param{type}"} = 1; if ($perms) { my $pref_param = $app->load_template_prefs( $perms->template_prefs ); %param = ( %param, %$pref_param ); } # Populate structure for template snippets if ( my $snippets = $app->registry('template_snippets') || {} ) { my @snippets; for my $snip_id ( keys %$snippets ) { my $label = $snippets->{$snip_id}{label}; $label = $label->() if ref($label) eq 'CODE'; push @snippets, { id => $snip_id, trigger => $snippets->{$snip_id}{trigger}, label => $label, content => $snippets->{$snip_id}{content}, }; } @snippets = sort { $a->{label} cmp $b->{label} } @snippets; $param{template_snippets} = \@snippets; } # Populate structure for tag documentation my $all_tags = MT::Component->registry("tags"); my $tag_docs = {}; foreach my $tag_set (@$all_tags) { my $url = $tag_set->{help_url}; $url = $url->() if ref($url) eq 'CODE'; # hey, at least give them a google search $url ||= 'http://www.google.com/search?q=mt%t'; my $tag_list = ''; foreach my $type (qw( block function )) { my $tags = $tag_set->{$type} or next; $tag_list .= ($tag_list eq '' ? '' : ',') . join(",", keys(%$tags)); } $tag_list =~ s/(^|,)plugin(,|$)/,/; if (exists $tag_docs->{$url}) { $tag_docs->{$url} .= ',' . $tag_list; } else { $tag_docs->{$url} = $tag_list; } } $param{tag_docs} = $tag_docs; $param{link_doc} = $app->help_url('appendices/tags/'); # template language $param{template_lang} = 'html'; if ( $obj && $obj->outfile ) { if ( $obj->outfile =~ m/\.(css|js|html|php|pl|asp)$/ ) { $param{template_lang} = { css => 'css', js => 'javascript', html => 'html', php => 'php', pl => 'perl', asp => 'asp', }->{$1}; } } } elsif ( $type eq 'blog' ) { if ( !$param{site_path} && !( $param{site_path} = $app->config('DefaultSiteRoot') ) ) { my $cwd = ''; if ( $ENV{MOD_PERL} ) { ## If mod_perl, just use the document root. $cwd = $app->{apache}->document_root; } else { $cwd = $ENV{DOCUMENT_ROOT} || $app->mt_dir; } $cwd = File::Spec->canonpath($cwd); $cwd =~ s!([\\/])cgi(?:-bin)?([\\/].*)?$!$1!; $cwd =~ s!([\\/])mt[\\/]?$!$1!i; $param{suggested_site_path} = $cwd; } if ( !$param{id} ) { if ( $param{site_path} ) { $param{site_path} = File::Spec->catdir( $param{site_path}, 'BLOG-NAME' ); } else { $param{suggested_site_path} = File::Spec->catdir( $param{suggested_site_path}, 'BLOG-NAME' ); } } # If not yet defined, set the site_url to the config default, if one exists. $param{site_url} ||= $app->config('DefaultSiteURL'); if ( !$param{site_url} ) { $param{suggested_site_url} = $app->base . '/'; $param{suggested_site_url} =~ s!/cgi(?:-bin)?(/.*)?$!/!; $param{suggested_site_url} =~ s!/mt/?$!/!i; } if ( !$param{id} ) { if ( $param{site_url} ) { $param{site_url} .= '/' unless $param{site_url} =~ /\/$/; $param{site_url} .= 'BLOG-NAME/'; } else { $param{suggested_site_url} .= '/' unless $param{suggested_site_url} =~ /\/$/; $param{suggested_site_url} .= 'BLOG-NAME/'; } } } elsif ( $type eq 'author' ) { $app->add_breadcrumb( $app->translate("Users"), $app->user->is_superuser ? $app->uri( mode => 'list_authors' ) : undef ); my $auth_prefs; if ($obj) { $app->add_breadcrumb( $obj->name ); $param{languages} = $app->languages_list( $obj->preferred_language ); $auth_prefs = $obj->entry_prefs; } else { $app->add_breadcrumb( $app->translate("Create User") ); $param{languages} = $app->languages_list( $app->config('DefaultUserLanguage') ) unless ( exists $param{languages} ); $auth_prefs = { tag_delim => $app->config->DefaultUserTagDelimiter } unless ( exists $param{'auth_pref_tag_delim'} ); } $param{text_filters} = $app->load_text_filters( $obj ? $obj->text_format : undef, 'comment' ); unless ( exists $param{'auth_pref_tag_delim'} ) { my $delim = chr( $auth_prefs->{tag_delim} ); if ( $delim eq ',' ) { $param{'auth_pref_tag_delim_comma'} = 1; } elsif ( $delim eq ' ' ) { $param{'auth_pref_tag_delim_space'} = 1; } else { $param{'auth_pref_tag_delim_other'} = 1; } $param{'auth_pref_tag_delim'} = $delim; } $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 && $cmntr->url ) { $param{commenter_url} = $cmntr->url; } } $param{page_actions} = $app->page_actions($type, $obj); if ( $class->can('class_label') ) { $param{object_label} = $class->class_label; } if ( $class->can('class_label_plural') ) { $param{object_label_plural} = $class->class_label_plural; } my $tmpl_file = $param{output} || "edit_${type}.tmpl"; $param{object_type} ||= $type; unless ( $param{screen_class} ) { $param{screen_id} = "edit-$type"; $param{screen_class} = "edit-$type"; $param{screen_class} .= " edit-entry" if $param{object_type} eq "page"; # to piggyback on edit-entry styles } return $app->load_tmpl( $tmpl_file, \%param ); } sub CMSTemplateParam_edit_asset { my ($cb, $app, $param, $tmpl) = @_; my $asset = $param->{asset} or return; $asset->edit_template_param(@_); } sub load_default_entry_prefs { my $app = shift; my $prefs; require MT::Permission; my $blog_id; $blog_id = $app->blog->id if $app->blog; my $perm = MT::Permission->load( { blog_id => $blog_id, author_id => 0 } ); my %default = %{ $app->config->DefaultEntryPrefs }; if ( $perm && $perm->entry_prefs ) { $prefs = $perm->entry_prefs; } else { my %default = %{ $app->config->DefaultEntryPrefs }; if ( lc( $default{type} ) ne 'default' ) { $prefs = 'Advanced'; } elsif ( lc( $default{type} ) eq 'custom' ) { my %map = ( Category => 'category', Excerpt => 'excerpt', Keywords => 'keywords', Tags => 'tags', Publishing => 'publishing', Feedback => 'feedback', ); my @p; foreach my $p ( keys %map ) { push @p, $map{$p} . ':' . ( $default{$p} || $default{ lc $p } ) if ( $default{$p} || $default{ lc $p } ); } $prefs = join ',', @p; $prefs ||= 'Custom'; } $default{button} = 'Bottom' if lc( $default{button} ) eq 'below'; $default{button} = 'Top' if lc( $default{button} ) eq 'above'; $prefs .= '|' . $default{button} if $prefs; } $prefs ||= 'Default|Bottom'; return $prefs; } sub load_template_prefs { my $app = shift; my ($prefs) = @_; my %param; if ( !$prefs ) { $prefs = ''; } my @p = split /,/, $prefs; for my $p (@p) { if ( $p =~ m/^(.+?):(\d+)$/ ) { $param{ 'disp_prefs_height_' . $1 } = $2; } } \%param; } sub _parse_entry_prefs { my $app = shift; my ( $prefs, $param ) = @_; my @p = split /,/, $prefs; for my $p (@p) { if ( $p =~ m/^(.+?):(\d+)$/ ) { my ( $name, $num ) = ( $1, $2 ); if ($num) { $param->{ 'disp_prefs_height_' . $name } = $num; } $param->{ 'disp_prefs_show_' . $name } = 1; } else { $p = 'Default' if lc($p) eq 'basic'; if ( ( lc($p) eq 'advanced' ) || ( lc($p) eq 'default' ) ) { $param->{ 'disp_prefs_' . $p } = 1; foreach my $def ( qw(title body category tags keywords feedback publishing )) { $param->{ 'disp_prefs_show_' . $def } = 1; } if ( lc($p) eq 'advanced' ) { foreach my $def (qw(excerpt feedback)) { $param->{ 'disp_prefs_show_' . $def } = 1; } } } else { $param->{ 'disp_prefs_show_' . $p } = 1; } } } } sub load_entry_prefs { my $app = shift; my ($prefs) = @_; my %param; my $pos; my $is_from_db = 1; # defaults: if ( !$prefs ) { $prefs = $app->load_default_entry_prefs; ( $prefs, $pos ) = split /\|/, $prefs; $is_from_db = 0; $app->_parse_entry_prefs( $prefs, \%param ); my @fields; foreach ( keys %param ) { if (m/^disp_prefs_show_(.+)/) { push @fields, { name => $1 }; } } $param{disp_prefs_default_fields} = \@fields; } else { ( $prefs, $pos ) = split /\|/, $prefs; } $app->_parse_entry_prefs( $prefs, \%param ); if ($is_from_db) { my $default_prefs = $app->load_default_entry_prefs; ( $default_prefs, my ($default_pos) ) = split /\|/, $default_prefs; $pos ||= $default_pos; $app->_parse_entry_prefs( $default_prefs, \my %def_param ); my @fields; foreach ( keys %def_param ) { if (m/^disp_prefs_show_(.+)/) { push @fields, { name => $1 }; } } if ( $prefs eq 'Default' ) { foreach my $p ( keys %param ) { delete $param{$p} if $p =~ m/^disp_prefs_show_/; } } if ( $param{disp_prefs_Default} ) { # apply default settings %param = ( %def_param, %param ); } $param{disp_prefs_default_fields} = \@fields; } $pos ||= 'Bottom'; if ( !exists $param{'disp_prefs_Default'} && !exists $param{'disp_prefs_Advanced'} ) { $param{'disp_prefs_Custom'} = 1; } if ( lc $pos eq 'both' ) { $param{'position_actions_top'} = 1; $param{'position_actions_bottom'} = 1; $param{'position_actions_both'} = 1; } else { $param{ 'position_actions_' . $pos } = 1; } \%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*//; $log = encode_html($log); for ( my $j = $i + 1 ; $j < scalar(@log) ; $j++ ) { my $line = encode_html( $log[$j] ); if ( $line =~ m/^\t+(.*)$/s ) { $i = $j; $log .= "<br />" . $1; } else { last; } } push @junk, { test => $test, score => $score, log => $log }; } $param->{junk_log_loop} = \@junk; \@junk; } sub CMSViewPermissionFilter_blog { my ( $eh, $app, $id ) = @_; my $perms = $app->permissions; if ( $id && ( $perms->can_set_publish_paths && !$perms->can_administer_blog ) ) { return 1 if 'view' eq $app->mode; } if ( ( $id && !( $perms->can_edit_config || $perms->can_set_publish_paths || $perms->can_manage_feedback ) ) || ( !$id && !$app->user->can_create_blog ) ) { return 0; } 1; } sub CMSViewPermissionFilter_template { my ( $eh, $app, $id ) = @_; my $perms = $app->permissions; return !$id || ($perms && $perms->can_edit_templates) || (!$app->blog && $app->user->can_edit_templates); } sub CMSViewPermissionFilter_page { my ( $eh, $app, $id, $objp ) = @_; my $perms = $app->permissions; if ( !$perms->can_manage_pages ) { return 0; } 1; } sub CMSViewPermissionFilter_entry { my ( $eh, $app, $id, $objp ) = @_; my $perms = $app->permissions; if ( !$id && !$perms->can_create_post ) { return 0; } if ($id) { my $obj = $objp->force(); if ( !$perms->can_edit_entry( $obj, $app->user ) ) { return 0; } } 1; } sub CMSViewPermissionFilter_author { my ( $eh, $app, $id ) = @_; return $id && ( $app->user->id == $id ); } sub CMSViewPermissionFilter_folder { my ( $eh, $app, $id ) = @_; my $perms = $app->permissions; return $perms->can_manage_pages(); } sub CMSViewPermissionFilter_category { my ( $eh, $app, $id ) = @_; my $perms = $app->permissions; return $perms->can_edit_categories(); } sub CMSViewPermissionFilter_asset { my ($eh, $app, $id) = @_; my $perms = $app->permissions; return $perms->can_edit_assets(); } sub CMSViewPermissionFilter_commenter { my $eh = shift; my ( $app, $id ) = @_; my $auth = MT::Author->load( { id => $id, type => MT::Author::COMMENTER() } ); $auth ? 1 : 0; } sub CMSViewPermissionFilter_comment { my $eh = shift; my ( $app, $id, $objp ) = @_; return 0 unless ($id); my $obj = $objp->force() or return 0; require MT::Entry; my $entry = MT::Entry->load( $obj->entry_id ) or return 0; my $perms = $app->permissions; if ( !( $entry->author_id == $app->user->id || $perms->can_edit_all_posts || $perms->can_manage_feedback ) ) { return 0; } 1; } sub CMSViewPermissionFilter_ping { my $eh = shift; my ( $app, $id, $objp ) = @_; my $obj = $objp->force() or return 0; require MT::Trackback; my $tb = MT::Trackback->load( $obj->tb_id ); my $perms = $app->permissions; if ($tb) { if ( $tb->entry_id ) { require MT::Entry; my $entry = MT::Entry->load( $tb->entry_id ); return ( $entry->author_id == $app->user->id || $perms->can_manage_feedback || $perms->can_edit_all_posts ); } elsif ( $tb->category_id ) { require MT::Category; my $cat = MT::Category->load( $tb->category_id ); return $cat && $perms->can_edit_categories; } } else { return 0; # no TrackBack center--no edit } } sub CMSSaveFilter_author { my ( $eh, $app ) = @_; my $status = $app->param('status'); return 1 if $status and $status == MT::Author::INACTIVE(); require MT::Auth; my $auth_mode = $app->config('AuthenticationModule'); my ($pref) = split /\s+/, $auth_mode; my $name = $app->param('name'); if ( $pref eq 'MT' ) { if ( defined $name ) { $name =~ s/(^\s+|\s+$)//g; $app->param( 'name', $name ); } return $eh->error( $app->translate("User requires username") ) if ( !$name ); } require MT::Author; my $existing = MT::Author->load( { name => $name, type => MT::Author::AUTHOR() } ); my $id = $app->param('id'); if ( $existing && ( ( $id && $existing->id ne $id ) || !$id ) ) { return $eh->error( $app->translate("A user with the same name already exists.") ); } return 1 if ( $pref ne 'MT' ); if ( !$app->param('id') ) { # it's a new object return $eh->error( $app->translate("User requires password") ) if ( !$app->param('pass') ); return $eh->error( $app->translate("User requires password recovery word/phrase") ) if ( !$app->param('hint') ); } return $eh->error( MT->translate("Email Address is required for password recovery") ) unless $app->param('email'); if ( $app->param('url') ) { my $url = $app->param('url'); return $eh->error( MT->translate("Website URL is imperfect") ) unless is_url($url); } 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 ) && ( $obj->id ne $app->param('id') ) ) { return $eh->error( $app->translate( "The e-mail address you entered is already on the Notification List for this blog." ) ); } } 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 blog.") ); } 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 ); } my $perms = $app->permissions; my $screen = $app->param('cfg_screen') || ''; return $eh->error( MT->translate("You did not specify a blog name.") ) if ( !( $screen && $perms->can_edit_config ) && ( defined $app->param('name') && ( $app->param('name') eq '' ) ) ); return $eh->error( MT->translate("Site URL must be an absolute URL.") ) if ( $screen eq 'cfg_archives' ) && $perms->can_set_publish_paths && $app->param('site_url') !~ m.^https?://.; return $eh->error( MT->translate("Archive URL must be an absolute URL.") ) if ( $screen eq 'cfg_archives' ) && $perms->can_set_publish_paths && $app->param('archive_url') !~ m.^https?://. && $app->param('enable_archive_paths'); return $eh->error( MT->translate("You did not specify an Archive Root.") ) if ( $screen eq 'cfg_archives' ) && $app->param('archive_path') =~ m/^\s*$/ && $app->param('enable_archive_paths'); return 1; } sub CMSSaveFilter_folder { 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 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, $original ) = @_; my $perms = $app->permissions; return 1 unless $perms->can_publish_post || $perms->can_edit_categories || $perms->can_edit_all_posts || $perms->can_manage_feedback; unless ( $perms->can_edit_all_posts || $perms->can_manage_feedback ) { return 1 unless $perms->can_publish_post || $perms->can_edit_categories; require MT::Trackback; my $tb = MT::Trackback->load( $obj->tb_id ); if ($tb) { if ( $tb->entry_id ) { require MT::Entry; my $entry = MT::Entry->load( $tb->entry_id ); return 1 unless ( $entry->author_id == $app->user->id ) && $perms->can_publish_post; } } elsif ( $tb->category_id ) { require MT::Category; my $cat = MT::Category->load( $tb->category_id ); return 1 unless $cat && $perms->can_edit_categories; } } my $status = $app->param('status'); if ( $status eq 'publish' ) { $obj->approve; if ( $original->junk_status != $obj->junk_status ) { $app->run_callbacks( 'handle_ham', $app, $obj ); } } elsif ( $status eq 'moderate' ) { $obj->moderate; $obj->junk_status(0); } elsif ( $status eq 'junk' ) { $obj->junk; if ( $original->junk_status != $obj->junk_status ) { $app->run_callbacks( 'handle_spam', $app, $obj ); } } return 1; } sub CMSPreSave_comment { my $eh = shift; my ( $app, $obj, $original ) = @_; my $perms = $app->permissions; return 1 unless $perms->can_publish_post || $perms->can_edit_all_posts || $perms->can_manage_feedback; unless ( $perms->can_edit_all_posts || $perms->can_manage_feedback ) { return 1 unless $perms->can_publish_post; require MT::Entry; my $entry = MT::Entry->load( $obj->entry_id ) or return 1; return 1 unless $entry->author_id == $app->user->id; } my $status = $app->param('status'); if ( $status eq 'publish' ) { $obj->approve; if ( $original->junk_status != $obj->junk_status ) { $app->run_callbacks( 'handle_ham', $app, $obj ); } } elsif ( $status eq 'moderate' ) { $obj->moderate; $obj->junk_status(0); } elsif ( $status eq 'junk' ) { $obj->junk; if ( $original->junk_status != $obj->junk_status ) { $app->run_callbacks( 'handle_spam', $app, $obj ); } } return 1; } sub CMSPreSave_author { my $eh = shift; my ( $app, $obj, $original ) = @_; # 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); } elsif ( !$obj->id ) { $obj->password('(none)'); } my ( $delim, $delim2 ) = $app->param('tag_delim'); $delim = $delim ? $delim : $delim2; if ( $delim =~ m/comma/i ) { $delim = ord(','); } elsif ( $delim =~ m/space/i ) { $delim = ord(' '); } else { $delim = ord(','); } $obj->entry_prefs( 'tag_delim' => $delim ); unless ( $obj->id ) { $obj->created_by( $app->user->id ); } 1; } sub CMSPreSave_template { my $eh = shift; my ( $app, $obj ) = @_; $obj->rebuild_me(0) if $app->param('current_rebuild_me') && !$app->param('rebuild_me'); $obj->build_dynamic(0) if $app->param('current_build_dynamic') && !$app->param('build_dynamic'); ## Strip linefeed characters. ( my $text = $obj->text ) =~ tr/\r//d; if ($text =~ m/<(MT|_)_trans/i) { $text = $app->translate_templatized($text); } $obj->text($text); # update text heights if necessary if ( my $perms = $app->permissions ) { my $prefs = $perms->template_prefs || ''; my $text_height = $app->param('text_height'); if ( defined $text_height ) { my ($pref_text_height) = $prefs =~ m/\btext:(\d+)\b/; $pref_text_height ||= 0; if ( $text_height != $pref_text_height ) { if ( $prefs =~ m/\btext\b/ ) { $prefs =~ s/\btext(:\d+)\b/text:$text_height/; } else { $prefs = 'text:' . $text_height . ',' . $prefs; } } } if ( $prefs ne ( $perms->template_prefs || '' ) ) { $perms->template_prefs($prefs); $perms->save; } } 1; } 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_web_services' ) { } elsif ( $screen eq 'cfg_archives' ) { @fields = qw(file_extension); } elsif ( $screen eq 'cfg_templatemaps' ) { } elsif ( $screen eq 'cfg_comments' ) { @fields = qw( allow_comment_html autolink_urls use_comment_confirmation ); } elsif ( $screen eq 'cfg_registration' ) { @fields = qw( allow_commenter_regist require_comment_emails allow_unreg_comments require_typekey_emails ); } elsif ( $screen eq 'cfg_entry' ) { @fields = qw( allow_comments_default allow_pings_default ); } elsif ( $screen eq 'cfg_trackbacks' ) { @fields = qw( allow_pings moderate_pings autodiscover_links internal_autodiscovery ); } elsif ( $screen eq 'cfg_plugins' ) { } for my $cb (@fields) { unless ( defined $app->param($cb) ) { # two possibilities: user unchecked the option, or user was not allowed to # set the value (and therefore there was no field to submit). my $perms = $app->permissions; if ( $app->user->is_superuser || ( $perms && ( $perms->can_administer_blog || $perms->can_edit_config ) ) ) { $obj->$cb(0); } else { delete $obj->{column_values}->{$cb}; delete $obj->{changed_cols}->{$cb}; } } } if ( $screen eq 'cfg_comments' ) { # value for comments: 1 == Accept from anyone # 2 == Accept authenticated only # 0 == No comments if ( $app->param('allow_comments') ) { $obj->allow_reg_comments(1); } else { $obj->allow_unreg_comments(0); $obj->allow_reg_comments(0); } $obj->moderate_unreg_comments( $app->param('moderate_comments') ); $obj->nofollow_urls( $app->param('nofollow_urls') ? 1 : 0 ); $obj->follow_auth_links( $app->param('follow_auth_links') ? 1 : 0 ); my $cp_old = $obj->captcha_provider; $obj->captcha_provider( $app->param('captcha_provider') ); my $rebuild = $cp_old ne $obj->captcha_provider ? 1 : 0; $app->add_return_arg( need_full_rebuild => 1 ) if $rebuild; } if ( $screen eq 'cfg_web_services' ) { my $tok = ''; ( $tok = $obj->remote_auth_token ) =~ s/\s//g; $obj->remote_auth_token($tok); my $ping_servers = $app->registry('ping_servers'); my @pings_list; push @pings_list, $_ foreach grep { defined( $app->param( 'ping_' . $_ ) ) && $app->param( 'ping_' . $_ ) } keys %$ping_servers; $obj->update_pings( join( ',', @pings_list ) ); } if ( $screen eq 'cfg_entry' ) { my %param = $_[0] ? %{ $_[0] } : (); my $pref_param = $app->load_entry_prefs; %param = ( %param, %$pref_param ); } if ( $screen eq 'cfg_trackbacks' ) { if ( my $pings = $app->param('allow_pings') ) { if ($pings) { $obj->moderate_pings( $app->param('moderate_pings') ); $obj->nofollow_urls( $app->param('nofollow_urls') ? 1 : 0 ); } else { $obj->moderate_pings(1); $obj->email_new_pings(1); } } } if ( $screen eq 'cfg_registration' ) { $obj->allow_commenter_regist( $app->param('allow_commenter_regist') ); $obj->allow_unreg_comments( $app->param('allow_unreg_comments') ); if ( $app->param('allow_unreg_comments') ) { $obj->require_comment_emails( $app->param('require_comment_emails') ); } else { $obj->require_comment_emails(0); } my @authenticators; my $c = $app->registry('commenter_authenticators'); foreach ( keys %$c ) { if ( $app->param( 'enabled_' . $_ ) ) { push @authenticators, $_; } } push @authenticators, 'MovableType' if $app->param('enabled_MovableType'); my $c_old = $obj->commenter_authenticators; $obj->commenter_authenticators( join( ',', @authenticators ) ); my $rebuild = $obj->commenter_authenticators ne $c_old ? 1 : 0; if ( $app->param('enabled_TypeKey') ) { $rebuild = $obj->require_typekey_emails ? 0 : 1; $obj->require_typekey_emails( $app->param('require_typekey_emails') ); } else { $obj->require_typekey_emails(0); } my $tok = ''; ( $tok = $obj->remote_auth_token ) =~ s/\s//g; $obj->remote_auth_token($tok); $app->add_return_arg( need_full_rebuild => 1 ) if $rebuild; } if ( $screen eq 'cfg_spam' ) { my $threshold = $app->param('junk_score_threshold'); $threshold =~ s/\+//; $threshold ||= 0; $obj->junk_score_threshold($threshold); if ( my $expiry = $app->param('junk_folder_expiry') ) { $obj->junk_folder_expiry($expiry); } my $perms = $app->permissions; unless ( defined $app->param('auto_delete_junk') ) { if ( $app->user->is_superuser || ( $perms && ( $perms->can_administer_blog || $perms->can_edit_config ) ) ) { $obj->junk_folder_expiry(0); } else { delete $obj->{column_values}->{junk_folder_expiry}; delete $obj->{changed_cols}->{junk_folder_expiry}; } } } if ( $screen eq 'cfg_entry' ) { my %param = $_[0] ? %{ $_[0] } : (); my $pref_param = $app->load_entry_prefs; %param = ( %param, %$pref_param ); $param{ 'sort_order_posts_' . ( $obj->sort_order_posts || 0 ) } = 1; $param{words_in_excerpt} = 40 unless defined $param{words_in_excerpt} && $param{words_in_excerpt} ne ''; 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); } $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* } if ( $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') ); } 1; } sub CMSPostSave_folder { my $eh = shift; my ( $app, $obj, $original ) = @_; if ( !$original->id ) { $app->log( { message => $app->translate( "Folder '[_1]' created by '[_2]'", $obj->label, $app->user->name ), level => MT::Log::INFO(), class => 'folder', category => 'new', } ); } 1; } sub CMSPostSave_category { my $eh = shift; my ( $app, $obj, $original ) = @_; if ( !$original->id ) { $app->log( { message => $app->translate( "Category '[_1]' created by '[_2]'", $obj->label, $app->user->name ), level => MT::Log::INFO(), class => 'category', category => 'new', } ); } 1; } sub CMSPreSave_folder { my $eh = shift; my ( $app, $obj ) = @_; my $pkg = $app->model('folder'); my @siblings = $pkg->load( { parent => $obj->parent, blog_id => $obj->blog_id } ); foreach (@siblings) { next if $obj->id && ( $_->id == $obj->id ); return $eh->error( $app->translate( "The folder '[_1]' conflicts with another folder. Folders with the same parent must have unique basenames.", $_->label ) ) if $_->basename eq $obj->basename; } 1; } sub CMSPreSave_category { my $eh = shift; my ( $app, $obj ) = @_; my $pkg = $app->model('category'); if ( defined( my $pass = $app->param('tb_passphrase') ) ) { $obj->{__tb_passphrase} = $pass; } my @siblings = $pkg->load( { parent => $obj->parent, blog_id => $obj->blog_id } ); foreach (@siblings) { next if $obj->id && ( $_->id == $obj->id ); return $eh->error( $app->translate( "The category name '[_1]' conflicts with another category. Top-level categories and sub-categories with the same parent must have unique names.", $_->label ) ) if $_->label eq $obj->label; return $eh->error( $app->translate( "The category basename '[_1]' conflicts with another category. Top-level categories and sub-categories with the same parent must have unique basenames.", $_->label ) ) if $_->basename eq $obj->basename; } 1; } sub CMSPreSave_page { &CMSPreSave_entry; } sub _convert_word_chars { my ( $app, $s ) = @_; return '' unless $s; return $s if 'utf-8' ne lc( $app->charset ); my $blog = $app->blog; my $smart_replace = $blog ? $blog->smart_replace : MT->config->NwcSmartReplace; return $s if $smart_replace == 2; if ($smart_replace) { # html character entity replacements $s =~ s/\342\200\231/’/g; $s =~ s/\342\200\230/‘/g; $s =~ s/\342\200\246/…/g; $s =~ s/\342\200\223/-/g; $s =~ s/\342\200\224/—/g; $s =~ s/\342\200\234/“/g; $s =~ s/\342\200\235/”/g; } else { # ascii equivalent replacements $s =~ s/\342\200[\230\231]/'/g; $s =~ s/\342\200\246/.../g; $s =~ s/\342\200\223/-/g; $s =~ s/\342\200\224/--/g; $s =~ s/\342\200[\234\235]/"/g; } # While we're fixing Word, remove processing instructions with # colons, as they can break PHP. $s =~ s{ <\? xml:namespace [^>]*> }{}xmsg; $s; } sub _translate_naughty_words { my $app = shift; my ($entry) = @_; my $blog = $app->blog; return if $blog->smart_replace == 2; my $fields = $blog->smart_replace_fields; return unless $fields; my @fields = split( /\s*,\s*/, $fields || '' ); foreach my $field (@fields) { if ( $entry->can($field) ) { $entry->$field( _convert_word_chars( $app, $entry->$field ) ); } } } sub CMSPostSave_page { &CMSPostSave_entry; } sub CMSPostSave_entry { my $eh = shift; my ( $app, $obj ) = @_; my $sess_obj = $app->autosave_session_obj; $sess_obj->remove if $sess_obj; 1; } sub CMSPreSave_entry { my $eh = shift; my ( $app, $obj ) = @_; # save tags my $tags = $app->param('tags'); if ( defined $tags ) { my $blog = $app->blog; my $fields = $blog->smart_replace_fields; if ( $fields =~ m/tags/ig ) { $tags = _convert_word_chars( $app, $tags ); } require MT::Tag; my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} ); my @tags = MT::Tag->split( $tag_delim, $tags ); if (@tags) { $obj->set_tags(@tags); } else { $obj->remove_tags(); } } # update text heights if necessary if ( my $perms = $app->permissions ) { my $prefs = $perms->entry_prefs || $app->load_default_entry_prefs; my $text_height = $app->param('text_height'); if ( defined $text_height ) { my ($pref_text_height) = $prefs =~ m/\bbody:(\d+)\b/; $pref_text_height ||= 0; if ( $text_height != $pref_text_height ) { if ( $prefs =~ m/\bbody\b/ ) { $prefs =~ s/\bbody(:\d+)\b/body:$text_height/; } else { $prefs = 'body:' . $text_height . ',' . $prefs; } } } if ( $prefs ne ( $perms->entry_prefs || '' ) ) { $perms->entry_prefs($prefs); $perms->save; } } $obj->discover_tb_from_entry(); 1; } sub CMSPreSave_asset { my $eh = shift; my ( $app, $obj ) = @_; # save tags my $tags = $app->param('tags'); if ( defined $tags ) { my $blog = $app->blog; my $fields = $blog ? $blog->smart_replace_fields : MT->config->NwcReplaceField; if ( $fields && $fields =~ m/tags/ig ) { $tags = _convert_word_chars( $app, $tags ); } require MT::Tag; my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} ); my @tags = MT::Tag->split( $tag_delim, $tags ); if (@tags) { $obj->set_tags(@tags); } else { $obj->remove_tags(); } } 1; } sub CMSPostSave_blog { my $eh = shift; my ( $app, $obj, $original ) = @_; my $perms = $app->permissions; return 1 unless $app->user->is_superuser || $app->user->can_create_blog || ( $perms && $perms->can_edit_config ); my $screen = $app->param('cfg_screen') || ''; if ( $screen eq 'cfg_archives' ) { if ( my $dcty = $app->param('dynamicity') ) { if ( $dcty ne 'none' ) { $app->update_dynamicity( $obj, $app->param('dynamic_cache') ? 1 : 0, $app->param('dynamic_conditional') ? 1 : 0 ); $app->rebuild( BlogID => $obj->id, NoStatic => 1 ) or return $app->publish_error(); } else { require MT::Template; my @tmpls = MT::Template->load({ build_dynamic => 1}); for my $tmpl (@tmpls) { $tmpl->build_dynamic(0); $tmpl->save; } } } $app->cfg_archives_save($obj); } if ( $screen eq 'cfg_prefs' ) { my $blog_id = $obj->id; # FIXME: Needs to exclude MT::Permission records for groups $app->model('permission') ->load( { blog_id => $blog_id, author_id => 0 } ); if ( !$perms ) { $perms = $app->model('permission')->new; $perms->blog_id($blog_id); $perms->author_id(0); } } if ( $screen eq 'cfg_entry' ) { my $blog_id = $obj->id; # FIXME: Needs to exclude MT::Permission records for groups my $perms = $app->model('permission') ->load( { blog_id => $blog_id, author_id => 0 } ); if ( !$perms ) { $perms = $app->model('permission')->new; $perms->blog_id($blog_id); $perms->author_id(0); } my $prefs = $app->_entry_prefs_from_params; if ($prefs) { $perms->entry_prefs($prefs); $perms->save or return $app->errtrans( "Saving permissions failed: [_1]", $perms->errstr ); } } 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. $obj->create_default_templates( $obj->template_set ); # Add this blog to the user's "favorite blogs", pushing any 10th # blog off the list my $auth = $app->user; # FIXME: Should we still be doing this? my $perms = $app->model('permission')->new; $perms->author_id( $auth->id ); $perms->blog_id( $obj->id ); $perms->set_full_permissions; $perms->save; require MT::Log; $app->log( { message => $app->translate( "Blog '[_1]' (ID:[_2]) created by '[_3]'", $obj->name, $obj->id, $auth->name ), level => MT::Log::INFO(), class => 'blog', category => 'new', } ); $app->run_callbacks( 'blog_template_set_change', { blog => $obj } ); } 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 ); } if ( ($original->template_set || '') ne ($obj->template_set || '') ) { $app->run_callbacks( 'blog_template_set_change', { blog => $obj } ); $app->add_return_arg( need_full_rebuild => 1 ); } } 1; } sub RegistrationAffectsArchives { 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 =~ /<MT:?IfRegistration/i } map { MT::Template->load( $_->template_id ) } @tms; } sub CMSPostSave_author { my $eh = shift; my ( $app, $obj, $original ) = @_; if ( !$original->id ) { $app->log( { message => $app->translate( "User '[_1]' (ID:[_2]) created by '[_3]'", $obj->name, $obj->id, $app->user->name ), level => MT::Log::INFO(), class => 'author', category => 'new', } ); $obj->add_default_roles; my $author_id = $obj->id; if ( $app->param('create_personal_weblog') ) { # provision new user with a personal blog $app->run_callbacks( 'new_user_provisioning', $obj ); } } else { if ( $app->user->id == $obj->id ) { ## If this is a user editing his/her profile, $id will be ## some defined value; if so we should update the user's ## cookie to reflect any changes made to username and password. ## Otherwise, this is a new user, and we shouldn't update the ## cookie. $app->user($obj); if ( ( $obj->name ne $original->name ) || ( $app->param('pass') ) ) { $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 ) or return $app->publish_error(); } 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 ) ) { my ( $entry, $cat ); if ( $tb->entry_id && ( $entry = MT::Entry->load( $tb->entry_id ) ) ) { if ( $obj->visible || ( ( $obj->visible || 0 ) != ( $original->visible || 0 ) ) ) { $app->rebuild_entry( Entry => $entry, BuildIndexes => 1 ) or return $app->publish_error(); } } elsif ( $tb->category_id && ( $cat = MT::Category->load( $tb->category_id ) ) ) { # FIXME: rebuild single category } } 1; } sub CMSPostSave_trackback { my $eh = shift; my ( $app, $obj, $original ) = @_; $app->rebuild_entry( Entry => $obj->entry_id, BuildIndexes => 1 ) or return $app->publish_error(); 1; } sub CMSPostSave_template { my $eh = shift; my ( $app, $obj, $original ) = @_; my $sess_obj = $app->autosave_session_obj; $sess_obj->remove if $sess_obj; require MT::TemplateMap; my $q = $app->param; my @p = $q->param; for my $p (@p) { if ( $p =~ /^archive_tmpl_preferred_(\w+)_(\d+)$/ ) { my $at = $1; my $map_id = $2; my $map = MT::TemplateMap->load($map_id); $map->prefer( $q->param($p) ); # prefer method saves in itself } elsif ( $p =~ /^archive_file_tmpl_(\d+)$/ ) { my $map_id = $1; my $map = MT::TemplateMap->load($map_id); $map->file_template( $q->param($p) ); $map->save; } } if ( !$original->id ) { $app->log( { message => $app->translate( "Template '[_1]' (ID:[_2]) created by '[_3]'", $obj->name, $obj->id, $app->user->name ), level => MT::Log::INFO(), class => 'template', category => 'new', } ); } if ( $obj->build_dynamic ) { if ( $obj->type eq 'index' ) { $app->rebuild_indexes( BlogID => $obj->blog_id, Template => $obj, NoStatic => 1, ) or return $app->publish_error(); # XXXX } else { $app->rebuild( BlogID => $obj->blog_id, TemplateID => $obj->id, NoStatic => 1, ) or return $app->publish_error(); } } 1; } sub CMSSavePermissionFilter_blog { my ( $eh, $app, $id ) = @_; my $perms = $app->permissions; return ( $id && ( $perms->can_edit_config || $perms->can_set_publish_paths ) ) || ( !$id && $app->user->can_create_blog ); } sub CMSSavePermissionFilter_template { my ( $eh, $app, $id ) = @_; my $perms = $app->permissions; return ($perms && $perms->can_edit_templates) || (!$perms && $app->user->can_edit_templates); } sub CMSSavePermissionFilter_folder { my ( $eh, $app, $id ) = @_; my $perms = $app->permissions; return $perms->can_manage_pages(); } sub CMSSavePermissionFilter_category { my ( $eh, $app, $id ) = @_; my $perms = $app->permissions; return $perms->can_edit_categories(); } sub CMSSavePermissionFilter_notification { my ( $eh, $app, $id ) = @_; my $perms = $app->permissions; return $perms->can_edit_notifications; } sub CMSSavePermissionFilter_author { my ( $eh, $app, $id ) = @_; my $author = $app->user; if ( !$id ) { return $author->is_superuser; } else { return $author->id == $id; } } sub CMSSavePermissionFilter_comment { my ( $eh, $app, $id ) = @_; return 0 unless $id; # Can't create new comments here return 1 if $app->user->is_superuser(); my $perms = $app->permissions; return 1 if $perms && ( $perms->can_edit_all_posts || $perms->can_manage_feedback ); my $c = MT::Comment->load($id); if ( $perms && $perms->can_create_post && $perms->can_publish_post ) { return $c->entry->author_id == $app->user->id; } elsif ( $perms && $perms->can_create_post ) { return ( $c->entry->author_id == $app->user->id ) && ( ( $c->is_junk && ( 'junk' eq $app->param('status') ) ) || ( $c->is_moderated && ( 'moderate' eq $app->param('status') ) ) || ( $c->is_published && ( 'publish' eq $app->param('status') ) ) ); } elsif ( $perms && $perms->can_publish_post ) { return 0 unless $c->entry->author_id == $app->user->id; return 0 unless ( $c->text eq $app->param('text') ) && ( $c->author eq $app->param('author') ) && ( $c->email eq $app->param('email') ) && ( $c->url eq $app->param('url') ); } else { return 0; } } sub CMSSavePermissionFilter_ping { my ( $eh, $app, $id ) = @_; return 0 unless $id; # Can't create new pings here return 1 if $app->user->is_superuser(); my $perms = $app->permissions; return 1 if $perms && ( $perms->can_edit_all_posts || $perms->can_manage_feedback ); my $p = MT::TBPing->load($id); my $tbitem = $p->parent; if ( $tbitem->isa('MT::Entry') ) { if ( $perms && $perms->can_publish_post && $perms->can_create_post ) { return $tbitem->author_id == $app->user->id; } elsif ( $perms->can_create_post ) { return ( $tbitem->author_id == $app->user->id ) && ( ( $p->is_junk && ( 'junk' eq $app->param('status') ) ) || ( $p->is_moderated && ( 'moderate' eq $app->param('status') ) ) || ( $p->is_published && ( 'publish' eq $app->param('status') ) ) ); } elsif ( $perms && $perms->can_publish_post ) { return 0 unless $tbitem->author_id == $app->user->id; return 0 unless ( $p->excerpt eq $app->param('excerpt') ) && ( $p->blog_name eq $app->param('blog_name') ) && ( $p->title eq $app->param('title') ) && ( $p->source_url eq $app->param('source_url') ); } } else { return $perms && $perms->can_edit_categories; } } sub CMSDeletePermissionFilter_association { my ( $eh, $app, $obj ) = @_; my $blog_id = $app->param('blog_id'); my $user = $app->user; if ( !$user->is_superuser ) { if ( !$blog_id || !$user->permissions($blog_id)->can_administer_blog ) { return $eh->error( MT->translate("Permission denied.") ); } if ( $obj->author_id == $user->id ) { return $eh->error( MT->translate("You cannot delete your own association.") ); } } 1; } sub CMSDeletePermissionFilter_author { my ( $eh, $app, $obj ) = @_; my $author = $app->user; if ( $author->id == $obj->id ) { return $eh->error( MT->translate("You cannot delete your own user record.") ); } return 1 if $author->is_superuser(); if ( !( $obj->created_by && $obj->created_by == $author->id ) ) { return $eh->error( MT->translate( "You have no permission to delete the user [_1].", $obj->name ) ); } } sub CMSDeletePermissionFilter_blog { my ( $eh, $app, $obj ) = @_; my $author = $app->user; return 1 if $author->is_superuser(); require MT::Permission; my $perms = $author->permissions( $obj->id ); return $perms && $perms->can_administer_blog; } sub CMSDeletePermissionFilter_folder { my ( $eh, $app, $obj ) = @_; return 1 if $app->user->is_superuser(); my $perms = $app->permissions; return $perms && $perms->can_manage_pages(); } sub CMSDeletePermissionFilter_category { my ( $eh, $app, $obj ) = @_; return 1 if $app->user->is_superuser(); my $perms = $app->permissions; return $perms && $perms->can_edit_categories(); } sub CMSDeletePermissionFilter_asset { my ( $eh, $app, $obj ) = @_; return 1 if $app->user->is_superuser(); my $perms = $app->permissions; return $perms && $perms->can_edit_assets(); } sub CMSDeletePermissionFilter_comment { my ( $eh, $app, $obj ) = @_; my $author = $app->user; return 1 if $author->is_superuser(); my $perms = $app->permissions; require MT::Entry; my $entry = MT::Entry->load( $obj->entry_id ); if ( !$perms || $perms->blog_id != $entry->blog_id ) { $perms ||= $author->permissions( $entry->blog_id ); } # publish_post allows entry author to delete comment. return 1 if $perms->can_edit_all_posts || $perms->can_manage_feedback || $perms->can_edit_entry( $entry, $author, 1 ); return 0 if $obj->visible; # otherwise, visible comment can't be deleted. return $perms && $perms->can_edit_entry( $entry, $author ); } sub CMSDeletePermissionFilter_commenter { my ( $eh, $app, $obj ) = @_; my $author = $app->user; return 1 if $author->is_superuser(); my $perms = $author->permissions( $obj->blog_id ); ( $perms && $perms->can_administer_blog ); } sub CMSDeletePermissionFilter_page { my ( $eh, $app, $obj ) = @_; my $author = $app->user; return 1 if $author->is_superuser(); my $perms = $app->permissions; if ( !$perms || $perms->blog_id != $obj->blog_id ) { $perms ||= $author->permissions( $obj->blog_id ); } return $perms && $perms->can_manage_pages; } sub CMSDeletePermissionFilter_entry { my ( $eh, $app, $obj ) = @_; my $author = $app->user; return 1 if $author->is_superuser(); my $perms = $app->permissions; if ( !$perms || $perms->blog_id != $obj->blog_id ) { $perms ||= $author->permissions( $obj->blog_id ); } return $perms && $perms->can_edit_entry( $obj, $author ); } sub CMSDeletePermissionFilter_ping { my ( $eh, $app, $obj ) = @_; my $author = $app->user; return 1 if $author->is_superuser(); my $perms = $app->permissions; require MT::Trackback; my $tb = MT::Trackback->load( $obj->tb_id ); if ( my $entry = $tb->entry ) { if ( !$perms || $perms->blog_id != $entry->blog_id ) { $perms ||= $author->permissions( $entry->blog_id ); } # publish_post allows entry author to delete comment. return 1 if $perms->can_edit_all_posts || $perms->can_manage_feedback || $perms->can_edit_entry( $entry, $author, 1 ); return 0 if $obj->visible; # otherwise, visible comment can't be deleted. return $perms->can_edit_entry( $entry, $author ); } elsif ( $tb->category_id ) { $perms ||= $author->permissions( $tb->blog_id ); return ( $perms && $perms->can_edit_categories() ); } return 0; } sub CMSDeletePermissionFilter_tag { my ( $eh, $app, $obj ) = @_; return 1 if $app->user->is_superuser(); my $perms = $app->permissions; return 1 if $app->blog && ( $perms->can_administer_blog() || $perms->can_edit_tags ); return 0; } sub CMSDeletePermissionFilter_template { my ( $eh, $app, $obj ) = @_; return 1 if $app->user->is_superuser(); my $perms = $app->permissions; return ($perms && $perms->can_edit_templates) || (!$perms && $app->user->can_edit_templates); } sub CMSPostDelete_blog { my ( $eh, $app, $obj ) = @_; $app->log( { message => $app->translate( "Blog '[_1]' (ID:[_2]) deleted by '[_3]'", $obj->name, $obj->id, $app->user->name ), level => MT::Log::INFO(), class => 'blog', category => 'delete' } ); $app->_delete_pseudo_association(undef, $obj->id); } sub CMSPostDelete_notification { my ( $eh, $app, $obj ) = @_; $app->log( { message => $app->translate( "Subscriber '[_1]' (ID:[_2]) deleted from address book by '[_3]'", $obj->email, $obj->id, $app->user->name ), level => MT::Log::INFO(), class => 'system', category => 'delete' } ); } sub CMSPostDelete_author { my ( $eh, $app, $obj ) = @_; $app->log( { message => $app->translate( "User '[_1]' (ID:[_2]) deleted by '[_3]'", $obj->name, $obj->id, $app->user->name ), level => MT::Log::INFO(), class => 'system', category => 'delete' } ); } sub CMSPostDelete_folder { my ( $eh, $app, $obj ) = @_; $app->log( { message => $app->translate( "Folder '[_1]' (ID:[_2]) deleted by '[_3]'", $obj->label, $obj->id, $app->user->name ), level => MT::Log::INFO(), class => 'system', category => 'delete' } ); } sub CMSPostDelete_category { my ( $eh, $app, $obj ) = @_; $app->log( { message => $app->translate( "Category '[_1]' (ID:[_2]) deleted by '[_3]'", $obj->label, $obj->id, $app->user->name ), level => MT::Log::INFO(), class => 'system', category => 'delete' } ); } sub CMSPostDelete_comment { my ( $eh, $app, $obj ) = @_; require MT::Entry; my $title = ''; if ( my $entry = MT::Entry->load( $obj->entry_id ) ) { $title = $entry->title; } $app->log( { message => $app->translate( "Comment (ID:[_1]) by '[_2]' deleted by '[_3]' from entry '[_4]'", $obj->id, $obj->author, $app->user->name, $title ), level => MT::Log::INFO(), class => 'system', category => 'delete' } ); } sub CMSPostDelete_page { my ( $eh, $app, $obj ) = @_; my $sess_obj = $app->autosave_session_obj; $sess_obj->remove if $sess_obj; $app->log( { message => $app->translate( "Page '[_1]' (ID:[_2]) deleted by '[_3]'", $obj->title, $obj->id, $app->user->name ), level => MT::Log::INFO(), class => 'system', category => 'delete' } ); } sub CMSPostDelete_entry { my ( $eh, $app, $obj ) = @_; my $sess_obj = $app->autosave_session_obj; $sess_obj->remove if $sess_obj; $app->log( { message => $app->translate( "Entry '[_1]' (ID:[_2]) deleted by '[_3]'", $obj->title, $obj->id, $app->user->name ), level => MT::Log::INFO(), class => 'system', category => 'delete' } ); } sub CMSPostDelete_ping { my ( $eh, $app, $obj ) = @_; my ( $message, $title ); my $obj_parent = $obj->parent(); if ( $obj_parent->isa('MT::Category') ) { $title = $obj_parent->label || $app->translate('(Unlabeled category)'); $message = $app->translate( "Ping (ID:[_1]) from '[_2]' deleted by '[_3]' from category '[_4]'", $obj->id, $obj->blog_name, $app->user->name, $title ); } else { $title = $obj_parent->title || $app->translate('(Untitled entry)'); $message = $app->translate( "Ping (ID:[_1]) from '[_2]' deleted by '[_3]' from entry '[_4]'", $obj->id, $obj->blog_name, $app->user->name, $title ); } $app->log( { message => $message, level => MT::Log::INFO(), class => 'system', category => 'delete' } ); } sub CMSPostDelete_template { my ( $eh, $app, $obj ) = @_; $app->log( { message => $app->translate( "Template '[_1]' (ID:[_2]) deleted by '[_3]'", $obj->name, $obj->id, $app->user->name ), level => MT::Log::INFO(), class => 'system', category => 'delete' } ); } sub CMSPostDelete_tag { my ( $eh, $app, $obj ) = @_; $app->log( { message => $app->translate( "Tag '[_1]' (ID:[_2]) deleted by '[_3]'", $obj->name, $obj->id, $app->user->name ), level => MT::Log::INFO(), class => 'system', category => 'delete' } ); } sub CMSPostSave_asset { my $eh = shift; my ( $app, $obj, $original ) = @_; if ( !$original->id ) { $app->log( { message => $app->translate( "File '[_1]' uploaded by '[_2]'", $obj->file_name, $app->user->name ), level => MT::Log::INFO(), class => 'asset', category => 'new', } ); } 1; } sub CMSPostDelete_asset { my ( $eh, $app, $obj ) = @_; $app->log( { message => $app->translate( "File '[_1]' (ID:[_2]) deleted by '[_3]'", $obj->file_name, $obj->id, $app->user->name ), level => MT::Log::INFO(), class => 'asset', category => 'delete' } ); } sub autosave_session_obj { my $app = shift; my ($or_make_one) = @_; my $q = $app->param; my $type = $q->param('_type'); return unless $type; my $id = $q->param('id'); $id = '0' unless $id; my $ident = 'autosave' . ':user=' . $app->user->id . ':type=' . $type . ':id=' . $id; if ( my $blog = $app->blog ) { $ident .= ':blog_id=' . $blog->id; } require MT::Session; my $sess_obj = MT::Session->load( { id => $ident, kind => 'AS' } ); if ( !$sess_obj && $or_make_one ) { # autosave is being requested, so provision an object for saving $sess_obj = new MT::Session; $sess_obj->kind('AS'); # AutoSave record $sess_obj->id($ident); $sess_obj->start(time); } return $sess_obj; } sub autosave_object { my $app = shift; my $sess_obj = $app->autosave_session_obj(1) or return; $sess_obj->data(''); my $class = $app->model( $app->param('_type') ) or return; my %data = $app->param_hash; delete $data{_autosave}; delete $data{magic_token}; # my $col_names = $class->column_names; # foreach my $c (@$col_names) { foreach my $c ( keys %data ) { $sess_obj->set( $c, $data{$c} ); } $sess_obj->start(time); $sess_obj->save; $app->send_http_header("text/javascript+json"); $app->{no_print_body} = 1; require JSON; $app->print("true"); } sub save_object { my $app = shift; my $q = $app->param; my $type = $q->param('_type'); return $app->errtrans("Invalid request.") unless $type; # being a general-purpose method, lets look for a mode handler # that is specifically for editing this type. if we find it, # reroute to it. my $save_mode = 'save_' . $type; if ( my $hdlrs = $app->handlers_for_mode($save_mode) ) { return $app->forward($save_mode); } my $id = $q->param('id'); $q->param( 'allow_pings', 0 ) if ( $type eq 'category' ) && !defined( $q->param('allow_pings') ); $app->validate_magic() or return; my $author = $app->user; # Check permissions my $perms = $app->permissions; if ( !$author->is_superuser ) { if ( ($type ne 'author') && ($type ne 'template') ) { # for authors, blog-ctx $perms is not relevant return $app->errtrans("Permisison denied.") if !$perms && $id; } $app->run_callbacks( 'cms_save_permission_filter.' . $type, $app, $id ) || return $app->error( $app->translate( "Permission denied: [_1]", $app->errstr() ) ); } my $param = {}; if ( $type eq 'author' ) { if ( my $delim = $q->param('tag_delim') ) { $param->{ 'auth_pref_tag_delim_' . $delim } = 1; $param->{'auth_pref_tag_delim'} = $delim; } $param->{languages} = $app->languages_list( $q->param('preferred_language') ) if $q->param('preferred_language'); $param->{create_personal_weblog} = $q->param('create_personal_weblog') ? 1 : 0; require MT::Permission; my $sys_perms = MT::Permission->perms('system'); foreach (@$sys_perms) { $param->{ 'perm_can_' . $_->[0] } = 1 if $q->param( 'can_' . $_->[0] ); } } my $filter_result = $app->run_callbacks( 'cms_save_filter.' . $type, $app ); if ( !$filter_result ) { my %param = (%$param); $param{error} = $app->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->forward( 'edit', \%param ); } } return $app->errtrans( 'The Template Name and Output File fields are required.') if $type eq 'template' && !$q->param('name') && !$q->param('outfile'); if ( $type eq 'author' ) { ## If we are saving a user profile, we need to do some ## password maintenance. First make sure that the two ## passwords match... my $editing_other_profile; if ( $id && ( $author->id != $id ) && ( $author->is_superuser ) ) { $editing_other_profile = 1; } my %param = (%$param); unless ($editing_other_profile) { require MT::Auth; my $error = MT::Auth->sanity_check($app); if ($error) { $param{error} = $error; require MT::Log; $app->log( { message => $error, level => MT::Log::ERROR(), class => 'system', category => 'save_author_profile' } ); } if ( $param{error} ) { $param{return_args} = $app->param('return_args'); my $qual = $id ? '' : 'author_state_'; for my $f (qw( name nickname email url state )) { $param{ $qual . $f } = $q->param($f); } return $app->edit_object( \%param ); } } } if ( $type eq 'template' ) { # check for autosave if ( $q->param('_autosave') ) { return $app->autosave_object(); } } my $class = $app->model($type) or return $app->errtrans( "Invalid type [_1]", $type ); 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 'blog' ) { unless ( $author->is_superuser || ( $perms && $perms->can_administer_blog ) ) { if ( $id && !( $perms->can_set_publish_paths ) ) { delete $values{site_url}; delete $values{site_path}; delete $values{archive_url}; delete $values{archive_path}; } if ( $id && !( $perms->can_edit_config ) ) { delete $values{$_} foreach grep { $_ ne 'site_path' && $_ ne 'site_url' && $_ ne 'archive_path' && $_ ne 'archive_url' } @$names; } } } if ( $type eq 'author' ) { #FIXME: Legacy columns - remove them my @cols = qw(is_superuser can_create_blog can_view_log can_edit_templates); if ( !$author->is_superuser ) { delete $values{$_} for @cols; } else { if ( !$id || ( $author->id != $id ) ) { # Assign the auth_type unless it was assigned # through the form. $obj->auth_type($app->config->AuthenticationModule) unless $obj->auth_type; if ( $values{'status'} == MT::Author::ACTIVE() ) { my $sys_perms = MT::Permission->perms('system'); if ( defined($q->param('is_superuser')) && $q->param('is_superuser')) { $obj->is_superuser(1); } else { foreach (@$sys_perms) { my $name = 'can_' . $_->[0]; $name = 'is_superuser' if $name eq 'can_administer'; if ( defined $q->param($name) ) { $obj->$name( $q->param($name) ); delete $values{$name}; } else { $obj->$name(0); } } } } } } delete $values{'password'}; } if ( $type eq 'blog' ) { # If this is a new blog, set the preferences, archive settings # and template set to the defaults. if ( !$obj->id ) { $obj->language( $app->user->preferred_language ); $obj->nofollow_urls(1); $obj->follow_auth_links(1); $obj->page_layout('layout-wtt'); my @authenticators = qw( MovableType ); foreach my $auth (qw( Vox LiveJournal )) { my $a = MT->commenter_authenticator($auth); if ( !defined $a || ( exists $a->{condition} && ( !$a->{condition}->() ) ) ) { next; } push @authenticators, $auth; } $obj->commenter_authenticators( join ',', @authenticators ); my $set = $app->param('template_set') || 'mt_blog'; $obj->template_set( $set ); } if ( $values{file_extension} ) { $values{file_extension} =~ s/^\.*// if ( $q->param('file_extension') || '' ) ne ''; } unless ( $values{site_url} =~ m!/$! ) { my $url = $values{site_url}; $values{site_url} = $url; } } if ( $type eq 'entry' || $type eq 'page' ) { # This has to happen prior to callbacks since callbacks may # be affected by the translation... # translates naughty words when PublishCharset is NOT UTF-8 $app->_translate_naughty_words($obj); } if ( $type eq 'template' ) { if ( $q->param('type') eq 'archive' && $q->param('archive_type') ) { $values{type} = $q->param('archive_type'); } } delete $values{'id'} if exists( $values{'id'} ) && !$values{'id'}; $obj->set_values( \%values ); if ( $obj->properties->{audit} ) { $obj->created_by( $author->id ) unless $obj->id; $obj->modified_by( $author->id ) if $obj->id; } unless ( $app->run_callbacks( 'cms_pre_save.' . $type, $app, $obj, $original ) ) { if ( 'blog' eq $type ) { my $meth = $q->param('cfg_screen'); if ( $meth && $app->can($meth) ) { $app->error( $app->translate( "Save failed: [_1]", $app->errstr ) ); return $app->$meth; } } return $app->edit_object( { %$param, error => $app->translate( "Save failed: [_1]", $app->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 ) ); # Now post-process it. $app->run_callbacks( 'cms_post_save.' . $type, $app, $obj, $original ) or return $app->error( $app->errstr() ); # Save NWC settings my $screen = $q->param('cfg_screen') || ''; if ( $type eq 'blog' && $screen eq 'cfg_entry' ) { my @fields; push( @fields, 'title' ) if $q->param('nwc_title'); push( @fields, 'text' ) if $q->param('nwc_text'); push( @fields, 'text_more' ) if $q->param('nwc_text_more'); push( @fields, 'keywords' ) if $q->param('nwc_keywords'); push( @fields, 'excerpt' ) if $q->param('nwc_excerpt'); push( @fields, 'tags' ) if $q->param('nwc_tags'); my $fields = @fields ? join( ',', @fields ) : 0; $obj->smart_replace_fields( $fields ); $obj->smart_replace( $q->param('nwc_smart_replace') ); $obj->save; } # 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 'notification' ) { return $app->redirect( $app->uri( 'mode' => 'list', args => { '_type' => 'notification', blog_id => $blog_id, saved => $obj->email } ) ); } elsif ( my $cfg_screen = $q->param('cfg_screen') ) { if ( $cfg_screen eq 'cfg_templatemaps' ) { $cfg_screen = 'cfg_archives'; } my $site_path = $obj->site_path; my $fmgr = $obj->file_mgr; unless ( $fmgr->exists($site_path) ) { $fmgr->mkpath($site_path); } $app->add_return_arg( no_writedir => 1 ) unless $fmgr->exists($site_path) && $fmgr->can_write($site_path); } 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(); } elsif ( $type eq 'blog' ) { return $app->redirect( $app->uri( 'mode' => 'cfg_prefs', args => { blog_id => $blog_id, saved => 1 } ) ); } elsif ( $type eq 'author' ) { # Delete the author's userpic thumb (if any); it'll be regenerated. if ($original->userpic_asset_id != $obj->userpic_asset_id) { my $thumb_file = $original->userpic_file(); my $fmgr = MT::FileMgr->new('Local'); if ($fmgr->exists($thumb_file)) { $fmgr->delete($thumb_file); } } } $app->add_return_arg( 'id' => $obj->id ) if !$original->id; $app->add_return_arg( 'saved' => 1 ); $app->call_return; } sub list_template { my $app = shift; my $perms = $app->blog ? $app->permissions : $app->user->permissions; return $app->return_to_dashboard( redirect => 1 ) unless $perms || $app->user->is_superuser; if ( $perms && !$perms->can_edit_templates ) { return $app->return_to_dashboard( permission => 1 ); } require MT::Template; my $blog_id = $app->param('blog_id') || 0; my $filter = $app->param('filter_key'); if ( !$filter ) { if ($app->blog) { $filter = 'index_templates'; $app->param( 'filter_key', 'index_templates' ); } else { $filter = 'module_templates'; $app->param( 'filter_key', 'module_templates' ); } } else { # global index templates redirect to module templates if ( !$app->blog && $filter eq 'index_templates' ) { $filter = 'module_templates'; $app->param( 'filter_key', 'module_templates' ); } } my $terms = { blog_id => $blog_id }; my $args = { sort => 'name' }; my $hasher = sub { my ( $obj, $row ) = @_; my $template_type; my $type = $row->{type} || ''; if ( $type =~ m/^(individual|page|category|archive)$/ ) { $template_type = 'archive'; } elsif ( $type eq 'widget' ) { $template_type = 'widget'; } elsif ( $type eq 'index' ) { $template_type = 'index'; } elsif ( $type eq 'custom' ) { $template_type = 'module'; } elsif ( $type eq 'email' ) { $template_type = 'email'; } elsif ( $type eq 'backup' ) { $template_type = 'backup'; } else { $template_type = 'system'; } $row->{template_type} = $template_type; $row->{type} = 'entry' if $type eq 'individual'; my $published_url = $obj->published_url; $row->{published_url} = $published_url if $published_url; }; my $params = {}; my $template_type = $filter; $template_type =~ s/_templates//; my $template_type_label = $app->translate($template_type); if ( $template_type_label eq $template_type ) { $template_type_label = "\u$template_type"; } $params->{template_type} = $template_type; $params->{template_type_label} = $template_type_label; #@tt_loop = sort { $a->{label} cmp $b->{label} } @tt_loop; #$params->{template_type_loop} = \@tt_loop; $app->load_list_actions( 'template', $params ); $params->{page_actions} = $app->page_actions('list_templates'); $params->{search_label} = $app->translate("Templates"); $params->{blog_view} = 1; $params->{refreshed} = $app->param('refreshed'); $params->{published} = $app->param('published'); return $app->listing( { type => 'template', terms => $terms, args => $args, params => $params, no_limit => 1, code => $hasher, } ); } sub list_objects { my $app = shift; my $q = $app->param; my $type = $q->param('_type'); return $app->errtrans("Invalid request.") unless $type; # being a general-purpose method, lets look for a mode handler # that is specifically for editing this type. if we find it, # reroute to it. my $list_mode = 'list_' . $type; if ( my $hdlrs = $app->handlers_for_mode($list_mode) ) { return $app->forward($list_mode); } my %param = $_[0] ? %{ $_[0] } : (); my $perms = $app->permissions; return $app->return_to_dashboard( redirect => 1 ) unless $perms; if ( $perms && ( ( $type eq 'blog' && !$perms->can_edit_config ) || ( $type eq 'template' && !$perms->can_edit_templates ) || ( $type eq 'notification' && !$perms->can_edit_notifications ) ) ) { return $app->return_to_dashboard( permission => 1 ); } my $id = $q->param('id'); my $class = $app->model($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 = $app->param('offset') || 0; for my $name (@$cols) { $terms{blog_id} = $blog_id, last if $name eq 'blog_id'; } if ( $type eq 'notification' ) { $args{direction} = 'descend'; $args{offset} = $offset; $args{limit} = $limit + 1; } elsif ( $type eq 'banlist' ) { $param{use_plugins} = $app->config->UsePlugins; $limit = 0; } my $iter = $class->load_iter( \%terms, \%args ); my ( @data, @index_data, @custom_data, @archive_data, @system_data, @widget_data ); my (%authors); my $blog_class = $app->model('blog'); my $blog = $blog_class->load($blog_id); my $set = $blog ? $blog->template_set : undef; require MT::DefaultTemplates; my $dtmpl = MT::DefaultTemplates->templates($set) || []; my %dtmpl = map { $_->{type} => $_ } @$dtmpl; while ( my $obj = $iter->() ) { my $row = $obj->column_values; if ( my $ts = $obj->created_on ) { $row->{created_on_formatted} = format_ts( LISTING_DATE_FORMAT, $ts, $blog, $app->user ? $app->user->preferred_language : undef ); $row->{created_on_time_formatted} = format_ts( LISTING_DATETIME_FORMAT, $ts, $blog, $app->user ? $app->user->preferred_language : undef ); $row->{created_on_relative} = relative_date( $ts, time, $blog ); } 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; my $published_url = $obj->published_url; $row->{published_url} = $published_url if $published_url; } elsif ( $obj->type eq 'custom' ) { push @custom_data, $row; } elsif ( $obj->type eq 'widget' ) { push @widget_data, $row; } elsif ($obj->type eq 'archive' || $obj->type eq 'category' || $obj->type eq 'page' || $obj->type eq 'individual' ) { # FIXME: enumeration of types push @archive_data, $row; } else { if ( my $def_tmpl = $dtmpl{ $obj->type } ) { $row->{description} = $def_tmpl->{description_label}; } else { # unknown system template; skip over it # or should we change it to a custom template # right now? next; } push @system_data, $row; } $param{search_label} = $app->translate('Templates'); } else { if ( $limit && ( scalar @data == $limit ) ) { $param{next_offset} = 1; last; } push @data, $row; } if ( $type eq 'ping' ) { return $app->list_pings(); require MT::Trackback; require MT::Entry; my $tb_center = MT::Trackback->load( $obj->tb_id ); my $entry = MT::Entry->load( $tb_center->entry_id ); if ( my $ts = $obj->created_on ) { $row->{created_on_time_formatted} = format_ts( LISTING_DATETIME_FORMAT, $ts, $blog, $app->user ? $app->user->preferred_language : undef ); $row->{has_edit_access} = $perms->can_edit_all_posts || $app->user->id == $entry->author_id; } } } # end loop over the set of objects; # NOW transform the @data array if ( $type eq 'notification' ) { $app->add_breadcrumb( $app->translate('Notification List') ); $param{nav_notifications} = 1; #@data = sort { $a->{email} cmp $b->{email} } @data; $param{object_type} = 'notification'; $param{list_noncron} = 1; $param{notification_count} = scalar @data; $param{search_type} = 'entry'; } if ( $type eq 'template' ) { my $blog = $blog_class->load( scalar $q->param('blog_id') ); $app->add_breadcrumb( $app->translate('Templates') ); $param{nav_templates} = 1; for my $ref ( \@index_data, \@custom_data, \@archive_data ) { @$ref = sort { $a->{name} cmp $b->{name} } @$ref; } my $tab = $app->param('tab') || 'index'; $param{template_group} = $tab; $param{"tab_$tab"} = 1; $param{object_index_loop} = \@index_data; $param{object_custom_loop} = \@custom_data; $param{object_widget_loop} = \@widget_data; $param{object_archive_loop} = \@archive_data; $param{object_system_loop} = \@system_data; $param{object_type} = 'template'; } else { $param{object_loop} = \@data; } # add any breadcrumbs if ( $type eq 'banlist' ) { my $blog = $blog_class->load($blog_id); $app->add_breadcrumb( $app->translate('IP Banning') ); $param{nav_config} = 1; $param{object_type} = 'banlist'; $param{show_ip_info} = 1; $param{list_noncron} = 1; $param{search_type} = 'entry'; $param{can_edit_config_or_publish_paths} = $perms->can_edit_config || $perms->can_set_publish_paths; } elsif ( $type eq 'ping' ) { $app->add_breadcrumb( $app->translate('TrackBacks') ); $param{nav_trackbacks} = 1; $param{object_type} = 'ping'; } $param{object_count} = scalar @data; if ( $type ne 'template' ) { $param{offset} = $offset; $param{list_start} = $offset + 1; delete $args{limit}; delete $args{offset}; $param{list_total} = $class->count( \%terms, \%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; $param{next_max} = 0 if ( $param{next_max} || 0 ) < $offset + 1; if ( $offset > 0 ) { $param{prev_offset} = 1; $param{prev_offset_val} = $offset - $limit; $param{prev_offset_val} = 0 if $param{prev_offset_val} < 0; } } $app->load_list_actions( $type, \%param ); $param{saved} = $q->param('saved'); $param{saved_deleted} = $q->param('saved_deleted'); $param{page_actions} = $app->page_actions( 'list_' . $type ); unless ( $param{screen_class} ) { $param{screen_class} = "list-$type"; } $app->load_tmpl( "list_${type}.tmpl", \%param ); } sub _delete_pseudo_association { my $app = shift; my ($pid, $bid) = @_; my $rid; if ($pid) { ( my $pseudo, $rid, $bid ) = split '-', $pid; } my @newdef; if ( my $def = $app->config->DefaultAssignments ) { my @def = split ',', $def; while ( my $role_id = shift @def ) { my $blog_id = shift @def; next unless $role_id && $blog_id; next if ( $rid && ( $role_id == $rid ) && ( $blog_id == $bid ) ) || ( !defined($rid) && ( $blog_id == $bid ) ); push @newdef, "$role_id,$blog_id"; } } if (@newdef) { $app->config( 'DefaultAssignments', join( ',', @newdef ), 1 ); } else { $app->config( 'DefaultAssignments', undef, 1 ); } $app->config->save_config; } sub delete { my $app = shift; my $q = $app->param; my $type = $q->param('_type'); return $app->errtrans("Invalid request.") unless $type; return $app->error( $app->translate("Invalid request.") ) if $app->request_method() ne 'POST'; # being a general-purpose method, lets look for a mode handler # that is specifically for editing this type. if we find it, # reroute to it. my $delete_mode = 'delete_' . $type; if ( my $hdlrs = $app->handlers_for_mode($delete_mode) ) { return $app->forward($delete_mode); } my $parent = $q->param('parent'); my $blog_id = $q->param('blog_id'); my $class = $app->model($type) or return; my $perms = $app->permissions; my $author = $app->user; $app->validate_magic() or return; my ( $entry_id, $cat_id, $author_id ) = ( "", "", "" ); my %rebuild_entries; my @rebuild_cats; my $required_items = 0; for my $id ( $q->param('id') ) { next unless $id; # avoid 'empty' ids if ( ( $type eq 'association' ) && ( $id =~ /PSEUDO-/ ) ) { $app->_delete_pseudo_association($id); next; } my $obj = $class->load($id); next unless $obj; $app->run_callbacks( 'cms_delete_permission_filter.' . $type, $app, $obj ) || return $app->error( $app->translate( "Permission denied: [_1]", $app->errstr() ) ); if ( $type eq 'comment' ) { $entry_id = $obj->entry_id; $rebuild_entries{$entry_id} = 1 if $obj->visible; } elsif ( $type eq 'ping' || $type eq 'ping_cat' ) { require MT::Trackback; my $tb = MT::Trackback->load( $obj->tb_id ); if ($tb) { $entry_id = $tb->entry_id; $cat_id = $tb->category_id; if ( $obj->visible ) { $rebuild_entries{$entry_id} = 1 if $entry_id; push @rebuild_cats, $cat_id if $cat_id; } } } elsif ( $type eq 'tag' ) { # if we're in a blog context, remove ONLY tags from that weblog if ($blog_id) { my $ot_class = $app->model('objecttag'); my $obj_type = $q->param('__type') || 'entry'; my $obj_class = $app->model($obj_type); my $iter = $ot_class->load_iter( { blog_id => $blog_id, object_datasource => $obj_class->datasource, tag_id => $id }, { 'join' => $obj_class->join_on( undef, { id => \'= objecttag_object_id', ( $obj_class =~ m/asset/i ? () : ( class => $obj_class->class_type ) ) } ) } ); if ($iter) { my @ot; while ( my $obj = $iter->() ) { push @ot, $obj->id; } foreach (@ot) { my $obj = $ot_class->load($_); $obj->remove or return $app->errtrans( 'Removing tag failed: [_1]', $obj->errstr ); } } $app->run_callbacks( 'cms_post_delete.' . $type, $app, $obj ); next; } } elsif ( $type eq 'category' ) { my @kids = MT::Category->load( { parent => $id } ); return $app->errtrans( "You can't delete that category because it has sub-categories. Move or delete the sub-categories first if you want to delete this one." ) if @kids; if ( $app->config('DeleteFilesAtRebuild') ) { require MT::Blog; require MT::Entry; require MT::Placement; my $blog = MT::Blog->load($blog_id); my $at = $blog->archive_type; if ( $at && $at ne 'None' ) { my @at = split /,/, $at; for my $target (@at) { my $archiver = $app->publisher->archiver($target); next unless $archiver; if ( $archiver->category_based ) { if ( $archiver->date_based ) { my @entries = MT::Entry->load( { status => MT::Entry::RELEASE() }, { join => MT::Placement->join_on( 'entry_id', { category_id => $id }, { unqiue => 1 } ) } ); for (@entries) { $app->publisher->remove_entry_archive_file( Category => $obj, ArchiveType => $target, Entry => $_ ); } } else { $app->publisher->remove_entry_archive_file( Category => $obj, ArchiveType => $target ); } } } } } } elsif ( $type eq 'entry' ) { if ( $app->config('DeleteFilesAtRebuild') ) { $app->publisher->remove_entry_archive_file( Entry => $obj, ArchiveType => 'Individual' ); require MT::Blog; my $blog = MT::Blog->load($blog_id); my $at = $blog->archive_type; if ( $at && $at ne 'None' ) { my @at = split (/,/, $at); for my $target (@at) { my $archiver = $app->publisher->archiver($target); next unless $archiver; my $entries_count = $archiver->archive_entries_count; if ($entries_count){ my $count = $entries_count->($blog, $target, $obj); if ( $count == 1 ) { $app->publisher->remove_entry_archive_file( Entry => $obj, ArchiveType => $target ); } } } } } } elsif ( $type eq 'page' ) { if ( $app->config('DeleteFilesAtRebuild') ) { $app->publisher->remove_entry_archive_file( Entry => $obj, ArchiveType => 'Page' ); } } elsif ( $type eq 'author' ) { if ( $app->config->ExternalUserManagement ) { require MT::LDAP; my $ldap = MT::LDAP->new or return $app->error( MT->translate( "Loading MT::LDAP failed: [_1].", MT::LDAP->errstr ) ); my $dn = $ldap->get_dn( $obj->name ); if ($dn) { $app->add_return_arg( author_ldap_found => 1 ); } } } # FIXME: enumeration of types if ( $type eq 'template' && $obj->type !~ /(custom|index|archive|page|individual|category|widget|backup)/o ) { $required_items++; } else { $obj->remove or return $app->errtrans( 'Removing [_1] failed: [_2]', $app->translate($type), $obj->errstr ); $app->run_callbacks( 'cms_post_delete.' . $type, $app, $obj ); } } require MT::Entry; for my $entry_id ( keys %rebuild_entries ) { my $entry = MT::Entry->load($entry_id); $app->rebuild_entry( Entry => $entry, BuildDependencies => 1 ) or return $app->publish_error(); } for my $cat_id (@rebuild_cats) { # FIXME: What about other category-based archives? # What if user is not publishing category archives? my $cat = MT::Category->load($cat_id); $app->rebuild( Category => $cat, BlogID => $blog_id, ArchiveType => 'Category' ) or return $app->publish_error(); } $app->run_callbacks( 'rebuild', MT::Blog->load($blog_id) ); $app->add_return_arg( $type eq 'ping' ? ( saved_deleted_ping => 1 ) : ( saved_deleted => 1 ) ); if ( $q->param('is_power_edit') ) { $app->add_return_arg( is_power_edit => 1 ); } if ($required_items) { $app->add_return_arg( error => $app->translate("System templates can not be deleted.") ); } $app->call_return; } sub enable_object { shift->set_object_status( MT::Author::ACTIVE() ) } sub disable_object { shift->set_object_status( MT::Author::INACTIVE() ) } sub set_object_status { my $app = shift; my ($new_status) = @_; $app->validate_magic() or return; return $app->error( $app->translate('Permission denied.') ) unless $app->user->is_superuser; return $app->error( $app->translate("Invalid request.") ) if $app->request_method ne 'POST'; my $q = $app->param; my $type = $q->param('_type'); return $app->error( $app->translate('Invalid type') ) unless ( $type eq 'user' ) || ( $type eq 'author' ) || ( $type eq 'group' ); my $class = $app->model($type); my @sync; my $saved = 0; for my $id ( $q->param('id') ) { next unless $id; # avoid 'empty' ids my $obj = $class->load($id); next unless $obj; if ( ( $obj->id == $app->user->id ) && ( $type eq 'author' ) ) { next; } next if $new_status == $obj->status; $obj->status($new_status); $obj->save; $saved++; if ( $type eq 'author' ) { if ( $new_status == MT::Author::ACTIVE() ) { push @sync, $obj; } } } my $unchanged = 0; if (@sync) { MT::Auth->synchronize_author( User => \@sync ); foreach (@sync) { if ( $_->status != MT::Author::ACTIVE() ) { $unchanged++; } } } if ( $saved && ( $saved > $unchanged ) ) { $app->add_return_arg( saved_status => ( $new_status == MT::Author::ACTIVE() ) ? 'enabled' : 'disabled' ); } $app->add_return_arg( is_power_edit => 1 ) if $q->param('is_power_edit'); $app->add_return_arg( unchanged => $unchanged ) if $unchanged; $app->call_return; } sub model { my $app = shift; my ($type) = @_; my $class = $app->SUPER::model($type) or return $app->error( $app->translate( "Unknown object type [_1]", $type ) ); $class; } *_load_driver_for = \&model; sub asset_insert { my $app = shift; my $text = $app->_process_post_upload(); return unless defined $text; $app->load_tmpl( 'dialog/asset_insert.tmpl', { upload_html => $text || '', edit_field => scalar $app->param('edit_field') || '', }, ); } sub asset_userpic { my $app = shift; my ($param) = @_; my ($id, $asset); if ($asset = $param->{asset}) { $id = $asset->id; } else { $id = $param->{asset_id} || scalar $app->param('id'); $asset = $app->model('asset')->lookup($id); } my $thumb_html = $app->model('author')->userpic_html( Asset => $asset ); my $user_id = $param->{user_id} || $app->param('user_id'); if ($user_id) { my $user = $app->model('author')->load( {id => $user_id} ); if ($user) { # Delete the author's userpic thumb (if any); it'll be regenerated. if ($user->userpic_asset_id != $asset->id) { my $old_file = $user->userpic_file(); my $fmgr = MT::FileMgr->new('Local'); if ($fmgr->exists($old_file)) { $fmgr->delete($old_file); } $user->userpic_asset_id($asset->id); $user->save; } } } $app->load_tmpl( 'dialog/asset_userpic.tmpl', { asset_id => $id, edit_field => $app->param('edit_field') || '', author_userpic => $thumb_html, }, ); } sub complete_upload { my $app = shift; my %param = $app->param_hash; my $asset; require MT::Asset; $param{id} && ( $asset = MT::Asset->load( $param{id} ) ) or return $app->errtrans("Invalid request."); $asset->label( $param{label} ) if $param{label}; $asset->description( $param{description} ) if $param{description}; if ( $param{tags} ) { require MT::Tag; my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} ); my @tags = MT::Tag->split( $tag_delim, $param{tags} ); $asset->set_tags(@tags); } $asset->save(); $asset->on_upload( \%param ); my $perms = $app->permissions; return $app->return_to_dashboard( permission => 1 ) unless $app->user->is_superuser || ( $perms && ( $perms->can_edit_assets || $perms->can_edit_all_posts || $perms->can_create_post ) ); return $app->redirect( $app->uri( 'mode' => 'list_assets', args => { 'blog_id' => $app->param('blog_id') } ) ); } sub start_upload_entry { my $app = shift; my $q = $app->param; $q->param( '_type', 'entry' ); defined( my $text = $app->_process_post_upload ) or return; $q->param( 'text', $text ); # strip any asset id $q->param( 'id', 0 ); # clear tags value $app->param->param( 'tags', '' ); $app->edit_object; } sub _process_post_upload { my $app = shift; my %param = $app->param_hash; my $asset; require MT::Asset; $param{id} && ( $asset = MT::Asset->load( $param{id} ) ) or return $app->errtrans("Invalid request."); $asset->label( $param{label} ) if $param{label}; $asset->description( $param{description} ) if $param{description}; if ( $param{tags} ) { require MT::Tag; my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} ); my @tags = MT::Tag->split( $tag_delim, $param{tags} ); $asset->set_tags(@tags); } $asset->save(); $asset->on_upload( \%param ); return $app->asset_insert_text( \%param ); } sub asset_insert_text { my $app = shift; my ($param) = @_; my $q = $app->param; my $id = $app->param('id') or return $app->errtrans("Invalid request."); require MT::Asset; my $asset = MT::Asset->load($id) or return $app->errtrans( "Can't load file #[_1].", $id ); return $asset->as_html($param); } use constant NEW_PHASE => 1; sub save_commenter_perm { my $app = shift; my ($params) = @_; my $q = $app->param; my $action = $q->param('action'); $app->validate_magic() or return; my $acted_on; my %rebuild_set; my @ids = $params ? @$params : $app->param('commenter_id'); my $blog_id = $q->param('blog_id'); my $author = $app->user; foreach my $id (@ids) { ( $id, $blog_id ) = @$id if ref $id eq 'ARRAY'; my $cmntr = MT::Author->load($id) or return $app->errtrans( "No such commenter [_1].", $id ); my $old_status = $cmntr->commenter_status($blog_id); if ( $action eq 'trust' && $cmntr->commenter_status($blog_id) != MT::Author::APPROVED() ) { $cmntr->approve($blog_id) or return $app->error( $cmntr->errstr ); $app->log( $app->translate( "User '[_1]' trusted commenter '[_2]'.", $author->name, $cmntr->name ) ); $acted_on++; } elsif ($action eq 'ban' && $cmntr->commenter_status($blog_id) != MT::Author::BANNED() ) { $cmntr->ban($blog_id) or return $app->error( $cmntr->errstr ); $app->log( $app->translate( "User '[_1]' banned commenter '[_2]'.", $author->name, $cmntr->name ) ); $acted_on++; } elsif ($action eq 'unban' && $cmntr->commenter_status($blog_id) == MT::Author::BANNED() ) { $cmntr->pending($blog_id) or return $app->error( $cmntr->errstr ); $app->log( $app->translate( "User '[_1]' unbanned commenter '[_2]'.", $author->name, $cmntr->name ) ); $acted_on++; } elsif ($action eq 'untrust' && $cmntr->commenter_status($blog_id) == MT::Author::APPROVED() ) { $cmntr->pending($blog_id) or return $app->error( $cmntr->errstr ); $app->log( $app->translate( "User '[_1]' untrusted commenter '[_2]'.", $author->name, $cmntr->name ) ); $acted_on++; } require MT::Entry; require MT::Comment; my $iter = MT::Entry->load_iter( undef, { join => MT::Comment->join_on( 'entry_id', { commenter_id => $cmntr->id } ) } ); my $e; while ( $e = $iter->() ) { $rebuild_set{$id} = $e; } } if ($acted_on) { my %msgs = ( trust => 'trusted', ban => 'banned', unban => 'unbanned', untrust => 'untrusted' ); $app->add_return_arg( $msgs{$action} => 1 ); } $app->call_return; } sub map_comment_to_commenter { my $app = shift; my ($comments) = @_; my %commenters; require MT::Comment; for my $id (@$comments) { my $cmt = MT::Comment->load($id); if ( $cmt->commenter_id ) { $commenters{ $cmt->commenter_id . ':' . $cmt->blog_id } = [ $cmt->commenter_id, $cmt->blog_id ]; } else { $app->add_return_arg( 'unauth', 1 ); } } return values %commenters; } sub trust_commenter_by_comment { my $app = shift; my @comments = $app->param('id'); my @commenters = $app->map_comment_to_commenter( \@comments ); $app->param( 'action', 'trust' ); $app->save_commenter_perm( \@commenters ); } sub untrust_commenter_by_comment { my $app = shift; my @comments = $app->param('id'); my @commenters = $app->map_comment_to_commenter( \@comments ); $app->param( 'action', 'untrust' ); $app->save_commenter_perm( \@commenters ); } sub ban_commenter_by_comment { my $app = shift; my @comments = $app->param('id'); my @commenters = $app->map_comment_to_commenter( \@comments ); $app->param( 'action', 'ban' ); $app->save_commenter_perm( \@commenters ); } sub unban_commenter_by_comment { my $app = shift; my @comments = $app->param('id'); my @commenters = $app->map_comment_to_commenter( \@comments ); $app->param( 'action', 'unban' ); $app->save_commenter_perm( \@commenters ); } sub trust_commenter { my $app = shift; my @commenters = $app->param('id'); $app->param( 'action', 'trust' ); $app->save_commenter_perm( \@commenters ); } sub ban_commenter { my $app = shift; my @commenters = $app->param('id'); $app->param( 'action', 'ban' ); $app->save_commenter_perm( \@commenters ); } sub unban_commenter { my $app = shift; my @commenters = $app->param('id'); $app->param( 'action', 'unban' ); $app->save_commenter_perm( \@commenters ); } sub untrust_commenter { my $app = shift; my @commenters = $app->param('id'); $app->param( 'action', 'untrust' ); $app->save_commenter_perm( \@commenters ); } sub rebuild_phase { my $app = shift; my $type = $app->param('_type') || 'entry'; my @ids = $app->param('id'); $app->{goback} = "window.location='" . $app->return_uri . "'"; $app->{value} ||= $app->translate('Go Back'); if ( $type eq 'entry' ) { my %ids = map { $_ => 1 } @ids; return $app->rebuild_these( \%ids ); } elsif ( $type eq 'template' ) { require MT::Template; foreach (@ids) { my $template = MT::Template->load($_); $app->rebuild_indexes( Template => $template, Force => 1 ) or return; } } $app->call_return; } sub draft_entries { my $app = shift; require MT::Entry; $app->update_entry_status( MT::Entry::HOLD(), $app->param('id') ); } sub publish_entries { my $app = shift; require MT::Entry; $app->update_entry_status( MT::Entry::RELEASE(), $app->param('id') ); } sub update_entry_status { my $app = shift; my ( $new_status, @ids ) = @_; return $app->errtrans("Need a status to update entries") unless $new_status; return $app->errtrans("Need entries to update status") unless @ids; my @bad_ids; my %rebuild_these; require MT::Entry; foreach my $id (@ids) { my $entry = MT::Entry->load($id) or return $app->errtrans( "One of the entries ([_1]) did not actually exist", $id ); next if $entry->status == $new_status; if ( $app->config('DeleteFilesAtRebuild') && ( MT::Entry::RELEASE() eq $entry->status ) ) { my $archive_type = $entry->class eq 'page' ? 'Page' : 'Individual'; $app->publisher->remove_entry_archive_file( Entry => $entry, ArchiveType => $archive_type ); } my $old_status = $entry->status; $entry->status($new_status); $entry->save() and $rebuild_these{$id} = 1; my $message = $app->translate( "[_1] '[_2]' (ID:[_3]) status changed from [_4] to [_5]", $entry->class_label, $entry->title, $entry->id, $app->translate( MT::Entry::status_text($old_status) ), $app->translate( MT::Entry::status_text($new_status) ) ); $app->log( { message => $message, level => MT::Log::INFO(), class => $entry->class, category => 'edit', metadata => $entry->id } ); } $app->rebuild_these( \%rebuild_these, how => NEW_PHASE ); } sub open_batch_editor { my $app = shift; my @ids = $app->param('id'); $app->param( 'is_power_edit', 1 ); $app->param( 'filter', 'power_edit' ); $app->param( 'filter_val', \@ids ); $app->mode( 'list_' . ( 'entry' eq $app->param('_type') ? 'entries' : 'pages' ) ); $app->list_entries( { type => $app->param('_type') } ); } sub approve_item { my $app = shift; my $perms = $app->permissions; $app->param( 'approve', 1 ); $app->set_item_visible; } sub unapprove_item { my $app = shift; my $perms = $app->permissions; $app->param( 'unapprove', 1 ); $app->set_item_visible; } sub set_item_visible { my $app = shift; my $perms = $app->permissions; my $author = $app->user; my $type = $app->param('_type'); my $class = $app->model($type); my @obj_ids = $app->param('id'); if ( my $req_nonce = $app->param('nonce') ) { if ( scalar @obj_ids == 1 ) { my $cmt_id = $obj_ids[0]; if ( my $obj = $class->load($cmt_id) ) { my $nonce = MT::Util::perl_sha1_digest_hex( $obj->id . $obj->created_on . $obj->blog_id . $app->config->SecretToken ); return $app->errtrans("Invalid request.") unless $nonce eq $req_nonce; my $return_args = $app->uri_params( mode => 'view', args => { '_type' => $type, id => $cmt_id, blog_id => $obj->blog_id } ); $return_args =~ s!^\?!!; $app->return_args($return_args); } else { return $app->errtrans("Invalid request."); } } else { return $app->errtrans("Invalid request."); } } else { $app->validate_magic() or return; } my $new_visible; if ( $app->param('approve') ) { $new_visible = 1; } elsif ( $app->param('unapprove') ) { $new_visible = 0; } my %rebuild_set = (); require MT::Entry; foreach my $id (@obj_ids) { my $obj = $class->load($id); my $old_visible = $obj->visible || 0; if ( $old_visible != $new_visible ) { if ( $obj->isa('MT::TBPing') ) { my $obj_parent = $obj->parent(); if ( $obj_parent->isa('MT::Category') ) { my $blog = MT::Blog->load( $obj_parent->blog_id ); next unless $blog; $app->publisher->_rebuild_entry_archive_type( Entry => undef, Blog => $blog, Category => $obj_parent, ArchiveType => 'Category' ); } else { if ( !$author->is_superuser ) { if ( !$perms || $perms->blog_id != $obj->blog_id ) { $perms = $author->permissions( $obj->blog_id ); } unless ($perms) { return $app->errtrans( "You don't have permission to approve this comment." ); } unless ( $perms->can_manage_feedback || ( $perms->can_publish_post && ( $obj_parent->author_id == $author->id ) ) ) { return $app->errtrans( "You don't have permission to approve this comment." ); } } $rebuild_set{ $obj_parent->id } = $obj_parent; } } elsif ( $obj->entry_id ) { # TODO: Factor out permissions checking my $entry = MT::Entry->load( $obj->entry_id ) || return $app->error( $app->translate("Comment on missing entry!") ); if ( !$author->is_superuser ) { if ( !$perms || $perms->blog_id != $obj->blog_id ) { $perms = $author->permissions( $obj->blog_id ); } unless ($perms) { return $app->errtrans( "You don't have permission to approve this comment." ); } unless ( $perms->can_manage_feedback || ( $perms->can_publish_post && ( $entry->author_id == $author->id ) ) ) { return $app->errtrans( "You don't have permission to approve this comment." ); } } $rebuild_set{ $obj->entry_id } = $entry; } $obj->visible($new_visible); $obj->save(); } } my $approved_flag = ( $new_visible ? '' : 'un' ) . 'approved'; $app->add_return_arg( $approved_flag => 1 ); return $app->rebuild_these( \%rebuild_set, how => NEW_PHASE ); } sub list_commenter { my $app = shift; unless ( $app->user_can_admin_commenters ) { return $app->error( $app->translate("Permission denied.") ); } my $q = $app->param; my $list_pref = $app->list_pref('commenter'); my $blog_id = $q->param('blog_id'); my %param = %$list_pref; my %terms = ( type => MT::Author::COMMENTER() ); my %terms2 = (); my $limit = $list_pref->{rows}; my $offset = $q->param('offset') || 0; my %arg; require MT::Comment; $arg{'join'} = MT::Comment->join_on( 'commenter_id', { ( $blog_id ? ( blog_id => $blog_id ) : () ) }, { 'sort' => 'created_on', direction => 'descend', unique => 1 } ); my ( $filter_col, $val ); $param{filter_args} = ""; if ( ( $filter_col = $q->param('filter') ) && ( $val = $q->param('filter_val') ) ) { if ( !exists( $terms{$filter_col} ) ) { if ( $filter_col eq 'status' ) { my ($perm) = ( $val eq 'neutral' ? undef : $val eq 'approved' ? 'comment' : $val eq 'banned' ? 'not_comment' : undef ); $arg{join} = MT::Permission->join_on( 'author_id', { blog_id => $blog_id, ( defined $perm ) ? ( permissions => $perm ) : () } ); } else { $terms{$filter_col} = $val; } $param{filter} = $filter_col; $param{filter_val} = $val; $param{filter_args} = "&filter=" . encode_url($filter_col) . "&filter_val=" . encode_url($val); } } $arg{'offset'} = $offset if $offset; $arg{'limit'} = $limit + 1; my $terms = \%terms; my $arg = \%arg; my $iter = MT::Author->load_iter( $terms, $arg ); my $data = $app->build_commenter_table( iter => $iter, param => \%param ); if ( @$data > $limit ) { pop @$data; $param{next_offset} = 1; $param{next_offset_val} = $offset + $limit; } if ( $offset > 0 ) { $param{prev_offset} = 1; $param{prev_offset_val} = $offset - $limit; $param{prev_offset_val} = 0 if $param{prev_offset_val} < 0; } $param{object_type} = 'commenter'; $param{search_label} = $app->translate('Commenters'); $param{list_start} = $offset + 1; $param{list_end} = $offset + scalar @$data; $param{offset} = $offset; $param{limit} = $limit; delete $arg->{limit}; delete $arg->{offset}; $param{list_total} = MT::Author->count( $terms, $arg ); if ( $param{list_total} ) { $param{next_max} = $param{list_total} - $limit; $param{next_max} = 0 if ( $param{next_max} || 0 ) < $offset + 1; } $app->add_breadcrumb( $app->translate('Authenticated Commenters') ); $param{nav_commenters} = 1; for my $msg (qw(trusted untrusted banned unbanned)) { $param{$msg} = 1 if $app->param($msg); } return $app->load_tmpl( 'list_commenter.tmpl', \%param ); } sub build_commenter_table { my $app = shift; my (%args) = @_; my $param = $args{param}; my $iter; if ( $args{load_args} ) { my $class = $app->model('commenter'); $iter = $class->load_iter( @{ $args{load_args} } ); } elsif ( $args{iter} ) { $iter = $args{iter}; } elsif ( $args{items} ) { $iter = sub { pop @{ $args{items} } }; } return [] unless $iter; my $app_user = $app->user; my $user_perm = $app->permissions; my $blog_id = $app->param('blog_id'); my @data; require MT::Blog; my $blog = MT::Blog->load($blog_id); while ( my $cmtr = $iter->() ) { require MT::Comment; my $cmt_count = MT::Comment->count( { commenter_id => $cmtr->id, blog_id => $blog_id } ); my $most_recent = MT::Comment->load( { commenter_id => $cmtr->id, blog_id => $blog_id }, { 'sort' => 'created_on', direction => 'descend' } ) if $cmt_count > 0; my $blog_connection = MT::Permission->load( { author_id => $cmtr->id, blog_id => $blog_id } ); # Tells us whether the commenter is associated with this # blog. the role flags are not important next if ( !$cmt_count && !$blog_connection ); my $row = {}; $row->{author_id} = $cmtr->id(); $row->{author} = $cmtr->name(); $row->{author_display} = $cmtr->nickname(); $row->{email} = $cmtr->email(); $row->{url} = $cmtr->url(); $row->{email_hidden} = $cmtr->is_email_hidden(); $row->{comment_count} = $cmt_count; if ($most_recent) { if ( my $ts = $most_recent->created_on ) { $row->{most_recent_time_formatted} = format_ts( LISTING_DATETIME_FORMAT, $ts, $blog, $app->user ? $app->user->preferred_language : undef ); $row->{most_recent_formatted} = format_ts( LISTING_DATE_FORMAT, $ts, $blog, $app->user ? $app->user->preferred_language : undef ); $row->{most_recent_relative} = relative_date( $ts, time, $blog ); } } $row->{status} = { PENDING => "neutral", APPROVED => "approved", BANNED => "banned" }->{ $cmtr->commenter_status($blog_id) }; $row->{commenter_approved} = $cmtr->commenter_status($blog_id) == MT::Author::APPROVED(); $row->{commenter_banned} = $cmtr->commenter_status($blog_id) == MT::Author::BANNED(); $row->{commenter_url} = $cmtr->url if $cmtr->url; $row->{has_edit_access} = $app->user_can_admin_commenters; $row->{object} = $cmtr; push @data, $row; } return [] unless @data; $param->{commenter_table}[0]{object_loop} = \@data if @data; $app->load_list_actions( 'commenter', $param->{commenter_table}[0] ); $param->{commenter_table}[0]{page_actions} = $app->page_actions('list_commenter'); $param->{object_loop} = $param->{commenter_table}[0]{object_loop}; \@data; } sub list_comments { my $app = shift; my $trim_length = $app->config('ShowIPInformation') ? const('DISPLAY_LENGTH_EDIT_COMMENT_TEXT_SHORT') : const('DISPLAY_LENGTH_EDIT_COMMENT_TEXT_LONG'); my $author_max_len = const('DISPLAY_LENGTH_EDIT_COMMENT_AUTHOR'); my $comment_short_len = const('DISPLAY_LENGTH_EDIT_COMMENT_TEXT_BREAK_UP_SHORT'); my $comment_long_len = const('DISPLAY_LENGTH_EDIT_COMMENT_TEXT_BREAK_UP_LONG'); my $title_max_len = const('DISPLAY_LENGTH_EDIT_COMMENT_TITLE'); my ( %entries, %blogs, %cmntrs ); my $perms = $app->permissions; my $user = $app->user; my $admin = $user->is_superuser || ( $perms && $perms->can_administer_blog ); my $can_empty_junk = $admin || ( $perms && $perms->can_manage_feedback ) ? 1 : 0; my $state_editable = $admin || ( $perms && ( $perms->can_publish_post || $perms->can_edit_all_posts || $perms->can_manage_feedback ) ) ? 1 : 0; my $entry_pkg = $app->model('entry'); my $code = sub { my ( $obj, $row ) = @_; # Comment column $row->{comment_short} = ( substr_text( $obj->text(), 0, $trim_length ) . ( length_text( $obj->text ) > $trim_length ? "..." : "" ) ); $row->{comment_short} = break_up_text( $row->{comment_short}, $comment_short_len ) ; # break up really long strings $row->{comment_long} = remove_html( $obj->text ); $row->{comment_long} = break_up_text( $row->{comment_long}, $comment_long_len ) ; # break up really long strings # Commenter name column $row->{author_display} = $row->{author}; $row->{author_display} = substr_text( $row->{author_display}, 0, $author_max_len ) . '...' if $row->{author_display} && length_text( $row->{author_display} ) > $author_max_len; if ( $row->{commenter_id} ) { my $cmntr = $cmntrs{ $row->{commenter_id} } ||= MT::Author->load( { id => $row->{commenter_id} } ); if ($cmntr) { $row->{email_hidden} = $cmntr && $cmntr->is_email_hidden(); $row->{auth_icon_url} = $cmntr->auth_icon_url; my $status = $cmntr->commenter_status( $obj->blog_id ); $row->{commenter_approved} = ( $cmntr->commenter_status( $obj->blog_id ) == MT::Author::APPROVED() ); $row->{commenter_banned} = ( $cmntr->commenter_status( $obj->blog_id ) == MT::Author::BANNED() ); } } # Entry column my $entry = $entries{ $obj->entry_id } ||= $entry_pkg->load( $obj->entry_id ); unless ($entry) { $entry = $entry_pkg->new; $entry->title( '* ' . $app->translate('Orphaned comment') . ' *' ); } $row->{entry_class} = $entry->class; $row->{entry_class_label} = $entry->class_label; $row->{entry_title} = ( defined( $entry->title ) ? $entry->title : defined( $entry->text ) ? $entry->text : '' ); $row->{entry_title} = $app->translate('(untitled)') if $row->{entry_title} eq ''; $row->{entry_title} = substr_text( $row->{entry_title}, 0, $title_max_len ) . '...' if $row->{entry_title} && length_text( $row->{entry_title} ) > $title_max_len; # Date column my $blog = $blogs{ $obj->blog_id } ||= $obj->blog; if ( my $ts = $obj->created_on ) { $row->{created_on_time_formatted} = format_ts( LISTING_DATETIME_FORMAT, $ts, $blog, $app->user ? $app->user->preferred_language : undef ); $row->{created_on_formatted} = format_ts( LISTING_DATE_FORMAT, $ts, $blog, $app->user ? $app->user->preferred_language : undef ); $row->{created_on_relative} = relative_date( $ts, time, $blog ); } $row->{has_edit_access} = $state_editable || ( $entry && ( $user->id == $entry->author_id ) ); # Blog column if ($blog) { $row->{weblog_id} = $blog->id; $row->{weblog_name} = $blog->name; } else { $row->{weblog_name} = '* ' . $app->translate('Orphaned comment') . ' *'; } $row->{reply_count} = $app->model('comment')->count( { parent_id => $obj->id } ); }; my %param; my $blog_id = $app->param('blog_id'); $param{feed_name} = $app->translate("Comments Activity Feed"); $param{feed_url} = $app->make_feed_link( 'comment', $blog_id ? { blog_id => $blog_id } : undef ); $param{filter_spam} = ( $app->param('filter_key') && $app->param('filter_key') eq 'spam' ); $param{has_expanded_mode} = 1; $param{object_type} = 'comment'; $param{screen_id} = 'list-comment'; $param{screen_class} = 'list-comment'; $param{search_label} = $app->translate('Comments'); $param{state_editable} = $state_editable; $param{can_empty_junk} = $can_empty_junk; return $app->listing( { type => 'comment', code => $code, args => { sort => 'created_on', direction => 'descend' }, params => \%param, } ); } sub build_asset_table { my $app = shift; my (%args) = @_; my $asset_class = $app->model('asset') or return; my $perms = $app->permissions; my $list_pref = $app->list_pref('asset'); my $limit = $args{limit}; my $param = $args{param} || {}; my $iter; if ( $args{load_args} ) { my $class = $app->model('asset'); $iter = $class->load_iter( @{ $args{load_args} } ); } elsif ( $args{iter} ) { $iter = $args{iter}; } elsif ( $args{items} ) { $iter = sub { pop @{ $args{items} } }; $limit = scalar @{ $args{items} }; } return [] unless $iter; my @data; my $hasher = $app->build_asset_hasher; while ( my $obj = $iter->() ) { my $row = $obj->column_values; $hasher->($obj, $row); $row->{object} = $obj; push @data, $row; last if $limit and @data > $limit; } return [] unless @data; $param->{template_table}[0] = {%$list_pref}; $param->{template_table}[0]{object_loop} = \@data; $param->{template_table}[0]{object_type} = 'asset'; $app->load_list_actions( 'asset', $param ); $param->{object_loop} = \@data; $param->{can_delete_files} = 1 if (($perms && $perms->can_edit_assets) || $app->user->is_superuser); \@data; } sub build_template_table { my $app = shift; my (%args) = @_; my $perms = $app->permissions; my $list_pref = $app->list_pref('template'); my $limit = $args{limit}; my $param = $args{param} || {}; my $iter; if ( $args{load_args} ) { my $class = $app->model('template'); $iter = $class->load_iter( @{ $args{load_args} } ); } elsif ( $args{iter} ) { $iter = $args{iter}; } elsif ( $args{items} ) { $iter = sub { pop @{ $args{items} } }; $limit = scalar @{ $args{items} }; } return [] unless $iter; my @data; my $i; my %blogs; while ( my $tmpl = $iter->() ) { my $blog = $blogs{ $tmpl->blog_id } ||= MT::Blog->load( $tmpl->blog_id ); my $row = $tmpl->column_values; $row->{name} = '' if !defined $row->{name}; $row->{name} =~ s/^\s+|\s+$//g; $row->{name} = "(" . $app->translate("No Name") . ")" if $row->{name} eq ''; my $published_url = $tmpl->published_url; $row->{published_url} = $published_url if $published_url; # FIXME: enumeration of types $row->{can_delete} = 1 if $tmpl->type =~ m/(custom|index|archive|page|individual|category)/; if ($blog) { $row->{weblog_name} = $blog->name; } elsif ($tmpl->blog_id) { $row->{weblog_name} = '* ' . $app->translate('Orphaned') . ' *'; } else { $row->{weblog_name} = '* ' . $app->translate('Global Templates') . ' *'; } $row->{object} = $tmpl; push @data, $row; last if @data > $limit; } return [] unless @data; $param->{template_table}[0] = {%$list_pref}; $param->{template_table}[0]{object_loop} = \@data; $param->{template_table}[0]{object_type} = 'template'; $app->load_list_actions( 'template', $param ); $param->{object_loop} = \@data; \@data; } sub build_comment_table { my $app = shift; my (%args) = @_; my $author = $app->user; my $class = $app->model('comment'); my $list_pref = $app->list_pref('comment'); my $entry_pkg = $app->model('entry'); my $iter; if ( $args{load_args} ) { $iter = $class->load_iter( @{ $args{load_args} } ); } elsif ( $args{iter} ) { $iter = $args{iter}; } elsif ( $args{items} ) { $iter = sub { pop @{ $args{items} } }; } return [] unless $iter; my $limit = $args{limit}; my $param = $args{param} || {}; my @data; my $i; $i = 1; my ( %blogs, %entries, %perms, %cmntrs ); my $trim_length = $app->config('ShowIPInformation') ? const('DISPLAY_LENGTH_EDIT_COMMENT_TEXT_SHORT') : const('DISPLAY_LENGTH_EDIT_COMMENT_TEXT_LONG'); my $author_max_len = const('DISPLAY_LENGTH_EDIT_COMMENT_AUTHOR'); my $comment_short_len = const('DISPLAY_LENGTH_EDIT_COMMENT_TEXT_BREAK_UP_SHORT'); my $comment_long_len = const('DISPLAY_LENGTH_EDIT_COMMENT_TEXT_BREAK_UP_LONG'); my $title_max_len = const('DISPLAY_LENGTH_EDIT_COMMENT_TITLE'); while ( my $obj = $iter->() ) { my $row = $obj->column_values; $row->{author_display} = $row->{author}; $row->{author_display} = substr_text( $row->{author_display}, 0, $author_max_len ) . '...' if $row->{author_display} && length_text( $row->{author_display} ) > $author_max_len; $row->{comment_short} = ( substr_text( $obj->text(), 0, $trim_length ) . ( length_text( $obj->text ) > $trim_length ? "..." : "" ) ); $row->{comment_short} = break_up_text( $row->{comment_short}, $comment_short_len ) ; # break up really long strings $row->{comment_long} = remove_html( $obj->text ); $row->{comment_long} = break_up_text( $row->{comment_long}, $comment_long_len ) ; # break up really long strings $row->{visible} = $obj->visible(); $row->{entry_id} = $obj->entry_id(); my $blog = $blogs{ $obj->blog_id } ||= $obj->blog; my $entry = $entries{ $obj->entry_id } ||= $entry_pkg->load( $obj->entry_id ); unless ($entry) { $entry = $entry_pkg->new; $entry->title( '* ' . $app->translate('Orphaned comment') . ' *' ); } $row->{entry_class} = $entry->class; $row->{entry_class_label} = $entry->class_label; $row->{entry_title} = ( defined( $entry->title ) ? $entry->title : defined( $entry->text ) ? $entry->text : '' ); $row->{entry_title} = $app->translate('(untitled)') if $row->{entry_title} eq ''; $row->{entry_title} = substr_text( $row->{entry_title}, 0, $title_max_len ) . '...' if $row->{entry_title} && length_text( $row->{entry_title} ) > $title_max_len; $row->{commenter_id} = $obj->commenter_id() if $obj->commenter_id(); my $cmntr; if ( $obj->commenter_id ) { $cmntr = $cmntrs{ $obj->commenter_id } ||= MT::Author->load( { id => $obj->commenter_id(), } ); } if ($cmntr) { $row->{email_hidden} = $cmntr && $cmntr->is_email_hidden(); $row->{auth_icon_url} = $cmntr->auth_icon_url; my $status = $cmntr->commenter_status( $obj->blog_id ); $row->{commenter_approved} = ( $cmntr->commenter_status( $obj->blog_id ) == MT::Author::APPROVED() ); $row->{commenter_banned} = ( $cmntr->commenter_status( $obj->blog_id ) == MT::Author::BANNED() ); } if ( my $ts = $obj->created_on ) { $row->{created_on_time_formatted} = format_ts( LISTING_DATETIME_FORMAT, $ts, $blog, $app->user ? $app->user->preferred_language : undef ); $row->{created_on_formatted} = format_ts( LISTING_DATE_FORMAT, $ts, $blog, $app->user ? $app->user->preferred_language : undef ); $row->{created_on_relative} = relative_date( $ts, time, $blog ); } if ( $author->is_superuser() ) { $row->{has_edit_access} = 1; $row->{has_bulk_access} = 1; } else { my $perms = $perms{ $obj->blog_id } ||= $author->permissions( $obj->blog_id ); $row->{has_bulk_access} = ( $perms && ( $perms->can_edit_all_posts || $perms->can_manage_feedback ) || ( ( $perms->can_publish_post ) && ( $author->id == $entry->author_id ) ) ); $row->{has_edit_access} = ( $perms && ( $perms->can_edit_all_posts || $perms->can_manage_feedback ) || ( ( $perms->can_create_post ) && ( $author->id == $entry->author_id ) ) ); } if ($blog) { $row->{weblog_id} = $blog->id; $row->{weblog_name} = $blog->name; } else { $row->{weblog_name} = '* ' . $app->translate('Orphaned comment') . ' *'; } $row->{reply_count} = $class->count( { parent_id => $obj->id } ); $row->{object} = $obj; push @data, $row; last if $limit and @data > $limit; } return [] unless @data; my $junk_tab = ( $app->param('tab') || '' ) eq 'junk'; $param->{comment_table}[0] = {%$list_pref}; $param->{comment_table}[0]{object_loop} = \@data; $param->{comment_table}[0]{object_type} = 'comment'; $param->{object_loop} = $param->{comment_table}[0]{object_loop} unless exists $param->{object_loop}; $app->load_list_actions( 'comment', \%$param ); \@data; } sub plugin_control { my $app = shift; $app->validate_magic or return; return unless $app->user->can_manage_plugins; my $plugin_sig = $app->param('plugin_sig') || ''; my $state = $app->param('state') || ''; my $cfg = $app->config; if ( $plugin_sig eq '*' ) { $cfg->UsePlugins( $state eq 'on' ? 1 : 0, 1 ); } else { if ( exists $MT::Plugins{$plugin_sig} ) { $cfg->PluginSwitch( $plugin_sig . '=' . ( $state eq 'on' ? '1' : '0' ), 1 ); } } $cfg->save_config; $app->add_return_arg( 'switched' => 1 ); $app->call_return; } sub build_plugin_table { my $app = shift; my (%opt) = @_; my $param = $opt{param}; my $scope = $opt{scope} || 'system'; my $cfg = $app->config; my $data = []; # we have to sort the plugin list in an odd fashion... # PLUGINS # (those at the top of the plugins directory and those # that only have 1 .pl script in a plugin folder) # PLUGIN SET # (plugins folders with multiple .pl files) my %list; my %folder_counts; for my $sig ( keys %MT::Plugins ) { my $sub = $sig =~ m!/! ? 1 : 0; my $obj = $MT::Plugins{$sig}{object}; # Prevents display of component objects next if $obj && !$obj->isa('MT::Plugin'); my $err = $MT::Plugins{$sig}{error} ? 0 : 1; my $on = $MT::Plugins{$sig}{enabled} ? 0 : 1; my ( $fld, $plg ); ( $fld, $plg ) = $sig =~ m!(.*)/(.*)!; $fld = '' unless $fld; $folder_counts{$fld}++ if $fld; $plg ||= $sig; $list{ $sub . sprintf( "%-100s", $fld ) . ( $obj ? '1' : '0' ) . $plg } = $sig; } my @keys = keys %list; foreach my $key (@keys) { my $fld = substr( $key, 1, 100 ); $fld =~ s/\s+$//; if ( !$fld || ( $folder_counts{$fld} == 1 ) ) { my $sig = $list{$key}; delete $list{$key}; my $plugin = $MT::Plugins{$sig}; my $name = $plugin && $plugin->{object} ? $plugin->{object}->name : $sig; $list{ '0' . ( ' ' x 100 ) . sprintf( "%-102s", $name ) } = $sig; } } my $last_fld = '*'; my $next_is_first; my $id = 0; ( my $cgi_path = $cfg->AdminCGIPath || $cfg->CGIPath ) =~ s|/$||; for my $list_key ( sort keys %list ) { $id++; my $plugin_sig = $list{$list_key}; next if $plugin_sig =~ m/^[^A-Za-z0-9]/; my $profile = $MT::Plugins{$plugin_sig}; my ($plg); ($plg) = $plugin_sig =~ m!(?:.*)/(.*)!; my $fld = substr( $list_key, 1, 100 ); $fld =~ s/\s+$//; my $folder = $fld ? $app->translate( "Plugin Set: [_1]", $fld ) : $app->translate("Individual Plugins"); my $row; my $icon = $app->static_path . 'images/plugin.gif'; if ( my $plugin = $profile->{object} ) { my $plugin_icon; if ( $plugin->icon ) { $plugin_icon = $app->static_path . $plugin->envelope . '/' . $plugin->icon; } else { $plugin_icon = $icon; } my $plugin_name = remove_html( $plugin->name() ); my $config_link = $plugin->config_link(); my $plugin_page = ( $cgi_path . '/' . $plugin->envelope . '/' . $config_link ) if $config_link; my $doc_link = $plugin->doc_link; if ( $doc_link && ( $doc_link !~ m!^https?://! ) ) { $doc_link = $app->static_path . $plugin->envelope . '/' . $doc_link; } my ($config_html); my %plugin_param; my $settings = $plugin->get_config_obj($scope); $plugin->load_config( \%plugin_param, $scope ); if ( my $snip_tmpl = $plugin->config_template( \%plugin_param, $scope ) ) { my $tmpl; if ( ref $snip_tmpl ne 'MT::Template' ) { require MT::Template; $tmpl = MT::Template->new( type => 'scalarref', source => ref $snip_tmpl ? $snip_tmpl : \$snip_tmpl # TBD: add path for plugin template directory ); } else { $tmpl = $snip_tmpl; } # Process template independent of $app to avoid premature # localization (give plugin a chance to do L10N first). $tmpl->param( \%plugin_param ); $config_html = $tmpl->output() or $config_html = "Error in configuration template: " . $tmpl->errstr; $config_html = $plugin->translate_templatized($config_html) if $config_html =~ m/<(?:__trans|mt_trans) /i; } else { # don't list non-configurable plugins for blog scope... next if $scope ne 'system'; } if ( $last_fld ne $fld ) { $row = { plugin_sig => $plugin_sig, plugin_folder => $folder, plugin_set => $fld ? $folder_counts{$fld} > 1 : 0, plugin_error => $profile->{error}, }; push @$data, $row; $last_fld = $fld; $next_is_first = 1; } my $registry = $plugin->registry; my $row = { first => $next_is_first, plugin_name => $plugin_name, plugin_page => $plugin_page, plugin_major => 1, plugin_icon => $plugin_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 => $doc_link, plugin_sig => $plugin_sig, plugin_key => $plugin->key(), plugin_config_link => $plugin->config_link(), plugin_config_html => $config_html, plugin_settings_id => $settings->id, plugin_id => $id, plugin_compat_errors => $registry->{compat_errors}, }; my $block_tags = $plugin->registry('tags', 'block'); my $function_tags = $plugin->registry('tags', 'function'); my $modifiers = $plugin->registry('tags', 'modifier'); my $junk_filters = $plugin->registry('junk_filters'); my $text_filters = $plugin->registry('text_filters'); $row->{plugin_tags} = listify( [ # Filter out 'plugin' registry entry grep { !/^<\$?MTplugin\$?>$/ } ( ( # Format all 'block' tags with <MT(name)> map { s/\?$//; "<MT$_>" } ( keys %{ $block_tags || {} } ) ), ( # Format all 'function' tags with <$MT(name)$> map { "<\$MT$_\$>" } ( keys %{ $function_tags || {} } ) ) ) ] ) if $block_tags || $function_tags; $row->{plugin_attributes} = listify( [ # Filter out 'plugin' registry entry grep { $_ ne 'plugin' } keys %{ $modifiers || {} } ] ) if $modifiers; $row->{plugin_junk_filters} = listify( [ # Filter out 'plugin' registry entry grep { $_ ne 'plugin' } keys %{ $junk_filters || {} } ] ) if $junk_filters; $row->{plugin_text_filters} = listify( [ # Filter out 'plugin' registry entry grep { $_ ne 'plugin' } keys %{ $text_filters || {} } ] ) if $text_filters; if ( $row->{plugin_tags} || $row->{plugin_attributes} || $row->{plugin_junk_filters} || $row->{plugin_text_filters} ) { $row->{plugin_resources} = 1; } push @$data, $row; } else { # don't list non-configurable plugins for blog scope... next if $scope ne 'system'; if ( $last_fld ne $fld ) { $row = { plugin_sig => $plugin_sig, plugin_folder => $folder, plugin_set => $fld ? $folder_counts{$fld} > 1 : 0, plugin_error => $profile->{error}, }; push @$data, $row; $last_fld = $fld; $next_is_first = 1; } # no registered plugin objects-- $row = { first => $next_is_first, plugin_major => $fld ? 0 : 1, plugin_icon => $icon, plugin_name => $plugin_sig, plugin_sig => $plugin_sig, plugin_error => $profile->{error}, plugin_disabled => $profile->{enabled} ? 0 : 1, plugin_id => $id, }; push @$data, $row; } $next_is_first = 0; } $param->{plugin_loop} = $data; } sub listify { my ($arr) = @_; my @ret; return unless ref($arr) eq 'ARRAY'; foreach (@$arr) { push @ret, { name => $_ }; } \@ret; } sub list_pings { my $app = shift; my $q = $app->param; my $perms = $app->permissions; my $can_empty_junk = 1; my $state_editable = 1; my $admin = $app->user->is_superuser || ( $perms && $perms->can_administer_blog ); if ($perms) { unless ( $perms->can_view_feedback ) { return $app->error( $app->translate("Permission denied.") ); } $can_empty_junk = $admin || ( $perms && $perms->can_manage_feedback ) ? 1 : 0; $state_editable = $admin || ( $perms && ( $perms->can_publish_post || $perms->can_edit_all_posts || $perms->can_manage_feedback ) ) ? 1 : 0; } # otherwise we simply filter the list of objects my $list_pref = $app->list_pref('ping'); my $class = $app->model("ping") or return; my $blog_id = $q->param('blog_id'); my $blog; if ($blog_id) { $blog = $app->model('blog')->load($blog_id); } my %param = %$list_pref; my %terms; if ($blog_id) { $terms{blog_id} = $blog_id; } elsif ( !$app->user->is_superuser ) { $terms{blog_id} = [ map { $_->blog_id } grep { $_->can_view_feedback } MT::Permission->load( { author_id => $app->user->id } ) ]; return $app->errtrans("Permission denied.") unless @{ $terms{blog_id} }; } my $cols = $class->column_names; my $limit = $list_pref->{rows}; my $offset = $app->param('offset') || 0; my $sort_direction = $q->param('sortasc') ? 'ascend' : 'descend'; ## We load $limit + 1 records so that we can easily tell if we have a ## page of next entries to link to. Obviously we only display $limit ## entries. my %arg; if ( ( $app->param('tab') || '' ) eq 'junk' ) { $app->param( 'filter', 'junk_status' ); $app->param( 'filter_val', '-1' ); $param{filter_special} = 1; $param{filter_phrase} = $app->translate('Junk TrackBacks'); } else { $terms{'junk_status'} = [ 0, 1 ]; } my $filter_key = $q->param('filter_key'); if ( !$filter_key && !$app->param('filter') ) { $filter_key = 'default'; } my @val = $q->param('filter_val'); my $filter_col = $q->param('filter'); if ( $filter_col && ( my $val = $q->param('filter_val') ) ) { if ( $filter_col eq 'status' ) { $terms{visible} = $val eq 'approved' ? 1 : 0; } elsif ($filter_col eq 'category_id' || $filter_col eq 'entry_id' ) { $arg{join} = $app->model('trackback')->join_on( undef, { id => \'= tbping_tb_id', $filter_col => $val, $blog_id ? ( blog_id => $blog_id ) : (), } ); if ( $filter_col eq 'entry_id' ) { my $pkg = $app->model('entry'); my $entry = $pkg->load($val); $param{filter_phrase} = $app->translate( "TrackBacks where <strong>[_1]</strong> is "[_2]".", $entry->class_label, encode_html( $entry->title ) ); } elsif ( $filter_col eq 'category_id' ) { my $pkg = $app->model('category'); my $cat = $pkg->load($val); $param{filter_phrase} = $app->translate( "TrackBacks where <strong>[_1]</strong> is "[_2]".", $cat->class_label, encode_html( $cat->label ) ); } $param{filter_special} = 1; } else { if ( $val[1] ) { $terms{$filter_col} = [ $val[0], $val[1] ]; $arg{'range_incl'} = { $filter_col => 1 }; $param{filter_val2} = $val[1]; $param{filter_range} = 1; } else { $terms{$filter_col} = $val; } } $param{filter_args} = "&filter=" . encode_url($filter_col) . "&filter_val=" . encode_url($val); $param{filter} ||= $filter_col; $param{filter_val} ||= $val; $param{is_filtered} = 1; $param{is_ip_filter} = $filter_col eq "ip"; } elsif ($filter_key) { my $filters = $app->registry("list_filters", "ping") || {}; if ( my $filter = $filters->{$filter_key} ) { if ( my $code = $filter->{code} || $app->handler_to_coderef( $filter->{handler} ) ) { $param{filter} = 1; $param{filter_key} = $filter_key; $param{filter_label} = $filter->{label}; $code->( \%terms, \%arg ); } } } my $ping_class = $app->model('ping'); my $total = $ping_class->count( \%terms, \%arg ) || 0; $arg{'sort'} = 'created_on'; $arg{direction} = $sort_direction; $arg{limit} = $limit + 1; if ( $total && $offset > $total - 1 ) { $arg{offset} = $offset = $total - $limit; } elsif ( $offset && ( ( $offset < 0 ) || ( $total - $offset < $limit ) ) ) { $arg{offset} = $offset = $total - $offset; } elsif ($offset) { $arg{offset} = $offset; } my $iter = $class->load_iter( \%terms, \%arg ); my $data = $app->build_ping_table( iter => $iter, param => \%param ); ## 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_entry = @$data > $limit; pop @$data while @$data > $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_entry) { $param{next_offset} = 1; $param{next_offset_val} = $offset + $limit; } $param{ping_count} = scalar @$data; $param{limit} = $limit; $param{offset} = $offset; $param{list_filters} = $app->list_filters('ping'); $param{saved} = $q->param('saved'); $param{junked} = $q->param('junked'); $param{unjunked} = $q->param('unjunked'); $param{approved} = $q->param('approved'); $param{unapproved} = $q->param('unapproved'); $param{emptied} = $q->param('emptied'); $param{state_editable} = $state_editable; $param{can_empty_junk} = $can_empty_junk; $param{saved_deleted_ping} = $q->param('saved_deleted') || $q->param('saved_deleted_ping'); $param{object_type} = 'ping'; $param{object_label} = $ping_class->class_label; $param{object_label_plural} = $ping_class->class_label_plural; $param{search_label} = $param{object_label_plural}; $param{list_start} = $offset + 1; $param{list_end} = $offset + scalar @$data; $param{list_total} = $total; $param{next_max} = $param{list_total} - $limit if $param{list_total}; $param{next_max} = 0 if ( $param{next_max} || 0 ) < $offset + 1; $param{page_actions} = $app->page_actions('list_pings') || $app->page_actions('list_ping'); $param{nav_trackbacks} = 1; $param{has_expanded_mode} = 1; $param{tab} = $app->param('tab') || 'pings'; $param{ "tab_" . ( $app->param('tab') || 'pings' ) } = 1; unless ($blog_id) { $param{system_overview_nav} = 1; $param{nav_pings} = 1; } $param{filter_spam} = ( $app->param('filter_key') && $app->param('filter_key') eq 'spam' ); if ( $param{'tab'} ne 'junk' ) { $param{feed_name} = $app->translate("TrackBack Activity Feed"); $param{feed_url} = $app->make_feed_link( 'ping', $blog_id ? { blog_id => $blog_id } : undef ); } $param{screen_id} = "list-ping"; $param{listing_screen} = 1; $app->add_breadcrumb( $app->translate('TrackBacks') ); $app->load_tmpl( "list_ping.tmpl", \%param ); } # takes param and one of load_args, iter, or items sub build_ping_table { my $app = shift; my (%args) = @_; require MT::Entry; require MT::Trackback; require MT::Category; my $author = $app->user; my $list_pref = $app->list_pref('ping'); my $iter; if ( $args{load_args} ) { my $class = $app->model('ping'); $iter = $class->load_iter( @{ $args{load_args} } ); } elsif ( $args{iter} ) { $iter = $args{iter}; } elsif ( $args{items} ) { $iter = sub { pop @{ $args{items} } }; } return [] unless $iter; my $limit = $args{limit}; my $param = $args{param}; my @data; my ( %blogs, %entries, %cats, %perms ); my $excerpt_max_len = const('DISPLAY_LENGTH_EDIT_PING_TITLE_FROM_EXCERPT'); my $title_max_len = const('DISPLAY_LENGTH_EDIT_PING_BREAK_UP'); while ( my $obj = $iter->() ) { my $row = $obj->column_values; my $blog = $blogs{ $obj->blog_id } ||= $obj->blog if $obj->blog_id; $row->{excerpt} = '[' . $app->translate("No Excerpt") . ']' unless ( $row->{excerpt} || '' ) ne ''; if ( ( $row->{title} || '' ) eq ( $row->{source_url} || '' ) ) { $row->{title} = '[' . $app->translate("No Title") . ']'; } if ( !defined( $row->{title} ) ) { $row->{title} = substr_text( $row->{excerpt} || "", 0, $excerpt_max_len ) . '...'; } $row->{excerpt} ||= ''; $row->{title} = break_up_text( $row->{title}, $title_max_len ); $row->{excerpt} = break_up_text( $row->{excerpt}, $title_max_len ); $row->{blog_name} = break_up_text( $row->{blog_name}, $title_max_len ); $row->{object} = $obj; push @data, $row; my $entry; my $cat; if ( my $tb_center = MT::Trackback->load( $obj->tb_id ) ) { if ( $tb_center->entry_id ) { $entry = $entries{ $tb_center->entry_id } ||= $app->model('entry')->load( $tb_center->entry_id ); my $class = $entry->class || 'entry'; if ($entry) { $row->{target_title} = $entry->title; $row->{target_link} = $app->uri( 'mode' => 'view', args => { '_type' => $class, id => $entry->id, blog_id => $entry->blog_id, tab => 'pings' } ); } else { $row->{target_title} = ( '* ' . $app->translate('Orphaned TrackBack') . ' *' ); } $row->{target_type} = $app->translate($class); } elsif ( $tb_center->category_id ) { $cat = $cats{ $tb_center->category_id } ||= MT::Category->load( $tb_center->category_id ); if ($cat) { $row->{target_title} = ( '* ' . $app->translate('Orphaned TrackBack') . ' *' ); $row->{target_title} = $cat->label; $row->{target_link} = $app->uri( 'mode' => 'view', args => { '_type' => 'category', id => $cat->id, blog_id => $cat->blog_id, tab => 'pings' } ); } $row->{target_type} = $app->translate('category'); } } if ( my $ts = $obj->created_on ) { $row->{created_on_time_formatted} = format_ts( LISTING_DATETIME_FORMAT, $ts, $blog, $app->user ? $app->user->preferred_language : undef ); $row->{created_on_formatted} = format_ts( LISTING_DATE_FORMAT, $ts, $blog, $app->user ? $app->user->preferred_language : undef ); $row->{created_on_relative} = relative_date( $ts, time, $blog ); } if ($blog) { $row->{weblog_id} = $blog->id; $row->{weblog_name} = $blog->name; } else { $row->{weblog_name} = '* ' . $app->translate('Orphaned TrackBack') . ' *'; } if ( $author->is_superuser() ) { $row->{has_edit_access} = 1; $row->{has_bulk_access} = 1; } else { my $perms = $perms{ $obj->blog_id } ||= $author->permissions( $obj->blog_id ); $row->{has_bulk_access} = ( ( $perms && ( ( $entry && ( $perms->can_edit_all_posts || $perms->can_manage_feedback ) ) || ( $cat && ( $perms->can_edit_categories || $perms->can_manage_feedback ) ) ) ) || ( $cat && $author->id == $cat->author_id ) || ( $entry && ( ( $author->id == $entry->author_id ) && $perms->can_publish_post ) ) ); $row->{has_edit_access} = ( ( $perms && ( ( $entry && ( $perms->can_edit_all_posts || $perms->can_manage_feedback ) ) || ( $cat && ( $perms->can_edit_categories || $perms->can_manage_feedback ) ) ) ) || ( $cat && $author->id == $cat->author_id ) || ( $entry && ( ( $author->id == $entry->author_id ) && $perms->can_create_post ) ) ); } } return [] unless @data; $param->{ping_table}[0] = {%$list_pref}; $param->{object_loop} = $param->{ping_table}[0]{object_loop} = \@data; $param->{ping_table}[0]{object_type} = 'ping'; $app->load_list_actions( 'ping', $param ); \@data; } sub list_entries { my $app = shift; my ($param) = @_; $param ||= {}; require MT::Entry; my $type = $param->{type} || MT::Entry->class_type; my $pkg = $app->model($type) or return "Invalid request."; my $q = $app->param; my $perms = $app->permissions; if ( $type eq 'page' ) { if ( $perms && ( !$perms->can_manage_pages ) ) { return $app->errtrans("Permission denied."); } } else { if ( $perms && ( !$perms->can_edit_all_posts && !$perms->can_create_post && !$perms->can_publish_post ) ) { return $app->errtrans("Permission denied."); } } my $list_pref = $app->list_pref($type); my %param = %$list_pref; my $blog_id = $q->param('blog_id'); my %terms; $terms{blog_id} = $blog_id if $blog_id; $terms{class} = $type; my $limit = $list_pref->{rows}; my $offset = $app->param('offset') || 0; if ( !$blog_id && !$app->user->is_superuser ) { require MT::Permission; $terms{blog_id} = [ map { $_->blog_id } grep { $_->can_create_post || $_->can_edit_all_posts } MT::Permission->load( { author_id => $app->user->id } ) ]; } my %arg; my $filter_key = $q->param('filter_key') || ''; my $filter_col = $q->param('filter') || ''; my $filter_val = $q->param('filter_val'); # check blog_id for deciding to apply category filter or not my ( $filter_name, $filter_value ); # human-readable versions if ( !exists( $terms{$filter_col} ) ) { if ( $filter_col eq 'category_id' ) { $filter_name = $app->translate('Category'); require MT::Category; my $cat = MT::Category->load($filter_val); return $app->errtrans( "Load failed: [_1]", MT::Category->errstr ) unless $cat; if ( $cat->blog_id != $blog_id ) { $filter_key = ''; $filter_col = ''; $filter_val = ''; } $filter_value = $cat->label; } elsif ( $filter_col eq 'asset_id' ) { $filter_name = $app->translate('Asset'); my $asset_class = MT->model('asset'); my $asset = $asset_class->load($filter_val); return $app->errtrans( "Load failed: [_1]", $asset_class->errstr ) unless $asset; if ($asset->blog_id != $blog_id) { $filter_key = ''; $filter_col = ''; $filter_val = ''; } $filter_value = $param{asset_label} = $asset->label; } } if ( $filter_col && $filter_val ) { if ( 'power_edit' eq $filter_col ) { $filter_col = 'id'; unless ( 'ARRAY' eq ref($filter_val) ) { my @values = $app->param('filter_val'); $filter_val = \@values; } } if ( !exists( $terms{$filter_col} ) ) { if ( $filter_col eq 'category_id' ) { $arg{'join'} = MT::Placement->join_on( 'entry_id', { category_id => $filter_val }, { unique => 1 } ); } elsif ( $filter_col eq 'asset_id' ) { $arg{'join'} = MT->model('objectasset')->join_on( 'object_id', { object_ds => $type, asset_id => $filter_val }, { unique => 1 } ); } elsif (( $filter_col eq 'normalizedtag' ) || ( $filter_col eq 'exacttag' ) ) { my $normalize = ( $filter_col eq 'normalizedtag' ); require MT::Tag; require MT::ObjectTag; my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} ); my @filter_vals = MT::Tag->split( $tag_delim, $filter_val ); my @filter_tags = @filter_vals; if ($normalize) { push @filter_tags, MT::Tag->normalize($_) foreach @filter_vals; } my @tags = MT::Tag->load( { name => [@filter_tags] }, { binary => { name => 1 } } ); my @tag_ids; foreach (@tags) { push @tag_ids, $_->id; if ($normalize) { my @more = MT::Tag->load( { n8d_id => $_->n8d_id ? $_->n8d_id : $_->id } ); push @tag_ids, $_->id foreach @more; } } @tag_ids = (0) unless @tags; $arg{'join'} = MT::ObjectTag->join_on( 'object_id', { tag_id => \@tag_ids, object_datasource => $pkg->datasource }, { unique => 1 } ); } else { $terms{$filter_col} = $filter_val; } $param{filter_args} = "&filter=" . encode_url($filter_col) . "&filter_val=" . encode_url($filter_val); if ( ( $filter_col eq 'normalizedtag' ) || ( $filter_col eq 'exacttag' ) ) { $filter_name = $app->translate('Tag'); $filter_value = $filter_val; } elsif ( $filter_col eq 'author_id' ) { $filter_name = $app->translate('User'); my $author = MT::Author->load($filter_val); return $app->errtrans( "Load failed: [_1]", MT::Author->errstr ) unless $author; $filter_value = $author->name; } elsif ( $filter_col eq 'status' ) { $filter_name = $app->translate('Entry Status'); $filter_value = $app->translate( MT::Entry::status_text($filter_val) ); } if ( $filter_name && $filter_value ) { $param{filter} = $filter_col; $param{ 'filter_col_' . $filter_col } = 1; $param{filter_val} = $filter_val; } } $param{filter_unpub} = $filter_col eq 'status'; } elsif ($filter_key) { my $filters = $app->registry("list_filters", "entry") || {}; if ( my $filter = $filters->{$filter_key} ) { if ( my $code = $filter->{code} || $app->handler_to_coderef( $filter->{handler} ) ) { $param{filter_key} = $filter_key; $param{filter_label} = $filter->{label}; $code->( \%terms, \%arg ); } } } require MT::Category; require MT::Placement; my $total = $pkg->count( \%terms, \%arg ) || 0; $arg{'sort'} = $type eq 'page' ? 'modified_on' : 'authored_on'; $arg{direction} = 'descend'; $arg{limit} = $limit + 1; if ( $total && $offset > $total - 1 ) { $arg{offset} = $offset = $total - $limit; } elsif ( $offset && ( ( $offset < 0 ) || ( $total - $offset < $limit ) ) ) { $arg{offset} = $offset = $total - $offset; } else { $arg{offset} = $offset if $offset; } my $iter = $pkg->load_iter( \%terms, \%arg ); my $is_power_edit = $q->param('is_power_edit'); if ($is_power_edit) { $param{has_expanded_mode} = 0; delete $param{view_expanded}; } else { $param{has_expanded_mode} = 1; } my $data = $app->build_entry_table( iter => $iter, is_power_edit => $is_power_edit, param => \%param, type => $type ); delete $param{entry_table} unless @$data; ## 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_entry = @$data > $limit; pop @$data while @$data > $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_entry) { $param{next_offset} = 1; $param{next_offset_val} = $offset + $limit; } $iter = MT::Author->load_iter( { type => MT::Author::AUTHOR() }, { 'join' => $pkg->join_on( 'author_id', { $blog_id ? ( blog_id => $blog_id ) : () }, { 'unique' => 1, 'sort' => 'authored_on', direction => 'descend' } ), } ); my %seen; my @authors; while ( my $au = $iter->() ) { next if $seen{ $au->id }; $seen{ $au->id } = 1; my $row = { author_name => $au->name, author_id => $au->id }; push @authors, $row; if ( @authors == 50 ) { $iter->('finish'); last; } } $param{entry_author_loop} = \@authors; $iter = $app->model('asset')->load_iter( { class => '*', blog_id => $blog_id }, { 'join' => MT->model('objectasset')->join_on( 'asset_id', {}, { unique => 1 } ), 'sort' => 'created_on', direction => 'descend', } ); %seen = (); my @assets; while ( my $asset = $iter->() ) { next if $seen{ $asset->id }; $seen{ $asset->id } = 1; my $row = { asset_label => $asset->label, asset_id => $asset->id, }; push @assets, $row; if ( @assets == 50 ) { $iter->('finish'); last; } } if ($filter_col eq 'asset_id' && !$seen{$filter_val}) { push @assets, { asset_label => $param{asset_label}, asset_id => $filter_val, }; } $param{entry_asset_loop} = \@assets; $param{page_actions} = $app->page_actions( $app->mode ); $param{list_filters} = $app->list_filters('entry'); $param{can_power_edit} = $blog_id && !$is_power_edit; $param{can_republish} = $blog_id ? $perms->can_rebuild : 1; $param{is_power_edit} = $is_power_edit; $param{saved_deleted} = $q->param('saved_deleted'); $param{saved} = $q->param('saved'); $param{limit} = $limit; $param{offset} = $offset; $param{object_type} = $type; $param{object_label} = $pkg->class_label; $param{object_label_plural} = $param{search_label} = $pkg->class_label_plural; $param{list_start} = $offset + 1; $param{list_end} = $offset + scalar @$data; $param{list_total} = $total; $param{next_max} = $param{list_total} - $limit; $param{next_max} = 0 if ( $param{next_max} || 0 ) < $offset + 1; $param{nav_entries} = 1; $param{feed_label} = $app->translate( "[_1] Feed", $pkg->class_label ); $param{feed_url} = $app->make_feed_link( $type, $blog_id ? { blog_id => $blog_id } : undef ); $app->add_breadcrumb( $pkg->class_label_plural ); $param{listing_screen} = 1; unless ($blog_id) { $param{system_overview_nav} = 1; } $param{container_label} = $pkg->container_label; unless ( $param{screen_class} ) { $param{screen_class} = "list-$type"; $param{screen_class} .= " list-entry" if $param{object_type} eq "page"; # to piggyback on list-entry styles } $param{mode} = $app->mode; if ( my $blog = MT::Blog->load($blog_id) ) { $param{sitepath_unconfigured} = $blog->site_path ? 0 : 1; } $param->{return_args} ||= $app->make_return_args; my @return_args = grep { $_ !~ /offset=\d/ } split /&/, $param->{return_args}; $param{return_args} = join '&', @return_args; $param{return_args} .= "&offset=$offset" if $offset; $param{screen_id} = "list-entry"; $param{screen_id} = "list-page" if $param{object_type} eq "page"; if ( $param{is_power_edit} ) { $param{screen_id} = "batch-edit-entry"; $param{screen_id} = "batch-edit-page" if $param{object_type} eq "page"; $param{screen_class} .= " batch-edit"; } $app->load_tmpl( "list_entry.tmpl", \%param ); } # FIXME: hack to add pages in here and above. search for "list_pages" sub list_pages { my $app = shift; $app->param( 'type', 'page' ); $app->list_entries( { type => 'page' } ); } sub build_entry_table { my $app = shift; my (%args) = @_; my $app_author = $app->user; my $perms = $app->permissions; my $type = $args{type}; my $class = $app->model($type); my $list_pref = $app->list_pref($type); if ( $args{is_power_edit} ) { delete $list_pref->{view_expanded}; } my $iter; if ( $args{load_args} ) { $iter = $class->load_iter( @{ $args{load_args} } ); } elsif ( $args{iter} ) { $iter = $args{iter}; } elsif ( $args{items} ) { $iter = sub { shift @{ $args{items} } }; } return [] unless $iter; my $limit = $args{limit}; my $is_power_edit = $args{is_power_edit} || 0; my $param = $args{param} || {}; ## Load list of categories for display in filter pulldown (and selection ## pulldown on power edit page). my ( $c_data, %cats ); my $blog_id = $app->param('blog_id'); if ($blog_id) { $c_data = $app->_build_category_list( blog_id => $blog_id, type => $class->container_type, ); my $i = 0; for my $row (@$c_data) { $row->{category_index} = $i++; my $spacer = $row->{category_label_spacer} || ''; $spacer =~ s/\ /\\u00A0/g; $row->{category_label_js} = $spacer . encode_js( $row->{category_label} ); $cats{ $row->{category_id} } = $row; } $param->{category_loop} = $c_data; } my ( $date_format, $datetime_format ); ## Load list of users for display in filter pulldown (and selection ## pulldown on power edit page). my ( @a_data, %authors ); if ($is_power_edit) { # FIXME: Scaling issue for lots of authors on one blog my $auth_iter = MT::Author->load_iter( { type => MT::Author::AUTHOR() }, { 'join' => MT::Permission->join_on( 'author_id', { blog_id => $blog_id } ) } ); while ( my $author = $auth_iter->() ) { $authors{ $author->id } = $author->name; push @a_data, { author_id => $author->id, author_name => encode_js( $author->name ) }; } @a_data = sort { $a->{author_name} cmp $b->{author_name} } @a_data; my $i = 0; for my $row (@a_data) { $row->{author_index} = $i++; } $param->{author_loop} = \@a_data; $date_format = "%Y.%m.%d"; $datetime_format = "%Y-%m-%d %H:%M:%S"; } else { $date_format = LISTING_DATE_FORMAT; $datetime_format = LISTING_DATETIME_FORMAT; } my ( @cat_list, @auth_list ); if ($is_power_edit) { @cat_list = sort { $cats{$a}->{category_index} <=> $cats{$b}->{category_index} } keys %cats; @auth_list = sort { $authors{$a} cmp $authors{$b} } keys %authors; } my @data; my %blogs; require MT::Blog; my $title_max_len = const('DISPLAY_LENGTH_EDIT_ENTRY_TITLE'); my $excerpt_max_len = const('DISPLAY_LENGTH_EDIT_ENTRY_TEXT_FROM_EXCERPT'); my $text_max_len = const('DISPLAY_LENGTH_EDIT_ENTRY_TEXT_BREAK_UP'); my %blog_perms; $blog_perms{ $perms->blog_id } = $perms if $perms; while ( my $obj = $iter->() ) { my $blog_perms; if ( !$app_author->is_superuser() ) { $blog_perms = $blog_perms{ $obj->blog_id } || $app_author->blog_perm( $obj->blog_id ); } my $row = $obj->column_values; $row->{text} ||= ''; if ( my $ts = ( $type eq 'page' ) ? $obj->modified_on : $obj->authored_on ) { $row->{created_on_formatted} = format_ts( $date_format, $ts, $obj->blog, $app->user ? $app->user->preferred_language : undef ); $row->{created_on_time_formatted} = format_ts( $datetime_format, $ts, $obj->blog, $app->user ? $app->user->preferred_language : undef ); $row->{created_on_relative} = relative_date( $ts, time, $obj->blog ); } my $author = $obj->author; $row->{author_name} = $author ? $author->name : $app->translate('(user deleted)'); if ( my $cat = $obj->category ) { $row->{category_label} = $cat->label; $row->{category_basename} = $cat->basename; } else { $row->{category_label} = ''; $row->{category_basename} = ''; } $row->{file_extension} = $obj->blog ? $obj->blog->file_extension : ''; $row->{title_short} = $obj->title; if ( !defined( $row->{title_short} ) || $row->{title_short} eq '' ) { my $title = remove_html( $obj->text ); $row->{title_short} = substr_text( defined($title) ? $title : "", 0, $title_max_len ) . '...'; } else { $row->{title_short} = remove_html( $row->{title_short} ); $row->{title_short} = substr_text( $row->{title_short}, 0, $title_max_len + 3 ) . '...' if length_text( $row->{title_short} ) > $title_max_len; } if ( $row->{excerpt} ) { $row->{excerpt} = remove_html( $row->{excerpt} ); } if ( !$row->{excerpt} ) { my $text = remove_html( $row->{text} ) || ''; $row->{excerpt} = first_n_text( $text, $excerpt_max_len ); if ( length($text) > length( $row->{excerpt} ) ) { $row->{excerpt} .= ' ...'; } } $row->{text} = break_up_text( $row->{text}, $text_max_len ) if $row->{text}; $row->{title_long} = remove_html( $obj->title ); $row->{status_text} = $app->translate( MT::Entry::status_text( $obj->status ) ); $row->{ "status_" . MT::Entry::status_text( $obj->status ) } = 1; $row->{has_edit_access} = $app_author->is_superuser || ( ( 'entry' eq $type ) && $blog_perms && $blog_perms->can_edit_entry( $obj, $app_author ) ) || ( ( 'page' eq $type ) && $blog_perms && $blog_perms->can_manage_pages ); if ($is_power_edit) { $row->{has_publish_access} = $app_author->is_superuser || ( ( 'entry' eq $type ) && $blog_perms && $blog_perms->can_edit_entry( $obj, $app_author, 1 ) ) || ( ( 'page' eq $type ) && $blog_perms && $blog_perms->can_manage_pages ); $row->{is_editable} = $row->{has_edit_access}; ## This is annoying. In order to generate and pre-select the ## category, user, and status pull down menus, we need to ## have a separate *copy* of the list of categories and ## users for every entry listed, so that each row in the list ## can "know" whether it is selected for this entry or not. my @this_c_data; my $this_category_id = $obj->category ? $obj->category->id : undef; for my $c_id (@cat_list) { push @this_c_data, { %{ $cats{$c_id} } }; $this_c_data[-1]{category_is_selected} = $this_category_id && $this_category_id == $c_id ? 1 : 0; } $row->{row_category_loop} = \@this_c_data; my @this_a_data; my $this_author_id = $obj->author_id; for my $a_id (@auth_list) { push @this_a_data, { author_name => $authors{$a_id}, author_id => $a_id }; $this_a_data[-1]{author_is_selected} = $this_author_id && $this_author_id == $a_id ? 1 : 0; } unless ( $obj->author ) { push @this_a_data, { author_name => $app->translate( '(user deleted - ID:[_1])', $obj->author_id ), author_id => $obj->author_id, author_is_selected => 1, }; } $row->{row_author_loop} = \@this_a_data; } if ( my $blog = $blogs{ $obj->blog_id } ||= MT::Blog->load( $obj->blog_id ) ) { $row->{weblog_id} = $blog->id; $row->{weblog_name} = $blog->name; } if ( $obj->status == MT::Entry::RELEASE() ) { $row->{entry_permalink} = $obj->permalink; } $row->{object} = $obj; push @data, $row; } return [] unless @data; $param->{entry_table}[0] = {%$list_pref}; $param->{object_loop} = $param->{entry_table}[0]{object_loop} = \@data; $app->load_list_actions( $type, \%$param ) unless $is_power_edit; \@data; } *build_page_table = \&build_entry_table; sub save_entries { my $app = shift; my $perms = $app->permissions; my $type = $app->param('_type'); return $app->errtrans("Permission denied.") unless $perms && ( $type eq 'page' ? ( $perms->can_manage_pages ) : ( $perms->can_publish_post || $perms->can_create_post || $perms->can_edit_all_posts ) ); $app->validate_magic() or return; my $q = $app->param; my @p = $q->param; require MT::Entry; require MT::Placement; require MT::Log; my $blog_id = $q->param('blog_id'); my $this_author = $app->user; my $this_author_id = $this_author->id; for my $p (@p) { next unless $p =~ /^category_id_(\d+)/; my $id = $1; my $entry = MT::Entry->load($id); return $app->error( $app->translate("Permission denied.") ) unless ( $perms->can_publish_post || $perms->can_create_post || $perms->can_edit_all_posts ); my $orig_obj = $entry->clone; if ( $perms->can_edit_entry( $entry, $this_author ) ) { my $author_id = $q->param( 'author_id_' . $id ); $entry->author_id( $author_id ? $author_id : 0 ); $entry->title( scalar $q->param( 'title_' . $id ) ); } if ( $perms->can_edit_entry( $entry, $this_author, 1 ) ) { ## can he/she change status? $entry->status( scalar $q->param( 'status_' . $id ) ); my $co = $q->param( 'created_on_' . $id ); unless ( $co =~ m!(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2})(?::(\d{2}))?! ) { return $app->error( $app->translate( "Invalid date '[_1]'; authored on dates must be in the format YYYY-MM-DD HH:MM:SS.", $co ) ); } my $s = $6 || 0; # Emit an error message if the date is bogus. return $app->error( $app->translate( "Invalid date '[_1]'; authored on dates should be real dates.", $co ) ) if $s > 59 || $s < 0 || $5 > 59 || $5 < 0 || $4 > 23 || $4 < 0 || $2 > 12 || $2 < 1 || $3 < 1 || ( MT::Util::days_in( $2, $1 ) < $3 && !MT::Util::leap_day( $0, $1, $2 ) ); # FIXME: Should be assigning the publish_date field here my $ts = sprintf "%04d%02d%02d%02d%02d%02d", $1, $2, $3, $4, $5, $s; if ($type eq 'page' ) { $entry->modified_on($ts); } else { $entry->authored_on($ts); } } $app->run_callbacks( 'cms_pre_save.' . $type, $app, $entry, $orig_obj ) || return $app->error( $app->translate( "Saving [_1] failed: [_2]", $entry->class_label, $app->errstr ) ); $entry->save or return $app->error( $app->translate( "Saving entry '[_1]' failed: [_2]", $entry->title, $entry->errstr ) ); my $cat_id = $q->param("category_id_$id"); my $place = MT::Placement->load( { entry_id => $id, is_primary => 1 } ); if ( $place && !$cat_id ) { $place->remove or return $app->error( $app->translate( "Removing placement failed: [_1]", $place->errstr ) ); } elsif ($cat_id) { unless ($place) { $place = MT::Placement->new; $place->entry_id($id); $place->blog_id($blog_id); $place->is_primary(1); } $place->category_id( scalar $q->param($p) ); $place->save or return $app->error( $app->translate( "Saving placement failed: [_1]", $place->errstr ) ); } my $message; if ( $orig_obj->status ne $entry->status ) { $message = $app->translate( "[_1] '[_2]' (ID:[_3]) edited and its status changed from [_4] to [_5] by user '[_6]'", $entry->class_label, $entry->title, $entry->id, $app->translate( MT::Entry::status_text( $orig_obj->status ) ), $app->translate( MT::Entry::status_text( $entry->status ) ), $this_author->name ); } else { $message = $app->translate( "[_1] '[_2]' (ID:[_3]) edited by user '[_4]'", $entry->class_label, $entry->title, $entry->id, $this_author->name ); } $app->log( { message => $message, level => MT::Log::INFO(), class => $entry->class, category => 'edit', metadata => $entry->id } ); $app->run_callbacks( 'cms_post_save.' . $type, $app, $entry, $orig_obj ); } $app->add_return_arg( 'saved' => 1, is_power_edit => 1 ); $app->call_return; } *save_pages = \&save_entries; sub remove_preview_file { my $app = shift; # Clear any preview file that may exist (returning from # a preview using the 'reedit', 'cancel' or 'save' buttons) if ( my $preview = $app->param('_preview_file') ) { require MT::Session; if ( my $tf = MT::Session->load( { id => $preview, kind => 'TF', } ) ) { my $file = $tf->name; my $fmgr = $app->blog->file_mgr; $fmgr->delete($file); $tf->remove; } } } sub save_entry { my $app = shift; $app->validate_magic or return; $app->remove_preview_file; if ( $app->param('is_power_edit') ) { return $app->save_entries(@_); } my $author = $app->user; my $type = $app->param('_type') || 'entry'; my $class = $app->model($type) or retrun $app->errtrans("Invalid parameter"); my $cat_class = $app->model( $class->container_type ); my $perms = $app->permissions or return $app->errtrans("Permission denied."); if ( $type eq 'page' ) { return $app->errtrans("Permission denied.") unless $perms->can_manage_pages; } my $id = $app->param('id'); if ( !$id ) { return $app->errtrans("Permission denied.") unless ( ( 'entry' eq $type ) && $perms->can_create_post ) || ( ( 'page' eq $type ) && $perms->can_manage_pages ); } $app->validate_magic() or return; # check for autosave if ( $app->param('_autosave') ) { return $app->autosave_object(); } require MT::Blog; my $blog_id = $app->param('blog_id'); my $blog = MT::Blog->load($blog_id); my $archive_type; my ( $obj, $orig_obj, $orig_file ); if ($id) { $obj = $class->load($id) || return $app->error( $app->translate( "No such [_1].", $class->class_label ) ); return $app->error( $app->translate("Invalid parameter") ) unless $obj->blog_id == $blog_id; if ( $type eq 'entry' ) { return $app->error( $app->translate("Permission denied.") ) unless $perms->can_edit_entry( $obj, $author ); return $app->error( $app->translate("Permission denied.") ) if ( $obj->status ne $app->param('status') ) && !( $perms->can_edit_entry( $obj, $author, 1 ) ); $archive_type = 'Individual'; } elsif ( $type eq 'page' ) { $archive_type = 'Page'; } $orig_obj = $obj->clone; $orig_file = archive_file_for( $orig_obj, $blog, $archive_type ); } else { $obj = $class->new; } my $status_old = $id ? $obj->status : 0; my $names = $obj->column_names; ## Get rid of category_id param, because we don't want to just set it ## in the Entry record; save it for later when we will set the Placement. my ( $cat_id, @add_cat ) = split /\s*,\s*/, ( $app->param('category_ids') || '' ); $app->delete_param('category_id'); if ($id) { ## Delete the author_id param (if present), because we don't want to ## change the existing author. $app->delete_param('author_id'); } my %values = map { $_ => scalar $app->param($_) } @$names; delete $values{'id'} unless $app->param('id'); ## Strip linefeed characters. for my $col (qw( text excerpt text_more keywords )) { $values{$col} =~ tr/\r//d if $values{$col}; } $values{allow_comments} = 0 if !defined( $values{allow_comments} ) || $app->param('allow_comments') eq ''; delete $values{week_number} if ( $app->param('week_number') || '' ) eq ''; delete $values{basename} unless $perms->can_publish_post || $perms->can_edit_all_posts; $obj->set_values( \%values ); $obj->allow_pings(0) if !defined $app->param('allow_pings') || $app->param('allow_pings') eq ''; my $ao_d = $app->param('authored_on_date'); my $ao_t = $app->param('authored_on_time'); if ( !$id ) { # basename check for this new entry... if ( ( my $basename = $app->param('basename') ) && !$app->param('basename_manual') && $type eq 'entry' ) { my $cnt = $class->count( { blog_id => $blog_id, basename => $basename } ); if ($cnt) { $obj->basename( MT::Util::make_unique_basename($obj) ); } } } if ( $type eq 'page' ) { # -1 is a special id for identifying the 'root' folder $cat_id = 0 if $cat_id == -1; my $dup_it = $class->load_iter( { blog_id => $blog_id, basename => $obj->basename, class => 'page', ( $id ? ( id => $id ) : () ) }, { ( $id ? ( not => { id => 1 } ) : () ) } ); while ( my $p = $dup_it->() ) { my $p_folder = $p->folder; my $dup_folder_path = defined $p_folder ? $p_folder->publish_path() : ''; my $folder = MT::Folder->load($cat_id) if $cat_id; my $folder_path = defined $folder ? $folder->publish_path() : ''; return $app->error( $app->translate( "Same Basename has already been used. You should use an unique basename." ) ) if ( $dup_folder_path eq $folder_path ); } } if ( $type eq 'entry' ) { $obj->status( MT::Entry::HOLD() ) if !$id && !$perms->can_publish_post && !$perms->can_edit_all_posts; } my $filter_result = $app->run_callbacks( 'cms_save_filter.' . $type, $app ); if ( !$filter_result ) { my %param = (); $param{error} = $app->errstr; $param{return_args} = $app->param('return_args'); return $app->edit_object( \%param ); } # check to make sure blog has site url and path defined. # otherwise, we can't publish a released entry if ( ( $obj->status || 0 ) != MT::Entry::HOLD() ) { if ( !$blog->site_path || !$blog->site_url ) { return $app->error( $app->translate( "Your blog has not been configured with a site path and URL. You cannot publish entries until these are defined." ) ); } } my ( $previous_old, $next_old ); if ( ( $perms->can_publish_post || $perms->can_edit_all_posts ) && ($ao_d) ) { my $ao = $ao_d . ' ' . $ao_t; unless ( $ao =~ m!^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2})(?::(\d{2}))?$! ) { return $app->error( $app->translate( "Invalid date '[_1]'; authored on dates must be in the format YYYY-MM-DD HH:MM:SS.", $ao ) ); } my $s = $6 || 0; return $app->error( $app->translate( "Invalid date '[_1]'; authored on dates should be real dates.", $ao ) ) if ( $s > 59 || $s < 0 || $5 > 59 || $5 < 0 || $4 > 23 || $4 < 0 || $2 > 12 || $2 < 1 || $3 < 1 || ( MT::Util::days_in( $2, $1 ) < $3 && !MT::Util::leap_day( $0, $1, $2 ) ) ); if ($obj->authored_on) { $previous_old = $obj->previous(1); $next_old = $obj->next(1); } my $ts = sprintf "%04d%02d%02d%02d%02d%02d", $1, $2, $3, $4, $5, $s; $obj->authored_on($ts); } my $is_new = $obj->id ? 0 : 1; $app->_translate_naughty_words($obj); $obj->modified_by( $author->id ) unless $is_new; $app->run_callbacks( 'cms_pre_save.' . $type, $app, $obj, $orig_obj ) || return $app->error( $app->translate( "Saving [_1] failed: [_2]", $class->class_label, $app->errstr ) ); $obj->save or return $app->error( $app->translate( "Saving [_1] failed: [_2]", $class->class_label, $obj->errstr ) ); my $message; if ($is_new) { $message = $app->translate( "[_1] '[_2]' (ID:[_3]) added by user '[_4]'", $class->class_label, $obj->title, $obj->id, $author->name ); } elsif ( $orig_obj->status ne $obj->status ) { $message = $app->translate( "[_1] '[_2]' (ID:[_3]) edited and its status changed from [_4] to [_5] by user '[_6]'", $class->class_label, $obj->title, $obj->id, $app->translate( MT::Entry::status_text( $orig_obj->status ) ), $app->translate( MT::Entry::status_text( $obj->status ) ), $author->name ); } else { $message = $app->translate( "[_1] '[_2]' (ID:[_3]) edited by user '[_4]'", $class->class_label, $obj->title, $obj->id, $author->name ); } require MT::Log; $app->log( { message => $message, level => MT::Log::INFO(), class => $type, $is_new ? ( category => 'new' ) : ( category => 'edit' ), metadata => $obj->id } ); my $error_string = MT::callback_errstr(); ## Now that the object is saved, we can be certain that it has an ## ID. So we can now add/update/remove the primary placement. require MT::Placement; my $place = MT::Placement->load( { entry_id => $obj->id, is_primary => 1 } ); if ($cat_id) { unless ($place) { $place = MT::Placement->new; $place->entry_id( $obj->id ); $place->blog_id( $obj->blog_id ); $place->is_primary(1); } $place->category_id($cat_id); $place->save; } else { if ( $place ) { $place->remove; } } my $placements_updated; # save secondary placements... my @place = MT::Placement->load( { entry_id => $obj->id, is_primary => 0 } ); for my $place (@place) { $place->remove; $placements_updated = 1; } for my $cat_id (@add_cat) { my $cat = $cat_class->load($cat_id); # blog_id sanity check next if $cat->blog_id != $obj->blog_id; my $place = MT::Placement->new; $place->entry_id( $obj->id ); $place->blog_id( $obj->blog_id ); $place->is_primary(0); $place->category_id($cat_id); $place->save or return $app->error( $app->translate( "Saving placement failed: [_1]", $place->errstr ) ); $placements_updated = 1; } $app->run_callbacks( 'cms_post_save.' . $type, $app, $obj, $orig_obj ); ## If the saved status is RELEASE, or if the *previous* status was ## RELEASE, then rebuild entry archives, indexes, and send the ## XML-RPC ping(s). Otherwise the status was and is HOLD, and we ## don't have to do anything. if ( ( $obj->status || 0 ) == MT::Entry::RELEASE() || $status_old eq MT::Entry::RELEASE() ) { if ( $app->config('DeleteFilesAtRebuild') && $orig_obj ) { my $file = archive_file_for( $obj, $blog, $archive_type ); if ( $file ne $orig_file || $obj->status != MT::Entry::RELEASE() ) { $app->publisher->remove_entry_archive_file( Entry => $orig_obj, ArchiveType => $archive_type ); } } # If there are no static pages, just rebuild indexes. if ( $blog->count_static_templates($archive_type) == 0 || MT::Util->launch_background_tasks() ) { my $res = MT::Util::start_background_task( sub { $app->rebuild_entry( Entry => $obj, BuildDependencies => 1, OldEntry => $orig_obj, OldPrevious => ($previous_old) ? $previous_old->id : undef, OldNext => ($next_old) ? $next_old->id : undef ) or return $app->publish_error(); $app->run_callbacks( 'rebuild', $blog ); 1; } ); return unless $res; return $app->ping_continuation( $obj, $blog, OldStatus => $status_old, IsNew => $is_new, ); } else { return $app->redirect( $app->uri( 'mode' => 'start_rebuild', args => { blog_id => $obj->blog_id, 'next' => 0, type => 'entry-' . $obj->id, entry_id => $obj->id, is_new => $is_new, old_status => $status_old, ( $previous_old ? ( old_previous => $previous_old->id ) : () ), ( $next_old ? ( old_next => $next_old->id ) : () ) } ) ); } } $app->_finish_rebuild_ping( $obj, !$id ); } sub publish_error { my $app = shift; my ($msg) = @_; if (defined $app->errstr) { require MT::Log; $app->log({ message => $app->translate("Error during publishing: [_1]", (defined $msg ? $msg : $app->errstr)), class => "system", level => MT::Log::ERROR(), category => "publish", }) } return undef; } sub ping_continuation { my $app = shift; my ( $entry, $blog, %options ) = @_; my $list = $app->needs_ping( Entry => $entry, Blog => $blog, OldStatus => $options{OldStatus} ); require MT::Entry; if ( $entry->status == MT::Entry::RELEASE() && $list ) { my @urls = map { { url => $_ } } @$list; $app->load_tmpl( 'pinging.tmpl', { blog_id => $blog->id, entry_id => $entry->id, old_status => $options{OldStatus}, is_new => $options{IsNew}, url_list => \@urls, } ); } else { $app->_finish_rebuild_ping( $entry, $options{IsNew} ); } } sub _finish_rebuild_ping { my $app = shift; my ( $entry, $is_new, $ping_errors ) = @_; $app->redirect( $app->uri( 'mode' => 'view', args => { '_type' => $entry->class, blog_id => $entry->blog_id, id => $entry->id, ( $is_new ? ( saved_added => 1 ) : ( saved_changes => 1 ) ), ( $ping_errors ? ( ping_errors => 1 ) : () ) } ) ); } sub _build_category_list { my $app = shift; my (%param) = @_; my $blog_id = $param{blog_id}; my $new_cat_id = $param{new_cat_id}; my $include_markers = $param{markers}; my $counts = $param{counts}; my $type = $param{type}; my @data; my %authors; my $class = $app->model($type) or return; my %expanded; if ($new_cat_id) { my $new_cat = $class->load($new_cat_id); my @parents = $new_cat->parent_categories; %expanded = map { $_->id => 1 } @parents; } my @cats = $class->_flattened_category_hierarchy($blog_id); my $cols = $class->column_names; my $depth = 0; my $i = 1; my $top_cat = 1; my ( $placement_counts, $tb_counts, %tb ); if ($counts) { $app->model('placement'); $app->model('trackback'); $app->model('ping'); my $max_cat_id = 0; foreach (@cats) { $max_cat_id = $_->id if ( ref $_ ) && ( $_->id > $max_cat_id ); } $placement_counts = {}; my $cat_entry_count_iter = MT::Placement->count_group_by( { blog_id => $blog_id }, { group => ['category_id'] } ); while ( my ( $count, $category_id ) = $cat_entry_count_iter->() ) { $placement_counts->{$category_id} = $count; } $tb_counts = {}; my $tb_count_iter = MT::TBPing->count_group_by( { blog_id => $blog_id, junk_status => [ 0, 1 ] }, { group => ['tb_id'] } ); while ( my ( $count, $tb_id ) = $tb_count_iter->() ) { $tb_counts->{$tb_id} = $count; } my $tb_iter = MT::Trackback->load_iter( { blog_id => $blog_id, category_id => [ 1, $max_cat_id ] }, { range_incl => { 'category_id' => 1 } } ); while ( my $tb = $tb_iter->() ) { $tb{ $tb->category_id } = $tb; } } while ( my $obj = shift @cats ) { my $row = {}; if ( !ref($obj) ) { if ( $obj eq 'BEGIN_SUBCATS' ) { $depth++; $top_cat = 1; } elsif ( $obj eq 'END_SUBCATS' ) { $depth--; } push @data, { $obj => 1 } if $include_markers; next; } for my $col (@$cols) { $row->{ 'category_' . $col } = $obj->$col(); } $row->{category_label} = remove_html( $row->{category_label} ); if ( $obj->class eq 'folder' ) { my $path = $obj->publish_path; $row->{category_selector_label} = $path; my $path_ids = []; foreach my $parent ( $obj->parent_categories ) { push @$path_ids, $parent->id; } $path .= $row->{category_label}; @$path_ids = reverse @$path_ids; $row->{category_path_ids} = $path_ids; # $row->{category_label} = $path . '/'; $row->{category_label_full} = $row->{category_basename} . '/' . ( $obj->label ne $row->{category_basename} ? ' <em>' . $obj->label . '</em>' : '' ); } else { my $path = ''; my $path_ids = []; foreach my $parent ( $obj->parent_categories ) { push @$path_ids, $parent->id; $path .= $parent->label . ' ‣ '; } $path .= $row->{category_label}; @$path_ids = reverse @$path_ids; $row->{category_path_ids} = $path_ids; $row->{category_selector_label} = $path; $row->{category_label_full} = $row->{category_label}; } $row->{category_label_spacer} = ' ' x $depth; if ($counts) { $row->{category_entrycount} = $placement_counts ? ( $placement_counts->{ $obj->id } || 0 ) : MT::Placement->count( { category_id => $obj->id } ); if ( my $tb = $tb{ $obj->id } ) { $row->{has_tb} = 1; $row->{tb_id} = $tb->id; $row->{category_tbcount} = $tb_counts ? ( $tb_counts->{ $tb->id } || 0 ) : MT::TBPing->count( { tb_id => $tb->id, junk_status => [ 0, 1 ] } ); } } $row->{category_is_expanded} = 1 if $expanded{ $obj->id }; $row->{category_pixel_depth} = 10 * $depth; $row->{top_cat} = $top_cat; $top_cat = 0; $row->{is_object} = 1; push @data, $row; } \@data; } sub list_category { my $app = shift; my $q = $app->param; my $type = $q->param('_type') || 'category'; my $class = $app->model($type); my $perms = $app->permissions; my $entry_class; my $entry_type; if ( $type eq 'category' ) { $entry_type = 'entry'; return $app->return_to_dashboard( redirect => 1 ) unless $perms && $perms->can_edit_categories; } elsif ( $type eq 'folder' ) { $entry_type = 'page'; return $app->return_to_dashboard( redirect => 1 ) unless $perms && $perms->can_manage_pages; } $entry_class = $app->model($entry_type); my $blog_id = scalar $q->param('blog_id'); require MT::Blog; my $blog = MT::Blog->load($blog_id) or return $app->errtrans("Invalid request."); my %param; my %authors; my $data = $app->_build_category_list( blog_id => $blog_id, counts => 1, new_cat_id => scalar $q->param('new_cat_id'), type => $type ); if ( $blog->site_url =~ /\/$/ ) { $param{blog_site_url} = $blog->site_url; } else { $param{blog_site_url} = $blog->site_url . '/'; } $param{object_loop} = $param{category_loop} = $data; $param{saved} = $q->param('saved'); $param{saved_deleted} = $q->param('saved_deleted'); $app->load_list_actions( $type, \%param ); #$param{nav_categories} = 1; $param{sub_object_label} = $type eq 'folder' ? $app->translate('Subfolder') : $app->translate('Subcategory'); $param{object_label} = $class->class_label; $param{object_label_plural} = $class->class_label_plural; $param{object_type} = $type; $param{entry_label_plural} = $entry_class->class_label_plural; $param{entry_label} = $entry_class->class_label; $param{search_label} = $param{entry_label_plural}; $param{search_type} = $entry_type; $param{screen_id} = $type eq 'folder' ? 'list-folder' : 'list-category'; $param{listing_screen} = 1; $app->add_breadcrumb( $param{object_label_plural} ); $param{screen_class} = "list-${type}"; $param{screen_class} .= " list-category" if $type eq 'folder'; # to piggyback on list-category styles my $tmpl_file = 'list_' . $type . '.tmpl'; $app->load_tmpl( $tmpl_file, \%param ); } sub move_category { my $app = shift; my $type = $app->param('_type'); my $class = $app->model($type) or return $app->errtrans("Invalid request."); $app->validate_magic() or return; my $cat = $class->load( $app->param('move_cat_id') ); my $new_parent = $app->param('move-radio'); return 1 if ( $new_parent == $cat->parent ); $cat->parent($new_parent); my @siblings = $class->load( { parent => $cat->parent, blog_id => $cat->blog_id } ); foreach (@siblings) { # FIXME: Language should support both category / folder return $app->errtrans( "The category name '[_1]' conflicts with another category. Top-level categories and sub-categories with the same parent must have unique names.", $_->label ) if $_->label eq $cat->label; } $cat->save or return $app->error( $app->translate( "Saving category failed: [_1]", $cat->errstr ) ); } sub save_category { my $app = shift; my $q = $app->param; my $perms = $app->permissions; my $type = $q->param('_type'); my $class = $app->model($type) or return $app->errtrans("Invalid request."); if ( $type eq 'category' ) { return $app->errtrans("Permission denied.") unless $perms && $perms->can_edit_categories; } elsif ( $type eq 'folder' ) { return $app->errtrans("Permission denied.") unless $perms && $perms->can_manage_pages; } $app->validate_magic() or return; my $blog_id = $q->param('blog_id'); my $cat; if ( my $moved_cat_id = $q->param('move_cat_id') ) { $cat = $class->load( $q->param('move_cat_id') ); $app->move_category() or return; } else { for my $p ( $q->param ) { my ($parent) = $p =~ /^category-new-parent-(\d+)$/; next unless ( defined $parent ); my $label = $q->param($p); $label =~ s/(^\s+|\s+$)//g; next unless ( $label ne '' ); $cat = $class->new; my $original = $cat->clone; $cat->blog_id($blog_id); $cat->label($label); $cat->author_id( $app->user->id ); $cat->parent($parent); $app->run_callbacks( 'cms_pre_save.' . $type, $app, $cat, $original ) || return $app->errtrans( "Saving [_1] failed: [_2]", $type, $app->errstr ); $cat->save or return $app->error( $app->translate( "Saving [_1] failed: [_2]", $type, $cat->errstr ) ); # Now post-process it. $app->run_callbacks( 'cms_post_save.' . $type, $app, $cat, $original ); } } return $app->errtrans( "The [_1] must be given a name!", $type ) if !$cat; $app->redirect( $app->uri( 'mode' => 'list_cat', args => { _type => $type, blog_id => $blog_id, saved => 1, new_cat_id => $cat->id } ) ); } sub save_asset { my $app = shift; my $q = $app->param; my $perms = $app->permissions; my $type = $q->param('_type'); my $class = $app->model($type) or return $app->errtrans("Invalid request."); $app->validate_magic() or return; return $app->errtrans("Permission denied.") unless $perms && $perms->can_edit_assets; my $blog_id = $q->param('blog_id'); my $id = $q->param('id'); my $obj = $id ? $class->load($id) : $class->new; my $original = $obj->clone(); $obj->set_values_from_query($q); $app->run_callbacks( 'cms_pre_save.' . $type, $app, $obj, $original ) || return $app->errtrans( "Saving [_1] failed: [_2]", $type, $app->errstr ); $obj->save or return $app->error( $app->translate( "Saving [_1] failed: [_2]", $type, $obj->errstr ) ); $app->run_callbacks( 'cms_post_save.' . $type, $app, $obj, $original ); $app->redirect( $app->uri( 'mode' => 'view', args => { _type => $type, blog_id => $blog_id, id => $obj->id, saved => 1, } ) ); } sub cfg_blog { my $q = $_[0]->{query}; $q->param( '_type', 'blog' ); $q->param( 'id', scalar $q->param('blog_id') ); $_[0]->edit_object( { output => 'cfg_prefs.tmpl' } ); } sub user_blog_prefs { my $app = shift; my $prefs = $app->request('user_blog_prefs'); return $prefs if $prefs && !$app->param('config_view'); my $perms = $app->permissions; return {} unless $perms; my @prefs = split /,/, $perms->blog_prefs || ''; my %prefs; foreach (@prefs) { my ( $name, $value ) = split /=/, $_, 2; $prefs{$name} = $value; } my $updated = 0; if ( my $view = $app->param('config_view') ) { $prefs{'config_view'} = $view; $updated = 1; } if ($updated) { my $pref = ''; foreach ( keys %prefs ) { $pref .= ',' if $pref ne ''; $pref .= $_ . '=' . $prefs{$_}; } $perms->blog_prefs($pref); if ( !$perms->blog_id ) { my $blog = $app->blog; $perms->blog_id( $blog->id ) if $blog; } $perms->save if $perms->blog_id; } $app->request( 'user_blog_prefs', \%prefs ); \%prefs; } sub cfg_prefs { my $app = shift; my $q = $app->param; my $blog_id = scalar $q->param('blog_id'); return $app->return_to_dashboard( redirect => 1 ) unless $blog_id; $q->param( '_type', 'blog' ); $q->param( 'id', $blog_id ); my $blog_prefs = $app->user_blog_prefs; my $perms = $app->permissions; return $app->error( $app->translate('Permission denied.') ) unless $app->user->is_superuser() || ( $perms && ( $perms->can_edit_config || $perms->can_administer_blog || $perms->can_set_publish_paths ) ); my $output = 'cfg_prefs.tmpl'; $app->edit_object( { output => $output, screen_class => 'settings-screen general-screen' } ); } sub cfg_plugins { my $app = shift; my $q = $app->param; my %param; $param{screen_class} = 'settings-screen'; if ( $q->param('blog_id') ) { $q->param( '_type', 'blog' ); $q->param( 'id', scalar $q->param('blog_id') ); $param{screen_id} = "list-plugins"; $param{screen_class} .= " plugin-settings"; $param{output} = 'cfg_plugin.tmpl'; $app->edit_object( \%param ); } else { my $cfg = $app->config; $param{can_config} = $app->user->can_manage_plugins; $param{use_plugins} = $cfg->UsePlugins; $app->build_plugin_table( param => \%param, scope => 'system' ); $param{nav_config} = 1; $param{nav_settings} = 1; $param{nav_plugins} = 1; $param{switched} = 1 if $app->param('switched'); $param{'reset'} = 1 if $app->param('reset'); $param{saved} = 1 if $app->param('saved'); $param{mod_perl} = 1 if $ENV{MOD_PERL}; $app->add_breadcrumb( $app->translate("Plugin Settings") ); $param{screen_id} = "list-plugins"; $param{screen_class} = "plugin-settings"; $app->load_tmpl( 'cfg_plugin.tmpl', \%param ); } } sub cfg_comments { my $app = shift; my $q = $app->param; my $blog_id = scalar $q->param('blog_id'); return $app->return_to_dashboard( redirect => 1 ) unless $blog_id; $q->param( '_type', 'blog' ); $q->param( 'id', scalar $q->param('blog_id') ); $app->edit_object( { output => 'cfg_comments.tmpl', screen_class => 'settings-screen', screen_id => 'comment-settings', } ); } sub cfg_trackbacks { my $app = shift; my $q = $app->param; my $blog_id = scalar $q->param('blog_id'); return $app->return_to_dashboard( redirect => 1 ) unless $blog_id; $q->param( '_type', 'blog' ); $q->param( 'id', scalar $q->param('blog_id') ); $app->edit_object( { output => 'cfg_trackbacks.tmpl', screen_class => 'settings-screen', screen_id => 'trackback-settings', } ); } sub cfg_registration { my $app = shift; my $q = $app->param; my $blog_id = scalar $q->param('blog_id'); return $app->return_to_dashboard( redirect => 1 ) unless $blog_id; $q->param( '_type', 'blog' ); $q->param( 'id', scalar $q->param('blog_id') ); eval { require Digest::SHA1; }; my $openid_available = $@ ? 0 : 1; $app->edit_object( { output => 'cfg_registration.tmpl', screen_class => 'settings-screen registration-screen', openid_enabled => $openid_available } ); } sub cfg_spam { my $app = shift; my $q = $app->param; $q->param( '_type', 'blog' ); $q->param( 'id', scalar $q->param('blog_id') ); my $plugin_config_html; my $plugin_name; my $plugin; if ( my $p = $q->param('plugin') ) { $plugin = $MT::Plugins{$p}; if ($plugin) { $plugin = $plugin->{object}; } else { $plugin = MT->component($p); } return $app->errtrans("Invalid request.") unless $plugin; my $scope; if ( $q->param('blog_id') ) { $scope = 'blog:' . $q->param('blog_id'); } else { $scope = 'system'; } $plugin_name = $plugin->name; my %plugin_param; $plugin->load_config( \%plugin_param, $scope ); my $snip_tmpl = $plugin->config_template( \%plugin_param, $scope ); my $tmpl; if ( ref $snip_tmpl ne 'MT::Template' ) { require MT::Template; $tmpl = MT::Template->new( type => 'scalarref', source => ref $snip_tmpl ? $snip_tmpl : \$snip_tmpl ); } else { $tmpl = $snip_tmpl; } # Process template independent of $app to avoid premature # localization (give plugin a chance to do L10N first). $tmpl->param( \%plugin_param ); $plugin_config_html = $tmpl->output(); $plugin_config_html = $plugin->translate_templatized($plugin_config_html) if $plugin_config_html =~ m/<(?:__trans|mt_trans) /i; } my $filters = MT::Component->registry('junk_filters') || []; my %plugins; foreach my $set (@$filters) { foreach my $f ( values %$set ) { $plugins{ $f->{plugin} } = $f->{plugin}; } } my @plugins = values %plugins; my $loop = []; foreach my $p (@plugins) { push @$loop, { name => $p->name, plugin => $p->id, active => ( $plugin && ( $p->id eq $plugin->id ) ? 1 : 0 ), }, ; } @$loop = sort { $a->{name} cmp $b->{name} } @$loop; $app->edit_object( { plugin_config_html => $plugin_config_html, plugin_name => $plugin_name, junk_filter_loop => $loop, output => 'cfg_spam.tmpl', screen_class => 'settings-screen spam-screen' } ); } sub cfg_entry { my $app = shift; my $q = $app->param; my $blog_id = scalar $q->param('blog_id'); return $app->return_to_dashboard( redirect => 1 ) unless $blog_id; $q->param( '_type', 'blog' ); $q->param( 'id', scalar $q->param('blog_id') ); $app->edit_object( { output => 'cfg_entry.tmpl', screen_class => 'settings-screen entry-screen' } ); } sub cfg_web_services { my $app = shift; my $q = $app->param; my $blog_id = scalar $q->param('blog_id'); return $app->return_to_dashboard( redirect => 1 ) unless $blog_id; $q->param( '_type', 'blog' ); $q->param( 'id', scalar $q->param('blog_id') ); $app->edit_object( { output => 'cfg_web_services.tmpl', screen_class => 'settings-screen web-services-settings' } ); } sub archive_type_sorter { my ( $a, $b ) = @_; # Logical ordering for exact archive types my %order = ( 'Individual' => 1, 'Page' => 2, 'Daily' => 3, 'Weekly' => 4, 'Monthly' => 5, 'Yearly' => 6 ); my $ord_a = $order{ $a->{archive_type} }; my $ord_b = $order{ $b->{archive_type} }; if ( defined($ord_a) && defined($ord_b) ) { return $ord_a <=> $ord_b; } # in the event a custom archive type includes the keyword 'Weekly', etc. # order it based on the mapping above. unless ($ord_a) { if ( ( my $best ) = grep { $a->{archive_type} =~ m/$_/ } keys %order ) { $ord_a = $order{$best}; } } unless ($ord_b) { if ( ( my $best ) = grep { $b->{archive_type} =~ m/$_/ } keys %order ) { $ord_b = $order{$best}; } } # Unknown archive types will follow this pattern (typically) # Foo # Foo-Bar # ie, Tag, Tag-Daily, etc. # So the archive_type is taken and we'll strip off the second # piece, if it matches one of our keys in %order, the sort key # becomes Tagnn (where nn is a zero-padded weight, selected from # the order hash). Tag-Daily would become Tag02. # 'Tag-Rank' would just become TagRank and would sort underneath 'Tag' # and other 'Tagnn' keys. my $str_a = $a->{archive_type}; if ( $str_a =~ s/-(.*)$// ) { $str_a .= $ord_a ? sprintf( "%02d", $ord_a ) : $1; } else { $str_a = "00" . $str_a if defined($ord_a); } my $str_b = $b->{archive_type}; if ( $str_b =~ s/-(.*)$// ) { $str_b .= $ord_b ? sprintf( "%02d", $ord_b ) : $1; } else { $str_b = "00" . $str_b if defined($ord_b); } return $str_a cmp $str_b; } sub cfg_archives { my $app = shift; my %param; %param = %{ $_[0] } if $_[0]; my $q = $app->param; my $blog_id = $q->param('blog_id'); return $app->return_to_dashboard( redirect => 1 ) unless $blog_id; my $blog = $app->model('blog')->load($blog_id); my @data; for my $at ( split /\s*,\s*/, $blog->archive_type ) { my $archiver = $app->publisher->archiver($at); next unless $archiver; next if 'entry' ne $archiver->entry_class; my $archive_label = $archiver->archive_label; $archive_label = $at unless $archive_label; $archive_label = $archive_label->() if ( ref $archive_label ) eq 'CODE'; push @data, { archive_type_translated => $archive_label, archive_type => $at, archive_type_is_preferred => ( $blog->archive_type_preferred eq $at ? 1 : 0 ), }; } @data = sort { archive_type_sorter( $a, $b ) } @data; $param{entry_archive_types} = \@data; $param{saved_deleted} = 1 if $q->param('saved_deleted'); $param{saved_added} = 1 if $q->param('saved_added'); $param{archives_changed} = 1 if $q->param('archives_changed'); $param{no_writedir} = $q->param('no_writedir'); $param{no_cachedir} = $q->param('no_cachedir'); $param{no_writecache} = $q->param('no_writecache'); $param{dynamic_none} = $blog->custom_dynamic_templates eq 'none'; $param{dynamic_archives} = $blog->custom_dynamic_templates eq 'archives'; $param{dynamic_custom} = $blog->custom_dynamic_templates eq 'custom'; $param{dynamic_all} = $blog->custom_dynamic_templates eq 'all'; if ( $app->config->ObjectDriver =~ qr/(db[id]::)?sqlite/i ) { $param{hide_build_option} = 1 unless $app->config->UseSQLite2; } my $mtview_path = File::Spec->catfile( $blog->site_path(), "mtview.php" ); if ( -f $mtview_path ) { open my ($fh), $mtview_path; while ( my $line = <$fh> ) { $param{dynamic_caching} = 1 if $line =~ m/^\s*\$mt->caching\s*=\s*true;/i; $param{dynamic_conditional} = 1 if $line =~ /^\s*\$mt->conditional\s*=\s*true;/i; } close $fh; } $param{output} = 'cfg_archives.tmpl'; $q->param( '_type', 'blog' ); $q->param( 'id', $blog_id ); $param{screen_class} = "settings-screen archive-settings"; $param{object_type} = 'author'; $param{search_label} = $app->translate('Users'); $app->edit_object( \%param ); } sub cfg_archives_save { my $app = shift; my ($blog) = @_; my $at = $app->param('preferred_archive_type'); $blog->archive_type_preferred($at); my $pq = $app->param('publish_queue'); $blog->publish_queue( $pq ? 1 : 0 ); $blog->save or return $app->error( $app->translate( "Saving blog failed: [_1]", $blog->errstr ) ); } sub cfg_system_general { my $app = shift; my %param; if ( $app->param('blog_id') ) { return $app->return_to_dashboard( redirect => 1 ); } return $app->errtrans("Permission denied.") unless $app->user->is_superuser(); my $cfg = $app->config; $app->add_breadcrumb( $app->translate('General Settings') ); $param{nav_config} = 1; $param{nav_settings} = 1; $param{languages} = $app->languages_list( $app->config('DefaultUserLanguage') ); my $tag_delim = $app->config('DefaultUserTagDelimiter') || 'comma'; $param{"tag_delim_$tag_delim"} = 1; ( my $tz = $app->config('DefaultTimezone') ) =~ s![-\.]!_!g; $tz =~ s!_00$!!; $param{ 'server_offset_' . $tz } = 1; $param{default_site_root} = $app->config('DefaultSiteRoot'); $param{default_site_url} = $app->config('DefaultSiteURL'); $param{personal_weblog_readonly} = $app->config->is_readonly('NewUserAutoProvisioning'); $param{personal_weblog} = $app->config->NewUserAutoProvisioning ? 1 : 0; if ( my $id = $param{new_user_template_blog_id} = $app->config('NewUserTemplateBlogId') || '' ) { my $blog = MT::Blog->load($id); if ($blog) { $param{new_user_template_blog_name} = $blog->name; } else { $app->config( 'NewUserTemplateBlogId', undef, 1 ); $cfg->save_config(); delete $param{new_user_template_blog_id}; } } $param{system_email_address} = $cfg->EmailAddressMain; $param{saved} = $app->param('saved'); $param{error} = $app->param('error'); $param{screen_class} = "settings-screen system-general-settings"; $app->load_tmpl( 'cfg_system_general.tmpl', \%param ); } sub cfg_system_users { my $app = shift; my %param; if ( $app->param('blog_id') ) { return $app->return_to_dashboard( redirect => 1 ); } return $app->errtrans("Permission denied.") unless $app->user->is_superuser(); my $cfg = $app->config; $app->add_breadcrumb( $app->translate('General Settings') ); $param{nav_config} = 1; $param{nav_settings} = 1; $param{languages} = $app->languages_list( $app->config('DefaultUserLanguage') ); my $tag_delim = $app->config('DefaultUserTagDelimiter') || ord(','); if ( $tag_delim eq ord(',') ) { $tag_delim = 'comma'; } elsif ( $tag_delim eq ord(' ') ) { $tag_delim = 'space'; } else { $tag_delim = 'comma'; } $param{"tag_delim_$tag_delim"} = 1; ( my $tz = $app->config('DefaultTimezone') ) =~ s![-\.]!_!g; $tz =~ s!_00$!!; $param{ 'server_offset_' . $tz } = 1; $param{default_site_root} = $app->config('DefaultSiteRoot'); $param{default_site_url} = $app->config('DefaultSiteURL'); $param{personal_weblog_readonly} = $app->config->is_readonly('NewUserAutoProvisioning'); $param{personal_weblog} = $app->config->NewUserAutoProvisioning ? 1 : 0; if ( my $id = $param{new_user_template_blog_id} = $app->config('NewUserTemplateBlogId') || '' ) { my $blog = MT::Blog->load($id); if ($blog) { $param{new_user_template_blog_name} = $blog->name; } else { $app->config( 'NewUserTemplateBlogId', undef, 1 ); $cfg->save_config(); delete $param{new_user_template_blog_id}; } } $param{system_email_address} = $cfg->EmailAddressMain; $param{saved} = $app->param('saved'); $param{error} = $app->param('error'); $param{screen_class} = "settings-screen system-general-settings"; my $registration = $cfg->CommenterRegistration; if ( $registration->{Allow} ) { $param{registration} = 1; if ( my $ids = $registration->{Notify} ) { my @ids = split ',', $ids; my @sysadmins = MT::Author->load( { id => \@ids, type => MT::Author::AUTHOR() }, { join => MT::Permission->join_on( 'author_id', { permissions => "\%'administer'\%", blog_id => '0', }, { 'like' => { 'permissions' => 1 } } ) } ); my @names; foreach my $a (@sysadmins) { push @names, $a->name . '(' . $a->id . ')'; } $param{notify_user_id} = $ids; $param{notify_user_name} = join ',', @names; } } $app->load_tmpl( 'cfg_system_users.tmpl', \%param ); } sub save_cfg_system_general { my $app = shift; $app->validate_magic or return; return $app->errtrans("Permission denied.") unless $app->user->is_superuser(); my $cfg = $app->config; $app->config( 'EmailAddressMain', $app->param('system_email_address') || undef, 1 ); $cfg->save_config(); my $args = (); $args->{saved} = 1; $app->redirect( $app->uri( 'mode' => 'cfg_system', args => $args ) ); } sub save_cfg_system_users { my $app = shift; $app->validate_magic or return; return $app->errtrans("Permission denied.") unless $app->user->is_superuser(); my $tmpl_blog_id = $app->param('new_user_template_blog_id') || ''; if ( $tmpl_blog_id =~ m/^\d+$/ ) { MT::Blog->load($tmpl_blog_id) or return $app->error( $app->translate( "Invalid ID given for personal blog clone source ID.") ); } else { if ( $tmpl_blog_id ne '' ) { return $app->error( $app->translate( "Invalid ID given for personal blog clone source ID.") ); } } my $cfg = $app->config; my $tz = $app->param('default_time_zone'); $app->config( 'DefaultTimezone', $tz || undef, 1 ); $app->config( 'DefaultSiteRoot', $app->param('default_site_root') || undef, 1 ); $app->config( 'DefaultSiteURL', $app->param('default_site_url') || undef, 1 ); $app->config( 'NewUserAutoProvisioning', $app->param('personal_weblog') ? 1 : 0, 1 ); $app->config( 'NewUserTemplateBlogId', $tmpl_blog_id || undef, 1 ); $app->config( 'DefaultUserLanguage', $app->param('default_language'), 1 ); $app->config( 'DefaultUserTagDelimiter', $app->param('default_user_tag_delimiter') || undef, 1 ); my $registration = $cfg->CommenterRegistration; if ( my $reg = $app->param('registration') ) { $registration->{Allow} = $reg ? 1 : 0; $registration->{Notify} = $app->param('notify_user_id'); $cfg->CommenterRegistration( $registration, 1 ); } elsif ( $registration->{Allow} ) { $registration->{Allow} = 0; $cfg->CommenterRegistration( $registration, 1 ); } $cfg->save_config(); my $args = (); if ( $app->config->NewUserAutoProvisioning() ne ( $app->param('personal_weblog') ? 1 : 0 ) ) { $args->{error} = $app->translate( 'If personal blog is set, the default site URL and root are required.' ); } else { $args->{saved} = 1; } $app->redirect( $app->uri( 'mode' => 'cfg_system_users', args => $args ) ); } sub cfg_system_feedback { my $app = shift; my %param; return $app->redirect( $app->uri( mode => 'cfg_comments', args => { blog_id => $app->param('blog_id') } ) ) if $app->param('blog_id'); return $app->errtrans("Permission denied.") unless $app->user->is_superuser(); my $cfg = $app->config; $param{nav_config} = 1; $app->add_breadcrumb( $app->translate('Feedback Settings') ); $param{nav_settings} = 1; $param{comment_disable} = $cfg->AllowComments ? 0 : 1; $param{ping_disable} = $cfg->AllowPings ? 0 : 1; $param{disabled_notify_ping} = $cfg->DisableNotificationPings ? 1 : 0; $param{system_no_email} = 1 unless $cfg->EmailAddressMain; my $send = $cfg->OutboundTrackbackLimit || 'any'; if ( $send =~ m/^(any|off|selected|local)$/ ) { $param{ "trackback_send_" . $cfg->OutboundTrackbackLimit } = 1; if ( $send eq 'selected' ) { my @domains = $cfg->OutboundTrackbackDomains; my $domains = join "\n", @domains; $param{trackback_send_domains} = $domains; } } else { $param{"trackback_send_any"} = 1; } $param{saved} = $app->param('saved'); $param{screen_class} = "settings-screen system-feedback-settings"; $app->load_tmpl( 'cfg_system_feedback.tmpl', \%param ); } sub save_cfg_system_feedback { my $app = shift; return $app->errtrans("Permission denied.") unless $app->user->is_superuser(); $app->validate_magic or return; my $cfg = $app->config; $cfg->AllowComments( ( $app->param('comment_disable') ? 0 : 1 ), 1 ); $cfg->AllowPings( ( $app->param('ping_disable') ? 0 : 1 ), 1 ); $cfg->DisableNotificationPings( ( $app->param('disable_notify_ping') ? 1 : 0 ), 1 ); my $send = $app->param('trackback_send') || 'any'; if ( $send =~ m/^(any|off|selected|local)$/ ) { $cfg->OutboundTrackbackLimit( $send, 1 ); if ( $send eq 'selected' ) { my $domains = $app->param('trackback_send_domains') || ''; $domains =~ s/[\r\n]+/ /gs; $domains =~ s/\s{2,}/ /gs; my @domains = split /\s/, $domains; $cfg->OutboundTrackbackDomains( \@domains, 1 ); } } $cfg->save_config(); $app->redirect( $app->uri( 'mode' => 'cfg_system_feedback', args => { saved => 1 } ) ); } sub reset_plugin_config { my $app = shift; $app->validate_magic or return; return $app->errtrans("Permission denied.") unless $app->user->can_manage_plugins; my $q = $app->param; my $plugin_sig = $q->param('plugin_sig'); my $profile = $MT::Plugins{$plugin_sig}; my $blog_id = $q->param('blog_id'); my %param; if ( $profile && $profile->{object} ) { $profile->{object} ->reset_config( $blog_id ? 'blog:' . $blog_id : 'system' ); } $app->add_return_arg( 'reset' => 1 ); $app->call_return; } sub save_plugin_config { my $app = shift; $app->validate_magic or return; return $app->errtrans("Permission denied.") unless $app->user->can_manage_plugins; my $q = $app->param; my $plugin_sig = $q->param('plugin_sig'); my $profile = $MT::Plugins{$plugin_sig}; my $blog_id = $q->param('blog_id'); my %param; my @params = $q->param; foreach (@params) { next if $_ =~ m/^(__mode|return_args|plugin_sig|magic_token|blog_id)$/; $param{$_} = $q->param($_); } if ( $profile && $profile->{object} ) { $profile->{object} ->save_config( \%param, $blog_id ? 'blog:' . $blog_id : 'system' ); } $app->add_return_arg( saved => 1 ); $app->call_return; } sub preview_object_basename { my $app = shift; my $q = $app->param; my @parts; my $blog = $app->blog; my $blog_id = $blog->id if $blog; my $id = $q->param('id'); push @parts, $app->user->id; push @parts, $blog_id || 0; push @parts, $id || 0; push @parts, $q->param('_type'); push @parts, $app->config->SecretToken; my $data = join ",", @parts; return MT::Util::perl_sha1_digest_hex($data); } sub preview_entry { my $app = shift; my $q = $app->param; my $type = $q->param('_type') || 'entry'; my $entry_class = $app->model($type); my $blog_id = $q->param('blog_id'); my $blog = $app->blog; my $id = $q->param('id'); my $entry; my $user_id = $app->user->id; if ($id) { $entry = $entry_class->load( { id => $id, blog_id => $blog_id } ) or return $app->errtrans( "Invalid request." ); $user_id = $entry->author_id; } else { $entry = $entry_class->new; $entry->author_id($user_id); $entry->id(-1); # fake out things like MT::Taggable::__load_tags $entry->blog_id($blog_id); } my $cat; my $names = $entry->column_names; my %values = map { $_ => scalar $app->param($_) } @$names; delete $values{'id'} unless $q->param('id'); ## Strip linefeed characters. for my $col (qw( text excerpt text_more keywords )) { $values{$col} =~ tr/\r//d if $values{$col}; } $values{allow_comments} = 0 if !defined( $values{allow_comments} ) || $q->param('allow_comments') eq ''; $values{allow_pings} = 0 if !defined( $values{allow_pings} ) || $q->param('allow_pings') eq ''; $entry->set_values( \%values ); my $cat_ids = $q->param('category_ids'); if ($cat_ids) { my @cats = split /,/, $cat_ids; if (@cats) { my $primary_cat = $cats[0]; $cat = MT::Category->load( { id => $primary_cat, blog_id => $blog_id } ); my @categories = MT::Category->load( { id => \@cats, blog_id => $blog_id }); $entry->cache_property('category', undef, $cat); $entry->cache_property('categories', undef, \@categories); } } else { $entry->cache_property('category', undef, undef); $entry->cache_property('categories', undef, []); } my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} ); my @tag_names = MT::Tag->split( $tag_delim, $q->param('tags') ); if (@tag_names) { my @tags; foreach my $tag_name (@tag_names) { my $tag = MT::Tag->new; $tag->name($tag_name); push @tags, $tag; } $entry->{__tags} = \@tag_names; $entry->{__tag_objects} = \@tags; } my $date = $q->param('authored_on_date'); my $time = $q->param('authored_on_time'); my $ts = $date . $time; $ts =~ s/\D//g; $entry->authored_on($ts); my $preview_basename = $app->preview_object_basename; $entry->basename($preview_basename); require MT::TemplateMap; require MT::Template; my $tmpl_map = MT::TemplateMap->load( { archive_type => ( $type eq 'page' ? 'Page' : 'Individual' ), is_preferred => 1, blog_id => $blog_id, } ); my $tmpl; my $fullscreen; my $archive_file; my $orig_file; my $file_ext; if ($tmpl_map) { $tmpl = MT::Template->load( $tmpl_map->template_id ); $file_ext = $blog->file_extension || ''; $archive_file = $entry->archive_file; my $blog_path = $type eq 'page' ? $blog->site_path : ($blog->archive_path || $blog->site_path); $archive_file = File::Spec->catfile( $blog_path, $archive_file ); require File::Basename; my $path; ( $orig_file, $path ) = File::Basename::fileparse( $archive_file ); $file_ext = '.' . $file_ext if $file_ext ne ''; $archive_file = File::Spec->catfile( $path, $preview_basename . $file_ext ); } else { $tmpl = $app->load_tmpl('preview_entry_content.tmpl'); $fullscreen = 1; } # translates naughty words when PublishCharset is NOT UTF-8 $app->_translate_naughty_words($entry); $entry->convert_breaks( scalar $q->param('convert_breaks') ); my @data = ( { data_name => 'author_id', data_value => $user_id } ); $app->run_callbacks( 'cms_pre_preview', $app, $entry, \@data ); my $ctx = $tmpl->context; $ctx->stash( 'entry', $entry ); $ctx->stash( 'blog', $blog ); $ctx->stash( 'category', $cat ) if $cat; $ctx->{current_timestamp} = $ts; $ctx->var('entry_template', 1); $ctx->var('main_template', 1); $ctx->var('archive_template', 1); $ctx->var('entry_template', 1); $ctx->var('feedback_template', 1); $ctx->var('archive_class', 'entry-archive'); $ctx->var('preview_template', 1); my $html = $tmpl->output; my %param; unless ( defined($html) ) { my $preview_error = $app->translate( "Publish error: [_1]", MT::Util::encode_html( $tmpl->errstr ) ); $param{preview_error} = $preview_error; my $tmpl_plain = $app->load_tmpl('preview_entry_content.tmpl'); $tmpl->text( $tmpl_plain->text ); $html = $tmpl->output; defined($html) or return $app->error( $app->translate( "Publish error: [_1]", $tmpl->errstr ) ); $fullscreen = 1; } # If MT is configured to do 'local' previews, convert all # the normal blog URLs into the domain used by MT itself (ie, # blog is published to www.example.com, which is a different # server from where MT runs, mt.example.com; previews therefore # should occur locally, so replace all http://www.example.com/ # with http://mt.example.com/). my ($old_url, $new_url); if ($app->config('LocalPreviews')) { $old_url = $blog->site_url; $old_url =~ s!^(https?://[^/]+?/)(.*)?!$1!; $new_url = $app->base . '/'; $html =~ s!\Q$old_url\E!$new_url!g; } if ( !$fullscreen ) { my $fmgr = $blog->file_mgr; ## Determine if we need to build directory structure, ## and build it if we do. DirUmask determines ## directory permissions. require File::Basename; my $path = File::Basename::dirname($archive_file); $path =~ s!/$!! unless $path eq '/'; ## OS X doesn't like / at the end in mkdir(). unless ( $fmgr->exists($path) ) { $fmgr->mkpath($path); } if ( $fmgr->exists($path) && $fmgr->can_write($path) ) { $fmgr->put_data( $html, $archive_file ); $param{preview_file} = $preview_basename; my $preview_url = $entry->archive_url; $preview_url =~ s! / \Q$orig_file\E ( /? ) $!/$preview_basename$file_ext$1!x; # We also have to translate the URL used for the # published file to be on the MT app domain. if (defined $new_url) { $preview_url =~ s!^\Q$old_url\E!$new_url!; } $param{preview_url} = $preview_url; # we have to make a record of this preview just in case it # isn't cleaned up by re-editing, saving or cancelling on # by the user. require MT::Session; my $sess_obj = MT::Session->get_by_key( { id => $preview_basename, kind => 'TF', # TF = Temporary File name => $archive_file, } ); $sess_obj->start(time); $sess_obj->save; } else { $fullscreen = 1; $param{preview_error} = $app->translate( "Unable to create preview file in this location: [_1]", $path ); my $tmpl_plain = $app->load_tmpl('preview_entry_content.tmpl'); $tmpl->text( $tmpl_plain->text ); $tmpl->reset_tokens; $html = $tmpl->output; $param{preview_body} = $html; } } else { $param{preview_body} = $html; } $param{id} = $id if $id; $param{new_object} = $param{id} ? 0 : 1; $param{title} = $entry->title; my $cols = $entry_class->column_names; for my $col (@$cols) { next if $col eq 'created_on' || $col eq 'created_by' || $col eq 'modified_on' || $col eq 'modified_by' || $col eq 'authored_on' || $col eq 'author_id' || $col eq 'pinged_urls' || $col eq 'tangent_cache' || $col eq 'template_id' || $col eq 'class' || $col eq 'meta'; if ( $col eq 'basename' ) { if ( ( !defined $q->param('basename') ) || ( $q->param('basename') eq '' ) ) { $q->param( 'basename', $q->param('basename_old') ); } } push @data, { data_name => $col, data_value => scalar $q->param($col) }; } for my $data ( qw( authored_on_date authored_on_time basename_manual basename_old category_ids tags ) ) { push @data, { data_name => $data, data_value => scalar $q->param($data) }; } $param{entry_loop} = \@data; my $list_mode; my $list_title; if ( $type eq 'page' ) { $list_title = 'Pages'; $list_mode = 'list_pages'; } else { $list_title = 'Entries'; $list_mode = 'list_entries'; } if ($id) { $app->add_breadcrumb( $app->translate($list_title), $app->uri( 'mode' => $list_mode, args => { blog_id => $blog_id } ) ); $app->add_breadcrumb( $entry->title || $app->translate('(untitled)') ); } else { $app->add_breadcrumb( $app->translate($list_title), $app->uri( 'mode' => $list_mode, args => { blog_id => $blog_id } ) ); $app->add_breadcrumb( $app->translate( 'New [_1]', $entry_class->class_label ) ); $param{nav_new_entry} = 1; } $param{object_type} = $type; $param{object_label} = $entry_class->class_label; if ($fullscreen) { return $app->load_tmpl( 'preview_entry.tmpl', \%param ); } else { return $app->load_tmpl( 'preview_strip.tmpl', \%param ); } } sub rebuild_confirm { my $app = shift; my $blog_id = $app->param('blog_id'); require MT::Blog; my $blog = MT::Blog->load($blog_id); my $at = $blog->archive_type || ''; my ( @blog_at, @at, @data ); my $archiver; my $archive_label; if ( $at && $at ne 'None' ) { @blog_at = split /,/, $at; foreach my $t (@blog_at) { $archiver = $app->publisher->archiver($t); next unless $archiver; # ignore unknown archive types push @at, $t; $archive_label = $archiver->archive_label; $archive_label = $at unless $archive_label; $archive_label = $archive_label->() if ( ref $archive_label ) eq 'CODE'; push( @data, { archive_type => $t, archive_type_label => $archive_label } ); } } my $order = join ',', @at, 'index'; my $entry_pkg = $app->model('entry'); my %param = ( archive_type_loop => \@data, build_order => $order, build_next => 0, ); $param{index_selected} = ( $app->param('prompt') || "" ) eq 'index'; if ( my $tmpl_id = $app->param('tmpl_id') ) { require MT::Template; my $tmpl = MT::Template->load($tmpl_id); $param{index_tmpl_id} = $tmpl->id; $param{index_tmpl_name} = $tmpl->name; } my $options = $app->registry("rebuild_options") || {}; my @options; if ($options) { foreach my $opt ( keys %$options ) { $options->{$opt}{key} ||= $opt; push @options, $options->{$opt}; } } $app->run_callbacks( 'rebuild_options', $app, \@options ); my $rebuild_options = $app->filter_conditional_list( \@options ); $param{rebuild_option_loop} = $rebuild_options; $param{refocus} = 1; $app->add_breadcrumb( $app->translate('Publish Site') ); $app->load_tmpl( 'popup/rebuild_confirm.tmpl', \%param ); } sub start_rebuild_pages { my $app = shift; my $q = $app->param; my $type = $q->param('type'); my $next = $q->param('next') || 0; my @order = split /,/, $type; my $total = $q->param('total') || 0; my $type_name = $order[$next]; my $archiver = $app->publisher->archiver($type_name); my $archive_label = $archiver ? $archiver->archive_label : ''; $archive_label = $app->translate($type_name) unless $archive_label; $archive_label = $archive_label->() if ( ref $archive_label ) eq 'CODE'; my $blog_id = $q->param('blog_id'); if ($archiver) { if ( $archiver->entry_based || $archiver->date_based ) { my $entry_class = $archiver->entry_class || 'entry'; require MT::Entry; my $terms = { class => $entry_class, status => MT::Entry::RELEASE(), blog_id => $blog_id, }; $total = MT::Entry->count($terms); } elsif ( $archiver->category_based ) { require MT::Category; my $terms = { blog_id => $blog_id, class => $archiver->category_class, }; $total = MT::Category->count($terms); } elsif ( $archiver->author_based ) { require MT::Author; require MT::Entry; my $terms = { blog_id => $blog_id, status => MT::Entry::RELEASE(), class => 'entry', }; $total = MT::Author->count( undef, { join => MT::Entry->join_on( 'author_id', $terms, { unique => 1 } ), unique => 1, } ); } } my %param = ( build_type => $type, build_next => $next, total => $total, complete => 0, incomplete => 100, build_type_name => $archive_label ); if ( $type_name =~ /^index-(\d+)$/ ) { my $tmpl_id = $1; require MT::Template; my $tmpl = MT::Template->load($tmpl_id); $param{build_type_name} = $app->translate( "index template '[_1]'", $tmpl->name ); $param{is_one_index} = 1; } elsif ( $type_name =~ /^entry-(\d+)$/ ) { my $entry_id = $1; require MT::Entry; my $entry = MT::Entry->load($entry_id); $param{build_type_name} = $app->translate( "[_1] '[_2]'", $entry->class_label, $entry->title ); $param{is_entry} = 1; $param{entry_id} = $entry_id; for my $col (qw( is_new old_status old_next old_previous )) { $param{$col} = $q->param($col); } } $param{is_full_screen} = ( $param{is_entry} ) || $q->param('single_template'); $param{page_titles} = [ { bc_name => 'Rebuilding' } ]; $app->load_tmpl( 'rebuilding.tmpl', \%param ); } sub object_edit_uri { my $app = shift; my ( $type, $id ) = @_; my $class = $app->model($type); die "no such object $type" unless $class; my $obj = $class->load($id) or die "object_edit_uri could not find $type object $id"; my $blog_id = $obj->column('blog_id') if $obj->has_column('blog_id'); return $app->uri( 'mode' => 'view', args => { '_type' => $type, ( $blog_id ? ( blog_id => $blog_id ) : () ), id => $_[1] } ); } sub rebuild_pages { my $app = shift; my $perms = $app->permissions or return $app->error( $app->translate("No permissions") ); require MT::Entry; require MT::Blog; my $q = $app->param; my $blog_id = $q->param('blog_id'); my $blog = MT::Blog->load($blog_id); my $order = $q->param('type'); my @order = split /,/, $order; my $next = $q->param('next'); my $done = 0; my $type = $order[$next]; my $archiver = $app->publisher->archiver($type); my $archive_label = $archiver ? $archiver->archive_label : ''; $archive_label = $app->translate($type) unless $archive_label; $archive_label = $archive_label->() if ( ref $archive_label ) eq 'CODE'; $next++; $done++ if $next >= @order; my $offset = 0; my ($total) = $q->param('total'); ## Tells MT::_rebuild_entry_archive_type to cache loaded templates so ## that each template is only loaded once. $app->{cache_templates} = 1; my ($tmpl_saved); # Make sure errors go to a sensible place when in fs mode # TODO: create contin. earlier, pass it thru if ( $app->param('fs') ) { my ( $type, $obj_id ) = $app->param('type') =~ m/(entry|index)-(\d+)/; if ( $type && $obj_id ) { my $edit_type = $type; $edit_type = 'template' if $type eq 'index'; if ($type eq 'entry') { require MT::Entry; my $entry = MT::Entry->load($obj_id); $edit_type = $entry->class; } $app->{goback} = "window.location='" . $app->object_edit_uri( $edit_type, $obj_id ) . "'"; $app->{value} ||= $app->translate('Go Back'); } } # FIXME: Wrap the entire rebuild operation with begin/end callbacks if ( $type eq 'all' ) { return $app->error( $app->translate("Permission denied.") ) unless $perms->can_rebuild; # FIXME: Rebuild the entire blog???? $app->rebuild( BlogID => $blog_id ) or return $app->publish_error(); } elsif ( $type eq 'index' ) { return $app->error( $app->translate("Permission denied.") ) unless $perms->can_rebuild; $app->rebuild_indexes( BlogID => $blog_id ) or return $app->publish_error(); } elsif ( $type =~ /^index-(\d+)$/ ) { return $app->error( $app->translate("Permission denied.") ) unless $perms->can_rebuild; my $tmpl_id = $1; require MT::Template; $tmpl_saved = MT::Template->load($tmpl_id); $app->rebuild_indexes( BlogID => $blog_id, Template => $tmpl_saved, Force => 1 ) or return $app->publish_error(); $order = $app->translate( "index template '[_1]'", $tmpl_saved->name ); } elsif ( $type =~ /^entry-(\d+)$/ ) { my $entry_id = $1; require MT::Entry; my $entry = MT::Entry->load($entry_id); return $app->error( $app->translate("Permission denied.") ) unless $perms->can_edit_entry( $entry, $app->user ); $app->rebuild_entry( Entry => $entry, BuildDependencies => 1, OldPrevious => $q->param('old_previous'), OldNext => $q->param('old_next') ) or return $app->publish_error(); $order = "entry '" . $entry->title . "'"; } elsif ( $archiver->category_based ) { return $app->error( $app->translate("Permission denied.") ) unless $perms->can_rebuild; $offset = $q->param('offset') || 0; my $start = time; my $count = 0; my $cb = sub { return 0 if time - $start > 20; # 10 seconds $count++; return 1; }; if ( $offset < $total ) { $app->rebuild( BlogID => $blog_id, ArchiveType => $type, NoIndexes => 1, Offset => $offset, FilterCallback => $cb, ) or return $app->publish_error(); $offset += $count; } if ( $offset < $total ) { $done-- if $done; $next--; } else { $offset = 0; } } elsif ($type) { my $special = 0; my @options = $app->{rebuild_options} ||= {}; $app->run_callbacks( 'rebuild_options', $app, \@options ); for my $optn (@options) { if ( ( $optn->{key} || '' ) eq $type ) { $optn->{code}->(); $special = 1; } } if ( !$special ) { return $app->error( $app->translate("Permission denied.") ) unless $perms->can_rebuild; $offset = $q->param('offset') || 0; if ( $offset < $total ) { my $start = time; my $count = 0; my $cb = sub { return 0 if time - $start > 20; # 10 seconds $count++; return 1; }; $app->rebuild( BlogID => $blog_id, ArchiveType => $type, NoIndexes => 1, Offset => $offset, FilterCallback => $cb, ) or return $app->publish_error(); $offset += $count; } if ( $offset < $total ) { $done-- if $done; $next--; } else { $offset = 0; } } } # Rebuild done--now form the continuation. unless ($done) { my $dynamic = 0; my $type_name = $order[$next]; ## If we're moving on to the next rebuild step, recalculate the ## limit. my $static_count; if ( $type_name !~ m/^index/ ) { $static_count = $blog->count_static_templates($type_name) || 0; } else { $static_count = 1; } if ( !$static_count ) { $dynamic = 1; } elsif ( defined($offset) && $offset == 0 ) { $dynamic = 0; } if ( $offset == 0 ) { # determine total if ( my $archiver = $app->publisher->archiver($type_name) ) { if ( $archiver->entry_based || $archiver->date_based ) { my $entry_class = $archiver->entry_class || 'entry'; require MT::Entry; my $terms = { class => $entry_class, status => MT::Entry::RELEASE(), blog_id => $blog_id, }; $total = MT::Entry->count($terms); } elsif ( $archiver->category_based ) { require MT::Category; my $terms = { blog_id => $blog_id, }; $total = MT::Category->count($terms); } elsif ( $archiver->author_based ) { require MT::Author; require MT::Entry; my $terms = { blog_id => $blog_id, status => MT::Entry::RELEASE(), class => 'entry', }; $total = MT::Author->count( undef, { join => MT::Entry->join_on( 'author_id', $terms, { unique => 1 } ), unique => 1, } ); } } } my $type = $order[$next]; if ($type) { $archiver = $app->publisher->archiver($type); $archive_label = $archiver ? $archiver->archive_label : ''; $archive_label = $app->translate($type) unless $archive_label; $archive_label = $archive_label->() if ( ref $archive_label ) eq 'CODE'; } my $complete = $total ? ( $total == $offset ? 100 : int( ( $offset / $total ) * 100 ) ) : 0; my %param = ( build_type => $order, build_next => $next, build_type_name => $archive_label, archives => $archiver ? 1 : 0, total => $total, offset => $offset, complete => $complete, incomplete => 100 - $complete, entry_id => scalar $q->param('entry_id'), dynamic => $dynamic, is_new => scalar $q->param('is_new'), old_status => scalar $q->param('old_status') ); $app->load_tmpl( 'rebuilding.tmpl', \%param ); } else { if ( $q->param('entry_id') ) { require MT::Entry; my $entry = MT::Entry->load( scalar $q->param('entry_id') ); require MT::Blog; my $blog = MT::Blog->load( $entry->blog_id ); $app->ping_continuation( $entry, $blog, OldStatus => scalar $q->param('old_status'), IsNew => scalar $q->param('is_new'), ); } else { my $all = $order =~ /,/; my $type = $order; my $is_one_index = $order =~ /index template/; my $is_entry = $order =~ /entry/; my $built_type; if ( $is_entry || $is_one_index ) { ( $built_type = $type ) =~ s/^(entry|index template)/$app->translate($1)/e; } else { $built_type = $app->translate($type); } my %param = ( all => $all, type => $archive_label, is_one_index => $is_one_index, is_entry => $is_entry, archives => $type ne 'index', ); if ($is_one_index) { $param{tmpl_url} = $blog->site_url; $param{tmpl_url} .= '/' if $param{tmpl_url} !~ m!/$!; $param{tmpl_url} .= $tmpl_saved->outfile; } if ( $q->param('fs') ) { # full screen--go to a useful app page my $type = $q->param('type'); $type =~ /index-(\d+)/; my $tmpl_id = $1; $app->run_callbacks( 'rebuild', $blog ); return $app->redirect( $app->uri( 'mode' => 'view', args => { '_type' => 'template', id => $tmpl_id, blog_id => $blog->id, saved_rebuild => 1 } ) ); } else { # popup--just go to cnfrmn. page $app->run_callbacks( 'rebuild', $blog ); return $app->load_tmpl( 'popup/rebuilt.tmpl', \%param ); } } } } sub send_pings { my $app = shift; my $q = $app->param; $app->validate_magic() or return; require MT::Entry; require MT::Blog; my $blog = MT::Blog->load( scalar $q->param('blog_id') ); my $entry = MT::Entry->load( scalar $q->param('entry_id') ); ## MT::ping_and_save pings each of the necessary URLs, then processes ## the return value from MT::ping to update the list of URLs pinged ## and not successfully pinged. It returns the return value from ## MT::ping for further processing. If a fatal error occurs, it returns ## undef. my $results = $app->ping_and_save( Blog => $blog, Entry => $entry, OldStatus => scalar $q->param('old_status') ) or return; my $has_errors = 0; require MT::Log; for my $res (@$results) { $has_errors++, $app->log( { message => $app->translate( "Ping '[_1]' failed: [_2]", $res->{url}, encode_text( $res->{error}, undef, undef ) ), class => 'system', level => MT::Log::WARNING() } ) unless $res->{good}; } $app->_finish_rebuild_ping( $entry, scalar $q->param('is_new'), $has_errors ); } sub edit_role { my $app = shift; $app->return_to_dashboard( redirect => 1 ) if $app->param('blog_id'); my %param = $_[0] ? %{ $_[0] } : (); my $q = $app->param; my $author = $app->user; my $id = $q->param('id'); require MT::Permission; if ( !$author->is_superuser ) { return $app->error( $app->translate("Invalid request.") ); } my $role; require MT::Role; if ($id) { $role = MT::Role->load($id); # $param{is_enabled} = $role->is_active; $param{is_enabled} = 1; $param{name} = $role->name; $param{description} = $role->description; $param{id} = $role->id; require MT::Author; my $creator = MT::Author->load( $role->created_by ) if $role->created_by; $param{created_by} = $creator ? $creator->name : ''; my $permissions = $role->permissions; if ( defined($permissions) && $permissions ) { my @perms = split ',', $permissions; my @roles = MT::Role->load_same( { 'id' => [$id] }, { not => { id => 1 } }, 1, # exact match @perms ); my @same_perms; for my $other_role (@roles) { push @same_perms, { name => $other_role->name, id => $other_role->id, }; } $param{same_perm_loop} = \@same_perms if @same_perms; } require MT::Association; require MT::Author; $param{user_count} = MT::Author->count( undef, { join => MT::Association->join_on( 'author_id', { role_id => $id }, { unique => 1 } ) } ); } my $all_perm_flags = MT::Permission->perms('blog'); my @p_data; for my $ref (@$all_perm_flags) { $param{ 'have_access-' . $ref->[0] } = ( $role && $role->has( $ref->[0] ) ) ? 1 : 0; $param{ 'prompt-' . $ref->[0] } = $app->translate( $ref->[1] ); } $param{saved} = $q->param('saved'); $param{nav_privileges} = 1; $app->add_breadcrumb( $app->translate('Roles'), $app->uri( mode => 'list_roles' ) ); if ($id) { $app->add_breadcrumb( $role->name ); } else { $app->add_breadcrumb( $app->translate('Create Role') ); } $param{screen_class} = "settings-screen edit-role"; $param{object_type} = 'role'; $param{object_label} = MT::Role->class_label; $param{object_label_plural} = MT::Role->class_label_plural; $param{search_label} = $app->translate('Users'); $app->load_tmpl( 'edit_role.tmpl', \%param ); } sub save_role { my $app = shift; my $q = $app->param; $app->validate_magic() or return; $app->user->is_superuser or return $app->errtrans("Invalid request."); my $id = $q->param('id'); my @perms = $q->param('permission'); my $role; require MT::Role; $role = $id ? MT::Role->load($id) : MT::Role->new; my $name = $q->param('name') || ''; $name =~ s/(^\s+|\s+$)//g; return $app->errtrans("Role name cannot be blank.") if $name eq ''; my $role_by_name = MT::Role->load( { name => $name } ); if ( $role_by_name && ( ( $id && ( $role->id != $id ) ) || !$id ) ) { return $app->errtrans("Another role already exists by that name."); } if ( !@perms ) { return $app->errtrans("You cannot define a role without permissions."); } $role->name( $q->param('name') ); $role->description( $q->param('description') ); $role->clear_full_permissions; $role->set_these_permissions(@perms); if ( $role->id ) { $role->modified_by( $app->user->id ); } else { $role->created_by( $app->user->id ); } $role->save or return $app->error( $role->errstr ); my $url; $url = $app->uri( 'mode' => 'edit_role', args => { id => $role->id, saved => 1 } ); $app->redirect($url); } sub languages_list { my $app = shift; my ($curr) = @_; my $langs = $app->supported_languages; my @data; $curr ||= $app->config('DefaultLanguage'); $curr = 'en-us' if ( lc($curr) eq 'en_us' ); for my $tag ( keys %$langs ) { ( my $name = $langs->{$tag} ) =~ s/\w+ English/English/; my $row = { l_tag => $tag, l_name => $app->translate($name) }; $row->{l_selected} = 1 if $curr eq $tag; push @data, $row; } [ sort { $a->{l_name} cmp $b->{l_name} } @data ]; } sub entry_notify { my $app = shift; my $user = $app->user; my $perms = $app->permissions; return $app->error( $app->translate("No permissions.") ) unless $perms->can_send_notifications; my $q = $app->param; my $entry_id = $q->param('entry_id') or return $app->error( $app->translate("No entry ID provided") ); require MT::Entry; require MT::Blog; my $entry = MT::Entry->load($entry_id) or return $app->error( $app->translate( "No such entry '[_1]'", $entry_id ) ); my $blog = MT::Blog->load( $entry->blog_id ); my $param = {}; $param->{entry_id} = $entry_id; return $app->load_tmpl( "dialog/entry_notify.tmpl", $param ); } sub send_notify { my $app = shift; $app->validate_magic() or return; my $q = $app->param; my $entry_id = $q->param('entry_id') or return $app->error( $app->translate("No entry ID provided") ); require MT::Entry; require MT::Blog; my $entry = MT::Entry->load($entry_id) or return $app->error( $app->translate( "No such entry '[_1]'", $entry_id ) ); my $blog = MT::Blog->load( $entry->blog_id ); my $user = $app->user; $app->blog($blog); my $perms = $user->permissions($blog); return $app->error( $app->translate("No permissions.") ) unless $perms->can_send_notifications; my $author = $entry->author; return $app->error( $app->translate( "No email address for user '[_1]'", $author->name ) ) unless $author->email; my $cols = 72; my %params; $params{blog} = $blog; $params{entry} = $entry; $params{author} = $author; if ( $q->param('send_excerpt') ) { $params{send_excerpt} = 1; } $params{message} = wrap_text( $q->param('message'), $cols, '', '' ); if ( $q->param('send_body') ) { $params{send_body} = 1; } my $entry_editurl = $app->uri( 'mode' => 'view', args => { '_type' => 'entry', blog_id => $entry->blog_id, id => $entry->id, } ); if ( $entry_editurl =~ m|^/| ) { my ($blog_domain) = $blog->archive_url =~ m|(.+://[^/]+)|; $entry_editurl = $blog_domain . $entry_editurl; } $params{entry_editurl} = $entry_editurl; my $addrs; if ( $q->param('send_notify_list') ) { require MT::Notification; my $iter = MT::Notification->load_iter( { blog_id => $blog->id } ); while ( my $note = $iter->() ) { next unless is_valid_email( $note->email ); $addrs->{ $note->email } = 1; } } if ( $q->param('send_notify_emails') ) { my @addr = split /[\n\r,]+/, $q->param('send_notify_emails'); for my $a (@addr) { next unless is_valid_email($a); $addrs->{$a} = 1; } } keys %$addrs or return $app->error( $app->translate( "No valid recipients found for the entry notification.") ); my $body = $app->build_email( 'notify-entry.tmpl', \%params ); my $subj = $app->translate( "[_1] Update: [_2]", $blog->name, $entry->title ); if ( $app->current_language ne 'ja' ) { # FIXME perhaps move to MT::I18N $subj =~ s![\x80-\xFF]!!g; } my $address = defined $author->nickname ? $author->nickname . ' <' . $author->email . '>' : $author->email; my %head = ( id => 'notify_entry', To => $address, From => $address, Subject => $subj, ); my $charset = $app->config('MailEncoding') || $app->charset; $head{'Content-Type'} = qq(text/plain; charset="$charset"); my $i = 1; require MT::Mail; MT::Mail->send( \%head, $body ) or return $app->error( $app->translate( "Error sending mail ([_1]); try another MailTransfer setting?", MT::Mail->errstr ) ); delete $head{To}; foreach my $email ( keys %{$addrs} ) { next unless $email; if ( $app->config('EmailNotificationBcc') ) { push @{ $head{Bcc} }, $email; if ( $i++ % 20 == 0 ) { MT::Mail->send( \%head, $body ) or return $app->error( $app->translate( "Error sending mail ([_1]); try another MailTransfer setting?", MT::Mail->errstr ) ); @{ $head{Bcc} } = (); } } else { $head{To} = $email; MT::Mail->send( \%head, $body ) or return $app->error( $app->translate( "Error sending mail ([_1]); try another MailTransfer setting?", MT::Mail->errstr ) ); delete $head{To}; } } if ( $head{Bcc} && @{ $head{Bcc} } ) { MT::Mail->send( \%head, $body ) or return $app->error( $app->translate( "Error sending mail ([_1]); try another MailTransfer setting?", MT::Mail->errstr ) ); } $app->redirect( $app->uri( 'mode' => 'view', args => { '_type' => $entry->class, blog_id => $entry->blog_id, id => $entry->id, saved_notify => 1 } ) ); } sub _set_start_upload_params { my $app = shift; my ($param) = @_; if (my $perms = $app->permissions) { return $app->error( $app->translate("Permission denied.") ) unless $perms->can_upload; my $blog_id = $app->param('blog_id'); require MT::Blog; my $blog = MT::Blog->load($blog_id); $param->{enable_archive_paths} = $blog->column('archive_path'); $param->{local_site_path} = $blog->site_path; $param->{local_archive_path} = $blog->archive_path; my $label_path; if ( $param->{enable_archive_paths} ) { $label_path = $app->translate('Archive Root'); } else { $label_path = $app->translate('Site Root'); } my @extra_paths; my $date_stamp = epoch2ts( $blog, time ); $date_stamp =~ s!^(\d\d\d\d)(\d\d)(\d\d).*!$1/$2/$3!; my $path_hash = { path => $date_stamp, label => '<' . $app->translate($label_path) . '>' . '/' . $date_stamp, }; if ( exists( $param->{middle_path} ) && ( $date_stamp eq $param->{middle_path} ) ) { $path_hash->{selected} = 1; delete $param->{archive_path}; } push @extra_paths, $path_hash; $param->{extra_paths} = \@extra_paths; $param->{refocus} = 1; $param->{missing_paths} = ( ( defined $blog->site_path || defined $blog->archive_path ) && ( -d $blog->site_path || -d $blog->archive_path ) ) ? 0 : 1; if ( $param->{missing_paths} ) { if ( $app->user->is_superuser || $app->run_callbacks( 'cms_view_permission_filter.blog', $app, $blog_id, $blog ) ) { $param->{have_permissions} = 1; } } $param->{enable_destination} = 1; my $data = $app->_build_category_list( blog_id => $blog_id, markers => 1, type => 'folder', ); my $top_cat = -1; my $cat_tree = [{ id => -1, label => '/', basename => '/', path => [], }]; foreach (@$data) { next unless exists $_->{category_id}; $_->{category_path_ids} ||= []; unshift @{ $_->{category_path_ids} }, -1; push @$cat_tree, { id => $_->{category_id}, label => $_->{category_label} . '/', basename => $_->{category_basename} . '/', path => $_->{category_path_ids} || [], }; } $param->{category_tree} = $cat_tree; } else { $param->{local_site_path} = ''; $param->{local_archive_path} = ''; } $param; } sub start_upload { my $app = shift; $app->add_breadcrumb( $app->translate('Upload File') ); my %param; %param = @_ if @_; $app->_set_start_upload_params(\%param); for my $field (qw( entry_insert edit_field upload_mode require_type asset_select )) { $param{$field} ||= $app->param($field); } $app->load_tmpl( 'dialog/asset_upload.tmpl', \%param ); } sub complete_insert { my $app = shift; my (%args) = @_; my $asset = $args{asset}; if ( !$asset && $app->param('id') ) { require MT::Asset; $asset = MT::Asset->load( $app->param('id') ) || return $app->errtrans( "Can't load file #[_1].", $app->param('id') ); } return $app->errtrans('Invalid request.') unless $asset; $args{is_image} = $asset->isa('MT::Asset::Image') ? 1 : 0 unless defined $args{is_image}; require MT::Blog; my $blog = $asset->blog or return $app->errtrans( "Can't load blog #[_1].", $app->param('blog_id') ); my $perms = $app->permissions or return $app->errtrans('No permissions'); my $param = { asset_id => $asset->id, bytes => $args{bytes}, fname => $asset->file_name, is_image => $args{is_image} || 0, url => $asset->url, middle_path => $app->param('middle_path') || '', extra_path => $app->param('extra_path') || '', }; for my $field (qw( direct_asset_insert edit_field entry_insert site_path asset_select )) { $param->{$field} = scalar $app->param($field) || ''; } if ( $args{is_image} ) { $param->{width} = $asset->image_width; $param->{height} = $asset->image_height; } if ( !$app->param('asset_select') && ($perms->can_create_post || $app->user->is_superuser) ) { my $html = $asset->insert_options($param); if ( $param->{direct_asset_insert} && !$html ) { $app->param( 'id', $asset->id ); return $app->asset_insert(); } $param->{options_snippet} = $html; } if ($perms) { my $pref_param = $app->load_entry_prefs( $perms->entry_prefs ); %$param = ( %$param, %$pref_param ); # Completion for tags my $author = $app->user; my $auth_prefs = $author->entry_prefs; if ( my $delim = chr( $auth_prefs->{tag_delim} ) ) { if ( $delim eq ',' ) { $param->{'auth_pref_tag_delim_comma'} = 1; } elsif ( $delim eq ' ' ) { $param->{'auth_pref_tag_delim_space'} = 1; } else { $param->{'auth_pref_tag_delim_other'} = 1; } $param->{'auth_pref_tag_delim'} = $delim; } require MT::ObjectTag; my $q = $app->param; my $blog_id = $q->param('blog_id'); my $count = MT::Tag->count( undef, { 'join' => MT::ObjectTag->join_on( 'tag_id', { blog_id => $blog_id, object_datasource => MT::Asset->datasource }, { unique => 1 } ) } ); if ( $count > 1000 ) { # FIXME: Configurable limit? $param->{defer_tag_load} = 1; } else { require JSON; $param->{tags_js} = JSON::objToJson( MT::Tag->cache( blog_id => $blog_id, class => 'MT::Asset', private => 1 ) ); } } $app->load_tmpl( 'dialog/asset_options.tmpl', $param ); } sub _make_string_csv { my $app = shift; my ( $value, $enc ) = @_; $value =~ s/\r|\r\n/\n/gs; if ( ( ( index( $value, '"' ) > -1 ) || ( index( $value, '\n' ) > -1 ) ) && !( $value =~ m/^".*"$/gs ) ) { $value = "\"$value\""; } return encode_text( $value, undef, $enc ); } sub upload_file { my $app = shift; my ($asset, $bytes) = $app->_upload_file( require_type => ($app->param('require_type') || ''), @_, ); return if !defined $asset; return $asset if !defined $bytes; # whatever it is $app->complete_insert( asset => $asset, bytes => $bytes, ); } sub upload_userpic { my $app = shift; my ($asset, $bytes) = $app->_upload_file( @_, require_type => 'image', ); return if !defined $asset; return $asset if !defined $bytes; # whatever it is ## TODO: should this be layered into _upload_file somehow, so we don't ## save the asset twice? $asset->tags('@userpic'); $asset->save; my $user_id = $app->param('user_id'); $app->forward( 'asset_userpic', { asset => $asset, user_id => $user_id } ); } sub _upload_file { my $app = shift; my (%upload_param) = @_; if (my $perms = $app->permissions) { return $app->error( $app->translate("Permission denied.") ) unless $perms->can_upload; } $app->validate_magic() or return; my $q = $app->param; my ($fh, $info) = $app->upload_info('file'); my $mimetype; if ($info) { $mimetype = $info->{'Content-Type'}; } my $has_overwrite = $q->param('overwrite_yes') || $q->param('overwrite_no'); my %param = ( entry_insert => $q->param('entry_insert'), middle_path => $q->param('middle_path'), edit_field => $q->param('edit_field'), site_path => $q->param('site_path'), extra_path => $q->param('extra_path'), upload_mode => $app->mode, ); return $app->start_upload( %param, error => $app->translate("Please select a file to upload.") ) if !$fh && !$has_overwrite; my $basename = $q->param('file') || $q->param('fname'); $basename =~ s!\\!/!g; ## Change backslashes to forward slashes $basename =~ s!^.*/!!; ## Get rid of full directory paths if ( $basename =~ m!\.\.|\0|\|! ) { return $app->start_upload( %param, error => $app->translate( "Invalid filename '[_1]'", $basename ) ); } if (my $asset_type = $upload_param{require_type}) { require MT::Asset; my $asset_pkg = MT::Asset->handler_for_file($basename); my %settings_for = ( audio => { class => 'MT::Asset::Audio', error => $app->translate( "Please select an audio file to upload." ), }, image => { class => 'MT::Asset::Image', error => $app->translate( "Please select an image to upload." ), }, video => { class => 'MT::Asset::Video', error => $app->translate( "Please select a video to upload." ), }, ); if (my $settings = $settings_for{$asset_type}) { return $app->start_upload( %param, error => $settings->{error} ) if !$asset_pkg->isa($settings->{class}); } } my ($blog_id, $blog, $fmgr, $local_file, $asset_file, $base_url, $asset_base_url, $relative_url, $relative_path); if ($blog_id = $q->param('blog_id')) { $param{blog_id} = $blog_id; require MT::Blog; $blog = MT::Blog->load($blog_id); $fmgr = $blog->file_mgr; ## Set up the full path to the local file; this path could start ## at either the Local Site Path or Local Archive Path, and could ## include an extra directory or two in the middle. my ( $root_path, $middle_path ); if ( $q->param('site_path') ) { $root_path = $blog->site_path; } else { $root_path = $blog->archive_path; } return $app->error( $app->translate( "Before you can upload a file, you need to publish your blog." ) ) unless -d $root_path; $relative_path = $q->param('extra_path'); $middle_path = $q->param('middle_path') || ''; my $relative_path_save = $relative_path; if ( $middle_path ne '' ) { $relative_path = $middle_path . ( $relative_path ? '/' . $relative_path : '' ); } my $path = $root_path; if ($relative_path) { if ( $relative_path =~ m!\.\.|\0|\|! ) { return $app->start_upload( %param, error => $app->translate( "Invalid extra path '[_1]'", $relative_path ) ); } $path = File::Spec->catdir( $path, $relative_path ); ## Untaint. We already checked for security holes in $relative_path. ($path) = $path =~ /(.+)/s; ## Build out the directory structure if it doesn't exist. DirUmask ## determines the permissions of the new directories. unless ( $fmgr->exists($path) ) { $fmgr->mkpath($path) or return $app->start_upload( %param, error => $app->translate( "Can't make path '[_1]': [_2]", $path, $fmgr->errstr ) ); } } $relative_url = File::Spec->catfile( $relative_path, encode_url($basename) ); $relative_path = $relative_path ? File::Spec->catfile( $relative_path, $basename ) : $basename; $asset_file = $q->param('site_path') ? '%r' : '%a'; $asset_file = File::Spec->catfile( $asset_file, $relative_path ); $local_file = File::Spec->catfile( $path, $basename ); $base_url = $app->param('site_path') ? $blog->site_url : $blog->archive_url; $asset_base_url = $app->param('site_path') ? '%r' : '%a'; ## Untaint. We have already tested $basename and $relative_path for security ## issues above, and we have to assume that we can trust the user's ## Local Archive Path setting. So we should be safe. ($local_file) = $local_file =~ /(.+)/s; ## If $local_file already exists, we try to write the upload to a ## tempfile, then ask for confirmation of the upload. if ( $fmgr->exists($local_file) ) { if ($has_overwrite) { my $tmp = $q->param('temp'); if ( $tmp =~ m!([^/]+)$! ) { $tmp = $1; } else { return $app->error( $app->translate( "Invalid temp file name '[_1]'", $tmp ) ); } my $tmp_dir = $app->config('TempDir'); my $tmp_file = File::Spec->catfile( $tmp_dir, $tmp ); if ( $q->param('overwrite_yes') ) { $fh = gensym(); open $fh, $tmp_file or return $app->error( $app->translate( "Error opening '[_1]': [_2]", $tmp_file, "$!" ) ); } else { if ( -e $tmp_file ) { unlink($tmp_file) or return $app->error( $app->translate( "Error deleting '[_1]': [_2]", $tmp_file, "$!" ) ); } return $app->start_upload; } } else { eval { require File::Temp }; if ($@) { return $app->error( $app->translate( "File with name '[_1]' already exists. (Install " . "File::Temp if you'd like to be able to overwrite " . "existing uploaded files.)", $basename ) ); } my $tmp_dir = $app->config('TempDir'); my ( $tmp_fh, $tmp_file ); eval { ( $tmp_fh, $tmp_file ) = File::Temp::tempfile( DIR => $tmp_dir ); }; if ($@) { #!$tmp_fh return $app->errtrans( "Error creating temporary file; please check your TempDir " . "setting in your coniguration file (currently '[_1]') " . "this location should be writable.", ( $tmp_dir ? $tmp_dir : '[' . $app->translate('unassigned') . ']' ) ); } defined( _write_upload( $fh, $tmp_fh ) ) or return $app->error( $app->translate( "File with name '[_1]' already exists; Tried to write " . "to tempfile, but open failed: [_2]", $basename, "$!" ) ); close $tmp_fh; my ( $vol, $path, $tmp ) = File::Spec->splitpath($tmp_file); return $app->load_tmpl( 'dialog/asset_replace.tmpl', { temp => $tmp, extra_path => $relative_path_save, site_path => scalar $q->param('site_path'), asset_select => $q->param('asset_select'), entry_insert => $q->param('entry_insert'), edit_field => $app->param('edit_field'), middle_path => $middle_path, fname => $basename } ); } } } else { $blog_id = 0; $asset_base_url = '%s/support/uploads'; $base_url = $app->static_path . 'support/uploads'; $param{support_path} = File::Spec->catdir( $app->static_file_path, 'support', 'uploads' ); require MT::FileMgr; $fmgr = MT::FileMgr->new('Local'); unless ( $fmgr->exists( $param{support_path} ) ) { $fmgr->mkpath( $param{support_path} ); unless ( $fmgr->exists( $param{support_path} ) ) { return $app->error( $app->translate( "Could not create upload path '[_1]': [_2]", $param{support_path}, $fmgr->errstr ) ); } } require File::Basename; my ($stem, undef, $type) = File::Basename::fileparse( $basename, qr/\.[A-Za-z0-9]+$/ ); my $unique_stem = $stem; $local_file = File::Spec->catfile( $param{support_path}, $unique_stem . $type ); my $i = 1; while ($fmgr->exists($local_file)) { $unique_stem = join q{-}, $stem, $i++; $local_file = File::Spec->catfile( $param{support_path}, $unique_stem . $type ); } my $unique_basename = $unique_stem . $type; $relative_path = $unique_basename; $relative_url = encode_url($unique_basename); $asset_file = File::Spec->catfile( '%s', 'support', 'uploads', $unique_basename ); } require MT::Image; my ($w, $h, $id, $write_file) = MT::Image->check_upload( Fh => $fh, Fmgr => $fmgr, Local => $local_file, Max => $upload_param{max_size}, MaxDim => $upload_param{max_image_dimension} ); return $app->error(MT::Image->errstr) unless $write_file; ## File does not exist, or else we have confirmed that we can overwrite. my $umask = oct $app->config('UploadUmask'); my $old = umask($umask); defined( my $bytes = $write_file->() ) or return $app->error( $app->translate( "Error writing upload to '[_1]': [_2]", $local_file, $fmgr->errstr ) ); umask($old); ## Close up the filehandle. close $fh; ## If we are overwriting the file, that means we still have a temp file ## lying around. Delete it. if ( $q->param('overwrite_yes') ) { my $tmp = $q->param('temp'); if ( $tmp =~ m!([^/]+)$! ) { $tmp = $1; } else { return $app->error( $app->translate( "Invalid temp file name '[_1]'", $tmp ) ); } my $tmp_file = File::Spec->catfile( $app->config('TempDir'), $tmp ); unlink($tmp_file) or return $app->error( $app->translate( "Error deleting '[_1]': [_2]", $tmp_file, "$!" ) ); } ## We are going to use $relative_path as the filename and as the url passed ## in to the templates. So, we want to replace all of the '\' characters ## with '/' characters so that it won't look like backslashed characters. ## Also, get rid of a slash at the front, if present. $relative_path =~ s!\\!/!g; $relative_path =~ s!^/!!; $relative_url =~ s!\\!/!g; $relative_url =~ s!^/!!; my $url = $base_url; $url .= '/' unless $url =~ m!/$!; $url .= $relative_url; my $asset_url = $asset_base_url . '/' . $relative_url; require File::Basename; my $local_basename = File::Basename::basename($local_file); my $ext = ( File::Basename::fileparse( $local_file, qr/[A-Za-z0-9]+$/ ) )[2]; require MT::Asset; my $asset_pkg = MT::Asset->handler_for_file($local_basename); my $is_image = defined($w) && defined($h) && $asset_pkg->isa('MT::Asset::Image'); my $asset; if ( !( $asset = $asset_pkg->load( { file_path => $asset_file, blog_id => $blog_id } ) ) ) { $asset = $asset_pkg->new(); $asset->file_path($asset_file); $asset->file_name($local_basename); $asset->file_ext($ext); $asset->blog_id($blog_id); $asset->created_by( $app->user->id ); } else { $asset->modified_by( $app->user->id ); } my $original = $asset->clone; $asset->url($asset_url); if ($is_image) { $asset->image_width($w); $asset->image_height($h); } $asset->mime_type($mimetype) if $mimetype; $asset->save; $app->run_callbacks( 'cms_post_save.asset', $app, $asset, $original ); if ($is_image) { $app->run_callbacks( 'cms_upload_file.' . $asset->class, File => $local_file, file => $local_file, Url => $url, url => $url, Size => $bytes, size => $bytes, Asset => $asset, asset => $asset, Type => 'image', type => 'image', Blog => $blog, blog => $blog ); $app->run_callbacks( 'cms_upload_image', File => $local_file, file => $local_file, Url => $url, url => $url, Size => $bytes, size => $bytes, Asset => $asset, asset => $asset, Height => $h, height => $h, Width => $w, width => $w, Type => 'image', type => 'image', ImageType => $id, image_type => $id, Blog => $blog, blog => $blog ); } else { $app->run_callbacks( 'cms_upload_file.' . $asset->class, File => $local_file, file => $local_file, Url => $url, url => $url, Size => $bytes, size => $bytes, Asset => $asset, asset => $asset, Type => 'file', type => 'file', Blog => $blog, blog => $blog ); } return ($asset, $bytes); } sub _write_upload { my ( $upload_fh, $dest_fh ) = @_; my $fh = gensym(); if ( ref($dest_fh) eq 'GLOB' ) { $fh = $dest_fh; } else { open $fh, ">$dest_fh" or return; } binmode $fh; binmode $upload_fh; my ( $bytes, $data ) = (0); while ( my $len = read $upload_fh, $data, 8192 ) { print $fh $data; $bytes += $len; } close $fh; $bytes; } sub remove_userpic { my $app = shift; $app->validate_magic() or return; my $q = $app->param; my $user_id = $q->param('user_id'); my $user = $app->model('author')->load( { id => $user_id } ) or return; if ($user->userpic_asset_id) { my $old_file = $user->userpic_file(); my $fmgr = MT::FileMgr->new('Local'); if ($fmgr->exists($old_file)) { $fmgr->delete($old_file); } $user->userpic_asset_id(0); $user->save; } return 'success'; } sub search_replace { my $app = shift; my $param = $app->do_search_replace(@_) or return; my $blog_id = $app->param('blog_id'); $app->add_breadcrumb( $app->translate('Search & Replace') ); $param->{nav_search} = 1; $param->{screen_class} = "search-replace"; $param->{screen_id} = "search-replace"; $param->{search_tabs} = $app->search_apis($blog_id ? 'blog' : 'system'); $param->{entry_type} = $app->param('entry_type'); my $tmpl = $app->load_tmpl( 'search_replace.tmpl', $param ); my $placeholder = $tmpl->getElementById('search_results'); $placeholder->innerHTML(delete $param->{results_template}); return $tmpl; } sub core_search_apis { my $app = shift; my $q = $app->param; my $blog_id = $q->param('blog_id'); my $author = $app->user; my @perms; if ( !$blog_id ) { if ( !$author->is_superuser() ) { require MT::Permission; @perms = MT::Permission->load( { author_id => $author->id } ); } } else { @perms = ( $app->permissions ) or return $app->error( $app->translate("No permissions") ); } return { 'entry' => { 'order' => 100, 'permission' => 'create_post,publish_post,edit_all_posts', 'label' => 'Entries', 'perm_check' => sub { grep { $_->can_edit_entry( $_[0], $author ) } @perms; }, 'search_cols' => { 'title' => sub { $app->translate('Title') }, 'text' => sub { $app->translate('Entry Body') }, 'text_more' => sub { $app->translate('Extended Entry') }, 'keywords' => sub { $app->translate('Keywords') }, 'excerpt' => sub { $app->translate('Excerpt') }, 'basename' => sub { $app->translate('Basename') }, }, 'replace_cols' => [qw(title text text_more keywords excerpt)], 'can_replace' => 1, 'can_search_by_date' => 1, 'date_column' => 'authored_on', }, 'comment' => { 'order' => 200, 'permission' => 'publish_post,create_post,edit_all_posts,manage_feedback', 'label' => 'Comments', 'perm_check' => sub { require MT::Entry; my $entry = MT::Entry->load( $_[0]->entry_id ); grep { $_->can_edit_entry( $entry, $author ) } @perms; }, 'search_cols' => { 'url' => sub { $app->translate('URL') }, 'text' => sub { $app->translate('Comment Text') }, 'email' => sub { $app->translate('Email Address') }, 'ip' => sub { $app->translate('IP Address') }, 'author' => sub { $app->translate('Name') }, }, 'replace_cols' => [qw(text)], 'can_replace' => 1, 'can_search_by_date' => 1, }, 'ping' => { 'order' => 300, 'permission' => 'create_post,publish_post,edit_all_posts,manage_feedback', 'label' => 'TrackBacks', 'perm_check' => sub { my $ping = shift; my $tb = MT::Trackback->load( $ping->tb_id ); if ( $tb->entry_id ) { require MT::Entry; my $entry = MT::Entry->load( $tb->entry_id ); return grep { $_->can_edit_entry( $entry, $author ) } @perms; } elsif ( $tb->category_id ) { return grep { $_->can_edit_categories } @perms; } }, 'search_cols' => { 'title' => sub { $app->translate('Title') }, 'excerpt' => sub { $app->translate('Excerpt') }, 'source_url' => sub { $app->translate('Source URL') }, 'ip' => sub { $app->translate('IP Address') }, 'blog_name' => sub { $app->translate('Blog Name') }, }, 'replace_cols' => [qw(title excerpt)], 'can_replace' => 1, 'can_search_by_date' => 1, }, 'page' => { 'order' => 400, 'permission' => 'manage_pages', 'label' => 'Pages', 'perm_check' => sub { grep { $_->can_manage_pages( $_[0], $author ) } @perms; }, 'search_cols' => { 'title' => sub { $app->translate('Title') }, 'text' => sub { $app->translate('Page Body') }, 'text_more' => sub { $app->translate('Extended Page') }, 'keywords' => sub { $app->translate('Keywords') }, 'excerpt' => sub { $app->translate('Excerpt') }, 'basename' => sub { $app->translate('Basename') }, }, 'replace_cols' => [qw(title text text_more keywords excerpt)], 'can_replace' => 1, 'can_search_by_date' => 1, 'date_column' => 'authored_on', 'results_table_template' => '<mt:include name="include/entry_table.tmpl">', }, 'template' => { 'order' => 500, 'permission' => 'edit_templates', 'label' => 'Templates', 'perm_check' => sub { my ($obj) = @_; # are there any perms that match this object and # allow template editing? my @check = grep { $_->blog_id == $obj->blog_id && $_->can_edit_templates } @perms; return @check; }, 'search_cols' => { 'name' => sub { $app->translate('Template Name') }, 'text' => sub { $app->translate('Text') }, 'linked_file' => sub { $app->translate('Linked Filename') }, 'outfile' => sub { $app->translate('Output Filename') }, }, 'replace_cols' => [qw(name text linked_file outfile)], 'can_replace' => 1, 'can_search_by_date' => 0, }, 'asset' => { 'order' => 600, 'permission' => 'manage_assets', 'label' => 'Assets', 'perm_check' => sub { 1; }, 'search_cols' => { 'file_name' => sub { $app->translate('Filename') }, 'description' => sub { $app->translate('Description') }, 'label' => sub { $app->translate('Label') }, }, 'replace_cols' => [], 'can_replace' => 0, 'can_search_by_date' => 1, 'setup_terms_args' => sub { my ($terms, $args, $blog_id) = @_; $terms->{class} = '*'; $terms->{blog_id} = $blog_id if $blog_id; } }, 'log' => { 'order' => 700, 'permission' => "view_blog_log", 'system_permission' => "view_log", 'label' => 'Activity Log', 'perm_check' => sub { my ($obj) = @_; return 1 if $author->can_view_log; my $perm = $author->permissions( $obj->blog_id ); return $perm->can_view_blog_log; }, 'search_cols' => { 'ip' => sub { $app->translate('Log Message') }, 'message' => sub { $app->translate('IP Address') }, }, 'can_replace' => 0, 'can_search_by_date' => 1, 'setup_terms_args' => sub { my ($terms, $args, $blog_id) = @_; $terms->{class} = '*'; $terms->{blog_id} = $blog_id if $blog_id; } }, 'author' => { 'order' => 800, 'system_permission' => 'administer', 'label' => 'Users', 'perm_check' => sub { return 1 if $author->is_superuser; if ($blog_id) { my $perm = $author->permissions($blog_id); return $perm->can_administer_blog; } return 0; }, 'search_cols' => { 'name' => sub { $app->translate('Username') }, 'nickname' => sub { $app->translate('Display Name') }, 'email' => sub { $app->translate('Email Address') }, 'url' => sub { $app->translate('URL') }, }, 'can_replace' => 0, 'can_search_by_date' => 0, 'setup_terms_args' => sub { my ($terms, $args, $blog_id) = @_; if ($blog_id) { $args->{'join'} = MT::Permission->join_on( 'author_id', { blog_id => $blog_id } ); } else { $terms->{'type'} = MT::Author::AUTHOR(); } }, 'results_table_template' => ' <mt:if name="blog_id"> <mt:include name="include/member_table.tmpl"> <mt:else> <mt:include name="include/author_table.tmpl"> </mt:if>', }, 'blog' => { 'order' => 900, 'system_permission' => 'administer', 'label' => 'Blogs', 'perm_check' => sub { return 1 if $author->is_superuser; my ($obj) = @_; my $perm = $author->permissions( $obj->id ); $perm && ( $perm->can_administer_blog || $perm->can_edit_config ); }, 'search_cols' => { 'name' => sub { $app->translate('Name') }, 'site_url' => sub { $app->translate('Site URL') }, 'site_path' => sub { $app->translate('Site Root') }, 'description' => sub { $app->translate('Description') }, }, 'replace_cols' => [qw(name site_url site_path description)], 'can_replace' => $author->is_superuser(), 'can_search_by_date' => 0, 'view' => 'system', 'setup_terms_args' => sub { my ($terms, $args, $blog_id) = @_; $args->{sort} = 'name'; $args->{direction} = 'ascend'; } } }; } sub _default_results_table_template { my $app = shift; my ($type, $results, $plural) = @_; if ($results) { return "<mt:include name=\"include/${type}_table.tmpl\">"; } else { return <<TMPL; <mtapp:statusmsg id="no-$plural" class="info"> <__trans phrase="No [_1] were found that match the given criteria." params="$plural"> </mtapp:statusmsg> </mt:if> TMPL } } sub do_search_replace { my $app = shift; my $q = $app->param; my $blog_id = $q->param('blog_id'); my $author = $app->user; my $search_api = $app->registry("search_apis"); my ( $search, $replace, $do_replace, $case, $is_regex, $is_limited, $type, $is_junk, $is_dateranged, $ids, $datefrom_year, $datefrom_month, $datefrom_day, $dateto_year, $dateto_month, $dateto_day, $from, $to, $show_all, $do_search, $orig_search, $quicksearch ) = map scalar $q->param($_), qw( search replace do_replace case is_regex is_limited _type is_junk is_dateranged replace_ids datefrom_year datefrom_month datefrom_day dateto_year dateto_month dateto_day from to show_all do_search orig_search quicksearch ); if ( !$type || ( 'category' eq $type ) || ( 'folder' eq $type ) ) { $type = 'entry'; } foreach my $obj_type (qw( role association )) { if ( $type eq $obj_type ) { $type = 'author'; } } $replace && ( $app->validate_magic() or return ); $search = $orig_search if $do_replace; # for safety's sake my $list_pref = $app->list_pref($type); $app->assert( $search_api->{$type}, "Invalid request." ) or return; # force action bars to top and bottom $list_pref->{"bar"} = 'both'; $list_pref->{"position_actions_both"} = 1; $list_pref->{"position_actions_top"} = 1; $list_pref->{"position_actions_bottom"} = 1; $list_pref->{"view"} = 'compact'; $list_pref->{"view_compact"} = 1; my ( @cols, $datefrom, $dateto, $date_col ); $do_replace = 0 unless $search_api->{$type}{can_replace}; $is_dateranged = 0 unless $search_api->{$type}{can_search_by_date}; my @ids; if ($ids) { @ids = split /,/, $ids; } if ($is_limited) { @cols = $q->param('search_cols'); my %search_api_cols = map { $_ => 1 } keys %{ $search_api->{$type}{search_cols} }; if ( @cols && ( $cols[0] =~ /,/ ) ) { @cols = split /,/, $cols[0]; } @cols = grep { $search_api_cols{$_} } @cols; } else { @cols = keys %{ $search_api->{$type}->{search_cols} }; } my $quicksearch_id; if ($quicksearch && ($search || '') ne '' && $search !~ m{ \D }xms) { $quicksearch_id = $search; unshift @cols, 'id'; } foreach ( $datefrom_year, $datefrom_month, $datefrom_day, $dateto_year, $dateto_month, $dateto_day ) { s!\D!!g if $_; } if ($is_dateranged) { $datefrom = sprintf( "%04d%02d%02d", $datefrom_year, $datefrom_month, $datefrom_day ); $dateto = sprintf( "%04d%02d%02d", $dateto_year, $dateto_month, $dateto_day ); if ( ( $datefrom eq '00000000' ) && ( $dateto eq '00000000' ) ) { $is_dateranged = 0; } else { if ( !is_valid_date( $datefrom . '000000' ) || !is_valid_date( $dateto . '000000' ) ) { return $app->error( $app->translate( "Invalid date(s) specified for date range.") ); } } } elsif ( $from && $to ) { $is_dateranged = 1; s!\D!!g foreach ( $from, $to ); $datefrom = substr( $from, 0, 8 ); $dateto = substr( $to, 0, 8 ); } my $tab = $q->param('tab') || 'entry'; ## Sometimes we need to pass in the search columns like 'title,text', so ## we look for a comma (not a valid character in a column name) and split ## on it if it's there. if ( ($search || '') ne '' ) { my $enc = $app->charset; $search = MT::I18N::encode_text( $search, 'utf-8', $enc ) if ( $enc !~ m/utf-?8/i ) && ( 'dialog_grant_role' eq $app->param('__mode') ); $search = quotemeta($search) unless $is_regex; $search = '(?i)' . $search unless $case; } my ( @to_save, @data ); my $api = $search_api->{$type}; my $class = $app->model($api->{object_type} || $type); my %param = %$list_pref; my $limit = $q->param('limit') || 125; # FIXME: mt.cfg setting? my $matches; $date_col = $api->{date_column} || 'created_on'; if ( ( $do_search && $search ne '' ) || $show_all || $do_replace ) { my %terms; my %args; ## we need to search all user/group for 'grant permissions', ## if $blog_id is specified. it affects the setup_terms_args. if ( $app->param('__mode') eq 'dialog_grant_role' ) { $blog_id = 0; } if (exists $api->{setup_terms_args}) { my $code = $app->handler_to_coderef($api->{setup_terms_args}); $code->(\%terms, \%args, $blog_id); } else { %terms = $blog_id ? ( blog_id => $blog_id ) : (); if ( $type ne 'template' ) { %args = ( 'sort' => $date_col, direction => 'descend' ); } } if ( $class->has_column('junk_status') ) { if ($is_junk) { $terms{junk_status} = -1; } else { $terms{junk_status} = [ 0, 1 ]; } } if ($is_dateranged) { $args{range_incl}{$date_col} = 1; if ( $datefrom gt $dateto ) { $terms{$date_col} = [ $dateto . '000000', $datefrom . '235959' ]; } else { $terms{$date_col} = [ $datefrom . '000000', $dateto . '235959' ]; } } my $iter; if ($do_replace) { $iter = sub { if ( my $id = pop @ids ) { $class->load($id); } }; } if ( $blog_id || ($type eq 'blog') ) { $iter = $class->load_iter( \%terms, \%args ) or die $class->errstr; } else { my @streams; if ( $author->is_superuser ) { @streams = ( { iter => $class->load_iter( \%terms, \%args ) } ); } else { # Get an iter for each accessible blog my @perms = $app->model('permission')->load( { blog_id => '0', author_id => $author->id }, { not => { blog_id => 1 } }, ); if (@perms) { @streams = map { { iter => $class->load_iter( { blog_id => $_->blog_id, %terms }, \%args ) } } @perms; } } # Pull out the head of each iterator # Next: effectively mergesort the various iterators # To call the iterator n times takes time in O(bn) # with 'b' the number of blogs # we expect to hit the iterator l/p times where 'p' is the # prob. of the search term appearing and 'l' is $limit $_->{head} = $_->{iter}->() foreach @streams; if ( $type ne 'template' ) { $iter = sub { # find the head with greatest created_on my $which = \$streams[0]; foreach my $iter (@streams) { next if !exists $iter->{head} || !$which || !${$which}->{head} || !defined( $iter->{head} ); if ( $iter->{head}->created_on > ${$which}->{head}->created_on ) { $which = \$iter; } } # Advance the chosen one my $result = ${$which}->{head}; ${$which}->{head} = ${$which}->{iter}->() if $result; $result; }; } else { $iter = sub { return undef unless @streams; # find the head with greatest created_on my $which = \$streams[0]; while ( @streams && ( !defined ${$which}->{head} ) ) { shift @streams; last unless @streams; $which = \$streams[0]; } my $result = ${$which}->{head}; ${$which}->{head} = ${$which}->{iter}->() if $result; $result; }; } } my $i = 1; my %replace_cols; if ($do_replace) { %replace_cols = map { $_ => 1 } @{ $api->{replace_cols} }; } my $re; if (($search || '') ne '') { $re = eval { qr/$search/ }; if ( my $err = $@ ) { return $app->error( $app->translate( "Error in search expression: [_1]", $@ ) ); } } while ( my $obj = $iter->() ) { next unless $author->is_superuser || $api->{perm_check}->($obj); my $match = 0; unless ($show_all) { for my $col (@cols) { next if $do_replace && !$replace_cols{$col}; my $text = $obj->column($col); $text = '' unless defined $text; if ($do_replace) { if ( $text =~ s!$re!$replace!g ) { $match++; $obj->$col($text); } } else { $match = $search ne '' ? $text =~ m!$re! : 1; last if $match; } } } if ( $match || $show_all ) { push @to_save, $obj if $do_replace && !$show_all; push @data, $obj; } last if ( $limit ne 'all' ) && @data > $limit; } if (@data) { $param{have_results} = 1; # We got one extra to see if there were more if ( ( $limit ne 'all' ) && @data > $limit ) { $param{have_more} = 1; pop @data; } $matches = @data; } else { $matches = 0; } } my $replace_count = 0; for my $obj (@to_save) { $replace_count++; $obj->save or return $app->error( $app->translate( "Saving object failed: [_2]", $obj->errstr ) ); } if (@data) { if ($quicksearch) { my $obj; if (1 == scalar @data) { ($obj) = @data; } elsif (defined $quicksearch_id) { ($obj) = grep { $_->id == $quicksearch_id } @data; } if ($obj) { my %args = ( _type => $type, id => $obj->id, search_result => 1, ); $args{blog_id} = $obj->blog_id if $obj->has_column('blog_id'); return $app->redirect($app->uri( mode => 'view', args => \%args, )); } } if (my $meth = $search_api->{$type}{handler}) { $meth = $app->handler_to_coderef($meth); $meth->($app, items => \@data, param => \%param, type => $type ); } else { my $meth = 'build_' . $type . '_table'; if ( $app->can($meth) ) { $app->$meth( items => \@data, param => \%param, type => $type ); } else { my @objects; push @objects, { object => $_ } for @data; $param{object_loop} = \@objects; } } $param{object_type} = $type; if ( exists $api->{results_table_template} ) { $param{results_template} = $api->{results_table_template}; } else { $param{results_template} = $app->_default_results_table_template($type, 1, $class->class_label_plural); } } else { if ( exists $api->{no_results_template} ) { $param{results_template} = $api->{no_results_template}; } else { $param{results_template} = $app->_default_results_table_template($type, 0, $class->class_label_plural); } } if ($is_dateranged) { ( $datefrom_year, $datefrom_month, $datefrom_day ) = $datefrom =~ m/^(\d\d\d\d)(\d\d)(\d\d)/; ( $dateto_year, $dateto_month, $dateto_day ) = $dateto =~ m/^(\d\d\d\d)(\d\d)(\d\d)/; } my %res = ( error => $q->param('error') || '', limit => $limit, limit_all => $limit eq 'all', count_matches => $matches, replace_count => $replace_count, "search_$type" => 1, search_label => $class->class_label_plural, object_label_plural => $class->class_label_plural, object_type => $type, search => ($do_replace ? $q->param('orig_search') : $q->param('search')) || '', searched => ( $do_replace ? $q->param('orig_search') : ( $do_search && $q->param('search') ne '' ) ) || $show_all, replace => $replace, do_replace => $do_replace, case => $case, datefrom_year => $datefrom_year, datefrom_month => $datefrom_month, datefrom_day => $datefrom_day, dateto_year => $dateto_year, dateto_month => $dateto_month, dateto_day => $dateto_day, is_regex => $is_regex, is_limited => $is_limited, is_dateranged => $is_dateranged, is_junk => $is_junk, can_search_junk => ( $type eq 'comment' || $type eq 'ping' ), can_replace => $search_api->{$type}{can_replace}, can_search_by_date => $search_api->{$type}{can_search_by_date}, quick_search => 0, "tab_$tab" => 1, %param ); $res{'tab_junk'} = 1 if $is_junk; my $search_cols = $search_api->{$type}{search_cols}; my %cols = map { $_ => 1 } @cols; my @search_cols; for my $field (keys %$search_cols) { my %search_field; $search_field{field} = $field; $search_field{selected} = 1 if exists($cols{$field}); $search_field{label} = 'CODE' eq ref($search_cols->{$field}) ? $search_cols->{$field}->() : $app->translate($search_cols->{$field}); push @search_cols, \%search_field; } $res{'search_cols'} = \@search_cols; \%res; } sub start_export { my $app = shift; my %param; my $blog_id = $app->param('blog_id'); my $perms = $app->permissions; return $app->return_to_dashboard( permission => 1 ) if ( $perms && !$perms->can_administer_blog ); $param{blog_id} = $blog_id; $app->load_tmpl( 'export.tmpl', \%param ); } sub export { my $app = shift; $app->{no_print_body} = 1; local $| = 1; my $charset = $app->charset; require MT::Blog; my $blog_id = $app->param('blog_id') or return $app->error( $app->translate("Please select a blog.") ); my $blog = MT::Blog->load($blog_id) or return $app->error( $app->translate( "Load of blog '[_1]' failed: [_2]", $blog_id, MT::Blog->errstr ) ); my $perms = $app->permissions; return $app->error( $app->translate("You do not have export permissions") ) unless $perms && $perms->can_edit_config; $app->validate_magic() or return; my $file = dirify( $blog->name ) . ".txt"; if ( $file eq ".txt" ) { my @ts = localtime(time); $file = sprintf "export-%06d-%04d%02d%02d%02d%02d%02d.txt", $app->param('blog_id'), $ts[5] + 1900, $ts[4] + 1, @ts[ 3, 2, 1, 0 ]; } $app->set_header( "Content-Disposition" => "attachment; filename=$file" ); $app->send_http_header( $charset ? "text/plain; charset=$charset" : 'text/plain' ); require MT::ImportExport; MT::ImportExport->export( $blog, sub { $app->print(@_) } ) or return $app->error( MT::ImportExport->errstr ); 1; } sub do_import { my $app = shift; my $q = $app->param; require MT::Blog; my $blog_id = $q->param('blog_id') or return $app->error( $app->translate("Please select a blog.") ); my $blog = MT::Blog->load($blog_id) or return $app->error( $app->translate( "Load of blog '[_1]' failed: [_2]", $blog_id, MT::Blog->errstr ) ); if ( 'POST' ne $app->request_method ) { return $app->redirect( $app->uri( 'mode' => 'start_import', args => { blog_id => $blog_id } ) ); } my $import_as_me = $q->param('import_as_me'); ## Determine the user as whom we will import the entries. my $author = $app->user; my $author_id = $author->id; if ( !$author->is_superuser ) { my $perms = $author->permissions($blog_id); return $app->error( $app->translate("You do not have import permissions") ) unless $perms && ( $perms->can_edit_config || $perms->can_administer_blog ); if ( !$import_as_me ) { return $app->error( $app->translate("You do not have permission to create users") ) unless $perms->can_administer_blog; } } my ($pass); if ( !$import_as_me ) { $pass = $q->param('password') or return $app->error( $app->translate( "You need to provide a password if you are going to " . "create new users for each user listed in your blog." ) ) if ( MT::Auth->password_exists ); } $app->validate_magic() or return; my ($fh) = $app->upload_info('file'); my $encoding = $q->param('encoding'); my $stream = $fh ? $fh : $app->config('ImportPath'); $app->{no_print_body} = 1; local $| = 1; $app->send_http_header('text/html'); my $param; $param = { import_as_me => $import_as_me, import_upload => ($fh ? 1 : 0) }; $app->print( $app->build_page( 'include/import_start.tmpl', $param ) ); require MT::Entry; require MT::Placement; require MT::Category; require MT::Permission; require MT::Comment; require MT::TBPing; my $import_type = $q->param('import_type'); require MT::Import; my $imp = MT::Import->new; my $importer = $imp->importer($import_type); return $app->error( $app->translate( 'Importer type [_1] was not found.', $import_type ) ) unless $importer; my %options = map { $_ => $q->param($_); } @{ $importer->{options} } if $importer->{options}; my $import_result = $imp->import_contents( Key => $import_type, Blog => $blog, Stream => $stream, Callback => sub { $app->print(@_) }, Encoding => $encoding, ($import_as_me) ? ( ImportAs => $author ) : ( ParentAuthor => $author ), NewAuthorPassword => ( $q->param('password') || undef ), DefaultCategoryID => ( $q->param('default_cat_id') || undef ), ConvertBreaks => ( $q->param('convert_breaks') || undef ), (%options) ? (%options) : (), ); $param->{import_success} = $import_result; $param->{error} = $importer->{type}->errstr unless $import_result; $app->print( $app->build_page( "include/import_end.tmpl", $param ) ); close $fh if $fh; 1; } sub save_entry_prefs { my $app = shift; my $perms = $app->permissions or return $app->error( $app->translate("No permissions") ); $app->validate_magic() or return; my $q = $app->param; my $prefs = $app->_entry_prefs_from_params; $perms->entry_prefs($prefs); $perms->save or return $app->error( $app->translate( "Saving permissions failed: [_1]", $perms->errstr ) ); $app->send_http_header("text/json"); return "true"; } sub add_to_favorite_blogs { my $app = shift; my ($fav) = @_; my $auth = $app->user; my @current = @{ $auth->favorite_blogs || [] }; return if @current && ( $current[0] == $fav ); @current = grep { $_ != $fav } @current; unshift @current, $fav; @current = @current[ 0 .. 9 ] if @current > 10; $auth->favorite_blogs( \@current ); $auth->save; } sub save_favorite_blogs { my $app = shift; $app->validate_magic() or return; my $fav = $app->param('id'); return unless int($fav) > 0; $app->add_to_favorite_blogs($fav); $app->send_http_header("text/javascript+json"); return 'true'; } sub _entry_prefs_from_params { my $app = shift; my $q = $app->param; my $type = $q->param('entry_prefs'); my %fields; if ( $type && lc $type ne 'custom' ) { $fields{$type} = 1; } else { $fields{$_} = 1 foreach $q->param('custom_prefs'); } if ( my $body_height = $q->param('text_height') ) { $fields{'body'} = $body_height; } my $prefs = ''; foreach ( keys %fields ) { $prefs .= ',' if $prefs ne ''; $prefs .= $_; $prefs .= ':' . $fields{$_} if $fields{$_} > 1; } $prefs .= '|' . $q->param('bar_position'); $prefs; } sub _generate_map_table { my $app = shift; my ( $blog_id, $template_id ) = @_; require MT::Template; require MT::Blog; my $blog = MT::Blog->load($blog_id); my $template = MT::Template->load($template_id); my $tmpl = $app->load_tmpl('include/archive_maps.tmpl'); my $maps = $app->_populate_archive_loop( $blog, $template ); $tmpl->param( { object_type => 'templatemap' } ); $tmpl->param( { object_loop => $maps } ) if @$maps; my $html = $tmpl->output(); if ( $html =~ m/<__trans / ) { $html = $app->translate_templatized($html); } $html; } sub delete_map { my $app = shift; $app->validate_magic() or return; my $perms = $app->{perms} or return $app->error( $app->translate("No permissions") ); my $q = $app->param; my $id = $q->param('id'); require MT::TemplateMap; MT::TemplateMap->remove( { id => $id } ); my $html = $app->_generate_map_table( $q->param('blog_id'), $q->param('template_id') ); $app->{no_print_body} = 1; $app->send_http_header("text/plain"); $app->print($html); } sub add_map { my $app = shift; $app->validate_magic() or return; my $perms = $app->{perms} or return $app->error( $app->translate("No permissions") ); my $q = $app->param; require MT::TemplateMap; my $blog_id = $q->param('blog_id'); my $at = $q->param('new_archive_type'); my $count = MT::TemplateMap->count( { blog_id => $blog_id, archive_type => $at } ); my $map = MT::TemplateMap->new; $map->is_preferred( $count ? 0 : 1 ); $map->template_id( scalar $q->param('template_id') ); $map->blog_id($blog_id); $map->archive_type($at); $map->save or return $app->error( $app->translate( "Saving map failed: [_1]", $map->errstr ) ); my $html = $app->_generate_map_table( $blog_id, scalar $q->param('template_id') ); $app->rebuild( BlogID => $blog_id, ArchiveType => $at, TemplateMap => $map, TemplateID => scalar $q->param('template_id'), NoStatic => 1 ) or return $app->publish_error(); $app->{no_print_body} = 1; $app->send_http_header("text/plain"); $app->print($html); } sub pinged_urls { my $app = shift; my $perms = $app->permissions or return $app->error( $app->translate("No permissions") ); my %param; my $entry_id = $app->param('entry_id'); require MT::Entry; my $entry = MT::Entry->load($entry_id); $param{url_loop} = [ map { { url => $_ } } @{ $entry->pinged_url_list } ]; $param{failed_url_loop} = [ map { { url => $_ } } @{ $entry->pinged_url_list( OnlyFailures => 1 ) } ]; $app->load_tmpl( 'popup/pinged_urls.tmpl', \%param ); } sub reg_file { my $app = shift; my $q = $app->param; my $uri = $app->base . $app->uri( 'mode' => 'reg_bm_js', args => { bm_show => $q->param('bm_show'), bm_height => $q->param('bm_height') } ); $app->{no_print_body} = 1; $app->set_header( 'Content-Disposition' => 'attachment; filename=mt.reg' ); $app->send_http_header('text/plain; name=mt.reg'); $app->print( qq(REGEDIT4\r\n) . qq([HKEY_CURRENT_USER\\Software\\Microsoft\\Internet Explorer\\MenuExt\\QuickPost]\r\n) . qq(@="$uri"\r\n) . qq("contexts"=hex:31) ); 1; } sub reg_bm_js { my $app = shift; my $q = $app->param; my $js = $app->_bm_js( scalar $q->param('bm_show'), scalar $q->param('bm_height') ); $js =~ s!d=document!d=external.menuArguments.document!; $js =~ s!d\.location\.href!external.menuArguments.location.href!; $js =~ s!^javascript:!!; $js =~ s!\%20! !g; $app->{no_print_body} = 1; $app->send_http_header('text/plain'); $app->print( '<script language="javascript">' . $js . '</script>' ); 1; } sub category_add { my $app = shift; my $q = $app->param; my $type = $q->param('_type') || 'category'; my $pkg = $app->model($type); my $data = $app->_build_category_list( blog_id => scalar $q->param('blog_id'), type => $type ); my %param; $param{'category_loop'} = $data; $app->add_breadcrumb( $app->translate( 'Add a [_1]', $pkg->class_label ) ); $param{object_type} = $type; $param{object_label} = $pkg->class_label; $app->load_tmpl( 'popup/category_add.tmpl', \%param ); } sub category_do_add { my $app = shift; my $q = $app->param; my $type = $q->param('_type') || 'category'; my $author = $app->user; my $pkg = $app->model($type); $app->validate_magic() or return; my $name = $q->param('label') or return $app->error( $app->translate("No label") ); $name =~ s/(^\s+|\s+$)//g; return $app->errtrans("Category name cannot be blank.") if $name eq ''; my $parent = $q->param('parent') || '0'; my $cat = $pkg->new; my $original = $cat->clone; $cat->blog_id( scalar $q->param('blog_id') ); $cat->author_id( $app->user->id ); $cat->label($name); $cat->parent($parent); if ( !$author->is_superuser ) { $app->run_callbacks( 'cms_save_permission_filter.' . $type, $app, undef ) || return $app->error( $app->translate( "Permission denied: [_1]", $app->errstr() ) ); } my $filter_result = $app->run_callbacks( 'cms_save_filter.' . $type, $app ) || return; $app->run_callbacks( 'cms_pre_save.' . $type, $app, $cat, $original ) || return; $cat->save or return $app->error( $cat->errstr ); # Now post-process it. $app->run_callbacks( 'cms_post_save.' . $type, $app, $cat, $original ) or return; my $id = $cat->id; $name = encode_js($name); my %param = ( javascript => <<SCRIPT); o.doAddCategoryItem('$name', '$id'); SCRIPT $app->load_tmpl( 'reload_opener.tmpl', \%param ); } sub cc_return { my $app = shift; my $code = $app->param('license_code'); my $url = $app->param('license_url'); my $image = $app->param('license_button'); my %param = ( license_name => MT::Util::cc_name($code) ); if ($url) { $param{license_code} = "$code $url $image"; } else { $param{license_code} = $code; } $app->load_tmpl( 'cc_return.tmpl', \%param ); } sub reset_blog_templates { my $app = shift; my $q = $app->param; my $perms = $app->permissions or return $app->error( $app->translate("No permissions") ); return $app->error( $app->translate("Permission denied.") ) unless $perms->can_edit_templates; $app->validate_magic() or return; my $blog = MT::Blog->load( $perms->blog_id ); require MT::Template; my @tmpl = MT::Template->load( { blog_id => $blog->id } ); for my $tmpl (@tmpl) { $tmpl->remove or return $app->error( $tmpl->errstr ); } my $set = $blog ? $blog->template_set : undef; require MT::DefaultTemplates; my $tmpl_list = MT::DefaultTemplates->templates($set) || []; 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); $tmpl->blog_id( $blog->id ); $tmpl->save or return $app->error( $app->translate( "Populating blog with default templates failed: [_1]", $tmpl->errstr ) ); # FIXME: enumeration of types if ( $val->{type} eq 'archive' || $val->{type} eq 'category' || $val->{type} eq 'page' || $val->{type} eq 'individual' ) { push @arch_tmpl, $tmpl; } } ## Set up mappings from new templates to archive types. for my $tmpl (@arch_tmpl) { my (@at); # FIXME: enumeration of types if ( $tmpl->type eq 'archive' ) { @at = qw( Daily Weekly Monthly Category ); } elsif ( $tmpl->type eq 'page' ) { @at = qw( Page ); } 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->error( $app->translate( "Setting up mappings failed: [_1]", $map->errstr ) ); } } $app->redirect( $app->uri( 'mode' => 'list', args => { '_type' => 'template', blog_id => $blog->id, 'reset' => 1 } ) ); } sub update_dynamicity { my $app = shift; my ( $blog, $cache, $conditional ) = @_; my $dcty = $blog->custom_dynamic_templates; if ( $dcty eq 'none' ) { require MT::Template; my @templates = MT::Template->load( { blog_id => $blog->id } ); for my $tmpl (@templates) { $tmpl->build_dynamic(0); $tmpl->save(); } } elsif ( $dcty eq 'archives' ) { require MT::Template; my @templates = MT::Template->load( { blog_id => $blog->id } ); for my $tmpl (@templates) { $tmpl->build_dynamic( $tmpl->type ne 'index' || 0 ); $tmpl->save(); } } elsif ( $dcty eq 'custom' ) { } elsif ( $dcty eq 'all' ) { require MT::Template; my @templates = MT::Template->load( { blog_id => $blog->id, # FIXME: enumeration of types type => [ 'index', 'archive', 'individual', 'page', 'category' ], } ); for my $tmpl (@templates) { $tmpl->build_dynamic(1); $tmpl->save(); } } if ( $dcty ne 'none' ) { $app->prepare_dynamic_publishing(@_, $blog->site_path, $blog->site_url); if ( $blog->archive_path ) { $app->prepare_dynamic_publishing(@_, $blog->archive_path, $blog->archive_url); } my $compiled_template_path = File::Spec->catfile( $blog->site_path(), 'templates_c' ); if ( -d $compiled_template_path ) { $app->add_return_arg( 'no_writecache' => 1 ) unless ( -w $compiled_template_path ); } else { $app->add_return_arg( 'no_cachedir' => 1 ) unless ( -d $compiled_template_path ); } # FIXME: use FileMgr if ($cache) { my $cache_path = File::Spec->catfile( $blog->site_path(), 'cache' ); if ( -d $cache_path ) { $app->add_return_arg( 'no_write_cache_path' => 1 ) unless ( -w $cache_path ); } else { $app->add_return_arg( 'no_cache_path' => 1 ) unless ( -d $cache_path ); } } } $app->add_return_arg( dynamic_set => 1 ); } sub prepare_dynamic_publishing { my ( $cb, $blog, $cache, $conditional, $site_path, $site_url ) = @_; my $htaccess_path = File::Spec->catfile( $site_path, ".htaccess" ); my $mtview_path = File::Spec->catfile( $site_path, "mtview.php" ); ## Don't re-create when files are there in callback. return 1 if !defined($cache) && !defined($conditional) && ( 'MT::Callback' eq ref($cb) ) && ( -f $htaccess_path ) && ( -f $mtview_path ); return 1 if ( 'none' eq $blog->custom_dynamic_templates ); require File::Spec; # IIS itself does not handle .htaccess, # but IISPassword (3rd party) does and dies with this. if ( $ENV{SERVER_SOFTWARE} !~ /Microsoft-IIS/ ) { eval { require URI; my $mtview_server_url = new URI( $site_url ); $mtview_server_url = $mtview_server_url->path(); $mtview_server_url .= ( $mtview_server_url =~ m|/$| ? "" : "/" ) . "mtview.php"; my $contents = ""; if ( open( HT, $htaccess_path ) ) { local $/ = undef; $contents = <HT>; close HT; } if ( $contents !~ /^\s*Rewrite(Cond|Engine|Rule)\b/m ) { my $htaccess = <<HTACCESS; ## %%%%%%% Movable Type generated this part; don't remove this line! %%%%%%% # Disable fancy indexes, so mtview.php gets a chance... Options -Indexes +SymLinksIfOwnerMatch <IfModule mod_rewrite.c> # The mod_rewrite solution is the preferred way to invoke # dynamic pages, because of its flexibility. # Add mtview.php to the list of DirectoryIndex options, listing it last, # so it is invoked only if the common choices aren't present... <IfModule mod_dir.c> DirectoryIndex index.php index.html index.htm default.htm default.html default.asp $mtview_server_url </IfModule> RewriteEngine on # don't serve mtview.php if the request is for a real directory # (allows the DirectoryIndex lookup to function) RewriteCond %{REQUEST_FILENAME} !-d # don't serve mtview.php if the request is for a real file # (allows the actual file to be served) RewriteCond %{REQUEST_FILENAME} !-f # anything else is handed to mtview.php for resolution RewriteRule ^(.*)\$ $mtview_server_url [L,QSA] </IfModule> <IfModule !mod_rewrite.c> # if mod_rewrite is unavailable, we forward any missing page # or unresolved directory index requests to mtview # if mtview.php can resolve the request, it returns a 200 # result code which prevents any 4xx error code from going # to the server's access logs. However, an error will be # reported in the error log file. If this is your only choice, # and you want to suppress these messages, adding a "LogLevel crit" # directive within your VirtualHost or root configuration for # Apache will turn them off. ErrorDocument 404 $mtview_server_url ErrorDocument 403 $mtview_server_url </IfModule> ## ******* Movable Type generated this part; don't remove this line! ******* HTACCESS $blog->file_mgr->mkpath( $site_path ); open( HT, ">>$htaccess_path" ) || die "Couldn't open $htaccess_path for appending"; print HT $htaccess || die "Couldn't write to $htaccess_path"; close HT; } }; if ($@) { print STDERR $@; } } eval { my $mv_contents = ''; if ( -f $mtview_path ) { open( my $mv, "<$mtview_path" ); while ( my $line = <$mv> ) { $mv_contents .= $line if ( $line !~ m!^//|<\?(?:php)?|\?>! ); } close $mv; } my $cgi_path = MT->instance->server_path() || ""; $cgi_path =~ s!/*$!!; my $mtphp_path = File::Spec->canonpath("$cgi_path/php/mt.php"); my $blog_id = $blog->id; my $config = MT->instance->{cfg_file}; my $cache_code = $cache ? "\n \$mt->caching = true;" : ''; my $conditional_code = $conditional ? "\n \$mt->conditional = true;" : ''; my $new_mtview = <<NEW_MTVIEW; include('$mtphp_path'); \$mt = new MT($blog_id, '$config');$cache_code$conditional_code \$mt->view(); NEW_MTVIEW if ( $new_mtview ne substr( $mv_contents, 0, length($new_mtview) ) ) { $mv_contents =~ s!\n!\n//!gs; my $mtview = <<MTVIEW; <?php $new_mtview $mv_contents ?> MTVIEW $blog->file_mgr->mkpath( $site_path ); open( my $mv, ">$mtview_path" ) || die "Couldn't open $mtview_path for appending"; print $mv $mtview || die "Couldn't write to $mtview_path"; close $mv; } }; if ($@) { print STDERR $@; } my $compiled_template_path = File::Spec->catfile( $site_path, 'templates_c' ); my $fmgr = $blog->file_mgr; my $cfg = MT->config; my $saved_umask = $cfg->DirUmask; $cfg->DirUmask('0000'); $fmgr->mkpath($compiled_template_path); $cfg->DirUmask($saved_umask); my $message = q(); if ( -d $compiled_template_path ) { $message = MT->translate( 'Error: Movable Type cannot write to the template cache directory. Please check the permissions for the directory called <code>[_1]</code> underneath your blog directory.', 'templates_c' ) unless ( -w $compiled_template_path ); } else { $message = MT->translate( 'Error: Movable Type was not able to create a directory to cache your dynamic templates. You should create a directory called <code>[_1]</code> underneath your blog directory.', 'templates_c' ) unless ( -d $compiled_template_path ); } # FIXME: use FileMgr if ($cache) { my $cache_path = File::Spec->catfile( $blog->site_path(), 'cache' ); $cfg->DirUmask('0000'); $fmgr->mkpath($cache_path); $cfg->DirUmask($saved_umask); if ( -d $cache_path ) { $message = MT->translate( 'Error: Movable Type cannot write to the template cache directory. Please check the permissions for the directory called <code>[_1]</code> underneath your blog directory.', 'cache' ) unless ( -w $cache_path ); } else { $message = MT->translate( 'Error: Movable Type was not able to create a directory to cache your dynamic templates. You should create a directory called <code>[_1]</code> underneath your blog directory.', 'cache' ) unless ( -d $cache_path ); } } MT->log( { message => $message, level => MT::Log::ERROR(), class => 'system', } ); } sub handshake { my $app = shift; my $blog_id = $app->param('blog_id'); my $remote_auth_token = $app->param('remote_auth_token'); my %param = (); $param{remote_auth_token} = $remote_auth_token; $app->load_tmpl( 'handshake_return.tmpl', \%param ); } sub do_list_action { my $app = shift; $app->validate_magic or return; # plugin_action_selector should always (?) be in the query; use it? my $action_name = $app->param('action_name'); my $type = $app->param('_type'); my ($the_action) = ( grep { $_->{key} eq $action_name } @{ $app->list_actions($type) } ); return $app->errtrans( "That action ([_1]) is apparently not implemented!", $action_name ) unless $the_action; unless ( ref( $the_action->{code} ) ) { if ( my $plugin = $the_action->{plugin} ) { $the_action->{code} = $app->handler_to_coderef( $the_action->{handler} || $the_action->{code} ); } } $the_action->{code}->($app); } sub do_page_action { my $app = shift; # plugin_action_selector should always (?) be in the query; use it? my $action_name = $app->param('action_name'); my $type = $app->param('_type'); my ($the_action) = ( grep { $_->{key} eq $action_name } @{ $app->page_actions($type) } ); return $app->errtrans( "That action ([_1]) is apparently not implemented!", $action_name ) unless $the_action; unless ( ref( $the_action->{code} ) ) { if ( my $plugin = $the_action->{plugin} ) { $the_action->{code} = $app->handler_to_coderef( $the_action->{handler} || $the_action->{code} ); } } $the_action->{code}->($app); } sub rebuild_new_phase { my ($app) = @_; my %reb_set = map { $_ => 1 } $app->param('id'); $app->rebuild_these( \%reb_set, how => NEW_PHASE ); } # rebuild_set is a hash whose keys are entry IDs # the value can be the entry itself, for efficiency, # but if the value is not a ref, the entry is loaded from the ID. # This is not a handler but a utility routine sub rebuild_these { my $app = shift; my ( $rebuild_set, %options ) = @_; # if there's nothing to rebuild, just return if ( !keys %$rebuild_set ) { # now, rebuild indexes for affected blogs my @blogs = $app->param('blog_ids'); foreach my $blog_id (@blogs) { my $blog = MT::Blog->load($blog_id) or next; $app->rebuild_indexes( Blog => $blog ) or return $app->publish_error(); } my $this_blog = MT::Blog->load( $app->param('blog_id') ); $app->run_callbacks( 'rebuild', $this_blog ); return $app->call_return; } if ( exists $options{how} && ( $options{how} eq NEW_PHASE ) ) { my $params = { return_args => $app->return_args, blog_id => $app->param('blog_id') || 0, id => [ keys %$rebuild_set ] }; my %param = ( is_full_screen => 1, redirect_target => $app->uri( mode => 'rebuild_phase', args => $params ) ); return $app->load_tmpl( 'rebuilding.tmpl', \%param ); } else { my @blogs = $app->param('blog_ids'); my %blogs = map { $_ => () } @blogs; my @set = keys %$rebuild_set; my @rest; my $entries_per_rebuild = $app->config('EntriesPerRebuild'); if ( scalar @set > $entries_per_rebuild ) { @rest = @set[ $entries_per_rebuild .. $#set ]; @set = @set[ 0 .. $entries_per_rebuild - 1 ]; } require MT::Entry; for my $id (@set) { my $e = ref $id ? $id : MT::Entry->load($id) or next; my $type = $e->class; # Rebuilding something that isn't an entry, rebless as required if ( $type ne MT::Entry->class_type ) { my $pkg = MT->model($type) or next; bless $e, $pkg; } $blogs{ $e->blog_id } = (); $app->rebuild_entry( Entry => $e, BuildDependencies => 1, BuildIndexes => 0 ) or return $app->publish_error(); } if (@rest) { foreach (@rest) { $_ = $_->id if ref $_; } } my $params = { return_args => $app->param('return_args'), build_type_name => $app->translate("entry"), blog_id => $app->param('blog_id') || 0, blog_ids => [ keys %blogs ], id => \@rest, }; my %param = ( is_full_screen => 1, redirect_target => $app->uri( mode => 'rebuild_phase', args => $params ) ); return $app->load_tmpl( 'rebuilding.tmpl', \%param ); } } sub empty_junk { my $app = shift; my $perms = $app->permissions; my $user = $app->user; my $blog_id = $app->param('blog_id'); return $app->errtrans("Permission denied.") if ( !$blog_id && !$user->is_superuser() ) || ( $perms && !( $perms->can_administer_blog || $perms->can_edit_all_posts || $perms->can_manage_feedback ) ); my $type = $app->param('_type'); my $class = $app->model($type); my $arg = {}; $arg->{junk_status} = -1; $arg->{blog_id} = $blog_id if $blog_id; $class->remove($arg); $app->add_return_arg( 'emptied' => 1 ); $app->call_return; } sub handle_junk { my $app = shift; my @ids = $app->param("id"); my $type = $app->param("_type"); my $class = $app->model($type); my @item_loop; my $i = 0; my $blog_id = $app->param('blog_id'); my ( %rebuild_entries, %rebuild_categories ); my @obj_ids = $app->param('id'); if ( my $req_nonce = $app->param('nonce') ) { if ( scalar @obj_ids == 1 ) { my $cmt_id = $obj_ids[0]; if ( my $obj = $class->load($cmt_id) ) { my $nonce = MT::Util::perl_sha1_digest_hex( $obj->id . $obj->created_on . $obj->blog_id . $app->config->SecretToken ); return $app->errtrans("Invalid request.") unless $nonce eq $req_nonce; my $return_args = $app->uri_params( mode => 'view', args => { '_type' => $type, id => $cmt_id, blog_id => $obj->blog_id } ); $return_args =~ s!^\?!!; $app->return_args($return_args); } else { return $app->errtrans("Invalid request."); } } else { return $app->errtrans("Invalid request."); } } else { $app->validate_magic() or return; } my $perms = $app->permissions; my $perm_checked = ( $app->user->is_superuser() || ( $app->param('blog_id') && ( $perms->can_manage_feedback || $perms->can_edit_all_posts ) ) ) ? 1 : 0; foreach my $id (@ids) { next unless $id; my $obj = $class->load($id) or die "No $class $id"; my $old_visible = $obj->visible || 0; unless ($perm_checked) { if ( $obj->isa('MT::TBPing') && $obj->parent->isa('MT::Entry') ) { next if $obj->parent->author_id != $app->user->id; } elsif ( $obj->isa('MT::Comment') ) { next if $obj->entry->author_id != $app->user->id; } next unless $perms->can_publish_post; } $obj->junk; $app->run_callbacks( 'handle_spam', $app, $obj ) ; # mv this into blk below? $obj->save; # (so that each cb doesn't have to save indiv'ly) next if $old_visible == $obj->visible; if ( $obj->isa('MT::TBPing') ) { my ( $parent_type, $parent_id ) = $obj->parent_id(); if ( $parent_type eq 'MT::Entry' ) { $rebuild_entries{$parent_id} = 1; } else { $rebuild_categories{ $obj->category_id } = 1; # TODO: do something with this list. } } else { $rebuild_entries{ $obj->entry_id } = 1; } } $app->add_return_arg( 'junked' => 1 ); if (%rebuild_entries) { $app->rebuild_these( \%rebuild_entries, how => NEW_PHASE ); } else { $app->call_return; } } sub not_junk { my $app = shift; my $perms = $app->permissions; my @ids = $app->param("id"); my @item_loop; my $i = 0; my $type = $app->param('_type'); my $class = $app->model($type); my %rebuild_set; my $perm_checked = ( $app->user->is_superuser() || ( $app->param('blog_id') && ( $perms->can_manage_feedback || $perms->can_edit_all_posts ) ) ) ? 1 : 0; foreach my $id (@ids) { next unless $id; my $obj = $class->load($id); unless ($perm_checked) { if ( $obj->isa('MT::TBPing') && $obj->parent->isa('MT::Entry') ) { next if $obj->parent->author_id != $app->user->id; } elsif ( $obj->isa('MT::Comment') ) { next if $obj->entry->author_id != $app->user->id; } next unless $perms->can_publish_post; } $obj->approve; $app->run_callbacks( 'handle_ham', $app, $obj ); if ( $obj->isa('MT::TBPing') ) { my ( $parent_type, $parent_id ) = $obj->parent_id(); if ( $parent_type eq 'MT::Entry' ) { $rebuild_set{$parent_id} = 1; } else { } } else { $rebuild_set{ $obj->entry_id } = 1; } $obj->save(); } $app->param( 'approve', 1 ); $app->add_return_arg( 'unjunked' => 1 ); $app->rebuild_these( \%rebuild_set, how => NEW_PHASE ); } sub _cb_notjunktest_filter { my ( $eh, $app, $obj ) = @_; require MT::JunkFilter; MT::JunkFilter->filter($obj); $obj->is_junk ? 0 : 1; } sub update_list_prefs { my $app = shift; my $prefs = $app->list_pref( $app->param('_type') ); $app->call_return; } sub add_tags_to_entries { my $app = shift; my @id = $app->param('id'); require MT::Tag; my $tags = $app->param('itemset_action_input'); my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} ); my @tags = MT::Tag->split( $tag_delim, $tags ); return $app->call_return unless @tags; require MT::Entry; my $user = $app->user; my $perms = $app->permissions; foreach my $id (@id) { next unless $id; my $entry = MT::Entry->load($id) or next; next unless $user->is_superuser || $perms->can_edit_entry( $entry, $user ); $entry->add_tags(@tags); $entry->save or return $app->trans_error( "Error saving entry: [_1]", $entry->errstr ); } $app->add_return_arg( 'saved' => 1 ); $app->call_return; } sub remove_tags_from_entries { my $app = shift; my @id = $app->param('id'); require MT::Tag; my $tags = $app->param('itemset_action_input'); my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} ); my @tags = MT::Tag->split( $tag_delim, $tags ); return $app->call_return unless @tags; require MT::Entry; my $user = $app->user; my $perms = $app->permissions; foreach my $id (@id) { next unless $id; my $entry = MT::Entry->load($id) or next; next unless $user->is_superuser || $perms->can_edit_entry( $entry, $user ); $entry->remove_tags(@tags); $entry->save or return $app->trans_error( "Error saving entry: [_1]", $entry->errstr ); } $app->add_return_arg( 'saved' => 1 ); $app->call_return; } sub dialog_select_weblog { my $app = shift; #return $app->errtrans("Permission denied.") # unless $app->user->is_superuser; my $favorites = $app->param('select_favorites'); my %favorite; my $confirm_js; my $terms = {}; my $args = {}; if ($favorites) { my $auth = $app->user or return; if ( my @favs = @{ $auth->favorite_blogs || [] } ) { $terms->{id} = \@favs; $args->{not}{id} = 1; } $confirm_js = 'saveFavorite'; } my $hasher = sub { my ( $obj, $row ) = @_; $row->{label} = $row->{name}; $row->{'link'} = $row->{site_url}; }; $app->listing( { type => 'blog', code => $hasher, template => 'dialog/select_weblog.tmpl', terms => $terms, args => $args, params => { dialog_title => $app->translate("Select Blog"), items_prompt => $app->translate("Selected Blog"), search_prompt => $app->translate( "Type a blog name to filter the choices below."), panel_label => $app->translate("Blog Name"), panel_description => $app->translate("Description"), panel_type => 'blog', panel_multi => defined $app->param('multi') ? $app->param('multi') : 0, panel_searchable => 1, panel_first => 1, panel_last => 1, list_noncron => 1, return_url => $app->uri . '?' . ( $app->param('return_args') || '' ), confirm_js => $confirm_js, idfield => ( $app->param('idfield') || '' ), namefield => ( $app->param('namefield') || '' ), }, } ); } sub dialog_select_sysadmin { my $app = shift; return $app->errtrans("Permission denied.") unless $app->user->is_superuser; my $hasher = sub { my ( $obj, $row ) = @_; $row->{label} = $row->{name}; $row->{description} = $row->{nickname}; }; $app->listing( { type => 'author', terms => { type => MT::Author::AUTHOR(), status => MT::Author::ACTIVE(), }, args => { sort => 'name', join => MT::Permission->join_on( 'author_id', { permissions => "\%'administer'\%", blog_id => '0', }, { 'like' => { 'permissions' => 1 } } ), }, code => $hasher, template => 'dialog/select_users.tmpl', params => { dialog_title => $app->translate("Select a System Administrator"), items_prompt => $app->translate("Selected System Administrator"), search_prompt => $app->translate( "Type a username to filter the choices below."), panel_label => $app->translate("System Administrator"), panel_description => $app->translate("Name"), panel_type => 'author', panel_multi => defined $app->param('multi') ? $app->param('multi') : 0, panel_searchable => 1, panel_first => 1, panel_last => 1, list_noncron => 1, idfield => $app->param('idfield'), namefield => $app->param('namefield'), }, } ); } sub add_tags_to_assets { my $app = shift; my @id = $app->param('id'); require MT::Tag; my $tags = $app->param('itemset_action_input'); my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} ); my @tags = MT::Tag->split( $tag_delim, $tags ); return $app->call_return unless @tags; require MT::Asset; my $user = $app->user; my $perms = $app->permissions; foreach my $id (@id) { next unless $id; my $asset = MT::Asset->load($id) or next; next unless $user->is_superuser || $perms->can_edit_assets; $asset->add_tags(@tags); $asset->save or return $app->trans_error( "Error saving file: [_1]", $asset->errstr ); } $app->add_return_arg( 'saved' => 1 ); $app->call_return; } sub remove_tags_from_assets { my $app = shift; my @id = $app->param('id'); require MT::Tag; my $tags = $app->param('itemset_action_input'); my $tag_delim = chr( $app->user->entry_prefs->{tag_delim} ); my @tags = MT::Tag->split( $tag_delim, $tags ); return $app->call_return unless @tags; require MT::Asset; my $user = $app->user; my $perms = $app->permissions; foreach my $id (@id) { next unless $id; my $asset = MT::Asset->load($id) or next; next unless $user->is_superuser || $perms->can_edit_assets; $asset->remove_tags(@tags); $asset->save or return $app->trans_error( "Error saving file: [_1]", $asset->errstr ); } $app->add_return_arg( 'saved' => 1 ); $app->call_return; } # This mode can be called to service a number of views # Adding roles->blogs for a user # Adding roles->blogs for a group # Adding users->roles->blogs # Adding groups->roles->blogs sub dialog_grant_role { my $app = shift; my $author_id = $app->param('author_id'); my $blog_id = $app->param('blog_id'); my $role_id = $app->param('role_id'); my $this_user = $app->user; if ( !$this_user->is_superuser ) { if ( !$blog_id || !$this_user->permissions($blog_id)->can_administer_blog ) { return $app->errtrans("Permission denied."); } } my $type = $app->param('_type'); my ( $user, $role ); if ($author_id) { $user = MT::Author->load($author_id); } if ($role_id) { require MT::Role; $role = MT::Role->load($role_id); } my $hasher = sub { my ( $obj, $row ) = @_; $row->{label} = $row->{name}; $row->{description} = $row->{nickname} if exists $row->{nickname}; }; # Only show active users who are not commenters. my $terms = {}; if ( $type && ( $type eq 'author' ) ) { $terms->{status} = MT::Author::ACTIVE(); $terms->{type} = MT::Author::AUTHOR(); } my $pseudo_user_row = { id => 'PSEUDO', label => $app->translate('(newly created user)'), description => $app->translate('represents a user who will be created afterwards'), }; if ( $app->param('search') || $app->param('json') ) { my $params = { panel_type => $type, list_noncron => 1, panel_multi => 1, }; $app->listing( { terms => $terms, type => $type, code => $hasher, params => $params, template => 'include/listing_panel.tmpl', $app->param('search') ? () : ( pre_build => sub { my ($param) = @_; if ( $type && $type eq 'author' ) { if ( !$app->param('offset') ) { my $objs = $param->{object_loop} ||= []; unshift @$objs, $pseudo_user_row; } } } ), } ); } else { # traditional, full-screen listing my $params = { ($author_id || 0) eq 'PSEUDO' ? ( edit_author_name => $app->translate('(newly created user)'), edit_author_id => 'PSEUDO' ) : $author_id ? ( edit_author_name => $user->nickname ? $user->nickname : $user->name, edit_author_id => $user->id, ) : (), $role_id ? ( role_name => $role->name, role_id => $role->id, ) : (), }; my @panels; if ( !$role_id ) { push @panels, 'role'; } if ( !$blog_id ) { push @panels, 'blog'; } if ( !$author_id ) { if ( $type eq 'user' ) { unshift @panels, 'author'; } } my $panel_info = { 'blog' => { panel_title => $app->translate("Select Blogs"), panel_label => $app->translate("Blog Name"), items_prompt => $app->translate("Blogs Selected"), search_label => $app->translate("Search Blogs"), panel_description => $app->translate("Description"), }, 'author' => { panel_title => $app->translate("Select Users"), panel_label => $app->translate("Username"), items_prompt => $app->translate("Users Selected"), search_label => $app->translate("Search Users"), panel_description => $app->translate("Name"), }, 'role' => { panel_title => $app->translate("Select Roles"), panel_label => $app->translate("Role Name"), items_prompt => $app->translate("Roles Selected"), search_label => $app->translate(""), panel_description => $app->translate("Description"), }, }; $params->{blog_id} = $blog_id; $params->{dialog_title} = $app->translate("Grant Permissions"); $params->{panel_loop} = []; $params->{panel_multi} = 1; for ( my $i = 0 ; $i <= $#panels ; $i++ ) { my $source = $panels[$i]; my $panel_params = { panel_type => $source, %{ $panel_info->{$source} }, list_noncron => 1, panel_last => $i == $#panels, panel_first => $i == 0, panel_number => $i + 1, panel_total => $#panels + 1, panel_has_steps => ( $#panels == '0' ? 0 : 1 ), panel_searchable => ( $source eq 'role' ? 0 : 1 ), }; # Only show active user/groups. my $terms = {}; if ( $source eq 'author' ) { $terms->{status} = MT::Author::ACTIVE(); $terms->{type} = MT::Author::AUTHOR(); } $app->listing( { no_html => 1, code => $hasher, type => $source, params => $panel_params, terms => $terms, args => { sort => 'name' }, } ); if ( $source && ( $source eq 'author' ) ) { if ( !$app->param('offset') ) { my $data = $panel_params->{object_loop} ||= []; unshift @$data, $pseudo_user_row; } } if ( !$panel_params->{object_loop} || ( $panel_params->{object_loop} && @{ $panel_params->{object_loop} } < 1 ) ) { $params->{"missing_$source"} = 1; $params->{"missing_data"} = 1; } push @{ $params->{panel_loop} }, $panel_params; } # save the arguments from whence we came... $params->{return_args} = $app->return_args; $app->load_tmpl( 'dialog/create_association.tmpl', $params ); } } sub revoke_role { my $app = shift; $app->validate_magic or return; my $user = $app->user; my $perms = $app->permissions; return $app->errtrans("Permission denied.") unless $perms->can_administer_blog; my $blog_id = $app->param('blog_id'); my $role_id = $app->param('role_id'); my $user_id = $app->param('author_id'); return $app->errtrans("Invalid request.") unless $blog_id && $role_id && $user_id; require MT::Association; require MT::Role; require MT::Blog; my $author = MT::Author->load( $user_id ); my $role = MT::Role->load( $role_id ); my $blog = MT::Blog->load( $blog_id ); return $app->errtrans("Invalid request.") unless $blog && $role && $author; MT::Association->unlink( $blog => $role => $author ); $app->add_return_arg( saved => 1 ); $app->call_return; } sub remove_user_assoc { my $app = shift; $app->validate_magic or return; my $user = $app->user; my $perms = $app->permissions; return $app->errtrans("Permission denied.") unless $perms->can_administer_blog; my $blog_id = $app->param('blog_id'); my @ids = $app->param('id'); return $app->errtrans("Invalid request.") unless $blog_id && @ids; require MT::Association; require MT::Permission; foreach my $id (@ids) { next unless $id; MT::Association->remove({ blog_id => $blog_id, author_id => $id }); # these too, just in case there are no real associations # (ie, commenters) MT::Permission->remove({ blog_id => $blog_id, author_id => $id }); } $app->add_return_arg( saved => 1 ); $app->call_return; } sub grant_role { my $app = shift; my $user = $app->user; my $blogs = $app->param('blog') || ''; my $authors = $app->param('author') || ''; my $roles = $app->param('role') || ''; my $author_id = $app->param('author_id'); my $blog_id = $app->param('blog_id'); my $role_id = $app->param('role_id'); my @blogs = split /,/, $blogs; my @authors = split /,/, $authors; my @roles = split /,/, $roles; require MT::Blog; require MT::Role; foreach (@blogs) { my $id = $_; $id =~ s/\D//g; $_ = MT::Blog->load($id); } foreach (@roles) { my $id = $_; $id =~ s/\D//g; $_ = MT::Role->load($id); } my $add_pseudo_new_user = 0; foreach (@authors) { my $id = $_; if ( 'author-PSEUDO' eq $id ) { $add_pseudo_new_user = 1; next; } $id =~ s/\D//g; $_ = MT::Author->load($id); } $app->error(undef); if ($author_id) { if ( $author_id eq 'PSEUDO' ) { $add_pseudo_new_user = 1; } else { push @authors, MT::Author->load($author_id); } } push @blogs, MT::Blog->load($blog_id) if $blog_id; push @roles, MT::Role->load($role_id) if $role_id; if ( !$user->is_superuser ) { if ( ( scalar @blogs != 1 ) || ( !$user->permissions( $blogs[0] )->can_administer_blog ) ) { return $app->errtrans("Permission denied."); } } require MT::Association; my @default_assignments; # TBD: handle case for associating system roles to users/groups foreach my $blog (@blogs) { next unless ref $blog; foreach my $role (@roles) { next unless ref $role; if ($add_pseudo_new_user) { push @default_assignments, $role->id . ',' . $blog->id; } foreach my $ug ( @authors ) { next unless ref $ug; MT::Association->link( $ug => $role => $blog ); } } } if ( $add_pseudo_new_user && @default_assignments ) { my $da = $app->config('DefaultAssignments'); $da .= ',' if $da; $app->config( 'DefaultAssignments', $da . join( ',', @default_assignments ), 1 ); $app->config->save_config; } $app->add_return_arg( saved => 1 ); $app->call_return; } sub start_backup { my $app = shift; my $user = $app->user; my $blog_id = $app->param('blog_id'); my $perms = $app->permissions; unless ( $user->is_superuser ) { return $app->errtrans("Permission denied.") unless defined($blog_id) && $perms->can_administer_blog; } my %param = (); if ( defined($blog_id) ) { $param{blog_id} = $blog_id; $app->add_breadcrumb( $app->translate('Backup') ); } else { $app->add_breadcrumb( $app->translate('Backup & Restore') ); } $param{system_overview_nav} = 1 unless $blog_id; $param{nav_backup} = 1; require MT::Util::Archive; my @formats = MT::Util::Archive->available_formats(); $param{archive_formats} = \@formats; my $limit = $app->config('CGIMaxUpload') || 2048; $param{over_300} = 1 if $limit >= 300 * 1024; $param{over_500} = 1 if $limit >= 500 * 1024; $param{over_1024} = 1 if $limit >= 1024 * 1024; $param{over_2048} = 1 if $limit >= 2048 * 1024; my $tmp = $app->config('TempDir'); unless ( ( -d $tmp ) && ( -w $tmp ) ) { $param{error} = $app->translate( 'Temporary directory needs to be writable for backup to work correctly. Please check TempDir configuration directive.' ); } $app->load_tmpl( 'backup.tmpl', \%param ); } sub start_restore { my $app = shift; my $user = $app->user; my $blog_id = $app->param('blog_id'); my $perms = $app->permissions; unless ( $user->is_superuser ) { return $app->errtrans("Permission denied.") unless defined($blog_id) && $perms->can_administer_blog; } my %param = (); if ( defined($blog_id) ) { $param{blog_id} = $blog_id; $app->add_breadcrumb( $app->translate('Backup') ); } else { $app->add_breadcrumb( $app->translate('Backup & Restore') ); } $param{system_overview_nav} = 1 unless $blog_id; $param{nav_backup} = 1; eval "require XML::SAX"; $param{missing_sax} = 1 if $@; my $tmp = $app->config('TempDir'); unless ( ( -d $tmp ) && ( -w $tmp ) ) { $param{error} = $app->translate( 'Temporary directory needs to be writable for restore to work correctly. Please check TempDir configuration directive.' ); } $app->load_tmpl( 'restore.tmpl', \%param ); } sub _backup_finisher { my $app = shift; my ( $fnames, $param ) = @_; unless ( ref $fnames ) { $fnames = [$fnames]; } $param->{filename} = $fnames->[0]; $param->{backup_success} = 1; require MT::Session; MT::Session->remove( { kind => 'BU' } ); foreach my $fname (@$fnames) { my $sess = MT::Session->new; $sess->id( $app->make_magic_token() ); $sess->kind('BU'); # BU == Backup $sess->name($fname); $sess->start(time); $sess->save; } my $message; if ( my $blog_id = $param->{blog_id} || $param->{blog_ids} ) { $message = $app->translate( "Blog(s) (ID:[_1]) was/were successfully backed up by user '[_2]'", $blog_id, $app->user->name ); } else { $message = $app->translate( "Movable Type system was successfully backed up by user '[_1]'", $app->user->name ); } $app->log( { message => $message, level => MT::Log::INFO(), class => 'system', category => 'restore' } ); $app->print( $app->build_page( 'include/backup_end.tmpl', $param ) ); } sub backup { my $app = shift; my $user = $app->user; my $q = $app->param; my $blog_id = $q->param('blog_id'); my $perms = $app->permissions; unless ( $user->is_superuser ) { return $app->errtrans("Permission denied.") unless defined($blog_id) && $perms->can_administer_blog; } $app->validate_magic() or return; my $blog_ids = $q->param('backup_what'); my $size = $q->param('size_limit') || 0; return $app->errtrans( '[_1] is not a number.', MT::Util::encode_html($size) ) if $size !~ /^\d+$/; my @blog_ids = split ',', $blog_ids; my $archive = $q->param('backup_archive_format'); my $enc = $app->charset || 'utf-8'; my @ts = gmtime(time); my $ts = sprintf "%04d-%02d-%02d-%02d-%02d-%02d", $ts[5] + 1900, $ts[4] + 1, @ts[ 3, 2, 1, 0 ]; my $file = "Movable_Type-$ts" . '-Backup'; my $param = { return_args => '__mode=start_backup' }; $app->{no_print_body} = 1; $app->add_breadcrumb( $app->translate('Backup & Restore'), $app->uri( mode => 'start_backup' ) ); $app->add_breadcrumb( $app->translate('Backup') ); $param->{system_overview_nav} = 1 if defined($blog_ids) && $blog_ids; $param->{blog_id} = $blog_id if $blog_id; $param->{blog_ids} = $blog_ids if $blog_ids; $param->{nav_backup} = 1; local $| = 1; $app->send_http_header('text/html'); $app->print( $app->build_page( 'include/backup_start.tmpl', $param ) ); require File::Temp; require File::Spec; use File::Copy; my $temp_dir = $app->config('TempDir'); require MT::BackupRestore; my $count_term = $blog_id ? { class => '*', blog_id => $blog_id } : { class => '*' }; my $num_assets = $app->model('asset')->count($count_term); my $printer; my $splitter; my $finisher; my $progress = sub { $app->_progress(@_); }; my $fh; my $fname; my $arc_buf; if ( !( $size || $num_assets ) ) { $splitter = sub { }; if ( '0' eq $archive ) { ( $fh, my $filepath ) = File::Temp::tempfile( 'xml.XXXXXXXX', DIR => $temp_dir ); ( my $vol, my $dir, $fname ) = File::Spec->splitpath($filepath); $printer = sub { my ($data) = @_; print $fh $data; return length($data); }; $finisher = sub { my ($asset_files) = @_; close $fh; $app->_backup_finisher( $fname, $param ); }; } else { # archive/compress files $printer = sub { my ($data) = @_; $arc_buf .= $data; return length($data); }; $finisher = sub { require MT::Util::Archive; my ($asset_files) = @_; ( my $fh, my $filepath ) = File::Temp::tempfile( $archive . '.XXXXXXXX', DIR => $temp_dir ); ( my $vol, my $dir, $fname ) = File::Spec->splitpath($filepath); close $fh; unlink $filepath; my $arc = MT::Util::Archive->new($archive, $filepath); $arc->add_string( $arc_buf, "$file.xml" ); $arc->add_string( "<manifest xmlns='" . MT::BackupRestore::NS_MOVABLETYPE() . "'><file type='backup' name='$file.xml' /></manifest>", "$file.manifest"); $arc->close; $app->_backup_finisher( $fname, $param ); }; } } else { my @files; my $filename = File::Spec->catfile( $temp_dir, $file . "-1.xml" ); $fh = gensym(); open $fh, ">$filename"; my $url = $app->uri . "?__mode=backup_download&name=$file-1.xml&magic_token=" . $app->current_magic; $url .= "&blog_id=$blog_id" if defined($blog_id); push @files, { url => $url, filename => $file . "-1.xml" }; $printer = sub { my ($data) = @_; print $fh $data; return length($data); }; $splitter = sub { my ($findex) = @_; print $fh '</movabletype>'; close $fh; my $filename = File::Spec->catfile( $temp_dir, $file . "-$findex.xml" ); $fh = gensym(); open $fh, ">$filename"; my $url = $app->uri . "?__mode=backup_download&name=$file-$findex.xml&magic_token=" . $app->current_magic; $url .= "&blog_id=$blog_id" if defined($blog_id); push @files, { url => $url, filename => $file . "-$findex.xml" }; my $header .= "<movabletype xmlns='" . MT::BackupRestore::NS_MOVABLETYPE() . "'>\n"; $header = "<?xml version='1.0' encoding='$enc'?>\n$header" if $enc !~ m/utf-?8/i; print $fh $header; }; $finisher = sub { my ($asset_files) = @_; close $fh; my $filename = File::Spec->catfile( $temp_dir, "$file.manifest" ); $fh = gensym(); open $fh, ">$filename"; print $fh "<manifest xmlns='" . MT::BackupRestore::NS_MOVABLETYPE() . "'>\n"; for my $file (@files) { my $name = $file->{filename}; print $fh "<file type='backup' name='$name' />\n"; } for my $id ( keys %$asset_files ) { my $name = $id . '-' . $asset_files->{$id}->[2]; my $tmp = File::Spec->catfile( $temp_dir, $name ); unless ( copy( $asset_files->{$id}->[1], $tmp ) ) { $app->log( { message => $app->translate( 'Copying file [_1] to [_2] failed: [_3]', $asset_files->{$id}->[1], $tmp, $! ), level => MT::Log::INFO(), class => 'system', category => 'backup' } ); next; } print $fh "<file type='asset' name='" . $asset_files->{$id}->[2] . "' asset_id='" . $id . "' />\n"; my $url = $app->uri . "?__mode=backup_download&assetname=$name&magic_token=" . $app->current_magic; $url .= "&blog_id=$blog_id" if defined($blog_id); push @files, { url => $url, filename => $name, }; } print $fh "</manifest>\n"; close $fh; my $url = $app->uri . "?__mode=backup_download&name=$file.manifest&magic_token=" . $app->current_magic; $url .= "&blog_id=$blog_id" if defined($blog_id); push @files, { url => $url, filename => "$file.manifest" }; if ( '0' eq $archive ) { $param->{files_loop} = \@files; $param->{tempdir} = $temp_dir; my @fnames = map { $_->{filename} } @files; $app->_backup_finisher( \@fnames, $param ); } else { my ( $fh_arc, $filepath ) = File::Temp::tempfile( $archive . '.XXXXXXXX', DIR => $temp_dir ); ( my $vol, my $dir, $fname ) = File::Spec->splitpath($filepath); require MT::Util::Archive; close $fh_arc; unlink $filepath; my $arc = MT::Util::Archive->new($archive, $filepath); for my $f (@files) { $arc->add_file( $temp_dir, $f->{filename} ); } $arc->close; # for safery, don't unlink before closing $arc here. for my $f (@files) { unlink File::Spec->catfile( $temp_dir, $f->{filename} ); } $app->_backup_finisher( $fname, $param ); } }; } my @tsnow = gmtime(time); my $metadata = { backup_by => $app->user->name . '(ID: ' . $app->user->id . ')', backup_on => sprintf( "%04d-%02d-%02dT%02d:%02d:%02d", $tsnow[5] + 1900, $tsnow[4] + 1, @tsnow[ 3, 2, 1, 0 ] ), backup_what => join( ',', @blog_ids ), schema_version => $app->config('SchemaVersion'), }; MT::BackupRestore->backup( \@blog_ids, $printer, $splitter, $finisher, $progress, $size * 1024, $enc, $metadata ); } sub _progress { my $app = shift; my $ids = $app->request('progress_ids') || {}; my ( $str, $id ) = @_; if ( $id && $ids->{$id} ) { require MT::Util; my $str_js = MT::Util::encode_js($str); $app->print( qq{<script type="text/javascript">progress('$str_js', '$id');</script>} ); } elsif ($id) { $ids->{$id} = 1; $app->print(qq{\n<span id="$id">$str</span>}); } else { $app->print("<span>$str</span>"); } $app->request( 'progress_ids', $ids ); } sub backup_download { my $app = shift; my $user = $app->user; my $blog_id = $app->param('blog_id'); unless ( $user->is_superuser ) { my $perms = $app->permissions; return $app->errtrans("Permission denied.") unless defined($blog_id) && $perms->can_administer_blog; } $app->validate_magic() or return; my $filename = $app->param('filename'); my $assetname = $app->param('assetname'); my $temp_dir = $app->config('TempDir'); my $newfilename; if ( defined($assetname) ) { my $sess = MT::Session->load( { kind => 'BU', name => $assetname } ); if ( !defined($sess) || !$sess ) { return $app->errtrans("Specified file was not found."); } $newfilename = $filename = $assetname; $sess->remove; } elsif ( defined($filename) ) { my $sess = MT::Session->load( { kind => 'BU', name => $filename } ); if ( !defined($sess) || !$sess ) { return $app->errtrans("Specified file was not found."); } my @ts = gmtime( $sess->start ); my $ts = sprintf "%04d-%02d-%02d-%02d-%02d-%02d", $ts[5] + 1900, $ts[4] + 1, @ts[ 3, 2, 1, 0 ]; $newfilename = "Movable_Type-$ts" . '-Backup'; $sess->remove; } else { $newfilename = $app->param('name'); return if $newfilename !~ /Movable_Type-\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}-Backup(?:-\d+)?\.\w+/; $filename = $newfilename; } require File::Spec; my $fname = File::Spec->catfile( $temp_dir, $filename ); my $contenttype; if ( !defined($assetname) && ( $filename =~ m/^xml\..+$/i ) ) { my $enc = $app->charset || 'utf-8'; $contenttype = "text/xml; charset=$enc"; $newfilename .= '.xml'; } elsif ( $filename =~ m/^tgz\..+$/i ) { $contenttype = 'application/x-tar-gz'; $newfilename .= '.tar.gz'; } elsif ( $filename =~ m/^zip\..+$/i ) { $contenttype = 'application/zip'; $newfilename .= '.zip'; } else { $contenttype = 'application/octet-stream'; } if ( open( my $fh, "<", $fname ) ) { binmode $fh; $app->{no_print_body} = 1; $app->set_header( "Content-Disposition" => "attachment; filename=$newfilename" ); $app->send_http_header($contenttype); my $data; while ( read $fh, my ($chunk), 8192 ) { $data .= $chunk; } close $fh; $app->print($data); $app->log( { message => $app->translate( '[_1] successfully downloaded backup file ([_2])', $app->user->name, $fname ), level => MT::Log::INFO(), class => 'system', category => 'restore' } ); unlink $fname; } else { $app->errtrans('Specified file was not found.'); } } sub restore { my $app = shift; my $user = $app->user; return $app->errtrans("Permission denied.") if !$user->is_superuser; $app->validate_magic() or return; my $q = $app->param; my ($fh) = $app->upload_info('file'); my $uploaded = $q->param('file'); my ( $volume, $directories, $uploaded_filename ) = File::Spec->splitpath($uploaded) if defined($uploaded); if ( defined($uploaded_filename) && ( $uploaded_filename =~ /^.+\.manifest$/i ) ) { return $app->restore_upload_manifest($fh); } my $param = { return_args => '__mode=dashboard' }; $app->add_breadcrumb( $app->translate('Backup & Restore'), $app->uri( mode => 'start_restore' ) ); $app->add_breadcrumb( $app->translate('Restore') ); $param->{system_overview_nav} = 1; $param->{nav_backup} = 1; $app->{no_print_body} = 1; local $| = 1; $app->send_http_header('text/html'); $app->print( $app->build_page( 'restore_start.tmpl', $param ) ); require File::Path; my $error = ''; my $result; if (!$fh) { $param->{restore_upload} = 0; my $dir = $app->config('ImportPath'); my ( $blog_ids, $asset_ids ) = $app->restore_directory( $dir, \$error ); if ( defined $blog_ids ) { $param->{open_dialog} = 1; $param->{blog_ids} = join( ',', @$blog_ids ); $param->{asset_ids} = join( ',', @$asset_ids ) if defined $asset_ids; $param->{tmp_dir} = $dir; } } else { $param->{restore_upload} = 1; if ( $uploaded_filename =~ /^.+\.xml$/i ) { my $blog_ids = $app->restore_file( $fh, \$error ); if ( defined $blog_ids ) { $param->{open_dialog} = 1; $param->{blog_ids} = join( ',', @$blog_ids ); } } else { require MT::Util::Archive; my $arc; my $error; if ( $uploaded_filename =~ /^.+\.tar(\.gz)?$/i ) { $arc = MT::Util::Archive->new('tgz', $fh); } elsif ( $uploaded_filename =~ /^.+\.zip$/i ) { $arc = MT::Util::Archive->new('zip', $fh); } else { $error = $app->translate( 'Please use xml, tar.gz, zip, or manifest as a file extension.' ); } unless ($arc) { $result = 0; $param->{restore_success} = 0; if ($error) { $param->{error} = $error; } else { $error = MT->translate('Unknown file format'); $app->log( { message => $error . ":$uploaded_filename", level => MT::Log::WARNING(), class => 'system', category => 'restore', metadata => MT::Util::Archive->errstr, } ); } $app->print( $error ); $app->print( $app->build_page( "restore_end.tmpl", $param ) ); close $fh if $fh; return 1; } my $temp_dir = $app->config('TempDir'); require File::Temp; my $tmp = File::Temp::tempdir( $uploaded_filename . 'XXXX', DIR => $temp_dir ); $arc->extract($tmp); $arc->close; my ( $blog_ids, $asset_ids ) = $app->restore_directory( $tmp, \$error ); if (defined $blog_ids) { $param->{open_dialog} = 1; $param->{blog_ids} = join( ',', @$blog_ids ) if defined $blog_ids; $param->{asset_ids} = join( ',', @$asset_ids ) if defined $asset_ids; $param->{tmp_dir} = $tmp; } } } $param->{restore_success} = !$error; $param->{error} = $error if $error; if ( ( exists $param->{open_dialog} ) && ( $param->{open_dialog} ) ) { $param->{dialog_mode} = 'dialog_adjust_sitepath'; $param->{dialog_params} = 'magic_token=' . $app->current_magic . '&blog_ids=' . $param->{blog_ids} . '&asset_ids=' . $param->{asset_ids} . '&tmp_dir=' . encode_url( $param->{tmp_dir} ); if ( ( $param->{restore_upload} ) && ( $param->{restore_upload} ) ) { $param->{dialog_params} .= '&restore_upload=1'; } if ( ( $param->{error} ) && ( $param->{error} ) ) { $param->{dialog_params} .= '&error=' . encode_url( $param->{error} ); } } $app->print( $app->build_page( "restore_end.tmpl", $param ) ); close $fh if $fh; 1; } sub _log_dirty_restore { my $app = shift; my ($deferred) = @_; my %deferred_by_class; for my $key ( keys %$deferred ) { my ( $class, $id ) = split( '#', $key ); if ( exists $deferred_by_class{$class} ) { push @{ $deferred_by_class{$class} }, $id; } else { $deferred_by_class{$class} = [$id]; } } while ( my ( $class_name, $ids ) = each %deferred_by_class ) { my $message = $app->translate( 'Some [_1] were not restored because their parent objects were not restored.', $class_name ); $app->log( { message => $message, level => MT::Log::WARNING(), class => 'system', category => 'restore', metadata => join( ', ', @$ids ), } ); } 1; } sub restore_file { my $app = shift; my ( $fh, $errormsg ) = @_; my $q = $app->param; my $schema_version = $q->param('ignore_schema_conflict') ? 'ignore' : $app->config('SchemaVersion'); my $overwrite_template = $q->param('overwrite_global_templates') ? 1 : 0; require MT::BackupRestore; my ( $deferred, $blogs ) = MT::BackupRestore->restore_file( $fh, $errormsg, $schema_version, $overwrite_template, sub { $app->_progress(@_); } ); if ( !defined($deferred) || scalar( keys %$deferred ) ) { $app->_log_dirty_restore($deferred); my $log_url = $app->uri( mode => 'view_log', args => {} ); $$errormsg .= '; ' if $$errormsg; $$errormsg .= $app->translate( 'Some objects were not restored because their parent objects were not restored. Detailed information is in the <a href="javascript:void(0);" onclick="closeDialog(\'[_1]\');">activity log</a>.', $log_url ); return $blogs; } if ($$errormsg) { $app->log( { message => $$errormsg, level => MT::Log::ERROR(), class => 'system', category => 'restore', } ); return $blogs; } $app->log( { message => $app->translate( "Successfully restored objects to Movable Type system by user '[_1]'", $app->user->name ), level => MT::Log::INFO(), class => 'system', category => 'restore' } ); $blogs; } sub restore_directory { my $app = shift; my ( $dir, $error ) = @_; if ( !-d $dir ) { $$error = $app->translate( '[_1] is not a directory.', $dir ); return ( undef, undef ); } my $q = $app->param; my $schema_version = $q->param('ignore_schema_conflict') ? 'ignore' : $app->config('SchemaVersion'); my $overwrite_template = $q->param('overwrite_global_templates') ? 1 : 0; my @errors; my %error_assets; require MT::BackupRestore; my ( $deferred, $blogs, $assets ) = MT::BackupRestore->restore_directory( $dir, \@errors, \%error_assets, $schema_version, $overwrite_template, sub { $app->_progress(@_); } ); if ( scalar @errors ) { $$error = $app->translate('Error occured during restore process.'); $app->log( { message => $$error, level => MT::Log::WARNING(), class => 'system', category => 'restore', metadata => join( '; ', @errors ), } ); } return ( $blogs, $assets ) unless ( defined($deferred) && %$deferred ); if ( scalar( keys %error_assets ) ) { my $data; while ( my ( $key, $value ) = each %error_assets ) { $data .= $app->translate( 'MT::Asset#[_1]: ', $key ) . $value . "\n"; } my $message = $app->translate('Some of files could not be restored.'); $app->log( { message => $message, level => MT::Log::WARNING(), class => 'system', category => 'restore', metadata => $data, } ); $$error .= $message; } if ( scalar( keys %$deferred ) ) { $app->_log_dirty_restore($deferred); my $log_url = $app->uri( mode => 'view_log', args => {} ); $$error = $app->translate( 'Some objects were not restored because their parent objects were not restored. Detailed information is in the <a href="javascript:void(0);" onclick="closeDialog(\'[_1]\');">activity log</a>.', $log_url ); return ( $blogs, $assets ); } return ( $blogs, $assets ) if $$error; $app->log( { message => $app->translate( "Successfully restored objects to Movable Type system by user '[_1]'", $app->user->name ), level => MT::Log::INFO(), class => 'system', category => 'restore' } ); return ( $blogs, $assets ); } sub restore_upload_manifest { my $app = shift; my ($fh) = @_; my $user = $app->user; return $app->errtrans("Permission denied.") if !$user->is_superuser; $app->validate_magic() or return; my $q = $app->param; require MT::BackupRestore; my $backups = MT::BackupRestore->process_manifest($fh); return $app->errtrans( "Uploaded file was not a valid Movable Type backup manifest file.") if !defined($backups); my $files = $backups->{files}; my $assets = $backups->{assets}; my $file_next = shift @$files if defined($files) && scalar(@$files); my $assets_json; my $param = {}; if ( !defined($file_next) ) { if ( scalar(@$assets) > 0 ) { my $asset = shift @$assets; $file_next = $asset->{name}; $param->{is_asset} = 1; } } require JSON; require MT::Util; $assets_json = MT::Util::encode_url( JSON::objToJson($assets) ) if scalar(@$assets) > 0; $param->{files} = join( ',', @$files ); $param->{assets} = $assets_json; $param->{filename} = $file_next; $param->{last} = scalar(@$files) ? 0 : ( scalar(@$assets) ? 0 : 1 ); $param->{open_dialog} = 1; $param->{schema_version} = $q->param('ignore_schema_conflict') ? 'ignore' : $app->config('SchemaVersion'); $param->{overwrite_templates} = $q->param('overwrite_global_templates') ? 1 : 0; $param->{dialog_mode} = 'dialog_restore_upload'; $param->{dialog_params} = 'start=1' . '&magic_token=' . $app->current_magic . '&files=' . $param->{files} . '&assets=' . $param->{assets} . '&current_file=' . $param->{filename} . '&last=' . $param->{'last'} . '&schema_version=' . $param->{schema_version} . '&overwrite_templates=' . $param->{overwrite_templates} . '&redirect=1'; $app->load_tmpl( 'restore.tmpl', $param ); #close $fh if $fh; } sub dialog_restore_upload { my $app = shift; my $user = $app->user; return $app->errtrans("Permission denied.") if !$user->is_superuser; $app->validate_magic() or return; my $q = $app->param; my $current = $q->param('current_file'); my $last = $q->param('last'); my $files = $q->param('files'); my $assets_json = $q->param('assets'); my $is_asset = $q->param('is_asset') || 0; my $schema_version = $q->param('schema_version') || $app->config('SchemaVersion'); my $overwrite_template = $q->param('overwrite_templates') ? 1 : 0; my $objects = {}; my $deferred = {}; require JSON; my $objects_json = $q->param('objects_json') if $q->param('objects_json'); $deferred = JSON::jsonToObj( $q->param('deferred_json') ) if $q->param('deferred_json'); my ($fh) = $app->upload_info('file'); my $param = {}; $param->{start} = $q->param('start') || 0; $param->{is_asset} = $is_asset; $param->{name} = $current; $param->{files} = $files; $param->{assets} = $assets_json; $param->{last} = $last; $param->{redirect} = 1; $param->{is_dirty} = $q->param('is_dirty'); $param->{objects_json} = $objects_json if defined($objects_json); $param->{deferred_json} = JSON::objToJson($deferred) if defined($deferred); $param->{blogs_meta} = $q->param('blogs_meta'); $param->{schema_version} = $schema_version; $param->{overwrite_templates} = $overwrite_template; my $uploaded = $q->param('file'); if ( defined($uploaded) ) { $uploaded =~ s!\\!/!g; ## Change backslashes to forward slashes my ( $volume, $directories, $uploaded_filename ) = File::Spec->splitpath($uploaded); if ( $current ne $uploaded_filename ) { close $fh if $uploaded_filename; $param->{error} = $app->translate( 'Please upload [_1] in this page.', $current ); return $app->load_tmpl( 'dialog/restore_upload.tmpl', $param ); } } if (!$fh) { $param->{error} = $app->translate('File was not uploaded.') if !( $q->param('redirect') ); return $app->load_tmpl( 'dialog/restore_upload.tmpl', $param ); } $app->{no_print_body} = 1; local $| = 1; $app->send_http_header('text/html'); $app->print( $app->build_page( 'dialog/restore_start.tmpl', $param ) ); if ( defined $objects_json ) { my $objects_tmp = JSON::jsonToObj($objects_json); my %class2ids; # { MT::CLASS#OLD_ID => NEW_ID } for my $key ( keys %$objects_tmp ) { my ( $class, $old_id ) = split '#', $key; if ( exists $class2ids{$class} ) { my $newids = $class2ids{$class}->{newids}; push @$newids, $objects_tmp->{$key}; my $keymaps = $class2ids{$class}->{keymaps}; push @$keymaps, { newid => $objects_tmp->{$key}, oldid => $old_id }; } else { $class2ids{$class} = { newids => [ $objects_tmp->{$key} ], keymaps => [ { newid => $objects_tmp->{$key}, oldid => $old_id } ] }; } } for my $class ( keys %class2ids ) { eval "require $class;"; next if $@; my $newids = $class2ids{$class}->{newids}; my $keymaps = $class2ids{$class}->{keymaps}; my @objs = $class->load( { id => $newids } ); for my $obj (@objs) { my @old_ids = grep { $_->{newid} eq $obj->id } @$keymaps; my $old_id = $old_ids[0]->{oldid}; $objects->{"$class#$old_id"} = $obj; } } } my $assets = JSON::jsonToObj( MT::Util::decode_html($assets_json) ) if ( defined($assets_json) && $assets_json ); $assets = [] if !defined($assets); my $asset; my @errors; my $error_assets = {}; require MT::BackupRestore; my $blog_ids; my $asset_ids; if ($is_asset) { $asset = shift @$assets; $asset->{fh} = $fh; my $blogs_meta = JSON::jsonToObj( $q->param('blogs_meta') || '{}' ); MT::BackupRestore->_restore_asset_multi( $asset, $objects, $error_assets, sub { $app->print(@_); }, $blogs_meta ); if ( defined( $error_assets->{ $asset->{asset_id} } ) ) { $app->log( { message => $app->translate('Restoring a file failed: ') . $error_assets->{ $asset->{asset_id} }, level => MT::Log::WARNING(), class => 'system', category => 'restore', } ); } } else { ( $blog_ids, $asset_ids ) = eval { MT::BackupRestore->restore_process_single_file( $fh, $objects, $deferred, \@errors, $schema_version, $overwrite_template, sub { $app->_progress(@_) } ); }; if ($@) { $last = 1; } } my @files = split( ',', $files ); my $file_next = shift @files if scalar(@files); if ( !defined($file_next) ) { if ( scalar(@$assets) ) { $asset = $assets->[0]; $file_next = $asset->{asset_id} . '-' . $asset->{name}; $param->{is_asset} = 1; } } $param->{files} = join( ',', @files ); $param->{assets} = MT::Util::encode_html( JSON::objToJson($assets) ); $param->{name} = $file_next; if ( 0 < scalar(@files) ) { $param->{last} = 0; } elsif ( 0 >= scalar(@$assets) - 1 ) { $param->{last} = 1; } else { $param->{last} = 0; } $param->{is_dirty} = scalar( keys %$deferred ); if ($last) { $param->{restore_end} = 1; if ( $param->{is_dirty} ) { $app->_log_dirty_restore($deferred); my $log_url = $app->uri( mode => 'view_log', args => {} ); $param->{error} = $app->translate( 'Some objects were not restored because their parent objects were not restored.' ); $param->{error_url} = $log_url; } elsif ( scalar( keys %$error_assets ) ) { $param->{error} = $app->translate('Some of the files were not restored correctly.'); my $log_url = $app->uri( mode => 'view_log', args => {} ); $param->{error_url} = $log_url; } elsif ( scalar @errors ) { $param->{error} = join '; ', @errors; my $log_url = $app->uri( mode => 'view_log', args => {} ); $param->{error_url} = $log_url; } else { $app->log( { message => $app->translate( "Successfully restored objects to Movable Type system by user '[_1]'", $app->user->name ), level => MT::Log::INFO(), class => 'system', category => 'restore' } ); $param->{ok_url} = $app->uri( mode => 'start_restore', args => {} ); } } else { my %objects_json; %objects_json = map { $_ => $objects->{$_}->id } keys %$objects; $param->{objects_json} = JSON::objToJson( \%objects_json ); $param->{deferred_json} = JSON::objToJson($deferred); $param->{error} = join( '; ', @errors ); if ( defined($blog_ids) && scalar(@$blog_ids) ) { $param->{next_mode} = 'dialog_adjust_sitepath'; $param->{blog_ids} = join( ',', @$blog_ids ); $param->{asset_ids} = join( ',', @$asset_ids ) if defined($asset_ids); } else { $param->{next_mode} = 'dialog_restore_upload'; } } MT->run_callbacks('restore', $objects, $deferred, \@errors, sub { $app->_progress(@_) }); $app->print( $app->build_page( 'dialog/restore_end.tmpl', $param ) ); close $fh if $fh; } sub restore_premature_cancel { my $app = shift; my $user = $app->user; return $app->errtrans("Permission denied.") if !$user->is_superuser; $app->validate_magic() or return; require JSON; my $deferred = JSON::jsonToObj( $app->param('deferred_json') ) if $app->param('deferred_json'); my $param = { restore_success => 1 }; if ( defined $deferred && ( scalar( keys %$deferred ) ) ) { $app->_log_dirty_restore($deferred); my $log_url = $app->uri( mode => 'view_log', args => {} ); $param->{restore_success} = 0; my $message = $app->translate( 'Some objects were not restored because their parent objects were not restored.' ); $param->{error} = $message . ' ' . $app->translate( "Detailed information is in the <a href='javascript:void(0)' onclick='closeDialog(\"[_1]\")'>activity log</a>.", $log_url ); } else { $app->log( { message => $app->translate( '[_1] has canceled the multiple files restore operation prematurely.', $app->user->name ), level => MT::Log::WARNING(), class => 'system', category => 'restore', } ); } $app->redirect( $app->uri( mode => 'view_log', args => {} ) ); } sub dialog_adjust_sitepath { my $app = shift; my $user = $app->user; return $app->errtrans("Permission denied.") if !$user->is_superuser; $app->validate_magic() or return; my $q = $app->param; my $tmp_dir = $q->param('tmp_dir'); my $error = $q->param('error') || q(); my $uploaded = $q->param('restore_upload') || 0; my @blog_ids = split ',', $q->param('blog_ids'); my $asset_ids = $q->param('asset_ids'); my @blogs = $app->model('blog')->load( { id => \@blog_ids } ); my @blogs_loop; foreach my $blog (@blogs) { push @blogs_loop, { name => $blog->name, id => $blog->id, old_site_path => $blog->site_path, old_site_url => $blog->site_url, $blog->column('archive_path') ? ( old_archive_path => $blog->archive_path ) : (), $blog->column('archive_url') ? ( old_archive_url => $blog->archive_url ) : (), }; } my $param = { blogs_loop => \@blogs_loop, tmp_dir => $tmp_dir }; $param->{error} = $error if $error; $param->{restore_upload} = $uploaded if $uploaded; $param->{asset_ids} = $asset_ids if $asset_ids; for my $key ( qw(files assets last redirect is_dirty is_asset objects_json deferred_json) ) { $param->{$key} = $q->param($key) if $q->param($key); } $param->{name} = $q->param('current_file'); $app->load_tmpl( 'dialog/adjust_sitepath.tmpl', $param ); } sub adjust_sitepath { my $app = shift; my $user = $app->user; return $app->errtrans("Permission denied.") if !$user->is_superuser; $app->validate_magic() or return; require MT::BackupRestore; my $q = $app->param; my $tmp_dir = $q->param('tmp_dir'); my $error = $q->param('error') || q(); my %asset_ids = split ',', $q->param('asset_ids'); $app->{no_print_body} = 1; local $| = 1; $app->send_http_header('text/html'); $app->print( $app->build_page( 'dialog/restore_start.tmpl', {} ) ); my %error_assets; my %blogs_meta; my @p = $q->param; foreach my $p (@p) { next unless $p =~ /^site_path_(\d+)/; my $id = $1; my $blog = $app->model('blog')->load($id); my $old_site_path = scalar $q->param("old_site_path_$id"); my $old_site_url = scalar $q->param("old_site_url_$id"); my $site_path = scalar $q->param("site_path_$id") || q(); my $site_url = scalar $q->param("site_url_$id") || q(); $blog->site_path($site_path); $blog->site_url($site_url); if ( $site_url || $site_path ) { $app->print( $app->translate( "Changing Site Path for the blog '[_1]' (ID:[_2])...", $blog->name, $blog->id ) ); } else { $app->print( $app->translate( "Removing Site Path for the blog '[_1]' (ID:[_2])...", $blog->name, $blog->id ) ); } my $old_archive_path = scalar $q->param("old_archive_path_$id"); my $old_archive_url = scalar $q->param("old_archive_url_$id"); my $archive_path = scalar $q->param("archive_path_$id") || q(); my $archive_url = scalar $q->param("archive_url_$id") || q(); $blog->archive_path($archive_path); $blog->archive_url($archive_url); if ( ( $old_archive_url && $archive_url ) || ( $old_archive_path && $archive_path ) ) { $app->print( "\n" . $app->translate( "Changing Archive Path for the blog '[_1]' (ID:[_2])...", $blog->name, $blog->id ) ); } elsif ( $old_archive_url || $old_archive_path ) { $app->print( "\n" . $app->translate( "Removing Archive Path for the blog '[_1]' (ID:[_2])...", $blog->name, $blog->id ) ); } $blog->save or $app->print( $app->translate("failed") . "\n" ), next; $app->print( $app->translate("ok") . "\n" ); $blogs_meta{$id} = { 'old_archive_path' => $old_archive_path, 'old_archive_url' => $old_archive_url, 'archive_path' => $archive_path, 'archive_url' => $archive_url, 'old_site_path' => $old_site_path, 'old_site_url' => $old_site_url, 'site_path' => $site_path, 'site_url' => $site_url, }; next unless %asset_ids; my $fmgr = ( $site_path || $archive_path ) ? $blog->file_mgr : undef; next unless defined $fmgr; my @assets = $app->model('asset')->load( { blog_id => $id, class => '*' } ); foreach my $asset (@assets) { my $path = $asset->column('file_path'); my $url = $asset->column('url'); if ($archive_path) { $path =~ s/^\Q$old_archive_path\E/$archive_path/i; $asset->file_path($path); } if ($archive_url) { $url =~ s/^\Q$old_archive_url\E/$archive_url/i; $asset->url($url); } if ($site_path) { $path =~ s/^\Q$old_site_path\E/$site_path/i; $asset->file_path($path); } if ($site_url) { $url =~ s/^\Q$old_site_url\E/$site_url/i; $asset->url($url); } $app->print( $app->translate( "Changing file path for the asset '[_1]' (ID:[_2])...", $asset->label, $asset->id ) ); $asset->save or $app->print( $app->translate("failed") . "\n" ), next; $app->print( $app->translate("ok") . "\n" ); unless ( $q->param('redirect') ) { my $old_id = $asset_ids{ $asset->id }; my $filename = "$old_id-" . $asset->file_name; my $file = File::Spec->catfile( $tmp_dir, $filename ); MT::BackupRestore->restore_asset( $file, $asset, $old_id, $fmgr, \%error_assets, sub { $app->print(@_); } ); } } } if (%error_assets) { my $data; while ( my ( $key, $value ) = each %error_assets ) { $data .= $app->translate( 'MT::Asset#[_1]: ', $key ) . $value . "\n"; } my $message = $app->translate( 'Some of the actual files for assets could not be restored.'); $app->log( { message => $message, level => MT::Log::WARNING(), class => 'system', category => 'restore', metadata => $data, } ); $error .= $message; } if ($tmp_dir) { require File::Path; File::Path::rmtree($tmp_dir); } my $param = {}; if ( scalar $q->param('redirect') ) { $param->{restore_end} = 0; # redirect=1 means we are from multi-uploads require JSON; require MT::Util; $param->{blogs_meta} = JSON::objToJson( \%blogs_meta ); $param->{next_mode} = 'dialog_restore_upload'; } else { $param->{restore_end} = 1; } if ($error) { $param->{error} = $error; $param->{error_url} = $app->uri( mode => 'view_log', args => {} ); } for my $key ( qw(files last redirect is_dirty is_asset objects_json deferred_json)) { $param->{$key} = scalar $q->param($key); } $param->{name} = $q->param('current_file'); $param->{assets} = MT::Util::encode_html( $q->param('assets') ); $app->print( $app->build_page( 'dialog/restore_end.tmpl', $param ) ); } sub dialog_post_comment { my $app = shift; $app->validate_magic or return; my $user = $app->user; my $parent_id = $app->param('reply_to'); return $app->errtrans('Parent comment id was not specified.') unless $parent_id; my $comment_class = $app->model('comment'); my $parent = $comment_class->load($parent_id) or return $app->errtrans('Parent comment was not found.'); return $app->errtrans("You can't reply to unapproved comment.") unless $parent->is_published; my $perms = $app->{perms}; return unless $perms; my $entry_class = $app->_load_driver_for('entry'); my $entry = $entry_class->load( $parent->entry_id ); unless ( $user->is_superuser() || $perms->can_edit_all_posts || $perms->can_manage_feedback ) { return $app->errtrans("Permission denied.") unless $perms->can_edit_entry( $entry, $user, 1 ) ; # check for publish_post } my $blog = $parent->blog || $app->model('blog')->load($app->param('blog_id')); require MT::Sanitize; my $spec = $blog->sanitize_spec || $app->config->GlobalSanitizeSpec; my $param = { reply_to => $parent_id, commenter_name => MT::Sanitize->sanitize($parent->author, $spec), entry_title => $entry->title, comment_created_on => format_ts( LISTING_DATETIME_FORMAT, $parent->created_on, $blog, $app->user ? $app->user->preferred_language : undef ), comment_text => MT::Sanitize->sanitize($parent->text, $spec), comment_script_url => $app->config('CGIPath') . $app->config('CommentScript'), return_url => $app->base . $app->mt_uri . '?' . $app->param('return_args'), }; $app->load_tmpl( 'dialog/comment_reply.tmpl', $param ); } sub _prepare_reply { my $app = shift; my $q = $app->param; my $comment_class = $app->model('comment'); my $parent = $comment_class->load( $q->param('reply_to') ); my $entry = $app->model('entry')->load( $parent->entry_id ); unless ( $parent->is_published ) { $app->error( $app->translate("You can't reply to unpublished comment.") ); return ( undef, $parent, $entry ); } unless ( $app->validate_magic ) { $app->error( $app->translate("Invalid request.") ); return ( undef, $parent, $entry ); } my $blog = $app->model('blog')->load( $entry->blog_id ); my $nick = $app->user->nickname || $app->translate('Registered User'); my $comment = $comment_class->new; ## Strip linefeed characters. my $text = $q->param('text'); $text = '' unless defined $text; $text =~ tr/\r//d; $comment->ip( $app->remote_ip ); $comment->commenter_id( $app->user->id ); $comment->blog_id( $entry->blog_id ); $comment->entry_id( $entry->id ); $comment->author( remove_html($nick) ); $comment->email( remove_html( $app->user->email ) ); $comment->text($text); $comment->visible(1); # leave as undefined $comment->is_junk(0); # strip of any null characters (done after junk checks so they can # monitor for that kind of activity) for my $field (qw(author email text)) { my $val = $comment->column($field); if ( $val =~ m/\x00/ ) { $val =~ tr/\x00//d; $comment->column( $field, $val ); } } ( $comment, $parent, $entry ); } #TODO: this should be refactored for more general use sub reply { my $app = shift; my $q = $app->param; my $reply_to = encode_html( $q->param('reply_to') ); my $magic_token = encode_html( $q->param('magic_token') ); my $blog_id = encode_html( $q->param('blog_id') ); my $return_url = encode_html( $q->param('return_url') ); my $text = encode_html( $q->param('text') ); my $indicator = $app->static_path . 'images/indicator.gif'; my $url = $app->uri; <<SPINNER; <html><head><style type="text/css"> #dialog-indicator {position: relative;top: 200px; background: url($indicator) no-repeat;width: 66px;height: 66px;margin: 0 auto; }</style><script type="text/javascript"> function init() { var f = document.getElementById('f'); f.submit(); } window.setTimeout("init()", 1500); </script></head><body> <div align="center"><div id="dialog-indicator"></div> <form id="f" method="post" action="$url"> <input type="hidden" name="__mode" value="do_reply" /> <input type="hidden" name="reply_to" value="$reply_to" /> <input type="hidden" name="magic_token" value="$magic_token" /> <input type="hidden" name="blog_id" value="$blog_id" /> <input type="hidden" name="return_url" value="$return_url" /> <input type="hidden" name="text" value="$text" /> </form></div></body></html> SPINNER } sub do_reply { my $app = shift; my $q = $app->param; my $param = { reply_to => $q->param('reply_to'), magic_token => $q->param('magic_token'), blog_id => $q->param('blog_id'), }; my ( $comment, $parent, $entry ) = $app->_prepare_reply; $param->{commenter_name} = $parent->author; $param->{entry_title} = $entry->title; $param->{comment_created_on} = format_ts( "%Y-%m-%d %H:%M:%S", $parent->created_on, undef, $app->user ? $app->user->preferred_language : undef ); $param->{comment_text} = $parent->text; return $app->build_page( 'dialog/comment_reply.tmpl', { %$param, error => $app->errstr } ) unless $comment; $comment->parent_id( $param->{reply_to} ); $comment->approve; return $app->handle_error( $app->translate( "An error occurred: [_1]", $comment->errstr() ) ) unless $comment->save; MT::Util::start_background_task( sub { $app->rebuild_entry( Entry => $parent->entry_id, BuildDependencies => 1 ) or return $app->publish_error( "Publish failed: [_1]", $app->errstr ); $app->_send_comment_notification( $comment, q(), $entry, $app->model('blog')->load( $param->{blog_id} ), $app->user ); } ); return $app->build_page( 'dialog/comment_reply.tmpl', { closing => 1, return_url => $q->param('return_url') } ); } sub reply_preview { my $app = shift; my $q = $app->param; my $cfg = $app->config; my $param = { reply_to => $q->param('reply_to'), magic_token => $q->param('magic_token'), blog_id => $q->param('blog_id'), }; my ( $comment, $parent, $entry ) = $app->_prepare_reply; my $blog = $parent->blog || $app->model('blog')->load($q->param('blog_id')); require MT::Sanitize; my $spec = $blog->sanitize_spec || $app->config->GlobalSanitizeSpec; $param->{commenter_name} = MT::Sanitize->sanitize($parent->author, $spec); $param->{entry_title} = $entry->title; $param->{comment_created_on} = format_ts( "%Y-%m-%d %H:%M:%S", $parent->created_on, undef, $app->user ? $app->user->preferred_language : undef ); $param->{comment_text} = MT::Sanitize->sanitize($parent->text, $spec); return $app->build_page( 'dialog/comment_reply.tmpl', { %$param, error => $app->errstr } ) unless $comment; my $cmt_tmpl = $app->model('template')->load( { identifier => 'comment_detail' } ); my $tmpl_name = $cmt_tmpl->name; require MT::Template; my $tmpl = MT::Template->new( type => 'scalarref', source => \"<\$MTInclude module=\"$tmpl_name\"\$>", ); my $ctx = $tmpl->context; ## Set timestamp as we would usually do in ObjectDriver. my @ts = MT::Util::offset_time_list( time, $entry->blog_id ); my $ts = sprintf "%04d%02d%02d%02d%02d%02d", $ts[5] + 1900, $ts[4] + 1, @ts[ 3, 2, 1, 0 ]; $comment->created_on($ts); $comment->commenter_id( $app->user->id ); $ctx->stash( 'comment', $comment ); require MT::Serialize; my $ser = MT::Serialize->new( $cfg->Serializer ); my $state = $comment->column_values; $state->{static} = $q->param('static'); $ctx->stash( 'comment_state', unpack 'H*', $ser->serialize( \$state ) ); $ctx->stash( 'comment_is_static', 1 ); $ctx->stash( 'entry', $entry ); $ctx->{current_timestamp} = $ts; $ctx->stash( 'commenter', $app->user ); $ctx->stash( 'blog_id', $parent->blog_id ); $ctx->stash( 'blog', $parent->blog ); my %cond; my $html = $tmpl->build( $ctx, \%cond ); return $app->build_page( 'dialog/comment_reply.tmpl', { %$param, error => $tmpl->errstr } ) unless defined $html; return $app->build_page( 'dialog/comment_reply.tmpl', { %$param, text => $q->param('text'), preview_html => $html } ); } sub dialog_refresh_templates { my $app = shift; $app->validate_magic or return; # permission check my $perms = $app->permissions; return $app->errtrans("Permission denied.") unless $app->user->is_superuser || $perms->can_administer_blog || $perms->can_edit_templates; my $param = {}; my $blog = $app->blog; $param->{return_args} = $app->param('return_args'); if ($blog) { $param->{blog_id} = $blog->id; my $sets = $app->registry("template_sets"); $sets->{$_}{key} = $_ for keys %$sets; $sets = $app->filter_conditional_list([ values %$sets ]); no warnings; # some sets may not define an order @$sets = sort { $a->{order} <=> $b->{order} } @$sets; $param->{'template_set_loop'} = $sets; my $existing_set = $blog->template_set || 'mt_blog'; foreach (@$sets) { if ($_->{key} eq $existing_set) { $_->{selected} = 1; } } $param->{'template_set_index'} = $#$sets; $param->{'template_set_count'} = scalar @$sets; $param->{template_sets} = $sets; $param->{screen_id} = "refresh-templates-dialog"; } # load template sets $app->build_page('dialog/refresh_templates.tmpl', $param); } sub refresh_all_templates { my ($app) = @_; my $backup = 0; if ($app->param('backup')) { # refresh templates dialog uses a 'backup' field $backup = 1; } my $template_set = $app->param('template_set'); my $refresh_type = $app->param('refresh_type') || 'refresh'; my $t = time; my @id; if ($app->param('blog_id')) { @id = ( scalar $app->param('blog_id') ); } else { @id = $app->param('id'); if (! @id) { # refresh global templates @id = ( 0 ); } } require MT::Template; require MT::DefaultTemplates; require MT::Blog; require MT::Permission; require MT::Util; foreach my $blog_id (@id) { my $blog; if ($blog_id) { $blog = MT::Blog->load($blog_id); next unless $blog; } if ( !$app->user->is_superuser() ) { my $perms = MT::Permission->load( { blog_id => $blog_id, author_id => $app->user->id } ); if ( !$perms || ( !$perms->can_edit_templates() && !$perms->can_administer_blog() ) ) { next; } } my $tmpl_list; if ($blog_id) { if ($refresh_type eq 'clean') { # the user wants to back up all templates and # install the new ones my @ts = MT::Util::offset_time_list( $t, $blog_id ); my $ts = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $ts[5] + 1900, $ts[4] + 1, @ts[ 3, 2, 1, 0 ]; my $tmpl_iter = MT::Template->load_iter({ blog_id => $blog_id, type => { not => 'backup' }, }); while (my $tmpl = $tmpl_iter->()) { if ($backup) { # zap all template maps require MT::TemplateMap; MT::TemplateMap->remove({ template_id => $tmpl->id, }); $tmpl->type('backup'); $tmpl->name( $tmpl->name . ' (Backup from ' . $ts . ')' ); $tmpl->identifier(undef); $tmpl->rebuild_me(0); $tmpl->linked_file(undef); $tmpl->outfile(''); $tmpl->save; } else { $tmpl->remove; } } # This also creates our template mappings $blog->create_default_templates( $template_set || $blog->template_set || 'mt_blog' ); if ($template_set) { $blog->template_set( $template_set ); $blog->save; $app->run_callbacks( 'blog_template_set_change', { blog => $blog } ); } next; } $tmpl_list = MT::DefaultTemplates->templates($template_set || $blog->template_set) || MT::DefaultTemplates->templates(); } else { $tmpl_list = MT::DefaultTemplates->templates(); } foreach my $val (@$tmpl_list) { if ($blog_id) { # when refreshing blog templates, # skip over global templates which # specify a blog_id of 0... next if $val->{global}; } else { next unless exists $val->{global}; } if ( !$val->{orig_name} ) { $val->{orig_name} = $val->{name}; $val->{name} = $app->translate( $val->{name} ); $val->{text} = $app->translate_templatized( $val->{text} ); } my $orig_name = $val->{orig_name}; my @ts = MT::Util::offset_time_list( $t, ( $blog_id ? $blog_id : undef ) ); my $ts = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $ts[5] + 1900, $ts[4] + 1, @ts[ 3, 2, 1, 0 ]; my $terms = {}; $terms->{blog_id} = $blog_id; $terms->{type} = $val->{type}; if ( $val->{type} =~ m/^(archive|individual|page|category|index|custom|widget)$/ ) { $terms->{name} = $val->{name}; } else { $terms->{identifier} = $val->{identifier}; } # this should only return 1 template; we're searching # within a given blog for a specific type of template (for # "system" templates; or for a type + name, which should be # unique for that blog. my $tmpl = MT::Template->load($terms); if ($tmpl && $backup) { # check for default template text... # if it is a default template, then outright replace it my $text = $tmpl->text; $text =~ s/\s+//g; my $def_text = $val->{text}; $def_text =~ s/\s+//g; # if it has been customized, back it up to a new tmpl record if ($def_text ne $text) { my $backup = $tmpl->clone; delete $backup->{column_values} ->{id}; # make sure we don't overwrite original delete $backup->{changed_cols}->{id}; $backup->name( $backup->name . $app->translate( ' (Backup from [_1])', $ts ) ); $backup->type('backup'); # if ( $backup->type !~ # m/^(archive|individual|page|category|index|custom|widget)$/ ) # { # $backup->type('custom') # ; # system templates can't be created # } $backup->outfile(''); $backup->linked_file( $tmpl->linked_file ); $backup->identifier(undef); $backup->rebuild_me(0); $backup->build_dynamic(0); $backup->save; } } if ($tmpl) { # we found that the previous template had not been # altered, so replace it with new default template... $tmpl->text( $val->{text} ); $tmpl->identifier( $val->{identifier} ); $tmpl->type( $val->{type} ) ; # fixes mismatch of types for cases like "archive" => "individual" $tmpl->linked_file(''); $tmpl->save; } else { # create this one... my $tmpl = new MT::Template; $tmpl->build_dynamic(0); $tmpl->set_values( { text => $val->{text}, name => $val->{name}, type => $val->{type}, identifier => $val->{identifier}, outfile => $val->{outfile}, rebuild_me => $val->{rebuild_me} } ); $tmpl->blog_id($blog_id); $tmpl->save or return $app->error( $app->translate("Error creating new template: ") . $tmpl->errstr ); } } } $app->add_return_arg( 'refreshed' => 1 ); $app->call_return; } sub refresh_individual_templates { my ($app) = @_; require MT::Util; my $user = $app->user; my $perms = $app->permissions; return $app->error( $app->translate( "Permission denied.") ) #TODO: system level-designer permission unless $user->is_superuser() || $user->can_edit_templates() || ( $perms && ( $perms->can_edit_templates() || $perms->can_administer_blog ) ); my $set; if ( my $blog_id = $app->param('blog_id') ) { my $blog = $app->model('blog')->load($blog_id); $set = $blog->template_set() if $blog; } require MT::DefaultTemplates; my $tmpl_list = MT::DefaultTemplates->templates($set) or return; my $trnames = {}; my $tmpl_types = {}; my $tmpl_ids = {}; my $tmpls = {}; foreach my $tmpl (@$tmpl_list) { $tmpl->{text} = $app->translate_templatized( $tmpl->{text} ); $tmpl_ids->{ $tmpl->{identifier} } = $tmpl if $tmpl->{identifier}; $trnames->{ $app->translate( $tmpl->{name} ) } = $tmpl->{name}; if ( $tmpl->{type} !~ m/^(archive|individual|page|category|index|custom|widget)$/ ) { $tmpl_types->{ $tmpl->{type} } = $tmpl; } else { $tmpls->{ $tmpl->{type} }{ $tmpl->{name} } = $tmpl; } } my $t = time; my @msg; my @id = $app->param('id'); require MT::Template; foreach my $tmpl_id (@id) { my $tmpl = MT::Template->load($tmpl_id); next unless $tmpl; my $blog_id = $tmpl->blog_id; # FIXME: permission check -- for this blog_id my @ts = MT::Util::offset_time_list( $t, $blog_id ); my $ts = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $ts[5] + 1900, $ts[4] + 1, @ts[ 3, 2, 1, 0 ]; my $orig_name = $trnames->{ $tmpl->name } || $tmpl->name; my $val = ( $tmpl->identifier ? $tmpl_ids->{ $tmpl->identifier() } : undef ) || $tmpl_types->{ $tmpl->type() } || $tmpls->{ $tmpl->type() }{$orig_name}; if ( !$val ) { push @msg, $app->translate( "Skipping template '[_1]' since it appears to be a custom template.", $tmpl->name ); next; } my $text = $tmpl->text; $text =~ s/\s+//g; my $def_text = $val->{text}; $def_text =~ s/\s+//g; if ($text ne $def_text) { # if it has been customized, back it up to a new tmpl record my $backup = $tmpl->clone; delete $backup->{column_values} ->{id}; # make sure we don't overwrite original delete $backup->{changed_cols}->{id}; $backup->name( $backup->name . ' (Backup from ' . $ts . ')' ); $backup->type('backup'); $backup->outfile(''); $backup->linked_file( $tmpl->linked_file ); $backup->rebuild_me(0); $backup->build_dynamic(0); $backup->identifier(undef); $backup->save; push @msg, $app->translate( 'Refreshing template <strong>[_3]</strong> with <a href="?__mode=view&blog_id=[_1]&_type=template&id=[_2]">backup</a>', $blog_id, $backup->id, $tmpl->name ); # we found that the previous template had not been # altered, so replace it with new default template... $tmpl->text( $val->{text} ); $tmpl->identifier( $val->{identifier} ); $tmpl->linked_file(''); $tmpl->save; } else { push @msg, $app->translate("Skipping template '[_1]' since it has not been changed.", $tmpl->name); } } my @msg_loop; push @msg_loop, { message => $_ } foreach @msg; $app->build_page( 'refresh_results.tmpl', { message_loop => \@msg_loop, return_url => $app->return_uri } ); } sub publish_index_templates { my $app = shift; $app->validate_magic or return; # permission check my $perms = $app->permissions; return $app->errtrans("Permission denied.") unless $app->user->is_superuser || $perms->can_administer_blog || $perms->can_rebuild; my $blog = $app->blog; my $templates = MT->model('template')->lookup_multi([ $app->param('id') ]); TEMPLATE: for my $tmpl (@$templates) { next TEMPLATE if !defined $tmpl; next TEMPLATE if $tmpl->blog_id != $blog->id; $app->rebuild_indexes( Blog => $blog, Template => $tmpl, ); } $app->call_return( published => 1 ); } 1; __END__ =head1 NAME MT::App::CMS =head1 SYNOPSIS The I<MT::App::CMS> module is the primary application module for Movable Type. It is the administrative interface that is used to manage blogs, entries, comments, trackbacks, templates, etc. =head1 METHODS =head2 $app->init Initializes the application, defining the modes that are serviced and core callbacks and itemset actions. =head2 $app->init_request Initializes the application to handle the request. The CMS also does a schema version check to make certain the schema is up-to-date. If it is not, it forces a direct to the configured UpgradeScript application (typically mt-upgrade.cgi). =head2 $app->init_plugins Overrides I<MT::init_plugins> to call L<init_core_callbacks> before other plugins are run (since init_core_callbacks defines some remapped callback names plugins may rely on). =head2 $app->apply_log_filter(\%param) Returns terms suitable for filtering L<MT::Log> based on the parameters given. 'filter', 'filter_val' and 'blog_id' are recognized parameters. 'filter' may be one of 'level' or 'class'. Valid 'filter_val' values for a 'filter' of 'level' is any number between 1 and 2^16, being a bitmask. Valid 'filter_val' values for a 'filter' of 'class' include any of the register L<MT::Log> class names. See L<MT::Log> for further information. =head2 $app->build_author_table(%args) =head2 $app->build_blog_table(%args) =head2 $app->build_comment_table(%args) =head2 $app->build_commenter_table(%args) =head2 $app->build_entry_table(%args) =head2 $app->build_group_table(%args) =head2 $app->build_junk_table(%args) =head2 $app->build_log_table(%args) =head2 $app->build_ping_table(%args) =head2 $app->build_tag_table(%args) =head2 $app->build_template_table(%args) Each of these routines have a similar interface and function. They load data for the E<lt>typeE<gt>_table.tmpl application template. Parameters for these routine are: =over 4 =item * param The parameter hash to populate. This is eventually applied to the application template. =item * load_args An array reference containing the terms and arguments that are to be passed on to Class-E<gt>load_iter method to load the data. =item * iter An existing iterator that can be used to load the records. =item * items An array of objects used to populate the listing. =back Only one of these arguments is allowed: param, load_args, iter. Upon return, the hashref provided in the 'param' element is populated with the data necessary to build the template. These routines are called by their listing modes (where a 'load_args' parameter is given) and also from the search_replace mode when displaying search results (where it passes an 'items' element). =head2 $app->build_page($tmpl_file, \%param) Overridden method for L<MT::App::build_page> that adds additional template parameters that are common to CMS application templates. =head2 $app->build_plugin_table(%opt) Populates template parameters necessary for rendering the cfg_plugin.tmpl for the blog-centric and system-overview views. =head2 $app->do_search_replace Utility method used to do the physical search and replace operation invoked with the 'search_replace' application mode. This method is also used by the 'listing' method when a 'search' parameter is sent in the request. =head2 $app->get_newsbox_content Handles the task of retrieving the "Movable Type News" from sixapart.com. This request is done at most once per day since the data is cached after retrieval. This operation can be disabled by setting the C<NewsboxURL> to "disable". =head2 $app->is_authorized Determines if the active user is authorized to access the blog being requested. If a blog-level request is active, this method sets the permissions object for the current user and blog. =head2 $app->list_actions($which, $type, @param) Utility method to handle the selection of itemset actions for a given C<$type>. Any @param data given here is passed through to the itemset action's "condition" coderef, if registered. =head2 $app->languages_list($lang) Returns a list of supported languages in a format suitable for the edit_author.tmpl, edit_profile.tmpl and cfg_system_general.tmpl application templates. The C<$lang> parameter pre-selects that language in the list. =head2 $app->list_tag_for(%param) Generates a listing screen for L<MT::Tag> objects that are attached to a given 'taggable' package. The parameters for this method are: =over 4 =item * Package The Perl package name of a class that supports the L<MT::Taggable> interface that the listing is for. =item * TagObjectType The registered type (ie, 'entry' for MT::Entry objects) of data these tags are associated with. =item * TagObjectLabelPlural Translated version of TagObjectTypePlural. =back To list tags for the L<MT::Entry> package, you would use this: $app->list_tag_for( Package => 'MT::Entry', TagObjectType => 'entry', TagObjectLabelPlural => $app->translate("entries") ); This method uses the list_tag.tmpl application template to render the tag listing view. =head2 $app->load_default_entry_prefs =head2 $app->load_entry_prefs =head2 $app->load_list_actions =head2 $app->list_pref($type) Returns a hashref of listing preferences suitable for the pager.tmpl application template. It will also update new listing preference settings if it sees the current request is posting them. =head2 $app->listing(\%param) This is a method for generating a typical listing screen. It is configured through the parameter hash passed to it. The accepted parameters include: =over 4 =item * Type Specifies a data type to be processed. This is one of the types registered with the application (see the L<register_type> method). Standard types include: C<blog>, C<entry>, C<author>, C<comment>, C<ping>, C<template>, C<notification>, C<templatemap>, C<commenter>, C<banlist>, C<category>, C<ping_cat>, C<log>, C<tag>, C<group>, C<role>, C<association>. The Type parameter provides a reference to the actual Perl package to be used for loading the data for the listing. L<MT::App::CMS> uses it's C<model> method to retrieve the package name and load the module if necessary. The module provided should be a MT::Object descendant. =item * Template The application template filename to be used to generate the page. If unspecified, the default is named after the 'Type' parameter: "list_E<lt>typeE<gt>.tmpl". Note that this parameter is not used when the 'NoHTML' parameter is specified. =item * Params A hashref of parameters that will be passed to the application template without further modification. =item * Code A coderef that is invoked for each row data displayed. This routine is passed two parameters: the object being processed and a hashref of parameter data for that row. This is typically used to further customize the row-level parameter data before the application template is processed. =item * Terms This is a hashref that is passed to the C<load_iter> method of the package that is providing the data for the listing. The Terms parameter provides a way of filtering the data retrieved. Refer to the L<MT::Object> documentation of the C<load> and C<load_iter> methods for more information about this parameter. =item * Args This is a hashref that is passed to the C<load_iter> method of the package that is providing the data for the listing. The Args parameter can be used to specify a sort order, a join operation and other operations that are applied upon loading the data for the listing. Refer to the L<MT::Object> documentation of the C<load> and C<load_iter> methods for more information about this parameter. =item * Iterator The default value for this parameter is 'load_iter'. This is a standard method for all L<MT::Object> type packages. So, for instance, if your 'Type' parameter is 'author', the package that will be used is 'MT::Author', so the listing method will call MT::Author-E<gt>load_iter to fetch the data. But the L<MT::Author> package provides some convenient iterators for fetching other things that are related to the author, such as roles they are assigned. So, for invoking the 'role_iter' iterator method, you'd specify 'role_iter' as the value for the Iterator parameter. =item * NoHTML Specify this parameter (with a true value, such as '1') to cause the listing method to simply return the hashref of data it would have given to the application template to return. =back The listing method is also aware of many of the query parameters common to the Movable Type CMS listings. It will automatically recognize the following query parameters: =over 4 =item * offset A number specifying the number of rows to skip when displaying the listing. =item * search A string to use for a search query of the data displayed. This parameter is only useful for data types supported by the CMS search function: currently, C<blog>, C<author>, C<entry>, C<comment>, C<template>, C<ping>. =item * filter, filter_val Used to filter the listing for a particular value. This will supplement the Terms parameter given to the listing method, adding a term for C<filter>=C<filter_val>. =back The listing method is also aware of the list preferences saved for the 'Type' identified. =head3 Examples This will produce a listing page (using the "list_entry.tmpl" application template) of all entries created by the currently logged-in user. return $app->listing({ Type => 'entry', Terms => { author_id => $app->user->id, }, }); =head2 $app->load_template_prefs Returns as hashref of the template edit screen preferences for the active user. =head2 $app->make_blog_list(\@blogs) Takes an array of blogs and returns an array reference of data that is suitable for the list_blog.tmpl and list_blog.tmpl application templates. =head2 $app->object_edit_uri($type, $id) A convenience method for returning an application URI that can be used to direct the user to an edit operation for the C<$type> and object C<$id> given. This method assumes the type given is serviced through the 'view' application mode. =head2 $app->pre_run Handles the menial task of localizing the stock "Convert Line Breaks" text filter for the current user. =head2 $app->update_dynamicity($blog, $cache, $conditional) When saving the blog's configuration settings regarding dynamic publishing, this method is called to update the blog's templates to match the dynamic configuration (if specifying no dynamic publishing, all templates are configured to be statically published, when specifying 'dynamic archives', archive templates are configured to be dynamically published and all other templates are set to static). Also, the '.htaccess' file and 'mtview.php' file in the blog's root path is generated/updated to include rules that invoke the dynamic publishing engine. If this file cannot be written or updated, the user is notified of this problem. =head2 $app->user_blog_prefs Returns a hashref of the current user's blog preferences (such as their tag delimiter choice). =head2 $app->user_can_admin_commenters A utility method used by commenter-based itemset actions to condition their display based on whether or not the active user has permissions to administer commenters for the current blog. =head2 $app->validate_magic Verifies the 'magic_token' POST parameter that is placed in forms that are submitted to the application. This token is used as a safety device to protect against CSRF (cross-site request forgery) style attacks. =head1 MODE HANDLERS The CMS mode handlers typically do a permission test to verify the user has permission to invoke the mode, then process the request and return the response using an application template. =over 4 =item * add_group Adds a user to one or more groups. Returns to the list_groups mode upon success. =item * add_member Adds one or more users to a group. Returns to the list_authors mode upon success. =item * add_tags_to_entries Applies a set of tags to one or more entries. =item * approve_item Approves a comment or trackback for publication. =item * ban_commenter Bans a commenter for a particular blog. =item * ban_commenter_by_comment Bans commenters on a blog based on a selection of one or more comments. =item * category_add Displays the popup window (used on the entry composition screen) for adding a new category. =item * category_do_add Processes the creation of a new category and returns the JavaScript that adds the category to the category drop down list on the entry composition screen. =item * cc_return Handles the Creative Commons license data returned from their license selection process. =item * cfg_comments Handles the display of the weblog comments settings screen. =item * cfg_trackbacks Handles the display of the weblog trackback settings screen. =item * cfg_spam Handles the display of the weblog spam settings screen. =item * cfg_entry Handles the display of the weblog entries settings screen. =item * cfg_web_services Handles the display of the weblog web services settings screen. =item * cfg_archives Handler for the Blog Archives configuration screen. =item * cfg_blog Handles the display of the general settings for the blog. =item * cfg_plugins Handles the display of the "Plugins" tab of the blog setting screen. =item * cfg_prefs Handles the display of the "Customize Entry display preferences" screen. =item * cfg_system_feedback Handles the display of the "System-Wide Feedback Settings" screen. =item * cfg_system_general Handles the display of the "System-Wide: General Settings" screen. =item * delete Handles the deletion of registered data types. =item * dialog_grant_role Handles display of the modal dialog for granting roles. =item * dialog_select_group Handles display of the modal dialog for selecting one or more groups. =item * dialog_select_user Handles the display of the modal dialog for selecting one or more users. =item * dialog_select_weblog Handles the display of the modal dialog for selecting a blog. =item * disable_object Handles the request to disable a user or group. =item * do_import Handles the request to process entry import file(s). =item * draft_entries Handles the request to unpublish existing entries. =item * edit_object Handles the display of editing registered data types. =item * edit_role Handles the display of editing a particular role. =item * empty_junk Handles the request to empty the junk folder for comments or trackbacks. =item * enable_object Handles the request to enable a user or group. =item * start_export Handler for the display of the export screen. =item * export Handles the request to export the data for a blog. =item * export_log Handles the request to export activity log data in a CSV format. =item * grant_role Handles the request from the "Grant Role" modal dialog to grant a role to an user or group. =item * handle_junk Handles the request to junk a given comment or trackback. =item * handshake Handles the response from TypeKey for assigning the TypeKey token to a particular blog. =item * itemset_action Generic application mode handler used by all itemset actions. This handler will locate the actual coderef to be run to service the itemset action and if found, calls it. =item * js_tag_check Ajax-style handler for testing whether a particular tag name exists or not. =item * js_tag_list Ajax-style handler for returning the tags that exist on a particular blog. =item * list_assets Handler for displaying a list of blog-level assets. =item * list_associations Handler for displaying the associations listing (either all associations, associations for a particular user, group, role or blog). =item * list_authors Handler for displaying a list of authors. =item * dashboard Handler for the front screen of MT, where the user's associated blogs are listed. =item * list_category Handler for displaying the categories for a particular blog. =item * list_commenter Handler for displaying a list of authenticated commenters for a particular blog. =item * list_comments Handler for displaying a list of comments (either blog-level or system-wide). =item * list_entries Handler for displaying a list of entries (either blog-level or system-wide). =item * list_groups Handler for displaying the group listing. =item * list_objects Handler for displaying the installed templates for a particular blog (this used to be a routine called for most listings, but they have been broken out into separate handlers). =item * list_pings Handler for displaying a list of trackbacks (either blog-level or system-wide). =item * cfg_system_plugins Handler for displaying all installed plugins (the system-overview view of plugins). =item * list_roles Handler for displaying the role listing. =item * list_tag Handler for displaying a list of tags (either blog-level or system-wide). =item * logout Handler for logging the active user out of the application, ending their session. =item * move_category Handles the request for moving a category from one parent to another (or to the top level). =item * not_junk Handler for approving previously junked comments or TrackBack objects. =item * pinged_urls Handler for displaying the list of URLs that have been pinged for a particular entry. =item * plugin_control Handler for the request to enable/disable a particular plugin or all plugins. =item * preview_entry Handler for displaying an entry preview (invoked from the entry composition screen). =item * publish_entries Itemset handler for the request to publish a selection of entries. =item * rebuild_confirm Handler for displaying the list of rebuild options in the rebuild site popup window. =item * rebuild_new_phase Displays the "rebuilding.tmpl" page and invokes a "rebuild_phase" mode request to process the rebuild of a set of entries/templates. =item * rebuild_pages Primary rebuild handler for the rebuild site process. =item * rebuild_phase Handler for rebuilding entries/templates selected from the listings. Rebuilds the requested set of items and returns to the originally viewed listing. =item * recover_password Handler for displaying the password recovery screen. =item * recover_passwords Handler for resetting the passwords for one or more users. =item * recover_profile_password Handler for the password reset request invoked by a system administrator from the user profile screen. =item * reg_bm_js Handler for returning the JavaScript code for displaying the QuickPost composition page for Internet Explorer users that have installed the QuickPost Windows registry file. =item * reg_file Handler that returns the "mt.reg" for enabling a conext-menu QuickPost item that is suitable for Internet Explorer. =item * remove_group Handler for removing one or more group associations from a particular user. =item * remove_member Handler for removing one or more user associations from a particular group. =item * remove_tags_from_entries Itemset handler for removing a list of tags from one or more entries. =item * rename_tag Handler for renaming a I<MT::Tag>, or merging with another tag if one exists by that name. =item * reset_blog_templates Handler for resetting a given blogs templates back to their default configuration. =item * reset_log Handler for resetting the system-wide activity log. =item * reset_password =item * reset_plugin_config =item * save_category =item * save_cfg_system_feedback =item * save_cfg_system_general =item * save_commenter_perm =item * save_entries =item * save_entry =item * save_entry_prefs =item * save_object =item * save_plugin_config =item * save_role =item * search_replace =item * send_notify =item * send_pings =item * set_item_visible =item * set_object_status =item * show_admin =item * show_entry_prefs =item * show_menu =item * show_upload_html =item * start_import Handler for the display of the import screen. =item * start_rebuild_pages =item * start_recover =item * start_upload =item * start_upload_entry =item * asset_insert =item * asset_insert_text =item * start_upload_entry =item * list_blogs Handler for the display of the system-wide list of blogs. =item * trust_commenter =item * trust_commenter_by_comment =item * unapprove_item =item * unban_commenter =item * unban_commenter_by_comment =item * untrust_commenter =item * untrust_commenter_by_comment =item * update_list_prefs Handler for saving changes to the user's list preferences. =item * update_welcome_message Handler for saving the change to the blog's welcome message. =item * upgrade Transient mode handler used to redirect user to the mt-upgrade script for either installation of upgrade of their database. =item * upload_file =item * complete_insert =item * view_log Handler for viewing the activity log (either blog-level or system-wide). =item * start_backup Handler for the display of the backup screen. =item * start_restore Handler for the display of the restore screen. =item * backup Handler for the system backup function. =item * restore Handler for the system backup function. =back =head1 UTILITY FUNCTIONS =head2 listify(\@array) Utility function that takes an array reference of strings and returns an array reference of hashes. So this: listify(['a','b','c']) yields this: [{name => 'a'},{name => 'b'},{name => 'c'}] This is handy for presenting a simple list of things in an HTML::Template template. =head2 $app->make_feed_link($view, $params) Utility method for constructing an Activity Feed link based on the current view. This routine requires the user have an API password assigned to their profile. =head2 $app->map_comment_to_commenter(\@comments) Utility method for retrieving the unique set of commenter_id and blog_id values for the set of comments given. The return value from this method is an list of array references that each have the commenter_id and blog_id in them. =head2 $app->ping_continuation Utility method that returns a response to show the "Pinging..." status page (pinging.tmpl application template). =head2 $app->rebuild_these(\%rebuild_set, %options) Utility method for rebuilding a set of entries. If C<$options{how}> is passed and it is set to the C<NEW_PHASE> constant, the handler returns the 'rebuilding.tmpl' application template to kick off the actual rebuild operation with a separate request. Otherwise, the entries are rebuilt immediately. =head2 $app->register_type($type, $package) Registers a new "API" type that goes into the C<%API> hash. This registry is used by the C<model> method and other generic handlers that expect a '_type' parameter to be passed in the request. =head2 RegistrationAffectsArchives($blog_id, $archive_type) Private utility routine to test if a particular archive type is affected by changes to commenter registration settings. Returns true if particular tags are in use (namely, MTIfRegistrationRequired, MTIfRegistrationNotRequired, MTIfRegistrationAllowed). =head2 $app->update_entry_status($new_status, @id_list) Called by the draft_entries and publish_entries handlers to actually apply the updates to the entries identified and publish pages that are necessary. If DeleteFilesAtRebuild directive is set to 1, it also removes the previously published individual entry archive page if the new status for the entry is the one other than RELEASE. =head1 CUSTOM REBUILD OPTIONS The pop-up rebuild dialog that you see in Movable Type has a drop-down list of rebuild options. This list can be populated with custom options provided through a plugin. For example, a plugin might want to provide the user with an option to rebuild a template identified for creating user archives. =head2 $app->add_rebuild_option(\%param) =over 4 =item label The label to display in the list of rebuild options. =item code The code to execute when running the custom rebuild option. =item key An identifier unique to this option (optional, will derive from the label if unavailable). =back In addition to this application method, there is also a C<rebuild_options> callback that can be used to further customize the list of extra rebuild options. This callback is useful when needing to add items to the list that are unique to the active user or blog. =head1 CALLBACKS The application-level callbacks of the C<MT::App::CMS> application are documented here. =over 4 =item rebuild_options callback($cb, $app, \@options) The rebuild_options callback provides control over an array of additional custom rebuild options that are displayed in MT's rebuild window. The array is populated with hashrefs, each containing: =over 4 =item code The code to execute when running the custom rebuild option. =item label The label to display in the list of rebuild options. =item key An identifier unique to this option (optional, will derive from the label if unavailable). =back =item CMSPostSave.entry Called when an entry has been saved, after all of its constituent parts (for example, category placements) have been saved. An CMSPostEntrySave callback would have the following signature: sub cms_post_entry_save($cb, $app, $entry, $original) { ... } =back For backward compatibility, C<CMSPostEntrySave> and C<AppPostEntrySave> are aliases to C<CMSPostSave.entry>. =head2 Parametric Calllbacks Every object "type" has a suite of callbacks defined for that type, as below. Each item in the list below forms a callback name by appending the object "type" after a period, e.g. C<CMSViewPermissionFilter.blog>, C<CMSPostSave.template>, etc. The "type" values come from the same space as passed to the CMS app's C<_type> query parameter. If you're not sure what C<_type> corresponds to a certain MT::Object subclass, consult the following list: =over 4 =item author => MT::Author =item comment => MT::Comment =item entry => MT::Entry =item template => MT::Template =item blog => MT::Blog =item notification => MT::Notification =item templatemap => MT::TemplateMap =item category => MT::Category =item banlist => MT::IPBanList =item ping => MT::TBPing =item ping_cat => MT::TBPing =item group => MT::Group =item role => MT::Role =item association => MT::Association =back Callbacks that apply to these object types are as follows: =over 4 =item CMSViewPermissionFilter Calling convention is: callback($cb, $app, $id, $obj_promise) Where C<$id> is the ID of an object, if it already exists, or C<undef> if the user will be creating a new object of this type. C<$obj_promise> is a promise for the object itself. You can use C<$obj_promise->force> to get ahold of the object, if you need it, but typically you won't need it. (See L<MT::Promise>) Return a false value to abort the operation and display a message to the user that s/he doesn't have permission to view the object. =item CMSDeletePermissionFilter callback($cb, $app, $obj) =item CMSSavePermissionFilter Calling convention is: callback($cb, $app, $id) Where C<$id> is the ID of the object, if it already exists, or C<undef> if it is a new object with this request. Note that at this point, the object may not yet exist. The request can be understood through the query parameters of the app, accessible through C<$app-E<gt>param()>. A C<CMSSavePermissionFilter> callback should be "safe"--it should not modify the database. Return a false value to abort the operation and display a message to the user that s/he doesn't have permission to modify the object. The method is not called if the acting user is a superuser. =item CMSSaveFilter This callback gives you the chance to "decline" for reasons other than lack of permissions. The routine is called as follows: callback($cb, $app) Returning a false value will decline the request. It is advisibable to return an error via the C<$cb> object in order to signal to the user what went wrong. Note that the new object has not been constructed yet. The operation can be understood by examining the C<$app> object's query parameters via C<$app-E<gt>param()> A C<CMSSaveFilter> callback should be "safe"--it should not modify the database. =item CMSPreSave callback($cb, $app, $obj, $original) C<$obj> and C<$original> hold the object which is about to be saved, and the object as it was when this request began, respectively. This allows the callback to determine what kind of changes are being attempted in the user's request. If the request is to create a new object, $original will be a valid object reference, but the object will be "blank": it will be just what is returned by the C<new> method on that class. =item CMSPostSave callback($cb, $app, $obj, $original) C<$obj> and C<$original> hold the object which is about to be saved, and the object as it was when this request began, respectively. When the callback routine is called, the new object as C<$obj> has already been committed to the database. This is a convenient time to trigger follow-up actions, such as notification and static-page rebuilds. =item CMSPostDelete callback($cb, $app, $obj) C<$obj> holds the object that has just been removed from the database. This callback is useful when removing data that is associated with the object being removed. =item CMSUploadFile callback($cb, %params) This callback is invoked for each file the user uploads to the blog. It is called for each file, regardless of type. If the user uploads an image, both the C<CMSUploadFile> and C<CMSUploadImage> callbacks are invoked. =head3 Parameters =over 4 =item * File The full file path of the file that has been saved into the blog. =item * Url The URL of the file that has been saved into the blog. =item * Size The length of the file in bytes. =item * Type Either 'image', 'file' or 'thumbnail'. =item * Blog The I<MT::Blog> object the uploaded file is associated with. =back =item CMSUploadImage callback($cb, %params) This callback is invoked for each uploaded image. In the case the user creates a thumbnail for their uploaded image, this callback will be invoked twice-- once for the uploaded original image and a second time for the thumbnail that was generated for it. =over 4 =item * File The full path and filename for the uploaded file. =item * Url The full URL for the uploaded file. =item * Size The length of the uploaded image in bytes. =item * Type Either "image" or "thumbnail" (for generated thumbnails). =item * Height The height of the image in pixels (available if C<Image::Size> module is present). =item * Width The width of the image in pixels (available if C<Image::Size> module is present). =item * ImageType The image identifier as reported by the C<Image::Size> module. Typically, 'GIF', 'JPG' or 'PNG'. =item * Blog The C<MT::Blog> object of the blog the image is associated with. =back =item Rebuild callback($cb, $blog) C<$blog> holds the current blog which is being rebuilt. This callback is called whenever with the CMS a blog completes the process of rebuilding. =back =cut