Users running a non-download version of Vanilla (pulled from github), on branch release/2019.016 or master from the last 2 weeks should upgrade to release/2019.017 or latest master for security reasons. Downloaded official open sources releases are not affected.
Please upgrade here. These earlier versions are no longer being updated and have security issues.

Queuing Notifications

Hey guys,

So I'm trying to write a Facebook notification plugin (basically it just passes on Vanilla notifications to Facebook).

I'm using @R_J 's excellent Pushbullet plugin as a basis (which seems to work).
The code to send the Facebook notifications works too, so that's good. But somehow the notifications are simply not send.
(The check for the correct preferences is also working, I tested that)

Anyone know what might be the problem?

Thanks!

<?php
if (!defined('APPLICATION')) {
    exit();
}

$PluginInfo['MoarNotificationOptions'] = array(
    'Name' => 'MoarNotificationOptions',
    'Description' => 'Adds Facebook notification',
    'Version' => '1.0',
    'MobileFriendly' => TRUE,
    'RequiredApplications' => array('Vanilla' => '2.3')
);

class moarnotificationoptionsPlugin extends Gdn_Plugin {

    /**
     * Run when plugin is enabled.
     *
     * @return void.
     */
    public function setup() {
        $this->structure();
    }

    /**
     * Change db structure.
     *
     * @return void.
     */
    public function structure() {
        // New column for sent status of push messages.
        Gdn::structure()
                ->table('Activity')
                ->column('Facebook', 'tinyint(1)', 0)
                ->set();
    }

    /**
     * Extend notifications screen to show additional notification provider.
     *
     * @param ProfileController $sender The calling controller.
     *
     * @return void.
     */
    public function profileController_afterPreferencesDefined_handler($sender) {
        // Add new column to notification preferences.
        foreach ($sender->Preferences as $preferenceGroup => $preferences) {
            foreach ($preferences as $name => $description) {
                $nameParts = explode('.', $name);
                $sender->Preferences[$preferenceGroup]['Facebook.' . $nameParts[1]] = $description;
            }
        }
    }

    /*
     * Break out IFrame Facebook puts you in
     */
    function plugincontroller_redirect_create() {
        header("X-Frame-Options: ALLOW-FROM https://facebook.com");
        ?><script>window.parent.location.href = "<?= Gdn_Url::webRoot(true); ?>";</script><?php
    }

    public function __construct() {
        parent::__construct();
    }

    /**
     * Add Activity to Queue.
     *
     * Ensure that this activity is queued.
     * TODO: check if this causes double activities!
     *
     * @param ActivityModel $sender Instance of the sending class.
     * @param mixed         $args   Event arguments.
     *
     * @return void.
     */
    public function activityModel_beforeCheckPreference_handler($sender, $args) {
        // Check if user wants to be notified of such events.
        if (
                !$sender->notificationPreference(
                        $args['Preference'], $args['Data']['NotifyUserID'], 'Facebook'
                )
        ) {
            return;
        }
        $args['Data']['Facebook'] = ActivityModel::SENT_PENDING;

        ActivityModel::$Queue[$args['Data']['NotifyUserID']][$args['Data']['ActivityType']] = [
            $args['Data'],
            $args['Options']
        ];
    }

    /**
     * Send custom notification and change activities sent status.
     *
     * @param ActivityModel $sender Instance of the sending class.
     * @param mixed         $args   Event arguments.
     *
     * @return void.
     */
    public function activityModel_beforeSave_handler($sender, $args) {
        // Only continue if notification has not been already sent or has
        // been a fatal error.
        if (
                !($args['Activity']['Facebook'] == ActivityModel::SENT_PENDING ||
                $args['Activity']['Facebook'] == ActivityModel::SENT_ERROR)
        ) {
            return;
        }

        // Result will be an "Activity Status" (see class ActivityModel).
        $result = $this->notify($args['Activity']);
        $args['Activity']['Facebook'] = $result;
    }

    /**
     * Send notification with custom notification provider.
     *
     * This function must return one of the "Activity Status" codes defined
     * in ActivityModel.
     * SENT_OK    = successful delivered
     * SENT_ERROR = repeat delivery
     * SENT_FAIL  = fatal error
     *
     * @param object $activity Activity object.
     *
     * @return integer One of the SENT_... constants of ActivityModel.
     */
    private function notify($activity) {
        $activity['Data'] = unserialize($activity['Data']);

        // Form the Activity headline
        $activity['Headline'] = formatString(
                $activity['HeadlineFormat'], $activity
        );
        try {
            $app_access = $this->getAppAccessToken();
            $notification_url = $this->getNotificationURL($activity['NotifyUserID']);
            if ($app_access && $notification_url) {
                $parameters = ["access_token" => $app_access,
                    "href" => "",
                    "template" => "There's a notification on the forum!"];
                $this->sendToFacebook($notification_url, $parameters);
            }
        } catch (Exception $e) {
            echo $e->getMessage();
            return ActivityModel::SENT_ERROR;
        }

        return ActivityModel::SENT_OK;
    }

    private function getAppAccessToken() {
        $app_access = c('Plugins.Facebook.AppAccessToken', false);
        if ($app_access === false) {
            $appid = c('Plugins.Facebook.ApplicationID', false);
            $appsecret = c('Plugins.Facebook.Secret', false);
            if ($appid && $appsecret) {
                $app_access = file_get_contents("https://graph.facebook.com/oauth/access_token?client_id=$appid&client_secret=$appsecret&grant_type=client_credentials");
                $app_access = substr($app_access, strpos($app_access, '=') + 1);
                saveToConfig('Plugins.Facebook.AppAccessToken', $app_access);
            }
        }
        return $app_access;
    }

    private function getNotificationURL($notifyUserID) {
        $access_token = Gdn::userModel()->getAttribute($notifyUserID, "Facebook.AccessToken");
        $userid = json_decode(file_get_contents("https://graph.facebook.com/me?fields=id&access_token=$access_token"))->id;
        if ($userid) {
            return "https://graph.facebook.com/$userid/notifications";
        }
        return false;
    }

    private function sendToFacebook($notification_url, $parameters) {
        $ch = curl_init();
//set the url, number of POST vars, POST data
        curl_setopt($ch, CURLOPT_URL, $notification_url);
        curl_setopt($ch, CURLOPT_POST, count($parameters));
        curl_setopt($ch, CURLOPT_POSTFIELDS, $parameters);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

//execute post
        $result = curl_exec($ch);

//close connection
        curl_close($ch);
    }

}

Comments

  • Found the error.

    If either email or popup preference was enabled, my code at line 89:


    $args['Data']['Facebook'] = ActivityModel::SENT_PENDING; ActivityModel::$Queue[$args['Data']['NotifyUserID']][$args['Data']['ActivityType']] = [ $args['Data'], $args['Options'] ];

    got overwritten in line 1310 of class.activitymodel.php.

            self::$Queue[$Data['NotifyUserID']][$Data['ActivityType']] = array($Data, $Options);
    

    I personally think that line 1293 of class.activitymodel.php should be

    $this->EventArguments['Data'] = &$Data;
    

    instead of

    $this->EventArguments['Data'] = $Data;
    

    Because it's (IMHO) unexpected that you can edit Data all you want in the BeforeCheckPreference event but that any changes you make are not persistent, since it will be overwritten by the old data anyway. Is this the intended behaviour or should I make a pull request?

    Fixed it for my specific situation in this way:


    public function activityModel_beforeCheckPreference_handler($sender, $args) { // Check if user wants to be notified of such events. if ( !$sender->notificationPreference( $args['Preference'], $args['Data']['NotifyUserID'], 'Facebook' ) ) { return; } //If Facebook preferences are switched on, make sure Activity gets scheduled. ActivityModel::$Queue[$args['Data']['NotifyUserID']][$args['Data']['ActivityType']] = [ $args['Data'], $args['Options'] ]; } /** * Send custom notification and change activities sent status. * * @param ActivityModel $sender Instance of the sending class. * @param mixed $args Event arguments. * * @return void. */ public function activityModel_beforeSave_handler($sender, $args) { if ( //Check if user wants to be notified via Facebook !$sender->notificationPreference( $args['Preference'], $args['Data']['NotifyUserID'], 'Facebook' ) ) { return; } // Result will be an "Activity Status" (see class ActivityModel). $result = $this->notify($args['Activity']); $args['Activity']['Facebook'] = $result; }
  • R_JR_J Cheerleader & Troubleshooter Munich Moderator

    I love statistics! I've searched through the source code (including YAGA, Articles etc) and found this:

       91:         $Sp->EventArguments['Data'] =& $Data;
      287:         $this->EventArguments['Data'] =& $Result;
      366:         $this->EventArguments['Data'] =& $Result;
      496:         $this->EventArguments['Data'] =& $Result;
     1276:         $this->EventArguments['Data'] =& $Data;
     1448:         $this->EventArguments['Data'] = &$Data;
      328:         $this->EventArguments['Data'] = $FormPostValues;
      162:         $this->EventArguments['Data'] = $Categories;
       64:         $this->EventArguments['Data'] = $Comments;
      144:         $this->EventArguments['Data'] = $Articles;
     1293:         $this->EventArguments['Data'] = $Data;
      457:         $this->EventArguments['Data'] = $Data;
      694:         $this->EventArguments['Data'] = $Data;
      823:         $this->EventArguments['Data'] = $Data;
     1129:         $this->EventArguments['Data'] = $Data;
      328:         $this->EventArguments['Data'] = $FormPostValues;
      724:                     $Pm->EventArguments['Data'] = $Data;
     1385:                             $Pm->EventArguments['Data'] = $Data;
    

    6:12, I would say there is a clear winner!

    But that is not important. If there is a valid reason to change the way $Data is passed, just create a pull request. These lines of code are quite new and your change would only enhance the functionality. So I do not think it would be rejected.


    Caylushgtonight
Sign In or Register to comment.