Mugo Web main content.

Auto-posting content to social media channels with OneAll

By: Thiago Campos Viana | March 1, 2018 | Business solutions, User experience, Web solutions, twitter, facebook, and social

We recently used OneAll to develop a feature that allows user generated content to be created, submitted for review, published, and then automatically posted to the creator's social media channels without additional effort on their part. Here's how we accomplished this.

A client requested the ability to enable users to create blog posts, and later share the published posts on their social media channels automatically.

We can auto-post to Facebook using the Facebook API, so that a user only has to authorize the connection once, and we can automatically post to Facebook on their behalf whenever a post gets published. But we also had to allow users to post on LinkedIn and Twitter. So instead of developing custom code to connect to each social media channel, we decided to use OneAll, a service that simplifies the integration of 30+ social networks such as Facebook, Twitter, Google, Instagram, Yahoo! and LinkedIn.

The advantage of OneAll is that it not only allows auto-posting to all these channels, but also it allows us to interact with only one API. Even if we were only going to post to one network, it would still be useful to use the OneAll API as long as it changes less often than those of the social networks mentioned above.

Setting up social media integrations with OneAll

When you create a "Site" in OneAll, there is an area in the dashboard that lists all the social networks available, and each one contains information about the necessary steps to set up the application and keys, and integrate them with OneAll. The only issue we found is that it was not very clear which permissions we would need for each application (which would also be the case even if not using OneAll).

Here is the list of permissions required per social media channel:

  • LinkedIn: r_basicprofile, r_emailaddress, w_share
  • Twitter: Read and Write Permissions, Additional Permissions - Request email addresses from users
  • Facebook: email, public_profile, publish_actions, user_friends

The user is able to manage their social media integration in their dashboard. We added a checkbox for each social network on the blog post compose screen so users can decide where they want the content shared once it's approved and live.

You can call a JavaScript code snippet and it will display the OneAll account connection widget, so the user can click any button to connect their account to the desired social networks.

OneAll connect account

Here's a sample JavaScript code snippet for the connection widget:

Note: All code samples are for eZ Platform / eZ Publish!

<script type="text/javascript">

 /* get_config( 'oneall_site_subdomain' ) should return the subdomain of a Site in your OneAll account */    
 var oneall_subdomain = '{{ get_config( 'oneall_site_subdomain' ) }}';

 /* The library is loaded asynchronously */
 var oa = document.createElement('script');
 oa.type = 'text/javascript'; oa.async = true;
 oa.src = '//' + oneall_subdomain + '.api.oneall.com/socialize/library.js';
 var s = document.getElementsByTagName('script')[0];
 s.parentNode.insertBefore(oa, s);

</script>

<div id="oa_social_link_container"></div>

You will also need to set some parameters specifying the callback for the account connection request:

<script type="text/javascript"> 

  /* Replace #get_config( 'oneall_callback_script' )# with the url to your own callback script */
  // Example: https://www.mydomain.com/OneALL/one_all_callback_handler
  var your_callback_script = '{{ get_config( 'oneall_callback_script' ) }}'; 

  /* Dynamically add the user_token of the currently logged in user. */
  /* Leave the field blank in case the user has no user_token yet. */
  // ** \OneAllHelper::get_user_token_for_user_id( $userID );
  var user_token = '{{ oneall_token }}';

  /* Embeds the buttons into the oa_social_link_container */
  var _oneall = _oneall || [];
  _oneall.push(['social_link', 'set_providers', ['twitter', 'facebook', 'linkedin']]);
  _oneall.push(['social_link', 'set_callback_uri', your_callback_script]);
  _oneall.push(['social_link', 'set_user_token', user_token]);
  _oneall.push(['social_link', 'do_render_ui', 'oa_social_link_container']);

</script>

With all this set, whenever a user clicks the widget and connects the account, OneAll is going to send the user back to the callback script URL you have specified, passing some information within the request. Here's some sample PHP code showing how to handle the callback request and store the user connection information in your database. Later, you can use this information to create requests with the OneAll API so you can, for example, share content on their social networks automatically.

<?php

class OneAllHelper
{
    public static function get_user_id_for_user_token( $user_token )
    {
        // Example Query: SELECT user_id FROM user_token_link WHERE user_token = <user_token>
        // Return the user_id or null if none found.
        $db = eZDB::instance();
        $user_id = $db->escapeString( $user_token );
        $rows = $db->arrayQuery( "SELECT user_id FROM user_token_link WHERE user_token = \"{$user_token}\"" );
        foreach( $rows as $row )
        {
            return $row['user_id'];
        }
    }

    public static function get_user_token_for_user_id( $user_id )
    {

        // Example Query: SELECT user_token FROM user_token_link WHERE user_id = <user_id>
        // Return the user_token or null if none found.
        $db = eZDB::instance();
        $user_id = $db->escapeString( $user_id );
        $rows = $db->arrayQuery( "SELECT user_token FROM user_token_link WHERE user_id = \"{$user_id}\"" );
        foreach( $rows as $row )
        {
            return $row['user_token'];
        }
    }

    public static function link_user_token_to_user_id( $user_token, $user_id )
    {
        // Example: INSERT INTO user_token_link SET user_token = <user_token>, user_id = <user_id>
        // Return true
        $db = eZDB::instance();
        $user_token = $db->escapeString( $user_token );
        $user_id = $db->escapeString( $user_id );
        $db->query( "DELETE FROM user_token_link where user_id = \"{$user_id}\"" );
        $db->query( "INSERT INTO user_token_link SET user_token = \"{$user_token}\", user_id = \"{$user_id}\"" );
        return true;
    }
    public static function process_link_callback( $user_id, $connection_token )
    {
        $ini = eZINI::instance('oneall.ini');
        // Your Site Settings
        $site_subdomain = $ini->variable('Settings', 'site_subdomain' );
        $site_public_key = $ini->variable('Settings', 'site_public_key' );
        $site_private_key = $ini->variable('Settings', 'site_private_key' );

        // API Access domain
        $site_domain = $site_subdomain.'.api.oneall.com';

        $user_token = false;

        //Connection Resource
        //http://docs.oneall.com/api/resources/connections/read-connection-details/
        $resource_uri = 'https://'.$site_domain.'/connections/'.$connection_token .'.json';

        //Setup connection
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $resource_uri);
        curl_setopt($curl, CURLOPT_HEADER, 0);
        curl_setopt($curl, CURLOPT_USERPWD, $site_public_key . ":" . $site_private_key);
        curl_setopt($curl, CURLOPT_TIMEOUT, 15);
        curl_setopt($curl, CURLOPT_VERBOSE, 0);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 1);
        curl_setopt($curl, CURLOPT_FAILONERROR, 0);

        //Send request
        $result_json = curl_exec($curl);

        //Error
        if ($result_json === false)
        {
          eZLog::write("Error when trying to get the json result for user {$user_id}\n" . curl_error($curl) . "\n" . curl_getinfo($curl), 'oneall.log' );
          curl_close($curl);
        }
        //Success
        else
        {
          //Close connection
          curl_close($curl);

          //Decode
          $json = json_decode ($result_json);

          //Extract data
          $data = $json->response->result->data;

          //Check for plugin
          if ($data->plugin->key == 'social_link')
          {
            //Operation successful
            if ($data->plugin->data->status == 'success')
            {
                //Identity linked
                if (isset( $data->user ) && isset( $data->user->user_token ) )
                {
                    $user_token = $data->user->user_token;
                    //$identity_token = $data->user->identity->identity_token;
                    OneAllHelper::link_user_token_to_user_id($user_token, $user_id);
                }
            }
          }
        }

        $token_id = OneAllHelper::get_user_token_for_user_id($user_id);
        // 1a) If the userID is empty then this is the first time that this user 
        // has connected with a social network account on your website
        if ($token_id === null)
        {
          OneAllHelper::link_user_token_to_user_id($user_token, $user_id);
        }
        
        $connected_networks = array_unique( \OneAllHelper::get_user_networks( $user_id ) );
        if( count( $connected_networks ) > 1 )
        {
            $userObj = eZContentObject::fetch($user_id);
            $attributes = array(
                    'socially_conscious_doctor' => true
            );
            ContentClass_Handler::update( $attributes, $userObj, true );
            eZContentCacheManager::clearContentCacheIfNeeded( $user_id );
        }
    }
    public static function get_user_networks( $user_id )
    {
        $user_token = self::get_user_token_for_user_id( $user_id );
        if( !$user_token )
        {
            return [];
        }
        $ini = eZINI::instance('oneall.ini');
        // Your Site Settings
        $site_subdomain = $ini->variable('Settings', 'site_subdomain' );
        $site_public_key = $ini->variable('Settings', 'site_public_key' );
        $site_private_key = $ini->variable('Settings', 'site_private_key' );

        // API Access domain
        $site_domain = $site_subdomain.'.api.oneall.com';

        //Connection Resource
        //http://docs.oneall.com/api/resources/connections/read-connection-details/
        $resource_uri = 'https://'.$site_domain.'/users/'.$user_token .'.json';

        //Setup connection
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $resource_uri);
        curl_setopt($curl, CURLOPT_HEADER, 0);
        curl_setopt($curl, CURLOPT_USERPWD, $site_public_key . ":" . $site_private_key);
        curl_setopt($curl, CURLOPT_TIMEOUT, 15);
        curl_setopt($curl, CURLOPT_VERBOSE, 0);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 1);
        curl_setopt($curl, CURLOPT_FAILONERROR, 0);

        //Send request
        $result_json = curl_exec($curl);

        //Error
        if ($result_json === false)
        {
          return [];
        }
        //Success
        else
        {
          //Close connection
          curl_close($curl);

          //Decode
          $json = json_decode ($result_json);
          $networks = [];
          //Extract data
          $identities = $json->response->result->data->user->identities;

          foreach( $identities as $identity )
          {
              $networks[] = $identity->provider;
          }
          return $networks;
          
        }
    }
    public static function get_user_identities( $user_id )
    {
        $iniOneAll = eZINI::instance('oneall.ini');
        // Your Site Settings
        $site_subdomain = $iniOneAll->variable('Settings', 'site_subdomain' );
        $site_domain = $site_subdomain.'.api.oneall.com';
        $site_public_key = $iniOneAll->variable('Settings', 'site_public_key' );
        $site_private_key = $iniOneAll->variable('Settings', 'site_private_key' );
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, 'https://'.$site_domain.'/users/'. OneAllHelper::get_user_token_for_user_id( $user_id ) . '.json');
        curl_setopt($curl, CURLOPT_HEADER, 0);
        curl_setopt($curl, CURLOPT_USERPWD, $site_public_key . ":" . $site_private_key);
        curl_setopt($curl, CURLOPT_TIMEOUT, 15);
        curl_setopt($curl, CURLOPT_VERBOSE, 0);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 1);
        curl_setopt($curl, CURLOPT_FAILONERROR, 0);
        $result = json_decode( curl_exec($curl), true );
        $data = [];
        if( $result['response']['request']['status']['code'] == 200 )
        {
            foreach( $result['response']['result']['data']['user']['identities'] as $identity  )
            {
                $data[$identity['provider']] = $identity['identity_token'];
            }
        }
        return $data;
    }
    public static function push_identity( $apiData, $network, $identity_token )
    {
        $iniOneAll = eZINI::instance('oneall.ini');
        // Your Site Settings
        $site_subdomain = $iniOneAll->variable('Settings', 'site_subdomain' );
        $site_domain = $site_subdomain.'.api.oneall.com';
        $site_public_key = $iniOneAll->variable('Settings', 'site_public_key' );
        $site_private_key = $iniOneAll->variable('Settings', 'site_private_key' );
        //Setup connection
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, 'https://'.$site_domain.'/push/identities/'. $identity_token .'/' . $network . '/post.json');
        curl_setopt($curl, CURLOPT_HEADER, 0);
        curl_setopt($curl, CURLOPT_USERPWD, $site_public_key . ":" . $site_private_key);
        curl_setopt($curl, CURLOPT_TIMEOUT, 15);
        curl_setopt($curl, CURLOPT_VERBOSE, 0);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 1);
        curl_setopt($curl, CURLOPT_FAILONERROR, 0);
        curl_setopt($curl, CURLOPT_POST, 1);
        curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode( $apiData ));

        //Send request
        return curl_exec($curl);
    }
    public static function push_twitter_pic( $apiData, $identity_token )
    {
        $iniOneAll = eZINI::instance('oneall.ini');
        // Your Site Settings
        $site_subdomain = $iniOneAll->variable('Settings', 'site_subdomain' );
        $site_domain = $site_subdomain.'.api.oneall.com';
        $site_public_key = $iniOneAll->variable('Settings', 'site_public_key' );
        $site_private_key = $iniOneAll->variable('Settings', 'site_private_key' );
        //Setup connection
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, 'https://'.$site_domain.'/push/identities/'. $identity_token .'/twitter/picture.json');
        curl_setopt($curl, CURLOPT_HEADER, 0);
        curl_setopt($curl, CURLOPT_USERPWD, $site_public_key . ":" . $site_private_key);
        curl_setopt($curl, CURLOPT_TIMEOUT, 15);
        curl_setopt($curl, CURLOPT_VERBOSE, 0);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 1);
        curl_setopt($curl, CURLOPT_FAILONERROR, 0);
        curl_setopt($curl, CURLOPT_POST, 1);
        curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode( $apiData ));

        //Send request
        $result = json_decode( curl_exec($curl), true );
        if( $result['response']['request']['status']['code'] == 200 )
        {
            return $result['response']['result']['data']['picture_id'];
        }
        return false;
    }

}

In our case, whenever a user submits a post for review, they can mark the content to be shared on their social networks after being approved and published.

OneAll: marking content to be shared

After an editor reviews the content and publishes it, we call PHP code requesting OneAll to share the content to the desired social media channels for the user who created it. 

// oneall network post
if( in_array( $object->attribute( 'class_identifier' ), ['expert_post', 'patient_post'] ) && in_array( $publishedStateID, $stateArray ) )
{
    $brontoSOAP = false;
    if( $object->attribute( 'class_identifier' ) == 'patient_post' )
    {
        $brontoSOAP = new BrontoSOAP( BrontoSOAP::PATIENT_ACCOUNT );
    }
    else
    {
        $brontoSOAP = new BrontoSOAP();
    }
    $baseURL = $ini->variable( 'Site', 'URL' );
    $node = $object->attribute('main_node');
    $nodeDataMap = $node->attribute('data_map');
    $dataMap = $node->attribute('parent')->attribute('data_map');
    $link = $node->attribute('url_alias');
    eZURI::transformURI($link);
    $user_id = $dataMap['author']->attribute('data_int');
    $connected_networks = array_unique( \OneAllHelper::get_user_networks( $user_id ) );
    $updateShareCount = false;
    $identityMap = OneAllHelper::get_user_identities( $user_id );
    foreach( ['twitter', 'facebook', 'linkedin'] as $network )
    {
        if( !in_array( $network, $connected_networks ) )
        {
            continue;
        }
        if( $nodeDataMap['social_account_' . $network]->attribute('content') )
        {
            $apiData = [
                "request"=> [
                  "push"=> [
                    "post"=> [
                      "link" => $baseURL . $link
                      , "message" => $object->attribute( 'name' ) . ' ' . $baseURL . $link
                    ]
                  ]
                ]
              ];
            if( in_array( $network, ['twitter', 'linkedin'] ) )
            {
                $imageURL = false;
                if( $nodeDataMap['image']->attribute('has_content') )
                {
                    $imageAlias = $nodeDataMap['image']->attribute('content');
                    $brontImage = $imageAlias->attribute('bronto_thumb');
                    $imageURL = $ini->variable( 'Site', 'URL' ) . '/' . $brontImage['full_path'];
                }
                else
                {
                    $imageObject = eZContentObject::fetch( $ini->variable( 'WorkflowSettings', 'default_image_id' ) );
                    $imageDataMap = $imageObject->attribute('data_map');
                    if( $imageDataMap['image']->attribute('has_content') )
                    {
                        $imageAlias = $imageDataMap['image']->attribute('content');
                        $brontImage = $imageAlias->attribute('bronto_thumb');
                        $imageURL = $ini->variable( 'Site', 'URL' ) . '/' . $brontImage['full_path'];
                    }
                }
                if( $imageURL  && $network == 'linkedin' )
                {
                    $apiData["request"]["push"]["post"]["picture"] = $imageURL;
                }
                else if( $imageURL  && $network == 'twitter' )
                {
                    $pictureData = [
                        "request"=> [
                          "push"=> [
                            "picture"=> [
                              "description" => $object->attribute( 'name' ) . ' ' . $baseURL . $link
                              , "url" => $imageURL
                            ]
                          ]
                        ]
                      ];
                    $pictureID = OneAllHelper::push_twitter_pic( $pictureData, $identityMap[$network] );
                    if( $pictureID )
                    {
                        $apiData["request"]["push"]["post"]["attachments"] = [ $pictureID ];
                    }
                }
            }
            $updateShareCount = true;
            eZLog::write( json_encode( $apiData ), 'oneall.log' );
            $result_json = OneAllHelper::push_identity( $apiData, $network, $identityMap[$network] );
            eZLog::write( json_encode( $result_json ), 'oneall.log');
        }
    }
}

You can see in the picture below a sample post on one of the social media channels:

A post on one of the social media channels

This is how we can share content automatically on a variety of social networks for numerous users. Please note that the sample code has been developed for eZ Platform / eZ Publish, but it should be straightforward to adapt to any platform. Feel free to contact us for more information.

 

loading form....
Thanks for reaching out. We will get back to you right away.