# Movable Type (r) Open Source (C) 2001-2009 Six Apart, Ltd. # This program is distributed under the terms of the # GNU General Public License, version 2. # # $Id$ package MT::App::CMS; use strict; use base qw( MT::App ); use MT::Util qw( format_ts epoch2ts perl_sha1_digest_hex perl_sha1_digest remove_html ); sub LISTING_DATE_FORMAT () {'%b %e, %Y'} sub LISTING_DATETIME_FORMAT () {'%b %e, %Y'} sub LISTING_TIMESTAMP_FORMAT () {"%Y-%m-%d %I:%M:%S%p"} sub NEW_PHASE () {1} 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', 'type' ]; $app->{template_dir} = 'cms'; $app->{plugin_template_path} = ''; $app->{is_admin} = 1; $app->{default_mode} = 'dashboard'; $app; } sub core_methods { my $app = shift; my $pkg = '$Core::MT::CMS::'; return { 'tools' => "${pkg}Tools::system_check", 'dashboard' => "${pkg}Dashboard::dashboard", 'menu' => "${pkg}Dashboard::dashboard", 'admin' => "${pkg}Dashboard::dashboard", ## Generic handlers 'save' => "${pkg}Common::save", 'edit' => "${pkg}Common::edit", 'view' => "${pkg}Common::edit", 'list' => "${pkg}Common::list", 'delete' => "${pkg}Common::delete", 'search_replace' => "${pkg}Search::search_replace", ## Edit methods 'edit_role' => "${pkg}User::edit_role", 'edit_widget' => "${pkg}Template::edit_widget", ## Listing methods 'list_ping' => "${pkg}TrackBack::list", 'list_entry' => "${pkg}Entry::list", 'list_template' => "${pkg}Template::list", 'list_widget' => "${pkg}Template::list_widget", 'list_page' => "${pkg}Page::list", 'list_comment' => "${pkg}Comment::list", 'list_member' => "${pkg}User::list_member", 'list_user' => "${pkg}User::list", 'list_author' => "${pkg}User::list", 'list_commenter' => "${pkg}Comment::list_commenter", 'list_asset' => "${pkg}Asset::list", 'list_blog' => "${pkg}Blog::list", 'list_category' => "${pkg}Category::list", 'list_tag' => "${pkg}Tag::list", 'list_association' => "${pkg}User::list_association", 'list_role' => "${pkg}User::list_role", 'asset_insert' => "${pkg}Asset::insert", 'asset_userpic' => "${pkg}Asset::asset_userpic", 'save_commenter_perm' => "${pkg}Comment::save_commenter_perm", 'trust_commenter' => "${pkg}Comment::trust_commenter", 'ban_commenter' => "${pkg}Comment::ban_commenter", 'approve_item' => "${pkg}Comment::approve_item", 'unapprove_item' => "${pkg}Comment::unapprove_item", 'preview_entry' => "${pkg}Entry::preview", ## Blog configuration screens 'cfg_archives' => "${pkg}Blog::cfg_archives", 'cfg_prefs' => "${pkg}Blog::cfg_prefs", 'cfg_plugins' => "${pkg}Plugin::cfg_plugins", 'cfg_comments' => "${pkg}Comment::cfg_comments", 'cfg_trackbacks' => "${pkg}TrackBack::cfg_trackbacks", 'cfg_registration' => "${pkg}Comment::cfg_registration", 'cfg_spam' => "${pkg}Comment::cfg_spam", 'cfg_entry' => "${pkg}Entry::cfg_entry", 'cfg_web_services' => "${pkg}Blog::cfg_web_services", ## Save 'save_cat' => "${pkg}Category::save", 'save_entries' => "${pkg}Entry::save_entries", 'save_pages' => "${pkg}Page::save_pages", 'save_entry' => "${pkg}Entry::save", 'save_role' => "${pkg}User::save_role", 'save_widget' => "${pkg}Template::save_widget", ## Delete 'delete_entry' => "${pkg}Entry::delete", 'delete_widget' => "${pkg}Template::delete_widget", ## List actions 'enable_object' => "${pkg}User::enable", 'disable_object' => "${pkg}User::disable", 'list_action' => "${pkg}Tools::do_list_action", 'empty_junk' => "${pkg}Comment::empty_junk", 'handle_junk' => "${pkg}Comment::handle_junk", 'not_junk' => "${pkg}Comment::not_junk", 'ping' => "${pkg}Entry::send_pings", 'rebuild_phase' => "${pkg}Blog::rebuild_phase", 'rebuild' => "${pkg}Blog::rebuild_pages", 'rebuild_new_phase' => "${pkg}Blog::rebuild_new_phase", 'start_rebuild' => "${pkg}Blog::start_rebuild_pages", 'rebuild_confirm' => "${pkg}Blog::rebuild_confirm", 'entry_notify' => "${pkg}AddressBook::entry_notify", 'send_notify' => "${pkg}AddressBook::send_notify", 'start_upload' => "${pkg}Asset::start_upload", 'upload_file' => "${pkg}Asset::upload_file", 'upload_userpic' => "${pkg}User::upload_userpic", 'complete_insert' => "${pkg}Asset::complete_insert", 'complete_upload' => "${pkg}Asset::complete_upload", 'start_upload_entry' => "${pkg}Asset::start_upload_entry", 'logout' => { code => sub { $_[0]->SUPER::logout(@_) }, requires_login => 0, }, 'start_recover' => { code => "${pkg}Tools::start_recover", requires_login => 0, }, 'recover' => { code => "${pkg}Tools::recover_password", requires_login => 0, }, 'new_pw' => { code => "${pkg}Tools::new_password", requires_login => 0, }, 'view_log' => "${pkg}Log::view", 'list_log' => "${pkg}Log::view", 'reset_log' => "${pkg}Log::reset", 'export_log' => "${pkg}Log::export", 'export_notification' => "${pkg}AddressBook::export", 'start_import' => "${pkg}Import::start_import", 'start_export' => "${pkg}Export::start_export", 'export' => "${pkg}Export::export", 'import' => "${pkg}Import::do_import", 'pinged_urls' => "${pkg}Entry::pinged_urls", 'save_entry_prefs' => "${pkg}Entry::save_entry_prefs", 'save_favorite_blogs' => "${pkg}Blog::save_favorite_blogs", 'folder_add' => "${pkg}Category::category_add", 'category_add' => "${pkg}Category::category_add", 'category_do_add' => "${pkg}Category::category_do_add", 'cc_return' => "${pkg}Blog::cc_return", 'reset_blog_templates' => "${pkg}Template::reset_blog_templates", 'handshake' => "${pkg}Blog::handshake", 'itemset_action' => "${pkg}Tools::do_list_action", 'page_action' => "${pkg}Tools::do_page_action", 'cfg_system' => "${pkg}Tools::cfg_system_general", 'cfg_system_users' => "${pkg}User::cfg_system_users", 'cfg_system_feedback' => "${pkg}Comment::cfg_system_feedback", 'save_plugin_config' => "${pkg}Plugin::save_config", 'reset_plugin_config' => "${pkg}Plugin::reset_config", 'save_cfg_system_feedback' => "${pkg}Comment::save_cfg_system_feedback", 'save_cfg_system_general' => "${pkg}Tools::save_cfg_system_general", 'save_cfg_system_users' => "${pkg}User::save_cfg_system_users", 'update_welcome_message' => "${pkg}Blog::update_welcome_message", 'upgrade' => { code => "${pkg}Tools::upgrade", requires_login => 0, }, 'plugin_control' => "${pkg}Plugin::plugin_control", 'recover_profile_password' => "${pkg}User::recover_profile_password", 'rename_tag' => "${pkg}Tag::rename_tag", 'remove_user_assoc' => "${pkg}User::remove_user_assoc", 'revoke_role' => "${pkg}User::revoke_role", 'grant_role' => "${pkg}User::grant_role", 'start_backup' => "${pkg}Tools::start_backup", 'start_restore' => "${pkg}Tools::start_restore", 'backup' => "${pkg}Tools::backup", 'backup_download' => "${pkg}Tools::backup_download", 'restore' => "${pkg}Tools::restore", 'restore_premature_cancel' => "${pkg}Tools::restore_premature_cancel", 'adjust_sitepath' => "${pkg}Tools::adjust_sitepath", 'system_check' => "${pkg}Tools::system_check", 'dialog_refresh_templates' => "${pkg}Template::dialog_refresh_templates", 'dialog_publishing_profile' => "${pkg}Template::dialog_publishing_profile", 'refresh_all_templates' => "${pkg}Template::refresh_all_templates", 'preview_template' => "${pkg}Template::preview", 'publish_index_templates' => "${pkg}Template::publish_index_templates", 'publish_archive_templates' => "${pkg}Template::publish_archive_templates", ## Comment Replies reply => "${pkg}Comment::reply", do_reply => "${pkg}Comment::do_reply", reply_preview => "${pkg}Comment::reply_preview", ## Dialogs 'dialog_restore_upload' => "${pkg}Tools::dialog_restore_upload", 'dialog_adjust_sitepath' => "${pkg}Tools::dialog_adjust_sitepath", 'dialog_post_comment' => "${pkg}Comment::dialog_post_comment", 'dialog_select_weblog' => "${pkg}Blog::dialog_select_weblog", 'dialog_select_sysadmin' => "${pkg}User::dialog_select_sysadmin", 'dialog_grant_role' => "${pkg}User::dialog_grant_role", 'dialog_select_author' => "${pkg}User::dialog_select_author", ## AJAX handlers 'delete_map' => "${pkg}Template::delete_map", 'add_map' => "${pkg}Template::add_map", 'js_tag_check' => "${pkg}Tag::js_tag_check", 'js_tag_list' => "${pkg}Tag::js_tag_list", 'convert_to_html' => "${pkg}Tools::convert_to_html", 'update_list_prefs' => "${pkg}Tools::update_list_prefs", 'js_add_category' => "${pkg}Category::js_add_category", 'remove_userpic' => "${pkg}User::remove_userpic", # declared in MT::App 'update_widget_prefs' => sub { return shift->update_widget_prefs(@_) }, 'js_recent_entries_for_tag' => "${pkg}Tag::js_recent_entries_for_tag", ## DEPRECATED ## 'list_pings' => "${pkg}TrackBack::list", 'list_entries' => "${pkg}Entry::list", 'list_pages' => "${pkg}Page::list", 'list_comments' => "${pkg}Comment::list", 'list_authors' => "${pkg}User::list", 'list_assets' => "${pkg}Asset::list", 'list_cat' => "${pkg}Category::list", 'list_blogs' => "${pkg}Blog::list", 'list_associations' => "${pkg}User::list_association", 'list_roles' => "${pkg}User::list_role", }; } sub core_widgets { my $app = shift; my $pkg = '$Core::MT::CMS::'; 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 => "${pkg}Dashboard::new_version_widget", }, this_is_you => { label => 'This is You', template => 'widget/this_is_you.tmpl', handler => "${pkg}Dashboard::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 => "${pkg}Dashboard::mt_news_widget", singular => 1, set => 'sidebar', }, blog_stats => { label => 'Blog Stats', template => 'widget/blog_stats.tmpl', handler => "${pkg}Dashboard::mt_blog_stats_widget", singular => 1, set => 'main', }, }; } sub core_blog_stats_tabs { my $app = shift; my $pkg = '$Core::MT::CMS::'; return { entry => { label => 'Entries', template => 'widget/blog_stats_entry.tmpl', handler => "${pkg}Dashboard::mt_blog_stats_widget_entry_tab", stats => "${pkg}Dashboard::generate_dashboard_stats_entry_tab", }, comment => { label => 'Comments', template => 'widget/blog_stats_comment.tmpl', handler => "${pkg}Dashboard::mt_blog_stats_widget_comment_tab", stats => "${pkg}Dashboard::generate_dashboard_stats_comment_tab", }, tag_cloud => { label => 'Tag Cloud', handler => "${pkg}Dashboard::mt_blog_stats_tag_cloud_tab", 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", dialog => 'dialog_refresh_templates', condition => sub { !MT->app->blog,; }, order => 1000, }, publishing_profile => { label => "Use Publishing Profile", dialog => 'dialog_publishing_profile', condition => sub { MT->app->blog,; }, order => 1100, }, }, }; } 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; my $pkg = '$Core::MT::CMS::'; return { 'entry' => { 'set_draft' => { label => "Unpublish Entries", order => 200, code => "${pkg}Entry::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 => "${pkg}Tag::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 => "${pkg}Tag::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 => "${pkg}Entry::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 ) && $app->param('filter_val') != MT::Entry::JUNK() && $app->param('filter_key') ne 'spam_entries' }, }, }, 'page' => { 'set_draft' => { label => "Unpublish Pages", order => 200, code => "${pkg}Entry::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 => "${pkg}Tag::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 => "${pkg}Tag::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 => "${pkg}Entry::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 => "${pkg}Tag::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 => "${pkg}Tag::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 => "${pkg}Comment::unapprove_item", permission => 'edit_all_posts,manage_feedback,publish_post', }, }, 'comment' => { 'unapprove_comment' => { label => "Unpublish Comment(s)", order => 100, code => "${pkg}Comment::unapprove_item", permission => 'edit_all_posts,manage_feedback,publish_post', condition => sub { return 1; }, }, 'trust_commenter' => { label => "Trust Commenter(s)", order => 200, code => "${pkg}Comment::trust_commenter_by_comment", permission => 'manage_feedback', }, 'untrust_commenter' => { label => "Untrust Commenter(s)", order => 300, code => "${pkg}Comment::untrust_commenter_by_comment", permission => 'manage_feedback', }, 'ban_commenter' => { label => "Ban Commenter(s)", order => 400, code => "${pkg}Comment::ban_commenter_by_comment", permission => 'manage_feedback', }, 'unban_commenter' => { label => "Unban Commenter(s)", order => 500, code => "${pkg}Comment::unban_commenter_by_comment", permission => 'manage_feedback', }, }, 'commenter' => { 'untrust' => { label => "Untrust Commenter(s)", order => 100, code => "${pkg}Comment::untrust_commenter", permission => 'manage_feedback', }, 'unban' => { label => "Unban Commenter(s)", order => 200, code => "${pkg}Comment::unban_commenter", permission => 'manage_feedback', }, }, 'author' => { 'recover_passwords' => { label => "Recover Password(s)", order => 100, continue_prompt_handler => sub { MT->translate("_WARNING_PASSWORD_RESET_MULTI"); }, code => "${pkg}Tools::recover_passwords", condition => sub { ( $app->user->is_superuser() && MT::Auth->can_recover_password ); }, }, 'delete_user' => { label => "Delete", order => 200, continue_prompt_handler => sub { $app->config->ExternalUserManagement ? MT->translate("_WARNING_DELETE_USER_EUM") : MT->translate("_WARNING_DELETE_USER"); }, code => "${pkg}Common::delete", condition => sub { $app->user->is_superuser(); }, }, }, 'blog' => { refresh_blog_templates => { label => "Refresh Template(s)", continue_prompt_handler => sub { MT->translate("_WARNING_REFRESH_TEMPLATES_FOR_BLOGS"); }, code => sub { my $app = MT->app; $app->param( 'backup', 1 ); require MT::CMS::Template; MT::CMS::Template::refresh_all_templates( $app, @_ ); }, }, }, 'template' => { refresh_tmpl_templates => { label => "Refresh Template(s)", code => "${pkg}Template::refresh_individual_templates", permission => 'edit_templates', order => 100, }, # Now a button! # publish_index_templates => { # label => "Publish Template(s)", # code => "${pkg}Template::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 # ; # }, # order => 200, # }, # Now a button! # publish_archive_templates => { # label => "Publish Template(s)", # code => "${pkg}Template::publish_archive_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 'archive_templates' ? 1 # : 0; # }, # order => 300, # }, copy_templates => { label => "Clone Template(s)", code => "${pkg}Template::clone_templates", permission => 'edit_templates', condition => sub { my $app = MT->app; my $tmpl_type = $app->param('filter_key') || ''; return $tmpl_type eq 'system_templates' ? 0 : $tmpl_type eq 'email_templates' ? 0 : 1; }, order => 400, }, }, }; } sub _entry_label { my $app = MT->instance; my $type = $app->param('type') || 'entry'; $app->model($type)->class_label_plural; } sub core_list_filters { my $app = shift; return { asset => sub { require MT::CMS::Asset; return MT::CMS::Asset::asset_list_filters( $app, @_ ); }, 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 - 7 * 24 * 60 * 60; $ts = epoch2ts( MT->app->blog, $ts ); $args->{join} = MT::Comment->join_on( 'entry_id', { created_on => [ $ts, undef ], junk_status => MT::Comment::NOT_JUNK(), }, { 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} = [ { column => \'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 = format_ts( $format, $from . '000000', undef, MT->current_language ) if $from; $to = 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} = [ $from, $to ]; $args->{range_incl}{authored_on} = 1; }, }, }, ping => { default => { label => 'Non-spam TrackBacks', order => 100, handler => sub { my ( $terms, $args ) = @_; require MT::TBPing; $terms->{junk_status} = MT::TBPing::NOT_JUNK(); }, }, my_posts => { label => 'TrackBacks on my entries', order => 200, handler => sub { my ( $terms, $args ) = @_; require MT::Entry; my $app = MT->instance; require MT::TBPing; require MT::Trackback; $terms->{junk_status} = MT::TBPing::NOT_JUNK(); $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 ) = @_; require MT::TBPing; $terms->{junk_status} = MT::TBPing::NOT_JUNK(); $terms->{visible} = 0; }, }, spam => { label => 'TrackBacks marked as Spam', order => 400, handler => sub { my ( $terms, $args ) = @_; require MT::TBPing; $terms->{junk_status} = MT::TBPing::JUNK(); }, }, 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 = epoch2ts( MT->app->blog, $ts ); $terms->{created_on} = [ $ts, undef ]; $args->{range_incl}{created_on} = 1; $terms->{junk_status} = MT::TBPing::NOT_JUNK(); }, }, }, comment => { default => { label => 'Non-spam Comments', order => 100, handler => sub { my ( $terms, $args ) = @_; require MT::Comment; $terms->{junk_status} = MT::Comment::NOT_JUNK(); }, }, my_posts => { label => 'Comments on my entries', order => 200, handler => sub { my ( $terms, $args ) = @_; require MT::Entry; require MT::Comment; my $app = MT->instance; $terms->{junk_status} = MT::Comment::NOT_JUNK(); # 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 ) = @_; require MT::Comment; $terms->{junk_status} = MT::Comment::NOT_JUNK(); $terms->{visible} = 0; }, }, spam => { label => 'Spam Comments', order => 400, handler => sub { my ( $terms, $args ) = @_; require MT::Comment; $terms->{junk_status} = MT::Comment::JUNK(); }, }, published => { label => 'Published comments', order => 500, handler => sub { my ( $terms, $args ) = @_; $terms->{visible} = 1; }, }, last_7_days => { label => 'Comments in the last 7 days', order => 700, handler => sub { my ( $terms, $args ) = @_; require MT::Comment; my $ts = time - 7 * 24 * 60 * 60; $ts = epoch2ts( MT->app->blog, $ts ); $terms->{created_on} = [ $ts, undef ]; $args->{range_incl}{created_on} = 1; $terms->{junk_status} = MT::Comment::NOT_JUNK(); }, }, _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 ) = @_; require MT::Comment; my $entry_id = int( MT->app->param('filter_val') ); $terms->{entry_id} = $entry_id; $terms->{junk_status} = MT::Comment::NOT_JUNK(); }, }, _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 = format_ts( $format, $from . '000000', undef, MT->current_language ) if $from; $to = 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 ) = @_; require MT::Comment; 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} = MT::Comment::NOT_JUNK(); $terms->{created_on} = [ $from, $to ]; $args->{range_incl}{created_on} = 1; }, }, }, 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 && $perms->can_view_feedback; } else { require MT::Permission; my @blogs = map { $_->blog_id } grep { $_->can_view_feedback } 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,manage_users', system_permission => 'administer', }, 'manage:notification' => { label => "Address Book", mode => 'list', args => { _type => 'notification' }, order => 10000, permission => 'edit_notifications', view => "blog", condition => sub { return $app->config->EnableAddressBook; }, }, 'design:template' => { label => "Templates", mode => 'list', args => { _type => 'template' }, order => 100, permission => 'edit_templates', system_permission => 'edit_templates', }, 'design:widgets' => { label => 'Widgets', mode => 'list_widget', order => 200, 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:settings' => { label => "General", mode => 'cfg_prefs', order => 100, permission => 'administer_blog,edit_config,set_publish_paths', system_permission => 'administer', view => "blog", }, 'prefs:publishing' => { label => "Publishing", mode => 'cfg_archives', order => 110, permission => 'administer_blog,edit_config,set_publish_paths', system_permission => 'administer', view => "blog", }, 'prefs:entry' => { label => "Entry", mode => 'cfg_entry', order => 120, permission => 'administer_blog,edit_config,set_publish_paths', system_permission => 'administer', view => "blog", }, 'prefs:comment' => { label => "Comment", mode => 'cfg_comments', order => 130, permission => 'administer_blog,edit_config,set_publish_paths', system_permission => 'administer', view => "blog", }, 'prefs:trackback' => { label => "TrackBack", mode => 'cfg_trackbacks', order => 140, permission => 'administer_blog,edit_config,set_publish_paths', system_permission => 'administer', view => "blog", }, 'prefs:registration' => { label => "Registration", mode => 'cfg_registration', order => 150, permission => 'administer_blog,edit_config,set_publish_paths', system_permission => 'administer', view => "blog", }, 'prefs:spam' => { label => "Spam", mode => 'cfg_spam', order => 160, permission => 'administer_blog,edit_config,set_publish_paths', system_permission => 'administer', view => "blog", }, 'prefs:web_services' => { label => "Web Services", mode => 'cfg_web_services', order => 170, permission => 'administer_blog,edit_config,set_publish_paths', system_permission => 'administer', view => "blog", }, 'prefs:ip_info' => { label => "IP Banning", mode => 'list', args => { _type => 'banlist' }, order => 180, permission => 'manage_feedback', condition => sub { $app->config->ShowIPInformation; }, view => "blog", }, 'tools:plugins' => { label => "Plugins", order => 100, mode => "cfg_plugins", permission => "administer_blog", system_permission => "manage_plugins", }, 'tools:activity_log' => { label => "Activity Log", order => 200, mode => "view_log", permission => "view_blog_log", system_permission => "view_log", }, 'tools:import' => { label => "Import", order => 300, mode => "start_import", view => "blog", permission => "administer_blog", }, 'tools:export' => { label => "Export", order => 400, mode => "start_export", view => "blog", permission => "administer_blog", }, 'tools:backup' => { label => "Backup", order => 500, mode => "start_backup", permission => "administer_blog", }, 'tools:restore' => { label => "Restore", order => 600, mode => "start_restore", permission => "administer_blog", view => "system", }, 'tools:system_information' => { label => "System Information", order => 700, mode => "tools", 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_'; my $pfx = '$Core::MT::CMS::'; $app->_register_core_callbacks( { # notification callbacks $pkg . 'save_permission_filter.notification' => "${pfx}AddressBook::can_save", $pkg . 'save_filter.notification' => "${pfx}AddressBook::save_filter", $pkg . 'post_delete.notification' => "${pfx}AddressBook::post_delete", # banlist callbacks $pkg . 'save_permission_filter.banlist' => "${pfx}BanList::can_save", $pkg . 'save_filter.banlist' => "${pfx}BanList::save_filter", # associations $pkg . 'delete_permission_filter.association' => "${pfx}User::can_delete_association", # user callbacks $pkg . 'edit.author' => "${pfx}User::edit", $pkg . 'view_permission_filter.author' => "${pfx}User::can_view", $pkg . 'save_permission_filter.author' => "${pfx}User::can_save", $pkg . 'delete_permission_filter.author' => "${pfx}User::can_delete", $pkg . 'save_filter.author' => "${pfx}User::save_filter", $pkg . 'pre_save.author' => "${pfx}User::pre_save", $pkg . 'post_save.author' => "${pfx}User::post_save", $pkg . 'post_delete.author' => "${pfx}User::post_delete", # blog callbacks $pkg . 'edit.blog' => "${pfx}Blog::edit", $pkg . 'view_permission_filter.blog' => "${pfx}Blog::can_view", $pkg . 'save_permission_filter.blog' => "${pfx}Blog::can_save", $pkg . 'delete_permission_filter.blog' => "${pfx}Blog::can_delete", $pkg . 'pre_save.blog' => "${pfx}Blog::pre_save", $pkg . 'post_save.blog' => "${pfx}Blog::post_save", $pkg . 'save_filter.blog' => "${pfx}Blog::save_filter", $pkg . 'post_delete.blog' => "${pfx}Blog::post_delete", # folder callbacks $pkg . 'edit.folder' => "${pfx}Folder::edit", $pkg . 'view_permission_filter.folder' => "${pfx}Folder::can_view", $pkg . 'save_permission_filter.folder' => "${pfx}Folder::can_save", $pkg . 'delete_permission_filter.folder' => "${pfx}Folder::can_delete", $pkg . 'pre_save.folder' => "${pfx}Folder::pre_save", $pkg . 'post_save.folder' => "${pfx}Folder::post_save", $pkg . 'save_filter.folder' => "${pfx}Folder::save_filter", $pkg . 'post_delete.folder' => "${pfx}Folder::post_delete", # category callbacks $pkg . 'edit.category' => "${pfx}Category::edit", $pkg . 'view_permission_filter.category' => "${pfx}Category::can_view", $pkg . 'save_permission_filter.category' => "${pfx}Category::can_save", $pkg . 'delete_permission_filter.category' => "${pfx}Category::can_delete", $pkg . 'pre_save.category' => "${pfx}Category::pre_save", $pkg . 'post_save.category' => "${pfx}Category::post_save", $pkg . 'save_filter.category' => "${pfx}Category::save_filter", $pkg . 'post_delete.category' => "${pfx}Category::post_delete", # comment callbacks $pkg . 'edit.comment' => "${pfx}Comment::edit", $pkg . 'view_permission_filter.comment' => "${pfx}Comment::can_view", $pkg . 'save_permission_filter.comment' => "${pfx}Comment::can_save", $pkg . 'delete_permission_filter.comment' => "${pfx}Comment::can_delete", $pkg . 'pre_save.comment' => "${pfx}Comment::pre_save", $pkg . 'post_save.comment' => "${pfx}Comment::post_save", $pkg . 'post_delete.comment' => "${pfx}Comment::post_delete", # commenter callbacks $pkg . 'edit.commenter' => "${pfx}Comment::edit_commenter", $pkg . 'view_permission_filter.commenter' => "${pfx}Comment::can_view_commenter", $pkg . 'delete_permission_filter.commenter' => "${pfx}Comment::can_delete_commenter", # entry callbacks $pkg . 'edit.entry' => "${pfx}Entry::edit", $pkg . 'view_permission_filter.entry' => "${pfx}Entry::can_view", $pkg . 'delete_permission_filter.entry' => "${pfx}Entry::can_delete", $pkg . 'pre_save.entry' => "${pfx}Entry::pre_save", $pkg . 'post_save.entry' => "${pfx}Entry::post_save", $pkg . 'post_delete.entry' => "${pfx}Entry::post_delete", # page callbacks $pkg . 'edit.page' => "${pfx}Page::edit", $pkg . 'view_permission_filter.page' => "${pfx}Page::can_view", $pkg . 'delete_permission_filter.page' => "${pfx}Page::can_delete", $pkg . 'pre_save.page' => "${pfx}Page::pre_save", $pkg . 'post_save.page' => "${pfx}Page::post_save", $pkg . 'post_delete.page' => "${pfx}Page::post_delete", # ping callbacks $pkg . 'edit.ping' => "${pfx}TrackBack::edit", $pkg . 'view_permission_filter.ping' => "${pfx}TrackBack::can_view", $pkg . 'save_permission_filter.ping' => "${pfx}TrackBack::can_save", $pkg . 'delete_permission_filter.ping' => "${pfx}TrackBack::can_delete", $pkg . 'pre_save.ping' => "${pfx}TrackBack::pre_save", $pkg . 'post_save.ping' => "${pfx}TrackBack::post_save", $pkg . 'post_delete.ping' => "${pfx}TrackBack::post_delete", # template callbacks $pkg . 'edit.template' => "${pfx}Template::edit", $pkg . 'view_permission_filter.template' => "${pfx}Template::can_view", $pkg . 'save_permission_filter.template' => "${pfx}Template::can_save", $pkg . 'delete_permission_filter.template' => "${pfx}Template::can_delete", $pkg . 'pre_save.template' => "${pfx}Template::pre_save", $pkg . 'post_save.template' => "${pfx}Template::post_save", $pkg . 'post_delete.template' => "${pfx}Template::post_delete", 'restore' => "${pfx}Template::restore_widgetmanagers", # tags $pkg . 'delete_permission_filter.tag' => "${pfx}Tag::can_delete", $pkg . 'post_delete.tag' => "${pfx}Tag::post_delete", # junk-related callbacks #'HandleJunk' => \&_builtin_spam_handler, #'HandleNotJunk' => \&_builtin_spam_unhandler, $pkg . 'not_junk_test' => "${pfx}Common::not_junk_test", # assets $pkg . 'edit.asset' => "${pfx}Asset::edit", $pkg . 'view_permission_filter.asset' => "${pfx}Asset::can_view", $pkg . 'delete_permission_filter.asset' => "${pfx}Asset::can_delete", $pkg . 'pre_save.asset' => "${pfx}Asset::pre_save", $pkg . 'post_save.asset' => "${pfx}Asset::post_save", $pkg . 'post_delete.asset' => "${pfx}Asset::post_delete", 'template_param.edit_asset' => "${pfx}Asset::template_param_edit", } ); } 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 = perl_sha1_digest_hex( 'feed:' . $pw ); return $feed_token eq $auth_token; } else { return $app->SUPER::validate_magic(@_); } } 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_newblog} = $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->{ "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; $param->{blog_template_set} = $blog->template_set; } 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); if ( !ref($page) || ( $page->isa('MT::Template') && !$page->param('page_actions') ) ) { # Using a sub here to delay the loading of page actions, since not all # templates actually utilize them. $param->{page_actions} ||= sub { $app->page_actions( $app->mode ) }; } $app->SUPER::build_page( $page, $param ); } 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, permissions => { not => "'comment'" } } ); $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 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 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 => 25, 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} || 25, 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 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 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 my $tmpl_edit_link = $app->uri( mode => 'view', args => { blog_id => $tmpl->blog_id, '_type' => 'template', id => $tmpl->id } ); if ( $app->param('fs') ) { $param->{fs} = 1; if ( exists $app->{goback} ) { $param->{goback} = "window.location='" . $app->{goback} . "'"; if ( $tmpl_edit_link ne $app->{goback} ) { push @{ $param->{button_loop} ||= [] }, { link => $tmpl_edit_link, label => $app->translate("Edit Template"), }; } } else { $param->{goback} = "window.location='$tmpl_edit_link'"; } } else { push @{ $param->{button_loop} ||= [] }, { link => $tmpl_edit_link, label => $app->translate("Edit Template"), }; } } if ( !exists( $param->{goback} ) && exists( $app->{goback} ) ) { $param->{goback} = "window.location='" . $app->{goback} . "'"; } else { 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->{value} || $app->translate('Go Back'); } return $app->SUPER::show_error($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 }; %default = map { lc $_ => $default{$_} } keys %default; if ( $perm && $perm->entry_prefs ) { $prefs = $perm->entry_prefs; } else { if ( 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{ lc $p } if $default{ lc $p }; } $prefs = join ',', @p; $prefs ||= 'Custom'; } elsif ( lc( $default{type} ) ne 'default' ) { $prefs = 'Advanced'; } $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, $fields ) = @_; 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; push @$fields, { name => $name }; } 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; push @$fields, { name => $def }; } if ( lc($p) eq 'advanced' ) { foreach my $def (qw(excerpt feedback)) { $param->{ 'disp_prefs_show_' . $def } = 1; push @$fields, { name => $def }; } } } else { $param->{ 'disp_prefs_show_' . $p } = 1; push @$fields, { name => $p }; } } } } 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 ); $param{disp_prefs_default_fields} = \@fields; } else { ( $prefs, $pos ) = split /\|/, $prefs; } $app->_parse_entry_prefs( $prefs, \%param, \my @custom_fields ); 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 ); 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; $param{disp_prefs_custom_fields} = \@custom_fields if @custom_fields; } 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 _convert_word_chars { my ( $app, $s ) = @_; return '' if !defined($s) || ( $s eq '' ); 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; require MT::Util; return MT::Util::convert_word_chars($s, $smart_replace); } sub _translate_naughty_words { my ($app, $entry) = @_; require MT::Util; return MT::Util::translate_naughty_words($entry); } 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 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 listify { my ($arr) = @_; my @ret; return unless ref($arr) eq 'ARRAY'; foreach (@$arr) { push @ret, { name => $_ }; } \@ret; } 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 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 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-preview-' . perl_sha1_digest_hex($data); } 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 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 _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; } if ( $type && lc $type eq 'custom' ) { my @fields = split /,/, $q->param('custom_prefs'); foreach (@fields) { $prefs .= ',' if $prefs ne ''; $prefs .= $_; } } $prefs .= '|' . $q->param('bar_position'); $prefs; } # 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 ) { if ( my $start_time = $app->param('start_time') ) { $app->publisher->start_time($start_time); } # now, rebuild indexes for affected blogs my @blogs = $app->param('blog_ids'); if (@blogs) { $app->run_callbacks('pre_build') if @blogs; 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 $blog_id = int( $app->param('blog_id') ); my $this_blog = MT::Blog->load($blog_id) if $blog_id; $app->run_callbacks( 'rebuild', $this_blog ); $app->run_callbacks('post_build'); } return $app->call_return; } if ( exists $options{how} && ( $options{how} eq NEW_PHASE ) ) { my $start_time = time; $app->run_callbacks('pre_build'); my $params = { return_args => $app->return_args, blog_id => $app->param('blog_id') || 0, id => [ keys %$rebuild_set ], start_time => $start_time, }; 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 $start_time = $app->param('start_time'); $app->publisher->start_time($start_time); 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 ) { # die "had to rebless? $e"; # 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, start_time => $start_time, }; my %param = ( is_full_screen => 1, redirect_target => $app->uri( mode => 'rebuild_phase', args => $params ) ); return $app->load_tmpl( 'rebuilding.tmpl', \%param ); } } 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 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 _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 => MT::TBPing::NOT_JUNK() }, { 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} ? ' ' . $obj->label . '' : '' ); } 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 => MT::TBPing::NOT_JUNK(), } ); } } $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 publish_error { my $app = shift; my ($msg) = @_; if ( defined $app->errstr ) { require MT::Log; $app->log( { message => ( defined $msg ? $msg : $app->translate( "Error during publishing: [_1]", $app->errstr ) ), class => "system", level => MT::Log::ERROR(), category => "publish", } ); } return undef; } 1; __END__ =head1 NAME MT::App::CMS =head1 SYNOPSIS The I 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. =cut