Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Try Vanilla Forums Cloud product

Ready to contribute?

Amazing! Sign our contributors' agreement and then join us on GitHub.

Update for critical security issue in PHPMailer included in release Vanilla 2.3.1

Wordpress integration - automatic discussion per blog post

Hey guys,

So I'm aware that as soon as someone comments on a Wordpress blog post with integrated Vanilla comments a discussion gets created - but is it also possible to create discussions per blog post even without someone commenting on it?

In that way, forum users get notified there are new blog posts.

Comments

  • LincLinc Vanilla's Bard (and Director of Development) Detroit Vanilla Staff
    edited January 31

    To do that, you'd need an API call from the blog to the forum. We don't currently have a native API.

    If both sites are on the same server, you could do it programmatically entirely in the backend. But that'd be a bit of work to make that happen, and it's a very specific solution for only same-server setups.

    I can totally see this being something we add to the WordPress plugin once we get to 2.5 and have an API. :)

  • Is it bad I'm already excited about Vanilla 2.5? I know it's going to take about a year but your enthusiasm is contagious :P

    In the meantime, if I understand it correctly, the Wordpress blog posts are associated to discussions by their wordpress ID right?

    If you would have a discussion on your forum and you knew to what Wordpress post that belonged, would it be possible to link them programmatically easily? For example, if I update ForeignKey to 34 in gdn_discussion, does that mean that discussion will be linked to the wordpress blog post #34?

    I ask because I can read a RSS feed of the Wordpress blog to create discussions automatically, and I think I can also extract the wordpress ID of the page from that.

  • LincLinc Vanilla's Bard (and Director of Development) Detroit Vanilla Staff

    @Caylus said:
    Is it bad I'm already excited about Vanilla 2.5? I know it's going to take about a year but your enthusiasm is contagious :P

    Well, I hope not, because we all are. :chuffed:

    @Caylus said:
    In the meantime, if I understand it correctly, the Wordpress blog posts are associated to discussions by their wordpress ID right?

    If you would have a discussion on your forum and you knew to what Wordpress post that belonged, would it be possible to link them programmatically easily? For example, if I update ForeignKey to 34 in gdn_discussion, does that mean that discussion will be linked to the wordpress blog post #34?

    I believe that's correct.

    Caylus
  • CaylusCaylus ✭✭

    Bit of a necropost, but since it's the same topic I didn't see the point in creating a new topic:

    I've written a Wordpress/Vanilla sync plugin, but I've run into a problem:

    When a new post is created in Wordpress, it pings Vanilla. Vanilla then checks if a discussion is already linked to that post, if not, it tries to create one.

    However, creating that post doesn't work since there's no logged in user when Wordpress pings Vanilla (so since there is no account, it doesn't have posting rights, so it can't post).

    Is there a way to temporary disable checking for posting rights? (in the same way you can disable spamcheck by $DiscussionModel->SpamCheck = FALSE;)

    My current code is on Github:
    https://github.com/Caylus/vanilla_plugins/tree/master/wordpresssync

  • CaylusCaylus ✭✭

    I've got a solution, but I'm not sure if it's a good one.

    Instead of just using $DiscussionModel->save($DiscussionData);, now I set the session user to the user who is "posting" the new discussion, and then I switch it back after they "posted" it.

    Will this have unintended effects? Does Vanilla cache anything about users outside the Usermodel?

            $oldUser=gdn::session()->User;
            gdn::session()->User=gdn::userModel()->getID($DiscussionData[$DiscussionModel->InsertUserID]);
            $DiscussionModel->save($DiscussionData);
            gdn::session()->User=$oldUser;
    
  • R_JR_J Cheerleader & Troubleshooter Munich Moderator

    You "simply" have to choose a user with the appropriate rights. I would recommend the system user. Use UserModels getSystemUserID to get its ID and set that as the InsertUserID.

  • CaylusCaylus ✭✭

    That sadly doesn't work on its own. I'm using the System user as insertUserID already (System user is User 1 on my system), but it seems like the validation of the DiscussionModel only cares for the posting permissions of the session user, not the user "actually" posting it.

    If I temporary switch the session user to the system user as I do one post above it does work.

  • R_JR_J Cheerleader & Troubleshooter Munich Moderator

    Here is a snippet from hgtonights data generator plugin:

          $UserID = $this->GetRandomUserID();
    
          $Discussion = array(
              'TransientKey' => Gdn::UserModel()->SetTransientKey($UserID),
              'DraftID' => '0',
              'CategoryID' => $CategoryIDs[array_rand($CategoryIDs)],
              'Name' => $Generator->Get(2, 10),
              'Body' => $this->GenerateBody($Generator),
              'InsertUserID' => $UserID
          );
    
          $DiscussionModel->Save($Discussion);
    

    I forgot about the transient key

  • CaylusCaylus ✭✭
    edited August 30

    You can't access HG's plugin if you're not logged in (aka the session user exists) :P (Because you need Garden.Settings.Manage permissions.)

    The transient key is unnecessary if you disable the spam check.

    If you create a test account with Garden.Settings.Manage permission and no posting privileges in any category, you'll find that no discussions are created with hg's plugin if that account is the one pressing the button. At all. (Just tested it out, removed all posting privileges from moderators and left system users and admin intact. HG's plugin posts discussions from random users, so he should have a 5/6th chance per discussion that the random user had the correct posting permissions)

    If I return the posting priviliges to moderators, suddenly HG's plugin does manage to create discussions again if the logged in user is a moderator.

    Really, as I said, DiscussionModel only cares for the posting permissions of currently logged in user, not the user belonging to the InsertUserID.

  • hgtonighthgtonight ∞ · New Moderator

    @Caylus said:
    Really, as I said, DiscussionModel only cares for the posting permissions of currently logged in user, not the user belonging to the InsertUserID.

    Sounds like a bug. At the very least a disconnect between what you expect of a permission system.

    Search first

    Check out the Documentation! We are always looking for new content and pull requests.

    Click on insightful, awesome, and funny reactions to thank community volunteers for their valuable posts.

  • CaylusCaylus ✭✭

    I've got to admit that it was quite unexpected to find out it worked like that yes :P

    Is it big enough to file a bug report? It's probably almost never going to come up.

  • R_JR_J Cheerleader & Troubleshooter Munich Moderator

    The discussion model indeed checks the session users permissions, I've just looked it up. You could report that but I'm not sure if that would be considered as a bug or if it might be "expected behavior"

  • R_JR_J Cheerleader & Troubleshooter Munich Moderator

    I found a solution to your problem. Setting a different user as you did could be a problem if the server crashes during the save command. Then this old user would never be reset. I would never risk something like that.

    I've come up with a fix, which simply eliminates the error message under certain circumstances:

        public function pluginController_snippetAutoDiscussion_create($sender) {
            $categoryIDs = array_column(CategoryModel::categories(), 'CategoryID');
            array_shift($categoryIDs);
    
            $userModel = gdn::userModel();
    
            $discussion = [
                'InsertUserID' => $userModel->getSystemUserID(),
                'DraftID' => '0',
                'CategoryID' => $categoryIDs[array_rand($categoryIDs)],
                'Name' => 'Test discussion from '.Gdn_Format::date(),
                'Body' => 'Simple test.'
            ];
            $discussion['TransientKey'] = $userModel->setTransientKey($discussion['InsertUserID']);
    
            $discussionModel = new DiscussionModel();
            $discussion['SnippetSecret'] = 'abracadabra';
    
            // Create discussion and get the ID.
            $discussion['DiscussionID'] = $discussionModel->save($discussion);
    
            // Change body of existing discussion.
            $discussion['Body'] .= "<br>The ID of this discussion is {$discussion['DiscussionID']}.";
            // Update discussion.
            $discussionModel->save($discussion);
    
            // Show the new discussion.
            redirect(discussionUrl($discussion));
        }
    
        public function discussionModel_beforeSaveDiscussion_handler($sender, $args) {
            if (val('SnippetSecret', $args['FormPostValues'], false) != 'abracadabra') {
                return;
            }
    
            $results = $sender->validationResults();
            unset($results['CategoryID']);
    
            $sender->Validation->reset();
    
            foreach ($results as $fieldName => $errorCodes) {
                foreach ($errorCodes as $errorCode) {
                    $sender->Validation->addValidationResult($fieldName, $errorCode);
                }
            }
        }
    
    

    When the session users permissions are checked, there is an error message generated. Because of that error message the program flow stops during the save method.
    If you go to the discussion models before save event, check if the current event is "yours" and erase the error message concerning the category, everythings fine.

    Much better than the "abracadabra" would be a random string generated only for this request. I haven't had the time but you should create a private class variable, a __construct method which fills that variable with a random string and then use that variable instead of the abracadabra

    Caylushgtonight
  • CaylusCaylus ✭✭

    Thanks @R_J ! I can work with that. Seems a lot safer yeah.

    If before $discussionModel->save($discussion); I'd say

    $this->discussionToCreate=$discussionModel;
    

    and in discussionModel_beforeSaveDiscussion_handler I'd say

    if(isset($this->discussionToCreate)&&$this->discussionToCreate===$sender))
    

    That should work too right, no random string necessary?

  • CaylusCaylus ✭✭
    edited August 30

    Awww your fix is not going to work.

    BeforeSaveDiscussion is fired before the validation happens, as far as I can see.

    Lemme think...

    Never mind. The category validation happens before BeforeSaveDiscussion, the other validations happen afterwards. Your fix definitely works.

    vrijvlinder
  • R_JR_J Cheerleader & Troubleshooter Munich Moderator

    @Caylus said:
    Thanks @R_J ! I can work with that. Seems a lot safer yeah.

    If before $discussionModel->save($discussion); I'd say

    $this->discussionToCreate=$discussionModel;
    

    and in discussionModel_beforeSaveDiscussion_handler I'd say

    if(isset($this->discussionToCreate)&&$this->discussionToCreate===$sender))
    

    That should work too right, no random string necessary?

    Not sure if that would work. But it sounds as if you would use the Bible as a boomark for your X-Men comic O.o

    What the code in the beforeSaveDiscussion part above does, is skipping all Category permission checks. So you need to be absolutely sure that it is only run for the discussion that your plugin creates.
    The DiscussionModel->save method takes an array as its argument. So if you inject something unique into that array when you create your discussion, noone would be able to spoof that.

    As a second thought it would be better to create that random id when you create the discussion, add it to the discussion array, check for that existance in the beforeSaveDiscussion method and then remove it afterwards - you never know.

    You've used the DiscussionModel itself as that unique ID. The DiscussionModel holds quite a hugh amount of information. I wouldn't bet that there isn't the chance that this information changes.

    Just think of a plugin which alters something in the beforeSaveEvent before your plugin is run. I guess then that comparison would fail.
    Besides of that, comparing two objects is slower than comparing two single values.

  • CaylusCaylus ✭✭

    I think your "Bible for X-men comic" metaphor is correct up until PHP 4, not anymore for PHP >=5.

    Now variables contain object identifiers, not the actual object anymore. So the DiscussionModel itself might hold a lot of information, but $this->discussionToCreate doesn't, it only contains a few bytes of object identifier.
    http://ca2.php.net/manual/en/language.oop5.references.php

    I also think that your last two sentences are correct if you would use the "==" to compare, not when you use the "===" operand (since === simply compares the two object identifiers to see if they point to the same object, while == checks if the values in the object are the same (which is slow and would fail if there's any change in the object)).

    I've got to admit that I personally find the code to be easier to understand (making sure that the Sender of the event is the discussion you want to save, instead of using random identifier strings), so I think I'll keep it like this.

    I appreciate you thinking with me though!

    R_Jrbrahmson
  • R_JR_J Cheerleader & Troubleshooter Munich Moderator

    That was all new to me, very interesting! The only thing which I don't know about and which would concern me therefore, is the lifetime of that discussionToCreate property of your plugin. Does it have to be removed, could it be that it exists for other discussions too etc.

  • CaylusCaylus ✭✭

    It's better to remove it yeah (=set to false for example), because as long as a reference to an object exists, that object exists. So after it served its purpose it has to go so the object it's referring to doesn't uselessly stick around.

    It can't exist for other discussions, because if that was possible (having two variables with the same object identifier but that reference different objects) variables would have no way to tell which object of the two they're referring to.

Sign In or Register to comment.