function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Kenji775Kenji775 

Chatter Trigger Error on user status updates

Hey all,

I am working on an application that was working, trying to add a bit of new functionality. It just takes a list of objects, looks to see if any of the fields specified at run time have a value that is predefined on a custom object. If so, it performs some actions, ranging from sending emails to blocking the save entirly. This is mostly for chatter posts, to prevent users from posting sensative data. My class that does all the heavy lifting works perfectly for commenting on objects, but fails a users 'wall' post. The error it returns is Error: Error: Feed item doesn't exist.

 

What's weird is everything in my class works, just the chatter post doesn't actually get saved. The emails get sent, the logging records get created, every last line of code in the class seems to get run, so I'm not sure what the issue is.

 

The trigger for the objects (which works) looks like this.

 

trigger filterChatterFeedItem on FeedItem (before insert, before update) 
{
     //when a user posts on an object, the field that contains the text is the 'body' field, so pass that for filtering
String[] filterFields = new String[]{'Body'}; redacted.filterObject(trigger.new,filterFields,'parentId'); }

 

 

And the failing one for users looks like this

 

trigger filterChatterUserStatus on User (before insert, before update) 
{
    //when a user updates, we want to filter their current status field.
    String[] filterFields = new String[] {'CurrentStatus'};
    redacted.filterObject(trigger.new,filterFields);   
}

 

And here is the class. It's kind of complicated, but pretty well commented.

global class redacted
{
    //takes a list of objects, and a list of fields on that object to filter.
    public static void filterObject(list<sObject> recordsToFilter, list<string> fieldsToFilter)
    {
       //what type of object are we filtering (returns a string representing the object type, such as 'contact' or 'user'
        string objectType = string.valueOf(recordsToFilter[0].getSObjectType());
        
        //who is posting this content?
        string postingUserId = UserInfo.getUserId();
        
        //which field on the passed in set of records is going to contain the relationship that points to the object this chatter post is related to?
        string objectIdField = 'parentId';
        
        //if this a user object, the id field is what we want, because that points to the user themselves, which is the object which the post is on.
        if(objectType.toLowerCase() == 'user')
        {
            objectIdField = 'id';
        }
                                   
        list<Redacted_Filter_Match__c> matches = new list<Redacted_Filter_Match__c>();
        
        //Find the current (posting users) users email. So we know who to email if they match a rule.
        User posterInfo = [select id, email from user where id = :UserInfo.getUserId()];


        
        list<Redacted_Filter__c> filters = [   select
                                              Name,
                                              Applies_To__c,
                                              Error_on_Match__c,   
                                              Notification_Email_Address__c,
                                              Pattern_String__c,
                                              Replacement_Pattern__c,
                                              Replace_Pattern__c,
                                              Send_Email_on_Match__c,
                                              Error_Message__c,
                                              Track_Matches__c,
                                              Email_Poster_On_Match__c,
                                              Warning_Message__c
                                              from Redacted_Filter__c
                                              Where Active__c = true ];
        for(sObject post : recordsToFilter)
        {
            for(Redacted_Filter__c filter : filters)
            {
                //Don't bother running this filter if it doesn't apply to this object type
                
                //Find and filters that apply to this type of object that has been passed in.
                string relatedObjectType = getObjectTypeFromId(string.ValueOf(post.get(objectIdField)));
                if(!filter.Applies_To__c.toLowerCase().contains(relatedObjectType.toLowerCase()))
                {
                    continue;
                }            

                for(String postBodyField : fieldsToFilter)
                {
                    String postBody = string.valueOf(post.get(postBodyField));
                    if(postBody == null)
                    {
                        continue;
                    }  
                    //okay, we have to get a little tricky here. Since the native apex string.contains() method
                    //does not support regular expressions, we have to use of the methods that does, and test to see if that
                    //did anything. If it did, then we know the string contains the regular expression and we can continue accordingly.
                    //so lets use the find and replace method to replace any match with this big long string that should never appear normally
                    //then check to see if the string contains it. If so, it matched the post and we should continue accordingly.
                    string matchString = 'thisisamatchingstringthatshouldnoteverappearinaregularchatterpost';
                   
                    string bodyMatchesPattern = postBody.replaceAll(filter.Pattern_String__c, matchString);
                                                       
                    //If a match was found, lets do some filtering and crap
                    if(bodyMatchesPattern.contains(matchString))
                    {
                        //If the user has configured this rule to be tracked, create a new tracking object
                        //and populate it with data.
                        if(filter.Track_Matches__c)
                        {
                            Redacted_Filter_Match__c thisMatch = new Redacted_Filter_Match__c();
                            thisMatch.Filter__c = filter.id;
                            thisMatch.Chatter_Post__c = postBody;
                            thisMatch.Poster__c = postingUserId;
                            matches.add(thisMatch);
                        }
                        //If this rule is set to send an email lets do that
                       
                        list<string> emailRecips = new list<string>();
                        //send the email to the notification email address specified on the filter if there is one, and that option is set
                        if(filter.Notification_Email_Address__c != null && filter.Send_Email_on_Match__c)
                        {
                            emailRecips = new list<string>();
                            emailRecips.add(filter.Notification_Email_Address__c);                        
                            sendPatternMatchEmail(emailRecips, filter, postingUserId, postBody, null, string.ValueOf(post.get(objectIdField)));
                        }
                        //send the email to the user tho posted the content in the first place if that option is set. Send them the warning message.
                        if(filter.Email_Poster_On_Match__c)
                        {
                            emailRecips = new list<string>();
                            emailRecips.add(posterInfo.email);
                            sendPatternMatchEmail(emailRecips, filter, postingUserId, postBody, filter.Warning_Message__c, string.ValueOf(post.get(objectIdField)));
                        }
                       
                        
                        //if this rule errors on match, attach an error to this post
                        
                        if(filter.Error_on_Match__c)
                        {
                            post.addError(filter.Error_Message__c);
                        }
                        //otherwise if it doesn't have erroring, but it does have replacements, lets do that
                        else if(filter.Replace_Pattern__c)
                        {
                            try
                            {                        
                                //Run a replacment using the provided regular expression and the replacment string specified
                                postBody = postBody.replaceAll(filter.Pattern_String__c,filter.Replacement_Pattern__c);
                                //overwrite the post body with the updated body
 
                                post.put(postBodyField,postBody);
                            }
                            catch(Exception e)
                            {
                                post.addError('This post has triggered a filtering rule. However the '+postBodyField+' on the '+objectType+' object is not editable so the filter rule may not be completed. This indicates an error with filter setup. Please notify your administrator' + e.getMessage());
                            }
                        }
                        
                    }
                }
            }
        }
        //if there are any feed matches to insert, do that now.
        if(!matches.isEmpty())
        {       
                database.insert(matches,false);
        }
    }

    public static void sendPatternMatchEmail(list<String> recipients, Redacted_Filter__c filter, Id userId, string postBody, string emailBody, id chatterPostId)
    {
        if(userId != null)
        {
            //lets try and find the user based on the id passed in. Failing that, then set the offending user as the current user (this will always be the case anyway, except in the case of imports)
            User offendingUser = [select firstname, lastname, id, email, username from user where id = :userId];
          
            Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
            //Set email address
            String[] toAddresses = recipients;
            mail.setToAddresses(toAddresses);
            mail.setReplyTo('noreply@salesforce.com'); //the reply address doesn't matter
            mail.setSubject('Chatter Compliance Rule '+filter.name+' was Invoked');
            mail.setBccSender(false);  //we don't want to Bcc ourselves on this
            if(emailBody == null)
            {
                  // mail.setPlainTextBody('User ' +user.username+ ' created a chatter post which matched filter rule ' +filter.name+'. The content of the post was "'+postBody+'".');
                   mail.setPlainTextBody('User ' +offendingUser.username+ ' created a chatter post which matched filter rule ' +
                   filter.name+'. The content of the post was "'+postBody+'. View the post here ' + 
                   URL.getSalesforceBaseUrl().toExternalForm() +'/'+chatterPostId);
            }
            else
            {
                mail.setPlainTextBody(emailbody);
            }
           
            Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail }); //send the email
        }   
    }

    global static string getObjectTypeFromId(string objectId)
    {
        Map<String, Schema.SObjectType> gd = Schema.getGlobalDescribe(); 
        Map<String,String> keyPrefixMap = new Map<String,String>{};
        Set<String> keyPrefixSet = gd.keySet();
        for(String sObj : keyPrefixSet)
        {
           Schema.DescribeSObjectResult r =  gd.get(sObj).getDescribe();
           String tempName = r.getName();
           String tempPrefix = r.getKeyPrefix();
           keyPrefixMap.put(tempPrefix,tempName);
        }
    
        return keyPrefixMap.get(objectId.subString(0,3));  
    }
       
   

 

Any ideas are appreciated. Thank you.

Kenji775Kenji775

Some other things,

 

The debug log records the error as a successful execution. However it does display a small yellow alert icon informing me the log is so large that it must be displayed in raw mode. This does not happen during an actual successful posting, but that may be more to do with the other triggers attached to users firing than something to do with the error.

 

I recorded the log, and made it available here.

http://pastebin.com/1EWUMxAR

 

Also, here are a few screenshots.

 

 

http://imgur.com/a/4Q9KZ