root/trunk/lib/MT/App/CMS.pm @ 3082

Revision 3082, 112.1 kB (checked in by bchoate, 14 months ago)

Merging fireball branch changes to-date to trunk: svn merge -r2974:3081 http://code.sixapart.com/svn/movabletype/branches/fireball .

  • Property svn:keywords set to Author Date Id Revision
Line 
1# Movable Type (r) Open Source (C) 2001-2008 Six Apart, Ltd.
2# This program is distributed under the terms of the
3# GNU General Public License, version 2.
4#
5# $Id$
6
7package MT::App::CMS;
8
9use strict;
10use base qw( MT::App );
11
12use MT::Util qw( format_ts epoch2ts perl_sha1_digest_hex perl_sha1_digest
13    remove_html );
14
15sub LISTING_DATE_FORMAT ()      {'%b %e, %Y'}
16sub LISTING_DATETIME_FORMAT ()  {'%b %e, %Y'}
17sub LISTING_TIMESTAMP_FORMAT () {"%Y-%m-%d %I:%M:%S%p"}
18sub NEW_PHASE ()                {1}
19
20sub id {'cms'}
21
22sub init {
23    my $app = shift;
24    $app->SUPER::init(@_) or return;
25    $app->{state_params} = [
26        '_type',      'id',         'tab',     'offset',
27        'filter',     'filter_val', 'blog_id', 'is_power_edit',
28        'filter_key', 'type'
29    ];
30    $app->{template_dir}         = 'cms';
31    $app->{plugin_template_path} = '';
32    $app->{is_admin}             = 1;
33    $app->{default_mode}         = 'dashboard';
34    $app;
35}
36
37sub core_methods {
38    my $app = shift;
39    my $pkg = '$Core::MT::CMS::';
40    return {
41        'tools'     => "${pkg}Tools::system_check",
42        'dashboard' => "${pkg}Dashboard::dashboard",
43        'menu'      => "${pkg}Dashboard::dashboard",
44        'admin'     => "${pkg}Dashboard::dashboard",
45
46        ## Generic handlers
47        'save'           => "${pkg}Common::save",
48        'edit'           => "${pkg}Common::edit",
49        'view'           => "${pkg}Common::edit",
50        'list'           => "${pkg}Common::list",
51        'delete'         => "${pkg}Common::delete",
52        'search_replace' => "${pkg}Search::search_replace",
53
54        ## Edit methods
55        'edit_role'   => "${pkg}User::edit_role",
56        'edit_widget' => "${pkg}Template::edit_widget",
57
58        ## Listing methods
59        'list_ping'     => "${pkg}TrackBack::list",
60        'list_entry'    => "${pkg}Entry::list",
61        'list_template' => "${pkg}Template::list",
62        'list_widget'   => "${pkg}Template::list_widget",
63        'list_page'     => "${pkg}Page::list",
64        'list_comment'  => {
65            handler    => "${pkg}Comment::list",
66            permission => 'view_feedback',
67        },
68        'list_member'      => "${pkg}User::list_member",
69        'list_user'        => "${pkg}User::list",
70        'list_author'      => "${pkg}User::list",
71        'list_commenter'   => "${pkg}Comment::list_commenter",
72        'list_asset'       => "${pkg}Asset::list",
73        'list_blog'        => "${pkg}Blog::list",
74        'list_category'    => "${pkg}Category::list",
75        'list_tag'         => "${pkg}Tag::list",
76        'list_association' => "${pkg}User::list_association",
77        'list_role'        => "${pkg}User::list_role",
78
79        'asset_insert'        => "${pkg}Asset::insert",
80        'asset_userpic'       => "${pkg}Asset::asset_userpic",
81        'save_commenter_perm' => "${pkg}Comment::save_commenter_perm",
82        'trust_commenter'     => "${pkg}Comment::trust_commenter",
83        'ban_commenter'       => "${pkg}Comment::ban_commenter",
84        'approve_item'        => "${pkg}Comment::approve_item",
85        'unapprove_item'      => "${pkg}Comment::unapprove_item",
86        'preview_entry'       => "${pkg}Entry::preview",
87
88        ## Blog configuration screens
89        'cfg_archives'     => "${pkg}Blog::cfg_archives",
90        'cfg_prefs'        => "${pkg}Blog::cfg_prefs",
91        'cfg_plugins'      => "${pkg}Plugin::cfg_plugins",
92        'cfg_comments'     => "${pkg}Comment::cfg_comments",
93        'cfg_trackbacks'   => "${pkg}TrackBack::cfg_trackbacks",
94        'cfg_registration' => "${pkg}Comment::cfg_registration",
95        'cfg_spam'         => "${pkg}Comment::cfg_spam",
96        'cfg_entry'        => "${pkg}Entry::cfg_entry",
97        'cfg_web_services' => "${pkg}Blog::cfg_web_services",
98
99        ## Save
100        'save_cat'     => "${pkg}Category::save",
101        'save_entries' => "${pkg}Entry::save_entries",
102        'save_pages'   => "${pkg}Page::save_pages",
103        'save_entry'   => "${pkg}Entry::save",
104        'save_role'    => "${pkg}User::save_role",
105        'save_widget'  => "${pkg}Template::save_widget",
106
107        ## Delete
108        'delete_entry'  => "${pkg}Entry::delete",
109        'delete_widget' => "${pkg}Template::delete_widget",
110
111        ## List actions
112        'enable_object'  => "${pkg}User::enable",
113        'disable_object' => "${pkg}User::disable",
114        'list_action'    => "${pkg}Tools::do_list_action",
115        'empty_junk'     => "${pkg}Comment::empty_junk",
116        'handle_junk'    => "${pkg}Comment::handle_junk",
117        'not_junk'       => "${pkg}Comment::not_junk",
118
119        'ping'               => "${pkg}Entry::send_pings",
120        'rebuild_phase'      => "${pkg}Blog::rebuild_phase",
121        'rebuild'            => "${pkg}Blog::rebuild_pages",
122        'rebuild_new_phase'  => "${pkg}Blog::rebuild_new_phase",
123        'start_rebuild'      => "${pkg}Blog::start_rebuild_pages",
124        'rebuild_confirm'    => "${pkg}Blog::rebuild_confirm",
125        'entry_notify'       => "${pkg}AddressBook::entry_notify",
126        'send_notify'        => "${pkg}AddressBook::send_notify",
127        'start_upload'       => "${pkg}Asset::start_upload",
128        'upload_file'        => "${pkg}Asset::upload_file",
129        'upload_userpic'     => "${pkg}User::upload_userpic",
130        'complete_insert'    => "${pkg}Asset::complete_insert",
131        'complete_upload'    => "${pkg}Asset::complete_upload",
132        'start_upload_entry' => "${pkg}Asset::start_upload_entry",
133        'logout'             => {
134            code           => sub { $_[0]->SUPER::logout(@_) },
135            requires_login => 0,
136        },
137        'start_recover' => {
138            code           => "${pkg}Tools::start_recover",
139            requires_login => 0,
140        },
141        'recover' => {
142            code           => "${pkg}Tools::recover_password",
143            requires_login => 0,
144        },
145
146        'view_log'             => "${pkg}Log::view",
147        'list_log'             => "${pkg}Log::view",
148        'reset_log'            => "${pkg}Log::reset",
149        'export_log'           => "${pkg}Log::export",
150        'export_notification'  => "${pkg}AddressBook::export",
151        'start_import'         => "${pkg}Import::start_import",
152        'start_export'         => "${pkg}Export::start_export",
153        'export'               => "${pkg}Export::export",
154        'import'               => "${pkg}Import::do_import",
155        'pinged_urls'          => "${pkg}Entry::pinged_urls",
156        'save_entry_prefs'     => "${pkg}Entry::save_entry_prefs",
157        'save_favorite_blogs'  => "${pkg}Blog::save_favorite_blogs",
158        'folder_add'           => "${pkg}Category::category_add",
159        'category_add'         => "${pkg}Category::category_add",
160        'category_do_add'      => "${pkg}Category::category_do_add",
161        'cc_return'            => "${pkg}Blog::cc_return",
162        'reset_blog_templates' => "${pkg}Template::reset_blog_templates",
163        'handshake'            => "${pkg}Blog::handshake",
164        'itemset_action'       => "${pkg}Tools::do_list_action",
165        'page_action'          => "${pkg}Tools::do_page_action",
166        'cfg_system'           => "${pkg}Tools::cfg_system_general",
167        'cfg_system_users'     => "${pkg}User::cfg_system_users",
168        'cfg_system_feedback'  => "${pkg}Comment::cfg_system_feedback",
169        'save_plugin_config'   => "${pkg}Plugin::save_config",
170        'reset_plugin_config'  => "${pkg}Plugin::reset_config",
171        'save_cfg_system_feedback' =>
172            "${pkg}Comment::save_cfg_system_feedback",
173        'save_cfg_system_general' => "${pkg}Tools::save_cfg_system_general",
174        'save_cfg_system_users'   => "${pkg}User::save_cfg_system_users",
175        'update_welcome_message'  => "${pkg}Blog::update_welcome_message",
176        'upgrade'                 => {
177            code           => "${pkg}Tools::upgrade",
178            requires_login => 0,
179        },
180        'plugin_control'           => "${pkg}Plugin::plugin_control",
181        'recover_profile_password' => "${pkg}User::recover_profile_password",
182        'rename_tag'               => "${pkg}Tag::rename_tag",
183        'remove_user_assoc'        => "${pkg}User::remove_user_assoc",
184        'revoke_role'              => "${pkg}User::revoke_role",
185        'grant_role'               => "${pkg}User::grant_role",
186        'start_backup'             => "${pkg}Tools::start_backup",
187        'start_restore'            => "${pkg}Tools::start_restore",
188        'backup'                   => "${pkg}Tools::backup",
189        'backup_download'          => "${pkg}Tools::backup_download",
190        'restore'                  => "${pkg}Tools::restore",
191        'restore_premature_cancel' => "${pkg}Tools::restore_premature_cancel",
192        'adjust_sitepath'          => "${pkg}Tools::adjust_sitepath",
193        'system_check'             => "${pkg}Tools::system_check",
194        'dialog_refresh_templates' =>
195            "${pkg}Template::dialog_refresh_templates",
196        'dialog_publishing_profile' =>
197            "${pkg}Template::dialog_publishing_profile",
198        'refresh_all_templates' => "${pkg}Template::refresh_all_templates",
199        'preview_template'      => "${pkg}Template::preview",
200        'publish_index_templates' =>
201            "${pkg}Template::publish_index_templates",
202        'publish_archive_templates' =>
203            "${pkg}Template::publish_archive_templates",
204
205        ## Comment Replies
206        reply         => "${pkg}Comment::reply",
207        do_reply      => "${pkg}Comment::do_reply",
208        reply_preview => "${pkg}Comment::reply_preview",
209
210        ## Dialogs
211        'dialog_restore_upload'  => "${pkg}Tools::dialog_restore_upload",
212        'dialog_adjust_sitepath' => "${pkg}Tools::dialog_adjust_sitepath",
213        'dialog_post_comment'    => "${pkg}Comment::dialog_post_comment",
214        'dialog_select_weblog'   => "${pkg}Blog::dialog_select_weblog",
215        'dialog_select_sysadmin' => "${pkg}User::dialog_select_sysadmin",
216        'dialog_grant_role'      => "${pkg}User::dialog_grant_role",
217        'dialog_select_author'   => "${pkg}User::dialog_select_author",
218
219        ## AJAX handlers
220        'delete_map'        => "${pkg}Template::delete_map",
221        'add_map'           => "${pkg}Template::add_map",
222        'js_tag_check'      => "${pkg}Tag::js_tag_check",
223        'js_tag_list'       => "${pkg}Tag::js_tag_list",
224        'convert_to_html'   => "${pkg}Tools::convert_to_html",
225        'update_list_prefs' => "${pkg}Tools::update_list_prefs",
226        'js_add_category'   => "${pkg}Category::js_add_category",
227        'remove_userpic'    => "${pkg}User::remove_userpic",
228
229        # declared in MT::App
230        'update_widget_prefs' =>
231            sub { return shift->update_widget_prefs(@_) },
232
233        'js_recent_entries_for_tag' => "${pkg}Tag::js_recent_entries_for_tag",
234
235        ## DEPRECATED ##
236        'list_pings'    => "${pkg}TrackBack::list",
237        'list_entries'  => "${pkg}Entry::list",
238        'list_pages'    => "${pkg}Page::list",
239        'list_comments' => {
240            handler    => "${pkg}Comment::list",
241            permission => 'view_feedback',
242        },
243        'list_authors'      => "${pkg}User::list",
244        'list_assets'       => "${pkg}Asset::list",
245        'list_cat'          => "${pkg}Category::list",
246        'list_blogs'        => "${pkg}Blog::list",
247        'list_associations' => "${pkg}User::list_association",
248        'list_roles'        => "${pkg}User::list_role",
249    };
250}
251
252sub core_widgets {
253    my $app = shift;
254    my $pkg = '$Core::MT::CMS::';
255    return {
256        new_install => {
257            template => 'widget/new_install.tmpl',
258            set      => 'main',    # forces this widget to the main group
259            singular => 1,
260        },
261        new_user => {
262            template => 'widget/new_user.tmpl',
263            set      => 'main',    # forces this widget to the main group
264            singular => 1,
265        },
266        new_version => {
267            template => 'widget/new_version.tmpl',
268            set      => 'main',
269            singular => 1,
270            handler  => "${pkg}Dashboard::new_version_widget",
271        },
272        this_is_you => {
273            label    => 'This is You',
274            template => 'widget/this_is_you.tmpl',
275            handler  => "${pkg}Dashboard::this_is_you_widget",
276            set      => 'sidebar',
277            singular => 1,
278        },
279        mt_shortcuts => {
280            label    => 'Handy Shortcuts',
281            template => 'widget/mt_shortcuts.tmpl',
282            singular => 1,
283            set      => 'sidebar',
284        },
285        mt_news => {
286            label    => 'Movable Type News',
287            template => 'widget/mt_news.tmpl',
288            handler  => "${pkg}Dashboard::mt_news_widget",
289            singular => 1,
290            set      => 'sidebar',
291        },
292        blog_stats => {
293            label    => 'Blog Stats',
294            template => 'widget/blog_stats.tmpl',
295            handler  => "${pkg}Dashboard::mt_blog_stats_widget",
296            singular => 1,
297            set      => 'main',
298        },
299    };
300}
301
302sub core_blog_stats_tabs {
303    my $app = shift;
304    my $pkg = '$Core::MT::CMS::';
305    return {
306        entry => {
307            label    => 'Entries',
308            template => 'widget/blog_stats_entry.tmpl',
309            handler  => "${pkg}Dashboard::mt_blog_stats_widget_entry_tab",
310            stats    => "${pkg}Dashboard::generate_dashboard_stats_entry_tab",
311        },
312        comment => {
313            label    => 'Comments',
314            template => 'widget/blog_stats_comment.tmpl',
315            handler  => "${pkg}Dashboard::mt_blog_stats_widget_comment_tab",
316            stats => "${pkg}Dashboard::generate_dashboard_stats_comment_tab",
317        },
318        tag_cloud => {
319            label    => 'Tag Cloud',
320            handler  => "${pkg}Dashboard::mt_blog_stats_tag_cloud_tab",
321            template => 'widget/blog_stats_tag_cloud.tmpl',
322        },
323    };
324}
325
326sub core_page_actions {
327    return {
328        list_templates => {
329            refresh_all_blog_templates => {
330                label     => "Refresh Blog Templates",
331                dialog    => 'dialog_refresh_templates',
332                condition => sub {
333                    MT->app->blog,;
334                },
335                order => 1000,
336            },
337            refresh_global_templates => {
338                label     => "Refresh Global Templates",
339                dialog    => 'dialog_refresh_templates',
340                condition => sub {
341                    !MT->app->blog,;
342                },
343                order => 1000,
344            },
345            publishing_profile => {
346                label     => "Use Publishing Profile",
347                dialog    => 'dialog_publishing_profile',
348                condition => sub {
349                    MT->app->blog,;
350                },
351                order => 1100,
352            },
353        },
354    };
355}
356
357sub init_plugins {
358    my $app = shift;
359
360    # This has to be done prior to plugin initialization since we
361    # may have plugins that register themselves using some of the
362    # older callback names. The callback aliases are declared
363    # in init_core_callbacks.
364    $app->init_core_callbacks();
365    $app->SUPER::init_plugins(@_);
366}
367
368sub init_request {
369    my $app = shift;
370    $app->SUPER::init_request(@_);
371    $app->set_no_cache;
372    $app->{requires_login} = 1
373        unless exists $app->{requires_login};   # by default, we require login
374
375    my $mode = $app->mode;
376
377    # Global 'blog_id' parameter check; if we get something
378    # other than an integer, die
379    if ( my $blog_id = $app->param('blog_id') ) {
380        if ( $blog_id ne int($blog_id) ) {
381            die $app->translate("Invalid request");
382        }
383    }
384
385    unless ( defined $app->{upgrade_required} ) {
386        $app->{upgrade_required} = 0;
387        if (   ( $mode ne 'logout' )
388            && ( $mode ne 'start_recover' )
389            && ( $mode ne 'recover' )
390            && ( $mode ne 'upgrade' ) )
391        {
392            my $schema  = $app->config('SchemaVersion');
393            my $version = $app->config('MTVersion');
394            if (   !$schema
395                || ( $schema < $app->schema_version )
396                || ( ( !$version || ( $version < $app->version_number ) )
397                    && $app->config->NotifyUpgrade )
398                )
399            {
400                $app->{upgrade_required} = 1;
401            }
402            else {
403                foreach my $plugin (@MT::Components) {
404                    if ( $plugin->needs_upgrade ) {
405                        $app->{upgrade_required} = 1;
406                        last;
407                    }
408                }
409            }
410        }
411    }
412
413    if ( $app->{upgrade_required} ) {
414        $app->{requires_login} = 0;
415        $app->mode('upgrade');
416    }
417}
418
419sub core_list_actions {
420    my $app = shift;
421    my $pkg = '$Core::MT::CMS::';
422    return {
423        'entry' => {
424            'set_draft' => {
425                label      => "Unpublish Entries",
426                order      => 200,
427                code       => "${pkg}Entry::draft_entries",
428                permission => 'edit_all_posts,publish_post',
429                condition  => sub {
430                    return 0 if $app->mode eq 'view';
431                    return $app->blog && $app->blog->site_path ? 1 : 0;
432                    }
433            },
434            'add_tags' => {
435                label       => "Add Tags...",
436                order       => 300,
437                code        => "${pkg}Tag::add_tags_to_entries",
438                input       => 1,
439                input_label => 'Tags to add to selected entries',
440                permission  => 'edit_all_posts',
441                condition   => sub {
442                    return $app->mode ne 'view';
443                    }
444            },
445            'remove_tags' => {
446                label       => "Remove Tags...",
447                order       => 400,
448                code        => "${pkg}Tag::remove_tags_from_entries",
449                input       => 1,
450                input_label => 'Tags to remove from selected entries',
451                permission  => 'edit_all_posts',
452                condition   => sub {
453                    return $app->mode ne 'view';
454                    }
455            },
456            'open_batch_editor' => {
457                label     => "Batch Edit Entries",
458                code      => "${pkg}Entry::open_batch_editor",
459                order     => 500,
460                condition => sub {
461                    return 0 if $app->mode eq 'view';
462                    $app->param('blog_id')
463                        && ( $app->user->is_superuser()
464                        || $app->permissions->can_edit_all_posts );
465                },
466            },
467        },
468        'page' => {
469            'set_draft' => {
470                label      => "Unpublish Pages",
471                order      => 200,
472                code       => "${pkg}Entry::draft_entries",
473                permission => 'manage_pages',
474                condition  => sub {
475                    return 0 if $app->mode eq 'view';
476                    return $app->blog && $app->blog->site_path ? 1 : 0;
477                },
478            },
479            'add_tags' => {
480                label       => "Add Tags...",
481                order       => 300,
482                code        => "${pkg}Tag::add_tags_to_entries",
483                input       => 1,
484                input_label => 'Tags to add to selected pages',
485                permission  => 'manage_pages',
486                condition   => sub {
487                    return $app->mode ne 'view';
488                },
489            },
490            'remove_tags' => {
491                label       => "Remove Tags...",
492                order       => 400,
493                code        => "${pkg}Tag::remove_tags_from_entries",
494                input       => 1,
495                input_label => 'Tags to remove from selected pages',
496                permission  => 'manage_pages',
497                condition   => sub {
498                    return $app->mode ne 'view';
499                },
500            },
501            'open_batch_editor' => {
502                label     => "Batch Edit Pages",
503                code      => "${pkg}Entry::open_batch_editor",
504                order     => 500,
505                condition => sub {
506                    return 0 if $app->mode eq 'view';
507                    $app->param('blog_id')
508                        && ( $app->user->is_superuser()
509                        || $app->permissions->can_manage_pages );
510                },
511            },
512        },
513        'asset' => {
514            'add_tags' => {
515                label       => "Add Tags...",
516                order       => 100,
517                code        => "${pkg}Tag::add_tags_to_assets",
518                input       => 1,
519                input_label => 'Tags to add to selected assets',
520                permission  => 'edit_assets',
521            },
522            'remove_tags' => {
523                label       => "Remove Tags...",
524                order       => 200,
525                code        => "${pkg}Tag::remove_tags_from_assets",
526                input       => 1,
527                input_label => 'Tags to remove from selected assets',
528                permission  => 'edit_assets',
529            },
530        },
531        'ping' => {
532            'unapprove_ping' => {
533                label      => "Unpublish TrackBack(s)",
534                order      => 100,
535                code       => "${pkg}Comment::unapprove_item",
536                permission => 'manage_feedback,publish_post',
537            },
538        },
539        'comment' => {
540            'unapprove_comment' => {
541                label      => "Unpublish Comment(s)",
542                order      => 100,
543                code       => "${pkg}Comment::unapprove_item",
544                permission => 'manage_feedback,publish_post',
545                condition  => sub {
546                    return 1;
547                },
548            },
549            'trust_commenter' => {
550                label      => "Trust Commenter(s)",
551                order      => 200,
552                code       => "${pkg}Comment::trust_commenter_by_comment",
553                permission => 'manage_feedback',
554            },
555            'untrust_commenter' => {
556                label      => "Untrust Commenter(s)",
557                order      => 300,
558                code       => "${pkg}Comment::untrust_commenter_by_comment",
559                permission => 'manage_feedback',
560            },
561            'ban_commenter' => {
562                label      => "Ban Commenter(s)",
563                order      => 400,
564                code       => "${pkg}Comment::ban_commenter_by_comment",
565                permission => 'manage_feedback',
566            },
567            'unban_commenter' => {
568                label      => "Unban Commenter(s)",
569                order      => 500,
570                code       => "${pkg}Comment::unban_commenter_by_comment",
571                permission => 'manage_feedback',
572            },
573        },
574        'commenter' => {
575            'untrust' => {
576                label      => "Untrust Commenter(s)",
577                order      => 100,
578                code       => "${pkg}Comment::untrust_commenter",
579                permission => 'manage_feedback',
580            },
581            'unban' => {
582                label      => "Unban Commenter(s)",
583                order      => 200,
584                code       => "${pkg}Comment::unban_commenter",
585                permission => 'manage_feedback',
586            },
587        },
588        'author' => {
589            'recover_passwords' => {
590                label                   => "Recover Password(s)",
591                order                   => 100,
592                continue_prompt_handler => sub {
593                    MT->translate("_WARNING_PASSWORD_RESET_MULTI");
594                },
595                code      => "${pkg}Tools::recover_passwords",
596                condition => sub {
597                    ( $app->user->is_superuser()
598                            && MT::Auth->can_recover_password );
599                },
600            },
601            'delete_user' => {
602                label                   => "Delete",
603                order                   => 200,
604                continue_prompt_handler => sub {
605                    $app->config->ExternalUserManagement
606                        ? MT->translate("_WARNING_DELETE_USER_EUM")
607                        : MT->translate("_WARNING_DELETE_USER");
608                },
609                code      => "${pkg}Common::delete",
610                condition => sub {
611                    $app->user->is_superuser();
612                },
613            },
614        },
615        'blog' => {
616            refresh_blog_templates => {
617                label                   => "Refresh Template(s)",
618                continue_prompt_handler => sub {
619                    MT->translate("_WARNING_REFRESH_TEMPLATES_FOR_BLOGS");
620                },
621                code => sub {
622                    my $app = MT->app;
623                    $app->param( 'backup', 1 );
624                    require MT::CMS::Template;
625                    MT::CMS::Template::refresh_all_templates( $app, @_ );
626                },
627            },
628        },
629        'template' => {
630            refresh_tmpl_templates => {
631                label      => "Refresh Template(s)",
632                code       => "${pkg}Template::refresh_individual_templates",
633                permission => 'edit_templates',
634                order      => 100,
635            },
636
637            # Now a button!
638            # publish_index_templates => {
639            #     label => "Publish Template(s)",
640            #     code => "${pkg}Template::publish_index_templates",
641            #     permission => 'rebuild',
642            #     condition => sub {
643            #         my $app = MT->app;
644            #         my $tmpl_type = $app->param('filter_key');
645            #         return $app->mode eq 'itemset_action'  ? 1
646            #              : !$app->blog                     ? 0
647            #              : !$tmpl_type                     ? 0
648            #              : $tmpl_type eq 'index_templates' ? 1
649            #              :                                   0
650            #              ;
651            #     },
652            #     order => 200,
653            # },
654            # Now a button!
655            # publish_archive_templates => {
656            #     label      => "Publish Template(s)",
657            #     code       => "${pkg}Template::publish_archive_templates",
658            #     permission => 'rebuild',
659            #     condition  => sub {
660            #         my $app       = MT->app;
661            #         my $tmpl_type = $app->param('filter_key');
662            #         return $app->mode eq 'itemset_action' ? 1
663            #           : !$app->blog ? 0
664            #           : !$tmpl_type ? 0
665            #           : $tmpl_type eq 'archive_templates' ? 1
666            #           :                                     0;
667            #     },
668            #     order => 300,
669            # },
670            copy_templates => {
671                label      => "Clone Template(s)",
672                code       => "${pkg}Template::clone_templates",
673                permission => 'edit_templates',
674                condition  => sub {
675                    my $app = MT->app;
676                    my $tmpl_type = $app->param('filter_key') || '';
677                    return
678                          $tmpl_type eq 'system_templates' ? 0
679                        : $tmpl_type eq 'email_templates'  ? 0
680                        :                                    1;
681                },
682                order => 400,
683            },
684        },
685    };
686}
687
688sub _entry_label {
689    my $app = MT->instance;
690    my $type = $app->param('type') || 'entry';
691    $app->model($type)->class_label_plural;
692}
693
694sub core_list_filters {
695    my $app = shift;
696    return {
697        asset => sub {
698            require MT::CMS::Asset;
699            return MT::CMS::Asset::asset_list_filters( $app, @_ );
700        },
701        entry => {
702            published => {
703                label => sub {
704                    $app->translate( 'Published [_1]', _entry_label );
705                },
706                order   => 100,
707                handler => sub {
708                    my ( $terms, $args ) = @_;
709                    $terms->{status} = 2;
710                },
711            },
712            unpublished => {
713                label => sub {
714                    $app->translate( 'Unpublished [_1]', _entry_label );
715                },
716                order   => 200,
717                handler => sub {
718                    my ( $terms, $args ) = @_;
719                    $terms->{status} = 1;
720                },
721            },
722            scheduled => {
723                label => sub {
724                    $app->translate( 'Scheduled [_1]', _entry_label );
725                },
726                order   => 300,
727                handler => sub {
728                    my ( $terms, $args ) = @_;
729                    $terms->{status} = 4;
730                },
731            },
732            my_posts => {
733                label => sub {
734                    $app->translate( 'My [_1]', _entry_label );
735                },
736                order   => 400,
737                handler => sub {
738                    my ( $terms, $args ) = @_;
739                    $terms->{author_id} = $app->user->id;
740                },
741            },
742            received_comments_in_last_7_days => {
743                label => sub {
744                    $app->translate( '[_1] with comments in the last 7 days',
745                        _entry_label );
746                },
747                order   => 500,
748                handler => sub {
749                    my ( $terms, $args ) = @_;
750                    my $ts = time - 7 * 24 * 60 * 60;
751                    $ts = epoch2ts( MT->app->blog, $ts );
752                    $args->{join} = MT::Comment->join_on(
753                        'entry_id',
754                        {   created_on  => [ $ts, undef ],
755                            junk_status => MT::Comment::NOT_JUNK(),
756                        },
757                        { range_incl => { created_on => 1 }, unique => 1 }
758                    );
759
760                    # Since we're selecting content from the mt_entry
761                    # table, but we want to sort by the joined
762                    # 'comment_created_on' column, we have to specify the
763                    # sort column as a reference and a full field name,
764                    # to prevent MT from adding a 'entry_' prefix to
765                    # the column name.
766                    $args->{sort} = [ { column => \'comment_created_on' } ];
767                    $args->{direction} = 'descend';
768                },
769            },
770            _by_date => {
771                label => sub {
772                    my $app = MT->instance;
773                    my $val = $app->param('filter_val');
774                    my ( $from, $to ) = split /-/, $val;
775                    $from = undef unless $from =~ m/^\d{8}$/;
776                    $to   = undef unless $to   =~ m/^\d{8}$/;
777                    my $format = '%x';
778                    $from = format_ts(
779                        $format, $from . '000000',
780                        undef,   MT->current_language
781                    ) if $from;
782                    $to = format_ts(
783                        $format, $to . '000000',
784                        undef,   MT->current_language
785                    ) if $to;
786                    my $label = _entry_label;
787
788                    if ( $from && $to ) {
789                        return $app->translate(
790                            '[_1] posted between [_2] and [_3]',
791                            $label, $from, $to );
792                    }
793                    elsif ($from) {
794                        return $app->translate( "[_1] posted since [_2]",
795                            $label, $from );
796                    }
797                    elsif ($to) {
798                        return $app->translate(
799                            "[_1] posted on or before [_2]",
800                            $label, $to );
801                    }
802                },
803                handler => sub {
804                    my ( $terms, $args ) = @_;
805                    my $val = $app->param('filter_val');
806                    my ( $from, $to ) = split /-/, $val;
807                    $from = undef unless $from =~ m/^\d{8}$/;
808                    $from .= '000000';
809                    $to = undef unless $to =~ m/^\d{8}$/;
810                    $to .= '235959';
811                    $terms->{authored_on} = [ $from, $to ];
812                    $args->{range_incl}{authored_on} = 1;
813                },
814            },
815        },
816        ping => {
817            default => {
818                label   => 'Non-spam TrackBacks',
819                order   => 100,
820                handler => sub {
821                    my ( $terms, $args ) = @_;
822                    require MT::TBPing;
823                    $terms->{junk_status} = MT::TBPing::NOT_JUNK();
824                },
825            },
826            my_posts => {
827                label   => 'TrackBacks on my entries',
828                order   => 200,
829                handler => sub {
830                    my ( $terms, $args ) = @_;
831                    require MT::Entry;
832                    my $app = MT->instance;
833                    require MT::TBPing;
834                    require MT::Trackback;
835                    $terms->{junk_status} = MT::TBPing::NOT_JUNK();
836                    $args->{join}         = MT::Trackback->join_on(
837                        undef,
838                        { id => \'= tbping_tb_id', },
839                        {   join => MT::Entry->join_on(
840                                undef,
841                                {   id        => \'= trackback_entry_id',
842                                    author_id => $app->user->id
843                                }
844                            )
845                        },
846                    );
847                },
848            },
849            published => {
850                label   => 'Published TrackBacks',
851                order   => 200,
852                handler => sub {
853                    my ( $terms, $args ) = @_;
854                    $terms->{visible} = 1;
855                },
856            },
857            unpublished => {
858                label   => 'Unpublished TrackBacks',
859                order   => 300,
860                handler => sub {
861                    my ( $terms, $args ) = @_;
862                    require MT::TBPing;
863                    $terms->{junk_status} = MT::TBPing::NOT_JUNK();
864                    $terms->{visible}     = 0;
865                },
866            },
867            spam => {
868                label   => 'TrackBacks marked as Spam',
869                order   => 400,
870                handler => sub {
871                    my ( $terms, $args ) = @_;
872                    require MT::TBPing;
873                    $terms->{junk_status} = MT::TBPing::JUNK();
874                },
875            },
876            last_7_days => {
877                label   => 'All TrackBacks in the last 7 days',
878                order   => 700,
879                handler => sub {
880                    my ( $terms, $args ) = @_;
881                    my $ts = time - 7 * 24 * 60 * 60;
882                    $ts = epoch2ts( MT->app->blog, $ts );
883                    $terms->{created_on} = [ $ts, undef ];
884                    $args->{range_incl}{created_on} = 1;
885                    $terms->{junk_status} = MT::TBPing::NOT_JUNK();
886                },
887            },
888        },
889        comment => {
890            default => {
891                label   => 'Non-spam Comments',
892                order   => 100,
893                handler => sub {
894                    my ( $terms, $args ) = @_;
895                    require MT::Comment;
896                    $terms->{junk_status} = MT::Comment::NOT_JUNK();
897                },
898            },
899            my_posts => {
900                label   => 'Comments on my entries',
901                order   => 200,
902                handler => sub {
903                    my ( $terms, $args ) = @_;
904                    require MT::Entry;
905                    require MT::Comment;
906                    my $app = MT->instance;
907                    $terms->{junk_status} = MT::Comment::NOT_JUNK();
908
909                    # This join syntax employs a hack that allows us
910                    # to do joins on abitrary columns. Typically,
911                    # objectdriver joins are applied with the primary
912                    # key column of the driving table (here,
913                    # mt_comment.comment_id), but we actually want to
914                    # join to the mt_entry table where the entry_id
915                    # matches to mt_comment.comment_entry_id (not the
916                    # primary key). So by specifying NO 'join' column
917                    # (which is always compared with the primary key),
918                    # we specify the actual join conditions in the
919                    # terms. And using a reference for the
920                    # 'comment_entry_id' column name, to pass that
921                    # on directly to the SQL statement that is generated.
922                    $args->{join} = MT::Entry->join_on(
923                        undef,
924                        {   id        => \'= comment_entry_id',
925                            author_id => $app->user->id
926                        }
927                    );
928                },
929            },
930            unpublished => {
931                label   => 'Pending comments',
932                order   => 300,
933                handler => sub {
934                    my ( $terms, $args ) = @_;
935                    require MT::Comment;
936                    $terms->{junk_status} = MT::Comment::NOT_JUNK();
937                    $terms->{visible}     = 0;
938                },
939            },
940            spam => {
941                label   => 'Spam Comments',
942                order   => 400,
943                handler => sub {
944                    my ( $terms, $args ) = @_;
945                    require MT::Comment;
946                    $terms->{junk_status} = MT::Comment::JUNK();
947                },
948            },
949            published => {
950                label   => 'Published comments',
951                order   => 500,
952                handler => sub {
953                    my ( $terms, $args ) = @_;
954                    $terms->{visible} = 1;
955                },
956            },
957            last_7_days => {
958                label   => 'Comments in the last 7 days',
959                order   => 700,
960                handler => sub {
961                    my ( $terms, $args ) = @_;
962                    require MT::Comment;
963                    my $ts = time - 7 * 24 * 60 * 60;
964                    $ts = epoch2ts( MT->app->blog, $ts );
965                    $terms->{created_on} = [ $ts, undef ];
966                    $args->{range_incl}{created_on} = 1;
967                    $terms->{junk_status} = MT::Comment::NOT_JUNK();
968                },
969            },
970            _comments_by_user => {
971                label => sub {
972                    my $app     = MT->app;
973                    my $user_id = $app->param('filter_val');
974                    my $user    = MT::Author->load($user_id);
975                    require MT::Author;
976                    return $app->translate(
977                        "All comments by [_1] '[_2]'",
978                        (     $user->type == MT::Author::COMMENTER()
979                            ? $app->translate("Commenter")
980                            : $app->translate("Author")
981                        ),
982                        (     $user->nickname
983                            ? $user->nickname . ' (' . $user->name . ')'
984                            : $user->name
985                        )
986                    );
987                },
988                handler => sub {
989                    my ( $terms, $args ) = @_;
990                    my $cmtr_id = int( MT->app->param('filter_val') );
991                    $terms->{commenter_id} = $cmtr_id;
992                },
993            },
994            _comments_by_entry => {
995                label => sub {
996                    my $entry_id = MT->app->param('filter_val');
997                    my $entry    = MT::Entry->load($entry_id);
998                    return MT->translate( "All comments for [_1] '[_2]'",
999                        $entry->class_label, $entry->title );
1000                },
1001                handler => sub {
1002                    my ( $terms, $args ) = @_;
1003                    require MT::Comment;
1004                    my $entry_id = int( MT->app->param('filter_val') );
1005                    $terms->{entry_id}    = $entry_id;
1006                    $terms->{junk_status} = MT::Comment::NOT_JUNK();
1007                },
1008            },
1009            _by_date => {
1010                label => sub {
1011                    my $app = MT->instance;
1012                    my $val = $app->param('filter_val');
1013                    my ( $from, $to ) = split /-/, $val;
1014                    $from = undef unless $from =~ m/^\d{8}$/;
1015                    $to   = undef unless $to   =~ m/^\d{8}$/;
1016                    my $format = '%x';
1017                    $from = format_ts(
1018                        $format, $from . '000000',
1019                        undef,   MT->current_language
1020                    ) if $from;
1021                    $to = format_ts(
1022                        $format, $to . '000000',
1023                        undef,   MT->current_language
1024                    ) if $to;
1025
1026                    if ( $from && $to ) {
1027                        return $app->translate(
1028                            'Comments posted between [_1] and [_2]',
1029                            $from, $to );
1030                    }
1031                    elsif ($from) {
1032                        return $app->translate( "Comments posted since [_1]",
1033                            $from );
1034                    }
1035                    elsif ($to) {
1036                        return $app->translate(
1037                            "Comments posted on or before [_1]", $to );
1038                    }
1039                },
1040                handler => sub {
1041                    my ( $terms, $args ) = @_;
1042                    require MT::Comment;
1043                    my $val = $app->param('filter_val');
1044                    my ( $from, $to ) = split /-/, $val;
1045                    $from = undef unless $from =~ m/^\d{8}$/;
1046                    $from .= '000000';
1047                    $to = undef unless $to =~ m/^\d{8}$/;
1048                    $to .= '235959';
1049                    $terms->{junk_status}           = MT::Comment::NOT_JUNK();
1050                    $terms->{created_on}            = [ $from, $to ];
1051                    $args->{range_incl}{created_on} = 1;
1052                },
1053            },
1054        },
1055        tag => {
1056            entry => {
1057                label => 'Tags with entries',
1058                order => 100,
1059            },
1060            page => {
1061                label => 'Tags with pages',
1062                order => 200,
1063            },
1064            asset => {
1065                label => 'Tags with assets',
1066                order => 300,
1067            },
1068        },
1069        sys_user => {
1070            enabled => {
1071                label   => 'Enabled Users',
1072                order   => 100,
1073                handler => sub {
1074                    my ($terms) = @_;
1075                    $terms->{status} = 1;
1076                },
1077            },
1078            disabled => {
1079                label   => "Disabled Users",
1080                order   => 200,
1081                handler => sub {
1082                    my ($terms) = @_;
1083                    $terms->{status} = 2;
1084                },
1085            },
1086            pending => {
1087                label   => "Pending Users",
1088                order   => 300,
1089                handler => sub {
1090                    my ($terms) = @_;
1091                    $terms->{status} = 3;
1092                },
1093            },
1094        },
1095        user => {
1096            author => {
1097                label   => 'Authors',
1098                order   => 100,
1099                handler => sub {
1100                    my ($terms) = @_;
1101                    require MT::Author;
1102                    $terms->{type} = MT::Author::AUTHOR();
1103                },
1104            },
1105            commenter => {
1106                label   => 'Commenters',
1107                order   => 200,
1108                handler => sub {
1109                    my ($terms) = @_;
1110                    require MT::Author;
1111                    $terms->{type} = MT::Author::COMMENTER();
1112                },
1113            },
1114        },
1115    };
1116}
1117
1118sub core_menus {
1119    my $app = shift;
1120    return {
1121        'create' => {
1122            label => "Create",
1123            order => 100,
1124        },
1125        'manage' => {
1126            label => "Manage",
1127            order => 200,
1128        },
1129        'design' => {
1130            label => "Design",
1131            order => 300,
1132        },
1133        'prefs' => {
1134            label => "Preferences",
1135            order => 400,
1136        },
1137        'tools' => {
1138            label => "Tools",
1139            order => 500,
1140        },
1141
1142        'create:blog' => {
1143            label => "Blog",
1144            order => 100,
1145            view  => "system",
1146        },
1147        'create:blog' => {
1148            label      => "Blog",
1149            order      => 200,
1150            view       => "system",
1151            mode       => "view",
1152            args       => { _type => "blog" },
1153            permission => "create_blog",
1154        },
1155        'create:user' => {
1156            label      => "User",
1157            order      => 200,
1158            view       => "system",
1159            mode       => "view",
1160            args       => { _type => "author" },
1161            permission => "administer",
1162            condition  => sub {
1163                return !MT->config->ExternalUserManagement;
1164            },
1165        },
1166        'create:entry' => {
1167            label      => "Entry",
1168            order      => 100,
1169            mode       => 'view',
1170            args       => { _type => 'entry' },
1171            permission => 'create_post',
1172            view       => "blog",
1173        },
1174        'create:page' => {
1175            label      => "Page",
1176            order      => 200,
1177            mode       => 'view',
1178            args       => { _type => 'page' },
1179            permission => 'manage_pages',
1180            view       => "blog",
1181        },
1182        'create:file' => {
1183            label      => "Upload File",
1184            order      => 300,
1185            dialog     => 'start_upload',
1186            permission => 'upload,edit_assets',
1187            view       => "blog",
1188        },
1189
1190        'manage:blog' => {
1191            label => "Blogs",
1192            mode  => "list_blog",
1193            order => 100,
1194            view  => "system",
1195        },
1196        'manage:user' => {
1197            label      => "Users",
1198            mode       => "list_user",
1199            order      => 200,
1200            permission => "administer",
1201            view       => "system",
1202        },
1203        'manage:entry' => {
1204            label     => "Entries",
1205            mode      => 'list_entry',
1206            order     => 1000,
1207            condition => sub {
1208                return 1 if $app->user->is_superuser;
1209                if ( $app->param('blog_id') ) {
1210                    my $perms
1211                        = $app->user->permissions( $app->param('blog_id') );
1212                    return 1
1213                        if $perms->can_create_post
1214                            || $perms->can_publish_post
1215                            || $perms->can_edit_all_posts;
1216                }
1217                else {
1218                    require MT::Permission;
1219                    my @blogs
1220                        = map { $_->blog_id }
1221                        grep {
1222                        $_->can_create_post
1223                            || $_->can_publish_post
1224                            || $_->can_edit_all_posts
1225                        } MT::Permission->load(
1226                        { author_id => $app->user->id } );
1227                    return 1 if @blogs;
1228                }
1229                return 0;
1230            },
1231
1232            #permission => 'create_post,publish_post,edit_all_posts',
1233        },
1234        'manage:comment' => {
1235            label     => "Comments",
1236            mode      => 'list_comment',
1237            order     => 2000,
1238            condition => sub {
1239                return 1 if $app->user->is_superuser;
1240                if ( $app->param('blog_id') ) {
1241                    my $perms
1242                        = $app->user->permissions( $app->param('blog_id') );
1243                    return 1
1244                        if $perms->can_create_post
1245                            || $perms->can_manage_feedback
1246                            || $perms->can_edit_all_posts
1247                            || $perms->can_comment;
1248                }
1249                else {
1250                    require MT::Permission;
1251                    my @blogs
1252                        = map { $_->blog_id }
1253                        grep {
1254                               $_->can_create_post
1255                            || $_->can_manage_feedback
1256                            || $_->can_edit_all_posts
1257                            || $_->can_comment
1258                        } MT::Permission->load(
1259                        { author_id => $app->user->id } );
1260                    return 1 if @blogs;
1261                }
1262                return 0;
1263            },
1264
1265          #permission => 'create_post,edit_all_posts,manage_feedback,comment',
1266        },
1267        'manage:asset' => {
1268            label      => "Assets",
1269            mode       => 'list_asset',
1270            order      => 3000,
1271            permission => 'edit_assets',
1272        },
1273        'manage:page' => {
1274            label      => "Pages",
1275            mode       => 'list_pages',
1276            order      => 4000,
1277            permission => 'manage_pages',
1278        },
1279        'manage:ping' => {
1280            label      => "TrackBacks",
1281            mode       => 'list_pings',
1282            order      => 5000,
1283            permission => 'create_post,edit_all_posts,manage_feedback',
1284        },
1285        'manage:category' => {
1286            label      => "Categories",
1287            mode       => 'list_cat',
1288            order      => 6000,
1289            permission => 'edit_categories',
1290            view       => "blog",
1291        },
1292        'manage:folder' => {
1293            label      => "Folders",
1294            mode       => 'list_cat',
1295            args       => { _type => 'folder' },
1296            order      => 7000,
1297            permission => 'manage_pages',
1298            view       => "blog",
1299        },
1300        'manage:tag' => {
1301            label             => "Tags",
1302            mode              => 'list_tag',
1303            order             => 8000,
1304            permission        => 'edit_tags',
1305            system_permission => 'administer',
1306        },
1307        'manage:blog_user' => {
1308            label             => "Users",
1309            mode              => 'list_member',
1310            order             => 9000,
1311            view              => "blog",
1312            permission        => 'administer_blog',
1313            system_permission => 'administer',
1314        },
1315        'manage:notification' => {
1316            label      => "Address Book",
1317            mode       => 'list',
1318            args       => { _type => 'notification' },
1319            order      => 10000,
1320            permission => 'edit_notifications',
1321            view       => "blog",
1322            condition  => sub {
1323                return $app->config->EnableAddressBook;
1324            },
1325        },
1326
1327        'design:template' => {
1328            label             => "Templates",
1329            mode              => 'list',
1330            args              => { _type => 'template' },
1331            order             => 100,
1332            permission        => 'edit_templates',
1333            system_permission => 'edit_templates',
1334        },
1335        'design:widgets' => {
1336            label             => 'Widgets',
1337            mode              => 'list_widget',
1338            order             => 200,
1339            permission        => 'edit_templates',
1340            system_permission => "edit_templates",
1341        },
1342
1343        'prefs:general' => {
1344            label      => "General",
1345            order      => 100,
1346            mode       => "cfg_system",
1347            view       => "system",
1348            permission => "administer",
1349        },
1350        'prefs:user' => {
1351            label      => "User",
1352            order      => 200,
1353            mode       => "cfg_system_users",
1354            view       => "system",
1355            permission => "administer",
1356        },
1357        'prefs:feedback' => {
1358            label      => "Feedback",
1359            order      => 300,
1360            mode       => "cfg_system_feedback",
1361            view       => "system",
1362            permission => "administer",
1363        },
1364        'prefs:settings' => {
1365            label      => "General",
1366            mode       => 'cfg_prefs',
1367            order      => 100,
1368            permission => 'administer_blog,edit_config,set_publish_paths',
1369            system_permission => 'administer',
1370            view              => "blog",
1371        },
1372        'prefs:publishing' => {
1373            label      => "Publishing",
1374            mode       => 'cfg_archives',
1375            order      => 110,
1376            permission => 'administer_blog,edit_config,set_publish_paths',
1377            system_permission => 'administer',
1378            view              => "blog",
1379        },
1380        'prefs:entry' => {
1381            label      => "Entry",
1382            mode       => 'cfg_entry',
1383            order      => 120,
1384            permission => 'administer_blog,edit_config,set_publish_paths',
1385            system_permission => 'administer',
1386            view              => "blog",
1387        },
1388        'prefs:comment' => {
1389            label      => "Comment",
1390            mode       => 'cfg_comments',
1391            order      => 130,
1392            permission => 'administer_blog,edit_config,set_publish_paths',
1393            system_permission => 'administer',
1394            view              => "blog",
1395        },
1396        'prefs:trackback' => {
1397            label      => "TrackBack",
1398            mode       => 'cfg_trackbacks',
1399            order      => 140,
1400            permission => 'administer_blog,edit_config,set_publish_paths',
1401            system_permission => 'administer',
1402            view              => "blog",
1403        },
1404        'prefs:registration' => {
1405            label      => "Registration",
1406            mode       => 'cfg_registration',
1407            order      => 150,
1408            permission => 'administer_blog,edit_config,set_publish_paths',
1409            system_permission => 'administer',
1410            view              => "blog",
1411        },
1412        'prefs:spam' => {
1413            label      => "Spam",
1414            mode       => 'cfg_spam',
1415            order      => 160,
1416            permission => 'administer_blog,edit_config,set_publish_paths',
1417            system_permission => 'administer',
1418            view              => "blog",
1419        },
1420        'prefs:web_services' => {
1421            label      => "Web Services",
1422            mode       => 'cfg_web_services',
1423            order      => 170,
1424            permission => 'administer_blog,edit_config,set_publish_paths',
1425            system_permission => 'administer',
1426            view              => "blog",
1427        },
1428
1429        'tools:plugins' => {
1430            label             => "Plugins",
1431            order             => 100,
1432            mode              => "cfg_plugins",
1433            permission        => "administer_blog",
1434            system_permission => "manage_plugins",
1435        },
1436        'tools:activity_log' => {
1437            label             => "Activity Log",
1438            order             => 200,
1439            mode              => "view_log",
1440            permission        => "view_blog_log",
1441            system_permission => "view_log",
1442        },
1443        'tools:import' => {
1444            label      => "Import",
1445            order      => 300,
1446            mode       => "start_import",
1447            view       => "blog",
1448            permission => "administer_blog",
1449        },
1450        'tools:export' => {
1451            label      => "Export",
1452            order      => 400,
1453            mode       => "start_export",
1454            view       => "blog",
1455            permission => "administer_blog",
1456        },
1457        'tools:backup' => {
1458            label      => "Backup",
1459            order      => 500,
1460            mode       => "start_backup",
1461            permission => "administer_blog",
1462        },
1463        'tools:restore' => {
1464            label      => "Restore",
1465            order      => 600,
1466            mode       => "start_restore",
1467            permission => "administer_blog",
1468            view       => "system",
1469        },
1470        'tools:system_information' => {
1471            label => "System Information",
1472            order => 700,
1473            mode  => "tools",
1474            view  => "system",
1475        },
1476        'tools:ip_info' => {
1477            label      => "IP Banning",
1478            mode       => 'list',
1479            args       => { _type => 'banlist' },
1480            order      => 900,
1481            permission => 'manage_feedback',
1482            condition  => sub {
1483                $app->config->ShowIPInformation;
1484            },
1485            view => "blog",
1486        },
1487
1488        # System menu which is actually separate
1489        # in the CMS navigation
1490        'system' => {
1491            label => "System Overview",
1492            mode  => 'dashboard',
1493            order => 10000,
1494        },
1495        'system:user' => {
1496            label             => "Users",
1497            mode              => 'list_authors',
1498            order             => 100,
1499            permission        => 'administer_blog',
1500            system_permission => 'administer',
1501        },
1502        'system:blog' => {
1503            label => "Blogs",
1504            mode  => 'list_blogs',
1505            order => 200,
1506        },
1507        'system:template' => {
1508            label             => "Global Templates",
1509            mode              => 'list_template',
1510            order             => 250,
1511            system_permission => 'edit_templates',
1512        },
1513        'system:settings' => {
1514            label             => "Settings",
1515            mode              => 'cfg_system',
1516            order             => 300,
1517            system_permission => 'administer',
1518        },
1519        'system:plugins' => {
1520            label             => "Plugins",
1521            mode              => 'cfg_plugins',
1522            order             => 400,
1523            system_permission => 'manage_plugins',
1524        },
1525        'system:log' => {
1526            label             => "Activity Log",
1527            mode              => 'view_log',
1528            order             => 500,
1529            system_permission => 'view_log',
1530        },
1531        'system:tools' => {
1532            label             => "Tools",
1533            mode              => 'tools',
1534            order             => 600,
1535            system_permission => 'administer',
1536        },
1537    };
1538}
1539
1540sub init_core_callbacks {
1541    my $app = shift;
1542    my $pkg = 'cms_';
1543    my $pfx = '$Core::MT::CMS::';
1544    $app->_register_core_callbacks(
1545        {
1546
1547            # notification callbacks
1548            $pkg
1549                . 'save_permission_filter.notification' =>
1550                "${pfx}AddressBook::can_save",
1551            $pkg
1552                . 'save_filter.notification' =>
1553                "${pfx}AddressBook::save_filter",
1554            $pkg
1555                . 'post_delete.notification' =>
1556                "${pfx}AddressBook::post_delete",
1557
1558            # banlist callbacks
1559            $pkg
1560                . 'save_permission_filter.banlist' =>
1561                "${pfx}BanList::can_save",
1562            $pkg . 'save_filter.banlist' => "${pfx}BanList::save_filter",
1563
1564            # associations
1565            $pkg
1566                . 'delete_permission_filter.association' =>
1567                "${pfx}User::can_delete_association",
1568
1569            # user callbacks
1570            $pkg . 'edit.author'                   => "${pfx}User::edit",
1571            $pkg . 'view_permission_filter.author' => "${pfx}User::can_view",
1572            $pkg . 'save_permission_filter.author' => "${pfx}User::can_save",
1573            $pkg
1574                . 'delete_permission_filter.author' =>
1575                "${pfx}User::can_delete",
1576            $pkg . 'save_filter.author' => "${pfx}User::save_filter",
1577            $pkg . 'pre_save.author'    => "${pfx}User::pre_save",
1578            $pkg . 'post_save.author'   => "${pfx}User::post_save",
1579            $pkg . 'post_delete.author' => "${pfx}User::post_delete",
1580
1581            # blog callbacks
1582            $pkg . 'edit.blog'                   => "${pfx}Blog::edit",
1583            $pkg . 'view_permission_filter.blog' => "${pfx}Blog::can_view",
1584            $pkg . 'save_permission_filter.blog' => "${pfx}Blog::can_save",
1585            $pkg
1586                . 'delete_permission_filter.blog' => "${pfx}Blog::can_delete",
1587            $pkg . 'pre_save.blog'    => "${pfx}Blog::pre_save",
1588            $pkg . 'post_save.blog'   => "${pfx}Blog::post_save",
1589            $pkg . 'save_filter.blog' => "${pfx}Blog::save_filter",
1590            $pkg . 'post_delete.blog' => "${pfx}Blog::post_delete",
1591
1592            # folder callbacks
1593            $pkg . 'edit.folder' => "${pfx}Folder::edit",
1594            $pkg
1595                . 'view_permission_filter.folder' => "${pfx}Folder::can_view",
1596            $pkg
1597                . 'save_permission_filter.folder' => "${pfx}Folder::can_save",
1598            $pkg
1599                . 'delete_permission_filter.folder' =>
1600                "${pfx}Folder::can_delete",
1601            $pkg . 'pre_save.folder'    => "${pfx}Folder::pre_save",
1602            $pkg . 'post_save.folder'   => "${pfx}Folder::post_save",
1603            $pkg . 'save_filter.folder' => "${pfx}Folder::save_filter",
1604            $pkg . 'post_delete.folder' => "${pfx}Folder::post_delete",
1605
1606            # category callbacks
1607            $pkg . 'edit.category' => "${pfx}Category::edit",
1608            $pkg
1609                . 'view_permission_filter.category' =>
1610                "${pfx}Category::can_view",
1611            $pkg
1612                . 'save_permission_filter.category' =>
1613                "${pfx}Category::can_save",
1614            $pkg
1615                . 'delete_permission_filter.category' =>
1616                "${pfx}Category::can_delete",
1617            $pkg . 'pre_save.category'    => "${pfx}Category::pre_save",
1618            $pkg . 'post_save.category'   => "${pfx}Category::post_save",
1619            $pkg . 'save_filter.category' => "${pfx}Category::save_filter",
1620            $pkg . 'post_delete.category' => "${pfx}Category::post_delete",
1621
1622            # comment callbacks
1623            $pkg . 'edit.comment' => "${pfx}Comment::edit",
1624            $pkg
1625                . 'view_permission_filter.comment' =>
1626                "${pfx}Comment::can_view",
1627            $pkg
1628                . 'save_permission_filter.comment' =>
1629                "${pfx}Comment::can_save",
1630            $pkg
1631                . 'delete_permission_filter.comment' =>
1632                "${pfx}Comment::can_delete",
1633            $pkg . 'pre_save.comment'    => "${pfx}Comment::pre_save",
1634            $pkg . 'post_save.comment'   => "${pfx}Comment::post_save",
1635            $pkg . 'post_delete.comment' => "${pfx}Comment::post_delete",
1636
1637            # commenter callbacks
1638            $pkg . 'edit.commenter' => "${pfx}Comment::edit_commenter",
1639            $pkg
1640                . 'view_permission_filter.commenter' =>
1641                "${pfx}Comment::can_view_commenter",
1642            $pkg
1643                . 'delete_permission_filter.commenter' =>
1644                "${pfx}Comment::can_delete_commenter",
1645
1646            # entry callbacks
1647            $pkg . 'edit.entry'                   => "${pfx}Entry::edit",
1648            $pkg . 'view_permission_filter.entry' => "${pfx}Entry::can_view",
1649            $pkg
1650                . 'delete_permission_filter.entry' =>
1651                "${pfx}Entry::can_delete",
1652            $pkg . 'pre_save.entry'    => "${pfx}Entry::pre_save",
1653            $pkg . 'post_save.entry'   => "${pfx}Entry::post_save",
1654            $pkg . 'post_delete.entry' => "${pfx}Entry::post_delete",
1655
1656            # page callbacks
1657            $pkg . 'edit.page'                   => "${pfx}Page::edit",
1658            $pkg . 'view_permission_filter.page' => "${pfx}Page::can_view",
1659            $pkg
1660                . 'delete_permission_filter.page' => "${pfx}Page::can_delete",
1661            $pkg . 'pre_save.page'    => "${pfx}Page::pre_save",
1662            $pkg . 'post_save.page'   => "${pfx}Page::post_save",
1663            $pkg . 'post_delete.page' => "${pfx}Page::post_delete",
1664
1665            # ping callbacks
1666            $pkg . 'edit.ping' => "${pfx}TrackBack::edit",
1667            $pkg
1668                . 'view_permission_filter.ping' =>
1669                "${pfx}TrackBack::can_view",
1670            $pkg
1671                . 'save_permission_filter.ping' =>
1672                "${pfx}TrackBack::can_save",
1673            $pkg
1674                . 'delete_permission_filter.ping' =>
1675                "${pfx}TrackBack::can_delete",
1676            $pkg . 'pre_save.ping'    => "${pfx}TrackBack::pre_save",
1677            $pkg . 'post_save.ping'   => "${pfx}TrackBack::post_save",
1678            $pkg . 'post_delete.ping' => "${pfx}TrackBack::post_delete",
1679
1680            # template callbacks
1681            $pkg . 'edit.template' => "${pfx}Template::edit",
1682            $pkg
1683                . 'view_permission_filter.template' =>
1684                "${pfx}Template::can_view",
1685            $pkg
1686                . 'save_permission_filter.template' =>
1687                "${pfx}Template::can_save",
1688            $pkg
1689                . 'delete_permission_filter.template' =>
1690                "${pfx}Template::can_delete",
1691            $pkg . 'pre_save.template'    => "${pfx}Template::pre_save",
1692            $pkg . 'post_save.template'   => "${pfx}Template::post_save",
1693            $pkg . 'post_delete.template' => "${pfx}Template::post_delete",
1694            'restore' => "${pfx}Template::restore_widgetmanagers",
1695
1696            # tags
1697            $pkg . 'delete_permission_filter.tag' => "${pfx}Tag::can_delete",
1698            $pkg . 'post_delete.tag'              => "${pfx}Tag::post_delete",
1699
1700            # junk-related callbacks
1701            #'HandleJunk' => \&_builtin_spam_handler,
1702            #'HandleNotJunk' => \&_builtin_spam_unhandler,
1703            $pkg . 'not_junk_test' => "${pfx}Common::not_junk_test",
1704
1705            # assets
1706            $pkg . 'edit.asset'                   => "${pfx}Asset::edit",
1707            $pkg . 'view_permission_filter.asset' => "${pfx}Asset::can_view",
1708            $pkg
1709                . 'delete_permission_filter.asset' =>
1710                "${pfx}Asset::can_delete",
1711            $pkg . 'pre_save.asset'     => "${pfx}Asset::pre_save",
1712            $pkg . 'post_save.asset'    => "${pfx}Asset::post_save",
1713            $pkg . 'post_delete.asset'  => "${pfx}Asset::post_delete",
1714            'template_param.edit_asset' => "${pfx}Asset::template_param_edit",
1715        }
1716    );
1717}
1718
1719sub user_can_admin_commenters {
1720    my $app   = shift;
1721    my $perms = $app->permissions;
1722    $app->user->is_superuser()
1723        || ( $perms
1724        && ( $perms->can_administer_blog || $perms->can_manage_feedback ) );
1725}
1726
1727sub validate_magic {
1728    my $app = shift;
1729    if ( my $feed_token = $app->param('feed_token') ) {
1730        return unless $app->user;
1731        my $pw = $app->user->api_password;
1732        return undef if ( $pw || '' ) eq '';
1733        my $auth_token = perl_sha1_digest_hex( 'feed:' . $pw );
1734        return $feed_token eq $auth_token;
1735    }
1736    else {
1737        return $app->SUPER::validate_magic(@_);
1738    }
1739}
1740
1741sub is_authorized {
1742    my $app     = shift;
1743    my $blog_id = $app->param('blog_id');
1744    $app->permissions(undef);
1745    return 1 unless $blog_id;
1746    return unless my $user = $app->user;
1747    my $perms = $app->permissions( $user->permissions($blog_id) );
1748    $perms
1749        ? 1
1750        : $app->error(
1751        $app->translate("You are not authorized to log in to this blog.") );
1752}
1753
1754sub set_default_tmpl_params {
1755    my $app = shift;
1756    $app->SUPER::set_default_tmpl_params(@_);
1757    my ($tmpl) = @_;
1758    my $param = {};
1759
1760    my $mode      = $app->mode;
1761    my $auth_mode = $app->config('AuthenticationModule');
1762    my ($pref) = split /\s+/, $auth_mode;
1763
1764    # TODO - remove after testing or after new IA is defined
1765    $param->{app_layout_fixed} = 0;
1766    $param->{athena_nav}       = 1;
1767
1768    $param->{"auth_mode_$pref"} = 1;
1769    $param->{mt_news}           = $app->config('NewsURL');
1770    $param->{mt_support}        = $app->config('SupportURL');
1771    my $lang = lc MT->current_language || 'en_us';
1772    $param->{language_id} = ( $lang !~ /en[_-]us/ ) ? $lang : '';
1773    $param->{mode} = $app->mode;
1774
1775    my $blog_id = $app->param('blog_id') || 0;
1776    my $blog;
1777    my $blog_class = $app->model('blog');
1778    $blog ||= $blog_class->load($blog_id) if $blog_id;
1779    if ( my $auth = $app->user ) {
1780        $param->{is_administrator} = $auth->is_superuser;
1781        $param->{can_create_newblog}  = $auth->can_create_blog;
1782        $param->{can_view_log} ||= $auth->can_view_log;
1783        $param->{can_manage_plugins}    = $auth->can_manage_plugins;
1784        $param->{can_edit_templates}    = $auth->can_edit_templates;
1785        $param->{can_publish_feedbacks} = $auth->is_superuser;
1786        $param->{can_search_replace}    = $auth->is_superuser;
1787        $param->{has_authors_button}    = $auth->is_superuser;
1788        $param->{author_id}             = $auth->id;
1789        $param->{author_name}           = $auth->name;
1790        $param->{author_display_name}   = $auth->nickname || $auth->name;
1791    }
1792
1793    if ( my $perms = $app->permissions ) {
1794        my $perm_hash = $perms->to_hash;
1795        foreach my $perm_name ( keys %$perm_hash ) {
1796            my $perm_token = $perm_name;
1797            $perm_token =~ s/^permission\.//;
1798            $param->{$perm_token} = $perm_hash->{$perm_name}
1799                if defined $perm_hash->{$perm_name};
1800        }
1801        $param->{can_edit_entries} 
1802            = $param->{can_create_post}
1803            || $param->{can_edit_all_entries}
1804            || $param->{can_publish_post};
1805        $param->{can_search_replace} = $param->{can_edit_all_posts};
1806        $param->{can_edit_authors}   = $param->{can_administer_blog};
1807        $param->{can_access_assets} 
1808            = $param->{can_create_post}
1809            || $param->{can_edit_all_posts}
1810            || $param->{can_edit_assets};
1811        $param->{can_edit_commenters} = $param->{can_manage_feedback};
1812        $param->{has_manage_label} 
1813            = $param->{can_edit_templates}
1814            || $param->{can_administer_blog}
1815            || $param->{can_edit_categories}
1816            || $param->{can_edit_config}
1817            || $param->{can_edit_tags}
1818            || $param->{can_set_publish_paths}
1819            || $param->{show_ip_info};
1820        $param->{has_posting_label} 
1821            = $param->{can_create_post}
1822            || $param->{can_edit_entries}
1823            || $param->{can_access_assets};
1824        $param->{has_community_label} = $param->{can_edit_entries}
1825            || $param->{can_edit_notifications};
1826        $param->{can_publish_feedbacks} 
1827            = $param->{can_manage_feedback}
1828            || $param->{can_publish_post}
1829            || $param->{can_edit_all_posts};
1830        $param->{can_view_log}     = $param->{can_view_blog_log};
1831        $param->{can_publish_post} = $param->{can_publish_post}
1832            || $param->{can_edit_all_posts};
1833        $param->{show_ip_info} = $app->config->ShowIPInformation
1834            && $param->{can_manage_feedback};
1835    }
1836
1837    my $static_app_url = $app->static_path;
1838    $param->{help_url} = $app->help_url() || $static_app_url . 'docs/';
1839
1840    $param->{show_ip_info} ||= $app->config('ShowIPInformation');
1841    my $type = $app->param('_type') || '';
1842
1843    $param->{ "mode_$mode" . ( $type ? "_$type" : '' ) } = 1;
1844    $param->{return_args} ||= $app->make_return_args;
1845    $tmpl->param($param);
1846}
1847
1848sub build_page {
1849    my $app = shift;
1850    my ( $page, $param ) = @_;
1851    $param ||= {};
1852
1853    my $blog_id = $app->param('blog_id') || 0;
1854    my $blog;
1855    my $blog_class = $app->model('blog');
1856    $blog ||= $blog_class->load($blog_id) if $blog_id;
1857    if ( $blog_id && $page ne 'login.tmpl' ) {
1858        if ($blog) {
1859            $param->{blog_name}         = $blog->name;
1860            $param->{blog_id}           = $blog->id;
1861            $param->{blog_url}          = $blog->site_url;
1862            $param->{blog_template_set} = $blog->template_set;
1863        }
1864        else {
1865            $app->error( $app->translate( "No such blog [_1]", $blog_id ) );
1866        }
1867    }
1868    if ( $page ne 'login.tmpl' ) {
1869        if ( ref $app eq 'MT::App::CMS' ) {
1870            $param->{system_overview_nav} = 1
1871                unless $blog_id
1872                    || exists $param->{system_overview_nav}
1873                    || $param->{no_breadcrumbs};
1874            $param->{quick_search} = 1 unless defined $param->{quick_search};
1875        }
1876    }
1877
1878    $app->build_blog_selector($param);
1879    $app->build_menus($param);
1880    if ( !ref($page)
1881        || ( $page->isa('MT::Template') && !$page->param('page_actions') ) )
1882    {
1883        $param->{page_actions} ||= $app->page_actions( $app->mode );
1884    }
1885
1886    $app->SUPER::build_page( $page, $param );
1887}
1888
1889sub build_blog_selector {
1890    my $app = shift;
1891    my ($param) = @_;
1892
1893    return if exists $param->{top_blog_loop};
1894
1895    my $blog = $app->blog;
1896    my $blog_id = $blog->id if $blog;
1897    $param->{dynamic_all} = $blog->custom_dynamic_templates eq 'all' if $blog;
1898
1899    my $blog_class = $app->model('blog');
1900    my $auth = $app->user or return;
1901
1902    # Any access to a blog will put it on the top of your
1903    # recently used blogs list (the blog selector)
1904    $app->add_to_favorite_blogs($blog_id) if $blog_id;
1905
1906    my %args;
1907    $args{join} = MT::Permission->join_on( 'blog_id',
1908        { author_id => $auth->id, permissions => { not => "'comment'" } } );
1909    $args{limit} = 11;    # don't load more than 11
1910    my @blogs = $blog_class->load( undef, \%args );
1911
1912    my @fav_blogs = @{ $auth->favorite_blogs || [] };
1913    @fav_blogs = grep { $_ != $blog_id } @fav_blogs if $blog_id;
1914
1915    # Special case for when a user only has access to a single blog.
1916    if (   ( !defined( $app->param('blog_id') ) )
1917        && ( @blogs == 1 )
1918        && ( scalar @fav_blogs <= 1 ) )
1919    {
1920
1921        # User only has visibility to a single blog. Don't
1922        # bother giving them a dashboard link for 'all blogs', or
1923        # to 'select a blog'.
1924        $param->{single_blog_mode} = 1;
1925        my $blog = $blogs[0];
1926        $blog_id = $blog->id;
1927        my $perms = MT::Permission->load(
1928            {   blog_id   => $blog_id,
1929                author_id => $auth->id
1930            }
1931        );
1932        if ( !$app->blog ) {
1933            if ( $app->mode eq 'dashboard' ) {
1934                $app->param( 'blog_id', $blog_id );
1935                $param->{blog_id}   = $blog_id;
1936                $param->{blog_name} = $blog->name;
1937                $app->permissions($perms);
1938                $app->blog($blog);
1939            }
1940            else {
1941                @fav_blogs = ($blog_id);
1942                $blog_id   = undef;
1943            }
1944        }
1945    }
1946    elsif ( @blogs && ( @blogs <= 10 ) ) {
1947
1948        # This user only has visibility to 10 or fewer blogs;
1949        # no need to reference their 'favorite' blogs list.
1950        my @ids = map { $_->id } @blogs;
1951        if ($blog_id) {
1952            @ids = grep { $_ != $blog_id } @ids;
1953        }
1954        @fav_blogs = @ids;
1955        if ( $auth->is_superuser ) {
1956
1957            # Better check to see if there are more than
1958            # 10 blogs in the system; if so, a superuser
1959            # will still want the 'Select a blog...' chooser.
1960            # Otherwise, hide it.
1961            my $all_blog_count = $blog_class->count();
1962            if ( $all_blog_count < 11 ) {
1963                $param->{selector_hide_chooser} = 1;
1964            }
1965        }
1966        else {
1967
1968            # This user is not a superuser and only has
1969            # 10 blogs, so they don't need a 'select blog'
1970            # link...
1971            $param->{selector_hide_chooser} = 1;
1972        }
1973    }
1974    $param->{selector_hide_chooser} ||= 0;
1975
1976    # Logic for populating the blog selector control
1977    #   * Pull list of 'favorite blogs' from user record
1978    #   * Load all of those blogs so we can display them
1979    #   * Exclude the current blog from the favorite list so it isn't
1980    #     shown twice.
1981    @blogs = $blog_class->load( { id => \@fav_blogs } ) if @fav_blogs;
1982    my %blogs = map { $_->id => $_ } @blogs;
1983    @blogs = ();
1984    foreach my $id (@fav_blogs) {
1985        push @blogs, $blogs{$id} if $blogs{$id};
1986    }
1987
1988    my @data;
1989    if (@blogs) {
1990        my @perms = grep { !$_->is_empty } MT::Permission->load(
1991            {   author_id => $auth->id,
1992                blog_id   => \@fav_blogs,
1993            }
1994        );
1995        my %perms = map { $_->blog_id => $_ } @perms;
1996        for my $blog (@blogs) {
1997            my $perm = $perms{ $blog->id };
1998            next
1999                unless $auth->is_superuser || ( $perm && !$perm->is_empty );
2000            push @data,
2001                {
2002                top_blog_id   => $blog->id,
2003                top_blog_name => $blog->name
2004                };
2005            $data[-1]{top_blog_selected} = 1
2006                if $blog_id && ( $blog->id == $blog_id );
2007        }
2008    }
2009    $param->{top_blog_loop} = \@data;
2010    if ( !$app->user->can_create_blog
2011        && ( $param->{single_blog_mode} || scalar(@data) <= 1 ) )
2012    {
2013        $param->{no_submenu} = 1;
2014    }
2015}
2016
2017sub build_menus {
2018    my $app = shift;
2019    my ($param) = @_;
2020    return if exists $param->{top_nav_loop};
2021
2022    my $menus   = $app->registry('menus');
2023    my $blog    = $app->blog;
2024    my $blog_id = $blog->id if $blog;
2025
2026    my @top_ids = grep { !/:/ } keys %$menus;
2027    my @top;
2028    my @sys;
2029    my $user = $app->user
2030        or return;
2031    my $perms = $app->permissions || $user->permissions;
2032    my $view = $blog_id ? "blog" : "system";
2033
2034    my $hide_disabled_options = $app->config('HideDisabledMenus') || 0;
2035
2036    my $admin = $user->is_superuser()
2037        ;    # || ($perms && $perms->has('administer_blog'));
2038
2039    foreach my $id (@top_ids) {
2040        my $menu = $menus->{$id};
2041        next if $menu->{view} && $menu->{view} ne $view;
2042        if ( my $cond = $menu->{condition} ) {
2043            if ( !ref($cond) ) {
2044                $cond = $menu->{condition} = $app->handler_to_coderef($cond);
2045            }
2046            next unless $cond->();
2047        }
2048
2049        $menu->{allowed} = 1;
2050        $menu->{'id'} = $id;
2051
2052        my @sub_ids = grep {m/^$id:/} keys %$menus;
2053        my @sub;
2054        foreach my $sub_id (@sub_ids) {
2055            my $sub = $menus->{$sub_id};
2056            next
2057                if $sub->{view}
2058                    && ( $sub->{view} ne $view );
2059            $sub->{'id'} = $sub_id;
2060            if ( my $cond = $sub->{condition} ) {
2061                if ( !ref($cond) ) {
2062                    $cond = $sub->{condition}
2063                        = $app->handler_to_coderef($cond);
2064                }
2065                next unless $cond->();
2066            }
2067            push @sub, $sub;
2068        }
2069
2070        if (my $p
2071            = $blog_id
2072            ? $menu->{permission} || $menu->{system_permission}
2073            : $menu->{system_permission}
2074            || $menu->{permission}
2075            )
2076        {
2077            my $allowed = 0;
2078            my @p = split /,/, $p;
2079            foreach my $p (@p) {
2080                my $perm = 'can_' . $p;
2081                $allowed = 1, last if ( $perms && $perms->$perm() ) || $admin;
2082            }
2083            $menu->{allowed} = $allowed;
2084        }
2085        elsif ( !$perms && !$blog_id ) {
2086            $menu->{allowed} = 0 if $menu->{system_permission} && !$admin;
2087        }
2088
2089        next if $hide_disabled_options && ( !$menu->{allowed} );
2090
2091        if (@sub) {
2092            if ( $id eq 'system' ) {
2093                push @sys, $menu;
2094            }
2095            else {
2096                push @top, $menu;
2097            }
2098
2099            my $has_sub = 0;
2100            foreach my $sub (@sub) {
2101                my $sys_only = $sub->{id} =~ m/^system:/;
2102                $sub->{allowed} = 1;
2103                if ( $sub->{mode} ) {
2104                    $sub->{link} = $app->uri(
2105                        mode => $sub->{mode},
2106                        args => {
2107                            %{ $sub->{args} || {} },
2108                            (   $blog_id && !$sys_only
2109                                ? ( blog_id => $blog_id )
2110                                : ()
2111                            )
2112                        }
2113                    );
2114                }
2115                $sub->{allowed} = 0
2116                    if $sub->{view} && ( $sub->{view} ne $view );
2117                if ( $sub->{allowed} ) {
2118                    my $sub_perms
2119                        = ( $sys_only || ( $sub->{view} || '' ) eq 'system' )
2120                        ? $app->user->permissions(0)
2121                        : $perms;
2122                    if ($sub_perms
2123                        && (my $p
2124                            = $blog_id
2125                            ? $sub->{permission} || $sub->{system_permission}
2126                            : $sub->{system_permission}
2127                            || $sub->{permission}
2128                        )
2129                        )
2130                    {
2131                        my $allowed = 0;
2132                        my @p = split /,/, $p;
2133                        foreach my $p (@p) {
2134                            my $perm = 'can_' . $p;
2135                            $allowed = 1, last
2136                                if ( $sub_perms && $sub_perms->$perm() )
2137                                || $admin;
2138                        }
2139                        $sub->{allowed} = $allowed;
2140                    }
2141                    elsif ( !$sub_perms && !$blog_id ) {
2142                        $sub->{allowed} = 0
2143                            if ( $sub->{system_permission}
2144                            || $sub->{permission} )
2145                            && !$admin;
2146                    }
2147                    $has_sub = 1 if $sub->{allowed};
2148                }
2149            }
2150            if ( $menu->{mode} ) {
2151                my $sys_only = 1 if $menu->{id} eq 'system';
2152                $menu->{link} = $app->uri(
2153                    mode => $menu->{mode},
2154                    args => {
2155                        %{ $menu->{args} || {} },
2156                        (   $blog_id
2157                                && !$sys_only ? ( blog_id => $blog_id ) : ()
2158                        )
2159                    }
2160                );
2161            }
2162            @sub = sort { $a->{order} <=> $b->{order} } @sub;
2163
2164            @sub = grep { $_->{allowed} } @sub if $hide_disabled_options;
2165            if ( !$menu->{mode} ) {
2166                $menu->{link} = $sub[0]->{link};
2167            }
2168            $menu->{sub_nav_loop} = \@sub;
2169
2170            if ( $menu->{allowed} ) {
2171                $menu->{allowed} = 0 unless $has_sub;
2172            }
2173        }
2174    }
2175
2176    @top = grep { $_->{allowed} } @top if $hide_disabled_options;
2177    @sys = grep { $_->{allowed} } @sys if $hide_disabled_options;
2178    @top = sort { $a->{order} <=> $b->{order} } @top;
2179    $param->{top_nav_loop} = \@top;
2180    $param->{sys_nav_loop} = \@sys;
2181}
2182
2183sub return_to_dashboard {
2184    my $app = shift;
2185    my (%param) = @_;
2186    $param{redirect} = 1 unless %param;
2187    my $blog_id = $app->param('blog_id');
2188    $param{blog_id} = $blog_id if $blog_id;
2189    return $app->redirect(
2190        $app->uri( mode => 'dashboard', args => \%param ) );
2191}
2192
2193sub list_pref {
2194    my $app      = shift;
2195    my ($list)   = @_;
2196    my $updating = $app->mode eq 'update_list_prefs';
2197    unless ($updating) {
2198        my $pref = $app->request("list_pref_$list");
2199        return $pref if defined $pref;
2200    }
2201
2202    my $cookie = $app->cookie_val('mt_list_pref') || '';
2203    my $mode = $app->mode;
2204
2205    # defaults:
2206    my $d = $app->config->DefaultListPrefs || {};
2207    my %default = (
2208        Rows       => 25,
2209        Format     => 'Compact',
2210        SortOrder  => 'Ascend',      # Ascend|Descend
2211        Button     => 'Above',       # Above|Below|Both
2212        DateFormat => 'Relative',    # Relative|Full
2213    );
2214    if ( ( $list eq 'comment' ) || ( $list eq 'ping' ) ) {
2215        $default{Format} = 'expanded';
2216    }
2217    $default{$_} = lc( $d->{$_} ) for keys %$d;
2218    my $list_pref;
2219    if ( $list eq 'main_menu' ) {
2220        $list_pref = {
2221            'sort' => 'name',
2222            order  => $default{SortOrder} || 'ascend',
2223            view   => $default{Format} || 'compact',
2224            dates  => $default{DateFormat} || 'relative',
2225        };
2226    }
2227    else {
2228        $list_pref = {
2229            rows  => $default{Rows}       || 25,
2230            view  => $default{Format}     || 'compact',
2231            bar   => $default{Button}     || 'above',
2232            dates => $default{DateFormat} || 'relative',
2233        };
2234    }
2235    my @list_prefs = split /;/, $cookie;
2236    my $new_cookie = '';
2237    foreach my $pref (@list_prefs) {
2238        my ( $name, $prefs ) = $pref =~ m/^(\w+):(.*)$/;
2239        next unless $name && $prefs;
2240        if ( $name eq $list ) {
2241            my @prefs = split /,/, $prefs;
2242            foreach (@prefs) {
2243                my ( $k, $v ) = split /=/;
2244                $list_pref->{$k} = $v if exists $list_pref->{$k};
2245            }
2246        }
2247        else {
2248            $new_cookie .= ( $new_cookie ne '' ? ';' : '' ) . $pref;
2249        }
2250    }
2251
2252    if ($updating) {
2253        my $updated = 0;
2254        if ( my $limit = $app->param('limit') ) {
2255            $limit = 20 if $limit eq 'none';
2256            $list_pref->{rows} = $limit > 0 ? $limit : 20;
2257            $updated = 1;
2258        }
2259        if ( my $view = $app->param('verbosity') ) {
2260            if ( $view =~ m!^compact|expanded$! ) {
2261                $list_pref->{view} = $view;
2262                $updated = 1;
2263            }
2264        }
2265        if ( my $bar = $app->param('actions') ) {
2266            if ( $bar =~ m!^above|below|both$! ) {
2267                $list_pref->{bar} = $bar;
2268                $updated = 1;
2269            }
2270        }
2271        if ( my $ord = $app->param('order') ) {
2272            if ( $ord =~ m!^ascend|descend$! ) {
2273                $list_pref->{order} = $ord;
2274                $updated = 1;
2275            }
2276        }
2277        if ( my $sort = $app->param('sort') ) {
2278            if ( $sort =~ m!^name|created|updated$! ) {
2279                $list_pref->{'sort'} = $sort;
2280                $updated = 1;
2281            }
2282        }
2283        if ( my $dates = $app->param('dates') ) {
2284            if ( $dates =~ m!^relative|full$! ) {
2285                $list_pref->{'dates'} = $dates;
2286                $updated = 1;
2287            }
2288        }
2289
2290        if ($updated) {
2291            my @list_prefs;
2292            foreach ( keys %$list_pref ) {
2293                push @list_prefs, $_ . '=' . $list_pref->{$_};
2294            }
2295            my $prefs = join ',', @list_prefs;
2296            $new_cookie
2297                .= ( $new_cookie ne '' ? ';' : '' ) . $list . ':' . $prefs;
2298            $app->bake_cookie(
2299                -name    => 'mt_list_pref',
2300                -value   => $new_cookie,
2301                -expires => '+10y'
2302            );
2303        }
2304    }
2305
2306    if ( $list_pref->{rows} ) {
2307        $list_pref->{ "limit_" . $list_pref->{rows} } = $list_pref->{rows};
2308    }
2309    if ( $list_pref->{view} ) {
2310        $list_pref->{ "view_" . $list_pref->{view} } = 1;
2311    }
2312    if ( $list_pref->{dates} ) {
2313        $list_pref->{ "dates_" . $list_pref->{dates} } = 1;
2314    }
2315    if ( $list_pref->{bar} ) {
2316        if ( lc $list_pref->{bar} eq 'both' ) {
2317            $list_pref->{"position_actions_both"}   = 1;
2318            $list_pref->{"position_actions_top"}    = 1;
2319            $list_pref->{"position_actions_bottom"} = 1;
2320        }
2321        elsif ( lc $list_pref->{bar} eq 'below' ) {
2322            $list_pref->{"position_actions_bottom"} = 1;
2323        }
2324        elsif ( lc $list_pref->{bar} eq 'above' ) {
2325            $list_pref->{"position_actions_top"} = 1;
2326        }
2327    }
2328    if ( $list_pref->{'sort'} ) {
2329        $list_pref->{ 'sort_' . $list_pref->{'sort'} } = 1;
2330    }
2331    if ( $list_pref->{'order'} ) {
2332        $list_pref->{ 'order_' . $list_pref->{'order'} } = 1;
2333    }
2334    $app->request( "list_pref_$list", $list_pref );
2335}
2336
2337sub make_feed_link {
2338    my $app = shift;
2339    my ( $view, $params ) = @_;
2340    my $user = $app->user;
2341    return if ( $user->api_password || '' ) eq '';
2342
2343    $params ||= {};
2344    $params->{view}     = $view;
2345    $params->{username} = $user->name;
2346    $params->{token} = perl_sha1_digest_hex( 'feed:' . $user->api_password );
2347    $app->base
2348        . $app->mt_path
2349        . $app->config('ActivityFeedScript')
2350        . $app->uri_params( args => $params );
2351}
2352
2353sub show_error {
2354    my $app = shift;
2355    my ($param) = @_;
2356
2357    # handle legacy scalar error string signature
2358    $param = { error => $param } unless ref($param) eq 'HASH';
2359
2360    my $mode = $app->mode;
2361    if ( $mode eq 'rebuild' ) {
2362
2363        my $r = MT::Request->instance;
2364        if ( my $tmpl = $r->cache('build_template') ) {
2365
2366            # this is the template that likely caused the rebuild error
2367            my $tmpl_edit_link = $app->uri(
2368                        mode => 'view',
2369                        args => {
2370                            blog_id => $tmpl->blog_id,
2371                            '_type' => 'template',
2372                            id      => $tmpl->id
2373                        }
2374                    );
2375
2376            if ( $app->param('fs') ) {
2377                $param->{fs} = 1;
2378                if ( exists $app->{goback} ) {
2379                    $param->{goback} = "window.location='" . $app->{goback} . "'";
2380                    if ( $tmpl_edit_link ne $app->{goback} ) {
2381                        push @{ $param->{button_loop} ||= [] },
2382                          {
2383                            link => $tmpl_edit_link,
2384                            label => $app->translate("Edit Template"),
2385                          };
2386                    }
2387                }
2388                else {
2389                    $param->{goback} = "window.location='$tmpl_edit_link'";
2390                }
2391            }
2392            else {
2393                push @{ $param->{button_loop} ||= [] },
2394                  {
2395                    link => $tmpl_edit_link,
2396                    label => $app->translate("Edit Template"),
2397                  };
2398            }
2399        }
2400
2401        if ( !exists( $param->{goback} ) && exists( $app->{goback} ) ) {
2402            $param->{goback} = "window.location='" . $app->{goback} . "'";
2403        }
2404        else {
2405            my $blog_id = $app->param('blog_id');
2406            my $url     = $app->uri(
2407                mode => 'rebuild_confirm',
2408                args => { blog_id => $blog_id }
2409            );
2410            $param->{goback} ||= qq{window.location='$url'};
2411        }
2412        $param->{value} ||= $app->{value} || $app->translate('Go Back');
2413    }
2414
2415    return $app->SUPER::show_error($param);
2416}
2417
2418sub load_default_entry_prefs {
2419    my $app = shift;
2420    my $prefs;
2421    require MT::Permission;
2422    my $blog_id;
2423    $blog_id = $app->blog->id if $app->blog;
2424    my $perm
2425        = MT::Permission->load( { blog_id => $blog_id, author_id => 0 } );
2426    my %default = %{ $app->config->DefaultEntryPrefs };
2427    if ( $perm && $perm->entry_prefs ) {
2428        $prefs = $perm->entry_prefs;
2429    }
2430    else {
2431        if ( lc( $default{type} ) eq 'custom' ) {
2432            my %map = (
2433                Category   => 'category',
2434                Excerpt    => 'excerpt',
2435                Keywords   => 'keywords',
2436                Tags       => 'tags',
2437                Publishing => 'publishing',
2438                Feedback   => 'feedback',
2439            );
2440            my @p;
2441            foreach my $p ( keys %map ) {
2442                push @p,
2443                    $map{$p} . ':' . ( $default{$p} || $default{ lc $p } )
2444                    if ( $default{$p} || $default{ lc $p } );
2445            }
2446            $prefs = join ',', @p;
2447            $prefs ||= 'Custom';
2448        }
2449        elsif ( lc( $default{type} ) ne 'default' ) {
2450            $prefs = 'Advanced';
2451        }
2452        $default{button} = 'Bottom' if lc( $default{button} ) eq 'below';
2453        $default{button} = 'Top'    if lc( $default{button} ) eq 'above';
2454        $prefs .= '|' . $default{button} if $prefs;
2455    }
2456    $prefs ||= 'Default|Bottom';
2457    return $prefs;
2458}
2459
2460sub load_template_prefs {
2461    my $app = shift;
2462    my ($prefs) = @_;
2463    my %param;
2464
2465    if ( !$prefs ) {
2466        $prefs = '';
2467    }
2468    my @p = split /,/, $prefs;
2469    for my $p (@p) {
2470        if ( $p =~ m/^(.+?):(\d+)$/ ) {
2471            $param{ 'disp_prefs_height_' . $1 } = $2;
2472        }
2473    }
2474    \%param;
2475}
2476
2477sub _parse_entry_prefs {
2478    my $app = shift;
2479    my ( $prefs, $param ) = @_;
2480
2481    my @p = split /,/, $prefs;
2482    for my $p (@p) {
2483        if ( $p =~ m/^(.+?):(\d+)$/ ) {
2484            my ( $name, $num ) = ( $1, $2 );
2485            if ($num) {
2486                $param->{ 'disp_prefs_height_' . $name } = $num;
2487            }
2488            $param->{ 'disp_prefs_show_' . $name } = 1;
2489        }
2490        else {
2491            $p = 'Default' if lc($p) eq 'basic';
2492            if ( ( lc($p) eq 'advanced' ) || ( lc($p) eq 'default' ) ) {
2493                $param->{ 'disp_prefs_' . $p } = 1;
2494                foreach my $def (
2495                    qw(title body category tags keywords feedback publishing )
2496                    )
2497                {
2498                    $param->{ 'disp_prefs_show_' . $def } = 1;
2499                }
2500                if ( lc($p) eq 'advanced' ) {
2501                    foreach my $def (qw(excerpt feedback)) {
2502                        $param->{ 'disp_prefs_show_' . $def } = 1;
2503                    }
2504                }
2505            }
2506            else {
2507                $param->{ 'disp_prefs_show_' . $p } = 1;
2508            }
2509        }
2510    }
2511}
2512
2513sub load_entry_prefs {
2514    my $app = shift;
2515    my ($prefs) = @_;
2516    my %param;
2517    my $pos;
2518    my $is_from_db = 1;
2519
2520    # defaults:
2521    if ( !$prefs ) {
2522        $prefs = $app->load_default_entry_prefs;
2523        ( $prefs, $pos ) = split /\|/, $prefs;
2524        $is_from_db = 0;
2525        $app->_parse_entry_prefs( $prefs, \%param );
2526        my @fields;
2527        foreach ( keys %param ) {
2528            if (m/^disp_prefs_show_(.+)/) {
2529                push @fields, { name => $1 };
2530            }
2531        }
2532        $param{disp_prefs_default_fields} = \@fields;
2533    }
2534    else {
2535        ( $prefs, $pos ) = split /\|/, $prefs;
2536    }
2537    $app->_parse_entry_prefs( $prefs, \%param );
2538    if ($is_from_db) {
2539        my $default_prefs = $app->load_default_entry_prefs;
2540        ( $default_prefs, my ($default_pos) ) = split /\|/, $default_prefs;
2541        $pos ||= $default_pos;
2542        $app->_parse_entry_prefs( $default_prefs, \my %def_param );
2543        my @fields;
2544        foreach ( keys %def_param ) {
2545            if (m/^disp_prefs_show_(.+)/) {
2546                push @fields, { name => $1 };
2547            }
2548        }
2549        if ( $prefs eq 'Default' ) {
2550            foreach my $p ( keys %param ) {
2551                delete $param{$p} if $p =~ m/^disp_prefs_show_/;
2552            }
2553        }
2554        if ( $param{disp_prefs_Default} ) {
2555
2556            # apply default settings
2557            %param = ( %def_param, %param );
2558        }
2559        $param{disp_prefs_default_fields} = \@fields;
2560    }
2561    $pos ||= 'Bottom';
2562    if (   !exists $param{'disp_prefs_Default'}
2563        && !exists $param{'disp_prefs_Advanced'} )
2564    {
2565        $param{'disp_prefs_Custom'} = 1;
2566    }
2567    if ( lc $pos eq 'both' ) {
2568        $param{'position_actions_top'}    = 1;
2569        $param{'position_actions_bottom'} = 1;
2570        $param{'position_actions_both'}   = 1;
2571    }
2572    else {
2573        $param{ 'position_actions_' . $pos } = 1;
2574    }
2575    \%param;
2576}
2577
2578sub _convert_word_chars {
2579    my ( $app, $s ) = @_;
2580    return '' if !defined($s) || ( $s eq '' );
2581    return $s if 'utf-8' ne lc( $app->charset );
2582
2583    my $blog = $app->blog;
2584    my $smart_replace
2585        = $blog
2586        ? $blog->smart_replace
2587        : MT->config->NwcSmartReplace;
2588    return $s if $smart_replace == 2;
2589
2590    if ($smart_replace) {
2591
2592        # html character entity replacements
2593        $s =~ s/\342\200\231/&#8217;/g;
2594        $s =~ s/\342\200\230/&#8216;/g;
2595        $s =~ s/\342\200\246/&#133;/g;
2596        $s =~ s/\342\200\223/-/g;
2597        $s =~ s/\342\200\224/&#8212;/g;
2598        $s =~ s/\342\200\234/&#8220;/g;
2599        $s =~ s/\342\200\235/&#8221;/g;
2600    }
2601    else {
2602
2603        # ascii equivalent replacements
2604        $s =~ s/\342\200[\230\231]/'/g;
2605        $s =~ s/\342\200\246/.../g;
2606        $s =~ s/\342\200\223/-/g;
2607        $s =~ s/\342\200\224/--/g;
2608        $s =~ s/\342\200[\234\235]/"/g;
2609    }
2610
2611    # While we're fixing Word, remove processing instructions with
2612    # colons, as they can break PHP.
2613    $s =~ s{ <\? xml:namespace [^>]*> }{}ximsg;
2614<