# Movable Type (r) (C) 2001-2009 Six Apart, Ltd. All Rights Reserved. # This code cannot be redistributed without permission from www.sixapart.com. # For more information, consult your Movable Type license. # # $Id: ContextHandlers.pm 3527 2009-03-12 00:35:23Z bsmith $ package MT::Template::Context; use strict; use MT; use MT::Util qw( start_end_day start_end_week start_end_month week2ymd archive_file_for format_ts offset_time_list first_n_words dirify get_entry encode_html encode_js remove_html wday_from_ts days_in spam_protect encode_php encode_url decode_html encode_xml decode_xml relative_date asset_cleanup ); use MT::Request; use Time::Local qw( timegm timelocal ); use MT::Promise qw( delay ); use MT::Category; use MT::Entry; use MT::I18N qw( first_n_text const uppercase lowercase substr_text length_text wrap_text ); use MT::Asset; sub init_default_handlers {} sub init_default_filters {} sub core_tags { return { help_url => sub { MT->translate('http://www.movabletype.org/documentation/appendices/tags/%t.html') }, block => { 'App:Setting' => \&_hdlr_app_setting, 'App:Widget' => \&_hdlr_app_widget, 'App:StatusMsg' => \&_hdlr_app_statusmsg, 'App:Listing' => \&_hdlr_app_listing, 'App:SettingGroup' => \&_hdlr_app_setting_group, 'App:Form' => \&_hdlr_app_form, # Core tags 'If?' => \&_hdlr_if, 'Unless?' => sub { defined(my $r = &_hdlr_if) or return; !$r }, 'For' => \&_hdlr_for, 'Else' => \&_hdlr_else, 'ElseIf' => \&_hdlr_elseif, 'IfImageSupport?' => \&_hdlr_if_image_support, 'EntryIfTagged?' => \&_hdlr_entry_if_tagged, 'IfArchiveTypeEnabled?' => \&_hdlr_archive_type_enabled, 'IfArchiveType?' => \&_hdlr_if_archive_type, 'IfCategory?' => \&_hdlr_if_category, 'EntryIfCategory?' => \&_hdlr_if_category, 'IfExternalUserManagement?' => \&_hdlr_if_external_user_management, 'IfCommenterRegistrationAllowed?' => \&_hdlr_if_commenter_registration_allowed, # Subcategory handlers 'SubCatIsFirst?' => \&_hdlr_sub_cat_is_first, 'SubCatIsLast?' => \&_hdlr_sub_cat_is_last, 'HasSubCategories?' => \&_hdlr_has_sub_categories, 'HasNoSubCategories?' => \&_hdlr_has_no_sub_categories, 'HasParentCategory?' => \&_hdlr_has_parent_category, 'HasNoParentCategory?' => \&_hdlr_has_no_parent_category, 'IfIsAncestor?' => \&_hdlr_is_ancestor, 'IfIsDescendant?' => \&_hdlr_is_descendant, IfStatic => \&_hdlr_pass_tokens, IfDynamic => \&_hdlr_pass_tokens_else, 'AssetIfTagged?' => \&_hdlr_asset_if_tagged, 'PageIfTagged?' => \&_hdlr_page_if_tagged, 'IfFolder?' => \&_hdlr_if_folder, 'FolderHeader?' => \&_hdlr_folder_header, 'FolderFooter?' => \&_hdlr_folder_footer, 'HasSubFolders?' => \&_hdlr_has_sub_folders, 'HasParentFolder?' => \&_hdlr_has_parent_folder, IncludeBlock => \&_hdlr_include_block, Loop => \&_hdlr_loop, Section => \&_hdlr_section, 'IfNonEmpty?' => \&_hdlr_if_nonempty, 'IfNonZero?' => \&_hdlr_if_nonzero, 'IfCommenterTrusted?' => \&_hdlr_commenter_trusted, 'CommenterIfTrusted?' => \&_hdlr_commenter_trusted, 'IfCommenterIsAuthor?' => \&_hdlr_commenter_isauthor, 'IfCommenterIsEntryAuthor?' => \&_hdlr_commenter_isauthor, 'IfBlog?' => \&_hdlr_blog_id, 'IfAuthor?' => \&_hdlr_if_author, 'AuthorHasEntry?' => \&_hdlr_author_has_entry, 'AuthorHasPage?' => \&_hdlr_author_has_page, Authors => \&_hdlr_authors, AuthorNext => \&_hdlr_author_next_prev, AuthorPrevious => \&_hdlr_author_next_prev, Blogs => \&_hdlr_blogs, 'BlogIfCCLicense?' => \&_hdlr_blog_if_cc_license, Entries => \&_hdlr_entries, EntriesHeader => \&_hdlr_pass_tokens, EntriesFooter => \&_hdlr_pass_tokens, EntryCategories => \&_hdlr_entry_categories, EntryAdditionalCategories => \&_hdlr_entry_additional_categories, 'BlogIfCommentsOpen?' => \&_hdlr_blog_if_comments_open, EntryPrevious => \&_hdlr_entry_previous, EntryNext => \&_hdlr_entry_next, EntryTags => \&_hdlr_entry_tags, DateHeader => \&_hdlr_pass_tokens, DateFooter => \&_hdlr_pass_tokens, PingsHeader => \&_hdlr_pass_tokens, PingsFooter => \&_hdlr_pass_tokens, ArchivePrevious => \&_hdlr_archive_prev_next, ArchiveNext => \&_hdlr_archive_prev_next, SetVarBlock => \&_hdlr_set_var, SetVarTemplate => \&_hdlr_set_var, SetVars => \&_hdlr_set_vars, SetHashVar => \&_hdlr_set_hashvar, 'IfCommentsModerated?' => \&_hdlr_comments_moderated, 'IfRegistrationRequired?' => \&_hdlr_reg_required, 'IfRegistrationNotRequired?' => \&_hdlr_reg_not_required, 'IfRegistrationAllowed?' => \&_hdlr_reg_allowed, 'IfTypeKeyToken?' => \&_hdlr_if_typekey_token, Comments => \&_hdlr_comments, CommentsHeader => \&_hdlr_pass_tokens, CommentsFooter => \&_hdlr_pass_tokens, CommentEntry => \&_hdlr_comment_entry, 'CommentIfModerated?' => \&_hdlr_comment_if_moderated, CommentParent => \&_hdlr_comment_parent, CommentReplies => \&_hdlr_comment_replies, 'IfCommentParent?' => \&_hdlr_if_comment_parent, 'IfCommentReplies?' => \&_hdlr_if_comment_replies, IndexList => \&_hdlr_index_list, Archives => \&_hdlr_archive_set, ArchiveList => \&_hdlr_archives, ArchiveListHeader => \&_hdlr_pass_tokens, ArchiveListFooter => \&_hdlr_pass_tokens, Calendar => \&_hdlr_calendar, CalendarWeekHeader => \&_hdlr_pass_tokens, CalendarWeekFooter => \&_hdlr_pass_tokens, CalendarIfBlank => \&_hdlr_pass_tokens, CalendarIfToday => \&_hdlr_pass_tokens, CalendarIfEntries => \&_hdlr_pass_tokens, CalendarIfNoEntries => \&_hdlr_pass_tokens, Categories => \&_hdlr_categories, 'CategoryIfAllowPings?' => \&_hdlr_category_allow_pings, CategoryPrevious => \&_hdlr_category_prevnext, CategoryNext => \&_hdlr_category_prevnext, Pings => \&_hdlr_pings, PingsSent => \&_hdlr_pings_sent, PingEntry => \&_hdlr_ping_entry, 'IfAllowCommentHTML?' => \&_hdlr_if_allow_comment_html, 'IfCommentsAllowed?' => \&_hdlr_if_comments_allowed, 'IfCommentsAccepted?' => \&_hdlr_if_comments_accepted, 'IfCommentsActive?' => \&_hdlr_if_comments_active, 'IfPingsAllowed?' => \&_hdlr_if_pings_allowed, 'IfPingsAccepted?' => \&_hdlr_if_pings_accepted, 'IfPingsActive?' => \&_hdlr_if_pings_active, 'IfPingsModerated?' => \&_hdlr_if_pings_moderated, 'IfNeedEmail?' => \&_hdlr_if_need_email, 'IfRequireCommentEmails?' => \&_hdlr_if_need_email, 'EntryIfAllowComments?' => \&_hdlr_entry_if_allow_comments, 'EntryIfCommentsOpen?' => \&_hdlr_entry_if_comments_open, 'EntryIfAllowPings?' => \&_hdlr_entry_if_allow_pings, 'EntryIfExtended?' => \&_hdlr_entry_if_extended, SubCategories => \&_hdlr_sub_categories, TopLevelCategories => \&_hdlr_top_level_categories, ParentCategory => \&_hdlr_parent_category, ParentCategories => \&_hdlr_parent_categories, TopLevelParent => \&_hdlr_top_level_parent, EntriesWithSubCategories => \&_hdlr_entries_with_sub_categories, Tags => \&_hdlr_tags, Ignore => sub { '' }, # Asset handlers EntryAssets => \&_hdlr_assets, PageAssets => \&_hdlr_assets, Assets => \&_hdlr_assets, AssetTags => \&_hdlr_asset_tags, Asset => \&_hdlr_asset, AssetIsFirstInRow => \&_hdlr_pass_tokens, AssetIsLastInRow => \&_hdlr_pass_tokens, AssetsHeader => \&_hdlr_pass_tokens, AssetsFooter => \&_hdlr_pass_tokens, AuthorUserpicAsset => \&_hdlr_author_userpic_asset, EntryAuthorUserpicAsset => \&_hdlr_entry_author_userpic_asset, CommenterUserpicAsset => \&_hdlr_commenter_userpic_asset, # Page handlers Pages => \&_hdlr_pages, PagePrevious => \&_hdlr_page_previous, PageNext => \&_hdlr_page_next, PageTags => \&_hdlr_page_tags, PageFolder => \&_hdlr_page_folder, PagesHeader => \&_hdlr_pass_tokens, PagesFooter => \&_hdlr_pass_tokens, # Folder handlers Folders => \&_hdlr_folders, FolderPrevious => \&_hdlr_folder_prevnext, FolderNext => \&_hdlr_folder_prevnext, SubFolders => \&_hdlr_sub_folders, ParentFolders => \&_hdlr_parent_folders, ParentFolder => \&_hdlr_parent_folder, TopLevelFolders => \&_hdlr_top_level_folders, TopLevelFolder => \&_hdlr_top_level_folder, # Pager handlers 'IfMoreResults?' => \&_hdlr_if_more_results, 'IfPreviousResults?' => \&_hdlr_if_previous_results, PagerBlock => \&_hdlr_pager_block, IfCurrentPage => \&_hdlr_pass_tokens, # stubs for mt-search tags used in template includes IfTagSearch => sub { '' }, SearchResults => sub { '' }, IfStraightSearch => sub { '' }, NoSearchResults => \&_hdlr_pass_tokens, NoSearch => \&_hdlr_pass_tokens, SearchResultsHeader => sub { '' }, SearchResultsFooter => sub { '' }, BlogResultHeader => sub { '' }, BlogResultFooter => sub { '' }, IfMaxResultsCutoff => sub { '' }, }, function => { 'App:PageActions' => \&_hdlr_app_page_actions, 'App:ListFilters' => \&_hdlr_app_list_filters, 'App:ActionBar' => \&_hdlr_app_action_bar, 'App:Link' => \&_hdlr_app_link, Var => \&_hdlr_get_var, CGIPath => \&_hdlr_cgi_path, AdminCGIPath => \&_hdlr_admin_cgi_path, CGIRelativeURL => \&_hdlr_cgi_relative_url, CGIHost => \&_hdlr_cgi_host, StaticWebPath => \&_hdlr_static_path, StaticFilePath => \&_hdlr_static_file_path, AdminScript => \&_hdlr_admin_script, CommentScript => \&_hdlr_comment_script, TrackbackScript => \&_hdlr_trackback_script, SearchScript => \&_hdlr_search_script, XMLRPCScript => \&_hdlr_xmlrpc_script, AtomScript => \&_hdlr_atom_script, NotifyScript => \&_hdlr_notify_script, Date => \&_hdlr_sys_date, Version => \&_hdlr_mt_version, ProductName => \&_hdlr_product_name, PublishCharset => \&_hdlr_publish_charset, DefaultLanguage => \&_hdlr_default_language, CGIServerPath => \&_hdlr_cgi_server_path, ConfigFile => \&_hdlr_config_file, UserSessionCookieTimeout => \&_hdlr_user_session_cookie_timeout, UserSessionCookieName => \&_hdlr_user_session_cookie_name, UserSessionCookiePath => \&_hdlr_user_session_cookie_path, UserSessionCookieDomain => \&_hdlr_user_session_cookie_domain, CommenterNameThunk => \&_hdlr_commenter_name_thunk, CommenterUsername => \&_hdlr_commenter_username, CommenterName => \&_hdlr_commenter_name, CommenterEmail => \&_hdlr_commenter_email, CommenterAuthType => \&_hdlr_commenter_auth_type, CommenterAuthIconURL => \&_hdlr_commenter_auth_icon_url, CommenterUserpic => \&_hdlr_commenter_userpic, CommenterUserpicURL => \&_hdlr_commenter_userpic_url, CommenterID => \&_hdlr_commenter_id, CommenterURL => \&_hdlr_commenter_url, FeedbackScore => \&_hdlr_feedback_score, AuthorID => \&_hdlr_author_id, AuthorName => \&_hdlr_author_name, AuthorDisplayName =>\&_hdlr_author_display_name, AuthorEmail => \&_hdlr_author_email, AuthorURL => \&_hdlr_author_url, AuthorAuthType => \&_hdlr_author_auth_type, AuthorAuthIconURL => \&_hdlr_author_auth_icon_url, AuthorUserpic => \&_hdlr_author_userpic, AuthorUserpicURL => \&_hdlr_author_userpic_url, AuthorBasename => \&_hdlr_author_basename, BlogID => \&_hdlr_blog_id, BlogName => \&_hdlr_blog_name, BlogDescription => \&_hdlr_blog_description, BlogLanguage => \&_hdlr_blog_language, BlogURL => \&_hdlr_blog_url, BlogArchiveURL => \&_hdlr_blog_archive_url, BlogRelativeURL => \&_hdlr_blog_relative_url, BlogSitePath => \&_hdlr_blog_site_path, BlogHost => \&_hdlr_blog_host, BlogTimezone => \&_hdlr_blog_timezone, BlogCategoryCount => \&_hdlr_blog_category_count, BlogEntryCount => \&_hdlr_blog_entry_count, BlogCommentCount => \&_hdlr_blog_comment_count, BlogPingCount => \&_hdlr_blog_ping_count, BlogCCLicenseURL => \&_hdlr_blog_cc_license_url, BlogCCLicenseImage => \&_hdlr_blog_cc_license_image, CCLicenseRDF => \&_hdlr_cc_license_rdf, BlogFileExtension => \&_hdlr_blog_file_extension, BlogTemplateSetID => \&_hdlr_blog_template_set_id, EntriesCount => \&_hdlr_entries_count, EntryID => \&_hdlr_entry_id, EntryTitle => \&_hdlr_entry_title, EntryStatus => \&_hdlr_entry_status, EntryFlag => \&_hdlr_entry_flag, EntryCategory => \&_hdlr_entry_category, EntryBody => \&_hdlr_entry_body, EntryMore => \&_hdlr_entry_more, EntryExcerpt => \&_hdlr_entry_excerpt, EntryKeywords => \&_hdlr_entry_keywords, EntryLink => \&_hdlr_entry_link, EntryBasename => \&_hdlr_entry_basename, EntryAtomID => \&_hdlr_entry_atom_id, EntryPermalink => \&_hdlr_entry_permalink, EntryClass => \&_hdlr_entry_class, EntryClassLabel => \&_hdlr_entry_class_label, EntryAuthor => \&_hdlr_entry_author, EntryAuthorDisplayName => \&_hdlr_entry_author_display_name, EntryAuthorUsername => \&_hdlr_entry_author_username, EntryAuthorEmail => \&_hdlr_entry_author_email, EntryAuthorURL => \&_hdlr_entry_author_url, EntryAuthorLink => \&_hdlr_entry_author_link, EntryAuthorNickname => \&_hdlr_entry_author_nick, EntryAuthorID => \&_hdlr_entry_author_id, EntryAuthorUserpic => \&_hdlr_entry_author_userpic, EntryAuthorUserpicURL => \&_hdlr_entry_author_userpic_url, EntryDate => \&_hdlr_entry_date, EntryCreatedDate => \&_hdlr_entry_create_date, EntryModifiedDate => \&_hdlr_entry_mod_date, EntryCommentCount => \&_hdlr_entry_comments, EntryTrackbackCount => \&_hdlr_entry_ping_count, EntryTrackbackLink => \&_hdlr_entry_tb_link, EntryTrackbackData => \&_hdlr_entry_tb_data, EntryTrackbackID => \&_hdlr_entry_tb_id, EntryBlogID => \&_hdlr_entry_blog_id, EntryBlogName => \&_hdlr_entry_blog_name, EntryBlogDescription => \&_hdlr_entry_blog_description, EntryBlogURL => \&_hdlr_entry_blog_url, EntryEditLink => \&_hdlr_entry_edit_link, Include => \&_hdlr_include, Link => \&_hdlr_link, WidgetManager => \&_hdlr_widget_manager, WidgetSet => \&_hdlr_widget_manager, ErrorMessage => \&_hdlr_error_message, GetVar => \&_hdlr_get_var, SetVar => \&_hdlr_set_var, TypeKeyToken => \&_hdlr_typekey_token, CommentFields => \&_hdlr_comment_fields, RemoteSignOutLink => \&_hdlr_remote_sign_out_link, RemoteSignInLink => \&_hdlr_remote_sign_in_link, SignOutLink => \&_hdlr_sign_out_link, SignInLink => \&_hdlr_sign_in_link, CommentID => \&_hdlr_comment_id, CommentBlogID => \&_hdlr_comment_blog_id, CommentEntryID => \&_hdlr_comment_entry_id, CommentName => \&_hdlr_comment_author, CommentIP => \&_hdlr_comment_ip, CommentAuthor => \&_hdlr_comment_author, CommentAuthorLink => \&_hdlr_comment_author_link, CommentAuthorIdentity => \&_hdlr_comment_author_identity, CommentEmail => \&_hdlr_comment_email, CommentLink => \&_hdlr_comment_link, CommentURL => \&_hdlr_comment_url, CommentBody => \&_hdlr_comment_body, CommentOrderNumber => \&_hdlr_comment_order_num, CommentDate => \&_hdlr_comment_date, CommentParentID => \&_hdlr_comment_parent_id, CommentReplyToLink => \&_hdlr_comment_reply_link, CommentPreviewAuthor => \&_hdlr_comment_author, CommentPreviewIP => \&_hdlr_comment_ip, CommentPreviewAuthorLink => \&_hdlr_comment_author_link, CommentPreviewEmail => \&_hdlr_comment_email, CommentPreviewURL => \&_hdlr_comment_url, CommentPreviewBody => \&_hdlr_comment_body, CommentPreviewDate => \&_hdlr_date, CommentPreviewState => \&_hdlr_comment_prev_state, CommentPreviewIsStatic => \&_hdlr_comment_prev_static, CommentRepliesRecurse => \&_hdlr_comment_replies_recurse, IndexLink => \&_hdlr_index_link, IndexName => \&_hdlr_index_name, IndexBasename => \&_hdlr_index_basename, ArchiveLink => \&_hdlr_archive_link, ArchiveTitle => \&_hdlr_archive_title, ArchiveType => \&_hdlr_archive_type, ArchiveTypeLabel => \&_hdlr_archive_label, ArchiveLabel => \&_hdlr_archive_label, ArchiveCount => \&_hdlr_archive_count, ArchiveDate => \&_hdlr_date, ArchiveDateEnd => \&_hdlr_archive_date_end, ArchiveCategory => \&_hdlr_archive_category, ArchiveFile => \&_hdlr_archive_file, ImageURL => \&_hdlr_image_url, ImageWidth => \&_hdlr_image_width, ImageHeight => \&_hdlr_image_height, CalendarDay => \&_hdlr_calendar_day, CalendarCellNumber => \&_hdlr_calendar_cell_num, CalendarDate => \&_hdlr_date, CategoryID => \&_hdlr_category_id, CategoryLabel => \&_hdlr_category_label, CategoryBasename => \&_hdlr_category_basename, CategoryDescription => \&_hdlr_category_desc, CategoryArchiveLink => \&_hdlr_category_archive, CategoryCount => \&_hdlr_category_count, CategoryCommentCount => \&_hdlr_category_comment_count, CategoryTrackbackLink => \&_hdlr_category_tb_link, CategoryTrackbackCount => \&_hdlr_category_tb_count, PingsSentURL => \&_hdlr_pings_sent_url, PingTitle => \&_hdlr_ping_title, PingID => \&_hdlr_ping_id, PingURL => \&_hdlr_ping_url, PingExcerpt => \&_hdlr_ping_excerpt, PingBlogName => \&_hdlr_ping_blog_name, PingIP => \&_hdlr_ping_ip, PingDate => \&_hdlr_ping_date, FileTemplate => \&_hdlr_file_template, SignOnURL => \&_hdlr_signon_url, SubCatsRecurse => \&_hdlr_sub_cats_recurse, SubCategoryPath => \&_hdlr_sub_category_path, TagName => \&_hdlr_tag_name, TagLabel => \&_hdlr_tag_name, TagID => \&_hdlr_tag_id, TagCount => \&_hdlr_tag_count, TagRank => \&_hdlr_tag_rank, TagSearchLink => \&_hdlr_tag_search_link, TemplateNote => sub { '' }, TemplateCreatedOn => \&_hdlr_template_created_on, HTTPContentType => \&_hdlr_http_content_type, AssetID => \&_hdlr_asset_id, AssetFileName => \&_hdlr_asset_file_name, AssetLabel => \&_hdlr_asset_label, AssetURL => \&_hdlr_asset_url, AssetType => \&_hdlr_asset_type, AssetMimeType => \&_hdlr_asset_mime_type, AssetFilePath => \&_hdlr_asset_file_path, AssetDateAdded => \&_hdlr_asset_date_added, AssetAddedBy => \&_hdlr_asset_added_by, AssetProperty => \&_hdlr_asset_property, AssetFileExt => \&_hdlr_asset_file_ext, AssetThumbnailURL => \&_hdlr_asset_thumbnail_url, AssetLink => \&_hdlr_asset_link, AssetThumbnailLink => \&_hdlr_asset_thumbnail_link, AssetDescription => \&_hdlr_asset_description, AssetCount => \&_hdlr_asset_count, PageID => \&_hdlr_page_id, PageTitle=> \&_hdlr_page_title, PageBody => \&_hdlr_page_body, PageMore => \&_hdlr_page_more, PageDate => \&_hdlr_page_date, PageModifiedDate => \&_hdlr_page_modified_date, PageAuthorDisplayName => \&_hdlr_page_author_display_name, PageKeywords => \&_hdlr_page_keywords, PageBasename => \&_hdlr_page_basename, PagePermalink => \&_hdlr_page_permalink, PageAuthorEmail => \&_hdlr_page_author_email, PageAuthorLink => \&_hdlr_page_author_link, PageAuthorURL => \&_hdlr_page_author_url, PageExcerpt => \&_hdlr_page_excerpt, BlogPageCount => \&_hdlr_blog_page_count, FolderBasename => \&_hdlr_folder_basename, FolderCount => \&_hdlr_folder_count, FolderDescription => \&_hdlr_folder_description, FolderID => \&_hdlr_folder_id, FolderLabel => \&_hdlr_folder_label, FolderPath => \&_hdlr_folder_path, SubFolderRecurse => \&_hdlr_sub_folder_recurse, SearchString => sub { '' }, SearchResultCount => sub { 0 }, MaxResults => sub { '' }, SearchMaxResults => \&_hdlr_search_max_results, SearchIncludeBlogs => sub { '' }, SearchTemplateID => sub { 0 }, UserSessionState => \&_hdlr_user_session_state, BuildTemplateID => \&_hdlr_build_template_id, CaptchaFields => \&_hdlr_captcha_fields, # Rating related handlers EntryScore => \&_hdlr_entry_score, CommentScore => \&_hdlr_comment_score, PingScore => \&_hdlr_ping_score, AssetScore => \&_hdlr_asset_score, AuthorScore => \&_hdlr_author_score, EntryScoreHigh => \&_hdlr_entry_score_high, CommentScoreHigh => \&_hdlr_comment_score_high, PingScoreHigh => \&_hdlr_ping_score_high, AssetScoreHigh => \&_hdlr_asset_score_high, AuthorScoreHigh => \&_hdlr_author_score_high, EntryScoreLow => \&_hdlr_entry_score_low, CommentScoreLow => \&_hdlr_comment_score_low, PingScoreLow => \&_hdlr_ping_score_low, AssetScoreLow => \&_hdlr_asset_score_low, AuthorScoreLow => \&_hdlr_author_score_low, EntryScoreAvg => \&_hdlr_entry_score_avg, CommentScoreAvg => \&_hdlr_comment_score_avg, PingScoreAvg => \&_hdlr_ping_score_avg, AssetScoreAvg => \&_hdlr_asset_score_avg, AuthorScoreAvg => \&_hdlr_author_score_avg, EntryScoreCount => \&_hdlr_entry_score_count, CommentScoreCount => \&_hdlr_comment_score_count, PingScoreCount => \&_hdlr_ping_score_count, AssetScoreCount => \&_hdlr_asset_score_count, AuthorScoreCount => \&_hdlr_author_score_count, EntryRank => \&_hdlr_entry_rank, CommentRank => \&_hdlr_comment_rank, PingRank => \&_hdlr_ping_rank, AssetRank => \&_hdlr_asset_rank, AuthorRank => \&_hdlr_author_rank, # Pager related handlers PagerLink => \&_hdlr_pager_link, NextLink => \&_hdlr_next_link, PreviousLink => \&_hdlr_previous_link, CurrentPage => \&_hdlr_current_page, TotalPages => \&_hdlr_total_pages, }, modifier => { 'numify' => \&_fltr_numify, 'mteval' => \&_fltr_mteval, 'filters' => \&_fltr_filters, 'trim_to' => \&_fltr_trim_to, 'trim' => \&_fltr_trim, 'ltrim' => \&_fltr_ltrim, 'rtrim' => \&_fltr_rtrim, 'decode_html' => \&_fltr_decode_html, 'decode_xml' => \&_fltr_decode_xml, 'remove_html' => \&_fltr_remove_html, 'dirify' => \&_fltr_dirify, 'sanitize' => \&_fltr_sanitize, 'encode_sha1' => \&_fltr_sha1, 'encode_html' => \&_fltr_encode_html, 'encode_xml' => \&_fltr_encode_xml, 'encode_js' => \&_fltr_encode_js, 'encode_php' => \&_fltr_encode_php, 'encode_url' => \&_fltr_encode_url, 'upper_case' => \&_fltr_upper_case, 'lower_case' => \&_fltr_lower_case, 'strip_linefeeds' => \&_fltr_strip_linefeeds, 'space_pad' => \&_fltr_space_pad, 'zero_pad' => \&_fltr_zero_pad, 'sprintf' => \&_fltr_sprintf, 'regex_replace' => \&_fltr_regex_replace, 'capitalize' => \&_fltr_capitalize, 'count_characters' => \&_fltr_count_characters, 'cat' => \&_fltr_cat, 'count_paragraphs' => \&_fltr_count_paragraphs, 'count_words' => \&_fltr_count_words, 'escape' => \&_fltr_escape, 'indent' => \&_fltr_indent, 'nl2br' => \&_fltr_nl2br, 'replace' => \&_fltr_replace, 'spacify' => \&_fltr_spacify, 'string_format' => \&_fltr_sprintf, 'strip' => \&_fltr_strip, 'strip_tags' => \&_fltr_strip_tags, '_default' => \&_fltr_default, 'nofollowfy' => \&_fltr_nofollowfy, 'wrap_text' => \&_fltr_wrap_text, 'setvar' => \&_fltr_setvar, }, }; } ########################################################################### =head2 numify Adds commas to a number. Converting "12345" into "12,345" for instance. The argument for the numify attribute is the separator character to use (ie, "," or "."); "," is the default. =cut sub _fltr_numify { my ($str, $arg, $ctx) = @_; $arg = ',' if (!defined $arg) || ($arg eq '1'); $str =~ s/(^[−+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1$arg/g; return $str; } ########################################################################### =head2 mteval Processes the input string for any MT template tags and returns the output. =cut sub _fltr_mteval { my ($str, $arg, $ctx) = @_; my $builder = $ctx->stash('builder'); my $tokens = $builder->compile($ctx, $str); return $ctx->error($builder->errstr) unless defined $tokens; my $out = $builder->build($ctx, $tokens); return $ctx->error($builder->errstr) unless defined $out; return $out; } ########################################################################### =head2 encode_sha1 Outputs a SHA1-hex digest of the content from the tag it is applied to. B <$mt:EntryTitle encode_sha1="1"$> outputs: 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33 =cut sub _fltr_sha1 { my ($str) = @_; require MT::Util; return MT::Util::perl_sha1_digest_hex($str); } ########################################################################### =head2 setvar Takes the content from the tag it is applied to and assigns it to the given variable name. Example, assigning a HTML link of the last published entry with a '@featured' tag to a template variable named 'featured_entry_link': <$mt:EntryTitle$> The output from the L tag is suppressed, and placed into the template variable 'featured_entry_link' instead. To retrieve it, just use the L tag. =cut sub _fltr_setvar { my ($str, $arg, $ctx) = @_; if ( my $hash = $ctx->{__inside_set_hashvar} ) { $hash->{$arg} = $str; } else { $ctx->var($arg, $str); } return ''; } ########################################################################### =head2 nofollowfy Processes the 'a' tags from the tag it is applied to and adds a 'rel' attribute of 'nofollow' to it (or appends to an existing rel attribute). B <$mt:CommentBody nofollowfy="1"$> =cut sub _fltr_nofollowfy { my ($str, $arg, $ctx) = @_; return $str unless $arg; $str =~ s#<\s*a\s([^>]*\s*href\s*=[^>]*)># my @attr = $1 =~ /[^=\s]*\s*=\s*"[^"]*"|[^=\s]*\s*=\s*'[^']*'|[^=\s]*\s*=[^\s]*/g; my @rel = grep { /^rel\s*=/i } @attr; my $rel; $rel = pop @rel if @rel; if ($rel) { $rel =~ s/^(rel\s*=\s*['"]?)/$1nofollow /i unless $rel =~ m/\bnofollow\b/; } else { $rel = 'rel="nofollow"'; } @attr = grep { !/^rel\s*=/i } @attr; ''; #xieg; $str; } ########################################################################### =head2 filters Applies one or more text format filters. =head4 Values See the list of acceptable values in the L config directive docs =head4 Examples Remove the default text filter specified in the Edit Entry screen (Rich Text, Markdown, etc) by setting convert_breaks="0" and then use the filter attribute to specify the desired filter. <$mt:EntryBody convert_breaks="0" filters="__default__"$> If you want to use Markdown for the body, but Markdown with SmartyPants for the extended entry, do this: <$mt:EntryMore convert_breaks="0" filters="markdown_with_smartypants"$> If you want no formatting on L or L (extended) text, just use convert_breaks="0". =cut sub _fltr_filters { my ($str, $val, $ctx) = @_; MT->apply_text_filters($str, [ split /\s*,\s*/, $val ], $ctx); } ########################################################################### =head2 trim_to Trims the input content to the requested length. B <$mt:EntryTitle trim_to="4"$> =cut sub _fltr_trim_to { my ($str, $val, $ctx) = @_; return '' if $val <= 0; $str = substr_text($str, 0, $val) if $val < length_text($str); $str; } ########################################################################### =head2 trim Trims all leading and trailing whitespace from the input. B <$mt:EntryTitle trim="1"$> =cut sub _fltr_trim { my ($str, $val, $ctx) = @_; $str =~ s/^\s+|\s+$//gs; $str; } ########################################################################### =head2 ltrim Trims all leading whitespace from the input. B <$mt:EntryTitle ltrim="1"$> =cut sub _fltr_ltrim { my ($str, $val, $ctx) = @_; $str =~ s/^\s+//s; $str; } ########################################################################### =head2 rtrim Trims all trailing (right-side) whitespace from the input. B <$mt:EntryTitle rtrim="1"$> =cut sub _fltr_rtrim { my ($str, $val, $ctx) = @_; $str =~ s/\s+$//s; $str; } ########################################################################### =head2 decode_html Decodes any HTML entities from the input. =cut sub _fltr_decode_html { my ($str, $val, $ctx) = @_; MT::Util::decode_html($str); } ########################################################################### =head2 decode_xml Removes XML encoding from the input. Strips a 'CDATA' wrapper as well. =cut sub _fltr_decode_xml { my ($str, $val, $ctx) = @_; decode_xml($str); } ########################################################################### =head2 remove_html Removes any HTML markup from the input. =cut sub _fltr_remove_html { my ($str, $val, $ctx) = @_; MT::Util::remove_html($str); } ########################################################################### =head2 dirify Converts the input into a filename-friendly format. This strips accents from accented characters and changes spaces into underscores (or dashes, depending on parameter). The dirify parameter can be either "1", "-" or "_". "1" is equivalent to "_". B <$mt:EntryTitle dirify="-"$> which would translate an entry titled "Cafe" into "cafe". =cut sub _fltr_dirify { my ($str, $val, $ctx) = @_; return $str if (defined $val) && ($val eq '0'); MT::Util::dirify($str, $val); } ########################################################################### =head2 sanitize Filters input of particular HTML tags and other markup that may be unsafe content. If the sanitize parameter is "1", it will use the MT configured "GlobalSanitizeSpec" setting to control how it processes the input. But the parameter may also specify this directly. For example: <$mt:CommentBody sanitize="b,strong,em,i,ul,li,ol,p,br/"$> This would strip any HTML tags from the comment that are not specified in this list. =cut sub _fltr_sanitize { my ($str, $val, $ctx) = @_; my $blog = $ctx->stash('blog'); require MT::Sanitize; if ($val eq '1') { $val = ($blog && $blog->sanitize_spec) || $ctx->{config}->GlobalSanitizeSpec; } MT::Sanitize->sanitize($str, $val); } ########################################################################### =head2 encode_html Encodes any special characters into HTML entities (ie, '<' becomes '<'). =cut sub _fltr_encode_html { my ($str, $val, $ctx) = @_; MT::Util::encode_html($str, 1); } ########################################################################### =head2 encode_xml Encodes input into an XML-friendly format. May wrap in a CDATA block if the input contains tags or HTML entities. =cut sub _fltr_encode_xml { my ($str, $val, $ctx) = @_; MT::Util::encode_xml($str); } ########################################################################### =head2 encode_js Encodes any special characters so that the string can be used safely as the value in Javascript. B EOT return $js; } else { my $old_lang = MT->current_language; MT->set_language($lang) if $lang && ($lang ne $old_lang); my $date = relative_date($ts, time, $blog, $args->{format}, $r); MT->set_language($old_lang) if $lang && ($lang ne $old_lang); if ($date) { return $date; } else { if (!$args->{format}) { return ''; } } } } my $mail_flag = $args->{mail} || 0; return format_ts($args->{'format'}, $ts, $blog, $lang, $mail_flag); } sub _comment_follow { my($ctx, $arg) = @_; my $c = $ctx->stash('comment'); return unless $c; my $blog = $ctx->stash('blog'); if ($blog && $blog->nofollow_urls) { if ($blog->follow_auth_links) { my $cmntr = $ctx->stash('commenter'); unless ($cmntr) { if ($c->commenter_id) { $cmntr = MT::Author->load($c->commenter_id) || undef; } } if (!defined $cmntr || ($cmntr && !$cmntr->is_trusted($blog->id))) { nofollowfy_on($arg); } } else { nofollowfy_on($arg); } } } ########################################################################### =head2 Comments A container tag which iterates over a list of comments on an entry or for a blog. By default, all comments in context (e.g. on an entry or in a blog) are returned. When used in a blog context, only comments on published entries are returned. B =over 4 =item * lastn Display the last N comments in context where N is a positive integer. B =item * offset (optional; default "0") Specifies a number of comments to skip. =item * sort_by (optional) Specifies a sort column. =item * sort_order (optional) Specifies the sort order and overrides the General Settings. Recognized values are "ascend" and "descend." =item * namespace Used in conjunction with the "min*", "max*" attributes to select comments based on a particular scoring mechanism. =item * min_score If 'namespace' is also specified, filters the comments based on the score within that namespace. This specifies the minimum score to consider the comment for inclusion. =item * max_score If 'namespace' is also specified, filters the comments based on the score within that namespace. This specifies the maximum score to consider the comment for inclusion. =item * min_rank If 'namespace' is also specified, filters the comments based on the rank within that namespace. This specifies the minimum rank to consider the comment for inclusion. =item * max_rate If 'namespace' is also specified, filters the comments based on the rank within that namespace. This specifies the maximum rank to consider the comment for inclusion. =item * min_count If 'namespace' is also specified, filters the comments based on the count within that namespace. This specifies the minimum count to consider the comment for inclusion. =item * max_count If 'namespace' is also specified, filters the comments based on the count within that namespace. This specifies the maximum count to consider the comment for inclusion. =back =for tags multiblog, comments, loop, scoring =cut sub _hdlr_comments { my($ctx, $args, $cond) = @_; my (%terms, %args); my @filters; my @comments; my $comments = $ctx->stash('comments'); my $blog_id = $ctx->stash('blog_id'); my $blog = $ctx->stash('blog'); my $namespace = $args->{namespace}; if ($args->{namespace}) { my $need_join = 0; if ($args->{sort_by} && ($args->{sort_by} eq 'score' || $args->{sort_by} eq 'rate')) { $need_join = 1; } else { for my $f qw( min_score max_score min_rate max_rate min_count max_count scored_by ) { if ($args->{$f}) { $need_join = 1; last; } } } if ($need_join) { my $scored_by = $args->{scored_by} || undef; if ($scored_by) { require MT::Author; my $author = MT::Author->load({ name => $scored_by }) or return $ctx->error(MT->translate( "No such user '[_1]'", $scored_by )); $scored_by = $author; } $args{join} = MT->model('objectscore')->join_on(undef, { object_id => \'=comment_id', object_ds => 'comment', namespace => $namespace, (!$comments && $scored_by ? (author_id => $scored_by->id) : ()), }, { unique => 1, }); if ($comments && $scored_by) { push @filters, sub { $_[0]->get_score($namespace, $scored_by) }; } } # Adds a rate or score filter to the filter list. if ($args->{min_score}) { push @filters, sub { $_[0]->score_for($namespace) >= $args->{min_score}; }; } if ($args->{max_score}) { push @filters, sub { $_[0]->score_for($namespace) <= $args->{max_score}; }; } if ($args->{min_rate}) { push @filters, sub { $_[0]->score_avg($namespace) >= $args->{min_rate}; }; } if ($args->{max_rate}) { push @filters, sub { $_[0]->score_avg($namespace) <= $args->{max_rate}; }; } if ($args->{min_count}) { push @filters, sub { $_[0]->vote_for($namespace) >= $args->{min_count}; }; } if ($args->{max_count}) { push @filters, sub { $_[0]->vote_for($namespace) <= $args->{max_count}; }; } } my $so = lc ($args->{sort_order} || ($blog ? $blog->sort_order_comments : undef) || 'ascend'); my $no_resort; if ($comments) { my $n = $args->{lastn}; my $col = lc($args->{sort_by} || 'created_on'); @$comments = $so eq 'ascend' ? sort { $a->created_on cmp $b->created_on } @$comments : sort { $b->created_on cmp $a->created_on } @$comments; $no_resort = 1 unless $args->{sort_order} || $args->{sort_by}; if (@filters) { my $offset = $args->{offset} || 0; my $j = 0; COMMENTS: for my $c (@$comments) { for (@filters) { next COMMENTS unless $_->($c); } next if $offset && $j++ < $offset; push @comments, $c; } } else { my $offset; if ($offset = $args->{offset}) { if ($offset < scalar @comments) { @comments = @$comments[$offset..$#comments]; } else { @comments = (); } } else { @comments = @$comments; } } if ($n) { my $max = $n - 1 > $#comments ? $#comments : $n - 1; @comments = $so eq 'ascend' ? @comments[$#comments-$max..$#comments] : @comments[0..$max]; } } else { $terms{visible} = 1; $ctx->set_blog_load_context($args, \%terms, \%args) or return $ctx->error($ctx->errstr); ## If there is a "lastn" arg, then we need to check if there is an entry ## in context. If so, grab the N most recent comments for that entry; ## otherwise, grab the N most recent comments for the entire blog. my $n = $args->{lastn}; if (my $e = $ctx->stash('entry')) { ## Sort in descending order, then grab the first $n ($n most ## recent) comments. $args{'sort'} = 'created_on'; $args{'direction'} = 'descend'; my $cmts = $e->comments(\%terms, \%args); my $offset = $args->{offset} || 0; if (@filters) { my $i = 0; my $j = 0; my $offset = $args->{offset} || 0; COMMENTS: for my $c (@$cmts) { for (@filters) { next COMMENTS unless $_->($c); } next if $offset && $j++ < $offset; push @comments, $c; last if $n && ( $n <= ++$i ); } } elsif ($offset || $n) { if ($offset) { if ($offset < scalar @$cmts - 1) { @$cmts = @$cmts[$offset..(scalar @$cmts - 1)]; } else { @$cmts = (); } } if ($n) { my $max = $n - 1 > scalar @$cmts - 1 ? scalar @$cmts - 1 : $n - 1; @$cmts = @$cmts[0..$max]; } @comments = @$cmts; } else { @comments = @$cmts; } } else { $args{'sort'} = lc $args->{sort_by} || 'created_on'; if ($args->{lastn} || $args->{offset}) { $args{'direction'} = 'descend'; $so = 'descend'; } else { $args{'direction'} = 'ascend'; $no_resort = 1 unless $args->{sort_order} || $args->{sort_by}; } require MT::Comment; if (!@filters) { $args{limit} = $n if $n; $args{offset} = $args->{offset} if $args->{offset}; $args{join} = MT->model('entry')->join_on( undef, { id => \'=comment_entry_id', status => MT::Entry::RELEASE(), }, {unique => 1}); @comments = MT::Comment->load(\%terms, \%args); } else { my $iter = MT::Comment->load_iter(\%terms, \%args); my %entries; my $j = 0; my $offset = $args->{offset} || 0; COMMENT: while (my $c = $iter->()) { my $e = $entries{$c->entry_id} ||= $c->entry; next unless $e; next if $e->status != MT::Entry::RELEASE(); for (@filters) { next COMMENT unless $_->($c); } next if $offset && $j++ < $offset; push @comments, $c; if ($n && (scalar @comments == $n)) { $iter->end; last; } } } } } if (!$no_resort) { my $col = lc ($args->{sort_by} || 'created_on'); if (@comments) { if ('created_on' eq $col) { my @comm; @comm = $so eq 'ascend' ? sort { $a->created_on <=> $b->created_on } @comments : sort { $b->created_on <=> $a->created_on } @comments; # filter out comments from unapproved commenters @comments = grep { $_->visible() } @comm; } elsif ('score' eq $col) { my %m = map { $_->id => $_ } @comments; my @cid = keys %m; require MT::ObjectScore; my $scores = MT::ObjectScore->sum_group_by( { 'object_ds' => 'comment', 'namespace' => $namespace, object_id => \@cid }, { 'sum' => 'score', group => ['object_id'], $so eq 'ascend' ? (direction => 'ascend') : (direction => 'descend'), }); my @tmp; while (my ($score, $object_id) = $scores->()) { push @tmp, delete $m{ $object_id } if exists $m{ $object_id }; $scores->end, last unless %m; } @comments = @tmp; } elsif ('rate' eq $col) { my %m = map { $_->id => $_ } @comments; my @cid = keys %m; require MT::ObjectScore; my $scores = MT::ObjectScore->avg_group_by( { 'object_ds' => 'comment', 'namespace' => $namespace, object_id => \@cid }, { 'avg' => 'score', group => ['object_id'], $so eq 'ascend' ? (direction => 'ascend') : (direction => 'descend'), }); my @tmp; while (my ($score, $object_id) = $scores->()) { push @tmp, delete $m{ $object_id } if exists $m{ $object_id }; $scores->end, last unless %m; } @comments = @tmp; } } } my $html = ''; my $builder = $ctx->stash('builder'); my $tokens = $ctx->stash('tokens'); my $i = 1; local $ctx->{__stash}{commenter} = $ctx->{__stash}{commenter}; my $vars = $ctx->{__stash}{vars} ||= {}; my $glue = $args->{glue}; for my $c (@comments) { local $vars->{__first__} = $i == 1; local $vars->{__last__} = ($i == scalar @comments); local $vars->{__odd__} = ($i % 2) == 1; local $vars->{__even__} = ($i % 2) == 0; local $vars->{__counter__} = $i; local $ctx->{__stash}{blog} = $c->blog; local $ctx->{__stash}{blog_id} = $c->blog_id; local $ctx->{__stash}{comment} = $c; local $ctx->{current_timestamp} = $c->created_on; $ctx->stash('comment_order_num', $i); if ($c->commenter_id) { $ctx->stash('commenter', delay(sub {MT::Author->load($c->commenter_id)})); } else { $ctx->stash('commenter', undef); } my $out = $builder->build($ctx, $tokens, { CommentsHeader => $i == 1, CommentsFooter => ($i == scalar @comments), %$cond } ); return $ctx->error( $builder->errstr ) unless defined $out; $html .= $glue if defined $glue && length($html) && length($out); $html .= $out; $i++; } if (!@comments) { return _hdlr_pass_tokens_else(@_); } return $html; } ########################################################################### =head2 CommentDate Outputs the creation date for the current comment in context. See the L tag for support attributes. =for tags date, comments =cut sub _hdlr_comment_date { my ($ctx, $args) = @_; my $c = $ctx->stash('comment') or return $ctx->_no_comment_error(); $args->{ts} = $c->created_on; return _hdlr_date($ctx, $args); } ########################################################################### =head2 CommentID Outputs the numeric ID for the current comment in context. =for tags comments =cut sub _hdlr_comment_id { my ($ctx, $args) = @_; my $c = $ctx->stash('comment') or return $ctx->_no_comment_error(); my $id = $c->id || 0; return $args && $args->{pad} ? (sprintf "%06d", $id) : $id; } ########################################################################### =head2 CommentEntryID Outputs the numeric ID for the parent entry (or page) of the current comment in context. =for tags comments =cut sub _hdlr_comment_entry_id { my ($ctx, $args) = @_; my $c = $ctx->stash('comment') or return $ctx->_no_comment_error(); return $args && $args->{pad} ? (sprintf "%06d", $c->entry_id) : $c->entry_id; } ########################################################################### =head2 CommentBlogID Outputs the numeric ID of the blog for the current comment in context. =for tags comments =cut sub _hdlr_comment_blog_id { my ($ctx) = @_; my $c = $ctx->stash('comment') or return $ctx->_no_comment_error(); return $c->blog_id; } ########################################################################### =head2 CommentIfModerated Conditional tag for testing whether the current comment in context is moderated or not (used for application, email and comment response templates where the comment may not actually be published). =for tags comments =cut sub _hdlr_comment_if_moderated { my ($ctx) = @_; my $c = $ctx->stash('comment') or return $ctx->_no_comment_error(); return $c->visible ? 1 : 0; } ########################################################################### =head2 CommentName Deprecated in favor of the L tag. =for tags deprecated =cut ########################################################################### =head2 CommentAuthor Outputs the name field of the current comment in context (for comments left by authenticated users, this will return their display name). =for tags comments =cut sub _hdlr_comment_author { my ($ctx, $args) = @_; sanitize_on($args); my $c = $ctx->stash('comment') or return $ctx->_no_comment_error(); my $a = defined $c->author ? $c->author : ''; $args->{default} = MT->translate("Anonymous") unless exists $args->{default}; $a ||= $args->{default}; return remove_html($a); } ########################################################################### =head2 CommentIP Outputs the IP address where the current comment in context was posted from. =for tags comments =cut sub _hdlr_comment_ip { my ($ctx) = @_; my $c = $ctx->stash('comment') or return $ctx->_no_comment_error(); return defined $c->ip ? $c->ip : ''; } ########################################################################### =head2 CommentAuthorLink A linked version of the comment author name, using the comment author's URL if provided in the comment posting form. Otherwise, the comment author name is unlinked. This behavior can be altered with optional attributes. B =over 4 =item * show_email (optional; default "0") Specifies if the comment author's email can be displayed. =item * show_url (optional; default "1") Specifies if the comment author's URL can be displayed. =item * new_window (optional; default "0") Specifies to open the link in a new window by adding C to the anchor tag. See example below. =item * default_name (optional; default "Anonymous") Used in the event that the commenter did not provide a value for their name. =item * no_redirect (optional; default "0") Prevents use of the mt-comments.cgi script to handle the comment author link. =item * nofollowfy (optional) If assigned, applies a C link relation to the link. =back =for tags comments =cut sub _hdlr_comment_author_link { #sanitize_on($_[1]); my($ctx, $args) = @_; _comment_follow($ctx, $args); my $c = $ctx->stash('comment') or return $ctx->_no_comment_error(); my $name = $c->author; $name = '' unless defined $name; $name ||= $args->{default_name}; $name ||= MT->translate("Anonymous"); $name = encode_html( remove_html( $name ) ); my $show_email = $args->{show_email} ? 1 : 0; my $show_url = 1 unless exists $args->{show_url} && !$args->{show_url}; # Open the link in a new window if requested (with new_window="1"). my $target = $args->{new_window} ? ' target="_blank"' : ''; my $cmntr = $ctx->stash('commenter'); if ( !$cmntr ) { $cmntr = MT::Author->load( $c->commenter_id ) if $c->commenter_id; } if ( $cmntr ) { $name = encode_html( remove_html( $cmntr->nickname ) ) if $cmntr->nickname; if ($cmntr->url) { return sprintf(qq(%s), encode_html( $cmntr->url ), encode_html( $cmntr->url ), $target, $name); } return $name; } elsif ($show_url && $c->url) { my $cfg = $ctx->{config}; my $cgi_path = _hdlr_cgi_path($ctx); my $comment_script = $cfg->CommentScript; $name = remove_html($name); my $url = remove_html($c->url); if ($c->id && !$args->{no_redirect} && !$args->{nofollowfy}) { return sprintf(qq(%s), encode_html( $url ), $cgi_path, $comment_script, $c->id, $target, $name); } else { # In the case of preview, show URL directly without a redirect return sprintf(qq(%s), $url, $url, $target, $name); } } elsif ($show_email && $c->email && MT::Util::is_valid_email($c->email)) { my $email = remove_html($c->email); my $str = "mailto:" . encode_html( $email ); $str = spam_protect($str) if $args->{'spam_protect'}; return sprintf qq(%s), $str, $name; } return $name; } ########################################################################### =head2 CommentEmail Publishes the commenter's e-mail address. B It is not recommended to publish any email addresses. B =over 4 =item * spam_protect (optional; default "0") =back =for tags comments =cut sub _hdlr_comment_email { my ($ctx, $args) = @_; sanitize_on($args); my $c = $ctx->stash('comment') or return $ctx->_no_comment_error(); return '' unless defined $c->email; return '' unless $c->email =~ m/@/; my $email = remove_html($c->email); return $args && $args->{'spam_protect'} ? spam_protect($email) : $email; } ########################################################################### =head2 CommentAuthorIdentity Returns a profile icon link for the current commenter in context. The icon is for the authentication service used (ie, TypeKey, OpenID, Vox LiveJournal, etc.). If the commenter has a URL in their profile the icon is linked to that URL. =for tags comments =cut sub _hdlr_comment_author_identity { my ($ctx, $args) = @_; my $cmt = $ctx->stash('comment') or return $ctx->_no_comment_error(); my $cmntr = $ctx->stash('commenter'); unless ($cmntr) { if ($cmt->commenter_id) { $cmntr = MT::Author->load($cmt->commenter_id) or return "?"; } else { return q(); } } my $link = $cmntr->url; my $static_path = _hdlr_static_path($ctx); my $logo = $cmntr->auth_icon_url; unless ($logo) { my $root_url = $static_path . "images"; $logo = "$root_url/nav-commenters.gif"; } if ($logo =~ m!^/!) { # relative path, prepend blog domain my $blog = $ctx->stash('blog'); if ($blog) { my ($blog_domain) = $blog->archive_url =~ m|(.+://[^/]+)|; $logo = $blog_domain . $logo; } } my $result = qq{\"Author}; if ($link) { $result = qq{$result}; } return $result; } ########################################################################### =head2 CommentLink Outputs the permalink for the comment currently in context. This is the permalink for the parent entry, plus an anchor for the comment itself (in the format '#comment-ID'). =for tags comments =cut sub _hdlr_comment_link { my ($ctx) = @_; my $c = $ctx->stash('comment') or return $ctx->_no_comment_error(); return '#' unless $c->id; my $entry = $c->entry or return $ctx->error("No entry exists for comment #" . $c->id); return $entry->archive_url . '#comment-' . $c->id; } ########################################################################### =head2 CommentURL Outputs the URL of the current comment in context. The URL is the link provided in the comment from an anonymous comment, or for authenticated comments, is the URL from the commenter's profile. =for tags comments =cut sub _hdlr_comment_url { my ($ctx, $args) = @_; sanitize_on($args); my $c = $ctx->stash('comment') or return $ctx->_no_comment_error(); my $url = defined $c->url ? $c->url : ''; return remove_html($url); } ########################################################################### =head2 CommentPreviewEmail A deprecated tag, replaced with L. =for tags deprecated =cut ########################################################################### =head2 CommentPreviewBody A deprecated tag, replaced with L. =for tags deprecated =cut ########################################################################### =head2 CommentPreviewAuthorLink A deprecated tag, replaced with L. =for tags deprecated =cut ########################################################################### =head2 CommentPreviewURL A deprecated tag, replaced with L. =for tags deprecated =cut ########################################################################### =head2 CommentPreviewDate A deprecated tag, replaced with L. =for tags deprecated =cut ########################################################################### =head2 CommentPreviewAuthor A deprecated tag, replaced with L. =for tags deprecated =cut ########################################################################### =head2 CommentPreviewIP A deprecated tag, replaced with L. =for tags deprecated =cut ########################################################################### =head2 CommentBody B =over 4 =item * autolink (optional) If unspecified, any plaintext links in the comment body will be automatically linked if the blog is configured to do that on the comment preferences screen. If this attribute is specified, it will override the blog preference. =item * convert_breaks (optional) By default, the comment text is formatted according to the comment text formatting choice in the blog preferences. If convert_breaks is disabled, the raw content of the comment body is output without any re-formatting applied. =item * words (optional) Limits the length of the comment body to the specified number of words. This is useful for producing a list of recent comments with an excerpt of each. =back =for tags comments =cut sub _hdlr_comment_body { my($ctx, $args) = @_; sanitize_on($args); _comment_follow($ctx, $args); my $blog = $ctx->stash('blog'); return q() unless $blog; my $c = $ctx->stash('comment') or return $ctx->_no_comment_error(); my $t = defined $c->text ? $c->text : ''; unless ($blog->allow_comment_html) { $t = remove_html($t); } my $convert_breaks = exists $args->{convert_breaks} ? $args->{convert_breaks} : $blog->convert_paras_comments; $t = $convert_breaks ? MT->apply_text_filters($t, $blog->comment_text_filters, $ctx) : $t; return first_n_text($t, $args->{words}) if exists $args->{words}; if (!(exists $args->{autolink} && !$args->{autolink}) && $blog->autolink_urls) { $t =~ s!(^|\s|>)(https?://[^\s<]+)!$1$2!gs; } $t; } ########################################################################### =head2 CommentOrderNumber Outputs a number relating to the position of the comment in the list of all comments published using the C tag, starting with "1". =for tags comments =cut sub _hdlr_comment_order_num { my ($ctx) = @_; return $ctx->stash('comment_order_num'); } ########################################################################### =head2 CommentPreviewState For the comment preview template only. =for tags comments =cut sub _hdlr_comment_prev_state { my ($ctx) = @_; return $ctx->stash('comment_state'); } ########################################################################### =head2 CommentPreviewIsStatic For the comment preview template only. =for tags comments =cut sub _hdlr_comment_prev_static { my ($ctx) = @_; my $s = encode_html($ctx->stash('comment_is_static')) || ''; return defined $s ? $s : '' } ########################################################################### =head2 CommentEntry A block tag that can be used to set the parent entry of the comment in context. B <$mt:CommentAuthor$> left a comment on <$mt:EntryTitle$>. =for tags comments =cut sub _hdlr_comment_entry { my($ctx, $args, $cond) = @_; my $c = $ctx->stash('comment') or return $ctx->_no_comment_error(); my $entry = MT::Entry->load($c->entry_id) or return ''; local $ctx->{__stash}{entry} = $entry; local $ctx->{current_timestamp} = $entry->authored_on; $ctx->stash('builder')->build($ctx, $ctx->stash('tokens'), $cond); } ########################################################################### =head2 CommentParent A block tag that can be used to set the parent comment of the current comment in context. If the current comment has no parent, it produces nothing. B (a reply to <$mt:CommentAuthor$>'s comment) =for tags comments =cut sub _hdlr_comment_parent { my($ctx, $args, $cond) = @_; my $c = $ctx->stash('comment') or return $ctx->_no_comment_error(); $c->parent_id && (my $parent = MT::Comment->load($c->parent_id)) or return ''; local $ctx->{__stash}{comment} = $parent; $ctx->stash('builder')->build($ctx, $ctx->stash('tokens'), $cond); } ########################################################################### =head2 CommentReplyToLink Produces the "Reply" link for the current comment in context. By default, this relies on using the MT "Javascript" default template, which supplies the C function. B =over 4 =item * label or text (optional) A custom phrase for the link (default is "Reply"). =item * onclick (optional) Custom JavaScript code for the onclick attribute. By default, this value is "mtReplyCommentOnClick(%d, '%s')" (note that %d is replaced with the comment ID; %s is replaced with the name of the commenter). =back =for tags comments =cut sub _hdlr_comment_reply_link { my($ctx, $args) = @_; my $comment = $ctx->stash('comment') or return $ctx->_no_comment_error(); my $label = $args->{label} || $args->{text} || MT->translate('Reply'); my $comment_author = MT::Util::encode_html( MT::Util::encode_js($comment->author) ); my $onclick = sprintf( $args->{onclick} || "mtReplyCommentOnClick(%d, '%s')", $comment->id, $comment_author); return sprintf(qq(%s), $label, $label); } ########################################################################### =head2 CommentParentID Outputs the ID of the parent comment for the comment currently in context. If there is no parent comment, outputs '0'. B =over 4 =item * pad If specified, zero-pads the ID to 6 digits. Example: 001234. =back =for tags comments =cut sub _hdlr_comment_parent_id { my ($ctx, $args) = @_; my $c = $ctx->stash('comment') or return ''; my $id = $c->parent_id || 0; $args && $args->{pad} ? (sprintf "%06d", $id) : ($id ? $id : ''); } ########################################################################### =head2 CommentReplies A block tag which iterates over a list of reply comments to the in context. B Replies to the comment: <$mt:CommentBody$> =for tags comments, loop =cut sub _hdlr_comment_replies { my($ctx, $args, $cond) = @_; my $comment = $ctx->stash('comment') or return $ctx->_no_comment_error(); my $tokens = $ctx->stash('tokens'); $ctx->stash('_comment_replies_tokens', $tokens); my (%terms, %args); $terms{parent_id} = $comment->id; $terms{visible} = 1; $args{'sort'} = 'created_on'; $args{'direction'} = 'descend'; require MT::Comment; my $iter = MT::Comment->load_iter(\%terms, \%args); my %entries; my $blog = $ctx->stash('blog'); my $so = lc($args->{sort_order}) || ($blog ? $blog->sort_order_comments : undef) || 'ascend'; my $n = $args->{lastn}; my @comments; while (my $c = $iter->()) { push @comments, $c; if ($n && (scalar @comments == $n)) { $iter->end; last; } } @comments = $so eq 'ascend' ? sort { $a->created_on <=> $b->created_on } @comments : sort { $b->created_on <=> $a->created_on } @comments; my $html = ''; my $builder = $ctx->stash('builder'); my $i = 1; @comments = grep { $_->visible() } @comments; local $ctx->{__stash}{commenter} = $ctx->{__stash}{commenter}; my $vars = $ctx->{__stash}{vars} ||= {}; for my $c (@comments) { local $vars->{__first__} = $i == 1; local $vars->{__last__} = ($i == scalar @comments); local $vars->{__odd__} = ($i % 2) == 1; local $vars->{__even__} = ($i % 2) == 0; local $vars->{__counter__} = $i; local $ctx->{__stash}{blog} = $c->blog; local $ctx->{__stash}{blog_id} = $c->blog_id; local $ctx->{__stash}{comment} = $c; local $ctx->{current_timestamp} = $c->created_on; $ctx->stash('comment_order_num', $i); if ($c->commenter_id) { $ctx->stash('commenter', delay(sub {MT::Author->load($c->commenter_id)})); } else { $ctx->stash('commenter', undef); } my $out = $builder->build($ctx, $tokens, { CommentsHeader => $i == 1, CommentsFooter => ($i == scalar @comments), } ); return $ctx->error( $builder->errstr ) unless defined $out; $html .= $out; $i++; } if (!@comments) { return _hdlr_pass_tokens_else(@_); } $html; } ########################################################################### =head2 CommentRepliesRecurse Recursively call the block with the replies to the comment in context. This tag, when placed at the end of loop controlled by MTCommentReplies tag will cause them to recursively descend into any replies to comments that exist during the loop. B <$mt:CommentBody$>
  • <$mt:CommentID$> <$mt:CommentRepliesRecurse$>
=for tags comments =cut sub _hdlr_comment_replies_recurse { my($ctx, $args, $cond) = @_; my $comment = $ctx->stash('comment') or return $ctx->_no_comment_error(); my $tokens = $ctx->stash('_comment_replies_tokens'); my (%terms, %args); $terms{parent_id} = $comment->id; $terms{visible} = 1; $args{'sort'} = 'created_on'; $args{'direction'} = 'descend'; require MT::Comment; my $iter = MT::Comment->load_iter(\%terms, \%args); my %entries; my $blog = $ctx->stash('blog'); my $so = lc($args->{sort_order}) || ($blog ? $blog->sort_order_comments : undef) || 'ascend'; my $n = $args->{lastn}; my @comments; while (my $c = $iter->()) { push @comments, $c; if ($n && (scalar @comments == $n)) { $iter->end; last; } } @comments = $so eq 'ascend' ? sort { $a->created_on <=> $b->created_on } @comments : sort { $b->created_on <=> $a->created_on } @comments; my $html = ''; my $builder = $ctx->stash('builder'); my $i = 1; @comments = grep { $_->visible() } @comments; local $ctx->{__stash}{commenter} = $ctx->{__stash}{commenter}; my $vars = $ctx->{__stash}{vars} ||= {}; for my $c (@comments) { local $vars->{__first__} = $i == 1; local $vars->{__last__} = ($i == scalar @comments); local $vars->{__odd__} = ($i % 2) == 1; local $vars->{__even__} = ($i % 2) == 0; local $vars->{__counter__} = $i; local $ctx->{__stash}{blog} = $c->blog; local $ctx->{__stash}{blog_id} = $c->blog_id; local $ctx->{__stash}{comment} = $c; local $ctx->{current_timestamp} = $c->created_on; $ctx->stash('comment_order_num', $i); if ($c->commenter_id) { $ctx->stash('commenter', delay(sub {MT::Author->load($c->commenter_id)})); } else { $ctx->stash('commenter', undef); } my $out = $builder->build($ctx, $tokens, { CommentsHeader => $i == 1, CommentsFooter => ($i == scalar @comments), } ); return $ctx->error( $builder->errstr ) unless defined $out; $html .= $out; $i++; } if (!@comments) { return _hdlr_pass_tokens_else(@_); } $html; } ########################################################################### =head2 CommentsHeader The contents of this container tag will be displayed when the first comment listed by a L or L tag is reached. =for tags comments =cut ########################################################################### =head2 CommentsFooter The contents of this container tag will be displayed when the last comment listed by a L or L tag is reached. =for tags comments =cut ########################################################################### =head2 IfCommentParent A conditional tag that is true when the comment currently in context is a reply to another comment. B (a reply) =for tags comments =cut sub _hdlr_if_comment_parent { my($ctx, $args, $cond) = @_; my $c = $ctx->stash('comment'); return 0 if !$c; my $parent_id = $c->parent_id; return 0 unless $parent_id; require MT::Comment; my $parent = MT::Comment->load($c->parent_id); return 0 unless $parent; return $parent->visible ? 1 : 0; } ########################################################################### =head2 IfCommentReplies A conditional tag that is true when the comment currently in context has replies. =for tags comments =cut sub _hdlr_if_comment_replies { my($ctx, $args, $cond) = @_; my $c = $ctx->stash('comment'); return 0 if !$c; MT::Comment->exist( { parent_id => $c->id, visible => 1 } ); } ########################################################################### =head2 CommenterNameThunk Used to populate the commenter_name Javascript variable. Deprecated in favor of the L tag. =for tags deprecated =cut sub _hdlr_commenter_name_thunk { my $ctx = shift; my $cfg = $ctx->{config}; my $blog = $ctx->stash('blog') || MT::Blog->load($ctx->stash('blog_id')); return $ctx->error(MT->translate('Can\'t load blog #[_1].', $ctx->stash('blog_id'))) unless $blog; my ($blog_domain) = $blog->archive_url =~ m|://([^/]*)|; my $cgi_path = _hdlr_cgi_path($ctx); my ($mt_domain) = $cgi_path =~ m|://([^/]*)|; $mt_domain ||= ''; if ($blog_domain ne $mt_domain) { my $cmt_script = $cfg->CommentScript; return ""; } else { return ""; } } ########################################################################### =head2 CommenterUsername This template tag returns the username of the current commenter in context. If no name exists, then it returns an empty string. B

<$mt:EntryTitle$>

<$mt:CommentBody$>

<$mt:CommenterUsername$>
=for tags comments =cut sub _hdlr_commenter_username { my ($ctx) = @_; my $a = $ctx->stash('commenter'); return $a ? $a->name : ''; } ########################################################################### =head2 UserSessionState Returns a JSON-formatted data structure that represents the user that is currently logged in. =for tags comments, authentication =cut sub _hdlr_user_session_state { my ($ctx, $args, $cond) = @_; my $app = MT->app; return 'null' unless $app->can('session_state'); my ( $state, $commenter ) = $app->session_state(); my $json = MT::Util::to_json($state); return $json; } ########################################################################### =head2 UserSessionCookieTimeout Returns the value of the C configuration setting. =for tags comments, configuration, authentication =cut sub _hdlr_user_session_cookie_timeout { my ($ctx) = @_; return $ctx->{config}->UserSessionCookieTimeout; } ########################################################################### =head2 UserSessionCookiePath Returns the value of the C configuration setting. The C may also use MT tags. If it does, they will be evaluated for the blog in context. =for tags comments, configuration, authentication =cut sub _hdlr_user_session_cookie_path { my ($ctx, $args, $cond) = @_; my $blog = $ctx->stash('blog'); my $blog_id = $blog ? $blog->id : '0'; my $blog_path = $ctx->stash('blog_cookie_path_' . $blog_id); if (!defined $blog_path) { $blog_path = $ctx->{config}->UserSessionCookiePath; if ($blog_path =~ m/<\$?mt/i) { # hey, a MT tag! lets evaluate my $builder = $ctx->stash('builder'); my $tokens = $builder->compile($ctx, $blog_path); return $ctx->error($builder->errstr) unless defined $tokens; $blog_path = $builder->build($ctx, $tokens, $cond); return $ctx->error($builder->errstr) unless defined $blog_path; } $ctx->stash('blog_cookie_path_' . $blog_id, $blog_path); } return $blog_path; } ########################################################################### =head2 UserSessionCookieDomain Returns the value of the C configuration setting, or the domain name of the blog currently in context. Any "www" subdomain will be ignored (ie, "www.sixapart.com" becomes ".sixapart.com"). The C may also use MT tags. If it does, they will be evaluated for the blog in context. =for tags comments, configuration, authentication =cut sub _hdlr_user_session_cookie_domain { my ($ctx, $args, $cond) = @_; my $blog = $ctx->stash('blog'); my $blog_id = $blog ? $blog->id : '0'; my $blog_domain = $ctx->stash('blog_cookie_domain_' . $blog_id); if (!defined $blog_domain) { $blog_domain = $ctx->{config}->UserSessionCookieDomain; if ($blog_domain =~ m/<\$?mt/i) { # hey, a MT tag! lets evaluate my $builder = $ctx->stash('builder'); my $tokens = $builder->compile($ctx, $blog_domain); return $ctx->error($builder->errstr) unless defined $tokens; $blog_domain = $builder->build($ctx, $tokens, $cond); return $ctx->error($builder->errstr) unless defined $blog_domain; } # strip off common 'www' subdomain $blog_domain =~ s/^www\.//; $blog_domain = '.' . $blog_domain unless $blog_domain =~ m/^\./; $ctx->stash('blog_cookie_domain_' . $blog_id, $blog_domain); } return $blog_domain; } ########################################################################### =head2 UserSessionCookieName Returns the value of the C configuration setting. If the setting contains the C<%b> string, it will replaced with the blog ID of the blog currently in context. B <$mt:UserSessionCookieName$> =for tags comments, configuration =cut sub _hdlr_user_session_cookie_name { my ($ctx) = @_; my $name = $ctx->{config}->UserSessionCookieName; if ($name =~ m/%b/) { my $blog_id = '0'; if (my $blog = $ctx->stash('blog')) { $blog_id = $blog->id; } $name =~ s/%b/$blog_id/g; } return $name; } ########################################################################### =head2 CommenterName The name of the commenter for the comment currently in context. B <$mt:CommenterName$> =for tags comments =cut sub _hdlr_commenter_name { my ($ctx) = @_; my $a = $ctx->stash('commenter'); my $name = $a ? $a->nickname || '' : ''; $name = _hdlr_comment_author($ctx) unless $name; return $name; } ########################################################################### =head2 CommenterEmail The email address of the commenter. The spam_protect global filter may be used. B <$mt:CommenterEmail$> =for tags comments =cut sub _hdlr_commenter_email { my ($ctx) = @_; my $a = $ctx->stash('commenter'); return '' if $a && $a->email !~ m/@/; return $a ? $a->email || '' : ''; } ########################################################################### =head2 CommenterAuthType Returns a string which identifies what authentication provider the commenter in context used to authenticate him/herself. Commenter context is created by either MTComments or MTCommentReplies template tag. For example, 'MT' will be returned when the commenter in context is authenticated by Movable Type. When the commenter in context is authenticated by Vox, 'Vox' will be returned. B <$mt:CommenterName$> (<$mt:CommenterAuthType$>) said: <$mt:CommentBody$> =for tags comments, authentication =cut sub _hdlr_commenter_auth_type { my ($ctx) = @_; my $a = $ctx->stash('commenter'); return $a ? $a->auth_type || '' : ''; } ########################################################################### =head2 CommenterAuthIconURL Returns URL to a small (16x16) image represents in what authentication provider the commenter in context is authenticated. Commenter context is created by either a L or L block tag. For commenters authenticated by Movable Type, it will be a small spanner logo of Movable Type. Otherwise, icon image is provided by each of authentication provider. Movable Type provides images for Vox, LiveJournal and OpenID out of the box. B <$mt:CommenterName$><$mt:CommenterAuthIconURL$>: <$mt:CommentBody$> =for tags comments, authentication =cut sub _hdlr_commenter_auth_icon_url { my ($ctx, $args) = @_; my $a = $ctx->stash('commenter'); return q() unless $a; my $size = $args->{size} || 'logo_small'; my $url = $a->auth_icon_url($size); if ($url =~ m!^/!) { # relative path, prepend blog domain my $blog = $ctx->stash('blog'); if ($blog) { my ($blog_domain) = $blog->archive_url =~ m|(.+://[^/]+)|; $url = $blog_domain . $url; } } return $url; } ########################################################################### =head2 CommenterIfTrusted Deprecated in favor of the L tag. =for tags deprecated =cut ########################################################################### =head2 IfCommenterTrusted A conditional tag that displays its contents if the commenter in context has been marked as trusted. =for tags comments, authentication =cut sub _hdlr_commenter_trusted { my ($ctx, $args, $cond) = @_; my $a = $ctx->stash('commenter'); return '' unless $a; if ($a->is_trusted($ctx->stash('blog_id'))) { return 1; } else { return 0; } } ########################################################################### =head2 IfCommenterIsAuthor Conditional tag that is true when the current comment was left by a user who is also a native MT user. =cut ########################################################################### =head2 IfCommenterIsEntryAuthor Conditional tag that is true when the current comment was left by the author of the current entry in context. =for tags comments =cut sub _hdlr_commenter_isauthor { my ($ctx, $args, $cond) = @_; my $a = $ctx->stash('commenter'); return 0 unless $a; if ($a->type == MT::Author::AUTHOR()) { my $tag = lc $ctx->stash('tag'); if ($tag eq 'ifcommenterisentryauthor') { my $c = $ctx->stash('comment'); my $e = $c ? $c->entry : $ctx->stash('entry'); if ($e) { if ($e->author_id == $a->id) { return 1; } } } else { return 1; } } return 0; } ########################################################################### =head2 CommenterID Outputs the numeric ID of the current commenter in context. =for tags comments =cut sub _hdlr_commenter_id { my ($ctx) = @_; my $c = $ctx->stash('comment') or return $ctx->_no_comment_error(); my $cmntr = $ctx->stash('commenter') or return ''; return $cmntr->id; } ########################################################################### =head2 CommenterURL Outputs the URL from the profile of the current commenter in context. =for tags comments =cut sub _hdlr_commenter_url { my ($ctx) = @_; my $c = $ctx->stash('comment') or return $ctx->_no_comment_error(); my $cmntr = $_[0]->stash('commenter') or return ''; return $cmntr->url; } ########################################################################### =head2 CommenterUserpic This template tag returns a complete HTML C tag for the current commenter's userpic. For example: B

Recent Commenters

<$mt:CommenterUserpic$>
=for tags comments =cut sub _hdlr_commenter_userpic { my ($ctx) = @_; my $c = $ctx->stash('comment') or return $ctx->_no_comment_error(); my $cmntr = $ctx->stash('commenter') or return ''; return $cmntr->userpic_html() || ''; } ########################################################################### =head2 CommenterUserpicURL This template tag returns the URL to the image of the current commenter's userpic. B =for tags comments =cut sub _hdlr_commenter_userpic_url { my ($ctx) = @_; my $cmntr = $ctx->stash('commenter') or return ''; return $cmntr->userpic_url() || ''; } ########################################################################### =head2 CommenterUserpicAsset This template tag is a container tag that puts the current commenter's userpic asset in context. Because userpics are stored as assets within Movable Type, this allows you to utilize all of the asset-related template tags when displaying a user's userpic. B " width="20" height="20" /> =for tags comments, assets =cut sub _hdlr_commenter_userpic_asset { my ($ctx, $args, $cond) = @_; my $c = $ctx->stash('comment') or return $ctx->_no_comment_error(); my $cmntr = $ctx->stash('commenter'); # undef means commenter has no commenter_id # need default userpic asset? do nothing now. return '' unless $cmntr; my $asset = $cmntr->userpic or return ''; my $tok = $ctx->stash('tokens'); my $builder = $ctx->stash('builder'); local $ctx->{__stash}{asset} = $asset; $builder->build($ctx, $tok, { %$cond }); } ########################################################################### =head2 FeedbackScore Outputs the numeric junk score calculated for the current comment or TrackBack ping in context. Outputs '0' if no score is present. A negative number indicates spam. =cut sub _hdlr_feedback_score { my ($ctx) = @_; my $fb = $ctx->stash('comment') || $ctx->stash('ping'); $fb ? $fb->junk_score || 0 : ''; } ########################################################################### =head2 ArchivePrevious A container tag that creates a context to the "previous" archive relative to the current archive context. This tag also works with the else tag to produce content if there is no "previous" archive. B =over 4 =item * type or archive_type (optional) Specifies the "previous" archive type the context is for. See the L tag for supported values for this attribute. =back B ">Next =for tags archives =cut ########################################################################### =head2 ArchiveNext A container tag that creates a context to the "next" archive relative to the current archive context. This tag also works with the else tag to produce content if there is no "next" archive. B =over 4 =item * type or archive_type (optional) Specifies the "next" archive type the context is for. See the L tag for supported values for this attribute. =back B ">Next =for tags archives =cut ## Archives sub _hdlr_archive_prev_next { my($ctx, $args, $cond) = @_; my $tag = lc $ctx->stash('tag'); my $is_prev = $tag eq 'archiveprevious'; my $res = ''; my $at = ($args->{type} || $args->{archive_type}) || $ctx->{current_archive_type} || $ctx->{archive_type}; my $arctype = MT->publisher->archiver($at); return '' unless $arctype; my $entry; if ($arctype->date_based && $arctype->category_based) { my $param = { ts => $ctx->{current_timestamp}, blog_id => $ctx->stash('blog_id'), category => $ctx->stash('archive_category'), }; $entry = $is_prev ? $arctype->previous_archive_entry($param) : $arctype->next_archive_entry($param); } elsif ($arctype->date_based && $arctype->author_based) { my $param = { ts => $ctx->{current_timestamp}, blog_id => $ctx->stash('blog_id'), author => $ctx->stash('author'), }; $entry = $is_prev ? $arctype->previous_archive_entry($param) : $arctype->next_archive_entry($param); } elsif ($arctype->category_based) { return _hdlr_category_prevnext(@_); } elsif ($arctype->author_based) { if ($is_prev) { $ctx->stash('tag', 'AuthorPrevious'); } else { $ctx->stash('tag', 'AuthorNext'); } return _hdlr_author_next_prev(@_); } elsif ($arctype->entry_based) { my $e = $ctx->stash('entry'); if ($is_prev) { $entry = $e->previous(1); } else { $entry = $e->next(1); } } else { my $ts = $ctx->{current_timestamp} or return $ctx->error(MT->translate( "You used an [_1] tag without a date context set up.", "MT$tag" )); return $ctx->error(MT->translate( "[_1] can be used only with Daily, Weekly, or Monthly archives.", "" )) unless $arctype->date_based; my $param = { ts => $ctx->{current_timestamp}, blog_id => $ctx->stash('blog_id'), }; $entry = $is_prev ? $arctype->previous_archive_entry($param) : $arctype->next_archive_entry($param); } if ($entry) { my $builder = $ctx->stash('builder'); local $ctx->{__stash}->{entries} = [ $entry ]; if (my($start, $end) = $arctype->date_range($entry->authored_on)) { local $ctx->{current_timestamp} = $start; local $ctx->{current_timestamp_end} = $end; defined(my $out = $builder->build($ctx, $ctx->stash('tokens'), $cond)) or return $ctx->error( $builder->errstr ); $res .= $out; } else { local $ctx->{current_timestamp} = $entry->authored_on; local $ctx->{current_timestamp_end} = $entry->authored_on; defined(my $out = $builder->build($ctx, $ctx->stash('tokens'), $cond)) or return $ctx->error( $builder->errstr ); $res .= $out; } } $res; } ########################################################################### =head2 IndexList A block tag that builds a list of all available index templates, sorting them by name. =cut sub _hdlr_index_list { my ($ctx, $args, $cond) = @_; my $tokens = $ctx->stash('tokens'); my $builder = $ctx->stash('builder'); my $iter = MT::Template->load_iter({ type => 'index', blog_id => $ctx->stash('blog_id') }, { 'sort' => 'name' }); my $res = ''; while (my $tmpl = $iter->()) { local $ctx->{__stash}{'index'} = $tmpl; defined(my $out = $builder->build($ctx, $tokens, $cond)) or return $ctx->error( $builder->errstr ); $res .= $out; } $res; } ########################################################################### =head2 IndexLink Outputs the URL for the current index template in context. Used in an L tag. B =over 4 =item * with_index (optional; default "0") If enabled, will retain the "index.html" (or similar index filename) in the link. =back =cut sub _hdlr_index_link { my ($ctx, $args, $cond) = @_; my $idx = $ctx->stash('index'); return '' unless $idx; my $blog = $ctx->stash('blog'); my $path = $blog->site_url; $path .= '/' unless $path =~ m!/$!; $path .= $idx->outfile; $path = MT::Util::strip_index($path, $blog) unless $args->{with_index}; $path; } ########################################################################### =head2 IndexName Outputs the name for the current index template in context. Used in an L tag. =cut sub _hdlr_index_name { my ($ctx, $args, $cond) = @_; my $idx = $ctx->stash('index'); return '' unless $idx; $idx->name || ''; } ########################################################################### =head2 IndexBasename Outputs the C MT configuration setting. B =over 4 =item * extension (optional; default "0") If specified, will append the blog's configured file extension. =back =cut sub _hdlr_index_basename { my ($ctx, $args, $cond) = @_; my $name = $ctx->{config}->IndexBasename; if (!$args->{extension}) { return $name; } my $blog = $ctx->stash('blog'); my $ext = $blog->file_extension; $ext = '.' . $ext if $ext; $name . $ext; } ########################################################################### =head2 Archives A container tag representing a list of all the enabled archive types in a blog. This tag exists to facilitate the publication of a Google sitemap or something of a similar nature. B =over 4 =item * type or archive_type (optional) Specify a comma-delimited list of archive types to loop over. If you only wish to publish a list of Individual and Category archives, you can specify: =back B This will publish a link for each archive type you publish (the primary archive links, at least). =cut sub _hdlr_archive_set { my($ctx, $args, $cond) = @_; my $blog = $ctx->stash('blog'); my $at = $args->{type} || $args->{archive_type} || $blog->archive_type; return '' if !$at || $at eq 'None'; my @at = split /,/, $at; my $res = ''; my $tokens = $ctx->stash('tokens'); my $builder = $ctx->stash('builder'); my $old_at = $blog->archive_type_preferred(); foreach my $type (@at) { $blog->archive_type_preferred($type); local $ctx->{current_archive_type} = $type; defined(my $out = $builder->build($ctx, $tokens, $cond)) or return $ctx->error( $builder->errstr ); $res .= $out; } $blog->archive_type_preferred($old_at); $res; } ########################################################################### =head2 ArchiveList A container tag representing a list of all the archive pages of a certain type. B =over 4 =item * type or archive_type An optional attribute that specifies the type of archives to list. Recognized values are "Yearly", "Monthly", "Weekly", "Daily", "Individual", "Author", "Author-Yearly", "Author-Monthly", "Author-Weekly", "Author-Daily", "Category", "Category-Yearly", "Category-Monthly", "Category-Weekly" and "Category-Daily" (and perhaps others, if custom archive types are provided through third-party plugins). The default is to list the Preferred Archive Type specified in the blog settings. =item * lastn (optional) An optional attribute that can be used to limit the number of archives in the list. =item * sort_order (optional; default "descend") An optional attribute that specifies the sort order of the archives in the list. It is effective within any of the date-based and "Individual" archive types. Recognized values are "ascend" and "descend". =back B You may produce an archive list of any supported archive type even if you are not publishing that archive type. However, the L tag will only work for archive types you are publishing. B <$mt:ArchiveTitle$> Here, we're combining two L tags (the inner L tag is bound to the date range of the year in context):
  • <$mt:ArchiveDate format="%Y"$>
    • <$mt:ArchiveDate format="%b"$>
to publish something like this:
  • 2006
    • Mar
    • Apr
    • May
  • 2007
    • Apr
    • Jun
    • Dec
=cut sub _hdlr_archives { my($ctx, $args, $cond) = @_; my $blog = $ctx->stash('blog'); my $at = $blog->archive_type; return '' if !$at || $at eq 'None'; my $arg_at; if ($arg_at = $args->{type} || $args->{archive_type}) { $at = $arg_at; } elsif ($blog->archive_type_preferred) { $at = $blog->archive_type_preferred; } else { $at = (split /,/, $at)[0]; } my $archiver = MT->publisher->archiver($at); return '' unless $archiver; my $save_stamps; if ($ctx->{current_archive_type} && $arg_at && ($ctx->{current_archive_type} eq $arg_at)) { $save_stamps = 1; } local $ctx->{current_archive_type} = $at; ## If we are producing a Category archive list, don't bother to ## handle it here--instead hand it over to . return _hdlr_categories(@_) if $at eq 'Category'; my %args; my $sort_order = lc ($args->{sort_order} || '') eq 'ascend' ? 'ascend' : 'descend'; $args{'sort'} = 'authored_on'; $args{direction} = $sort_order; my $tokens = $ctx->stash('tokens'); my $builder = $ctx->stash('builder'); my $res = ''; my $i = 0; my $n = $args->{lastn}; my $save_ts; my $save_tse; my $tmpl = $ctx->stash('template'); if ( ($tmpl && $tmpl->type eq 'archive') && $archiver->date_based) { my $uncompiled = $ctx->stash('uncompiled') || ''; if ($uncompiled =~ /?/i) { $save_stamps = 1; } } if ($save_stamps) { $save_ts = $ctx->{current_timestamp}; $save_tse = $ctx->{current_timestamp_end}; $ctx->{current_timestamp} = undef; $ctx->{current_timestamp_end} = undef; } my $order = $sort_order eq 'ascend' ? 'asc' : 'desc'; my $group_iter = $archiver->archive_group_iter($ctx, $args); return $ctx->error(MT->translate("Group iterator failed.")) unless defined($group_iter); my ($cnt, %curr) = $group_iter->(); my %save = map { $_ => $ctx->{__stash}{$_} } keys %curr; my $vars = $ctx->{__stash}{vars} ||= {}; while (defined($cnt)) { $i++; my ($next_cnt, %next) = $group_iter->(); my $last; $last = 1 if $n && ($i >= $n); $last = 1 unless defined $next_cnt; my ($start, $end); if ($archiver->date_based) { ($start, $end) = ($curr{'start'}, $curr{'end'}); } else { my $entry = $curr{entries}->[0] if exists($curr{entries}); ($start, $end) = (ref $entry ? $entry->authored_on : ""); } local $ctx->{current_timestamp} = $start; local $ctx->{current_timestamp_end} = $end; local $vars->{__first__} = $i == 1; local $vars->{__last__} = $last; local $vars->{__even__} = $i % 2 == 0; local $vars->{__odd__} = $i % 2 == 1; local $vars->{__counter__} = $i; local $ctx->{__stash}{archive_count} = $cnt; local $ctx->{__stash}{entries} = delay(sub{ $archiver->archive_group_entries($ctx, %curr) }) if $archiver->group_based; $ctx->{__stash}{$_} = $curr{$_} for keys %curr; local $ctx->{inside_archive_list} = 1; defined(my $out = $builder->build($ctx, $tokens, { %$cond, ArchiveListHeader => $i == 1, ArchiveListFooter => $last })) or return $ctx->error( $builder->errstr ); $res .= $out; last if $last; ($cnt, %curr) = ($next_cnt, %next); } $ctx->{__stash}{$_} = $save{$_} for keys %save; $ctx->{current_timestamp} = $save_ts if $save_ts; $ctx->{current_timestamp_end} = $save_tse if $save_tse; $res; } ########################################################################### =head2 ArchiveListHeader The contents of this container tag will be displayed when the first entry listed by a L tag is reached. =for tags archives =cut ########################################################################### =head2 ArchiveListFooter The contents of this container tag will be displayed when the last entry listed by a L tag is reached. =for tags archives =cut ########################################################################### =head2 ArchiveTitle A descriptive title of the current archive. The value returned from this tag will vary based on the archive type: =over 4 =item * Category The label of the category. Note that any HTML tags present in the label will be removed. =item * Daily The date in "Month, Day YYYY" form. =item * Weekly The range of dates in the week in "Month, Day YYYY - Month, Day YYYY" =item * Monthly The range of dates in the week in "Month YYYY" form. =item * Individual The title of the entry. Note that any HTML tags present in the label will be removed. = item * Author The display name of the author. Note that any HTML tags present in the display name will be removed. =back B <$mt:ArchiveTitle$> =for tags archives =cut sub _hdlr_archive_title { my($ctx, $args) = @_; my $at = $ctx->{current_archive_type} || $ctx->{archive_type}; return _hdlr_category_label(@_) if $at eq 'Category'; my $archiver = MT->publisher->archiver($at); my @entries; if ($archiver->entry_based) { my $entries = $ctx->stash('entries'); if (!$entries && (my $e = $ctx->stash('entry'))) { push @$entries, $e; } if ($entries && ref($entries) eq 'ARRAY' && $at) { @entries = @$entries; } else { my $blog = $ctx->stash('blog'); if (!@entries) { ## This situation arises every once in awhile. We have ## a date-based archive page, but no entries to go on it--this ## might happen, for example, if you have daily archives, and ## you post an entry, and then you change the status to draft. ## The page will be rebuilt in order to empty it, but in the ## process, there won't be any entries in $entries. So, we ## build a stub MT::Entry object and set the created_on date ## to the current timestamp (start of day/week/month). ## But, it's not generally true that draft-izing an entry ## erases all of its manifestations. The individual ## archive lingers, for example. --ez if ($at && $archiver->date_based()) { my $e = MT::Entry->new; $e->authored_on($ctx->{current_timestamp}); @entries = ($e); } else { return $ctx->error(MT->translate( "You used an [_1] tag outside of the proper context.", '<$MTArchiveTitle$>' )); } } } } my $title = (@entries && $entries[0]) ? $archiver->archive_title($ctx, $entries[0]) : $archiver->archive_title($ctx, $ctx->{current_timestamp}); defined $title ? $title : ''; } ########################################################################### =head2 ArchiveDateEnd The ending date of the archive in context. For use with the Monthly, Weekly, and Daily archive types only. Date format tags may be applied with the format attribute along with the language attribute. See L tag for supported attributes. B =over 4 =item * format (optional) A string that provides the format in which to publish the date. If unspecified, the default that is appropriate for the language of the blog is used (for English, this is "%B %e, %Y %l:%M %p"). See the L tag for the supported formats. =item * language (optional; defaults to blog language) Forces the date to the format associated with the specified language. =item * utc (optional; default "0") Forces the date to UTC time zone. =item * relative (optional; default "0") Produces a relative date (relative to current date and time). Suitable for dynamic publishing (for instance, from PHP or search result templates). If a relative date cannot be produced (the archive date is sufficiently old), the 'format' attribute will govern the output of the date. =back B <$mt:ArchiveDateEnd$> =for tags date =cut sub _hdlr_archive_date_end { my ($ctx, $args) = @_; my $end = $ctx->{current_timestamp_end} or return $ctx->error(MT->translate( "[_1] can be used only with Daily, Weekly, or Monthly archives.", '<$MTArchiveDateEnd$>' )); $args->{ts} = $end; return _hdlr_date(@_); } ########################################################################### =head2 ArchiveType Publishes the identifier for the current archive type. Typically, one of "Daily", "Weekly", "Monthly", "Yearly", "Category", "Individual", "Page", etc. =cut sub _hdlr_archive_type { my ($ctx, $args, $cond) = @_; my $at = $ctx->{current_archive_type} || $ctx->{archive_type}; $at = 'Category' if $ctx->{inside_mt_categories}; defined $at ? $at : ''; } ########################################################################### =head2 ArchiveLabel An alias for the L tag. B Deprecated in favor of the more specific tag: L =for tags deprecated =cut ########################################################################### =head2 ArchiveTypeLabel A descriptive label of the current archive type. The value returned from this tag will vary based on the archive type: Daily, Weekly, Monthly, Yearly, Author, Author Daily, Author Weekly, Author Monthly, Author Yearly, Category, Category Daily, Category Weekly, Category Monthly, Category Yearly B <$mt:ArchiveTypeLabel$> Related Tags: L =for tags archives =cut sub _hdlr_archive_label { my ($ctx, $args, $cond) = @_; my $at = $ctx->{current_archive_type} || $ctx->{archive_type}; $at = 'Category' if $ctx->{inside_mt_categories}; return q() unless defined $at; my $archiver = MT->publisher->archiver($at); my $al = $archiver->archive_label; if ( 'CODE' eq ref($al) ) { $al = $al->(); } return $al; } ########################################################################### =head2 ArchiveFile The archive filename including file extension for the archive in context. This can be controlled through the archive mapping section of the blog's Publishing settings screen. B For the URL http://www.example.com/categories/politics.html, the L tag would output "politics.html". B =over 4 =item * extension set to '0' to exclude the file extension (ie, produce "politics" instead of "politics.html") =item * separator set to '-' to force any underscore characters in the filename to dashes =back B <$mt:ArchiveFile$> =cut sub _hdlr_archive_file { my ($ctx, $args, $cond) = @_; my $at = $ctx->{current_archive_type} || $ctx->{archive_type}; $at = 'Category' if $ctx->{inside_mt_categories}; my $archiver = MT->publisher->archiver($at); my $f; if (!$at || ($archiver->entry_based)) { my $e = $ctx->stash('entry'); return $ctx->error(MT->translate("Could not determine entry")) if !$e; $f = $e->basename; } else { $f = $ctx->{config}->IndexBasename; } if (exists $args->{extension} && !$args->{extension}) { } else { my $blog = $ctx->stash('blog'); if (my $ext = $blog->file_extension) { $f .= '.' . $ext; } } if ($args->{separator}) { if ($args->{separator} eq '-') { $f =~ s/_/-/g; } } $f; } ########################################################################### =head2 ArchiveLink Publishes a link to the archive template for the current archive context. You may specify an alternate archive type with the "type" attribute to publish a different archive link. B =over 4 =item * type (optional) =item * archive_type (optional) Identifies the specific archive type to generate a link for. If unspecified, uses the current archive type in context, when publishing an archive template. =item * with_index (optional; default "0") If specified, forces any index filename to be included in the link to the archive page. =back B When publishing an entry archive template, you can use the following tag to get a link to the appropriate Monthly archive template relevant to that entry (in other words, if the entry was published in March 2008, the archive link tag would output a permalink for the March 2008 archives). <$MTArchiveLink type="Monthly"$> =cut sub _hdlr_archive_link { my($ctx, $args) = @_; my $at = $args->{type} || $args->{archive_type}; return _hdlr_category_archive(@_) if ($at && ('Category' eq $at)) || ($ctx->{current_archive_type} && 'Category' eq $ctx->{current_archive_type}); $at ||= $ctx->{current_archive_type} || $ctx->{archive_type}; my $archiver = MT->publisher->archiver($at); return '' unless $archiver; my $blog = $ctx->stash('blog'); my $entry; if ($archiver->entry_based) { $entry = $ctx->stash('entry'); } my $author = $ctx->stash('author'); #return $ctx->error(MT->translate( # "You used an [_1] tag outside of the proper context.", # '<$MTArchiveLink$>' )) # unless $ctx->{current_timestamp} || $entry; my $cat; if ($archiver->category_based) { $cat = $ctx->stash('category') || $ctx->stash('archive_category'); } return $ctx->error(MT->translate( "You used an [_1] tag for linking into '[_2]' archives, but that archive type is not published.", '<$MTArchiveLink$>', $at)) unless $blog->has_archive_type($at); my $arch = $blog->archive_url; $arch = $blog->site_url if $entry && $entry->class eq 'page'; $arch .= '/' unless $arch =~ m!/$!; $arch .= archive_file_for($entry, $blog, $at, $cat, undef, $ctx->{current_timestamp}, $author); $arch = MT::Util::strip_index($arch, $blog) unless $args->{with_index}; return $arch; } ########################################################################### =head2 ArchiveCount This tag will potentially return two different values depending upon the context in which it is invoked. If invoked within L this tag will behave as if it was an alias to L. Otherwise it will return the number corresponding to the number of entries currently in context. For example within any L context, this tag will return the number of entries that that L tag corresponds to. B There are <$mt:ArchiveCount$> entries in the <$mt:CategoryLabel$> category. =for tags count =cut sub _hdlr_archive_count { my ($ctx, $args, $cond) = @_; my $at = $ctx->{current_archive_type} || $ctx->{archive_type}; my $archiver = MT->publisher->archiver($at); if ($ctx->{inside_mt_categories} && !$archiver->date_based) { return _hdlr_category_count($ctx); } elsif (my $count = $ctx->stash('archive_count')) { return $ctx->count_format($count, $args); } my $e = $ctx->stash('entries'); my @entries = @$e if ref($e) eq 'ARRAY'; my $count = scalar @entries; return $ctx->count_format($count, $args); } ########################################################################### =head2 ArchiveCategory This tag has been deprecated in favor of L. Returns the label of the current category. B <$mt:ArchiveCategory$> =for tags deprecated =cut sub _hdlr_archive_category { return &_hdlr_category_label; } ########################################################################### =head2 ImageURL Outputs the uploaded image URL (used only for the system template for uploaded images). =cut sub _hdlr_image_url { my ($ctx) = @_; return $ctx->stash('image_url'); } ########################################################################### =head2 ImageWidth Outputs the uploaded image width in pixels (used only for the system template for uploaded images). =cut sub _hdlr_image_width { my ($ctx) = @_; return $ctx->stash('image_width'); } ########################################################################### =head2 ImageHeight Outputs the uploaded image height in pixels (used only for the system template for uploaded images). =cut sub _hdlr_image_height { my ($ctx) = @_; return $ctx->stash('image_height'); } ########################################################################### =head2 Calendar A container tag representing a calendar month that lists a single calendar "cell" in the calendar display. B =over 4 =item * month An optional attribute that specifies the calendar month and year the tagset is to generate. The value must be in YYYYMM format. The month attribute also recognizes two special values. Given a value of "last", the calendar will be generated for the previous month from the current date. Using a value of "this" will generate a calendar for the current month. The default behavior is to generate a monthly calendar based on the archive in context. When used in the context of an archive type other then "Category," the calendar will be generated for the month in which the archive falls. =item * category An optional attribute that specifies the name of a category from which to return entries. =back B To produce a calendar for January, 2002 of entries in the category "Foo": ... =for tags calendar =cut sub _hdlr_calendar { my($ctx, $args, $cond) = @_; my $blog_id = $ctx->stash('blog_id'); my($prefix); my @ts = offset_time_list(time, $blog_id); my $today = sprintf "%04d%02d", $ts[5]+1900, $ts[4]+1; if ($prefix = lc($args->{month}||'')) { if ($prefix eq 'this') { my $ts = $ctx->{current_timestamp} or return $ctx->error(MT->translate( "You used an [_1] tag without a date context set up.", qq() )); $prefix = substr $ts, 0, 6; } elsif ($prefix eq 'last') { my $year = substr $today, 0, 4; my $month = substr $today, 4, 2; if ($month - 1 == 0) { $prefix = $year - 1 . "12"; } else { $prefix = $year . $month - 1; } } else { return $ctx->error(MT->translate( "Invalid month format: must be YYYYMM" )) unless length($prefix) eq 6; } } else { $prefix = $today; } my($cat_name, $cat); if ($cat_name = $args->{category}) { $cat = MT::Category->load({ label => $cat_name, blog_id => $blog_id }) or return $ctx->error(MT->translate( "No such category '[_1]'", $cat_name )); } else { $cat_name = ''; ## For looking up cached calendars. } my $uncompiled = $ctx->stash('uncompiled') || ''; my $r = MT::Request->instance; my $calendar_cache = $r->cache('calendar'); unless ($calendar_cache) { $r->cache('calendar', $calendar_cache = { }); } if (exists $calendar_cache->{$prefix . $cat_name} && $calendar_cache->{$prefix . $cat_name}{'uc'} eq $uncompiled) { return $calendar_cache->{$prefix . $cat_name}{output}; } $today .= sprintf "%02d", $ts[3]; my($start, $end) = start_end_month($prefix); my($y, $m) = unpack 'A4A2', $prefix; my $days_in_month = days_in($m, $y); my $pad_start = wday_from_ts($y, $m, 1); my $pad_end = 6 - wday_from_ts($y, $m, $days_in_month); my $iter = MT::Entry->load_iter({ blog_id => $blog_id, authored_on => [ $start, $end ], status => MT::Entry::RELEASE() }, { range_incl => { authored_on => 1 }, 'sort' => 'authored_on', direction => 'ascend', }); my @left; my $res = ''; my $tokens = $ctx->stash('tokens'); my $builder = $ctx->stash('builder'); my $iter_drained = 0; for my $day (1..$pad_start+$days_in_month+$pad_end) { my $is_padding = $day < $pad_start+1 || $day > $pad_start+$days_in_month; my($this_day, @entries) = (''); local($ctx->{__stash}{entries}, $ctx->{__stash}{calendar_day}, $ctx->{current_timestamp}, $ctx->{current_timestamp_end}); local $ctx->{__stash}{calendar_cell} = $day; unless ($is_padding) { $this_day = $prefix . sprintf("%02d", $day - $pad_start); my $no_loop = 0; if (@left) { if (substr($left[0]->authored_on, 0, 8) eq $this_day) { @entries = @left; @left = (); } else { $no_loop = 1; } } unless ($no_loop || $iter_drained) { while (my $entry = $iter->()) { next unless !$cat || $entry->is_in_category($cat); my $e_day = substr $entry->authored_on, 0, 8; push(@left, $entry), last unless $e_day eq $this_day; push @entries, $entry; } $iter_drained++ unless @left; } $ctx->{__stash}{entries} = \@entries; $ctx->{current_timestamp} = $this_day . '000000'; $ctx->{current_timestamp_end} = $this_day . '235959'; $ctx->{__stash}{calendar_day} = $day - $pad_start; } defined(my $out = $builder->build($ctx, $tokens, { %$cond, CalendarWeekHeader => ($day-1) % 7 == 0, CalendarWeekFooter => $day % 7 == 0, CalendarIfEntries => !$is_padding && scalar @entries, CalendarIfNoEntries => !$is_padding && !(scalar @entries), CalendarIfToday => ($today eq $this_day), CalendarIfBlank => $is_padding, })) or return $ctx->error( $builder->errstr ); $res .= $out; } $calendar_cache->{$prefix . $cat_name} = { output => $res, 'uc' => $uncompiled }; return $res; } ########################################################################### =head2 CalendarIfBlank A conditional tag that will display its contents if the current calendar cell is for a day in another month. B   =for tags calendar =cut ########################################################################### =head2 CalendarIfEntries A conditional tag that will display its contents if there are any entries for this day in the blog. B "> <$mt:CalendarDay$> =for tags calendar, entries =cut ########################################################################### =head2 CalendarIfNoEntries A conditional tag that will display its contents if there are not entries for this day in the blog. This tag predates the introduction of L, a tag that could be used with L to replace C. =for tags calendar, entries =cut ########################################################################### =head2 CalendarDate The timestamp of the current day of the month. Date format tags may be applied with the format attribute along with the language attribute. B <$mt:CalendarDate$> =for tags calendar, date =cut ########################################################################### =head2 CalendarIfToday A conditional tag that will display its contents if the current cell is for the current day. =for tags calendar =cut ########################################################################### =head2 CalendarWeekHeader A conditional tag that will display its contents before a calendar week is started. B =for tags calendar =cut ########################################################################### =head2 CalendarWeekFooter A conditional tag that will display its contents before a calendar week is ended. B =for tags calendar =cut ########################################################################### =head2 CalendarDay The numeric day of the month for the cell of the calendar being published. This tag may only be used inside a L tag. B <$mt:CalendarDay$> =for tags calendar =cut sub _hdlr_calendar_day { my ($ctx) = @_; my $day = $ctx->stash('calendar_day') or return $ctx->error(MT->translate( "You used an [_1] tag outside of the proper context.", '<$MTCalendarDay$>' )); return $day; } ########################################################################### =head2 CalendarCellNumber The number of the "cell" in the calendar, beginning with 1. The count begins with the first cell regardless of whether a day of the month falls on it. This tag may only be used inside a L tag. B <$mt:CalendarCellNumber$> =for tags calendar =cut sub _hdlr_calendar_cell_num { my ($ctx) = @_; my $num = $ctx->stash('calendar_cell') or return $ctx->error(MT->translate( "You used an [_1] tag outside of the proper context.", '<$MTCalendarCellNumber$>' )); return $num; } ########################################################################### =head2 Categories Produces a list of categories defined for the current blog. This tag produces output for every category with no regard to their hierarchical structure. =head4 Attributes =over =item * `show_empty` Setting this optional attribute to true (1) will include categories with no entries assigned. The default is false (0), where only categories with entries assigned. =item * `glue` A string used to join the output of the separate iterations of MTCategories. This can be as simple as a comma and space (", ") to list out category labels or complex HTML markup to be inserted between the markup generated by MTCategories. =back =head4 Example List out the categories used on at least one blog entry, separated by commas: Categories: List all categories and link to categories it the category has one or more entries: =head4 Related =over =item * [Template Loop Meta Variables](/documentation/designer/loop-meta-variables.html) offer conditionals for odd, even, first, last, and counter. =back =for tags block, categories, category, entrycategories, template tag, multiblog =cut sub _hdlr_categories { my($ctx, $args, $cond) = @_; my (%terms, %args); $ctx->set_blog_load_context($args, \%terms, \%args) or return $ctx->error($ctx->errstr); require MT::Placement; $args{'sort'} = 'label'; $args{'direction'} = 'ascend'; my $class_type = $args->{class_type} || 'category'; my $class = MT->model($class_type); my $entry_class = MT->model( $class_type eq 'category' ? 'entry' : 'page'); my %counts; my $count_tag = $class_type eq 'category' ? 'CategoryCount' : 'FolderCount' ; my $uncompiled = $ctx->stash('uncompiled') || ''; my $count_all = 0; if (!$args->{show_empty} || $uncompiled =~ /<\$?mt:?$count_tag/i) { $count_all = 1; } ## Supplies a routine that will yield the number of entries associated ## with the category in context in the most efficient manner. ## If we can determine counts will be gathered for all categories, ## a 'count_group_by' request is done for MT::Placement to fetch counts ## with a single query (storing them in %counts). ## Otherwise, counts are collected on an as-needed basis, using the ## 'entry_count' method in MT::Category. my $counts_fetched = 0; my $entry_count_of = sub { my $cat = shift; return delay(sub{$cat->entry_count}) unless $count_all; return $cat->entry_count(defined $counts{$cat->id} ? $counts{$cat->id} : 0) if $counts_fetched; return $cat->cache_property( 'entry_count', sub{ # issue a single count_group_by for all categories my $cnt_iter = MT::Placement->count_group_by( {%terms}, { group => ['category_id'], join => $entry_class->join_on( undef, { id => \'=placement_entry_id', status => MT::Entry::RELEASE(), } ), } ); while (my ($count, $cat_id) = $cnt_iter->()) { $counts{$cat_id} = $count; } $counts_fetched = 1; $counts{$cat->id}; } ); }; my $iter = $class->load_iter(\%terms, \%args); my $res = ''; my $builder = $ctx->stash('builder'); my $tokens = $ctx->stash('tokens'); my $glue = $args->{glue}; ## In order for this handler to double as the handler for ## , it needs to support ## the <$MTArchiveLink$> and <$MTArchiveTitle$> tags local $ctx->{inside_mt_categories} = 1; my $i = 0; my $cat = $iter->(); if ( !$args->{show_empty} ) { while ( defined $cat && !$entry_count_of->($cat) ) { $cat = $iter->(); } } my $n = $args->{lastn}; my $vars = $ctx->{__stash}{vars} ||= {}; while (defined($cat)) { $i++; my $next_cat = $iter->(); if ( !$args->{show_empty} ) { while ( defined $next_cat && !$entry_count_of->($next_cat) ) { $next_cat = $iter->(); } } my $last; $last = 1 if $n && ($i >= $n); $last = 1 unless defined $next_cat; local $ctx->{__stash}{category} = $cat; local $ctx->{__stash}{entries}; local $ctx->{__stash}{category_count}; local $ctx->{__stash}{blog_id} = $cat->blog_id; local $ctx->{__stash}{blog} = MT::Blog->load($cat->blog_id); local $ctx->{__stash}{folder_header} = ($i == 1) if $class_type ne 'category'; local $ctx->{__stash}{folder_footer} = ($last) if $class_type ne 'category'; local $vars->{__first__} = $i == 1; local $vars->{__last__} = $last; local $vars->{__odd__} = ($i % 2) == 1; local $vars->{__even__} = ($i % 2) == 0; local $vars->{__counter__} = $i; $ctx->{__stash}{category_count} = $entry_count_of->($cat); defined(my $out = $builder->build($ctx, $tokens, { %$cond, ArchiveListHeader => $i == 1, ArchiveListFooter => $last })) or return $ctx->error( $builder->errstr ); $res .= $glue if defined $glue && length($res) && length($out); $res .= $out; last if $last; $cat = $next_cat; } $res; } ########################################################################### =head2 CategoryID The numeric system ID of the category. B <$mt:CategoryID$> =for tags categories =cut sub _hdlr_category_id { my ($ctx) = @_; my $cat = ($ctx->stash('category') || $ctx->stash('archive_category')) or return $ctx->error(MT->translate( "You used an [_1] tag outside of the proper context.", '<$MT'.$ctx->stash('tag').'$>')); return $cat->id; } ########################################################################### =head2 CategoryLabel The label of the category in context. The current category in context can be placed there by either the following contexts (in order of precedence): =over 4 =item * the current category you might be by looping through a list of categories =item * the current category archive template/mapping you are in =item * the primary category of the current entry in context =back B <$mt:CategoryLabel$> =for tags categories =cut sub _hdlr_category_label { my ($ctx, $args, $cond) = @_; my $class_type = $args->{class_type} || 'category'; my $e = $ctx->stash('entry'); my $cat = ($ctx->stash('category') || $ctx->stash('archive_category')) || (($e = $ctx->stash('entry')) && $e->category) or return (defined($args->{default}) ? $args->{default} : $ctx->error(MT->translate( "You used an [_1] tag outside of the proper context.", '<$MT'.$ctx->stash('tag').'$>'))); my $label = $cat->label; $label = '' unless defined $label; return $label; } ########################################################################### =head2 CategoryBasename Produces the dirified basename defined for the category in context. B =over 4 =item * default A value to use in the event that no category is in context. =item * separator Valid values are "_" and "-", dash is the default value. Specifying an underscore will convert any dashes to underscores. Specifying a dash will convert any underscores to dashes. =back B <$mt:CategoryBasename$> =cut sub _hdlr_category_basename { my ($ctx, $args, $cond) = @_; my $class_type = $args->{class_type} || 'category'; my $e = $ctx->stash('entry'); my $cat = ($ctx->stash('category') || $ctx->stash('archive_category')) || (($e = $ctx->stash('entry')) && $e->category) or return (defined($args->{default}) ? $args->{default} : $ctx->error(MT->translate( "You used an [_1] tag outside of the proper context.", '<$MT'.$ctx->stash('tag').'$>'))); my $basename = $cat->basename || ''; if (my $sep = $args->{separator}) { if ($sep eq '-') { $basename =~ s/_/-/g; } elsif ($sep eq '_') { $basename =~ s/-/_/g; } } return $basename; } ########################################################################### =head2 CategoryDescription The description for the category in context. B <$mt:CategoryDescription$> =cut sub _hdlr_category_desc { my ($ctx) = @_; my $cat = ($ctx->stash('category') || $ctx->stash('archive_category')) or return $ctx->error(MT->translate( "You used an [_1] tag outside of the proper context.", '<$MT'.$ctx->stash('tag').'$>')); return defined $cat->description ? $cat->description : ''; } ########################################################################### =head2 CategoryCount The number of published entries for the category in context. B Entries in this category: <$mt:CategoryCount$> =for tags categories, count =cut sub _hdlr_category_count { my ($ctx, $args, $cond) = @_; my $cat = ($ctx->stash('category') || $ctx->stash('archive_category')) or return $ctx->error(MT->translate( "You used an [_1] tag outside of the proper context.", '<$MT' . $ctx->stash('tag') . '$>')); my $count = $ctx->stash('category_count'); $count = $cat->entry_count unless defined $count; return $ctx->count_format($count, $args); } ########################################################################### =head2 CategoryCommentCount This template tag returns the number of comments found within a category. B
  • <$mt:CategoryLabel$> (<$mt:CategoryCommentCount$>)
=for tags categories, comments =cut sub _hdlr_category_comment_count { my ($ctx, $args, $cond) = @_; my $cat = ($ctx->stash('category') || $ctx->stash('archive_category')) or return $ctx->error(MT->translate( "You used an [_1] tag outside of the proper context.", '<$MT' . $ctx->stash('tag') . '$>')); my($count); my $blog_id = $ctx->stash ('blog_id'); my $class = MT->model( $ctx->stash('tag') =~ m/Category/ig ? 'entry' : 'page'); my @args = ({ blog_id => $blog_id, visible => 1 }, { 'join' => MT::Entry->join_on(undef, { id => \'= comment_entry_id', status => MT::Entry::RELEASE(), blog_id => $blog_id, }, { 'join' => MT::Placement->join_on('entry_id', { category_id => $cat->id, blog_id => $blog_id } ) } ) } ); require MT::Comment; $count = scalar MT::Comment->count(@args); return $ctx->count_format($count, $args); } ########################################################################### =head2 CategoryArchiveLink A link to the archive page of the category. B <$mt:CategoryArchiveLink$> =cut sub _hdlr_category_archive { my ($ctx, $args) = @_; my $cat = ($ctx->stash('category') || $ctx->stash('archive_category')) or return $ctx->error(MT->translate( "You used an [_1] tag outside of the proper context.", '<$MTCategoryArchiveLink$>' )); my $curr_at = $ctx->{current_archive_type} || $ctx->{archive_type} || 'Category'; my $blog = $ctx->stash('blog'); return '' unless $blog || $curr_at eq 'Category'; if ( $curr_at ne 'Category' ) { # Check if "Category" archive is published my $at = $blog->archive_type; my @at = split /,/, $at; my $cat_arc = 0; for (@at) { if ( 'Category' eq $_ ) { $cat_arc = 1; last; } } return $ctx->error(MT->translate( "[_1] cannot be used without publishing Category archive.", '<$MTCategoryArchiveLink$>' )) unless $cat_arc; } my $arch = $blog->archive_url; $arch .= '/' unless $arch =~ m!/$!; $arch = $arch . archive_file_for(undef, $blog, 'Category', $cat); $arch = MT::Util::strip_index($arch, $blog) unless $args->{with_index}; $arch; } ########################################################################### =head2 CategoryTrackbackLink The URL that TrackBack pings can be sent for the category in context. B <$mt:CategoryTrackbackLink$> =cut sub _hdlr_category_tb_link { my($ctx, $args) = @_; my $cat = $ctx->stash('category') || $ctx->stash('archive_category'); if (!$cat) { my $cat_name = $args->{category} or return $ctx->error(MT->translate("<\$MTCategoryTrackbackLink\$> must be used in the context of a category, or with the 'category' attribute to the tag.")); $cat = MT::Category->load({ label => $cat_name, blog_id => $ctx->stash('blog_id') }) or return $ctx->error("No such category '$cat_name'"); } require MT::Trackback; my $tb = MT::Trackback->load({ category_id => $cat->id }) or return ''; my $cfg = $ctx->{config}; my $path = _hdlr_cgi_path($ctx); return $path . $cfg->TrackbackScript . '/' . $tb->id; } ########################################################################### =head2 CategoryIfAllowPings A conditional tag that displays its contents if pings are enabled for the category in context. =for tags categories, pings =cut sub _hdlr_category_allow_pings { my ($ctx) = @_; my $cat = $ctx->stash('category') || $ctx->stash('archive_category'); return $cat->allow_pings ? 1 : 0; } ########################################################################### =head2 CategoryTrackbackCount The number of published TrackBack pings for the category in context. B <$mt:CategoryTrackbackCount$> =for tags categories, pings, count =cut sub _hdlr_category_tb_count { my($ctx, $args) = @_; my $cat = $ctx->stash('category') || $ctx->stash('archive_category'); return 0 unless $cat; require MT::Trackback; my $tb = MT::Trackback->load( { category_id => $cat->id } ); return 0 unless $tb; require MT::TBPing; my $count = MT::TBPing->count( { tb_id => $tb->id, visible => 1 } ); return $ctx->count_format($count || 0, $args); } sub _load_sibling_categories { my ($ctx, $cat, $class_type) = @_; my $blog_id = $cat->blog_id; my $r = MT::Request->instance; my $cats = $r->stash('__cat_cache_'.$blog_id.'_'.$cat->parent); return $cats if $cats; my $class = MT->model($class_type); my @cats = $class->load({blog_id => $blog_id, parent => $cat->parent}, {'sort' => 'label', direction => 'ascend'}); $r->stash('__cat_cache_'.$blog_id.'_'.$cat->parent, \@cats); \@cats; } ########################################################################### =head2 CategoryPrevious A container tag which creates a category context of the previous category relative to the current entry category or archived category. If the current category has sub-categories, then CategoryPrevious will generate a link to the previous sibling category. For example, in the following category hierarchy: News Gossip Politics Sports Events Oakland Palo Alto San Francisco Sports Local College MBA NFL If the current category is "Events" then CategoryPrevious will link to "News". If the current category is "San Francisco" then CategoryPrevious will link to "Palo Alto". B =over 4 =item * show_empty Specifies whether categories with no entries assigned should be counted. =back B "><$mt:CategoryLabel$> =for tags categories, archives =cut ########################################################################### =head2 CategoryNext A container tag which creates a category context of the next category relative to the current entry category or archived category. If the current category has sub-categories, then CategoryNext will generate a link to the next sibling category. For example, in the following category hierarchy: News Gossip Politics Sports Events Oakland Palo Alto San Francisco Sports Local College MBA NFL If the current category is "News" then CategoryNext will link to "Events". If the current category is "Oakland" then CategoryNext will link to "Palo Alto". B =over 4 =item * show_empty Specifies whether categories with no entries assigned should be counted and displayed. =back =for tags categories, archives =cut sub _hdlr_category_prevnext { my ($ctx, $args, $cond) = @_; my $class_type = $args->{class_type} || 'category'; my $e = $ctx->stash('entry'); my $tag = $ctx->stash('tag'); my $step = $tag =~ m/Next/i ? 1 : -1; my $cat = $e ? $e->category ? $e->category : ($ctx->stash('category') || $ctx->stash('archive_category')) : ($ctx->stash('category') || $ctx->stash('archive_category')); return $ctx->error(MT->translate( "You used an [_1] tag outside of the proper context.", "" )) if !defined $cat; require MT::Placement; my $needs_entries; my $uncompiled = $ctx->stash('uncompiled') || ''; $needs_entries = $class_type eq 'category' ? (($uncompiled =~ /blog_id; my $cats = _load_sibling_categories($ctx, $cat, $class_type); my ($pos, $idx); $idx = 0; foreach (@$cats) { $pos = $idx, last if $_->id == $cat->id; $idx++; } return '' unless defined $pos; $pos += $step; while ( $pos >= 0 && $pos < scalar @$cats ) { if (!exists $cats->[$pos]->{_placement_count}) { if ($needs_entries) { require MT::Entry; my @entries = MT::Entry->load({ blog_id => $blog_id, status => MT::Entry::RELEASE() }, { 'join' => [ 'MT::Placement', 'entry_id', { category_id => $cat->id } ], 'sort' => 'authored_on', direction => 'descend', }); $cats->[$pos]->{_entries} = \@entries; $cats->[$pos]->{_placement_count} = scalar @entries; } else { $cats->[$pos]->{_placement_count} = MT::Placement->count({ category_id => $cats->[$pos]->id }); } } $pos += $step, next unless $cats->[$pos]->{_placement_count} || $args->{show_empty}; local $ctx->{__stash}{category} = $cats->[$pos]; local $ctx->{__stash}{entries} = $cats->[$pos]->{_entries} if $needs_entries; local $ctx->{__stash}{category_count} = $cats->[$pos]->{_placement_count}; return _hdlr_pass_tokens($ctx, $args, $cond); } return ''; } ########################################################################### =head2 IfCategory A conditional tag used to test for category assignments for the entry in context, or generically to test for which category is in context. B =over 4 =item * name (or label; optional) The name of a category. If given, tests the category in context (or categories of an entry in context) to see if it matches with the given category name. =item * type (optional) Either the keyword "primary" or "secondary". Used to test whether the specified category (specified by the name or label attribute) is a primary or secondary category assignment for the entry in context. =back B (current entry in context has a category assignment) (current entry in context has one or more secondary category) (current entry in context is assigned to the "News" category) (current entry in context has a "News" category as its primary category) =for tags entries, categories =cut ########################################################################### =head2 EntryIfCategory This tag has been deprecated in favor of L. =for tags deprecated =cut sub _hdlr_if_category { my ($ctx, $args, $cond) = @_; my $e = $ctx->stash('entry'); my $tag = lc $ctx->stash('tag'); my $entry_context = $tag =~ m/(entry|page)if(category|folder)/; return $ctx->_no_entry_error() if $entry_context && !$e; my $name = $args->{name} || $args->{label}; my $primary = $args->{type} && ($args->{type} eq 'primary'); my $secondary = $args->{type} && ($args->{type} eq 'secondary'); $entry_context ||= ($primary || $secondary); my $cat = $entry_context ? $e->category : ($ctx->stash('category') || $ctx->stash('archive_category')); if (!$cat && $e && !$entry_context) { $cat = $e->category; $entry_context = 1; } my $cats; if ($cat && ($primary || !$entry_context)) { $cats = [ $cat ]; } elsif ($e) { $cats = $e->categories; } if ($secondary && $cat) { my @cats = grep { $_->id != $cat->id } @$cats; $cats = \@cats; } if (!defined $name) { return @$cats ? 1 : 0; } foreach my $cat (@$cats) { return 1 if $cat->label eq $name; } 0; } ########################################################################### =head2 EntryAdditionalCategories This block tag iterates over all secondary categories for the entry in context. Must be used in an entry context (entry archive or L loop). All categories can be listed using L loop tag. =for tags entries, categories =cut sub _hdlr_entry_additional_categories { my($ctx, $args, $cond) = @_; my $e = $ctx->stash('entry') or return $ctx->_no_entry_error(); my $cats = $e->categories; return '' unless $cats && @$cats; my $builder = $ctx->stash('builder'); my $tokens = $ctx->stash('tokens'); my $res = ''; my $glue = $args->{glue}; for my $cat (@$cats) { next if $e->category && ($cat->label eq $e->category->label); local $ctx->{__stash}->{category} = $cat; defined(my $out = $builder->build($ctx, $tokens, $cond)) or return $ctx->error( $builder->errstr ); $res .= $glue if defined $glue && length($res) && length($out); $res .= $out if length($out); } $res; } ########################################################################### =head2 Pings A context-sensitive container tag that lists all of the pings sent to a particular entry, category or blog. If used in an entry context the tagset will list all pings for the entry. Likewise for a TrackBack-enabled category in context. If not in an entry or category context, a blog context is assumed and all associated pings are listed. B =over 4 =item * category This attribute creates a specific category context regardless of its placement. =item * lastn Display the last N pings in context. N is a positive integer. =item * sort_order Specifies the sort order. Recognized values are "ascend" (default) and "descend." =back =for tags pings, multiblog =cut sub _hdlr_pings { my($ctx, $args, $cond) = @_; require MT::Trackback; require MT::TBPing; my($tb, $cat); my $blog = $ctx->stash('blog'); nofollowfy_on($args) if ($blog->nofollow_urls); if (my $e = $ctx->stash('entry')) { $tb = $e->trackback; return '' unless $tb; } elsif ($cat = $ctx->stash('archive_category')) { $tb = MT::Trackback->load({ category_id => $cat->id }); return '' unless $tb; } elsif (my $cat_name = $args->{category}) { $cat = MT::Category->load({ label => $cat_name, blog_id => $ctx->stash('blog_id') }) or return $ctx->error(MT->translate("No such category '[_1]'", $cat_name)); $tb = MT::Trackback->load({ category_id => $cat->id }); return '' unless $tb; } my $res = ''; my $builder = $ctx->stash('builder'); my $tokens = $ctx->stash('tokens'); my (%terms, %args); $ctx->set_blog_load_context($args, \%terms, \%args) or return $ctx->error($ctx->errstr); $terms{tb_id} = $tb->id if $tb; $terms{visible} = 1; $args{'sort'} = 'created_on'; $args{'direction'} = $args->{sort_order} || 'ascend'; if (my $limit = $args->{lastn}) { $args{direction} = $args->{sort_order} || 'descend'; $args{limit} = $limit; } my @pings = MT::TBPing->load(\%terms, \%args); my $count = 0; my $max = scalar @pings; my $vars = $ctx->{__stash}{vars} ||= {}; for my $ping (@pings) { $count++; local $ctx->{__stash}{ping} = $ping; local $ctx->{__stash}{blog} = $ping->blog; local $ctx->{__stash}{blog_id} = $ping->blog_id; local $ctx->{current_timestamp} = $ping->created_on; local $vars->{__first__} = $count == 1; local $vars->{__last__} = ($count == ($max)); local $vars->{__odd__} = ($count % 2) == 1; local $vars->{__even__} = ($count % 2) == 0; local $vars->{__counter__} = $count; my $out = $builder->build($ctx, $tokens, { %$cond, PingsHeader => $count == 1, PingsFooter => ($count == $max) }); return $ctx->error( $builder->errstr ) unless defined $out; $res .= $out; } $res; } ########################################################################### =head2 PingsHeader The contents of this container tag will be displayed when the first ping listed by a L tag is reached. =for tags pings =cut ########################################################################### =head2 PingsFooter The contents of this container tag will be displayed when the last ping listed by a L tag is reached. =for tags pings =cut ########################################################################### =head2 PingsSent A container tag representing a list of TrackBack pings sent from an entry. Use the L tag to display the URL pinged. B

Ping'd

  • <$mt:PingsSentURL$>
=for tags pings, entries =cut sub _hdlr_pings_sent { my($ctx, $args, $cond) = @_; my $e = $ctx->stash('entry') or return $ctx->_no_entry_error(); my $builder = $ctx->stash('builder'); my $tokens = $ctx->stash('tokens'); my $res = ''; my $pings = $e->pinged_url_list; for my $url (@$pings) { $ctx->stash('ping_sent_url', $url); defined(my $out = $builder->build($ctx, $tokens, $cond)) or return $ctx->error($builder->errstr); $res .= $out; } $res; } ########################################################################### =head2 PingsSentURL The URL of the TrackBack ping was sent to. This is the TrackBack Ping URL and not a permalink. B <$mt:PingsSentURL$> =for tags pings =cut sub _hdlr_pings_sent_url { my ($ctx) = @_; return $ctx->stash('ping_sent_url'); } ########################################################################### =head2 PingDate The timestamp of when the ping was submitted. Date format tags may be applied with the format attribute along with the language attribute. B =over 4 =item * format (optional) A string that provides the format in which to publish the date. If unspecified, the default that is appropriate for the language of the blog is used (for English, this is "%B %e, %Y %l:%M %p"). See the L tag for the supported formats. =item * language (optional; defaults to blog language) Forces the date to the format associated with the specified language. =item * utc (optional; default "0") Forces the date to UTC time zone. =item * relative (optional; default "0") Produces a relative date (relative to current date and time). Suitable for dynamic publishing (for instance, from PHP or search result templates). If a relative date cannot be produced (the archive date is sufficiently old), the 'format' attribute will govern the output of the date. =back B <$mt:PingDate$> =for tags pings, date =cut sub _hdlr_ping_date { my ($ctx, $args) = @_; my $p = $ctx->stash('ping') or return $ctx->_no_ping_error(); $args->{ts} = $p->created_on; return _hdlr_date($ctx, $args); } ########################################################################### =head2 PingID A numeric system ID of the TrackBack ping in context. B <$mt:PingID$> =for tags pings =cut sub _hdlr_ping_id { my ($ctx) = @_; my $ping = $ctx->stash('ping') or return $ctx->_no_ping_error(); return $ping->id; } ########################################################################### =head2 PingTitle The title of the remote resource that the TrackBack ping sent. B <$mt:PingTitle$> =for tags pings =cut sub _hdlr_ping_title { my ($ctx, $args) = @_; sanitize_on($args); my $ping = $ctx->stash('ping') or return $ctx->_no_ping_error(); return defined $ping->title ? $ping->title : ''; } ########################################################################### =head2 PingURL The URL of the remote resource that the TrackBack ping sent. B <$mt:PingURL$> =for tags pings =cut sub _hdlr_ping_url { my ($ctx, $args) = @_; sanitize_on($args); my $ping = $ctx->stash('ping') or return $ctx->_no_ping_error(); return defined $ping->source_url ? $ping->source_url : ''; } ########################################################################### =head2 PingExcerpt An excerpt describing the URL of the ping sent. B <$mt:PingExcerpt$> =for tags pings =cut sub _hdlr_ping_excerpt { my ($ctx, $args) = @_; sanitize_on($args); my $ping = $ctx->stash('ping') or return $ctx->_no_ping_error(); return defined $ping->excerpt ? $ping->excerpt : ''; } ########################################################################### =head2 PingIP The IP (Internet Protocol) network address the TrackBack ping was sent from. B <$mt:PingIP$> =for tags pings =cut sub _hdlr_ping_ip { my ($ctx) = @_; my $ping = $ctx->stash('ping') or return $ctx->_no_ping_error(); return defined $ping->ip ? $ping->ip : ''; } ########################################################################### =head2 PingBlogName The site name that sent the TrackBack ping. B <$mt:BlogName$> =for tags pings =cut sub _hdlr_ping_blog_name { my ($ctx, $args) = @_; sanitize_on($args); my $ping = $ctx->stash('ping') or return $ctx->_no_ping_error(); return defined $ping->blog_name ? $ping->blog_name : ''; } ########################################################################### =head2 PingEntry Provides an entry context for the parent entry of the TrackBack ping in context. B Last TrackBack received was for the entry titled: <$mt:EntryTitle$> =for tags pings, entries =cut sub _hdlr_ping_entry { my ($ctx, $args, $cond) = @_; my $ping = $ctx->stash('ping') or return $ctx->_no_ping_error(); require MT::Trackback; my $tb = MT::Trackback->load($ping->tb_id); return '' unless $tb; return '' unless $tb->entry_id; my $entry = MT::Entry->load($tb->entry_id) or return ''; local $ctx->{__stash}{entry} = $entry; local $ctx->{current_timestamp} = $entry->authored_on; $ctx->stash('builder')->build($ctx, $ctx->stash('tokens'), $cond); } ########################################################################### =head2 IfAllowCommentHTML Conditional block that is true when the blog has been configured to permit HTML in comments. =for tags comments =cut sub _hdlr_if_allow_comment_html { my ($ctx, $args, $cond) = @_; my $blog = $ctx->stash('blog'); return '' unless $blog; if ($blog->allow_comment_html) { return 1; } else { return 0; } } ########################################################################### =head2 IfCommentsAllowed Conditional block that is true when the blog is configured to accept comments, and comments are accepted on a system-wide basis. This tag does not take the current entry context into account; use the L tag for this. =for tags comments, configuration =cut sub _hdlr_if_comments_allowed { my ($ctx, $args, $cond) = @_; my $blog = $ctx->stash('blog'); my $cfg = $ctx->{config}; if ((!$blog || ($blog && ($blog->allow_unreg_comments || ($blog->allow_reg_comments && $blog->effective_remote_auth_token)))) && $cfg->AllowComments) { return 1; } else { return 0; } } ########################################################################### =head2 IfCommentsActive Conditional tag that displays its contents if comments are enabled or comments exist for the entry in context. =for tags comments, entries =cut # comments exist in stash OR entry context allows comments sub _hdlr_if_comments_active { my ($ctx, $args, $cond) = @_; my $blog = $ctx->stash('blog'); my $cfg = $ctx->{config}; my $active; if (my $entry = $ctx->stash('entry')) { $active = 1 if ($blog->accepts_comments && $entry->allow_comments && $cfg->AllowComments); $active = 1 if $entry->comment_count; } else { $active = 1 if ($blog->accepts_comments && $cfg->AllowComments); } if ($active) { return 1; } else { return 0; } } ########################################################################### =head2 IfCommentsAccepted Conditional tag that displays its contents if commenting is enabled for the entry in context. B

What do you think?

(comment form)
=for tags comments, entries, configuration =cut sub _hdlr_if_comments_accepted { my ($ctx, $args, $cond) = @_; my $blog = $ctx->stash('blog'); my $cfg = $ctx->{config}; my $entry = $ctx->stash('entry'); my $blog_comments_accepted = $blog->accepts_comments && $cfg->AllowComments; my $accepted = $blog_comments_accepted; $accepted = 0 if $entry && !$entry->allow_comments; if ($accepted) { return 1; } else { return 0; } } ########################################################################### =head2 IfPingsActive Conditional tag that displays its contents if TrackBack pings are enabled or pings exist for the entry in context. =for tags entries, pings =cut sub _hdlr_if_pings_active { my ($ctx, $args, $cond) = @_; my $blog = $ctx->stash('blog'); my $cfg = $ctx->{config}; my $entry = $ctx->stash('entry'); my $active; $active = 1 if $cfg->AllowPings && $blog->allow_pings; $active = 0 if $entry && !$entry->allow_pings; $active = 1 if !$active && $entry && $entry->ping_count; if ($active) { return 1; } else { return 0; } } ########################################################################### =head2 IfPingsModerated Conditional tag that is positive when the blog has a policy to moderate all incoming pings by default. =cut sub _hdlr_if_pings_moderated { my ($ctx, $args, $cond) = @_; my $blog = $ctx->stash('blog'); if ($blog->moderate_pings) { return 1; } else { return 0; } } ########################################################################### =head2 IfPingsAccepted Conditional tag that is positive when pings are allowed for the blog and the entry (if one is in context) and the MT installation. =cut sub _hdlr_if_pings_accepted { my ($ctx, $args, $cond) = @_; my $blog = $ctx->stash('blog'); my $cfg = $ctx->{config}; my $accepted; my $entry = $ctx->stash('entry'); $accepted = 1 if $blog->allow_pings && $cfg->AllowPings; $accepted = 0 if $entry && !$entry->allow_pings; if ($accepted) { return 1; } else { return 0; } } ########################################################################### =head2 IfPingsAllowed Conditional tag that is positive when pings are allowed by the blog and the MT installation (does not test for an entry context). =cut sub _hdlr_if_pings_allowed { my ($ctx, $args, $cond) = @_; my $blog = $ctx->stash('blog'); my $cfg = $ctx->{config}; if ($blog->allow_pings && $cfg->AllowPings) { return 1; } else { return 0; } } ########################################################################### =head2 IfNeedEmail Conditional tag that is positive when the blog is configured to require an e-mail address for anonymous comments. =cut ########################################################################### =head2 IfRequireCommentEmails Conditional tag that is positive when the blog is configured to require an e-mail address for anonymous comments. =cut sub _hdlr_if_need_email { my ($ctx, $args, $cond) = @_; my $blog = $ctx->stash('blog'); my $cfg = $ctx->{config}; if ($blog->require_comment_emails) { return 1; } else { return 0; } } ########################################################################### =head2 EntryIfAllowComments Conditional tag that is positive when the entry in context is configured to allow commenting and the blog and MT installation also permits comments. =cut sub _hdlr_entry_if_allow_comments { my ($ctx, $args, $cond) = @_; my $blog = $ctx->stash('blog'); my $cfg = $ctx->{config}; my $blog_comments_accepted = $blog->accepts_comments && $cfg->AllowComments; my $entry = $ctx->stash('entry'); if ($blog_comments_accepted && $entry->allow_comments) { return 1; } else { return 0; } } ########################################################################### =head2 EntryIfCommentsOpen Deprecated in favor of L. =for tags deprecated =cut sub _hdlr_entry_if_comments_open { my ($ctx, $args, $cond) = @_; my $blog = $ctx->stash('blog'); my $cfg = $ctx->{config}; my $blog_comments_accepted = $blog->accepts_comments && $cfg->AllowComments; my $entry = $ctx->stash('entry'); if ($entry && $blog_comments_accepted && $entry->allow_comments && $entry->allow_comments eq '1') { return 1; } else { return 0; } } ########################################################################### =head2 EntryIfAllowPings Deprecated in favor of L. =for tags deprecated =cut sub _hdlr_entry_if_allow_pings { my ($ctx, $args, $cond) = @_; my $entry = $ctx->stash('entry'); my $blog = $ctx->stash('blog'); my $cfg = $ctx->{config}; my $blog_pings_accepted = 1 if $cfg->AllowPings && $blog->allow_pings; if ($blog_pings_accepted && $entry->allow_pings) { return 1; } else { return 0; } } ########################################################################### =head2 EntryIfExtended Conditional tag that is positive when content is in the extended text field of the current entry in context. =cut sub _hdlr_entry_if_extended { my ($ctx, $args, $cond) = @_; my $entry = $ctx->stash('entry'); my $more = ''; if ($entry) { $more = $entry->text_more; $more = '' unless defined $more; $more =~ s!(^\s+|\s+$)!!g; } if ($more ne '') { return 1; } else { return 0; } } # FIXME: Unused? sub _hdlr_if_commenter_pending { my ($ctx, $args, $cond) = @_; my $cmtr = $ctx->stash('commenter'); my $blog = $ctx->stash('blog'); if ($cmtr && $blog && $cmtr->commenter_status($blog->id) == MT::Author::PENDING()) { return 1; } else { return 0; } } ########################################################################### =head2 SubCatIsFirst The contents of this container tag will be displayed when the first category listed by a L tag is reached. =for tags categories =cut sub _hdlr_sub_cat_is_first { my ($ctx) = @_; return $ctx->stash('subCatIsFirst'); } ########################################################################### =head2 SubCatIsLast The contents of this container tag will be displayed when the last category listed by a L tag is reached. =for tags categories =cut sub _hdlr_sub_cat_is_last { my ($ctx) = @_; return $ctx->stash('subCatIsLast'); } ########################################################################### =head2 SubCatsRecurse Recursively call the L or L container with the subcategories of the category in context. This tag, when placed at the end of loop controlled by one of the tags above will cause them to recursively descend into any subcategories that exist during the loop. B =over 4 =item * max_depth (optional) An optional attribute that specifies the maximum number of times the system should recurse. The default is infinite depth. =back B The following code prints out a recursive list of categories/subcategories, linking those with entries assigned to their category archive pages.
  • <$mt:CategoryLabel$> <$mt:SubCatsRecurse$>
Or more simply: <$mt:CategoryLabel$> <$mt:SubCatsRecurse$> =for tags categories =cut sub _hdlr_sub_cats_recurse { my ($ctx, $args) = @_; my $class_type = $args->{class_type} || 'category'; my $class = MT->model($class_type); my $entry_class = MT->model( $class_type eq 'category' ? 'entry' : 'page'); # Make sure were in the right context # mostly to see if we have anything to actually build my $tokens = $ctx->stash('subCatTokens') or return $ctx->error( MT->translate("[_1] used outside of [_2]", $class_type eq 'category' ? (qw(MTSubCatRecurse MTSubCategories)) : (qw(MTSubFolderRecurse MTSubFolders)) ) ); my $builder = $ctx->stash('builder'); my $cat = $ctx->stash('category'); # Get the depth info my $max_depth = $args->{max_depth}; my $depth = $ctx->stash('subCatsDepth') || 0; # Get the sorting info my $sort_method = $ctx->stash('subCatsSortMethod'); my $sort_order = $ctx->stash('subCatsSortOrder'); # If we're too deep, return an emtry string because we're done return '' if ($max_depth && $depth >= $max_depth); my $cats = _sort_cats($ctx, $sort_method, $sort_order, [ $cat->children_categories ]) or return $ctx->error($ctx->errstr); # Init variables my $count = 0; my $res = ''; # Loop through each immediate child, incrementing the depth by 1 while (my $c = shift @$cats) { next if (!defined $c); local $ctx->{__stash}{'category'} = $c; local $ctx->{__stash}{'subCatIsFirst'} = !$count; local $ctx->{__stash}{'subCatIsLast'} = !scalar @$cats; local $ctx->{__stash}{'subCatsDepth'} = $depth + 1; local $ctx->{__stash}{vars}->{'__depth__'} = $depth + 1; local $ctx->{__stash}{'folder_header'} = !$count if $class_type ne 'category'; local $ctx->{__stash}{'folder_footer'} = !scalar @$cats if $class_type ne 'category'; local $ctx->{__stash}{'category_count'}; local $ctx->{__stash}{'entries'} = delay(sub { my @args = ({ blog_id => $ctx->stash('blog_id'), status => MT::Entry::RELEASE() }, { 'join' => [ 'MT::Placement', 'entry_id', { category_id => $c->id } ], 'sort' => 'authored_on', direction => 'descend', }); my @entries = $entry_class->load(@args); \@entries }); defined (my $out = $builder->build($ctx, $tokens)) or return $ctx->error($ctx->errstr); $res .= $out; $count++; } $res; } ########################################################################### =head2 TopLevelCategories A block tag listing the categories that do not have a parent and exist at "the top" of the category hierarchy. Same as using Cmt:SubCategories top="1"E>. =for tags categories =cut sub _hdlr_top_level_categories { my ($ctx, $args, $cond) = @_; # Unset the normaly hiding places for categories so # MTSubCategories doesn't pick them up local $ctx->{__stash}{'category'} = undef; local $ctx->{__stash}{'archive_category'} = undef; local $args->{top} = 1; # Call MTSubCategories &_hdlr_sub_categories; } ########################################################################### =head2 SubCategories A specialized version of the L container tag that respects the hierarchical structure of categories. B =over 4 =item * include_current An optional boolean attribute that controls the inclusion of the current category in the list. =item * sort_method An optional and advanced usage attribute. A fully qualified Perl method name to be used to sort the categories. =item * sort_order Specifies the sort order of the category labels. Recognized values are "ascend" and "descend." The default is "ascend." This attribute is ignored if sort_method has been set. =item * top If set to 1, displays only top level categories. Same as using L. =item * category Specifies a specific category by name for which to return subcategories. If subcategory label contains a slash (such as if the category was "Mickey/Minnie Mouse"), this will be recognized as the divider specifying a category and subcategory. To avoid this issue surround the value of the category attribute with square brackets: category="[Mickey/Minnie Mouse]" =item * glue =back =for tags categories =cut sub _hdlr_sub_categories { my ($ctx, $args, $cond) = @_; my $class_type = $args->{class_type} || 'category'; my $class = MT->model($class_type); my $entry_class = MT->model( $class_type eq 'category' ? 'entry' : 'page'); my $builder = $ctx->stash('builder'); my $tokens = $ctx->stash('tokens'); # Do we want the current category? my $include_current = $args->{include_current}; # Sorting information # sort_order ::= 'ascend' | 'descend' # sort_method ::= method name (e.g. package::method) # # sort_method takes precedence my $sort_order = $args->{sort_order} || 'ascend'; my $sort_method = $args->{sort_method}; # Store the tokens for recursion $ctx->stash('subCatTokens', $tokens); my $current_cat; my @cats; if ($args->{top}) { @cats = $class->top_level_categories($ctx->stash('blog_id')); } else { # Use explicit category or category context if ($args->{category}) { # user specified category; list from this category down ($current_cat) = cat_path_to_category($args->{category}, $ctx->stash('blog_id'), $class_type); } else { $current_cat = $ctx->stash('category') || $ctx->stash('archive_category'); } if ($current_cat) { if ($include_current) { # If we're to include it, just use it to seed the category list @cats = ($current_cat); } else { # Otherwise, use its children @cats = $current_cat->children_categories; } } } return '' unless @cats; my $cats = _sort_cats($ctx, $sort_method, $sort_order, \@cats) or return $ctx->error($ctx->errstr); # Init variables my $count = 0; my $res = ''; # Be sure the regular MT tags know we're in a category context local $ctx->{inside_mt_categories} = 1; local $ctx->{__stash}{'subCatsSortOrder'} = $sort_order; local $ctx->{__stash}{'subCatsSortMethod'} = $sort_method; # Loop through the immediate children (or the current cat, # depending on the arguments while (my $cat = shift @$cats) { next if (!defined $cat); local $ctx->{__stash}{'category'} = $cat; local $ctx->{__stash}{'subCatIsFirst'} = !$count; local $ctx->{__stash}{'subCatIsLast'} = !scalar @$cats; local $ctx->{__stash}{'folder_header'} = !$count if $class_type ne 'category'; local $ctx->{__stash}{'folder_footer'} = !scalar @$cats if $class_type ne 'category'; local $ctx->{__stash}{'category_count'}; local $ctx->{__stash}{'entries'} = delay(sub { my @args = ({ blog_id => $ctx->stash('blog_id'), status => MT::Entry::RELEASE() }, { 'join' => [ 'MT::Placement', 'entry_id', { category_id => $cat->id } ], 'sort' => 'authored_on', direction => 'descend' }); my @entries = $entry_class->load(@args); \@entries } ); defined (my $out = $builder->build($ctx, $tokens, $cond)) or return $ctx->error($ctx->errstr); $res .= $out; $count++; } $res; } ########################################################################### =head2 ParentCategory A container tag that creates a context to the current category's parent. B Up: =for tags categories =cut sub _hdlr_parent_category { my ($ctx, $args, $cond) = @_; my $builder = $ctx->stash('builder'); my $tokens = $ctx->stash('tokens'); # Get the current category my $cat = _get_category_context($ctx); return if !defined($cat) && defined($ctx->errstr); return '' if ($cat eq ''); # The category must have a parent, otherwise return empty string my $parent = $cat->parent_category or return ''; # Setup the context and let 'er rip local $ctx->{__stash}->{category} = $parent; defined (my $out = $builder->build($ctx, $tokens, $cond)) or return $ctx->error($ctx->errstr); $out; } ########################################################################### =head2 ParentCategories A block tag that lists all the ancestors of the current category. B =over 4 =item * glue This optional attribute is a shortcut for connecting each category label with its value. Single and double quotes are not permitted in the string. =item * exclude_current This optional boolean attribute controls the exclusion of the current category in the list. =back =for tags categories =cut sub _hdlr_parent_categories { my ($ctx, $args, $cond) = @_; my $builder = $ctx->stash('builder'); my $tokens = $ctx->stash('tokens'); # Get the arguments my $exclude_current = $args->{'exclude_current'}; my $glue = $args->{'glue'}; # Get the current category defined (my $cat = _get_category_context($ctx)) or return $ctx->error($ctx->errstr); return '' if ($cat eq ''); my $res = ''; # Put together the list of parent categories # including the current one unless instructed otherwise my @cats = $cat->parent_categories; @cats = ($cat, @cats) unless ($exclude_current); # Start from the top and work our way down while (my $c = pop @cats) { local $ctx->{__stash}->{category} = $c; defined (my $out = $builder->build($ctx, $tokens, $cond)) or return $ctx->error($ctx->errstr); if ($args->{sub_cats_path_hack} && $out !~ /\w/) { $out = 'cat-' . $c->id; } $res .= $glue if defined $glue && length($res) && length($out); $res .= $out if length($out); } $res; } ########################################################################### =head2 TopLevelParent A container tag that creates a context to the top-level ancestor of the current category. =for tags categories =cut sub _hdlr_top_level_parent { my ($ctx, $args, $cond) = @_; my $builder = $ctx->stash('builder'); my $tokens = $ctx->stash('tokens'); # Get the current category defined (my $cat = _get_category_context($ctx)) or return $ctx->error($ctx->errstr); return '' if ($cat eq ''); my $out = ""; # Get the list of parents my @parents = ($cat, $cat->parent_categories); # If there are any # Pop the top one of the list if (scalar @parents) { $cat = pop @parents; local $ctx->{__stash}->{category} = $cat; defined($out = $builder->build($ctx, $tokens, $cond)) or return $ctx->error($ctx->errstr); } $out; } sub cat_path_to_category { my ($path, $blog_id, $class_type) = @_; my $class = MT->model($class_type); # The argument version always takes precedence # followed by the current category (i.e. MTCategories/MTSubCategories style) # then the current category for the archive # then undef my @cat_path = $path =~ m@(\[[^]]+?\]|[^]/]+)@g; # split on slashes, fields quoted by [] @cat_path = map { $_ =~ s/^\[(.*)\]$/$1/; $_ } @cat_path; # remove any [] my $last_cat_id = 0; my $cat; my (%blog_terms, %blog_args); if (ref $blog_id eq 'ARRAY') { %blog_terms = %{$blog_id->[0]}; %blog_args = %{$blog_id->[1]}; } elsif ($blog_id) { $blog_terms{blog_id} = $blog_id; } my $top = shift @cat_path; my @cats = $class->load({ label => $top, parent => 0, %blog_terms }, \%blog_args); if (@cats) { for my $label (@cat_path) { my @parents = map { $_->id } @cats; @cats = $class->load({ label => $label, parent => \@parents, %blog_terms }, \%blog_args) or last; } } if (!@cats && $path) { @cats = ($class->load({ label => $path, %blog_terms, }, \%blog_args)); } @cats; } ########################################################################### =head2 EntriesWithSubCategories A specialized version of L that is aware of subcategories. The difference between the two tags is the behavior of the category attribute. B =over 4 =item * category The value of this attribute is a category label. This will include any entries to that category and any of its subcategories. Since it is possible for two categories to have the same label, you can specify one particular category by including its ancestors, separated by slashes. For instance if you have a category "Flies" and within it a subcategory labeled "Fruit", you can ask for that category with category="Flies/Fruit". This would distinguish it from a category labeled "Fruit" within another called "Food Groups", for example, which could be identified using category="Food Groups/Fruit". If any category in the ancestor chain has a slash in its label, the label must be quoted using square brackets: category="Beverages/[Coffee/Tea]" identifies a category labeled Coffee/Tea within a category labeled Beverages. =back You can also use any of the other attributes available to L; and they should behave just as they do with the original tag. =for tags entries, categories =cut sub _hdlr_entries_with_sub_categories { my ($ctx, $args, $cond) = @_; my $cat = $ctx->stash('category') || $ctx->stash('archive_category'); my $save_entries = defined $ctx->stash('archive_category'); my $saved_stash_entries; if (defined $cat) { $saved_stash_entries = $ctx->{__stash}{entries} if $save_entries; delete $ctx->{__stash}{entries}; } local $args->{include_subcategories} = 1; local $args->{category} ||= ['OR', [$cat]] if defined $cat; my $res = _hdlr_entries($ctx, $args, $cond); $ctx->{__stash}{entries} = $saved_stash_entries if $save_entries && $saved_stash_entries; $res; } ########################################################################### =head2 SubCategoryPath The path to the category relative to L. In other words, this tag returns a string that is a concatenation of the current category and its ancestors. This tag is provided for convenience and is the equivalent of the following template tags: B =over 4 =item * separator Valid values are "_" and "-", dash is the default value. Specifying an underscore will convert any dashes to underscores. Specifying a dash will convert any underscores to dashes. =back B The category "Bar" in a category "Foo" C$mt:SubCategoryPath$E> becomes C. =for tags categories =cut sub _hdlr_sub_category_path { my ($ctx, $args, $cond) = @_; my $builder = $ctx->stash('builder'); my $dir = ''; if ($args->{separator}) { $dir = "separator='$args->{separator}'"; } my $tokens = $builder->compile($ctx, ""); # unfortunately, there's no way to apply a filter that would # take the output of the dirify step and, if it were blank, # use instead some other property of the category (such as the # category ID). this hack tells parent_categories to do that # to the output of its contents. $args->{'sub_cats_path_hack'} = 1; $args->{'glue'} = '/'; local $ctx->{__stash}->{tokens} = $tokens; &_hdlr_parent_categories; } ########################################################################### =head2 HasSubCategories Returns true if the current category has a sub-category. =for tags categories =cut sub _hdlr_has_sub_categories { my ($ctx, $args, $cond) = @_; # Get the current category context defined (my $cat = _get_category_context($ctx)) or return $ctx->error($ctx->errstr); return if ($cat eq ''); # Return the number of children for the category my @children = $cat->children_categories; scalar @children; } ########################################################################### =head2 HasNoSubCategories Returns true if the current category has no sub-categories. =for tags categories =cut sub _hdlr_has_no_sub_categories { !&_hdlr_has_sub_categories; } ########################################################################### =head2 HasParentCategory Returns true if the current category has a parent category other than the root. =for tags categories =cut sub _hdlr_has_parent_category { my ($ctx, $args) = @_; # Get the current category defined (my $cat = _get_category_context($ctx)) or return $ctx->error($ctx->errstr); return 0 if ($cat eq ''); # Return the parent of the category return $cat->parent_category ? 1 : 0; } ########################################################################### =head2 HasNoParentCategory Returns true if the current category does not have a parent category other than the root. B is but an orphan and has no parents. has a parent category! =for tags categories =cut sub _hdlr_has_no_parent_category { return !&_hdlr_has_parent_category; } ########################################################################### =head2 IfIsAncestor Conditional tag that is true when the category in context is an ancestor category of the specified 'child' attribute. B =over 4 =item * child (required) The label of a category in the current blog. =back B (category in context is a parent category to a subcategory named "Featured".) =for tags categories =cut sub _hdlr_is_ancestor { my ($ctx, $args) = @_; # Get the current category defined (my $cat = _get_category_context($ctx)) or return $ctx->error($ctx->errstr); return if ($cat eq ''); # Get the possible child category my $blog_id = $ctx->stash('blog_id'); my $iter = MT::Category->load_iter({ blog_id => $blog_id, label => $args->{'child'} }) || undef; while (my $child = $iter->()) { if ($cat->is_ancestor($child)) { $iter->end; return 1; } } 0; } ########################################################################### =head2 IfIsDescendant Conditional tag that is true when the category in context is a child category of the specified 'parent' attribute. B =over 4 =item * parent (required) The label of a category in the current blog. =back B (category in context is a child category to the 'Featured' category.) =for tags categories =cut sub _hdlr_is_descendant { my ($ctx, $args) = @_; # Get the current category defined (my $cat = _get_category_context($ctx)) or return $ctx->error($ctx->errstr); return if ($cat eq ''); # Get the possible parent category my $blog_id = $ctx->stash('blog_id'); my $iter = MT::Category->load_iter({ blog_id => $blog_id, label => $args->{'parent'} }); while (my $parent = $iter->()) { if ($cat->is_descendant($parent)) { $iter->end; return 1; } } 0; } sub _get_category_context { my ($ctx) = @_; my $tag = $ctx->stash('tag'); # Get our hands on the category for the current context # Either in MTCategories, a Category Archive Template # Or the category for the current entry my $cat = $ctx->stash('category') || $ctx->stash('archive_category'); if (!defined $cat) { # No category found so far, test the entry if ($ctx->stash('entry')) { $cat = $ctx->stash('entry')->category; # Return empty string if entry has no category # as the tag has been used in the correct context # but there is no category to work with return '' if (!defined $cat); } else { return $ctx->error(MT->translate("MT[_1] must be used in a [_2] context", $tag, $tag =~ m/folder/ig ? 'folder' : 'category')); } } return $cat; } sub _sort_cats { my ($ctx, $sort_method, $sort_order, $cats) = @_; my $tag = $ctx->stash('tag'); # If sort_method is defined if (defined $sort_method) { my $package = $sort_method; # Check if it has a package name if ($package =~ /::/) { # Extract the package name $package =~ s/::[^(::)]+$//; # Make sure it's loaded eval (qq(use $package;)); if (my $err = $@) { return $ctx->error(MT->translate("Cannot find package [_1]: [_2]", $package, $err)); } } # Sort the categories based on sort_method eval ("\@\$cats = sort $sort_method \@\$cats"); if (my $err = $@) { return $ctx->error(MT->translate("Error sorting [_2]: [_1]", $err,$tag =~ m/folder/ig ? 'folders' : 'categories')); } } else { if (lc $sort_order eq 'descend') { @$cats = sort { $b->label cmp $a->label } @$cats; } else { @$cats = sort { $a->label cmp $b->label } @$cats; } } return $cats; } ########################################################################### =head2 EntryBlogID The numeric system ID of the blog that is parent to the entry currently in context. B <$mt:EntryBlogID$> =for tags entries, blogs =cut sub _hdlr_entry_blog_id { my ($ctx, $args) = @_; my $e = $ctx->stash('entry') or return $ctx->_no_entry_error(); return $args && $args->{pad} ? (sprintf "%06d", $e->blog_id) : $e->blog_id; } ########################################################################### =head2 EntryBlogName Returns the blog name of the blog to which the entry in context belongs. The blog name is set in the General Blog Settings. B <$mt:EntryBlogName$> =for tags entries, blogs =cut sub _hdlr_entry_blog_name { my ($ctx, $args) = @_; my $e = $ctx->stash('entry') or return $ctx->_no_entry_error(); my $b = MT::Blog->load($e->blog_id) or return $ctx->error(MT->translate('Can\'t load blog #[_1].', $e->blog_id)); return $b->name; } ########################################################################### =head2 EntryBlogDescription Returns the blog description of the blog to which the entry in context belongs. The blog description is set in the General Blog Settings. B <$mt:EntryBlogDescription$> =for tags blogs, entries =cut sub _hdlr_entry_blog_description { my ($ctx, $args) = @_; my $e = $ctx->stash('entry') or return $ctx->_no_entry_error(); my $b = MT::Blog->load($e->blog_id) or return $ctx->error(MT->translate('Can\'t load blog #[_1].', $e->blog_id)); my $d = $b->description; return defined $d ? $d : ''; } ########################################################################### =head2 EntryBlogURL Returns the blog URL for the blog to which the entry in context belongs. B <$mt:EntryBlogURL$> =for tags blogs, entries =cut sub _hdlr_entry_blog_url { my ($ctx, $args) = @_; my $e = $ctx->stash('entry') or return $ctx->_no_entry_error(); my $b = MT::Blog->load($e->blog_id) or return $ctx->error(MT->translate('Can\'t load blog #[_1].', $e->blog_id)); return $b->site_url; } ########################################################################### =head2 EntryEditLink A link to edit the entry in context from the Movable Type CMS. This tag is only recognized in system templates where an authenticated user is logged-in. B =over 4 =item * text (optional; default "Edit") A phrase to use for the edit link. =back B <$mt:EntryEditLink$> =for tags search =cut sub _hdlr_entry_edit_link { my($ctx, $args) = @_; my $user = $ctx->stash('user') or return ''; my $entry = $ctx->stash('entry') or return $ctx->error(MT->translate( 'You used an [_1] tag outside of the proper context.', '<$MTEntryEditLink$>' )); my $blog_id = $entry->blog_id; my $cfg = MT->config; my $url = $cfg->AdminCGIPath || $cfg->CGIPath; $url .= '/' unless $url =~ m!/$!; require MT::Permission; my $perms = MT::Permission->load({ author_id => $user->id, blog_id => $blog_id }); return '' unless $perms && $perms->can_edit_entry($entry, $user); my $app = MT->instance; my $edit_text = $args->{text} || $app->translate("Edit"); return sprintf q([%s]), $url, $cfg->AdminScript, $app->uri_params('mode' => 'view', args => {'_type' => $entry->class, id => $entry->id, blog_id => $blog_id}), $edit_text; } ########################################################################### =head2 HTTPContentType When this tag is used in a dynamically published index template, the value specified to the type attribute will be returned in the Content-Type HTTP header sent to web browser. This content is never displayed directly to the user but is instead used to signal to the browser the data type of the response. When this tag is used in a system template, such as the search results template, mt-search.cgi will use the value specified to "type" attribute and returns it in Content-Type HTTP header to web browser. When this tag is used in statically published template, this template tag outputs nothing. B =over 4 =item * type A valid HTTP Content-Type value, for example 'application/xml'. Note that you must not specify charset portion of Content-Type header value in this attribute. MT will set the portion automatically by using PublishCharset configuration directive. =back B <$mt:HTTPContentType type="application/xml"$> =cut sub _hdlr_http_content_type { my($ctx, $args) = @_; my $type = $args->{type}; $ctx->stash('content_type', $type); return qq{}; } ########################################################################### =head2 Assets A container tag which iterates over a list of assets. B =over 4 =item * type Specifies a particular type(s) of asset to select. This may be one of image, audio, video, or file(a generic for unrecognized file types). If unspecified, will select all asset types. Supports a comma-delimited list. =item * file_ext Specifies a particular file extension to select. For instance, gif, mp3, pdf, etc. Supports a comma-delimited list. =item * days Selects assets created in the last number of days specified. =item * author Selects assets uploaded by a particular author (where the author's username is given). =item * lastn Limits the selection of assets to the specified number. =item * limit a positive integer to limit results. =item * offset Used in coordination with lastn, starts N assets from the start of the list. N is a positive integer. =item * tag Selects assets with particular tags (supports expressions such as "interesting AND featured"). =item * sort_by Supported values: file_name, created_by, created_on, score. =item * sort_order Supported values: ascend, descend. =item * namespace Used in conjunction with the sort_by attribute when sorting in "score" order. The namespace identifies the method of scoring to use in sorting assets. =item * assets_per_row When publishing a grid of thumbnails, this attribute sets how many iterations the Assets tag publishes before setting the state that enables the L tag. Supported values: numbers between 1 and 100. Also supported is the keyword "auto" which selects the most aesthetically pleasing number of items per row based on the total number of assets. For example, if you had 18 total assets, three rows of six would publish, but for 16 assets, four rows of four would publish. =back B " alt="" title="" /> =for tags multiblog, assets =cut sub _hdlr_assets { my($ctx, $args, $cond) = @_; return $ctx->error(MT->translate('sort_by="score" must be used in combination with namespace.')) if ((exists $args->{sort_by}) && ('score' eq $args->{sort_by}) && (!exists $args->{namespace})); my $class_type = $args->{class_type} || 'asset'; my $class = MT->model($class_type); my $assets; my $tag = lc $ctx->stash('tag'); if ($tag eq 'entryassets' || $tag eq 'pageassets') { my $e = $ctx->stash('entry') or return $ctx->_no_entry_error(); require MT::ObjectAsset; my @assets = MT::Asset->load({ class => '*' }, { join => MT::ObjectAsset->join_on(undef, { asset_id => \'= asset_id', object_ds => 'entry', object_id => $e->id })}); return '' unless @assets; $assets = \@assets; } else { $assets = $ctx->stash('assets'); } local $ctx->{__stash}{assets}; my (@filters, %blog_terms, %blog_args, %terms, %args); my $blog_id = $ctx->stash('blog_id'); $ctx->set_blog_load_context($args, \%blog_terms, \%blog_args) or return $ctx->error($ctx->errstr); %terms = %blog_terms; %args = %blog_args; # Adds parent filter (skips any generated files such as thumbnails) $args{null}{parent} = 1; $terms{parent} = \'is null'; # Adds an author filter to the filters list. if (my $author_name = $args->{author}) { require MT::Author; my $author = MT::Author->load({ name => $author_name }) or return $ctx->error(MT->translate( "No such user '[_1]'", $author_name )); if ($assets) { push @filters, sub { $_[0]->created_by == $author->id }; } else { $terms{created_by} = $author->id; } } # Added a type filter to the filters list. if (my $type = $args->{type}) { my @types = split(',', $args->{type}); if ($assets) { push @filters, sub { my $a = $_[0]->class; grep(m/$a/, @types) }; } else { $terms{class} = \@types; } } else { $terms{class} = '*'; } # Added a file_ext filter to the filters list. if (my $ext = $args->{file_ext}) { my @exts = split(',', $args->{file_ext}); if ($assets) { push @filters, sub { my $a = $_[0]->file_ext; grep(m/$a/, @exts) }; } else { $terms{file_ext} = \@exts; } } # Adds a tag filter to the filters list. if (my $tag_arg = $args->{tags} || $args->{tag}) { require MT::Tag; require MT::ObjectTag; my $terms; if ($tag_arg !~ m/\b(AND|OR|NOT)\b|\(|\)/i) { my @tags = MT::Tag->split(',', $tag_arg); $terms = { name => \@tags }; $tag_arg = join " or ", @tags; } my $tags = [ MT::Tag->load($terms, { binary => { name => 1 }, join => ['MT::ObjectTag', 'tag_id', { blog_id => $blog_id, object_datasource => MT::Asset->datasource }] }) ]; my $cexpr = $ctx->compile_tag_filter($tag_arg, $tags); if ($cexpr) { my @tag_ids = map { $_->id, ( $_->n8d_id ? ( $_->n8d_id ) : () ) } @$tags; my $preloader = sub { my ($entry_id) = @_; my $terms = { tag_id => \@tag_ids, object_id => $entry_id, object_datasource => $class->datasource, %blog_terms, }; my $args = { %blog_args, fetchonly => ['tag_id'], no_triggers => 1, }; my @ot_ids = MT::ObjectTag->load( $terms, $args ) if @tag_ids; my %map; $map{ $_->tag_id } = 1 for @ot_ids; \%map; }; push @filters, sub { $cexpr->( $preloader->( $_[0]->id ) ) }; } else { return $ctx->error(MT->translate("You have an error in your '[_2]' attribute: [_1]", $args->{tags} || $args->{tag}, 'tag')); } } if ($args->{namespace}) { my $namespace = $args->{namespace}; my $need_join = 0; for my $f qw( min_score max_score min_rate max_rate min_count max_count scored_by ) { if ($args->{$f}) { $need_join = 1; last; } } if ($need_join) { my $scored_by = $args->{scored_by} || undef; if ($scored_by) { require MT::Author; my $author = MT::Author->load({ name => $scored_by }) or return $ctx->error(MT->translate( "No such user '[_1]'", $scored_by )); $scored_by = $author; } $args{join} = MT->model('objectscore')->join_on(undef, { object_id => \'=asset_id', object_ds => 'asset', namespace => $namespace, (!$assets && $scored_by ? (author_id => $scored_by->id) : ()), }, { unique => 1, }); if ($assets && $scored_by) { push @filters, sub { $_[0]->get_score($namespace, $scored_by) }; } } # Adds a rate or score filter to the filter list. if ($args->{min_score}) { push @filters, sub { $_[0]->score_for($namespace) >= $args->{min_score}; }; } if ($args->{max_score}) { push @filters, sub { $_[0]->score_for($namespace) <= $args->{max_score}; }; } if ($args->{min_rate}) { push @filters, sub { $_[0]->score_avg($namespace) >= $args->{min_rate}; }; } if ($args->{max_rate}) { push @filters, sub { $_[0]->score_avg($namespace) <= $args->{max_rate}; }; } if ($args->{min_count}) { push @filters, sub { $_[0]->vote_for($namespace) >= $args->{min_count}; }; } if ($args->{max_count}) { push @filters, sub { $_[0]->vote_for($namespace) <= $args->{max_count}; }; } } my $no_resort = 0; require MT::Asset; my @assets; if (!$assets) { my ($start, $end) = ($ctx->{current_timestamp}, $ctx->{current_timestamp_end}); if ($start && $end) { $terms{created_on} = [$start, $end]; $args{range_incl}{created_on} = 1; } if (my $days = $args->{days}) { my @ago = offset_time_list(time - 3600 * 24 * $days, $ctx->stash('blog_id')); my $ago = sprintf "%04d%02d%02d%02d%02d%02d", $ago[5]+1900, $ago[4]+1, @ago[3,2,1,0]; $terms{created_on} = [ $ago ]; $args{range_incl}{created_on} = 1; } $args{'sort'} = 'created_on'; if ($args->{sort_by}) { if (MT::Asset->has_column($args->{sort_by})) { $args{sort} = $args->{sort_by}; $no_resort = 1; } elsif ('score' eq $args->{sort_by} || 'rate' eq $args->{sort_by}) { $no_resort = 0; } } if (!@filters) { if (my $last = $args->{lastn}) { $args{'sort'} = 'created_on'; $args{direction} = 'descend'; $args{limit} = $last; $no_resort = 0 if $args->{sort_by}; } else { $args{direction} = $args->{sort_order} || 'descend' if exists($args{sort}); $no_resort = 1 unless $args->{sort_by}; $args{limit} = $args->{limit} if $args->{limit}; } $args{offset} = $args->{offset} if $args->{offset}; @assets = MT::Asset->load(\%terms, \%args); } else { if ($args->{lastn}) { $args{direction} = 'descend'; $args{sort} = 'created_on'; $no_resort = 0 if $args->{sort_by}; } else { $args{direction} = $args->{sort_order} || 'descend'; $no_resort = 1 unless $args->{sort_by}; $args->{lastn} = $args->{limit} if $args->{limit}; } my $iter = MT::Asset->load_iter(\%terms, \%args); my $i = 0; my $j = 0; my $off = $args->{offset} || 0; my $n = $args->{lastn}; ASSET: while (my $e = $iter->()) { for (@filters) { next ASSET unless $_->($e); } next if $off && $j++ < $off; push @assets, $e; $i++; last if $n && $i >= $n; } } } else { my $blog = $ctx->stash('blog'); my $so = lc($args->{sort_order}) || ($blog ? $blog->sort_order_posts : undef) || ''; my $col = lc ($args->{sort_by} || 'created_on'); # TBD: check column being sorted; if it is numeric, use numeric sort @$assets = $so eq 'ascend' ? sort { $a->$col() cmp $b->$col() } @$assets : sort { $b->$col() cmp $a->$col() } @$assets; $no_resort = 1; if (@filters) { my $i = 0; my $j = 0; my $off = $args->{offset} || 0; my $n = $args->{lastn} || $args->{limit}; ASSET2: foreach my $e (@$assets) { for (@filters) { next ASSET2 unless $_->($e); } next if $off && $j++ < $off; push @assets, $e; $i++; last if $n && $i >= $n; } } else { my $offset; if ($offset = $args->{offset}) { if ($offset < scalar @$assets) { @assets = @$assets[$offset..$#$assets]; } else { @assets = (); } } else { @assets = @$assets; } if (my $last = $args->{lastn} || $args->{limit}) { if (scalar @assets > $last) { @assets = @assets[0..$last-1]; } } } } unless ($no_resort) { my $so = lc($args->{sort_order} || ''); my $col = lc($args->{sort_by} || 'created_on'); if ('score' eq $col) { my $namespace = $args->{namespace}; my $so = $args->{sort_order} || ''; my %a = map { $_->id => $_ } @assets; require MT::ObjectScore; my $scores = MT::ObjectScore->sum_group_by( { 'object_ds' => 'asset', 'namespace' => $namespace }, { 'sum' => 'score', group => ['object_id'], $so eq 'ascend' ? (direction => 'ascend') : (direction => 'descend'), }); my @tmp; while (my ($score, $object_id) = $scores->()) { push @tmp, delete $a{ $object_id } if exists $a{ $object_id }; } if ($so eq 'ascend') { unshift @tmp, $_ foreach (values %a); } else { push @tmp, $_ foreach (values %a); } @assets = @tmp; } elsif ('rate' eq $col) { my $namespace = $args->{namespace}; my $so = $args->{sort_order} || ''; my %a = map { $_->id => $_ } @assets; require MT::ObjectScore; my $scores = MT::ObjectScore->avg_group_by( { 'object_ds' => 'asset', 'namespace' => $namespace }, { 'avg' => 'score', group => ['object_id'], $so eq 'ascend' ? (direction => 'ascend') : (direction => 'descend'), }); my @tmp; while (my ($score, $object_id) = $scores->()) { push @tmp, delete $a{ $object_id } if exists $a{ $object_id }; } if ($so eq 'ascend') { unshift @tmp, $_ foreach (values %a); } else { push @tmp, $_ foreach (values %a); } @assets = @tmp; } else { # TBD: check column being sorted; if it is numeric, use numeric sort @assets = $so eq 'ascend' ? sort { $a->$col() cmp $b->$col() } @assets : sort { $b->$col() cmp $a->$col() } @assets; } } my $res = ''; my $tok = $ctx->stash('tokens'); my $builder = $ctx->stash('builder'); my $per_row = $args->{assets_per_row} || 0; $per_row -= 1 if $per_row; my $row_count = 0; my $i = 0; my $total_count = @assets; my $vars = $ctx->{__stash}{vars} ||= {}; for my $a (@assets) { local $ctx->{__stash}{asset} = $a; local $vars->{__first__} = !$i; local $vars->{__last__} = !defined $assets[$i+1]; local $vars->{__odd__} = ($i % 2) == 0; # 0-based $i local $vars->{__even__} = ($i % 2) == 1; local $vars->{__counter__} = $i+1; my $f = $row_count == 0; my $l = $row_count == $per_row; $l = 1 if (($i + 1) == $total_count); my $out = $builder->build($ctx, $tok, { %$cond, AssetIsFirstInRow => $f, AssetIsLastInRow => $l, AssetsHeader => !$i, AssetsFooter => !defined $assets[$i+1], }); $res .= $out; $row_count++; $row_count = 0 if $row_count > $per_row; $i++; } $res; } ########################################################################### =head2 EntryAssets A container tag which iterates over a list of assets for the current entry in context. Supports all the attributes provided by the L tag. =for tags entries, assets =cut ########################################################################### =head2 PageAssets A container tag which iterates over a list of assets for the current page in context. Supports all the attributes provided by the L tag. =for tags pages, assets =cut ########################################################################### =head2 AssetsHeader The contents of this container tag will be displayed when the first entry listed by a L tag is reached. =for tags assets =cut ########################################################################### =head2 AssetsFooter The contents of this container tag will be displayed when the last entry listed by a L tag is reached. =for tags assets =cut ########################################################################### =head2 AssetIsFirstInRow A conditional tag that displays its contents if the asset in context is the first item in the row in context when publishing a grid of assets (e.g. thumbnails). Grid of assets can be created by specifying assets_per_row attribute value to L block tag. For example, the first, the fourth and the seventh asset are the first assets in row when L iterates eight assets and assets_per_row is set to "3". B
<$mt:AssetThumbnailLink$>
=cut ########################################################################### =head2 AssetIsLastInRow A conditional tag that displays its contents if the asset in context is the last item in the row in context when publishing a grid of assets (e.g. thumbnails). Grid of assets can be created by specifying assets_per_row attribute value to L block tag. For example, the third, the sixth and the eighth asset are the last assets in row when L iterates eight assets and assets_per_row is set to "3". B
<$mt:AssetThumbnailLink$>
=for tags assets =cut ########################################################################### =head2 Asset Container tag that provides an asset context for a specific asset, from which all asset variable tags can be used to retreive metadata about that asset. B =over 4 =item * id The unique numeric id of the asset. =back B <$mt:AssetLabel$> =for tags assets =cut sub _hdlr_asset { my ($ctx, $args, $cond) = @_; my $assets = $ctx->stash('assets'); local $ctx->{__stash}{assets}; return '' if !defined $args->{id}; return '' if $assets; require MT::Asset; my $out = ''; my $asset = MT::Asset->load({ id => $args->{id} }); if ($asset) { my $tok = $ctx->stash('tokens'); my $builder = $ctx->stash('builder'); local $ctx->{__stash}{asset} = $asset; $out = $builder->build($ctx, $tok, { %$cond, }); } $out; } ########################################################################### =head2 AssetTags A container tag used to output infomation about the asset tags assigned to the asset in context. This tag's functionality is analogous to that of L and its attributes are identical. To avoid printing out the leading text when no asset tags are assigned you can use the L conditional block to first test for tags on the asset. You can also use the L conditional block with the tag attribute to test for the assignment of a particular tag. B =over 4 =item * glue A text string that is used to join each of the items together. For example: <$mt:TagName$> would print out each tag name separated by a comma and a space. =item * include_private A boolean value which controls whether private tags (i.e. tags which start with @) should be output by the block. The default is 0 which suppresses the output of private tags. If set to 1, the tags will be displayed. See L for an example of its usage. =back B The following code can be used anywhere L can be used. It prints a list of all of the tags assigned to each asset returned by L glued together by a comma and a space. The asset "<$mt:AssetLabel$>" is tagged: <$mt:TagName$> =for tags tags, assets =cut sub _hdlr_asset_tags { my ($ctx, $args, $cond) = @_; require MT::ObjectTag; require MT::Asset; my $asset = $ctx->stash('asset'); return '' unless $asset; my $glue = $args->{glue}; local $ctx->{__stash}{tag_max_count} = undef; local $ctx->{__stash}{tag_min_count} = undef; local $ctx->{__stash}{all_tag_count} = undef; local $ctx->{__stash}{class_type} = 'asset'; my $iter = MT::Tag->load_iter(undef, { 'sort' => 'name', 'join' => MT::ObjectTag->join_on('tag_id', { object_id => $asset->id, blog_id => $asset->blog_id, object_datasource => MT::Asset->datasource }, { unique => 1 } )}); my $builder = $ctx->stash('builder'); my $tokens = $ctx->stash('tokens'); my $res = ''; while (my $tag = $iter->()) { next if $tag->is_private && !$args->{include_private}; local $ctx->{__stash}{Tag} = $tag; local $ctx->{__stash}{tag_count} = undef; local $ctx->{__stash}{tag_asset_count} = undef; defined(my $out = $builder->build($ctx, $tokens, $cond)) or return $ctx->error( $builder->errstr ); $res .= $glue if defined $glue && length($res) && length($out); $res .= $out; } return $res; } ########################################################################### =head2 AssetID A numeric system ID of the Asset currently in context. B <$mt:AssetID$> =for tags assets =cut sub _hdlr_asset_id { my ($ctx, $args) = @_; my $a = $ctx->stash('asset') or return $ctx->_no_asset_error(); return $args && $args->{pad} ? (sprintf "%06d", $a->id) : $a->id; } ########################################################################### =head2 AssetFileName The file name of the asset in context. For file-based assets only. Returns the file name without the path (i.e. file.jpg, not /home/user/public_html/images/file.jpg). B <$mt:AssetFileName$> =for tags assets =cut sub _hdlr_asset_file_name { my ($ctx, $args) = @_; my $a = $ctx->stash('asset') or return $ctx->_no_asset_error(); return $a->file_name; } ########################################################################### =head2 AssetLabel Returns the label of the asset in context. Label can be specified upon uploading a file. B <$mt:AssetLabel$> =for tags assets =cut sub _hdlr_asset_label { my ($ctx, $args) = @_; my $a = $ctx->stash('asset') or return $ctx->_no_asset_error(); return defined $a->label ? $a->label : ''; } ########################################################################### =head2 AssetDescription This tag returns the description text of the asset currently in context. B <$mt:AssetDescription$> =for tags assets =cut sub _hdlr_asset_description { my ($ctx, $args) = @_; my $a = $ctx->stash('asset') or return $ctx->_no_asset_error(); return defined $a->description ? $a->description : ''; } ########################################################################### =head2 AssetURL Produces a permalink for the uploaded asset. B <$mt:AssetURL$> =for tags assets =cut sub _hdlr_asset_url { my ($ctx, $args) = @_; my $a = $ctx->stash('asset') or return $ctx->_no_asset_error(); return $a->url; } ########################################################################### =head2 AssetType Returns the localized name for the type of asset currently in context. For instance, for an image asset, this will tag will output (for English blogs), "image". B <$mt:AssetType$> =for tags assets =cut sub _hdlr_asset_type { my ($ctx) = @_; my $a = $ctx->stash('asset') or return $ctx->_no_asset_error(); return lc $a->class_label; } ########################################################################### =head2 AssetMimeType Returns MIME type of the asset in context. MIME type of a file is typically provided by web browser upon uploading. B <$mt:AssetMimeType$> =for tags assets =cut sub _hdlr_asset_mime_type { my ($ctx) = @_; my $a = $ctx->stash('asset') or return $ctx->_no_asset_error(); return $a->mime_type || ''; } ########################################################################### =head2 AssetFilePath Path information of the asset in context. For file-based assets only. Returns the local file path with the name of the file (i.e. /home/user/public_html/images/file.jpg). B <$mt:AssetFilePath$> =for tags assets =cut sub _hdlr_asset_file_path { my ($ctx) = @_; my $a = $ctx->stash('asset') or return $ctx->_no_asset_error(); return $a->file_path || ''; } ########################################################################### =head2 AssetDateAdded The date the asset in context was added to Movable Type. B =over 4 =item * format A string that provides the format in which to publish the date. If unspecified, the default that is appropriate for the language of the blog is used (for English, this is "%B %e, %Y %l:%M %p"). See the L tag for the supported formats. =item * language Forces the date to the format associated with the specified language. =item * utc Forces the date to UTC format. =back B <$mt:AssetDateAdded$> =for tags date, assets =cut sub _hdlr_asset_date_added { my ($ctx, $args) = @_; my $a = $ctx->stash('asset') or return $ctx->_no_asset_error(); $args->{ts} = $a->created_on; return _hdlr_date($ctx, $args); } ########################################################################### =head2 AssetAddedBy Display name (or username if display name isn't assigned) of the user who added the asset to the system. B <$mt:AssetAddedBy$> =for tags assets =cut sub _hdlr_asset_added_by { my ($ctx) = @_; my $a = $ctx->stash('asset') or return $ctx->_no_asset_error(); require MT::Author; my $author = MT::Author->load($a->created_by); return '' unless $author; return $author->nickname || $author->name; } ########################################################################### =head2 AssetProperty Returns the additional metadata of the asset in context. Some of these properties only make sense for certain file types. For example, image_width and image_height apply only to images. B =over 4 =item * property (required) Specifies what property to return from the tag. B =over 4 =item * file_size asset's file size =item * image_width asset's width (for image only; otherwise returns 0) =item * image_height asset's height (for image only; otherwise returns 0) =item * description asset's description =back =item * format Used in conjunction with file_size property. B =over 4 =item * 0 return raw size =item * 1 (default) auto format depending on the size =item * k size expressed in kilobytes =item * m size expressed in megabytes =back =back =for tags assets =cut sub _hdlr_asset_property { my ($ctx, $args) = @_; my $a = $ctx->stash('asset') or return $ctx->_no_asset_error(); my $prop = $args->{property}; return '' unless $prop; my $class = ref($a); my $ret; if ($prop =~ m/file_size/i) { my @stat = stat($a->file_path); my $size = $stat[7]; my $format = $args->{format}; $format = 1 if !defined $format; if ($format eq '1') { if ($size < 1024) { $ret = sprintf("%d Bytes", $size); } elsif ($size < 1024000) { $ret = sprintf("%.1f KB", $size / 1024); } else { $ret = sprintf("%.1f MB", $size / 1048576); } } elsif ($format =~ m/k/i) { $ret = sprintf("%.1f", $size / 1024); } elsif ($format =~ m/m/i) { $ret = sprintf("%.1f", $size / 1048576); } else { $ret = $size; } } elsif ($prop =~ m/^image_/ && $class->can($prop)) { # These are numbers, so default to 0. $ret = $a->$prop || 0; } elsif ($prop =~ m/^image_/) { $ret = 0; } else { $ret = $a->$prop || ''; } $ret; } ########################################################################### =head2 AssetFileExt The file extension of the asset in context. For file-based assets only. Returns the file extension without the leading period (ie: "jpg"). B <$mt:AssetFileExt$> =for tags assets =cut sub _hdlr_asset_file_ext { my ($ctx) = @_; my $a = $ctx->stash('asset') or return $ctx->_no_asset_error(); return $a->file_ext || ''; } ########################################################################### =head2 AssetThumbnailURL Returns the URL for a thumbnail you wish to generate for the current asset in context. B =over 4 =item * height The height of the thumbnail to generate. If this is the only parameter specified then the thumbnail's width will be scaled proportionally to the height. =item * width The width of the thumbnail to generate. If this is the only parameter specified then the thumbnail's height will be scaled proportionally to the width. =item * scale The percentage by which to reduce or increase the size of the current asset. =item * square If set to 1 (one) then the thumbnail generated will be square, where the length of each side of the square will be equal to the shortest side of the image. =back B The following will output thumbnails for all of the assets embedded in all of the entries on the system. Each thumbnail will be square and have a max height/width of 100 pixels. <$mt:AssetThumbnailURL width="100" square="1"$> =for tags assets =cut sub _hdlr_asset_thumbnail_url { my ($ctx, $args) = @_; my $a = $ctx->stash('asset') or return $ctx->_no_asset_error(); return '' unless $a->has_thumbnail; my %arg; foreach (keys %$args) { $arg{$_} = $args->{$_}; } $arg{Width} = $args->{width} if $args->{width}; $arg{Height} = $args->{height} if $args->{height}; $arg{Scale} = $args->{scale} if $args->{scale}; $arg{Square} = $args->{square} if $args->{square}; my ($url, $w, $h) = $a->thumbnail_url(%arg); return $url || ''; } ########################################################################### =head2 AssetLink Returns HTML anchor tag for the asset in context. For example, if the URL of the asset is C, the tag returns Ca href="http://example.com/image.jpg"Eimage.jpgE/aE>. B =over 4 =item * new_window (optional; default "0") Specifies if the tag generates 'target="_blank"' attribute to the anchor tag. =back B <$mt:AssetLink$> =for tags assets =cut sub _hdlr_asset_link { my ($ctx, $args) = @_; my $a = $ctx->stash('asset') or return $ctx->_no_asset_error(); my $ret = sprintf qq(url; if ($args->{new_window}) { $ret .= qq( target="_blank"); } $ret .= sprintf qq(>%s), $a->file_name; $ret; } ########################################################################### =head2 AssetThumbnailLink Produces a thumbnail image, linked to the image asset currently in context. B =over 4 =item * height The height of the thumbnail to generate. If this is the only parameter specified then the thumbnail's width will be scaled proportionally to the height. =item * width The width of the thumbnail to generate. If this is the only parameter specified then the thumbnail's height will be scaled proportionally to the width. =item * scale The percentage by which to reduce or increase the size of the current asset. =item * new_window (optional; default "0") If set to '1', causes the link to open a new window to the linked asset. =back =for tags assets =cut sub _hdlr_asset_thumbnail_link { my ($ctx, $args) = @_; my $a = $ctx->stash('asset') or return $ctx->_no_asset_error(); my $class = ref($a); return '' unless UNIVERSAL::isa($a, 'MT::Asset::Image'); # # Load MT::Image # require MT::Image; # my $img = new MT::Image(Filename => $a->file_path) # or return $ctx->error(MT->translate(MT::Image->errstr)); # Get dimensions my %arg; $arg{Width} = $args->{width} if $args->{width}; $arg{Height} = $args->{height} if $args->{height}; $arg{Scale} = $args->{scale} if $args->{scale}; $arg{Square} = $args->{square} if $args->{square}; my ($url, $w, $h) = $a->thumbnail_url(%arg); my $ret = sprintf qq(url; if ($args->{new_window}) { $ret .= qq( target="_blank"); } $ret .= sprintf qq(>), $url, $w, $h; $ret; } ########################################################################### =head2 AssetCount Returns the number of assets associated with the active blog. B =over 4 =item type Allows for filtering by file type. Built-in types supported are "image", "audio", "video". These types can be extended by plugins. =back B Images available: <$mt:AssetCount type="image"$> =for tags assets =cut sub _hdlr_asset_count { my ($ctx, $args, $cond) = @_; my (%terms, %args); $terms{blog_id} = $ctx->stash('blog_id') if $ctx->stash('blog_id'); $terms{class} = $args->{type} || '*'; my $count = MT::Asset->count(\%terms, \%args); return $ctx->count_format($count, $args); } ########################################################################### =head2 AssetIfTagged A conditional tag whose contents will be displayed if the asset in context has tags. B =over 4 =item * tag or name If either 'name' or 'tag' are specified, tests the asset in context for whether it has a tag association by that name. =back =for tags assets, tags =cut sub _hdlr_asset_if_tagged { my ($ctx, $args) = @_; my $a = $ctx->stash('asset') or return $ctx->_no_asset_error(); my $tag = defined $args->{name} ? $args->{name} : ( defined $args->{tag} ? $args->{tag} : '' ); if ($tag ne '') { $a->has_tag($tag); } else { my @tags = $a->tags; @tags = grep /^[^@]/, @tags; return @tags ? 1 : 0; } } ########################################################################### =head2 CaptchaFields Returns the HTML markup necessary to display the CAPTCHA on the published blog. The value returned is escaped for assignment to a JavaScript string, since the CAPTCHA field is displayed through the MT JavaScript code. B var captcha = '<$mt:CaptchaFields$>'; =for tags comments =cut sub _hdlr_captcha_fields { my ($ctx, $args, $cond) = @_; my $blog_id = $ctx->stash('blog_id'); my $blog = MT->model('blog')->load($blog_id); return $ctx->error(MT->translate('Can\'t load blog #[_1].', $blog_id)) unless $blog; if (my $provider = MT->effective_captcha_provider( $blog->captcha_provider ) ) { my $fields = $provider->form_fields($blog_id); $fields =~ s/[\r\n]//g; $fields =~ s/'/\\'/g; return $fields; } return q(); } ########################################################################### =head2 Pages A container tag which iterates over a list of pages--which pages depends on the context the tag is being used in. Within each iteration, you can use any of the page variable tags. Because pages are basically non-date-based entries, the the C tag is very similar to L. B =over 4 =item * folder or folders (optional) This attribute allows you to filter the pages based on their folder label. Please see the mt:Entries analogous category/categories attributes of for details. =item * no_folder (optional) This attribute filters the pages to return only those not contained in a folder. =item * include_subfolders (optional) Specify '1' to cause all pages that may exist within subfolders to the folder in context to be included. =back =for tags pages, multiblog =cut sub _hdlr_pages { my($ctx, $args, $cond) = @_; my $folder = $args->{folder} || $args->{folders}; $args->{categories} = $folder if $folder; if ($args->{no_folder}) { require MT::Folder; my $blog_id = $ctx->stash('blog_id'); my @fols = MT::Folder->load({blog_id => $blog_id}); my $not_folder; foreach my $folder (@fols) { if ($not_folder) { $not_folder .= " OR ".$folder->label; } else { $not_folder = $folder->label; } } if ($not_folder) { $args->{categories} = "NOT ($not_folder)"; } } # remove current_timestamp; local $ctx->{current_timestamp}; local $ctx->{current_timestamp_end}; require MT::Page; $args->{class_type} = MT::Page->properties->{class_type}; _hdlr_entries($ctx, $args, $cond); } ########################################################################### =head2 PagePrevious A container tag that create a context to the previous page. =for tags pages, archives =cut sub _hdlr_page_previous { my($ctx, $args, $cond) = @_; return undef unless &_check_page(@_); require MT::Page; $args->{class_type} = MT::Page->properties->{class_type}; &_hdlr_entry_previous(@_); } ########################################################################### =head2 PageNext A container tag that create a context to the next page. =for tags pages, archives =cut sub _hdlr_page_next { my($ctx, $args, $cond) = @_; require MT::Page; $args->{class_type} = MT::Page->properties->{class_type}; return undef unless &_check_page(@_); &_hdlr_entry_next(@_); } ########################################################################### =head2 PageTags A container tag used to output infomation about the tags assigned to the page in context. This tag's functionality is analogous to that of L and its attributes are identical. To avoid printing out the leading text when no page tags are assigned you can use the L conditional block to first test for tags on the page. You can also use the L conditional block with the tag attribute to test for the assignment of a particular tag. B =over 4 =item * glue A text string that is used to join each of the items together. For example: <$mt:TagName$> would print out each tag name separated by a comma and a space. =item * include_private A boolean value which controls whether private tags (i.e. tags which start with @) should be output by the block. The default is 0 which suppresses the output of private tags. If set to 1, the tags will be displayed. See L for an example of its usage. =back B Listing out the page's tags, separated by commas: <$mt:TagName$> =for tags pages, tags =cut sub _hdlr_page_tags { my($ctx, $args, $cond) = @_; return undef unless &_check_page(@_); require MT::Page; $args->{class_type} = MT::Page->properties->{class_type}; local $ctx->{__stash}{class_type} = $args->{class_type}; &_hdlr_entry_tags(@_); } ########################################################################### =head2 PageIfTagged This template tag evaluates a block of code if a tag has been assigned to the current entry in context. If the tag attribute is not assigned, then the template tag will evaluate if any tag is present. B =over 4 =item * tag If present, the template tag will evaluate if the specified tag is assigned to the current page. =back B =cut sub _hdlr_page_if_tagged { my($ctx, $args, $cond) = @_; return undef unless &_check_page(@_); require MT::Page; $args->{class_type} = MT::Page->properties->{class_type}; &_hdlr_entry_if_tagged(@_); } ########################################################################### =head2 PageFolder A container tag which holds folder context relating to the page. =for tags pages, folders =cut sub _hdlr_page_folder { my($ctx, $args, $cond) = @_; return undef unless &_check_page(@_); require MT::Page; $args->{class_type} = MT::Page->properties->{class_type}; local $ctx->{inside_mt_categories} = 1; &_hdlr_entry_categories(@_); } ########################################################################### =head2 PageID A numeric system ID of the Page currently in context. B <$mt:PageID$> =cut sub _hdlr_page_id { return undef unless &_check_page(@_); &_hdlr_entry_id(@_); } ########################################################################### =head2 PageTitle The title of the page in context. B <$mt:PageTitle$> =for tags pages =cut sub _hdlr_page_title { return undef unless &_check_page(@_); &_hdlr_entry_title(@_); } ########################################################################### =head2 PageBody This tag outputs the contents of the page's Body field. If a text formatting filter has been specified, it will automatically applied. B =over 4 =item * words Trims the number of words to display. By default all are displayed. =item * convert_breaks Controls the application of text formatting. By default convert_breaks is 0 (false). This should only be used if text formatting is set to "none" in the Entry/Page editor. =back B <$mt:PageBody$> =for tags pages =cut sub _hdlr_page_body { return undef unless &_check_page(@_); &_hdlr_entry_body(@_); } ########################################################################### =head2 PageMore This tag outputs the contents of the page's Extended field. If a text formatting filter has been specified it will automatically applied. B =over 4 =item * convert_breaks (optional) Controls the application of text formatting. By default convert_breaks is 0 (false). This should only be used if text formatting is set to "none" in the Entry/Page editor. =back B <$mt:PageMore$> =for tags pages =cut sub _hdlr_page_more { return undef unless &_check_page(@_); &_hdlr_entry_more(@_); } ########################################################################### =head2 PageDate The authored on timestamp for the page. B =over 4 =item * format A string that provides the format in which to publish the date. If unspecified, the default that is appropriate for the language of the blog is used (for English, this is "%B %e, %Y %l:%M %p"). See the L tag for the supported formats. =item * language Forces the date to the format associated with the specified language. =item * utc Forces the date to UTC format. =back B <$mt:PageDate$> =for tags pages, date =cut sub _hdlr_page_date { return undef unless &_check_page(@_); &_hdlr_entry_date(@_); } ########################################################################### =head2 PageModifiedDate The last modified timestamp for the page. B =over 4 =item * format A string that provides the format in which to publish the date. If unspecified, the default that is appropriate for the language of the blog is used (for English, this is "%B %e, %Y %l:%M %p"). See the L tag for the supported formats. =item * language Forces the date to the format associated with the specified language. =item * utc Forces the date to UTC format. =back B <$mt:PageModifiedDate$> =for tags pages, date =cut sub _hdlr_page_modified_date { return undef unless &_check_page(@_); &_hdlr_entry_mod_date(@_); } ########################################################################### =head2 PageKeywords The specified keywords of the page in context. B <$mt:PageKeywords$> =cut sub _hdlr_page_keywords { return undef unless &_check_page(@_); &_hdlr_entry_keywords(@_); } ########################################################################### =head2 PageBasename By default, the page basename is a constant and unique identifier for an page which is used as part of the individual pages's archive filename. The basename is created by dirifiying the page title when the page is first saved (regardless of the page status). From then on, barring direct manipulation, the page basename stays constant even when you change the page's title. In this way, Movable Type ensures that changes you make to an page after saving it don't change the URL to the page, subsequently breaking incoming links. The page basename can be modified by anyone who can edit the page. If it is modified after it is created, it is up to the user to ensure uniqueness and no incrementing will occur. This allows you to have complete and total control over your URLs when you want to as well as effortless simplicity when you don't care. B =over 4 =item * separator (optional) Valid values are "_" and "-", dash is the default value. Specifying an underscore will convert any dashes to underscores. Specifying a dash will convert any underscores to dashes. =back B <$mt:PageBasename$> =for tags pages =cut sub _hdlr_page_basename { return undef unless &_check_page(@_); &_hdlr_entry_basename(@_); } ########################################################################### =head2 PagePermalink An absolute URL pointing to the archive page containing this entry. An anchor (#) is included if the permalink is not pointing to an Individual Archive page. B <$mt:PagePermalink$> =for tags pages, archives =cut sub _hdlr_page_permalink { return undef unless &_check_page(@_); &_hdlr_entry_permalink(@_); } ########################################################################### =head2 PageAuthorEmail The email address of the page's author. B It is not recommended to publish email addresses. B <$mt:PageAuthorEmail$> =for tags pages, authors =cut sub _hdlr_page_author_email { return undef unless &_check_page(@_); &_hdlr_entry_author_email(@_); } ########################################################################### =head2 PageAuthorLink A linked version of the author's user name, using the author URL if provided in the author's profile. Otherwise, the author name is unlinked. This tag uses the author URL if available and the author email otherwise. If neither are on record the author name is unlinked. B =over 4 =item * show_email Specifies if the author's email can be displayed. The default is false (0). =item * show_url Specifies if the author's URL can be displayed. The default is true (1). =item * new_window Specifies to open the link in a new window by adding "target=_blank" to the anchor tag. See example below. The default is false (0). =back B <$mt:PageAuthorLink$> <$mt:PageAuthorLink new_window="1"$> =for tags pages, authors =cut sub _hdlr_page_author_link { return undef unless &_check_page(@_); &_hdlr_entry_author_link(@_); } ########################################################################### =head2 PageAuthorURL The URL of the page's author. B <$mt:PageAuthorURL$> =cut sub _hdlr_page_author_url { return undef unless &_check_page(@_); &_hdlr_entry_author_url(@_); } ########################################################################### =head2 PageExcerpt This tag outputs the contents of the page Excerpt field if one is specified or, if not, an auto-generated excerpt from the page Body field followed by an ellipsis ("..."). The length of the auto-generated output of this tag can be set in the blog's Entry Settings. B =over 4 =item no_generate (optional; default "0") When set to 1, the system will not auto-generate an excerpt if the excerpt field of the page is left blank. Instead it will output nothing. =item * convert_breaks (optional; default "0") When set to 1, the page's specified text formatting filter will be applied. By default, the text formatting is not applied and the excerpt is published either as input or auto-generated by the system. =back B <$mt:PageExcerpt$> =cut sub _hdlr_page_excerpt { return undef unless &_check_page(@_); &_hdlr_entry_excerpt(@_); } ########################################################################### =head2 BlogPageCount The number of published pages in the blog. This template tag supports the multiblog template tags. B <$mt:BlogPageCount$> =for tags blogs, pages, multiblog, count =cut sub _hdlr_blog_page_count { my($ctx, $args, $cond) = @_; require MT::Page; $args->{class_type} = MT::Page->properties->{class_type}; return _hdlr_blog_entry_count(@_); } ########################################################################### =head2 PageAuthorDisplayName The display name of the author of the page in context. If no display name is specified, returns an empty string, and no name is displayed. B <$mt:PageAuthorDisplayName$> =for tags authors =cut sub _hdlr_page_author_display_name { return undef unless &_check_page(@_); &_hdlr_entry_author_display_name(@_); } ########################################################################### =head2 Folders A container tags which iterates over a list of all folders and subfolders. B =over 4 =item * show_empty Setting this optional attribute to true (1) will include folders with no pages assigned. The default is false (0), where only folders with pages assigned. =back =for tags folders =cut sub _hdlr_folders { my($ctx, $args, $cond) = @_; require MT::Folder; $args->{class_type} = MT::Folder->properties->{class_type}; _hdlr_categories($ctx, $args, $cond); } ########################################################################### =head2 FolderPrevious A container tag which creates a folder context of the previous folder relative to the current page folder or archived folder. =for tags folders, archives =cut ########################################################################### =head2 FolderNext A container tag which creates a folder context of the next folder relative to the current page folder or archived folder. =for tags folders, archives =cut sub _hdlr_folder_prevnext { my($ctx, $args, $cond) = @_; require MT::Folder; $args->{class_type} = MT::Folder->properties->{class_type}; _hdlr_category_prevnext($ctx, $args, $cond); } ########################################################################### =head2 SubFolders A specialized version of the L container tag that respects the hierarchical structure of folders. B =over 4 =item * include_current An optional boolean attribute that controls the inclusion of the current folder in the list. =item * sort_method An optional and advanced usage attribute. A fully qualified Perl method name to be used to sort the folders. =item * sort_order Specifies the sort order of the folder labels. Recognized values are "ascend" and "descend." The default is "ascend." This attribute is ignored if C is unspecified. =item * top If set to 1, displays only top level folders. Same as using L. =back =for tags folders =cut sub _hdlr_sub_folders { my($ctx, $args, $cond) = @_; require MT::Folder; $args->{class_type} = MT::Folder->properties->{class_type}; _hdlr_sub_categories($ctx, $args, $cond); } ########################################################################### =head2 SubFolderRecurse Recursively call the L or L container with the subfolders of the folder in context. This tag, when placed at the end of loop controlled by one of the tags above will cause them to recursively descend into any subfolders that exist during the loop. B =over 4 =item * max_depth (optional) Specifies the maximum number of times the system should recurse. The default is infinite depth. =back B The following code prints out a recursive list of folders/subfolders, linking those with entries assigned to their folder archive pages. Or more simply: <$mt:FolderLabel$> <$mt:SubFolderRecurse$> =for tags folders =cut sub _hdlr_sub_folder_recurse { my($ctx, $args, $cond) = @_; require MT::Folder; $args->{class_type} = MT::Folder->properties->{class_type}; _hdlr_sub_cats_recurse($ctx, $args, $cond); } ########################################################################### =head2 ParentFolders A block tag that lists all the ancestors of the current folder. B =over 4 =item * glue This optional attribute is a shortcut for connecting each folder label with its value. Single and double quotes are not permitted in the string. =item * exclude_current This optional boolean attribute controls the exclusion of the current folder in the list. =back =for tags folders =cut sub _hdlr_parent_folders { my($ctx, $args, $cond) = @_; require MT::Folder; $args->{class_type} = MT::Folder->properties->{class_type}; _hdlr_parent_categories($ctx, $args, $cond); } ########################################################################### =head2 ParentFolder A container tag that creates a context to the current folder's parent. B Up: =for tags folders =cut sub _hdlr_parent_folder { my($ctx, $args, $cond) = @_; require MT::Folder; $args->{class_type} = MT::Folder->properties->{class_type}; _hdlr_parent_category($ctx, $args, $cond); } ########################################################################### =head2 TopLevelFolders A block tag listing the folders that do not have a parent and exist at "the top" of the folder hierarchy. Same as using Cmt:SubFolders top="1"E>. B