| 1 | # Movable Type (r) Open Source (C) 2006-2008 Six Apart, Ltd. |
|---|
| 2 | # This program is distributed under the terms of the |
|---|
| 3 | # GNU General Public License, version 2. |
|---|
| 4 | # |
|---|
| 5 | # $Id$ |
|---|
| 6 | |
|---|
| 7 | # Original Copyright (c) 2004-2006 David Raynes |
|---|
| 8 | |
|---|
| 9 | package MultiBlog; |
|---|
| 10 | |
|---|
| 11 | use strict; |
|---|
| 12 | use warnings; |
|---|
| 13 | |
|---|
| 14 | # Blog-level Access override statuses |
|---|
| 15 | sub DENIED () { 1 } |
|---|
| 16 | sub ALLOWED () { 2 } |
|---|
| 17 | |
|---|
| 18 | sub preprocess_native_tags { |
|---|
| 19 | my ( $ctx, $args, $cond ) = @_; |
|---|
| 20 | my $plugin = MT::Plugin::MultiBlog->instance; |
|---|
| 21 | |
|---|
| 22 | my $tag = lc $ctx->stash('tag'); |
|---|
| 23 | |
|---|
| 24 | # If we're running under MT-Search, set the context based on the search |
|---|
| 25 | # parameters available. |
|---|
| 26 | unless ($args->{blog_id} || $args->{include_blogs} || $args->{exclude_blogs}) { |
|---|
| 27 | my $app = MT->instance; |
|---|
| 28 | if ($app->isa('MT::App::Search')) { |
|---|
| 29 | if (my $excl = $app->{searchparam}{ExcludeBlogs}) { |
|---|
| 30 | $args->{exclude_blogs} ||= join ',', keys %$excl; |
|---|
| 31 | } elsif (my $incl = $app->{searchparam}{IncludeBlogs}) { |
|---|
| 32 | $args->{include_blogs} = join ',', keys %$incl; |
|---|
| 33 | } |
|---|
| 34 | if (($args->{include_blogs} || $args->{exclude_blogs}) && $args->{blog_id}) { |
|---|
| 35 | delete $args->{blog_id}; |
|---|
| 36 | } |
|---|
| 37 | } |
|---|
| 38 | } |
|---|
| 39 | |
|---|
| 40 | # Filter through MultiBlog's access controls. If no blogs |
|---|
| 41 | # are accessible given the specified attributes, we return |
|---|
| 42 | # NULL or an error if one occured. |
|---|
| 43 | if ( ! filter_blogs_from_args($plugin, $ctx, $args) ) { |
|---|
| 44 | return $ctx->errstr ? $ctx->error($ctx->errstr) : ''; |
|---|
| 45 | } |
|---|
| 46 | # Explicity set blog_id for MTInclude if not specified |
|---|
| 47 | # so that it never gets a multiblog context from MTMultiBlog |
|---|
| 48 | elsif ($tag eq 'include' and ! exists $args->{blog_id}) { |
|---|
| 49 | my $local_blog_id = $ctx->stash('local_blog_id'); |
|---|
| 50 | if (defined $local_blog_id) { |
|---|
| 51 | $args->{blog_id} = $ctx->stash('local_blog_id'); |
|---|
| 52 | } |
|---|
| 53 | } |
|---|
| 54 | # If no include_blogs/exclude_blogs specified look for a |
|---|
| 55 | # previously set MTMultiBlog context |
|---|
| 56 | elsif ( my $mode = $ctx->stash('multiblog_context') ) { |
|---|
| 57 | $args->{$mode} = $ctx->stash('multiblog_blog_ids'); |
|---|
| 58 | } |
|---|
| 59 | |
|---|
| 60 | # Save local blog ID for all tags other than |
|---|
| 61 | # MTMultiBlog since that tag handles it itself. |
|---|
| 62 | local $ctx->{__stash}{local_blog_id} = $ctx->stash('blog_id') |
|---|
| 63 | unless $ctx->stash('multiblog_context'); |
|---|
| 64 | # Remove local blog ID from MTTags since it is cross-blog |
|---|
| 65 | # and hence MTMultiBlogIfLocalBlog doesn't make sense there. |
|---|
| 66 | local $ctx->{__stash}{local_blog_id} = 0 if $tag eq 'tags'; |
|---|
| 67 | |
|---|
| 68 | # Call original tag handler with new args |
|---|
| 69 | defined(my $result = $ctx->super_handler( $args, $cond )) |
|---|
| 70 | or return $ctx->error($ctx->errstr); |
|---|
| 71 | return $result; |
|---|
| 72 | } |
|---|
| 73 | |
|---|
| 74 | |
|---|
| 75 | sub post_feedback_save { |
|---|
| 76 | my $plugin = shift; |
|---|
| 77 | my ( $trigger, $eh, $feedback ) = @_; |
|---|
| 78 | if ( $feedback->visible ) { |
|---|
| 79 | my $blog_id = $feedback->blog_id; |
|---|
| 80 | my $app = MT->instance; |
|---|
| 81 | foreach my $scope ("blog:$blog_id", "system") { |
|---|
| 82 | my $d = $plugin->get_config_value( $scope eq 'system' ? 'all_triggers' : 'other_triggers', $scope ); |
|---|
| 83 | while ( my ( $id, $a ) = each( %{ $d->{$trigger} } ) ) { |
|---|
| 84 | next if $id == $blog_id; |
|---|
| 85 | perform_mb_action( $app, $id, $_ ) foreach keys %$a; |
|---|
| 86 | } |
|---|
| 87 | } |
|---|
| 88 | } |
|---|
| 89 | } |
|---|
| 90 | |
|---|
| 91 | sub post_entry_save { |
|---|
| 92 | my $plugin = shift; |
|---|
| 93 | my ( $eh, $app, $entry ) = @_; |
|---|
| 94 | my $blog_id = $entry->blog_id; |
|---|
| 95 | |
|---|
| 96 | foreach my $scope ("blog:$blog_id", "system") { |
|---|
| 97 | my $d = $plugin->get_config_value( $scope eq 'system' ? 'all_triggers' : 'other_triggers', $scope ); |
|---|
| 98 | while ( my ( $id, $a ) = each( %{ $d->{'entry_save'} } ) ) { |
|---|
| 99 | next if $id == $blog_id; |
|---|
| 100 | perform_mb_action( $app, $id, $_ ) foreach keys %$a; |
|---|
| 101 | } |
|---|
| 102 | |
|---|
| 103 | require MT::Entry; |
|---|
| 104 | if ( ( $entry->status || 0 ) == MT::Entry::RELEASE() ) { |
|---|
| 105 | while ( my ( $id, $a ) = each( %{ $d->{'entry_pub'} } ) ) { |
|---|
| 106 | next if $id == $blog_id; |
|---|
| 107 | perform_mb_action( $app, $id, $_ ) foreach keys %$a; |
|---|
| 108 | } |
|---|
| 109 | } |
|---|
| 110 | } |
|---|
| 111 | } |
|---|
| 112 | |
|---|
| 113 | sub perform_mb_action { |
|---|
| 114 | my ( $app, $blog_id, $action ) = @_; |
|---|
| 115 | |
|---|
| 116 | # Don't rebuild the same thing twice in a request |
|---|
| 117 | require MT::Request; |
|---|
| 118 | my $r = MT::Request->instance; |
|---|
| 119 | my $rebuilt = $r->stash('multiblog_rebuilt') || {}; |
|---|
| 120 | return if exists $rebuilt->{"$blog_id,$action"}; |
|---|
| 121 | $rebuilt->{"$blog_id,$action"} = 1; |
|---|
| 122 | $r->stash('multiblog_rebuilt', $rebuilt); |
|---|
| 123 | |
|---|
| 124 | # If the action we are performing starts with ri |
|---|
| 125 | # we rebuild indexes for the given blog_id |
|---|
| 126 | if ( $action =~ /^ri/ ) { |
|---|
| 127 | $app->rebuild_indexes( BlogID => $blog_id ); |
|---|
| 128 | |
|---|
| 129 | # And if the action contains a p |
|---|
| 130 | # we send out pings for the given blog_id too |
|---|
| 131 | if ( $action =~ /p/ ) { |
|---|
| 132 | $app->ping( BlogID => $blog_id ); |
|---|
| 133 | } |
|---|
| 134 | } |
|---|
| 135 | } |
|---|
| 136 | |
|---|
| 137 | sub filter_blogs_from_args { |
|---|
| 138 | my ($plugin, $ctx, $args) = @_; |
|---|
| 139 | |
|---|
| 140 | # SANITY CHECK ON ARGUMENTS |
|---|
| 141 | my $err; |
|---|
| 142 | # Set and clean up working variables |
|---|
| 143 | my $incl = $args->{include_blogs} || $args->{blog_id} || $args->{blog_ids}; |
|---|
| 144 | my $excl = $args->{exclude_blogs}; |
|---|
| 145 | for ($incl,$excl) { |
|---|
| 146 | next unless $_; |
|---|
| 147 | s{\s+}{}g ; # Remove spaces |
|---|
| 148 | } |
|---|
| 149 | |
|---|
| 150 | # If there are no multiblog arguments to filter, we don't need to be here |
|---|
| 151 | return 1 unless $incl or $excl; |
|---|
| 152 | |
|---|
| 153 | # Only one multiblog argument can be used |
|---|
| 154 | my $arg_count = scalar grep { $_ and $_ ne '' } $args->{include_blogs}, |
|---|
| 155 | $args->{blog_id}, |
|---|
| 156 | $args->{blog_ids}, |
|---|
| 157 | $args->{exclude_blogs}; |
|---|
| 158 | if ($arg_count > 1) { |
|---|
| 159 | $err = $plugin->translate('The include_blogs, exclude_blogs, blog_ids and blog_id attributes cannot be used together.'); |
|---|
| 160 | } |
|---|
| 161 | # exclude_blogs="all" is not allowed |
|---|
| 162 | elsif ($excl and $excl =~ /all/i) { |
|---|
| 163 | $err = $plugin->translate('The attribute exclude_blogs cannot take "all" for a value.'); |
|---|
| 164 | } |
|---|
| 165 | # blog_id only accepts a single blog ID |
|---|
| 166 | elsif ($args->{blog_id} and $args->{blog_id} !~ /^\d+$/) { |
|---|
| 167 | $err = $plugin->translate('The value of the blog_id attribute must be a single blog ID.'); |
|---|
| 168 | } |
|---|
| 169 | # Make sure include_blogs/exclude_blogs is valid |
|---|
| 170 | elsif (($incl || $excl) ne 'all' |
|---|
| 171 | and ($incl || $excl) !~ /^\d+([,-]\d+)*$/) { |
|---|
| 172 | $err = $plugin->translate('The value for the include_blogs/exclude_blogs attributes must be one or more blog IDs, separated by commas.'); |
|---|
| 173 | } |
|---|
| 174 | return $ctx->error($err) if $err; |
|---|
| 175 | |
|---|
| 176 | # Prepare for filter_blogs |
|---|
| 177 | my ($attr, $val, @blogs); |
|---|
| 178 | if ($incl) { |
|---|
| 179 | ($attr, $val) = ('include_blogs', $incl); |
|---|
| 180 | } else { |
|---|
| 181 | ($attr, $val) = ('exclude_blogs', $excl); |
|---|
| 182 | } |
|---|
| 183 | |
|---|
| 184 | if ($val =~ m/-/) { |
|---|
| 185 | my @list = split /\s*,\s*/, $val; |
|---|
| 186 | foreach my $id (@list) { |
|---|
| 187 | if ($id =~ m/^(\d+)-(\d+)$/) { |
|---|
| 188 | push @blogs, $_ for $1..$2; |
|---|
| 189 | } else { |
|---|
| 190 | push @blogs, $id; |
|---|
| 191 | } |
|---|
| 192 | } |
|---|
| 193 | } |
|---|
| 194 | else { |
|---|
| 195 | @blogs = split(/\s*,\s*/, $val); |
|---|
| 196 | } |
|---|
| 197 | |
|---|
| 198 | # Filter the blogs using the MultiBlog access controls |
|---|
| 199 | ($attr, @blogs) = filter_blogs($plugin, $ctx, $attr, @blogs); |
|---|
| 200 | return unless $attr && @blogs; |
|---|
| 201 | |
|---|
| 202 | # Rewrite the args to the modifed value |
|---|
| 203 | delete $args->{blog_ids} if exists $args->{blog_ids}; # Deprecated |
|---|
| 204 | if ($args->{blog_id}) { |
|---|
| 205 | $args->{'blog_id'} = $blogs[0]; |
|---|
| 206 | } else { |
|---|
| 207 | delete $args->{include_blogs}; |
|---|
| 208 | delete $args->{exclude_blogs}; |
|---|
| 209 | $args->{$attr} = join(',', @blogs); |
|---|
| 210 | } |
|---|
| 211 | 1; |
|---|
| 212 | } |
|---|
| 213 | |
|---|
| 214 | ## Get a mode (include/exclude) and list of blogs |
|---|
| 215 | ## Process list using system default access setting and |
|---|
| 216 | ## any blog-level overrides. |
|---|
| 217 | ## Returns empty list if no blogs can be used |
|---|
| 218 | sub filter_blogs { |
|---|
| 219 | my $plugin = shift; |
|---|
| 220 | my ( $ctx, $is_include, @blogs ) = @_; |
|---|
| 221 | |
|---|
| 222 | # Set flag to indicate whether @blogs are to be included or excluded |
|---|
| 223 | $is_include = $is_include eq 'include_blogs' ? 1 : 0; |
|---|
| 224 | |
|---|
| 225 | # Set local blog |
|---|
| 226 | my $this_blog = $ctx->stash('blog_id') || 0; |
|---|
| 227 | |
|---|
| 228 | # Get the MultiBlog system config for default access and overrides |
|---|
| 229 | my $default_access_allowed = |
|---|
| 230 | $plugin->get_config_value( 'default_access_allowed', 'system' ); |
|---|
| 231 | my $access_overrides = |
|---|
| 232 | $plugin->get_config_value( 'access_overrides', 'system' ) || {}; |
|---|
| 233 | |
|---|
| 234 | # System setting allows access by default |
|---|
| 235 | if ($default_access_allowed) { |
|---|
| 236 | |
|---|
| 237 | # include_blogs="all" |
|---|
| 238 | if ($is_include and $blogs[0] eq "all") { |
|---|
| 239 | # Check for any deny overrides. |
|---|
| 240 | # If found, switch to exclude_blogs="..." |
|---|
| 241 | my @deny = grep { $_ != $this_blog |
|---|
| 242 | and exists $access_overrides->{$_} |
|---|
| 243 | and $access_overrides->{$_} == DENIED |
|---|
| 244 | } keys %$access_overrides; |
|---|
| 245 | return @deny ? ('exclude_blogs', @deny) |
|---|
| 246 | : ('include_blogs', 'all'); |
|---|
| 247 | } |
|---|
| 248 | # include_blogs="1,2,3,4" |
|---|
| 249 | elsif ($is_include and @blogs) { |
|---|
| 250 | # Remove any included blogs that are specifically deny override |
|---|
| 251 | # Return undef is all specified blogs are deny override |
|---|
| 252 | my @allow = grep { $_ == $this_blog |
|---|
| 253 | or ! exists $access_overrides->{$_} |
|---|
| 254 | or $access_overrides->{$_} == ALLOWED |
|---|
| 255 | } @blogs; |
|---|
| 256 | return @allow ? ('include_blogs', @allow) : undef; |
|---|
| 257 | } |
|---|
| 258 | # exclude_blogs="1,2,3,4" |
|---|
| 259 | else { |
|---|
| 260 | # Add any deny overrides blogs to the list and de-dupe |
|---|
| 261 | push(@blogs, grep { $_ != $this_blog |
|---|
| 262 | and $access_overrides->{$_} == DENIED |
|---|
| 263 | } keys %$access_overrides); |
|---|
| 264 | my %seen; |
|---|
| 265 | @seen{@blogs} = (); |
|---|
| 266 | @blogs = keys %seen; |
|---|
| 267 | return ('exclude_blogs', @blogs); |
|---|
| 268 | } |
|---|
| 269 | } |
|---|
| 270 | # System setting does not allow access by default |
|---|
| 271 | else { |
|---|
| 272 | # include_blogs="all" |
|---|
| 273 | if ($is_include and $blogs[0] eq "all") { |
|---|
| 274 | # Enumerate blogs from allow override |
|---|
| 275 | # Hopefully this is significantly smaller than @all_blogs |
|---|
| 276 | my @allow = grep { $_ == $this_blog |
|---|
| 277 | or $access_overrides->{$_} == ALLOWED |
|---|
| 278 | } ($this_blog, keys %$access_overrides); |
|---|
| 279 | return @allow ? ('include_blogs', @allow) : undef; |
|---|
| 280 | } |
|---|
| 281 | # include_blogs="1,2,3,4" |
|---|
| 282 | elsif ($is_include and @blogs) { |
|---|
| 283 | # Filter @blogs returning only those with allow override |
|---|
| 284 | my @allow = grep { $_ == $this_blog |
|---|
| 285 | or ( exists $access_overrides->{$_} |
|---|
| 286 | and $access_overrides->{$_} == ALLOWED) |
|---|
| 287 | } @blogs; |
|---|
| 288 | return @allow ? ('include_blogs', @allow) : undef; |
|---|
| 289 | } |
|---|
| 290 | # exclude_blogs="1,2,3,4" |
|---|
| 291 | else { |
|---|
| 292 | # Get allow override blogs and then omit |
|---|
| 293 | # the specified excluded blogs. |
|---|
| 294 | my @allow = grep { $_ == $this_blog |
|---|
| 295 | or $access_overrides->{$_} == ALLOWED |
|---|
| 296 | } ($this_blog, keys %$access_overrides); |
|---|
| 297 | my %seen; |
|---|
| 298 | @seen{@blogs} = (); |
|---|
| 299 | @blogs = grep { ! $seen{$_} } @allow; |
|---|
| 300 | return @blogs ? ('include_blogs', @blogs) : undef; |
|---|
| 301 | } |
|---|
| 302 | } |
|---|
| 303 | } |
|---|
| 304 | |
|---|
| 305 | |
|---|
| 306 | 1; |
|---|